@vibeframe/cli 0.113.10 → 0.113.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4756,15 +4756,15 @@ async function loadKokoroRuntime() {
4756
4756
  }
4757
4757
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
4758
4758
  try {
4759
- const { readFile: readFile45 } = await import("node:fs/promises");
4760
- const { join: join27, resolve: resolve66 } = await import("node:path");
4759
+ const { readFile: readFile46 } = await import("node:fs/promises");
4760
+ const { join: join28, resolve: resolve66 } = await import("node:path");
4761
4761
  const { pathToFileURL } = await import("node:url");
4762
4762
  const pkgDir = resolve66(baseDir, "node_modules", "kokoro-js");
4763
- const pkg = JSON.parse(await readFile45(join27(pkgDir, "package.json"), "utf-8"));
4763
+ const pkg = JSON.parse(await readFile46(join28(pkgDir, "package.json"), "utf-8"));
4764
4764
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
4765
4765
  if (!entry)
4766
4766
  return null;
4767
- const mod = await import(pathToFileURL(join27(pkgDir, entry)).href);
4767
+ const mod = await import(pathToFileURL(join28(pkgDir, entry)).href);
4768
4768
  return mod.KokoroTTS ?? null;
4769
4769
  } catch {
4770
4770
  return null;
@@ -4775,20 +4775,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
4775
4775
  if (!base)
4776
4776
  return null;
4777
4777
  try {
4778
- const { readFile: readFile45, writeFile: writeFile51, mkdir: mkdir26 } = await import("node:fs/promises");
4779
- const { join: join27, resolve: resolve66 } = await import("node:path");
4778
+ const { readFile: readFile46, writeFile: writeFile51, mkdir: mkdir26 } = await import("node:fs/promises");
4779
+ const { join: join28, resolve: resolve66 } = await import("node:path");
4780
4780
  const { pathToFileURL } = await import("node:url");
4781
4781
  const { homedir: homedir6 } = await import("node:os");
4782
4782
  const transformersDir = resolve66(base, "node_modules", "@huggingface", "transformers");
4783
4783
  const ortDistDir = resolve66(base, "node_modules", "onnxruntime-web", "dist");
4784
- const transformers = await import(pathToFileURL(join27(transformersDir, "dist", "transformers.web.js")).href);
4785
- const cacheRoot = join27(homedir6(), ".cache", "vibeframe", "models");
4784
+ const transformers = await import(pathToFileURL(join28(transformersDir, "dist", "transformers.web.js")).href);
4785
+ const cacheRoot = join28(homedir6(), ".cache", "vibeframe", "models");
4786
4786
  transformers.env.allowLocalModels = false;
4787
4787
  transformers.env.useCustomCache = true;
4788
4788
  transformers.env.customCache = {
4789
4789
  async match(key) {
4790
4790
  try {
4791
- return new Response(await readFile45(join27(cacheRoot, encodeURIComponent(String(key)))));
4791
+ return new Response(await readFile46(join28(cacheRoot, encodeURIComponent(String(key)))));
4792
4792
  } catch {
4793
4793
  return void 0;
4794
4794
  }
@@ -4797,7 +4797,7 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
4797
4797
  try {
4798
4798
  const buf = Buffer.from(await response.arrayBuffer());
4799
4799
  await mkdir26(cacheRoot, { recursive: true });
4800
- await writeFile51(join27(cacheRoot, encodeURIComponent(String(key))), buf);
4800
+ await writeFile51(join28(cacheRoot, encodeURIComponent(String(key))), buf);
4801
4801
  } catch {
4802
4802
  }
4803
4803
  }
@@ -31196,10 +31196,57 @@ var init_composer_resolve = __esm({
31196
31196
  }
31197
31197
  });
31198
31198
 
31199
+ // src/commands/_shared/transcribe-narration.ts
31200
+ import { existsSync as existsSync9 } from "node:fs";
31201
+ import { readFile as readFile9 } from "node:fs/promises";
31202
+ import { join as join9 } from "node:path";
31203
+ async function transcribeNarrationWords(audioAbsPath, opts) {
31204
+ try {
31205
+ const whisper = new WhisperProvider();
31206
+ await whisper.initialize({ apiKey: opts.apiKey });
31207
+ const audioBytes = await readFile9(audioAbsPath);
31208
+ const audioBlob = new Blob([new Uint8Array(audioBytes)]);
31209
+ const transcript = await whisper.transcribe(audioBlob, void 0, {
31210
+ granularity: "word",
31211
+ language: opts.language
31212
+ });
31213
+ if (transcript.status === "completed" && transcript.words?.length) {
31214
+ return transcript.words.map((w) => ({ text: w.text, start: w.start, end: w.end }));
31215
+ }
31216
+ return [];
31217
+ } catch {
31218
+ return [];
31219
+ }
31220
+ }
31221
+ function beatTranscriptRelPath(beatId) {
31222
+ return `assets/transcript-${beatId}.json`;
31223
+ }
31224
+ async function readBeatTranscript(projectDir, beatId) {
31225
+ const abs = join9(projectDir, beatTranscriptRelPath(beatId));
31226
+ if (!existsSync9(abs)) return void 0;
31227
+ try {
31228
+ const parsed = JSON.parse(await readFile9(abs, "utf-8"));
31229
+ if (!Array.isArray(parsed)) return void 0;
31230
+ const words = parsed.filter(
31231
+ (w) => typeof w === "object" && w !== null && typeof w.text === "string" && Number.isFinite(w.start) && Number.isFinite(w.end)
31232
+ ).map((w) => ({ text: w.text, start: w.start, end: w.end }));
31233
+ return words.length > 0 ? words : void 0;
31234
+ } catch {
31235
+ return void 0;
31236
+ }
31237
+ }
31238
+ var init_transcribe_narration = __esm({
31239
+ "src/commands/_shared/transcribe-narration.ts"() {
31240
+ "use strict";
31241
+ init_dist2();
31242
+ }
31243
+ });
31244
+
31199
31245
  // src/commands/_shared/compose-scenes-skills.ts
31200
31246
  var compose_scenes_skills_exports = {};
31201
31247
  __export(compose_scenes_skills_exports, {
31202
31248
  ComposeBeatError: () => ComposeBeatError,
31249
+ TRANSCRIPT_PROMPT_MAX_WORDS: () => TRANSCRIPT_PROMPT_MAX_WORDS,
31203
31250
  buildSystemPrompt: () => buildSystemPrompt,
31204
31251
  buildUserPrompt: () => buildUserPrompt,
31205
31252
  composeBeatHtml: () => composeBeatHtml,
@@ -31209,15 +31256,16 @@ __export(compose_scenes_skills_exports, {
31209
31256
  executeComposeScenesWithSkills: () => executeComposeScenesWithSkills,
31210
31257
  extractHtml: () => extractHtml,
31211
31258
  formatLintFeedback: () => formatLintFeedback,
31259
+ formatTranscriptSection: () => formatTranscriptSection,
31212
31260
  lintBeatHtml: () => lintBeatHtml
31213
31261
  });
31214
31262
  import Anthropic from "@anthropic-ai/sdk";
31215
31263
  import OpenAI from "openai";
31216
31264
  import { GoogleGenerativeAI } from "@google/generative-ai";
31217
31265
  import { createHash as createHash4 } from "node:crypto";
31218
- import { existsSync as existsSync9, mkdirSync } from "node:fs";
31219
- import { mkdir as mkdir6, readFile as readFile9, writeFile as writeFile12 } from "node:fs/promises";
31220
- import { dirname as dirname5, join as join9, resolve as resolve12 } from "node:path";
31266
+ import { existsSync as existsSync10, mkdirSync } from "node:fs";
31267
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile12 } from "node:fs/promises";
31268
+ import { dirname as dirname5, join as join10, resolve as resolve12 } from "node:path";
31221
31269
  import { runHyperframeLint as runHyperframeLint2 } from "@hyperframes/producer";
31222
31270
  function buildSystemPrompt(ctx) {
31223
31271
  return `You are a Hyperframes composition author. The skill content below
@@ -31251,6 +31299,46 @@ function formatBeatCues(cues) {
31251
31299
  }
31252
31300
  return lines.join("\n") + "\n";
31253
31301
  }
31302
+ function formatTranscriptSection(transcript, maxWords = TRANSCRIPT_PROMPT_MAX_WORDS) {
31303
+ if (!transcript || transcript.length === 0) return "";
31304
+ const round = (n) => Number(Math.max(0, n).toFixed(2));
31305
+ const noAudioRule = "This is for VISUAL sync only \u2014 do NOT add an `<audio>` tag or invent SFX; the narration audio is wired separately by the root timeline. If this beat is visual-only (no on-screen text), you may ignore these timings.";
31306
+ if (transcript.length <= maxWords) {
31307
+ const pairs = transcript.map((w) => `[${round(w.start)}, ${JSON.stringify(w.text)}]`).join(", ");
31308
+ return `
31309
+
31310
+ === Narration word timings (seconds) ===
31311
+
31312
+ Whisper word-level start times for this beat's narration. If you author captions
31313
+ or kinetic typography, reveal each word at its start time with absolute-time
31314
+ GSAP tweens, e.g.
31315
+ \`tl.fromTo('.caption .word[data-i="0"]', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18 }, ${round(
31316
+ transcript[0].start
31317
+ )});\`
31318
+ ${noAudioRule}
31319
+
31320
+ word starts (word-level): ${pairs}`;
31321
+ }
31322
+ const phraseCount = Math.max(1, Math.floor(maxWords / 2));
31323
+ const chunkSize = Math.ceil(transcript.length / phraseCount);
31324
+ const anchors = [];
31325
+ for (let i = 0; i < transcript.length; i += chunkSize) {
31326
+ const chunk = transcript.slice(i, i + chunkSize);
31327
+ const text = chunk.map((w) => w.text).join(" ");
31328
+ anchors.push(`[${round(chunk[0].start)}, ${JSON.stringify(text)}]`);
31329
+ }
31330
+ return `
31331
+
31332
+ === Narration timings (approximate, phrase-level) ===
31333
+
31334
+ This beat's narration is long (${transcript.length} words), so timings are
31335
+ grouped into phrase-level anchors (start time of each phrase). Use them to PACE
31336
+ caption / kinetic-type reveals across the beat \u2014 they are approximate, not
31337
+ per-word truth.
31338
+ ${noAudioRule}
31339
+
31340
+ phrase starts (approximate): ${anchors.join(", ")}`;
31341
+ }
31254
31342
  function buildUserPrompt(ctx) {
31255
31343
  const compositionId = `scene-${ctx.beat.id}`;
31256
31344
  const durLiteral = ctx.finalDurationSec !== void 0 ? String(ctx.finalDurationSec) : void 0;
@@ -31413,7 +31501,7 @@ ${ctx.storyboardGlobal || "(no global direction)"}
31413
31501
 
31414
31502
  ## ${ctx.beat.heading}
31415
31503
  ${formatBeatCues(ctx.beat.cues)}${cueDurationNote}
31416
- ${ctx.beat.body}
31504
+ ${ctx.beat.body}${formatTranscriptSection(ctx.transcript)}
31417
31505
 
31418
31506
  === Output format ===
31419
31507
 
@@ -31433,7 +31521,7 @@ function computeCacheKey(parts) {
31433
31521
  return createHash4("sha256").update(parts.provider).update("\u241E").update(parts.model).update("\u241E").update(parts.systemPrompt).update("\u241E").update(parts.userPrompt).digest("hex");
31434
31522
  }
31435
31523
  function defaultCacheDir() {
31436
- return join9(USER_CACHE_DIR, "compose-scenes");
31524
+ return join10(USER_CACHE_DIR, "compose-scenes");
31437
31525
  }
31438
31526
  function extractHtml(responseText) {
31439
31527
  const fenced = responseText.match(/```html\s*\n([\s\S]*?)\n```/);
@@ -31457,9 +31545,9 @@ async function composeBeatHtml(ctx, overrides) {
31457
31545
  model: settings.model
31458
31546
  });
31459
31547
  const cacheDir = ctx.cacheDir ?? defaultCacheDir();
31460
- const cachePath = join9(cacheDir, `${cacheKey}.html`);
31461
- if (existsSync9(cachePath)) {
31462
- const html2 = await readFile9(cachePath, "utf-8");
31548
+ const cachePath = join10(cacheDir, `${cacheKey}.html`);
31549
+ if (existsSync10(cachePath)) {
31550
+ const html2 = await readFile10(cachePath, "utf-8");
31463
31551
  return { html: html2, cached: true, cacheKey, model: settings.model, provider: ctx.provider };
31464
31552
  }
31465
31553
  if (!ctx.apiKey) {
@@ -31595,14 +31683,14 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31595
31683
  const projectRoot = params.project ? resolve12(outputDir, params.project) : resolve12(outputDir);
31596
31684
  const designPath = resolve12(projectRoot, params.design ?? "DESIGN.md");
31597
31685
  const storyboardPath = resolve12(projectRoot, params.storyboard ?? "STORYBOARD.md");
31598
- if (!existsSync9(designPath)) {
31686
+ if (!existsSync10(designPath)) {
31599
31687
  return { success: false, error: `DESIGN.md not found at ${designPath}` };
31600
31688
  }
31601
- if (!existsSync9(storyboardPath)) {
31689
+ if (!existsSync10(storyboardPath)) {
31602
31690
  return { success: false, error: `STORYBOARD.md not found at ${storyboardPath}` };
31603
31691
  }
31604
- const designMd = await readFile9(designPath, "utf-8");
31605
- const storyboardMd = await readFile9(storyboardPath, "utf-8");
31692
+ const designMd = await readFile10(designPath, "utf-8");
31693
+ const storyboardMd = await readFile10(storyboardPath, "utf-8");
31606
31694
  const { global: storyboardGlobal, beats } = parseStoryboard(storyboardMd);
31607
31695
  if (beats.length === 0) {
31608
31696
  return {
@@ -31622,7 +31710,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31622
31710
  provider = params.composer ?? "claude";
31623
31711
  apiKey = "";
31624
31712
  }
31625
- const compositionsDir = join9(projectRoot, "compositions");
31713
+ const compositionsDir = join10(projectRoot, "compositions");
31626
31714
  await mkdir6(compositionsDir, { recursive: true });
31627
31715
  let finalDurations = /* @__PURE__ */ new Map();
31628
31716
  try {
@@ -31636,6 +31724,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31636
31724
  async (beat, beatIndex) => {
31637
31725
  onProgress({ type: "beat-start", beatId: beat.id, beatIndex, totalBeats });
31638
31726
  try {
31727
+ const transcript = await readBeatTranscript(projectRoot, beat.id);
31639
31728
  const result = await composeBeatWithRetry(
31640
31729
  {
31641
31730
  beat,
@@ -31646,11 +31735,12 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31646
31735
  apiKey,
31647
31736
  effort: params.effort,
31648
31737
  cacheDir: params.cacheDir,
31649
- finalDurationSec: finalDurations.get(beat.id)
31738
+ finalDurationSec: finalDurations.get(beat.id),
31739
+ transcript
31650
31740
  },
31651
31741
  overrides
31652
31742
  );
31653
- const compositionPath = join9(compositionsDir, `scene-${beat.id}.html`);
31743
+ const compositionPath = join10(compositionsDir, `scene-${beat.id}.html`);
31654
31744
  await mkdir6(dirname5(compositionPath), { recursive: true });
31655
31745
  await writeFile12(compositionPath, result.html, "utf-8");
31656
31746
  if (result.cached) {
@@ -31729,7 +31819,7 @@ ${failures.map((f) => ` - ${f.beatId}: ${f.error}`).join("\n")}`,
31729
31819
  data: aggregateData
31730
31820
  };
31731
31821
  }
31732
- var MODEL_SETTINGS_BY_PROVIDER, ComposeBeatError, defaultCallLLM, CONFIG_PROVIDER_BY_COMPOSER;
31822
+ var MODEL_SETTINGS_BY_PROVIDER, TRANSCRIPT_PROMPT_MAX_WORDS, ComposeBeatError, defaultCallLLM, CONFIG_PROVIDER_BY_COMPOSER;
31733
31823
  var init_compose_scenes_skills = __esm({
31734
31824
  "src/commands/_shared/compose-scenes-skills.ts"() {
31735
31825
  "use strict";
@@ -31741,6 +31831,7 @@ var init_compose_scenes_skills = __esm({
31741
31831
  init_root_sync();
31742
31832
  init_composer_resolve();
31743
31833
  init_config();
31834
+ init_transcribe_narration();
31744
31835
  MODEL_SETTINGS_BY_PROVIDER = {
31745
31836
  // Anthropic Claude Sonnet 4.6 — v0.69 baseline. ~9 s/beat, $3/$15 per MTok.
31746
31837
  claude: {
@@ -31764,6 +31855,7 @@ var init_compose_scenes_skills = __esm({
31764
31855
  high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
31765
31856
  }
31766
31857
  };
31858
+ TRANSCRIPT_PROMPT_MAX_WORDS = 120;
31767
31859
  ComposeBeatError = class extends Error {
31768
31860
  constructor(code, message, cause) {
31769
31861
  super(message);
@@ -31849,22 +31941,22 @@ var init_compose_scenes_skills = __esm({
31849
31941
  });
31850
31942
 
31851
31943
  // src/commands/_shared/compose-prompts.ts
31852
- import { existsSync as existsSync10 } from "node:fs";
31853
- import { readFile as readFile10 } from "node:fs/promises";
31854
- import { join as join10, relative as relative4, resolve as resolve13 } from "node:path";
31944
+ import { existsSync as existsSync11 } from "node:fs";
31945
+ import { readFile as readFile11 } from "node:fs/promises";
31946
+ import { join as join11, relative as relative4, resolve as resolve13 } from "node:path";
31855
31947
  async function getComposePrompts(opts) {
31856
31948
  const projectDir = resolve13(opts.projectDir);
31857
- const designPath = join10(projectDir, "DESIGN.md");
31858
- const storyboardPath = join10(projectDir, "STORYBOARD.md");
31859
- const skillPath = join10(projectDir, "SKILL.md");
31860
- const compositionsDir = join10(projectDir, "compositions");
31949
+ const designPath = join11(projectDir, "DESIGN.md");
31950
+ const storyboardPath = join11(projectDir, "STORYBOARD.md");
31951
+ const skillPath = join11(projectDir, "SKILL.md");
31952
+ const compositionsDir = join11(projectDir, "compositions");
31861
31953
  const warnings = [];
31862
31954
  const baseError = (msg) => ({
31863
31955
  success: false,
31864
31956
  projectDir,
31865
31957
  designReference: "DESIGN.md",
31866
31958
  storyboardReference: "STORYBOARD.md",
31867
- skillReference: existsSync10(skillPath) ? "SKILL.md" : null,
31959
+ skillReference: existsSync11(skillPath) ? "SKILL.md" : null,
31868
31960
  compositionsDir: "compositions",
31869
31961
  beats: [],
31870
31962
  instructions: [],
@@ -31872,18 +31964,18 @@ async function getComposePrompts(opts) {
31872
31964
  warnings,
31873
31965
  error: msg
31874
31966
  });
31875
- if (!existsSync10(designPath)) {
31967
+ if (!existsSync11(designPath)) {
31876
31968
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
31877
31969
  }
31878
- if (!existsSync10(storyboardPath)) {
31970
+ if (!existsSync11(storyboardPath)) {
31879
31971
  return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
31880
31972
  }
31881
- if (!existsSync10(skillPath)) {
31973
+ if (!existsSync11(skillPath)) {
31882
31974
  warnings.push(
31883
31975
  "SKILL.md not installed \u2014 host agent won't have Hyperframes rules in context. Run `vibe scene install-skill` to install it."
31884
31976
  );
31885
31977
  }
31886
- const storyboardMd = await readFile10(storyboardPath, "utf-8");
31978
+ const storyboardMd = await readFile11(storyboardPath, "utf-8");
31887
31979
  const parsed = parseStoryboard(storyboardMd);
31888
31980
  if (parsed.beats.length === 0) {
31889
31981
  return baseError(`STORYBOARD.md has no \`## Beat \u2026\` headings.`);
@@ -31905,28 +31997,33 @@ async function getComposePrompts(opts) {
31905
31997
  finalDurations = await resolveProjectBeatDurations(projectDir);
31906
31998
  } catch {
31907
31999
  }
31908
- const result = beats.map((beat) => {
31909
- const outputPathAbs = join10(compositionsDir, `scene-${beat.id}.html`);
31910
- const outputPathRel = relative4(projectDir, outputPathAbs);
31911
- const finalDurationSec = finalDurations.get(beat.id);
31912
- const userPrompt = buildUserPrompt({
31913
- beat,
31914
- storyboardGlobal: parsed.global,
31915
- finalDurationSec
31916
- });
31917
- return {
31918
- id: beat.id,
31919
- heading: beat.heading,
31920
- outputPath: outputPathRel,
31921
- duration: beat.duration,
31922
- finalDurationSec,
31923
- cues: beat.cues,
31924
- body: beat.body,
31925
- userPrompt,
31926
- exists: existsSync10(outputPathAbs)
31927
- };
31928
- });
31929
- const skillRef = existsSync10(skillPath) ? "SKILL.md" : null;
32000
+ const result = await Promise.all(
32001
+ beats.map(async (beat) => {
32002
+ const outputPathAbs = join11(compositionsDir, `scene-${beat.id}.html`);
32003
+ const outputPathRel = relative4(projectDir, outputPathAbs);
32004
+ const finalDurationSec = finalDurations.get(beat.id);
32005
+ const transcript = await readBeatTranscript(projectDir, beat.id);
32006
+ const userPrompt = buildUserPrompt({
32007
+ beat,
32008
+ storyboardGlobal: parsed.global,
32009
+ finalDurationSec,
32010
+ transcript
32011
+ });
32012
+ return {
32013
+ id: beat.id,
32014
+ heading: beat.heading,
32015
+ outputPath: outputPathRel,
32016
+ duration: beat.duration,
32017
+ finalDurationSec,
32018
+ cues: beat.cues,
32019
+ body: beat.body,
32020
+ userPrompt,
32021
+ transcriptPath: transcript ? beatTranscriptRelPath(beat.id) : void 0,
32022
+ exists: existsSync11(outputPathAbs)
32023
+ };
32024
+ })
32025
+ );
32026
+ const skillRef = existsSync11(skillPath) ? "SKILL.md" : null;
31930
32027
  const instructions = buildInstructions({
31931
32028
  skillRef,
31932
32029
  beatCount: result.length,
@@ -31974,13 +32071,14 @@ var init_compose_prompts = __esm({
31974
32071
  init_compose_scenes_skills();
31975
32072
  init_root_sync();
31976
32073
  init_bundle();
32074
+ init_transcribe_narration();
31977
32075
  }
31978
32076
  });
31979
32077
 
31980
32078
  // src/commands/_shared/sub-comp-duration-sync.ts
31981
- import { existsSync as existsSync12 } from "node:fs";
31982
- import { readFile as readFile12 } from "node:fs/promises";
31983
- import { join as join12, resolve as resolve15 } from "node:path";
32079
+ import { existsSync as existsSync13 } from "node:fs";
32080
+ import { readFile as readFile13 } from "node:fs/promises";
32081
+ import { join as join13, resolve as resolve15 } from "node:path";
31984
32082
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
31985
32083
  const compositionId = `scene-${beatId}`;
31986
32084
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -32072,15 +32170,15 @@ async function createSubCompDurationSyncPlans(opts) {
32072
32170
  const results = [];
32073
32171
  for (const beat of opts.beats) {
32074
32172
  const fileRel = `compositions/scene-${beat.id}.html`;
32075
- const filePath = join12(projectDir, fileRel);
32076
- if (!existsSync12(filePath)) continue;
32173
+ const filePath = join13(projectDir, fileRel);
32174
+ if (!existsSync13(filePath)) continue;
32077
32175
  const newDurationSec = await resolveSyncedBeatDuration({
32078
32176
  projectDir,
32079
32177
  beatDuration: beat.duration,
32080
32178
  narrationPath: beat.narrationPath,
32081
32179
  sceneDurationSec: beat.sceneDurationSec
32082
32180
  });
32083
- const html = await readFile12(filePath, "utf-8");
32181
+ const html = await readFile13(filePath, "utf-8");
32084
32182
  const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
32085
32183
  results.push({ ...outcome, file: fileRel, beatId: beat.id });
32086
32184
  }
@@ -32104,7 +32202,7 @@ var init_sub_comp_duration_sync = __esm({
32104
32202
  });
32105
32203
 
32106
32204
  // src/commands/_shared/scene-repair.ts
32107
- import { readFile as readFile13, writeFile as writeFile14 } from "node:fs/promises";
32205
+ import { readFile as readFile14, writeFile as writeFile14 } from "node:fs/promises";
32108
32206
  import { relative as relative5, resolve as resolve16 } from "node:path";
32109
32207
  import { runHyperframeLint as runHyperframeLint3 } from "@hyperframes/producer";
32110
32208
  async function executeSceneRepair(opts) {
@@ -32150,7 +32248,7 @@ async function executeSceneRepair(opts) {
32150
32248
  }
32151
32249
  for (const target of targets) {
32152
32250
  const rel = relative5(projectDir, target.abs) || target.abs;
32153
- const html = await readFile13(target.abs, "utf-8");
32251
+ const html = await readFile14(target.abs, "utf-8");
32154
32252
  const findings = lintHtml(html, rel, target.isSub);
32155
32253
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
32156
32254
  if (fixedCodes.length > 0) {
@@ -32522,15 +32620,15 @@ var init_storyboard_edit = __esm({
32522
32620
  });
32523
32621
 
32524
32622
  // src/pipeline/renderers/chrome.ts
32525
- import { existsSync as existsSync14 } from "node:fs";
32623
+ import { existsSync as existsSync15 } from "node:fs";
32526
32624
  import { homedir as homedir5 } from "node:os";
32527
- import { join as join13 } from "node:path";
32625
+ import { join as join14 } from "node:path";
32528
32626
  function findChrome() {
32529
32627
  const candidates = [
32530
32628
  process.env.HYPERFRAMES_CHROME_PATH,
32531
32629
  process.env.CHROME_PATH,
32532
32630
  // puppeteer auto-downloaded headless shell
32533
- join13(
32631
+ join14(
32534
32632
  homedir5(),
32535
32633
  ".cache",
32536
32634
  "puppeteer",
@@ -32548,7 +32646,7 @@ function findChrome() {
32548
32646
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
32549
32647
  ];
32550
32648
  for (const c of candidates) {
32551
- if (c && existsSync14(c)) return c;
32649
+ if (c && existsSync15(c)) return c;
32552
32650
  }
32553
32651
  return void 0;
32554
32652
  }
@@ -32580,7 +32678,7 @@ var init_ffmpeg_gate = __esm({
32580
32678
  });
32581
32679
 
32582
32680
  // src/commands/_shared/scene-audio-scan.ts
32583
- import { readFile as readFile15 } from "node:fs/promises";
32681
+ import { readFile as readFile16 } from "node:fs/promises";
32584
32682
  import { resolve as resolve18 } from "node:path";
32585
32683
  function parseRootClips(rootHtml) {
32586
32684
  const clips = [];
@@ -32621,7 +32719,7 @@ function makeFsCompositionReader(projectDir) {
32621
32719
  return async (compositionSrcRel) => {
32622
32720
  const abs = resolve18(projectDir, compositionSrcRel);
32623
32721
  try {
32624
- return await readFile15(abs, "utf-8");
32722
+ return await readFile16(abs, "utf-8");
32625
32723
  } catch {
32626
32724
  return null;
32627
32725
  }
@@ -32821,9 +32919,9 @@ __export(scene_render_exports, {
32821
32919
  qualityToCrf: () => qualityToCrf
32822
32920
  });
32823
32921
  import { execFile as execFile2 } from "node:child_process";
32824
- import { existsSync as existsSync15 } from "node:fs";
32825
- import { mkdir as mkdir9, readFile as readFile16, stat as stat3, writeFile as writeFile16 } from "node:fs/promises";
32826
- import { resolve as resolve20, relative as relative7, dirname as dirname8, basename as basename4, join as join14, isAbsolute } from "node:path";
32922
+ import { existsSync as existsSync16 } from "node:fs";
32923
+ import { mkdir as mkdir9, readFile as readFile17, stat as stat3, writeFile as writeFile16 } from "node:fs/promises";
32924
+ import { resolve as resolve20, relative as relative7, dirname as dirname8, basename as basename4, join as join15, isAbsolute } from "node:path";
32827
32925
  import { promisify as promisify2 } from "node:util";
32828
32926
  import {
32829
32927
  createRenderJob,
@@ -32915,7 +33013,7 @@ async function executeSceneRender(opts = {}) {
32915
33013
  }
32916
33014
  let autoSyncApplied = false;
32917
33015
  let autoSyncWarning;
32918
- if (!opts.beatId && existsSync15(join14(projectDir, "STORYBOARD.md"))) {
33016
+ if (!opts.beatId && existsSync16(join15(projectDir, "STORYBOARD.md"))) {
32919
33017
  try {
32920
33018
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
32921
33019
  const subPlans = await createSubCompDurationSyncPlans({
@@ -33007,7 +33105,7 @@ async function executeSceneRender(opts = {}) {
33007
33105
  let audioMuxWarning;
33008
33106
  try {
33009
33107
  opts.onProgress?.(0.95, "Mixing audio");
33010
- const rootHtml = await readFile16(resolve20(projectDir, root), "utf-8");
33108
+ const rootHtml = await readFile17(resolve20(projectDir, root), "utf-8");
33011
33109
  const audios = await scanSceneAudio({ projectDir, rootHtml });
33012
33110
  audioCount = audios.length;
33013
33111
  if (audios.length > 0) {
@@ -33092,11 +33190,11 @@ async function safeStat(p) {
33092
33190
  }
33093
33191
  async function prepareBeatRenderRoot(opts) {
33094
33192
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
33095
- const reportPath = join14(opts.projectDir, "build-report.json");
33193
+ const reportPath = join15(opts.projectDir, "build-report.json");
33096
33194
  const retryWith = [
33097
33195
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
33098
33196
  ];
33099
- if (!existsSync15(reportPath)) {
33197
+ if (!existsSync16(reportPath)) {
33100
33198
  return {
33101
33199
  success: false,
33102
33200
  root,
@@ -33107,7 +33205,7 @@ async function prepareBeatRenderRoot(opts) {
33107
33205
  }
33108
33206
  let report;
33109
33207
  try {
33110
- report = JSON.parse(await readFile16(reportPath, "utf-8"));
33208
+ report = JSON.parse(await readFile17(reportPath, "utf-8"));
33111
33209
  } catch (error) {
33112
33210
  return {
33113
33211
  success: false,
@@ -33176,8 +33274,8 @@ async function prepareBeatRenderRoot(opts) {
33176
33274
  durationSec,
33177
33275
  narrationPath
33178
33276
  });
33179
- await mkdir9(dirname8(join14(opts.projectDir, root)), { recursive: true });
33180
- await writeFile16(join14(opts.projectDir, root), html, "utf-8");
33277
+ await mkdir9(dirname8(join15(opts.projectDir, root)), { recursive: true });
33278
+ await writeFile16(join15(opts.projectDir, root), html, "utf-8");
33181
33279
  return {
33182
33280
  success: true,
33183
33281
  root,
@@ -33241,7 +33339,7 @@ function reportedBeatAssetPaths(beat) {
33241
33339
  ]);
33242
33340
  }
33243
33341
  async function writeRenderReport(projectDir, result) {
33244
- const reportPath = join14(projectDir, "render-report.json");
33342
+ const reportPath = join15(projectDir, "render-report.json");
33245
33343
  try {
33246
33344
  await writeFile16(
33247
33345
  reportPath,
@@ -33283,7 +33381,7 @@ async function writeRenderReport(projectDir, result) {
33283
33381
  }
33284
33382
  function pathExists3(projectDir, value) {
33285
33383
  const abs = isAbsolute(value) ? value : resolve20(projectDir, value);
33286
- return existsSync15(abs);
33384
+ return existsSync16(abs);
33287
33385
  }
33288
33386
  function stringValue(value) {
33289
33387
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -33356,7 +33454,7 @@ var init_concurrency = __esm({
33356
33454
  });
33357
33455
 
33358
33456
  // src/commands/_shared/build-asset-reference.ts
33359
- import { existsSync as existsSync16 } from "node:fs";
33457
+ import { existsSync as existsSync17 } from "node:fs";
33360
33458
  import { extname as extname3, isAbsolute as isAbsolute2, relative as relative8, resolve as resolve21, sep } from "node:path";
33361
33459
  function resolveTypedAssetReference(projectDir, kind, value) {
33362
33460
  const raw = stringOrUndefined2(value);
@@ -33387,7 +33485,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
33387
33485
  const absPath = isAbsolute2(raw) ? resolve21(raw) : resolve21(projectDir, raw);
33388
33486
  const rel = relative8(resolve21(projectDir), absPath);
33389
33487
  const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute2(rel);
33390
- const exists = withinProject && existsSync16(absPath);
33488
+ const exists = withinProject && existsSync17(absPath);
33391
33489
  let error = forcedError;
33392
33490
  if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
33393
33491
  error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
@@ -33574,9 +33672,9 @@ var init_build_backdrop_prompt = __esm({
33574
33672
  });
33575
33673
 
33576
33674
  // src/commands/_shared/build-plan.ts
33577
- import { existsSync as existsSync17 } from "node:fs";
33578
- import { readFile as readFile17 } from "node:fs/promises";
33579
- import { dirname as dirname9, join as join15, resolve as resolve22 } from "node:path";
33675
+ import { existsSync as existsSync18 } from "node:fs";
33676
+ import { readFile as readFile18 } from "node:fs/promises";
33677
+ import { dirname as dirname9, join as join16, resolve as resolve22 } from "node:path";
33580
33678
  function backdropCostUsd(quality) {
33581
33679
  return quality === "hd" ? 0.2 : 0.05;
33582
33680
  }
@@ -33585,11 +33683,11 @@ async function createBuildPlan(opts) {
33585
33683
  loadPlanEnv(projectDir);
33586
33684
  const stage = opts.stage ?? "all";
33587
33685
  const config2 = await readProjectConfig(projectDir);
33588
- const storyboardPath = join15(projectDir, "STORYBOARD.md");
33686
+ const storyboardPath = join16(projectDir, "STORYBOARD.md");
33589
33687
  const warnings = [];
33590
33688
  const retryWith = [];
33591
33689
  const providerResolution = [];
33592
- if (!existsSync17(storyboardPath)) {
33690
+ if (!existsSync18(storyboardPath)) {
33593
33691
  const validation2 = {
33594
33692
  ok: false,
33595
33693
  issues: [
@@ -33618,7 +33716,7 @@ async function createBuildPlan(opts) {
33618
33716
  validation: validation2
33619
33717
  });
33620
33718
  }
33621
- const storyboardMd = await readFile17(storyboardPath, "utf-8");
33719
+ const storyboardMd = await readFile18(storyboardPath, "utf-8");
33622
33720
  const validation = validateStoryboardMarkdown(storyboardMd);
33623
33721
  const validationIssues = [...validation.issues];
33624
33722
  const parsed = parseStoryboard(storyboardMd);
@@ -33644,7 +33742,7 @@ async function createBuildPlan(opts) {
33644
33742
  const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
33645
33743
  const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
33646
33744
  const imageRatio = imageRatioForSize(imageSize2);
33647
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync17(join15(projectDir, `compositions/scene-${beat.id}.html`)));
33745
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync18(join16(projectDir, `compositions/scene-${beat.id}.html`)));
33648
33746
  const resolved = await resolvePlanProviders({
33649
33747
  projectDir,
33650
33748
  config: config2,
@@ -33792,7 +33890,7 @@ async function createBuildPlan(opts) {
33792
33890
  active: includeAssets
33793
33891
  }) : null;
33794
33892
  const compositionPath = `compositions/scene-${beat.id}.html`;
33795
- const compositionExists = existsSync17(join15(projectDir, compositionPath));
33893
+ const compositionExists = existsSync18(join16(projectDir, compositionPath));
33796
33894
  for (const asset of [narration, backdrop, keyframe, video, music]) {
33797
33895
  if (!asset) continue;
33798
33896
  if (asset.referenceError) {
@@ -33861,7 +33959,7 @@ async function createBuildPlan(opts) {
33861
33959
  }
33862
33960
  }
33863
33961
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
33864
- if (!existsSync17(join15(projectDir, config2.config.composition.entry))) {
33962
+ if (!existsSync18(join16(projectDir, config2.config.composition.entry))) {
33865
33963
  missing.add("root-composition");
33866
33964
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
33867
33965
  }
@@ -33895,7 +33993,7 @@ async function createBuildPlan(opts) {
33895
33993
  }
33896
33994
  if (referenced.size > 0) {
33897
33995
  const toGenerate = resolveCharacters(parsed.frontmatter).filter(
33898
- (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync17(join15(projectDir, `assets/character-${c.name}.png`)))
33996
+ (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync18(join16(projectDir, `assets/character-${c.name}.png`)))
33899
33997
  );
33900
33998
  if (toGenerate.length > 0) {
33901
33999
  estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
@@ -34008,8 +34106,8 @@ function assetPlan(opts) {
34008
34106
  reason: ready ? "referenced-asset" : "invalid-reference"
34009
34107
  };
34010
34108
  }
34011
- const canonicalExists = existsSync17(join15(opts.projectDir, opts.path));
34012
- const cacheHit = opts.cache ? existsSync17(join15(opts.projectDir, opts.cache.path)) : false;
34109
+ const canonicalExists = existsSync18(join16(opts.projectDir, opts.path));
34110
+ const cacheHit = opts.cache ? existsSync18(join16(opts.projectDir, opts.cache.path)) : false;
34013
34111
  const freshness = assetFreshnessFromMetadata({
34014
34112
  projectDir: opts.projectDir,
34015
34113
  kind: opts.kind,
@@ -34207,7 +34305,7 @@ async function firstConfiguredComposer(projectDir) {
34207
34305
  }
34208
34306
  function firstExisting2(projectDir, paths) {
34209
34307
  for (const path2 of paths) {
34210
- if (existsSync17(join15(projectDir, path2))) return path2;
34308
+ if (existsSync18(join16(projectDir, path2))) return path2;
34211
34309
  }
34212
34310
  return null;
34213
34311
  }
@@ -34248,12 +34346,12 @@ function unique3(items) {
34248
34346
  return [...new Set(items.filter((item) => item.length > 0))];
34249
34347
  }
34250
34348
  function loadPlanEnv(projectDir) {
34251
- (0, import_dotenv2.config)({ path: join15(projectDir, ".env"), quiet: true });
34349
+ (0, import_dotenv2.config)({ path: join16(projectDir, ".env"), quiet: true });
34252
34350
  (0, import_dotenv2.config)({ path: resolve22(process.cwd(), ".env"), quiet: true });
34253
34351
  let dir = process.cwd();
34254
34352
  while (dir !== dirname9(dir)) {
34255
- if (existsSync17(join15(dir, "pnpm-workspace.yaml"))) {
34256
- (0, import_dotenv2.config)({ path: join15(dir, ".env"), quiet: true });
34353
+ if (existsSync18(join16(dir, "pnpm-workspace.yaml"))) {
34354
+ (0, import_dotenv2.config)({ path: join16(dir, ".env"), quiet: true });
34257
34355
  return;
34258
34356
  }
34259
34357
  dir = dirname9(dir);
@@ -34315,7 +34413,7 @@ var init_validate = __esm({
34315
34413
  // src/commands/generate/music.ts
34316
34414
  import { resolve as resolve24 } from "node:path";
34317
34415
  import { writeFile as writeFile17 } from "node:fs/promises";
34318
- import { existsSync as existsSync18 } from "node:fs";
34416
+ import { existsSync as existsSync19 } from "node:fs";
34319
34417
  async function executeMusic(options) {
34320
34418
  try {
34321
34419
  const provider = options.provider || "elevenlabs";
@@ -34468,7 +34566,7 @@ function registerMusicCommand(parent) {
34468
34566
  if (options.melody) {
34469
34567
  spinner2.text = "Uploading melody reference...";
34470
34568
  const absPath = resolve24(process.cwd(), options.melody);
34471
- if (!existsSync18(absPath)) {
34569
+ if (!existsSync19(absPath)) {
34472
34570
  spinner2.fail(`Melody file not found: ${options.melody}`);
34473
34571
  exitWithError(notFoundError(options.melody));
34474
34572
  }
@@ -34601,9 +34699,9 @@ __export(scene_build_exports, {
34601
34699
  executeSceneBuild: () => executeSceneBuild,
34602
34700
  resolveSceneBuildMode: () => resolveSceneBuildMode
34603
34701
  });
34604
- import { existsSync as existsSync19, readFileSync as readFileSync3 } from "node:fs";
34605
- import { copyFile as copyFile3, mkdir as mkdir10, readFile as readFile18, rm, writeFile as writeFile18 } from "node:fs/promises";
34606
- import { dirname as dirname10, join as join16, resolve as resolve25 } from "node:path";
34702
+ import { existsSync as existsSync20, readFileSync as readFileSync3 } from "node:fs";
34703
+ import { copyFile as copyFile3, mkdir as mkdir10, readFile as readFile19, rm, writeFile as writeFile18 } from "node:fs/promises";
34704
+ import { dirname as dirname10, join as join17, resolve as resolve25 } from "node:path";
34607
34705
  async function executeSceneBuild(opts) {
34608
34706
  const startedAt = Date.now();
34609
34707
  const projectDir = resolve25(opts.projectDir);
@@ -34622,7 +34720,7 @@ async function executeSceneBuild(opts) {
34622
34720
  );
34623
34721
  }
34624
34722
  let sceneRepair = skippedSceneRepairSummary();
34625
- const storyboardPath = join16(projectDir, "STORYBOARD.md");
34723
+ const storyboardPath = join17(projectDir, "STORYBOARD.md");
34626
34724
  const buildPlan = await createBuildPlan({
34627
34725
  projectDir,
34628
34726
  stage: selectedStage,
@@ -34673,8 +34771,8 @@ async function executeSceneBuild(opts) {
34673
34771
  }
34674
34772
  if (!buildPlan.validation.ok) {
34675
34773
  let invalidBeats = [];
34676
- if (existsSync19(storyboardPath)) {
34677
- invalidBeats = parseStoryboard(await readFile18(storyboardPath, "utf-8")).beats;
34774
+ if (existsSync20(storyboardPath)) {
34775
+ invalidBeats = parseStoryboard(await readFile19(storyboardPath, "utf-8")).beats;
34678
34776
  }
34679
34777
  return finishBuildResult({
34680
34778
  success: false,
@@ -34698,7 +34796,7 @@ async function executeSceneBuild(opts) {
34698
34796
  totalLatencyMs: Date.now() - startedAt
34699
34797
  });
34700
34798
  }
34701
- if (!existsSync19(storyboardPath)) {
34799
+ if (!existsSync20(storyboardPath)) {
34702
34800
  return finishBuildResult({
34703
34801
  success: false,
34704
34802
  phase: "failed",
@@ -34712,7 +34810,7 @@ async function executeSceneBuild(opts) {
34712
34810
  totalLatencyMs: Date.now() - startedAt
34713
34811
  });
34714
34812
  }
34715
- const storyboardMd = await readFile18(storyboardPath, "utf-8");
34813
+ const storyboardMd = await readFile19(storyboardPath, "utf-8");
34716
34814
  const parsed = parseStoryboard(storyboardMd);
34717
34815
  if (parsed.beats.length === 0) {
34718
34816
  return finishBuildResult({
@@ -34819,6 +34917,7 @@ async function executeSceneBuild(opts) {
34819
34917
  skipVideo: opts.skipVideo ?? false,
34820
34918
  skipKeyframe: opts.skipKeyframe ?? false,
34821
34919
  skipMusic: opts.skipMusic ?? false,
34920
+ skipTranscript: opts.skipTranscript ?? false,
34822
34921
  force: opts.force ?? false,
34823
34922
  onProgress,
34824
34923
  characterPaths
@@ -34906,9 +35005,9 @@ async function executeSceneBuild(opts) {
34906
35005
  let composeData;
34907
35006
  if (shouldRunStage(selectedStage, "compose")) {
34908
35007
  if (mode === "agent") {
34909
- const compositionsDir = join16(projectDir, "compositions");
35008
+ const compositionsDir = join17(projectDir, "compositions");
34910
35009
  const missingBeats = activeBeats.filter(
34911
- (b) => !existsSync19(join16(compositionsDir, `scene-${b.id}.html`))
35010
+ (b) => !existsSync20(join17(compositionsDir, `scene-${b.id}.html`))
34912
35011
  );
34913
35012
  if (missingBeats.length > 0) {
34914
35013
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -34997,8 +35096,8 @@ async function executeSceneBuild(opts) {
34997
35096
  if (stageReports.compose.status === "done") {
34998
35097
  for (const beat of activeBeats) {
34999
35098
  const videoRel = `assets/video-${beat.id}.mp4`;
35000
- const compositionAbs = join16(projectDir, "compositions", `scene-${beat.id}.html`);
35001
- if (!existsSync19(join16(projectDir, videoRel)) || !existsSync19(compositionAbs)) continue;
35099
+ const compositionAbs = join17(projectDir, "compositions", `scene-${beat.id}.html`);
35100
+ if (!existsSync20(join17(projectDir, videoRel)) || !existsSync20(compositionAbs)) continue;
35002
35101
  const html = readFileSync3(compositionAbs, "utf-8");
35003
35102
  if (!html.includes(videoRel)) {
35004
35103
  warnings.push(
@@ -35056,7 +35155,7 @@ async function executeSceneBuild(opts) {
35056
35155
  });
35057
35156
  }
35058
35157
  if (shouldRunStage(selectedStage, "sync")) {
35059
- if (!existsSync19(join16(projectDir, "index.html"))) {
35158
+ if (!existsSync20(join17(projectDir, "index.html"))) {
35060
35159
  await scaffoldSceneProject({
35061
35160
  dir: projectDir,
35062
35161
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -35201,6 +35300,9 @@ async function buildBeatPrimitives(beat, ctx) {
35201
35300
  ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx, keyframe),
35202
35301
  ctx.skipMusic ? skipped("music", beat.id, "--skip-music", ctx) : dispatchMusic(beat, ctx)
35203
35302
  ]);
35303
+ if (!ctx.skipTranscript) {
35304
+ await dispatchTranscript(beat, narration, ctx);
35305
+ }
35204
35306
  return {
35205
35307
  outcome: {
35206
35308
  beatId: beat.id,
@@ -35318,7 +35420,7 @@ async function dispatchNarration(beat, ctx) {
35318
35420
  return {
35319
35421
  status: "cached",
35320
35422
  path: existingRel,
35321
- durationSec: await safeAudioDuration(join16(ctx.projectDir, existingRel)),
35423
+ durationSec: await safeAudioDuration(join17(ctx.projectDir, existingRel)),
35322
35424
  provider: metadata?.provider,
35323
35425
  cachePath: metadata?.cachePath,
35324
35426
  cacheKey: metadata?.cacheKey,
@@ -35342,9 +35444,9 @@ async function dispatchNarration(beat, ctx) {
35342
35444
  voice,
35343
35445
  ext: resolution.audioExtension
35344
35446
  });
35345
- const cacheAbs = join16(ctx.projectDir, cache.path);
35447
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35346
35448
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
35347
- const abs = join16(ctx.projectDir, rel);
35449
+ const abs = join17(ctx.projectDir, rel);
35348
35450
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
35349
35451
  projectDir: ctx.projectDir,
35350
35452
  kind: "narration",
@@ -35358,7 +35460,7 @@ async function dispatchNarration(beat, ctx) {
35358
35460
  return {
35359
35461
  status: "cached",
35360
35462
  path: existingRel,
35361
- durationSec: await safeAudioDuration(join16(ctx.projectDir, existingRel)),
35463
+ durationSec: await safeAudioDuration(join17(ctx.projectDir, existingRel)),
35362
35464
  provider: resolution.provider,
35363
35465
  cachePath: cache.path,
35364
35466
  cacheKey: cache.key,
@@ -35366,7 +35468,7 @@ async function dispatchNarration(beat, ctx) {
35366
35468
  freshness: "fresh"
35367
35469
  };
35368
35470
  }
35369
- if (existsSync19(cacheAbs) && !ctx.force) {
35471
+ if (existsSync20(cacheAbs) && !ctx.force) {
35370
35472
  await mkdir10(dirname10(abs), { recursive: true });
35371
35473
  await copyFile3(cacheAbs, abs);
35372
35474
  await writeAssetMetadata({
@@ -35401,8 +35503,8 @@ async function dispatchNarration(beat, ctx) {
35401
35503
  await mkdir10(dirname10(abs), { recursive: true });
35402
35504
  await writeFile18(abs, result.audioBuffer);
35403
35505
  for (const otherExt of ["mp3", "wav"]) {
35404
- const sibling = join16(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
35405
- if (sibling !== abs && existsSync19(sibling)) await rm(sibling, { force: true });
35506
+ const sibling = join17(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
35507
+ if (sibling !== abs && existsSync20(sibling)) await rm(sibling, { force: true });
35406
35508
  }
35407
35509
  await mkdir10(dirname10(cacheAbs), { recursive: true });
35408
35510
  await writeFile18(cacheAbs, result.audioBuffer);
@@ -35434,6 +35536,42 @@ async function dispatchNarration(beat, ctx) {
35434
35536
  freshness: "fresh"
35435
35537
  };
35436
35538
  }
35539
+ async function dispatchTranscript(beat, narration, ctx) {
35540
+ if (!narration.path) return;
35541
+ const relPath = beatTranscriptRelPath(beat.id);
35542
+ const abs = join17(ctx.projectDir, relPath);
35543
+ const narrationFresh = narration.status === "generated";
35544
+ if (existsSync20(abs) && !ctx.force && !narrationFresh) {
35545
+ ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
35546
+ return;
35547
+ }
35548
+ const apiKey = await getConfiguredApiKey("OPENAI_API_KEY", void 0, { cwd: ctx.projectDir });
35549
+ if (!apiKey) {
35550
+ ctx.onProgress({
35551
+ type: "transcript-skipped",
35552
+ beatId: beat.id,
35553
+ reason: "OPENAI_API_KEY not set \u2014 narration plays but word-sync timings unavailable"
35554
+ });
35555
+ return;
35556
+ }
35557
+ const words = await transcribeNarrationWords(join17(ctx.projectDir, narration.path), { apiKey });
35558
+ if (words.length === 0) {
35559
+ ctx.onProgress({
35560
+ type: "transcript-skipped",
35561
+ beatId: beat.id,
35562
+ reason: "transcription returned no word timings"
35563
+ });
35564
+ return;
35565
+ }
35566
+ await mkdir10(dirname10(abs), { recursive: true });
35567
+ await writeFile18(abs, JSON.stringify(words, null, 2), "utf-8");
35568
+ ctx.onProgress({
35569
+ type: "transcript-generated",
35570
+ beatId: beat.id,
35571
+ path: relPath,
35572
+ wordCount: words.length
35573
+ });
35574
+ }
35437
35575
  function characterSheetPrompt(name, description) {
35438
35576
  return [
35439
35577
  `Character reference sheet of an original fictional character named ${name}: ${description}.`,
@@ -35451,7 +35589,7 @@ async function buildCharacters(characters, ctx) {
35451
35589
  const size = ctx.imageSize ?? "1536x1024";
35452
35590
  for (const character of characters) {
35453
35591
  if (character.imagePath) {
35454
- if (existsSync19(join16(ctx.projectDir, character.imagePath))) {
35592
+ if (existsSync20(join17(ctx.projectDir, character.imagePath))) {
35455
35593
  paths.set(character.name, character.imagePath);
35456
35594
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
35457
35595
  } else {
@@ -35464,7 +35602,7 @@ async function buildCharacters(characters, ctx) {
35464
35602
  if (!character.prompt) continue;
35465
35603
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
35466
35604
  const rel = `assets/character-${character.name}.png`;
35467
- const abs = join16(ctx.projectDir, rel);
35605
+ const abs = join17(ctx.projectDir, rel);
35468
35606
  const metadataOptions = { quality: ctx.imageQuality, size };
35469
35607
  const cache = characterCacheDescriptor({
35470
35608
  name: character.name,
@@ -35473,7 +35611,7 @@ async function buildCharacters(characters, ctx) {
35473
35611
  quality: ctx.imageQuality,
35474
35612
  size
35475
35613
  });
35476
- if (existsSync19(abs) && !ctx.force) {
35614
+ if (existsSync20(abs) && !ctx.force) {
35477
35615
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
35478
35616
  if (!metadata || isFreshCanonicalAsset({
35479
35617
  projectDir: ctx.projectDir,
@@ -35489,8 +35627,8 @@ async function buildCharacters(characters, ctx) {
35489
35627
  continue;
35490
35628
  }
35491
35629
  }
35492
- const cacheAbs = join16(ctx.projectDir, cache.path);
35493
- if (existsSync19(cacheAbs) && !ctx.force) {
35630
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35631
+ if (existsSync20(cacheAbs) && !ctx.force) {
35494
35632
  await mkdir10(dirname10(abs), { recursive: true });
35495
35633
  await copyFile3(cacheAbs, abs);
35496
35634
  await writeAssetMetadata({
@@ -35556,7 +35694,7 @@ async function dispatchBackdrop(beat, ctx) {
35556
35694
  if (!cue) return { status: "no-cue" };
35557
35695
  const prompt3 = augmentBackdropPrompt(cue);
35558
35696
  const rel = `assets/backdrop-${beat.id}.png`;
35559
- const abs = join16(ctx.projectDir, rel);
35697
+ const abs = join17(ctx.projectDir, rel);
35560
35698
  const size = ctx.imageSize ?? "1536x1024";
35561
35699
  const ratio = imageRatioForSize(size);
35562
35700
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -35569,7 +35707,7 @@ async function dispatchBackdrop(beat, ctx) {
35569
35707
  ratio
35570
35708
  });
35571
35709
  const metadataPath = assetMetadataPath("backdrop", beat.id);
35572
- if (existsSync19(abs) && !ctx.force) {
35710
+ if (existsSync20(abs) && !ctx.force) {
35573
35711
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
35574
35712
  if (!metadata || isFreshCanonicalAsset({
35575
35713
  projectDir: ctx.projectDir,
@@ -35592,8 +35730,8 @@ async function dispatchBackdrop(beat, ctx) {
35592
35730
  };
35593
35731
  }
35594
35732
  }
35595
- const cacheAbs = join16(ctx.projectDir, cache.path);
35596
- if (existsSync19(cacheAbs) && !ctx.force) {
35733
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35734
+ if (existsSync20(cacheAbs) && !ctx.force) {
35597
35735
  await mkdir10(dirname10(abs), { recursive: true });
35598
35736
  await copyFile3(cacheAbs, abs);
35599
35737
  await writeAssetMetadata({
@@ -35725,7 +35863,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
35725
35863
  }
35726
35864
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35727
35865
  const rel = `assets/keyframe-${beat.id}.png`;
35728
- const abs = join16(ctx.projectDir, rel);
35866
+ const abs = join17(ctx.projectDir, rel);
35729
35867
  const size = ctx.imageSize ?? "1536x1024";
35730
35868
  const ratio = imageRatioForSize(size);
35731
35869
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -35738,7 +35876,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35738
35876
  ratio,
35739
35877
  characters: characterRefsRel
35740
35878
  });
35741
- if (existsSync19(abs) && !ctx.force) {
35879
+ if (existsSync20(abs) && !ctx.force) {
35742
35880
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
35743
35881
  if (!metadata || isFreshCanonicalAsset({
35744
35882
  projectDir: ctx.projectDir,
@@ -35752,8 +35890,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35752
35890
  return { status: "cached", rel };
35753
35891
  }
35754
35892
  }
35755
- const cacheAbs = join16(ctx.projectDir, cache.path);
35756
- if (existsSync19(cacheAbs) && !ctx.force) {
35893
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35894
+ if (existsSync20(cacheAbs) && !ctx.force) {
35757
35895
  await mkdir10(dirname10(abs), { recursive: true });
35758
35896
  await copyFile3(cacheAbs, abs);
35759
35897
  await writeAssetMetadata({
@@ -35771,8 +35909,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35771
35909
  }
35772
35910
  const sheetBuffers = [];
35773
35911
  for (const relPath of characterRefsRel) {
35774
- const sheetAbs = join16(ctx.projectDir, relPath);
35775
- if (existsSync19(sheetAbs)) sheetBuffers.push(await readFile18(sheetAbs));
35912
+ const sheetAbs = join17(ctx.projectDir, relPath);
35913
+ if (existsSync20(sheetAbs)) sheetBuffers.push(await readFile19(sheetAbs));
35776
35914
  }
35777
35915
  const imgCtx = {
35778
35916
  projectDir: ctx.projectDir,
@@ -35841,9 +35979,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
35841
35979
  const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
35842
35980
  if (!prompt3) return { status: "no-cue" };
35843
35981
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
35844
- const characterRefsAbs = characterRefsRel.map((p) => join16(ctx.projectDir, p));
35982
+ const characterRefsAbs = characterRefsRel.map((p) => join17(ctx.projectDir, p));
35845
35983
  const rel = `assets/video-${beat.id}.mp4`;
35846
- const abs = join16(ctx.projectDir, rel);
35984
+ const abs = join17(ctx.projectDir, rel);
35847
35985
  const cache = videoCacheDescriptor({
35848
35986
  beatId: beat.id,
35849
35987
  cue: prompt3,
@@ -35855,7 +35993,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35855
35993
  keyframe: keyframeCue
35856
35994
  });
35857
35995
  const metadataPath = assetMetadataPath("video", beat.id);
35858
- if (existsSync19(abs) && !ctx.force) {
35996
+ if (existsSync20(abs) && !ctx.force) {
35859
35997
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
35860
35998
  if (!metadata || isFreshCanonicalAsset({
35861
35999
  projectDir: ctx.projectDir,
@@ -35878,8 +36016,8 @@ async function dispatchVideo(beat, ctx, keyframe) {
35878
36016
  };
35879
36017
  }
35880
36018
  }
35881
- const cacheAbs = join16(ctx.projectDir, cache.path);
35882
- if (existsSync19(cacheAbs) && !ctx.force) {
36019
+ const cacheAbs = join17(ctx.projectDir, cache.path);
36020
+ if (existsSync20(cacheAbs) && !ctx.force) {
35883
36021
  await mkdir10(dirname10(abs), { recursive: true });
35884
36022
  await copyFile3(cacheAbs, abs);
35885
36023
  await writeAssetMetadata({
@@ -35910,7 +36048,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35910
36048
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
35911
36049
  return { status: "skipped" };
35912
36050
  }
35913
- const keyframeImageAbs = keyframeImageRel ? join16(ctx.projectDir, keyframeImageRel) : void 0;
36051
+ const keyframeImageAbs = keyframeImageRel ? join17(ctx.projectDir, keyframeImageRel) : void 0;
35914
36052
  loadSceneBuildEnv(ctx.projectDir);
35915
36053
  const result = await executeVideoGenerate({
35916
36054
  prompt: prompt3,
@@ -35934,7 +36072,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35934
36072
  ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
35935
36073
  return { status: "failed", error };
35936
36074
  }
35937
- if (result.status === "completed" && existsSync19(abs)) {
36075
+ if (result.status === "completed" && existsSync20(abs)) {
35938
36076
  await mkdir10(dirname10(cacheAbs), { recursive: true });
35939
36077
  await copyFile3(abs, cacheAbs);
35940
36078
  await writeAssetMetadata({
@@ -36002,7 +36140,7 @@ async function dispatchMusic(beat, ctx) {
36002
36140
  const prompt3 = stringOrUndefined4(beat.cues?.music);
36003
36141
  if (!prompt3) return { status: "no-cue" };
36004
36142
  const rel = `assets/music-${beat.id}.mp3`;
36005
- const abs = join16(ctx.projectDir, rel);
36143
+ const abs = join17(ctx.projectDir, rel);
36006
36144
  const duration = normalizeMusicDuration(beat.duration);
36007
36145
  const cache = musicCacheDescriptor({
36008
36146
  beatId: beat.id,
@@ -36011,7 +36149,7 @@ async function dispatchMusic(beat, ctx) {
36011
36149
  duration
36012
36150
  });
36013
36151
  const metadataPath = assetMetadataPath("music", beat.id);
36014
- if (existsSync19(abs) && !ctx.force) {
36152
+ if (existsSync20(abs) && !ctx.force) {
36015
36153
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
36016
36154
  if (!metadata || isFreshCanonicalAsset({
36017
36155
  projectDir: ctx.projectDir,
@@ -36035,8 +36173,8 @@ async function dispatchMusic(beat, ctx) {
36035
36173
  };
36036
36174
  }
36037
36175
  }
36038
- const cacheAbs = join16(ctx.projectDir, cache.path);
36039
- if (existsSync19(cacheAbs) && !ctx.force) {
36176
+ const cacheAbs = join17(ctx.projectDir, cache.path);
36177
+ if (existsSync20(cacheAbs) && !ctx.force) {
36040
36178
  await mkdir10(dirname10(abs), { recursive: true });
36041
36179
  await copyFile3(cacheAbs, abs);
36042
36180
  await writeAssetMetadata({
@@ -36110,7 +36248,7 @@ async function dispatchMusic(beat, ctx) {
36110
36248
  metadataPath
36111
36249
  };
36112
36250
  }
36113
- if (!existsSync19(abs)) {
36251
+ if (!existsSync20(abs)) {
36114
36252
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
36115
36253
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
36116
36254
  return { status: "failed", error };
@@ -36146,12 +36284,12 @@ async function dispatchMusic(beat, ctx) {
36146
36284
  };
36147
36285
  }
36148
36286
  function loadSceneBuildEnv(projectDir) {
36149
- (0, import_dotenv3.config)({ path: join16(projectDir, ".env"), quiet: true });
36287
+ (0, import_dotenv3.config)({ path: join17(projectDir, ".env"), quiet: true });
36150
36288
  (0, import_dotenv3.config)({ path: resolve25(process.cwd(), ".env"), quiet: true });
36151
36289
  let dir = process.cwd();
36152
36290
  while (dir !== dirname10(dir)) {
36153
- if (existsSync19(join16(dir, "pnpm-workspace.yaml"))) {
36154
- (0, import_dotenv3.config)({ path: join16(dir, ".env"), quiet: true });
36291
+ if (existsSync20(join17(dir, "pnpm-workspace.yaml"))) {
36292
+ (0, import_dotenv3.config)({ path: join17(dir, ".env"), quiet: true });
36155
36293
  return;
36156
36294
  }
36157
36295
  dir = dirname10(dir);
@@ -36367,7 +36505,7 @@ function mergeBeatOutcomes(base, updates) {
36367
36505
  }
36368
36506
  function firstExisting3(projectDir, relPaths) {
36369
36507
  for (const rel of relPaths) {
36370
- if (existsSync19(join16(projectDir, rel))) return rel;
36508
+ if (existsSync20(join17(projectDir, rel))) return rel;
36371
36509
  }
36372
36510
  return null;
36373
36511
  }
@@ -36398,7 +36536,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
36398
36536
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
36399
36537
  }
36400
36538
  async function finalizeBuildResult(projectDir, startedAt, result) {
36401
- const reportPath = join16(projectDir, "build-report.json");
36539
+ const reportPath = join17(projectDir, "build-report.json");
36402
36540
  if (result.stageReports) {
36403
36541
  for (const report of Object.values(result.stageReports)) {
36404
36542
  if (report.status === "pending") report.status = "skipped";
@@ -36557,7 +36695,7 @@ function normalizeReportDuration(value) {
36557
36695
  }
36558
36696
  function buildReportComposition(projectDir, result, beatId) {
36559
36697
  const path2 = `compositions/scene-${beatId}.html`;
36560
- const exists = existsSync19(join16(projectDir, path2));
36698
+ const exists = existsSync20(join17(projectDir, path2));
36561
36699
  const written = composeWrittenEntry(result, beatId);
36562
36700
  if (written) {
36563
36701
  return {
@@ -36614,8 +36752,8 @@ function summarizeBuildBeats(projectDir, beats) {
36614
36752
  let compositionsReady = 0;
36615
36753
  for (const beat of beats) {
36616
36754
  if (beatAssetsReady(beat)) assetsReady += 1;
36617
- const compositionPath = join16(projectDir, "compositions", `scene-${beat.beatId}.html`);
36618
- if (existsSync19(compositionPath)) {
36755
+ const compositionPath = join17(projectDir, "compositions", `scene-${beat.beatId}.html`);
36756
+ if (existsSync20(compositionPath)) {
36619
36757
  compositionsReady += 1;
36620
36758
  } else {
36621
36759
  needsAuthor.push(beat.beatId);
@@ -36657,6 +36795,8 @@ var init_scene_build = __esm({
36657
36795
  init_compose_scenes_skills();
36658
36796
  init_compose_prompts();
36659
36797
  init_config();
36798
+ init_api_key();
36799
+ init_transcribe_narration();
36660
36800
  init_scene_render();
36661
36801
  init_storyboard_parse();
36662
36802
  init_scene_project();
@@ -36785,18 +36925,18 @@ __export(ai_audio_exports, {
36785
36925
  executeVoiceClone: () => executeVoiceClone
36786
36926
  });
36787
36927
  import { resolve as resolve27, dirname as dirname11, basename as basename5, extname as extname4 } from "node:path";
36788
- import { readFile as readFile19, writeFile as writeFile19 } from "node:fs/promises";
36789
- import { existsSync as existsSync20 } from "node:fs";
36928
+ import { readFile as readFile20, writeFile as writeFile19 } from "node:fs/promises";
36929
+ import { existsSync as existsSync21 } from "node:fs";
36790
36930
  async function executeTranscribe(options) {
36791
36931
  const { audioPath, language, output, format, apiKey } = options;
36792
36932
  try {
36793
36933
  const key = apiKey || process.env.OPENAI_API_KEY;
36794
36934
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
36795
36935
  const absPath = resolve27(process.cwd(), audioPath);
36796
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${absPath}` };
36936
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${absPath}` };
36797
36937
  const whisper = new WhisperProvider();
36798
36938
  await whisper.initialize({ apiKey: key });
36799
- const audioBuffer = await readFile19(absPath);
36939
+ const audioBuffer = await readFile20(absPath);
36800
36940
  const audioBlob = new Blob([audioBuffer]);
36801
36941
  const result = await whisper.transcribe(audioBlob, language);
36802
36942
  if (result.status === "failed") {
@@ -36826,8 +36966,8 @@ async function executeIsolate(options) {
36826
36966
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
36827
36967
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
36828
36968
  const absPath = resolve27(process.cwd(), audioPath);
36829
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${absPath}` };
36830
- const audioBuffer = await readFile19(absPath);
36969
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${absPath}` };
36970
+ const audioBuffer = await readFile20(absPath);
36831
36971
  const elevenlabs = new ElevenLabsProvider();
36832
36972
  await elevenlabs.initialize({ apiKey: key });
36833
36973
  const result = await elevenlabs.isolateVocals(audioBuffer);
@@ -36852,8 +36992,8 @@ async function executeVoiceClone(options) {
36852
36992
  const audioBuffers = [];
36853
36993
  for (const samplePath of samplePaths) {
36854
36994
  const absPath = resolve27(process.cwd(), samplePath);
36855
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${samplePath}` };
36856
- const buffer = await readFile19(absPath);
36995
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${samplePath}` };
36996
+ const buffer = await readFile20(absPath);
36857
36997
  audioBuffers.push(buffer);
36858
36998
  }
36859
36999
  const elevenlabs = new ElevenLabsProvider();
@@ -36874,7 +37014,7 @@ async function executeDub(options) {
36874
37014
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
36875
37015
  try {
36876
37016
  const absPath = resolve27(process.cwd(), mediaPath);
36877
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${absPath}` };
37017
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${absPath}` };
36878
37018
  const openaiKey = process.env.OPENAI_API_KEY;
36879
37019
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
36880
37020
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -36891,7 +37031,7 @@ async function executeDub(options) {
36891
37031
  }
36892
37032
  const whisper = new WhisperProvider();
36893
37033
  await whisper.initialize({ apiKey: openaiKey });
36894
- const audioBuffer = await readFile19(audioPath);
37034
+ const audioBuffer = await readFile20(audioPath);
36895
37035
  const audioBlob = new Blob([audioBuffer]);
36896
37036
  const transcriptResult = await whisper.transcribe(audioBlob, source);
36897
37037
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -37002,8 +37142,8 @@ async function executeDuck(options) {
37002
37142
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
37003
37143
  const absMusicPath = resolve27(process.cwd(), musicPath);
37004
37144
  const absVoicePath = resolve27(process.cwd(), voicePath);
37005
- if (!existsSync20(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
37006
- if (!existsSync20(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
37145
+ if (!existsSync21(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
37146
+ if (!existsSync21(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
37007
37147
  const defaultOutput = resolve27(dirname11(absMusicPath), `${basename5(absMusicPath, extname4(absMusicPath))}-ducked${extname4(absMusicPath)}`);
37008
37148
  const outputPath = resolve27(process.cwd(), output || defaultOutput);
37009
37149
  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`;
@@ -37017,7 +37157,7 @@ async function executeDuck(options) {
37017
37157
  "-y",
37018
37158
  outputPath
37019
37159
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
37020
- if (!existsSync20(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
37160
+ if (!existsSync21(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
37021
37161
  return { success: true, outputPath };
37022
37162
  } catch (error) {
37023
37163
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -37033,8 +37173,8 @@ var init_ai_audio = __esm({
37033
37173
  });
37034
37174
 
37035
37175
  // src/commands/_shared/edit/silence-cut.ts
37036
- import { existsSync as existsSync21 } from "node:fs";
37037
- import { readFile as readFile20 } from "node:fs/promises";
37176
+ import { existsSync as existsSync22 } from "node:fs";
37177
+ import { readFile as readFile21 } from "node:fs/promises";
37038
37178
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
37039
37179
  const totalDuration = await getVideoDuration(videoPath);
37040
37180
  const { stdout, stderr } = await execSafe(
@@ -37084,7 +37224,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
37084
37224
  }
37085
37225
  const gemini = new GeminiProvider();
37086
37226
  await gemini.initialize({ apiKey: geminiApiKey });
37087
- const videoBuffer = await readFile20(videoPath);
37227
+ const videoBuffer = await readFile21(videoPath);
37088
37228
  const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
37089
37229
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
37090
37230
 
@@ -37160,7 +37300,7 @@ async function executeSilenceCut(options) {
37160
37300
  analyzeOnly = false,
37161
37301
  useGemini = false
37162
37302
  } = options;
37163
- if (!existsSync21(videoPath)) {
37303
+ if (!existsSync22(videoPath)) {
37164
37304
  return { success: false, error: `Video not found: ${videoPath}` };
37165
37305
  }
37166
37306
  if (!commandExists("ffmpeg")) {
@@ -37268,11 +37408,11 @@ var init_silence_cut = __esm({
37268
37408
  });
37269
37409
 
37270
37410
  // src/commands/_shared/edit/jump-cut.ts
37271
- import { existsSync as existsSync22 } from "node:fs";
37272
- import { readFile as readFile21, writeFile as writeFile20, mkdir as mkdir11 } from "node:fs/promises";
37273
- import { join as join17 } from "node:path";
37411
+ import { existsSync as existsSync23 } from "node:fs";
37412
+ import { readFile as readFile22, writeFile as writeFile20, mkdir as mkdir11 } from "node:fs/promises";
37413
+ import { join as join18 } from "node:path";
37274
37414
  async function transcribeWithWords(audioPath, apiKey, language) {
37275
- const audioBuffer = await readFile21(audioPath);
37415
+ const audioBuffer = await readFile22(audioPath);
37276
37416
  const audioBlob = new Blob([audioBuffer]);
37277
37417
  const formData = new FormData();
37278
37418
  formData.append("file", audioBlob, "audio.wav");
@@ -37331,7 +37471,7 @@ async function executeJumpCut(options) {
37331
37471
  analyzeOnly = false,
37332
37472
  apiKey
37333
37473
  } = options;
37334
- if (!existsSync22(videoPath)) {
37474
+ if (!existsSync23(videoPath)) {
37335
37475
  return { success: false, error: `Video not found: ${videoPath}` };
37336
37476
  }
37337
37477
  if (!commandExists("ffmpeg")) {
@@ -37350,7 +37490,7 @@ async function executeJumpCut(options) {
37350
37490
  try {
37351
37491
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
37352
37492
  await mkdir11(tmpDir, { recursive: true });
37353
- const audioPath = join17(tmpDir, "audio.wav");
37493
+ const audioPath = join18(tmpDir, "audio.wav");
37354
37494
  try {
37355
37495
  await execSafe(
37356
37496
  "ffmpeg",
@@ -37404,7 +37544,7 @@ async function executeJumpCut(options) {
37404
37544
  const segmentPaths = [];
37405
37545
  for (let i = 0; i < segments.length; i++) {
37406
37546
  const seg = segments[i];
37407
- const segPath = join17(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
37547
+ const segPath = join18(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
37408
37548
  const duration = seg.end - seg.start;
37409
37549
  await execSafe(
37410
37550
  "ffmpeg",
@@ -37427,7 +37567,7 @@ async function executeJumpCut(options) {
37427
37567
  segmentPaths.push(segPath);
37428
37568
  }
37429
37569
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
37430
- const listPath = join17(tmpDir, "concat.txt");
37570
+ const listPath = join18(tmpDir, "concat.txt");
37431
37571
  await writeFile20(listPath, concatList);
37432
37572
  await execSafe(
37433
37573
  "ffmpeg",
@@ -37534,8 +37674,8 @@ __export(remotion_exports, {
37534
37674
  wrapComponentWithVideo: () => wrapComponentWithVideo
37535
37675
  });
37536
37676
  import { writeFile as writeFile21, mkdir as mkdir12, rm as rm2, copyFile as copyFile4 } from "node:fs/promises";
37537
- import { existsSync as existsSync23 } from "node:fs";
37538
- import { join as join18 } from "node:path";
37677
+ import { existsSync as existsSync24 } from "node:fs";
37678
+ import { join as join19 } from "node:path";
37539
37679
  import { tmpdir } from "node:os";
37540
37680
  import { createHash as createHash6 } from "node:crypto";
37541
37681
  async function ensureRemotionInstalled() {
@@ -37554,7 +37694,7 @@ async function ensureRemotionInstalled() {
37554
37694
  }
37555
37695
  }
37556
37696
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
37557
- const dir = join18(tmpdir(), `vibe_motion_${Date.now()}`);
37697
+ const dir = join19(tmpdir(), `vibe_motion_${Date.now()}`);
37558
37698
  await mkdir12(dir, { recursive: true });
37559
37699
  const deps = {
37560
37700
  remotion: REMOTION_VERSION,
@@ -37573,7 +37713,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37573
37713
  dependencies: deps
37574
37714
  };
37575
37715
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
37576
- await writeFile21(join18(dir, "package.json"), packageJsonStr);
37716
+ await writeFile21(join19(dir, "package.json"), packageJsonStr);
37577
37717
  const tsconfig = {
37578
37718
  compilerOptions: {
37579
37719
  target: "ES2020",
@@ -37585,8 +37725,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37585
37725
  skipLibCheck: true
37586
37726
  }
37587
37727
  };
37588
- await writeFile21(join18(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
37589
- await writeFile21(join18(dir, "Component.tsx"), componentCode);
37728
+ await writeFile21(join19(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
37729
+ await writeFile21(join19(dir, "Component.tsx"), componentCode);
37590
37730
  const rootCode = `import { registerRoot, Composition } from "remotion";
37591
37731
  import { ${componentName} } from "./Component";
37592
37732
 
@@ -37605,14 +37745,14 @@ const Root = () => {
37605
37745
 
37606
37746
  registerRoot(Root);
37607
37747
  `;
37608
- await writeFile21(join18(dir, "Root.tsx"), rootCode);
37748
+ await writeFile21(join19(dir, "Root.tsx"), rootCode);
37609
37749
  const depsHash = createHash6("md5").update(packageJsonStr).digest("hex").slice(0, 12);
37610
- const cacheMarker = join18(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
37611
- const cachedModules = join18(REMOTION_CACHE_DIR, "node_modules");
37612
- if (existsSync23(cacheMarker) && existsSync23(cachedModules)) {
37750
+ const cacheMarker = join19(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
37751
+ const cachedModules = join19(REMOTION_CACHE_DIR, "node_modules");
37752
+ if (existsSync24(cacheMarker) && existsSync24(cachedModules)) {
37613
37753
  const { symlink } = await import("node:fs/promises");
37614
37754
  try {
37615
- await symlink(cachedModules, join18(dir, "node_modules"), "dir");
37755
+ await symlink(cachedModules, join19(dir, "node_modules"), "dir");
37616
37756
  } catch {
37617
37757
  await this_npmInstall(dir);
37618
37758
  }
@@ -37620,12 +37760,12 @@ registerRoot(Root);
37620
37760
  await this_npmInstall(dir);
37621
37761
  try {
37622
37762
  await mkdir12(REMOTION_CACHE_DIR, { recursive: true });
37623
- await writeFile21(join18(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
37624
- if (existsSync23(join18(dir, "node_modules"))) {
37763
+ await writeFile21(join19(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
37764
+ if (existsSync24(join19(dir, "node_modules"))) {
37625
37765
  const { rename: rename5, symlink } = await import("node:fs/promises");
37626
- await rename5(join18(dir, "node_modules"), cachedModules).catch(() => {
37766
+ await rename5(join19(dir, "node_modules"), cachedModules).catch(() => {
37627
37767
  });
37628
- await symlink(cachedModules, join18(dir, "node_modules"), "dir").catch(() => {
37768
+ await symlink(cachedModules, join19(dir, "node_modules"), "dir").catch(() => {
37629
37769
  });
37630
37770
  await writeFile21(cacheMarker, depsHash);
37631
37771
  }
@@ -37662,7 +37802,7 @@ async function renderMotion(options) {
37662
37802
  }
37663
37803
  );
37664
37804
  try {
37665
- const entryPoint = join18(dir, "Root.tsx");
37805
+ const entryPoint = join19(dir, "Root.tsx");
37666
37806
  const { execFile: execFile3 } = await import("node:child_process");
37667
37807
  const { promisify: promisify3 } = await import("node:util");
37668
37808
  const execFileAsync3 = promisify3(execFile3);
@@ -37795,10 +37935,10 @@ async function renderWithEmbeddedImage(options) {
37795
37935
  }
37796
37936
  );
37797
37937
  try {
37798
- const publicDir = join18(dir, "public");
37938
+ const publicDir = join19(dir, "public");
37799
37939
  await mkdir12(publicDir, { recursive: true });
37800
- await copyFile4(options.imagePath, join18(publicDir, options.imageFileName));
37801
- const entryPoint = join18(dir, "Root.tsx");
37940
+ await copyFile4(options.imagePath, join19(publicDir, options.imageFileName));
37941
+ const entryPoint = join19(dir, "Root.tsx");
37802
37942
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
37803
37943
  const { execFile: execFileImg } = await import("node:child_process");
37804
37944
  const { promisify: promisifyImg } = await import("node:util");
@@ -37859,10 +37999,10 @@ async function renderWithEmbeddedVideo(options) {
37859
37999
  }
37860
38000
  );
37861
38001
  try {
37862
- const publicDir = join18(dir, "public");
38002
+ const publicDir = join19(dir, "public");
37863
38003
  await mkdir12(publicDir, { recursive: true });
37864
- await copyFile4(options.videoPath, join18(publicDir, options.videoFileName));
37865
- const entryPoint = join18(dir, "Root.tsx");
38004
+ await copyFile4(options.videoPath, join19(publicDir, options.videoFileName));
38005
+ const entryPoint = join19(dir, "Root.tsx");
37866
38006
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
37867
38007
  const { execFile: execFileVid } = await import("node:child_process");
37868
38008
  const { promisify: promisifyVid } = await import("node:util");
@@ -38278,14 +38418,14 @@ var init_remotion = __esm({
38278
38418
  "use strict";
38279
38419
  init_exec_safe();
38280
38420
  REMOTION_VERSION = "4.0.447";
38281
- REMOTION_CACHE_DIR = join18(tmpdir(), "vibe_remotion_cache");
38421
+ REMOTION_CACHE_DIR = join19(tmpdir(), "vibe_remotion_cache");
38282
38422
  }
38283
38423
  });
38284
38424
 
38285
38425
  // src/commands/_shared/edit/caption.ts
38286
- import { existsSync as existsSync24 } from "node:fs";
38287
- import { readFile as readFile22, writeFile as writeFile22, mkdir as mkdir13 } from "node:fs/promises";
38288
- import { dirname as dirname12, basename as basename6, extname as extname5, join as join19 } from "node:path";
38426
+ import { existsSync as existsSync25 } from "node:fs";
38427
+ import { readFile as readFile23, writeFile as writeFile22, mkdir as mkdir13 } from "node:fs/promises";
38428
+ import { dirname as dirname12, basename as basename6, extname as extname5, join as join20 } from "node:path";
38289
38429
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
38290
38430
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
38291
38431
  const marginV = position === "center" ? 0 : 30;
@@ -38313,7 +38453,7 @@ async function executeCaption(options) {
38313
38453
  position = "bottom",
38314
38454
  apiKey
38315
38455
  } = options;
38316
- if (!existsSync24(videoPath)) {
38456
+ if (!existsSync25(videoPath)) {
38317
38457
  return { success: false, error: `Video not found: ${videoPath}` };
38318
38458
  }
38319
38459
  if (!commandExists("ffmpeg")) {
@@ -38332,8 +38472,8 @@ async function executeCaption(options) {
38332
38472
  try {
38333
38473
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
38334
38474
  await mkdir13(tmpDir, { recursive: true });
38335
- const audioPath = join19(tmpDir, "audio.wav");
38336
- const srtPath = join19(tmpDir, "captions.srt");
38475
+ const audioPath = join20(tmpDir, "audio.wav");
38476
+ const srtPath = join20(tmpDir, "captions.srt");
38337
38477
  try {
38338
38478
  await execSafe(
38339
38479
  "ffmpeg",
@@ -38354,7 +38494,7 @@ async function executeCaption(options) {
38354
38494
  );
38355
38495
  const whisper = new WhisperProvider();
38356
38496
  await whisper.initialize({ apiKey: openaiKey });
38357
- const audioBuffer = await readFile22(audioPath);
38497
+ const audioBuffer = await readFile23(audioPath);
38358
38498
  const audioBlob = new Blob([audioBuffer]);
38359
38499
  const transcriptResult = await whisper.transcribe(audioBlob, language);
38360
38500
  if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
@@ -38402,7 +38542,7 @@ async function executeCaption(options) {
38402
38542
  const remotionErr = await ensureRemotionInstalled2();
38403
38543
  if (remotionErr) {
38404
38544
  const outputDir2 = dirname12(outputPath);
38405
- const outputSrtPath2 = join19(
38545
+ const outputSrtPath2 = join20(
38406
38546
  outputDir2,
38407
38547
  basename6(outputPath, extname5(outputPath)) + ".srt"
38408
38548
  );
@@ -38441,7 +38581,7 @@ SRT saved to: ${outputSrtPath2}` };
38441
38581
  });
38442
38582
  if (!renderResult.success) {
38443
38583
  const outputDir2 = dirname12(outputPath);
38444
- const outputSrtPath2 = join19(
38584
+ const outputSrtPath2 = join20(
38445
38585
  outputDir2,
38446
38586
  basename6(outputPath, extname5(outputPath)) + ".srt"
38447
38587
  );
@@ -38454,7 +38594,7 @@ SRT saved to: ${outputSrtPath2}`
38454
38594
  }
38455
38595
  }
38456
38596
  const outputDir = dirname12(outputPath);
38457
- const outputSrtPath = join19(
38597
+ const outputSrtPath = join20(
38458
38598
  outputDir,
38459
38599
  basename6(outputPath, extname5(outputPath)) + ".srt"
38460
38600
  );
@@ -38491,10 +38631,10 @@ var init_caption = __esm({
38491
38631
  });
38492
38632
 
38493
38633
  // src/commands/_shared/edit/noise-reduce.ts
38494
- import { existsSync as existsSync25 } from "node:fs";
38634
+ import { existsSync as existsSync26 } from "node:fs";
38495
38635
  async function executeNoiseReduce(options) {
38496
38636
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
38497
- if (!existsSync25(inputPath)) {
38637
+ if (!existsSync26(inputPath)) {
38498
38638
  return { success: false, error: `File not found: ${inputPath}` };
38499
38639
  }
38500
38640
  if (!commandExists("ffmpeg")) {
@@ -38551,7 +38691,7 @@ var init_noise_reduce = __esm({
38551
38691
  });
38552
38692
 
38553
38693
  // src/commands/_shared/edit/fade.ts
38554
- import { existsSync as existsSync26 } from "node:fs";
38694
+ import { existsSync as existsSync27 } from "node:fs";
38555
38695
  async function executeFade(options) {
38556
38696
  const {
38557
38697
  videoPath,
@@ -38561,7 +38701,7 @@ async function executeFade(options) {
38561
38701
  audioOnly = false,
38562
38702
  videoOnly = false
38563
38703
  } = options;
38564
- if (!existsSync26(videoPath)) {
38704
+ if (!existsSync27(videoPath)) {
38565
38705
  return { success: false, error: `Video not found: ${videoPath}` };
38566
38706
  }
38567
38707
  if (!commandExists("ffmpeg")) {
@@ -38628,8 +38768,8 @@ var init_fade = __esm({
38628
38768
  });
38629
38769
 
38630
38770
  // src/commands/_shared/edit/translate-srt.ts
38631
- import { existsSync as existsSync27 } from "node:fs";
38632
- import { readFile as readFile23, writeFile as writeFile23 } from "node:fs/promises";
38771
+ import { existsSync as existsSync28 } from "node:fs";
38772
+ import { readFile as readFile24, writeFile as writeFile23 } from "node:fs/promises";
38633
38773
  async function executeTranslateSrt(options) {
38634
38774
  const {
38635
38775
  srtPath,
@@ -38639,11 +38779,11 @@ async function executeTranslateSrt(options) {
38639
38779
  sourceLanguage,
38640
38780
  apiKey
38641
38781
  } = options;
38642
- if (!existsSync27(srtPath)) {
38782
+ if (!existsSync28(srtPath)) {
38643
38783
  return { success: false, error: `SRT file not found: ${srtPath}` };
38644
38784
  }
38645
38785
  try {
38646
- const srtContent = await readFile23(srtPath, "utf-8");
38786
+ const srtContent = await readFile24(srtPath, "utf-8");
38647
38787
  const segments = parseSRT(srtContent);
38648
38788
  if (segments.length === 0) {
38649
38789
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -38756,7 +38896,7 @@ var init_translate_srt = __esm({
38756
38896
  });
38757
38897
 
38758
38898
  // src/commands/_shared/edit/text-overlay.ts
38759
- import { existsSync as existsSync28 } from "node:fs";
38899
+ import { existsSync as existsSync29 } from "node:fs";
38760
38900
  import { resolve as resolve28 } from "node:path";
38761
38901
  function detectSystemFont() {
38762
38902
  const platform = process.platform;
@@ -38767,7 +38907,7 @@ function detectSystemFont() {
38767
38907
  "/Library/Fonts/Arial.ttf"
38768
38908
  ];
38769
38909
  for (const f of candidates) {
38770
- if (existsSync28(f)) return f;
38910
+ if (existsSync29(f)) return f;
38771
38911
  }
38772
38912
  } else if (platform === "linux") {
38773
38913
  const candidates = [
@@ -38776,7 +38916,7 @@ function detectSystemFont() {
38776
38916
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
38777
38917
  ];
38778
38918
  for (const f of candidates) {
38779
- if (existsSync28(f)) return f;
38919
+ if (existsSync29(f)) return f;
38780
38920
  }
38781
38921
  } else if (platform === "win32") {
38782
38922
  const candidates = [
@@ -38784,7 +38924,7 @@ function detectSystemFont() {
38784
38924
  "C:\\Windows\\Fonts\\segoeui.ttf"
38785
38925
  ];
38786
38926
  for (const f of candidates) {
38787
- if (existsSync28(f)) return f;
38927
+ if (existsSync29(f)) return f;
38788
38928
  }
38789
38929
  }
38790
38930
  return null;
@@ -38805,7 +38945,7 @@ async function applyTextOverlays(options) {
38805
38945
  }
38806
38946
  const absVideoPath = resolve28(process.cwd(), videoPath);
38807
38947
  const absOutputPath = resolve28(process.cwd(), outputPath);
38808
- if (!existsSync28(absVideoPath)) {
38948
+ if (!existsSync29(absVideoPath)) {
38809
38949
  return { success: false, error: `Video not found: ${absVideoPath}` };
38810
38950
  }
38811
38951
  if (!commandExists("ffmpeg")) {
@@ -38986,7 +39126,7 @@ var init_ai_edit = __esm({
38986
39126
 
38987
39127
  // src/commands/ai-edit-cli.ts
38988
39128
  import { resolve as resolve29, extname as extname6, basename as basename7 } from "node:path";
38989
- import { existsSync as existsSync29 } from "node:fs";
39129
+ import { existsSync as existsSync30 } from "node:fs";
38990
39130
  function registerEditCommands(aiCommand) {
38991
39131
  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 \u2014 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", `
38992
39132
  Examples:
@@ -39003,7 +39143,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
39003
39143
  validateOutputPath(options.output);
39004
39144
  }
39005
39145
  const absVideoPath = resolve29(process.cwd(), videoPath);
39006
- if (!existsSync29(absVideoPath)) {
39146
+ if (!existsSync30(absVideoPath)) {
39007
39147
  exitWithError(notFoundError(absVideoPath));
39008
39148
  }
39009
39149
  if (!commandExists("ffmpeg")) {
@@ -39111,7 +39251,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39111
39251
  validateOutputPath(options.output);
39112
39252
  }
39113
39253
  const absVideoPath = resolve29(process.cwd(), videoPath);
39114
- if (!existsSync29(absVideoPath)) {
39254
+ if (!existsSync30(absVideoPath)) {
39115
39255
  exitWithError(notFoundError(absVideoPath));
39116
39256
  }
39117
39257
  if (!commandExists("ffmpeg")) {
@@ -39192,7 +39332,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39192
39332
  validateOutputPath(options.output);
39193
39333
  }
39194
39334
  const absInputPath = resolve29(process.cwd(), inputPath);
39195
- if (!existsSync29(absInputPath)) {
39335
+ if (!existsSync30(absInputPath)) {
39196
39336
  exitWithError(notFoundError(absInputPath));
39197
39337
  }
39198
39338
  if (!commandExists("ffmpeg")) {
@@ -39258,7 +39398,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39258
39398
  validateOutputPath(options.output);
39259
39399
  }
39260
39400
  const absVideoPath = resolve29(process.cwd(), videoPath);
39261
- if (!existsSync29(absVideoPath)) {
39401
+ if (!existsSync30(absVideoPath)) {
39262
39402
  exitWithError(notFoundError(absVideoPath));
39263
39403
  }
39264
39404
  if (!commandExists("ffmpeg")) {
@@ -39333,7 +39473,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39333
39473
  exitWithError(usageError("Target language required. Use -t or --target"));
39334
39474
  }
39335
39475
  const absSrtPath = resolve29(process.cwd(), srtPath);
39336
- if (!existsSync29(absSrtPath)) {
39476
+ if (!existsSync30(absSrtPath)) {
39337
39477
  exitWithError(notFoundError(absSrtPath));
39338
39478
  }
39339
39479
  if (options.dryRun) {
@@ -39409,7 +39549,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39409
39549
  }
39410
39550
  if (options.fillers) rejectControlChars(options.fillers);
39411
39551
  const absVideoPath = resolve29(process.cwd(), videoPath);
39412
- if (!existsSync29(absVideoPath)) {
39552
+ if (!existsSync30(absVideoPath)) {
39413
39553
  exitWithError(notFoundError(absVideoPath));
39414
39554
  }
39415
39555
  if (!commandExists("ffmpeg")) {
@@ -39902,8 +40042,8 @@ var init_project_resolver = __esm({
39902
40042
  });
39903
40043
 
39904
40044
  // src/commands/_shared/execute-fill-gaps.ts
39905
- import { existsSync as existsSync30 } from "node:fs";
39906
- import { readFile as readFile24, writeFile as writeFile24, mkdir as mkdir14, rename as renameFs } from "node:fs/promises";
40045
+ import { existsSync as existsSync31 } from "node:fs";
40046
+ import { readFile as readFile25, writeFile as writeFile24, mkdir as mkdir14, rename as renameFs } from "node:fs/promises";
39907
40047
  import { resolve as resolve31, dirname as dirname13 } from "node:path";
39908
40048
  function detectVideoGaps(videoClips, totalDuration) {
39909
40049
  const gaps = [];
@@ -39960,7 +40100,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
39960
40100
  }
39961
40101
  async function uploadFrameToImgbb(framePath, imgbbApiKey) {
39962
40102
  try {
39963
- const frameBuffer = await readFile24(framePath);
40103
+ const frameBuffer = await readFile25(framePath);
39964
40104
  const frameBase64 = frameBuffer.toString("base64");
39965
40105
  const formData = new FormData();
39966
40106
  formData.append("key", imgbbApiKey);
@@ -39985,14 +40125,14 @@ async function executeFillGaps(options) {
39985
40125
  try {
39986
40126
  onProgress("Loading timeline...");
39987
40127
  const filePath = await resolveTimelineFile(options.projectPath);
39988
- if (!existsSync30(filePath)) {
40128
+ if (!existsSync31(filePath)) {
39989
40129
  return {
39990
40130
  success: false,
39991
40131
  error: `Timeline file not found: ${filePath}`,
39992
40132
  humanLines
39993
40133
  };
39994
40134
  }
39995
- const content = await readFile24(filePath, "utf-8");
40135
+ const content = await readFile25(filePath, "utf-8");
39996
40136
  const data = JSON.parse(content);
39997
40137
  const project = Project.fromJSON(data);
39998
40138
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -40120,7 +40260,7 @@ async function executeFillGaps(options) {
40120
40260
  }
40121
40261
  const projectDir = dirname13(filePath);
40122
40262
  const footageDir = options.dir ? resolve31(process.cwd(), options.dir) : resolve31(projectDir, "footage");
40123
- if (!existsSync30(footageDir)) {
40263
+ if (!existsSync31(footageDir)) {
40124
40264
  await mkdir14(footageDir, { recursive: true });
40125
40265
  }
40126
40266
  humanLines.push("Generating AI Videos");
@@ -40440,8 +40580,8 @@ __export(ai_motion_exports, {
40440
40580
  registerMotionCommand: () => registerMotionCommand
40441
40581
  });
40442
40582
  import { resolve as resolve32 } from "node:path";
40443
- import { existsSync as existsSync31 } from "node:fs";
40444
- import { readFile as readFile25, writeFile as writeFile25 } from "node:fs/promises";
40583
+ import { existsSync as existsSync32 } from "node:fs";
40584
+ import { readFile as readFile26, writeFile as writeFile25 } from "node:fs/promises";
40445
40585
  async function getOptionalGoogleApiKey() {
40446
40586
  const configKey = await getApiKeyFromConfig("google");
40447
40587
  if (configKey) return configKey;
@@ -40488,7 +40628,7 @@ async function executeMotion(options) {
40488
40628
  };
40489
40629
  }
40490
40630
  const imagePath = resolve32(process.cwd(), options.image);
40491
- const imageBuffer = await readFile25(imagePath);
40631
+ const imageBuffer = await readFile26(imagePath);
40492
40632
  const gemini = new GeminiProvider();
40493
40633
  await gemini.initialize({ apiKey: geminiApiKey });
40494
40634
  const analysisResult = await gemini.analyzeImage(
@@ -40525,7 +40665,7 @@ Use this image analysis to inform the color palette, typography placement, and o
40525
40665
  }
40526
40666
  if (geminiApiKey) {
40527
40667
  const videoPath = resolve32(process.cwd(), options.video);
40528
- const videoBuffer = await readFile25(videoPath);
40668
+ const videoBuffer = await readFile26(videoPath);
40529
40669
  const gemini = new GeminiProvider();
40530
40670
  await gemini.initialize({ apiKey: geminiApiKey });
40531
40671
  const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
@@ -40556,10 +40696,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
40556
40696
  let result;
40557
40697
  if (options.fromTsx) {
40558
40698
  const tsxPath = resolve32(process.cwd(), options.fromTsx);
40559
- if (!existsSync31(tsxPath)) {
40699
+ if (!existsSync32(tsxPath)) {
40560
40700
  return { success: false, error: `TSX file not found: ${tsxPath}` };
40561
40701
  }
40562
- const existingCode = await readFile25(tsxPath, "utf-8");
40702
+ const existingCode = await readFile26(tsxPath, "utf-8");
40563
40703
  if (useGemini) {
40564
40704
  const gemini = new GeminiProvider();
40565
40705
  await gemini.initialize({ apiKey });
@@ -41088,7 +41228,7 @@ __export(project_builder_exports, {
41088
41228
  resolveSourceUrl: () => resolveSourceUrl
41089
41229
  });
41090
41230
  import { mkdtemp, mkdir as mkdir15, copyFile as copyFile5, writeFile as writeFile26, rm as rm3, readdir as readdir3 } from "node:fs/promises";
41091
- import { existsSync as existsSync32 } from "node:fs";
41231
+ import { existsSync as existsSync33 } from "node:fs";
41092
41232
  import { tmpdir as tmpdir2 } from "node:os";
41093
41233
  import { createRequire } from "node:module";
41094
41234
  import * as path from "node:path";
@@ -41100,7 +41240,7 @@ async function buildTempProject(state, projectFileDir) {
41100
41240
  for (const source of state.sources) {
41101
41241
  if (copied.has(source.id)) continue;
41102
41242
  const resolved = resolveSourceUrl(source.url, projectFileDir);
41103
- if (!existsSync32(resolved)) {
41243
+ if (!existsSync33(resolved)) {
41104
41244
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
41105
41245
  }
41106
41246
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -41237,7 +41377,7 @@ __export(motion_overlay_exports, {
41237
41377
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
41238
41378
  });
41239
41379
  import { mkdir as mkdir16 } from "node:fs/promises";
41240
- import { existsSync as existsSync33 } from "node:fs";
41380
+ import { existsSync as existsSync34 } from "node:fs";
41241
41381
  import { basename as basename9, dirname as dirname15, extname as extname7, resolve as resolve34 } from "node:path";
41242
41382
  function normalizeUnderstand2(value) {
41243
41383
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
@@ -41368,11 +41508,11 @@ async function executeMotionOverlay(options) {
41368
41508
  const validationError = assertValidRequest(options);
41369
41509
  if (validationError) return { success: false, error: validationError };
41370
41510
  const absVideo = resolve34(process.cwd(), options.videoPath);
41371
- if (!existsSync33(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
41511
+ if (!existsSync34(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
41372
41512
  const outputPath = options.output ? resolve34(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
41373
41513
  if (options.asset) {
41374
41514
  const absAsset = resolve34(process.cwd(), options.asset);
41375
- if (!existsSync33(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
41515
+ if (!existsSync34(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
41376
41516
  const [videoDuration, size] = await Promise.all([
41377
41517
  ffprobeDuration(absVideo),
41378
41518
  ffprobeVideoSize(absVideo)
@@ -41539,7 +41679,7 @@ __export(edit_cmd_exports, {
41539
41679
  executeUpscale: () => executeUpscale
41540
41680
  });
41541
41681
  import { resolve as resolve35, dirname as dirname16 } from "node:path";
41542
- import { readFile as readFile26, writeFile as writeFile27, mkdir as mkdir17 } from "node:fs/promises";
41682
+ import { readFile as readFile27, writeFile as writeFile27, mkdir as mkdir17 } from "node:fs/promises";
41543
41683
  async function executeGrade(options) {
41544
41684
  const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
41545
41685
  try {
@@ -41580,7 +41720,7 @@ async function executeSpeedRamp(options) {
41580
41720
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
41581
41721
  const whisper = new WhisperProvider();
41582
41722
  await whisper.initialize({ apiKey: openaiKey });
41583
- const audioBuffer = await readFile26(tempAudio);
41723
+ const audioBuffer = await readFile27(tempAudio);
41584
41724
  const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
41585
41725
  try {
41586
41726
  const { unlink: unlink5 } = await import("node:fs/promises");
@@ -41974,7 +42114,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
41974
42114
  spinner2.text = "Transcribing audio...";
41975
42115
  const whisper = new WhisperProvider();
41976
42116
  await whisper.initialize({ apiKey: openaiApiKey });
41977
- const audioBuffer = await readFile26(tempAudio);
42117
+ const audioBuffer = await readFile27(tempAudio);
41978
42118
  const audioBlob = new Blob([audioBuffer]);
41979
42119
  const transcript = await whisper.transcribe(audioBlob, options.language);
41980
42120
  if (!transcript.segments || transcript.segments.length === 0) {
@@ -42107,7 +42247,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42107
42247
  for (let i = 1; i <= numKeyframes && i <= 30; i++) {
42108
42248
  const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
42109
42249
  try {
42110
- const frameBuffer = await readFile26(framePath);
42250
+ const frameBuffer = await readFile27(framePath);
42111
42251
  const frameBase64 = frameBuffer.toString("base64");
42112
42252
  const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
42113
42253
  focusMode: options.focus,
@@ -42236,7 +42376,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42236
42376
  const imageBuffers = [];
42237
42377
  for (const imagePath of imagePaths) {
42238
42378
  const absPath = resolve35(process.cwd(), imagePath);
42239
- const buffer = await readFile26(absPath);
42379
+ const buffer = await readFile27(absPath);
42240
42380
  imageBuffers.push(buffer);
42241
42381
  }
42242
42382
  let result;
@@ -42513,8 +42653,8 @@ __export(ai_image_exports, {
42513
42653
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
42514
42654
  });
42515
42655
  import { resolve as resolve37, dirname as dirname18 } from "node:path";
42516
- import { readFile as readFile27, writeFile as writeFile29, mkdir as mkdir19 } from "node:fs/promises";
42517
- import { existsSync as existsSync35 } from "node:fs";
42656
+ import { readFile as readFile28, writeFile as writeFile29, mkdir as mkdir19 } from "node:fs/promises";
42657
+ import { existsSync as existsSync36 } from "node:fs";
42518
42658
  async function executeImageGenerate(options) {
42519
42659
  const {
42520
42660
  prompt: prompt3,
@@ -42658,10 +42798,10 @@ async function executeGeminiEdit(options) {
42658
42798
  const imageBuffers = [];
42659
42799
  for (const imagePath of imagePaths) {
42660
42800
  const absPath = resolve37(process.cwd(), imagePath);
42661
- if (!existsSync35(absPath)) {
42801
+ if (!existsSync36(absPath)) {
42662
42802
  return { success: false, error: `Image not found: ${absPath}` };
42663
42803
  }
42664
- const buffer = await readFile27(absPath);
42804
+ const buffer = await readFile28(absPath);
42665
42805
  imageBuffers.push(buffer);
42666
42806
  }
42667
42807
  const gemini = new GeminiProvider();
@@ -42706,7 +42846,7 @@ async function executeThumbnailBestFrame(options) {
42706
42846
  model = "flash",
42707
42847
  apiKey
42708
42848
  } = options;
42709
- if (!existsSync35(videoPath)) {
42849
+ if (!existsSync36(videoPath)) {
42710
42850
  return { success: false, error: `Video not found: ${videoPath}` };
42711
42851
  }
42712
42852
  if (!commandExists("ffmpeg")) {
@@ -42719,7 +42859,7 @@ async function executeThumbnailBestFrame(options) {
42719
42859
  try {
42720
42860
  const gemini = new GeminiProvider();
42721
42861
  await gemini.initialize({ apiKey: googleKey });
42722
- const videoData = await readFile27(videoPath);
42862
+ const videoData = await readFile28(videoPath);
42723
42863
  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>"}';
42724
42864
  const modelId = resolveGeminiTextModel(model);
42725
42865
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
@@ -42740,7 +42880,7 @@ async function executeThumbnailBestFrame(options) {
42740
42880
  reason = reasonMatch[1];
42741
42881
  }
42742
42882
  await execSafe("ffmpeg", ["-ss", String(timestamp), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
42743
- if (!existsSync35(outputPath)) {
42883
+ if (!existsSync36(outputPath)) {
42744
42884
  return { success: false, error: "FFmpeg failed to extract frame" };
42745
42885
  }
42746
42886
  return {
@@ -42771,9 +42911,9 @@ __export(ai_analyze_exports, {
42771
42911
  executeGeminiVideo: () => executeGeminiVideo,
42772
42912
  registerAnalyzeCommands: () => registerAnalyzeCommands
42773
42913
  });
42774
- import { readFile as readFile28 } from "node:fs/promises";
42914
+ import { readFile as readFile29 } from "node:fs/promises";
42775
42915
  import { extname as extname8, resolve as resolve38 } from "node:path";
42776
- import { existsSync as existsSync36 } from "node:fs";
42916
+ import { existsSync as existsSync37 } from "node:fs";
42777
42917
  async function executeGeminiVideo(options) {
42778
42918
  try {
42779
42919
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -42787,10 +42927,10 @@ async function executeGeminiVideo(options) {
42787
42927
  videoData = options.source;
42788
42928
  } else {
42789
42929
  const absPath = resolve38(process.cwd(), options.source);
42790
- if (!existsSync36(absPath)) {
42930
+ if (!existsSync37(absPath)) {
42791
42931
  return { success: false, error: `File not found: ${absPath}` };
42792
42932
  }
42793
- videoData = await readFile28(absPath);
42933
+ videoData = await readFile29(absPath);
42794
42934
  }
42795
42935
  const gemini = new GeminiProvider();
42796
42936
  await gemini.initialize({ apiKey });
@@ -42855,10 +42995,10 @@ async function executeAnalyze(options) {
42855
42995
  imageBuffer = Buffer.from(await response.arrayBuffer());
42856
42996
  } else {
42857
42997
  const absPath = resolve38(process.cwd(), source);
42858
- if (!existsSync36(absPath)) {
42998
+ if (!existsSync37(absPath)) {
42859
42999
  return { success: false, error: `File not found: ${absPath}` };
42860
43000
  }
42861
- imageBuffer = await readFile28(absPath);
43001
+ imageBuffer = await readFile29(absPath);
42862
43002
  }
42863
43003
  const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
42864
43004
  model: modelId,
@@ -42890,10 +43030,10 @@ async function executeAnalyze(options) {
42890
43030
  videoData = Buffer.from(await response.arrayBuffer());
42891
43031
  } else {
42892
43032
  const absPath = resolve38(process.cwd(), source);
42893
- if (!existsSync36(absPath)) {
43033
+ if (!existsSync37(absPath)) {
42894
43034
  return { success: false, error: `File not found: ${absPath}` };
42895
43035
  }
42896
- videoData = await readFile28(absPath);
43036
+ videoData = await readFile29(absPath);
42897
43037
  }
42898
43038
  const result = await gemini.analyzeVideo(videoData, options.prompt, {
42899
43039
  model: modelId,
@@ -43027,9 +43167,9 @@ __export(ai_review_exports, {
43027
43167
  executeReview: () => executeReview,
43028
43168
  registerReviewCommand: () => registerReviewCommand
43029
43169
  });
43030
- import { readFile as readFile29, rename as rename3 } from "node:fs/promises";
43170
+ import { readFile as readFile30, rename as rename3 } from "node:fs/promises";
43031
43171
  import { resolve as resolve39 } from "node:path";
43032
- import { existsSync as existsSync37 } from "node:fs";
43172
+ import { existsSync as existsSync38 } from "node:fs";
43033
43173
  function parseReviewFeedback(response) {
43034
43174
  let cleaned = response.trim();
43035
43175
  if (cleaned.startsWith("```json")) {
@@ -43061,7 +43201,7 @@ async function executeReview(options) {
43061
43201
  model = "flash"
43062
43202
  } = options;
43063
43203
  const absVideoPath = resolve39(process.cwd(), videoPath);
43064
- if (!existsSync37(absVideoPath)) {
43204
+ if (!existsSync38(absVideoPath)) {
43065
43205
  return { success: false, error: `Video not found: ${absVideoPath}` };
43066
43206
  }
43067
43207
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -43074,8 +43214,8 @@ async function executeReview(options) {
43074
43214
  let storyboardContext = "";
43075
43215
  if (storyboardPath) {
43076
43216
  const absStoryboardPath = resolve39(process.cwd(), storyboardPath);
43077
- if (existsSync37(absStoryboardPath)) {
43078
- const content = await readFile29(absStoryboardPath, "utf-8");
43217
+ if (existsSync38(absStoryboardPath)) {
43218
+ const content = await readFile30(absStoryboardPath, "utf-8");
43079
43219
  storyboardContext = `
43080
43220
 
43081
43221
  Original storyboard for reference:
@@ -43117,7 +43257,7 @@ ${projectContext}` : "";
43117
43257
  Score each category 1-10. Prefer beatIssues when you can map a problem to a storyboard beat or timestamp. Use the exact beatId values from the storyboard or beat timing summary, and include timeRange in seconds for every localized finding. In particular, flag narration/visual mismatches, overly static holds, and audio-visual sync problems as beatIssues. Use category issue arrays only for problems that cannot be localized to a beat. For fixable issues, provide an FFmpeg filter in autoFixable. Be specific and practical.${storyboardContext}${extraProjectContext}`;
43118
43258
  const gemini = new GeminiProvider();
43119
43259
  await gemini.initialize({ apiKey });
43120
- const videoData = await readFile29(absVideoPath);
43260
+ const videoData = await readFile30(absVideoPath);
43121
43261
  const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
43122
43262
  model: modelId
43123
43263
  });
@@ -43172,7 +43312,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
43172
43312
  }
43173
43313
  }
43174
43314
  if (verify && result.outputPath) {
43175
- const verifyVideoData = await readFile29(result.outputPath);
43315
+ const verifyVideoData = await readFile30(result.outputPath);
43176
43316
  const verifyResult = await gemini.analyzeVideo(
43177
43317
  verifyVideoData,
43178
43318
  'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
@@ -43636,7 +43776,7 @@ var init_sanitize = __esm({
43636
43776
 
43637
43777
  // src/commands/generate/storyboard.ts
43638
43778
  import { resolve as resolve46 } from "node:path";
43639
- import { readFile as readFile33, writeFile as writeFile33 } from "node:fs/promises";
43779
+ import { readFile as readFile34, writeFile as writeFile33 } from "node:fs/promises";
43640
43780
  async function executeStoryboard(options) {
43641
43781
  const { content, duration, creativity = "low", output, apiKey } = options;
43642
43782
  try {
@@ -43684,7 +43824,7 @@ function registerStoryboardCommand(parent) {
43684
43824
  let textContent = content;
43685
43825
  if (options.file) {
43686
43826
  const filePath = resolve46(process.cwd(), content);
43687
- textContent = await readFile33(filePath, "utf-8");
43827
+ textContent = await readFile34(filePath, "utf-8");
43688
43828
  }
43689
43829
  if (options.dryRun) {
43690
43830
  outputSuccess({
@@ -44077,7 +44217,7 @@ var init_speech = __esm({
44077
44217
 
44078
44218
  // src/commands/generate/thumbnail.ts
44079
44219
  import { resolve as resolve48, dirname as dirname20, basename as basename13, extname as extname9 } from "node:path";
44080
- import { existsSync as existsSync40 } from "node:fs";
44220
+ import { existsSync as existsSync41 } from "node:fs";
44081
44221
  import { writeFile as writeFile35, mkdir as mkdir21 } from "node:fs/promises";
44082
44222
  function registerThumbnailCommand(parent) {
44083
44223
  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) => {
@@ -44089,7 +44229,7 @@ function registerThumbnailCommand(parent) {
44089
44229
  }
44090
44230
  if (options.bestFrame) {
44091
44231
  const absVideoPath = resolve48(process.cwd(), options.bestFrame);
44092
- if (!existsSync40(absVideoPath)) {
44232
+ if (!existsSync41(absVideoPath)) {
44093
44233
  exitWithError(notFoundError(absVideoPath));
44094
44234
  }
44095
44235
  if (!commandExists("ffmpeg")) {
@@ -46273,7 +46413,7 @@ var init_dist3 = __esm({
46273
46413
 
46274
46414
  // src/commands/generate/video.ts
46275
46415
  import { resolve as resolve52 } from "node:path";
46276
- import { readFile as readFile34, writeFile as writeFile39 } from "node:fs/promises";
46416
+ import { readFile as readFile35, writeFile as writeFile39 } from "node:fs/promises";
46277
46417
  function registerVideoCommand(parent) {
46278
46418
  parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
46279
46419
  "-p, --provider <provider>",
@@ -46384,7 +46524,7 @@ Examples:
46384
46524
  let isImageToVideo = false;
46385
46525
  if (options.image) {
46386
46526
  const imagePath = resolve52(process.cwd(), options.image);
46387
- const imageBuffer = await readFile34(imagePath);
46527
+ const imageBuffer = await readFile35(imagePath);
46388
46528
  const ext = options.image.toLowerCase().split(".").pop();
46389
46529
  const mimeTypes = {
46390
46530
  jpg: "image/jpeg",
@@ -46624,7 +46764,7 @@ Examples:
46624
46764
  let lastFrame;
46625
46765
  if (options.lastFrame) {
46626
46766
  const lastFramePath = resolve52(process.cwd(), options.lastFrame);
46627
- const lastFrameBuffer = await readFile34(lastFramePath);
46767
+ const lastFrameBuffer = await readFile35(lastFramePath);
46628
46768
  const ext = options.lastFrame.toLowerCase().split(".").pop();
46629
46769
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46630
46770
  lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
@@ -46634,7 +46774,7 @@ Examples:
46634
46774
  refImages = [];
46635
46775
  for (const refPath of options.refImages.slice(0, 3)) {
46636
46776
  const absRefPath = resolve52(process.cwd(), refPath);
46637
- const refBuffer = await readFile34(absRefPath);
46777
+ const refBuffer = await readFile35(absRefPath);
46638
46778
  const ext = refPath.toLowerCase().split(".").pop();
46639
46779
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46640
46780
  refImages.push({ base64: refBuffer.toString("base64"), mimeType });
@@ -46766,7 +46906,7 @@ Examples:
46766
46906
  if (seedanceEndImagePath && seedanceReferences.length === 0) {
46767
46907
  try {
46768
46908
  const absEndImagePath = resolve52(process.cwd(), seedanceEndImagePath);
46769
- const endImageBuffer = await readFile34(absEndImagePath);
46909
+ const endImageBuffer = await readFile35(absEndImagePath);
46770
46910
  const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
46771
46911
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46772
46912
  const uploadHost = await resolveUploadHost();
@@ -46883,7 +47023,7 @@ async function fileInputToUrlOrDataUri2(input, fallbackMimeType) {
46883
47023
  return input;
46884
47024
  }
46885
47025
  const absPath = resolve52(process.cwd(), input);
46886
- const buffer = await readFile34(absPath);
47026
+ const buffer = await readFile35(absPath);
46887
47027
  return `data:${mimeTypeForPath2(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
46888
47028
  }
46889
47029
  function mimeTypeForPath2(path2, fallback2) {
@@ -47891,7 +48031,7 @@ __export(detect_exports, {
47891
48031
  executeDetectScenes: () => executeDetectScenes,
47892
48032
  executeDetectSilence: () => executeDetectSilence
47893
48033
  });
47894
- import { readFile as readFile39, writeFile as writeFile44 } from "node:fs/promises";
48034
+ import { readFile as readFile40, writeFile as writeFile44 } from "node:fs/promises";
47895
48035
  import { resolve as resolve57, basename as basename15 } from "node:path";
47896
48036
  async function executeDetectScenes(options) {
47897
48037
  try {
@@ -48135,7 +48275,7 @@ var init_detect = __esm({
48135
48275
  }
48136
48276
  if (options.project) {
48137
48277
  const projectPath = resolve57(process.cwd(), options.project);
48138
- const content = await readFile39(projectPath, "utf-8");
48278
+ const content = await readFile40(projectPath, "utf-8");
48139
48279
  const data = JSON.parse(content);
48140
48280
  const project = Project.fromJSON(data);
48141
48281
  let source = project.getSources().find((s) => s.url === absPath);
@@ -52986,8 +53126,8 @@ init_scene_project();
52986
53126
  init_project_config();
52987
53127
  init_cost_tier();
52988
53128
  import { resolve as resolve17, relative as relative6, dirname as dirname6 } from "node:path";
52989
- import { mkdir as mkdir8, readFile as readFile14, writeFile as writeFile15, access as access4, copyFile as copyFile2 } from "node:fs/promises";
52990
- import { existsSync as existsSync13 } from "node:fs";
53129
+ import { mkdir as mkdir8, readFile as readFile15, writeFile as writeFile15, access as access4, copyFile as copyFile2 } from "node:fs/promises";
53130
+ import { existsSync as existsSync14 } from "node:fs";
52991
53131
 
52992
53132
  // src/commands/_shared/scene-html-emit.ts
52993
53133
  var SCENE_PRESETS = [
@@ -53556,15 +53696,16 @@ function deriveInstallHosts(detected) {
53556
53696
 
53557
53697
  // src/commands/scene.ts
53558
53698
  init_compose_prompts();
53699
+ init_transcribe_narration();
53559
53700
 
53560
53701
  // src/commands/_shared/scene-submit.ts
53561
53702
  init_compose_scenes_skills();
53562
53703
  init_scene_lint();
53563
53704
  init_storyboard_parse();
53564
53705
  init_root_sync();
53565
- import { existsSync as existsSync11 } from "node:fs";
53566
- import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile13 } from "node:fs/promises";
53567
- import { join as join11, resolve as resolve14 } from "node:path";
53706
+ import { existsSync as existsSync12 } from "node:fs";
53707
+ import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile13 } from "node:fs/promises";
53708
+ import { join as join12, resolve as resolve14 } from "node:path";
53568
53709
  async function executeSceneSubmit(opts) {
53569
53710
  const projectDir = resolve14(opts.projectDir);
53570
53711
  const beatId = opts.beatId.trim();
@@ -53578,15 +53719,15 @@ async function executeSceneSubmit(opts) {
53578
53719
  warnings: [],
53579
53720
  retryWith: []
53580
53721
  };
53581
- const storyboardPath = join11(projectDir, "STORYBOARD.md");
53582
- if (!existsSync11(storyboardPath)) {
53722
+ const storyboardPath = join12(projectDir, "STORYBOARD.md");
53723
+ if (!existsSync12(storyboardPath)) {
53583
53724
  return {
53584
53725
  ...base,
53585
53726
  success: false,
53586
53727
  error: `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe init <dir>\` first.`
53587
53728
  };
53588
53729
  }
53589
- const parsed = parseStoryboard(await readFile11(storyboardPath, "utf-8"));
53730
+ const parsed = parseStoryboard(await readFile12(storyboardPath, "utf-8"));
53590
53731
  const beat = parsed.beats.find((b) => b.id === beatId);
53591
53732
  if (!beat) {
53592
53733
  const available = parsed.beats.map((b) => b.id).join(", ") || "(none)";
@@ -53656,8 +53797,8 @@ async function executeSceneSubmit(opts) {
53656
53797
  if (opts.validateOnly) {
53657
53798
  return { ...base, success: true };
53658
53799
  }
53659
- const absPath = join11(projectDir, scenePath);
53660
- await mkdir7(join11(projectDir, "compositions"), { recursive: true });
53800
+ const absPath = join12(projectDir, scenePath);
53801
+ await mkdir7(join12(projectDir, "compositions"), { recursive: true });
53661
53802
  await writeFile13(absPath, html.endsWith("\n") ? html : `${html}
53662
53803
  `, "utf-8");
53663
53804
  return {
@@ -53858,7 +53999,7 @@ sceneCommand.command("submit").description(
53858
53999
  }
53859
54000
  html = Buffer.concat(chunks).toString("utf-8");
53860
54001
  } else if (options.file) {
53861
- html = await readFile14(resolve17(String(options.file)), "utf-8");
54002
+ html = await readFile15(resolve17(String(options.file)), "utf-8");
53862
54003
  } else {
53863
54004
  exitWithError(
53864
54005
  usageError(
@@ -54188,8 +54329,8 @@ function readClipStart(rootHtml, beatId) {
54188
54329
  async function resolveNarrationText(value) {
54189
54330
  if (!value) return void 0;
54190
54331
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
54191
- if (looksLikePath && existsSync13(value)) {
54192
- return (await readFile14(value, "utf-8")).trim();
54332
+ if (looksLikePath && existsSync14(value)) {
54333
+ return (await readFile15(value, "utf-8")).trim();
54193
54334
  }
54194
54335
  return value.trim();
54195
54336
  }
@@ -54228,7 +54369,7 @@ async function executeSceneAdd(opts) {
54228
54369
  if (!opts.force && await pathExists2(scenePath)) {
54229
54370
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
54230
54371
  }
54231
- const rootHtmlBefore = await readFile14(rootPath, "utf-8");
54372
+ const rootHtmlBefore = await readFile15(rootPath, "utf-8");
54232
54373
  let dims = readRootDims(rootHtmlBefore);
54233
54374
  if (!dims) {
54234
54375
  const cfg2 = await loadVibeProjectConfig(projectDir);
@@ -54304,31 +54445,20 @@ async function executeSceneAdd(opts) {
54304
54445
  );
54305
54446
  } else {
54306
54447
  opts.onProgress?.("Transcribing narration (Whisper word-level)...");
54307
- try {
54308
- const whisper = new WhisperProvider();
54309
- await whisper.initialize({ apiKey: whisperKey });
54310
- const audioBytes = await readFile14(audioAbsPath);
54311
- const audioBlob = new Blob([new Uint8Array(audioBytes)]);
54312
- const transcript = await whisper.transcribe(audioBlob, void 0, {
54313
- granularity: "word",
54314
- language: opts.transcribeLanguage
54315
- });
54316
- if (transcript.status === "completed" && transcript.words?.length) {
54317
- transcriptRelPath = `assets/transcript-${id}.json`;
54318
- const transcriptAbs = resolve17(projectDir, transcriptRelPath);
54319
- await writeFile15(transcriptAbs, JSON.stringify(transcript.words, null, 2), "utf-8");
54320
- transcriptWordCount = transcript.words.length;
54321
- transcriptWords = transcript.words.map((w) => ({
54322
- text: w.text,
54323
- start: w.start,
54324
- end: w.end
54325
- }));
54326
- } else if (transcript.status === "failed") {
54327
- opts.onProgress?.(`Transcribe failed: ${transcript.error ?? "unknown error"}`);
54328
- }
54329
- } catch (error) {
54330
- const msg = error instanceof Error ? error.message : String(error);
54331
- opts.onProgress?.(`Transcribe failed: ${msg}`);
54448
+ const words = await transcribeNarrationWords(audioAbsPath, {
54449
+ apiKey: whisperKey,
54450
+ language: opts.transcribeLanguage
54451
+ });
54452
+ if (words.length > 0) {
54453
+ transcriptRelPath = beatTranscriptRelPath(id);
54454
+ const transcriptAbs = resolve17(projectDir, transcriptRelPath);
54455
+ await writeFile15(transcriptAbs, JSON.stringify(words, null, 2), "utf-8");
54456
+ transcriptWordCount = words.length;
54457
+ transcriptWords = words;
54458
+ } else {
54459
+ opts.onProgress?.(
54460
+ "Transcribe produced no word timings \u2014 narration plays without word-sync"
54461
+ );
54332
54462
  }
54333
54463
  }
54334
54464
  }
@@ -54437,7 +54567,7 @@ async function executeSceneAdd(opts) {
54437
54567
  const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
54438
54568
  if (shouldSyncStoryboard) {
54439
54569
  opts.onProgress?.("Syncing STORYBOARD.md...");
54440
- const before = await readFile14(storyboardAbsPath, "utf-8");
54570
+ const before = await readFile15(storyboardAbsPath, "utf-8");
54441
54571
  const synced = upsertStoryboardBeat(before, {
54442
54572
  beatId: id,
54443
54573
  title: opts.headline,
@@ -54479,7 +54609,7 @@ async function executeSceneAdd(opts) {
54479
54609
  });
54480
54610
  const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
54481
54611
  if (blockingIssue) return errResult(blockingIssue.message);
54482
- const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
54612
+ const rootHtmlAfterSync = await readFile15(rootPath, "utf-8");
54483
54613
  start = readClipStart(rootHtmlAfterSync, id) ?? 0;
54484
54614
  } else {
54485
54615
  start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
@@ -54653,7 +54783,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
54653
54783
  const root = rootArg || "index.html";
54654
54784
  const project = projectArg || ".";
54655
54785
  const defaultProject = project === ".";
54656
- if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync13(resolve17(root, "index.html"))) {
54786
+ if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync14(resolve17(root, "index.html"))) {
54657
54787
  return { projectDir: resolve17(root), rootRel: "index.html" };
54658
54788
  }
54659
54789
  return { projectDir: resolve17(project), rootRel: root };
@@ -55522,7 +55652,7 @@ init_exec_safe();
55522
55652
  init_remotion();
55523
55653
  import { resolve as resolve36, dirname as dirname17, basename as basename10 } from "node:path";
55524
55654
  import { writeFile as writeFile28, mkdir as mkdir18, rm as rm4 } from "node:fs/promises";
55525
- import { existsSync as existsSync34 } from "node:fs";
55655
+ import { existsSync as existsSync35 } from "node:fs";
55526
55656
  import { tmpdir as tmpdir3 } from "node:os";
55527
55657
  var ASS_STYLES = ["karaoke-sweep", "typewriter"];
55528
55658
  var SENTENCE_BREAKS = /[.!?]/;
@@ -55703,7 +55833,7 @@ async function executeAnimatedCaption(options) {
55703
55833
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
55704
55834
  const absOutputPath = resolve36(process.cwd(), outputPath);
55705
55835
  const outDir = dirname17(absOutputPath);
55706
- if (!existsSync34(outDir)) {
55836
+ if (!existsSync35(outDir)) {
55707
55837
  await mkdir18(outDir, { recursive: true });
55708
55838
  }
55709
55839
  if (tier === "ass") {
@@ -56250,14 +56380,14 @@ init_ai_review();
56250
56380
  init_dist2();
56251
56381
  init_engine();
56252
56382
  init_ai_helpers();
56253
- import { readFile as readFile30, writeFile as writeFile30 } from "node:fs/promises";
56383
+ import { readFile as readFile31, writeFile as writeFile30 } from "node:fs/promises";
56254
56384
  import { resolve as resolve40 } from "node:path";
56255
56385
  async function executeSuggestEdit(options) {
56256
56386
  try {
56257
56387
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
56258
56388
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
56259
56389
  const filePath = resolve40(process.cwd(), options.projectPath);
56260
- const content = await readFile30(filePath, "utf-8");
56390
+ const content = await readFile31(filePath, "utf-8");
56261
56391
  const data = JSON.parse(content);
56262
56392
  const project = Project.fromJSON(data);
56263
56393
  const gemini = new GeminiProvider();
@@ -56294,9 +56424,9 @@ init_project_config();
56294
56424
  init_scene_lint();
56295
56425
  init_root_sync();
56296
56426
  init_review_report();
56297
- import { existsSync as existsSync38 } from "node:fs";
56298
- import { readdir as readdir4, readFile as readFile31, stat as stat5 } from "node:fs/promises";
56299
- import { basename as basename11, isAbsolute as isAbsolute5, join as join21, relative as relative11, resolve as resolve41 } from "node:path";
56427
+ import { existsSync as existsSync39 } from "node:fs";
56428
+ import { readdir as readdir4, readFile as readFile32, stat as stat5 } from "node:fs/promises";
56429
+ import { basename as basename11, isAbsolute as isAbsolute5, join as join22, relative as relative11, resolve as resolve41 } from "node:path";
56300
56430
  async function inspectProject(opts) {
56301
56431
  const projectDir = resolve41(opts.projectDir);
56302
56432
  const issues = [];
@@ -56310,7 +56440,7 @@ async function inspectProject(opts) {
56310
56440
  assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
56311
56441
  rootSync: { ok: null, issueCount: 0, issues: [] }
56312
56442
  };
56313
- if (!existsSync38(projectDir) || !await isDirectory(projectDir)) {
56443
+ if (!existsSync39(projectDir) || !await isDirectory(projectDir)) {
56314
56444
  issues.push({
56315
56445
  severity: "error",
56316
56446
  code: "PROJECT_NOT_FOUND",
@@ -56325,15 +56455,15 @@ async function inspectProject(opts) {
56325
56455
  );
56326
56456
  }
56327
56457
  const coreFiles = {
56328
- storyboard: join21(projectDir, "STORYBOARD.md"),
56329
- design: join21(projectDir, "DESIGN.md"),
56330
- config: join21(projectDir, "vibe.config.json"),
56331
- legacyConfig: join21(projectDir, "vibe.project.yaml"),
56332
- buildReport: join21(projectDir, "build-report.json"),
56333
- root: join21(projectDir, "index.html")
56458
+ storyboard: join22(projectDir, "STORYBOARD.md"),
56459
+ design: join22(projectDir, "DESIGN.md"),
56460
+ config: join22(projectDir, "vibe.config.json"),
56461
+ legacyConfig: join22(projectDir, "vibe.project.yaml"),
56462
+ buildReport: join22(projectDir, "build-report.json"),
56463
+ root: join22(projectDir, "index.html")
56334
56464
  };
56335
56465
  for (const [name, path2] of Object.entries(coreFiles)) {
56336
- checks.files[name] = existsSync38(path2);
56466
+ checks.files[name] = existsSync39(path2);
56337
56467
  }
56338
56468
  let beatIds = [];
56339
56469
  let inspectedBeatIds = [];
@@ -56347,7 +56477,7 @@ async function inspectProject(opts) {
56347
56477
  });
56348
56478
  retryWith.push(`vibe init ${projectDir} --from "<brief>" --json`);
56349
56479
  } else {
56350
- const storyboard = await readFile31(coreFiles.storyboard, "utf-8");
56480
+ const storyboard = await readFile32(coreFiles.storyboard, "utf-8");
56351
56481
  const validation = validateStoryboardMarkdown(storyboard);
56352
56482
  storyboardBeats = validation.beats;
56353
56483
  beatIds = validation.beats.map((beat) => beat.id);
@@ -56389,7 +56519,7 @@ async function inspectProject(opts) {
56389
56519
  suggestedFix: "Create DESIGN.md or rerun `vibe init --from` in a new project."
56390
56520
  });
56391
56521
  } else {
56392
- const design = await readFile31(coreFiles.design, "utf-8");
56522
+ const design = await readFile32(coreFiles.design, "utf-8");
56393
56523
  for (const issue of designPlaceholderIssues(design)) {
56394
56524
  issues.push(issue);
56395
56525
  }
@@ -56414,20 +56544,20 @@ async function inspectProject(opts) {
56414
56544
  });
56415
56545
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
56416
56546
  }
56417
- const compositionsDir = join21(projectDir, "compositions");
56418
- const existingComps = existsSync38(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
56547
+ const compositionsDir = join22(projectDir, "compositions");
56548
+ const existingComps = existsSync39(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
56419
56549
  checks.compositions.expected = inspectedBeatIds.length;
56420
56550
  for (const beatId of inspectedBeatIds) {
56421
56551
  const file = `scene-${beatId}.html`;
56422
56552
  if (existingComps.has(file)) {
56423
56553
  checks.compositions.found++;
56424
56554
  } else {
56425
- checks.compositions.missing.push(join21("compositions", file));
56555
+ checks.compositions.missing.push(join22("compositions", file));
56426
56556
  issues.push({
56427
56557
  severity: "error",
56428
56558
  code: "MISSING_COMPOSITION",
56429
56559
  message: `Composition for beat "${beatId}" is missing.`,
56430
- file: join21("compositions", file),
56560
+ file: join22("compositions", file),
56431
56561
  scene: beatId,
56432
56562
  beatId,
56433
56563
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -56489,7 +56619,7 @@ async function inspectProject(opts) {
56489
56619
  try {
56490
56620
  const lint = await runProjectLint({ projectDir });
56491
56621
  const lintFiles = opts.beatId ? lint.files.filter(
56492
- (file) => file.file === "index.html" || file.file === join21("compositions", `scene-${opts.beatId}.html`)
56622
+ (file) => file.file === "index.html" || file.file === join22("compositions", `scene-${opts.beatId}.html`)
56493
56623
  ) : lint.files;
56494
56624
  const lintCounts = countLintFindings(lintFiles);
56495
56625
  checks.lint = {
@@ -56635,7 +56765,7 @@ function projectSourceReports(checks) {
56635
56765
  async function inspectBuildReport(projectDir, reportPath, beatId, checks, issues, retryWith, storyboardBeats) {
56636
56766
  checks.buildReport.exists = true;
56637
56767
  try {
56638
- const report = JSON.parse(await readFile31(reportPath, "utf-8"));
56768
+ const report = JSON.parse(await readFile32(reportPath, "utf-8"));
56639
56769
  if (typeof report.outputPath === "string") checks.buildReport.outputPath = report.outputPath;
56640
56770
  const reportBeats = report.beats ?? [];
56641
56771
  const selectedReportBeats = reportBeats.filter((item) => !beatId || item.id === beatId);
@@ -56772,7 +56902,7 @@ function inspectReportedAsset(opts) {
56772
56902
  opts.checks.assets.checked++;
56773
56903
  if (isExternalRef2(opts.value)) return;
56774
56904
  const abs = isAbsolute5(opts.value) ? opts.value : resolve41(opts.projectDir, opts.value);
56775
- if (existsSync38(abs)) return;
56905
+ if (existsSync39(abs)) return;
56776
56906
  opts.checks.assets.missing.push(opts.value);
56777
56907
  opts.issues.push({
56778
56908
  severity: "warning",
@@ -56866,9 +56996,9 @@ init_exec_safe();
56866
56996
  init_project_config();
56867
56997
  init_storyboard_parse();
56868
56998
  init_review_report();
56869
- import { existsSync as existsSync39 } from "node:fs";
56870
- import { readdir as readdir5, readFile as readFile32, stat as stat6 } from "node:fs/promises";
56871
- import { basename as basename12, isAbsolute as isAbsolute6, join as join22, relative as relative12, resolve as resolve42 } from "node:path";
56999
+ import { existsSync as existsSync40 } from "node:fs";
57000
+ import { readdir as readdir5, readFile as readFile33, stat as stat6 } from "node:fs/promises";
57001
+ import { basename as basename12, isAbsolute as isAbsolute6, join as join23, relative as relative12, resolve as resolve42 } from "node:path";
56872
57002
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
56873
57003
  var DEFAULT_AI_MODEL = "flash";
56874
57004
  var BLACK_FRAME_MIN_DURATION_SEC = 1;
@@ -57369,11 +57499,11 @@ async function inspectRender(opts) {
57369
57499
  }
57370
57500
  async function renderExpectsAudio(projectDir, beatId) {
57371
57501
  const root = resolve42(projectDir);
57372
- const rootHtml = await readTextIfExists(join22(root, "index.html"));
57502
+ const rootHtml = await readTextIfExists(join23(root, "index.html"));
57373
57503
  if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
57374
- const buildReport = await readJsonIfExists(join22(root, "build-report.json"));
57504
+ const buildReport = await readJsonIfExists(join23(root, "build-report.json"));
57375
57505
  if (buildReportExpectsAudio(buildReport, beatId)) return true;
57376
- const storyboard = await readTextIfExists(join22(root, "STORYBOARD.md"));
57506
+ const storyboard = await readTextIfExists(join23(root, "STORYBOARD.md"));
57377
57507
  if (!storyboard) return false;
57378
57508
  try {
57379
57509
  const parsed = parseStoryboard(storyboard);
@@ -57384,7 +57514,7 @@ async function renderExpectsAudio(projectDir, beatId) {
57384
57514
  `assets/narration-${beat.id}.wav`,
57385
57515
  `assets/music-${beat.id}.mp3`,
57386
57516
  `assets/music-${beat.id}.wav`
57387
- ].some((asset) => existsSync39(join22(root, asset)))
57517
+ ].some((asset) => existsSync40(join23(root, asset)))
57388
57518
  );
57389
57519
  } catch {
57390
57520
  return false;
@@ -57492,8 +57622,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
57492
57622
  }
57493
57623
  function renderSourceReports(projectDir, videoPath, checks) {
57494
57624
  const reports = [];
57495
- if (existsSync39(join22(projectDir, "build-report.json"))) reports.push("build-report.json");
57496
- if (existsSync39(join22(projectDir, "render-report.json"))) reports.push("render-report.json");
57625
+ if (existsSync40(join23(projectDir, "build-report.json"))) reports.push("build-report.json");
57626
+ if (existsSync40(join23(projectDir, "render-report.json"))) reports.push("render-report.json");
57497
57627
  if (videoPath) reports.push(displayPath(videoPath));
57498
57628
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
57499
57629
  reports.push("ffprobe");
@@ -57506,31 +57636,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
57506
57636
  async function resolveRenderVideoPath(projectDir, explicit, beatId) {
57507
57637
  if (explicit) {
57508
57638
  const candidate = resolve42(process.cwd(), explicit);
57509
- return existsSync39(candidate) ? candidate : null;
57639
+ return existsSync40(candidate) ? candidate : null;
57510
57640
  }
57511
- const renderReportPath = join22(projectDir, "render-report.json");
57512
- if (existsSync39(renderReportPath)) {
57641
+ const renderReportPath = join23(projectDir, "render-report.json");
57642
+ if (existsSync40(renderReportPath)) {
57513
57643
  try {
57514
- const report = JSON.parse(await readFile32(renderReportPath, "utf-8"));
57644
+ const report = JSON.parse(await readFile33(renderReportPath, "utf-8"));
57515
57645
  const reportBeat = typeof report.beat === "string" ? report.beat : null;
57516
57646
  if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
57517
57647
  const candidate = resolveReportedPath(projectDir, report.outputPath);
57518
- if (candidate && existsSync39(candidate)) return candidate;
57648
+ if (candidate && existsSync40(candidate)) return candidate;
57519
57649
  }
57520
57650
  } catch {
57521
57651
  }
57522
57652
  }
57523
- const reportPath = join22(projectDir, "build-report.json");
57524
- if (!beatId && existsSync39(reportPath)) {
57653
+ const reportPath = join23(projectDir, "build-report.json");
57654
+ if (!beatId && existsSync40(reportPath)) {
57525
57655
  try {
57526
- const report = JSON.parse(await readFile32(reportPath, "utf-8"));
57656
+ const report = JSON.parse(await readFile33(reportPath, "utf-8"));
57527
57657
  const candidate = resolveReportedPath(projectDir, report.outputPath);
57528
- if (candidate && existsSync39(candidate)) return candidate;
57658
+ if (candidate && existsSync40(candidate)) return candidate;
57529
57659
  } catch {
57530
57660
  }
57531
57661
  }
57532
- const rendersDir = join22(projectDir, "renders");
57533
- if (!existsSync39(rendersDir)) return null;
57662
+ const rendersDir = join23(projectDir, "renders");
57663
+ if (!existsSync40(rendersDir)) return null;
57534
57664
  const entries = await readdir5(rendersDir, { withFileTypes: true });
57535
57665
  const candidates = [];
57536
57666
  for (const entry of entries) {
@@ -57538,7 +57668,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
57538
57668
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
57539
57669
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
57540
57670
  if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
57541
- const full = join22(rendersDir, entry.name);
57671
+ const full = join23(rendersDir, entry.name);
57542
57672
  const info = await stat6(full);
57543
57673
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
57544
57674
  }
@@ -57556,14 +57686,14 @@ async function runAiRenderReview(projectDir, videoPath, model) {
57556
57686
  });
57557
57687
  }
57558
57688
  function resolveStoryboardPath(projectDir) {
57559
- const storyboardPath = join22(projectDir, "STORYBOARD.md");
57560
- return existsSync39(storyboardPath) ? storyboardPath : null;
57689
+ const storyboardPath = join23(projectDir, "STORYBOARD.md");
57690
+ return existsSync40(storyboardPath) ? storyboardPath : null;
57561
57691
  }
57562
57692
  async function expectedDurationFromBuildReport(projectDir, beatId) {
57563
- const reportPath = join22(projectDir, "build-report.json");
57564
- if (!existsSync39(reportPath)) return void 0;
57693
+ const reportPath = join23(projectDir, "build-report.json");
57694
+ if (!existsSync40(reportPath)) return void 0;
57565
57695
  try {
57566
- const report = JSON.parse(await readFile32(reportPath, "utf-8"));
57696
+ const report = JSON.parse(await readFile33(reportPath, "utf-8"));
57567
57697
  const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
57568
57698
  const durations = beats.map(
57569
57699
  (beat) => parseOptionalNumber2(beat.sceneDurationSec) ?? parseOptionalNumber2(beat.narration?.sceneDurationSec)
@@ -57575,10 +57705,10 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
57575
57705
  }
57576
57706
  }
57577
57707
  async function beatTimingsFromBuildReport(projectDir, beatId) {
57578
- const reportPath = join22(projectDir, "build-report.json");
57579
- if (!existsSync39(reportPath)) return [];
57708
+ const reportPath = join23(projectDir, "build-report.json");
57709
+ if (!existsSync40(reportPath)) return [];
57580
57710
  try {
57581
- const report = JSON.parse(await readFile32(reportPath, "utf-8"));
57711
+ const report = JSON.parse(await readFile33(reportPath, "utf-8"));
57582
57712
  const source = report.beats ?? [];
57583
57713
  const selected = beatId ? source.filter((beat) => beat.id === beatId) : source;
57584
57714
  let cursor = 0;
@@ -57604,17 +57734,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
57604
57734
  }
57605
57735
  async function buildRenderReviewProjectContext(projectDir) {
57606
57736
  const parts = [];
57607
- const storyboard = await readTextIfExists(join22(projectDir, "STORYBOARD.md"));
57737
+ const storyboard = await readTextIfExists(join23(projectDir, "STORYBOARD.md"));
57608
57738
  if (storyboard) {
57609
57739
  parts.push(`STORYBOARD.md:
57610
57740
  ${truncateText(storyboard, 4e3)}`);
57611
57741
  parts.push(`Storyboard beat cue summary:
57612
57742
  ${storyboardBeatCueSummary(storyboard)}`);
57613
57743
  }
57614
- const design = await readTextIfExists(join22(projectDir, "DESIGN.md"));
57744
+ const design = await readTextIfExists(join23(projectDir, "DESIGN.md"));
57615
57745
  if (design) parts.push(`DESIGN.md:
57616
57746
  ${truncateText(design, 4e3)}`);
57617
- const buildReport = await readTextIfExists(join22(projectDir, "build-report.json"));
57747
+ const buildReport = await readTextIfExists(join23(projectDir, "build-report.json"));
57618
57748
  if (buildReport) parts.push(`build-report.json:
57619
57749
  ${truncateText(buildReport, 6e3)}`);
57620
57750
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -57642,9 +57772,9 @@ function storyboardBeatCueSummary(storyboard) {
57642
57772
  }
57643
57773
  }
57644
57774
  async function readTextIfExists(path2) {
57645
- if (!existsSync39(path2)) return null;
57775
+ if (!existsSync40(path2)) return null;
57646
57776
  try {
57647
- return await readFile32(path2, "utf-8");
57777
+ return await readFile33(path2, "utf-8");
57648
57778
  } catch {
57649
57779
  return null;
57650
57780
  }
@@ -58462,16 +58592,16 @@ var generateTools = [
58462
58592
  ];
58463
58593
 
58464
58594
  // src/tools/manifest/storyboard.ts
58465
- import { existsSync as existsSync42 } from "node:fs";
58466
- import { readFile as readFile36, writeFile as writeFile41 } from "node:fs/promises";
58595
+ import { existsSync as existsSync43 } from "node:fs";
58596
+ import { readFile as readFile37, writeFile as writeFile41 } from "node:fs/promises";
58467
58597
  import { resolve as resolve54 } from "node:path";
58468
58598
  init_storyboard_parse();
58469
58599
  init_storyboard_edit();
58470
58600
 
58471
58601
  // src/commands/_shared/storyboard-revise.ts
58472
- import { existsSync as existsSync41 } from "node:fs";
58473
- import { readFile as readFile35, writeFile as writeFile40 } from "node:fs/promises";
58474
- import { join as join23, resolve as resolve53 } from "node:path";
58602
+ import { existsSync as existsSync42 } from "node:fs";
58603
+ import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
58604
+ import { join as join24, resolve as resolve53 } from "node:path";
58475
58605
 
58476
58606
  // src/agent/adapters/index.ts
58477
58607
  init_openai2();
@@ -58523,9 +58653,9 @@ init_storyboard_parse();
58523
58653
  init_storyboard_edit();
58524
58654
  async function executeStoryboardRevision(opts) {
58525
58655
  const projectDir = resolve53(opts.projectDir);
58526
- const storyboardPath = join23(projectDir, "STORYBOARD.md");
58656
+ const storyboardPath = join24(projectDir, "STORYBOARD.md");
58527
58657
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
58528
- if (!existsSync41(storyboardPath)) {
58658
+ if (!existsSync42(storyboardPath)) {
58529
58659
  return failureResult({
58530
58660
  projectDir,
58531
58661
  storyboardPath,
@@ -58535,7 +58665,7 @@ async function executeStoryboardRevision(opts) {
58535
58665
  retryWith: [`vibe init ${projectDir} --from "<brief>" --json`]
58536
58666
  });
58537
58667
  }
58538
- const currentStoryboard = await readFile35(storyboardPath, "utf-8");
58668
+ const currentStoryboard = await readFile36(storyboardPath, "utf-8");
58539
58669
  const projectConfig = await readProjectConfig(projectDir);
58540
58670
  const explicitComposer = opts.composer ?? projectConfig.config.providers.composer ?? void 0;
58541
58671
  let provider = explicitComposer ?? "claude";
@@ -58758,18 +58888,18 @@ function diffChangedBeats(before, after) {
58758
58888
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
58759
58889
  }
58760
58890
  async function readRevisionContext(projectDir) {
58761
- const designMd = await readOptionalText(join23(projectDir, "DESIGN.md"));
58762
- const buildReportJson = await readOptionalJsonText(join23(projectDir, "build-report.json"));
58891
+ const designMd = await readOptionalText(join24(projectDir, "DESIGN.md"));
58892
+ const buildReportJson = await readOptionalJsonText(join24(projectDir, "build-report.json"));
58763
58893
  return { designMd, buildReportJson };
58764
58894
  }
58765
58895
  async function readOptionalText(path2) {
58766
- if (!existsSync41(path2)) return null;
58767
- return readFile35(path2, "utf-8");
58896
+ if (!existsSync42(path2)) return null;
58897
+ return readFile36(path2, "utf-8");
58768
58898
  }
58769
58899
  async function readOptionalJsonText(path2) {
58770
- if (!existsSync41(path2)) return null;
58900
+ if (!existsSync42(path2)) return null;
58771
58901
  try {
58772
- const parsed = JSON.parse(await readFile35(path2, "utf-8"));
58902
+ const parsed = JSON.parse(await readFile36(path2, "utf-8"));
58773
58903
  return JSON.stringify(parsed, null, 2);
58774
58904
  } catch {
58775
58905
  return null;
@@ -58814,8 +58944,8 @@ var projectDirSchema = external_exports.object({
58814
58944
  });
58815
58945
  async function readStoryboard(projectDir) {
58816
58946
  const path2 = resolve54(projectDir, "STORYBOARD.md");
58817
- if (!existsSync42(path2)) return null;
58818
- return { path: path2, markdown: await readFile36(path2, "utf-8") };
58947
+ if (!existsSync43(path2)) return null;
58948
+ return { path: path2, markdown: await readFile37(path2, "utf-8") };
58819
58949
  }
58820
58950
  var storyboardListTool = defineTool({
58821
58951
  name: "storyboard_list",
@@ -59074,7 +59204,7 @@ var storyboardTools = [
59074
59204
  // src/tools/manifest/remix.ts
59075
59205
  import { writeFile as writeFile46 } from "node:fs/promises";
59076
59206
  import { tmpdir as tmpdir4 } from "node:os";
59077
- import { join as join24 } from "node:path";
59207
+ import { join as join25 } from "node:path";
59078
59208
 
59079
59209
  // src/commands/ai-script-pipeline.ts
59080
59210
  var import_yaml6 = __toESM(require_dist2(), 1);
@@ -59084,9 +59214,9 @@ init_config();
59084
59214
  init_audio();
59085
59215
  init_ai_helpers();
59086
59216
  init_video_utils();
59087
- import { readFile as readFile37, writeFile as writeFile42, unlink as unlink3, rename as rename4 } from "node:fs/promises";
59217
+ import { readFile as readFile38, writeFile as writeFile42, unlink as unlink3, rename as rename4 } from "node:fs/promises";
59088
59218
  import { resolve as resolve55, extname as extname10 } from "node:path";
59089
- import { existsSync as existsSync43 } from "node:fs";
59219
+ import { existsSync as existsSync44 } from "node:fs";
59090
59220
 
59091
59221
  // src/commands/_shared/video-providers.ts
59092
59222
  init_source();
@@ -59245,16 +59375,16 @@ async function executeRegenerateScene(options) {
59245
59375
  };
59246
59376
  try {
59247
59377
  const outputDir = resolve55(process.cwd(), options.projectDir);
59248
- if (!existsSync43(outputDir)) {
59378
+ if (!existsSync44(outputDir)) {
59249
59379
  return { ...result, error: `Project directory not found: ${outputDir}` };
59250
59380
  }
59251
59381
  const yamlPath = resolve55(outputDir, "storyboard.yaml");
59252
59382
  const jsonPath = resolve55(outputDir, "storyboard.json");
59253
- const storyboardPath = existsSync43(yamlPath) ? yamlPath : existsSync43(jsonPath) ? jsonPath : null;
59383
+ const storyboardPath = existsSync44(yamlPath) ? yamlPath : existsSync44(jsonPath) ? jsonPath : null;
59254
59384
  if (!storyboardPath) {
59255
59385
  return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
59256
59386
  }
59257
- const storyboardContent = await readFile37(storyboardPath, "utf-8");
59387
+ const storyboardContent = await readFile38(storyboardPath, "utf-8");
59258
59388
  const segments = storyboardPath.endsWith(".yaml") ? (0, import_yaml6.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
59259
59389
  for (const sceneNum of options.scenes) {
59260
59390
  if (sceneNum < 1 || sceneNum > segments.length) {
@@ -59343,15 +59473,15 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
59343
59473
  const refSceneNum = options.referenceScene;
59344
59474
  if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
59345
59475
  const refImagePath = resolve55(outputDir, `scene-${refSceneNum}.png`);
59346
- if (existsSync43(refImagePath)) {
59347
- referenceImageBuffer = await readFile37(refImagePath);
59476
+ if (existsSync44(refImagePath)) {
59477
+ referenceImageBuffer = await readFile38(refImagePath);
59348
59478
  }
59349
59479
  } else if (!refSceneNum) {
59350
59480
  for (let i = 1; i <= segments.length; i++) {
59351
59481
  if (i !== sceneNum) {
59352
59482
  const otherImagePath = resolve55(outputDir, `scene-${i}.png`);
59353
- if (existsSync43(otherImagePath)) {
59354
- referenceImageBuffer = await readFile37(otherImagePath);
59483
+ if (existsSync44(otherImagePath)) {
59484
+ referenceImageBuffer = await readFile38(otherImagePath);
59355
59485
  break;
59356
59486
  }
59357
59487
  }
@@ -59439,11 +59569,11 @@ Generate the single-person scene image now.`;
59439
59569
  }
59440
59570
  if (regenerateVideo && videoApiKey) {
59441
59571
  options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
59442
- if (!existsSync43(imagePath)) {
59572
+ if (!existsSync44(imagePath)) {
59443
59573
  result.failedScenes.push(sceneNum);
59444
59574
  continue;
59445
59575
  }
59446
- const imageBuffer = await readFile37(imagePath);
59576
+ const imageBuffer = await readFile38(imagePath);
59447
59577
  const videoDuration = segment.duration > 5 ? 10 : 5;
59448
59578
  const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
59449
59579
  if (options.generator === "grok") {
@@ -59651,9 +59781,9 @@ Generate the single-person scene image now.`;
59651
59781
  }
59652
59782
 
59653
59783
  // src/commands/ai-highlights.ts
59654
- import { readFile as readFile38, writeFile as writeFile43, mkdir as mkdir23 } from "node:fs/promises";
59784
+ import { readFile as readFile39, writeFile as writeFile43, mkdir as mkdir23 } from "node:fs/promises";
59655
59785
  import { resolve as resolve56, dirname as dirname22, basename as basename14, extname as extname11 } from "node:path";
59656
- import { existsSync as existsSync44 } from "node:fs";
59786
+ import { existsSync as existsSync45 } from "node:fs";
59657
59787
  init_dist2();
59658
59788
  init_engine();
59659
59789
  init_api_key();
@@ -59684,7 +59814,7 @@ function filterHighlights(highlights, options) {
59684
59814
  async function executeHighlights(options) {
59685
59815
  try {
59686
59816
  const absPath = resolve56(process.cwd(), options.media);
59687
- if (!existsSync44(absPath)) {
59817
+ if (!existsSync45(absPath)) {
59688
59818
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
59689
59819
  }
59690
59820
  const ext = extname11(absPath).toLowerCase();
@@ -59703,7 +59833,7 @@ async function executeHighlights(options) {
59703
59833
  sourceDuration = await ffprobeDuration(absPath);
59704
59834
  const gemini = new GeminiProvider();
59705
59835
  await gemini.initialize({ apiKey: geminiApiKey });
59706
- const videoBuffer = await readFile38(absPath);
59836
+ const videoBuffer = await readFile39(absPath);
59707
59837
  const criteriaText = options.criteria === "all" || !options.criteria ? "emotional, informative, and funny moments" : `${options.criteria} moments`;
59708
59838
  const durationText = targetDuration ? `Target a total highlight duration of ${targetDuration} seconds.` : "";
59709
59839
  const countText = maxCount ? `Find up to ${maxCount} highlights.` : "";
@@ -59797,10 +59927,10 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
59797
59927
  }
59798
59928
  const whisper = new WhisperProvider();
59799
59929
  await whisper.initialize({ apiKey: openaiApiKey });
59800
- const audioBuffer = await readFile38(audioPath);
59930
+ const audioBuffer = await readFile39(audioPath);
59801
59931
  const audioBlob = new Blob([audioBuffer]);
59802
59932
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
59803
- if (tempAudioPath && existsSync44(tempAudioPath)) {
59933
+ if (tempAudioPath && existsSync45(tempAudioPath)) {
59804
59934
  const { unlink: unlinkFile } = await import("node:fs/promises");
59805
59935
  await unlinkFile(tempAudioPath).catch(() => {
59806
59936
  });
@@ -59887,7 +60017,7 @@ async function executeAutoShorts(options) {
59887
60017
  return { success: false, shorts: [], error: "FFmpeg not found" };
59888
60018
  }
59889
60019
  const absPath = resolve56(process.cwd(), options.video);
59890
- if (!existsSync44(absPath)) {
60020
+ if (!existsSync45(absPath)) {
59891
60021
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
59892
60022
  }
59893
60023
  const targetDuration = options.duration ?? 60;
@@ -59900,7 +60030,7 @@ async function executeAutoShorts(options) {
59900
60030
  }
59901
60031
  const gemini = new GeminiProvider();
59902
60032
  await gemini.initialize({ apiKey: geminiApiKey });
59903
- const videoBuffer = await readFile38(absPath);
60033
+ const videoBuffer = await readFile39(absPath);
59904
60034
  const geminiPrompt = `Analyze this video to find the BEST moments for short-form vertical video content (TikTok, YouTube Shorts, Instagram Reels).
59905
60035
 
59906
60036
  Find ${shortCount * 3} potential clips that are ${targetDuration} seconds or shorter each.
@@ -59977,7 +60107,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
59977
60107
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
59978
60108
  const whisper = new WhisperProvider();
59979
60109
  await whisper.initialize({ apiKey: openaiApiKey });
59980
- const audioBuffer = await readFile38(tempAudio);
60110
+ const audioBuffer = await readFile39(tempAudio);
59981
60111
  const audioBlob = new Blob([audioBuffer]);
59982
60112
  const transcript = await whisper.transcribe(audioBlob, options.language);
59983
60113
  try {
@@ -60015,7 +60145,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
60015
60145
  };
60016
60146
  }
60017
60147
  const outputDir = options.outputDir ? resolve56(process.cwd(), options.outputDir) : dirname22(absPath);
60018
- if (options.outputDir && !existsSync44(outputDir)) {
60148
+ if (options.outputDir && !existsSync45(outputDir)) {
60019
60149
  await mkdir23(outputDir, { recursive: true });
60020
60150
  }
60021
60151
  const result = {
@@ -60094,8 +60224,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
60094
60224
  // src/pipeline/executor.ts
60095
60225
  var import_yaml7 = __toESM(require_dist2(), 1);
60096
60226
  import { resolve as resolve58 } from "node:path";
60097
- import { readFile as readFile40, writeFile as writeFile45, mkdir as mkdir24 } from "node:fs/promises";
60098
- import { existsSync as existsSync45 } from "node:fs";
60227
+ import { readFile as readFile41, writeFile as writeFile45, mkdir as mkdir24 } from "node:fs/promises";
60228
+ import { existsSync as existsSync46 } from "node:fs";
60099
60229
 
60100
60230
  // src/pipeline/resolver.ts
60101
60231
  function resolveStepParams(params, completedSteps) {
@@ -60508,10 +60638,10 @@ async function ensureActionsRegistered() {
60508
60638
  }
60509
60639
  async function loadPipeline(filePath) {
60510
60640
  const absPath = resolve58(process.cwd(), filePath);
60511
- if (!existsSync45(absPath)) {
60641
+ if (!existsSync46(absPath)) {
60512
60642
  throw new Error(`Pipeline file not found: ${absPath}`);
60513
60643
  }
60514
- const content = await readFile40(absPath, "utf-8");
60644
+ const content = await readFile41(absPath, "utf-8");
60515
60645
  const manifest2 = (0, import_yaml7.parse)(content);
60516
60646
  if (!manifest2.name) throw new Error("Pipeline missing 'name' field");
60517
60647
  if (!manifest2.steps || !Array.isArray(manifest2.steps)) throw new Error("Pipeline missing 'steps' array");
@@ -60534,8 +60664,8 @@ async function executePipeline(manifest2, options = {}) {
60534
60664
  const startTime = Date.now();
60535
60665
  if (options.resume) {
60536
60666
  const checkpointPath = resolve58(outputDir, CHECKPOINT_FILE);
60537
- if (existsSync45(checkpointPath)) {
60538
- const checkpointContent = await readFile40(checkpointPath, "utf-8");
60667
+ if (existsSync46(checkpointPath)) {
60668
+ const checkpointContent = await readFile41(checkpointPath, "utf-8");
60539
60669
  const checkpoint = (0, import_yaml7.parse)(checkpointContent);
60540
60670
  for (const cs of checkpoint.completedSteps) {
60541
60671
  completedSteps.set(cs.id, {
@@ -60807,7 +60937,7 @@ var pipelineRunTool = defineTool({
60807
60937
  let resolvedPath = args.pipelinePath;
60808
60938
  let tempPath;
60809
60939
  if (args.pipelineYaml) {
60810
- tempPath = join24(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
60940
+ tempPath = join25(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
60811
60941
  await writeFile46(tempPath, args.pipelineYaml, "utf-8");
60812
60942
  resolvedPath = tempPath;
60813
60943
  }
@@ -60981,10 +61111,10 @@ init_project_resolver();
60981
61111
  // src/tools/manifest/_project-io.ts
60982
61112
  init_engine();
60983
61113
  init_project_resolver();
60984
- import { readFile as readFile41, writeFile as writeFile47 } from "node:fs/promises";
61114
+ import { readFile as readFile42, writeFile as writeFile47 } from "node:fs/promises";
60985
61115
  async function loadProject(projectPath, cwd) {
60986
61116
  const absPath = await resolveTimelineFile(projectPath, cwd);
60987
- const content = await readFile41(absPath, "utf-8");
61117
+ const content = await readFile42(absPath, "utf-8");
60988
61118
  const data = JSON.parse(content);
60989
61119
  return { project: Project.fromJSON(data), absPath };
60990
61120
  }
@@ -61334,12 +61464,12 @@ init_engine();
61334
61464
 
61335
61465
  // src/commands/_shared/project-list.ts
61336
61466
  init_storyboard_parse();
61337
- import { readdir as readdir6, readFile as readFile42, stat as stat7 } from "node:fs/promises";
61338
- import { existsSync as existsSync46 } from "node:fs";
61339
- import { join as join25, resolve as resolve60 } from "node:path";
61467
+ import { readdir as readdir6, readFile as readFile43, stat as stat7 } from "node:fs/promises";
61468
+ import { existsSync as existsSync47 } from "node:fs";
61469
+ import { join as join26, resolve as resolve60 } from "node:path";
61340
61470
  async function executeProjectList(opts = {}) {
61341
61471
  const workspaceDir = resolve60(opts.workspaceDir ?? ".");
61342
- if (!existsSync46(workspaceDir)) {
61472
+ if (!existsSync47(workspaceDir)) {
61343
61473
  return {
61344
61474
  success: false,
61345
61475
  workspaceDir,
@@ -61356,11 +61486,11 @@ async function executeProjectList(opts = {}) {
61356
61486
  if (entry.name === "node_modules") continue;
61357
61487
  if (entry.name.startsWith(".")) continue;
61358
61488
  if (entry.name.startsWith("_")) {
61359
- archivedCount += await countProjects(join25(workspaceDir, entry.name));
61489
+ archivedCount += await countProjects(join26(workspaceDir, entry.name));
61360
61490
  continue;
61361
61491
  }
61362
- const projectDir = join25(workspaceDir, entry.name);
61363
- if (!existsSync46(join25(projectDir, "STORYBOARD.md"))) continue;
61492
+ const projectDir = join26(workspaceDir, entry.name);
61493
+ if (!existsSync47(join26(projectDir, "STORYBOARD.md"))) continue;
61364
61494
  projects.push(await describeProject(entry.name, projectDir));
61365
61495
  }
61366
61496
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -61370,7 +61500,7 @@ async function countProjects(dir) {
61370
61500
  try {
61371
61501
  const entries = await readdir6(dir, { withFileTypes: true });
61372
61502
  return entries.filter(
61373
- (e) => e.isDirectory() && existsSync46(join25(dir, e.name, "STORYBOARD.md"))
61503
+ (e) => e.isDirectory() && existsSync47(join26(dir, e.name, "STORYBOARD.md"))
61374
61504
  ).length;
61375
61505
  } catch {
61376
61506
  return 0;
@@ -61381,8 +61511,8 @@ async function describeProject(name, projectDir) {
61381
61511
  let storyboardDurationSec = 0;
61382
61512
  const timestamps = [];
61383
61513
  try {
61384
- const storyboardPath = join25(projectDir, "STORYBOARD.md");
61385
- const parsed = parseStoryboard(await readFile42(storyboardPath, "utf-8"));
61514
+ const storyboardPath = join26(projectDir, "STORYBOARD.md");
61515
+ const parsed = parseStoryboard(await readFile43(storyboardPath, "utf-8"));
61386
61516
  beats = parsed.beats.length;
61387
61517
  storyboardDurationSec = Number(
61388
61518
  parsed.beats.reduce((sum, beat) => sum + (beat.duration ?? 0), 0).toFixed(2)
@@ -61393,8 +61523,8 @@ async function describeProject(name, projectDir) {
61393
61523
  let buildStatus;
61394
61524
  let buildUpdatedAt;
61395
61525
  try {
61396
- const reportPath = join25(projectDir, "build-report.json");
61397
- const report = JSON.parse(await readFile42(reportPath, "utf-8"));
61526
+ const reportPath = join26(projectDir, "build-report.json");
61527
+ const report = JSON.parse(await readFile43(reportPath, "utf-8"));
61398
61528
  buildStatus = report.status ?? report.phase;
61399
61529
  buildUpdatedAt = report.updatedAt;
61400
61530
  timestamps.push((await stat7(reportPath)).mtimeMs);
@@ -61402,12 +61532,12 @@ async function describeProject(name, projectDir) {
61402
61532
  }
61403
61533
  let latestRender;
61404
61534
  try {
61405
- const rendersDir = join25(projectDir, "renders");
61535
+ const rendersDir = join26(projectDir, "renders");
61406
61536
  const files = await readdir6(rendersDir);
61407
61537
  let newest;
61408
61538
  for (const file of files) {
61409
61539
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
61410
- const { mtimeMs } = await stat7(join25(rendersDir, file));
61540
+ const { mtimeMs } = await stat7(join26(rendersDir, file));
61411
61541
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
61412
61542
  }
61413
61543
  if (newest) {
@@ -61541,7 +61671,7 @@ init_exec_safe();
61541
61671
  init_output();
61542
61672
  init_validate();
61543
61673
  init_project_resolver();
61544
- import { readFile as readFile43, access as access6 } from "node:fs/promises";
61674
+ import { readFile as readFile44, access as access6 } from "node:fs/promises";
61545
61675
  import { resolve as resolve62, basename as basename17 } from "node:path";
61546
61676
  import { spawn } from "node:child_process";
61547
61677
  function timelineBaseName(path2) {
@@ -61618,7 +61748,7 @@ async function runExport(projectPath, outputPath, options = {}) {
61618
61748
  };
61619
61749
  }
61620
61750
  const filePath = await resolveTimelineFile(projectPath);
61621
- const content = await readFile43(filePath, "utf-8");
61751
+ const content = await readFile44(filePath, "utf-8");
61622
61752
  const data = JSON.parse(content);
61623
61753
  const project = Project.fromJSON(data);
61624
61754
  const summary = project.getSummary();
@@ -61742,7 +61872,7 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
61742
61872
  }
61743
61873
  spinner2.text = "Loading project...";
61744
61874
  const filePath = await resolveTimelineFile(projectPath);
61745
- const content = await readFile43(filePath, "utf-8");
61875
+ const content = await readFile44(filePath, "utf-8");
61746
61876
  const data = JSON.parse(content);
61747
61877
  const project = Project.fromJSON(data);
61748
61878
  const summary = project.getSummary();
@@ -62340,14 +62470,14 @@ function getPresetSettings(preset, aspectRatio2) {
62340
62470
  }
62341
62471
  async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
62342
62472
  spinner2.text = "Loading project...";
62343
- const { readFile: readFile45 } = await import("node:fs/promises");
62473
+ const { readFile: readFile46 } = await import("node:fs/promises");
62344
62474
  const { resolve: resolve66 } = await import("node:path");
62345
62475
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
62346
62476
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
62347
62477
  const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
62348
62478
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
62349
62479
  const filePath = await resolveTimelineFile(projectPath);
62350
- const content = await readFile45(filePath, "utf-8");
62480
+ const content = await readFile46(filePath, "utf-8");
62351
62481
  const project = Project2.fromJSON(JSON.parse(content));
62352
62482
  const state = project.getState();
62353
62483
  const outputPath = options.output ? resolve66(process.cwd(), options.output) : resolve66(process.cwd(), `${timelineBaseName(projectPath)}.${options.format ?? "mp4"}`);
@@ -62416,8 +62546,8 @@ var exportVideoTool = defineTool({
62416
62546
  var exportTools = [exportVideoTool];
62417
62547
 
62418
62548
  // src/tools/manifest/agent-only.ts
62419
- import { readFile as readFile44, writeFile as writeFile50, readdir as readdir7, stat as stat8, access as access7, unlink as unlink4 } from "node:fs/promises";
62420
- import { resolve as resolve64, join as join26, basename as basename18, extname as extname12 } from "node:path";
62549
+ import { readFile as readFile45, writeFile as writeFile50, readdir as readdir7, stat as stat8, access as access7, unlink as unlink4 } from "node:fs/promises";
62550
+ import { resolve as resolve64, join as join27, basename as basename18, extname as extname12 } from "node:path";
62421
62551
  init_engine();
62422
62552
  init_exec_safe();
62423
62553
  init_project_resolver();
@@ -62495,7 +62625,7 @@ var fsListTool = defineTool({
62495
62625
  const results = [];
62496
62626
  for (const entry of entries) {
62497
62627
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
62498
- const fullPath = join26(absPath, entry.name);
62628
+ const fullPath = join27(absPath, entry.name);
62499
62629
  const stats = await stat8(fullPath);
62500
62630
  if (entry.isDirectory()) {
62501
62631
  results.push(`[DIR] ${entry.name}/`);
@@ -62524,7 +62654,7 @@ var fsReadTool = defineTool({
62524
62654
  async execute(args, ctx) {
62525
62655
  try {
62526
62656
  const absPath = resolve64(ctx.workingDirectory, args.path);
62527
- const content = await readFile44(absPath, "utf-8");
62657
+ const content = await readFile45(absPath, "utf-8");
62528
62658
  const maxLength = 4e3;
62529
62659
  const truncated = content.length > maxLength;
62530
62660
  const output = truncated ? content.substring(0, maxLength) + "\n... (truncated)" : content;
@@ -62597,7 +62727,7 @@ var batchImportTool = defineTool({
62597
62727
  async execute(args, ctx) {
62598
62728
  try {
62599
62729
  const filePath = resolve64(ctx.workingDirectory, args.project);
62600
- const content = await readFile44(filePath, "utf-8");
62730
+ const content = await readFile45(filePath, "utf-8");
62601
62731
  const data = JSON.parse(content);
62602
62732
  const project = Project.fromJSON(data);
62603
62733
  const dirPath = resolve64(ctx.workingDirectory, args.directory);
@@ -62608,7 +62738,7 @@ var batchImportTool = defineTool({
62608
62738
  const scanDir = async (dir) => {
62609
62739
  const entries = await readdir7(dir, { withFileTypes: true });
62610
62740
  for (const entry of entries) {
62611
- const entryPath = join26(dir, entry.name);
62741
+ const entryPath = join27(dir, entry.name);
62612
62742
  if (entry.isDirectory() && recursive) {
62613
62743
  await scanDir(entryPath);
62614
62744
  } else if (entry.isFile()) {
@@ -62669,7 +62799,7 @@ var batchConcatTool = defineTool({
62669
62799
  async execute(args, ctx) {
62670
62800
  try {
62671
62801
  const filePath = resolve64(ctx.workingDirectory, args.project);
62672
- const content = await readFile44(filePath, "utf-8");
62802
+ const content = await readFile45(filePath, "utf-8");
62673
62803
  const data = JSON.parse(content);
62674
62804
  const project = Project.fromJSON(data);
62675
62805
  const sourceIds = args.sourceIds ?? [];
@@ -62740,7 +62870,7 @@ var batchApplyEffectTool = defineTool({
62740
62870
  async execute(args, ctx) {
62741
62871
  try {
62742
62872
  const filePath = resolve64(ctx.workingDirectory, args.project);
62743
- const content = await readFile44(filePath, "utf-8");
62873
+ const content = await readFile45(filePath, "utf-8");
62744
62874
  const data = JSON.parse(content);
62745
62875
  const project = Project.fromJSON(data);
62746
62876
  const clipIds = args.clipIds ?? [];
@@ -62795,7 +62925,7 @@ var timelineClearTool = defineTool({
62795
62925
  async execute(args, ctx) {
62796
62926
  try {
62797
62927
  const filePath = await resolveTimelineFile(args.project, ctx.workingDirectory);
62798
- const content = await readFile44(filePath, "utf-8");
62928
+ const content = await readFile45(filePath, "utf-8");
62799
62929
  const data = JSON.parse(content);
62800
62930
  const project = Project.fromJSON(data);
62801
62931
  const what = args.what ?? "clips";
@@ -62872,7 +63002,7 @@ var projectSetTool = defineTool({
62872
63002
  async execute(args, ctx) {
62873
63003
  try {
62874
63004
  const filePath = await resolveTimelineFile(args.projectPath, ctx.workingDirectory);
62875
- const content = await readFile44(filePath, "utf-8");
63005
+ const content = await readFile45(filePath, "utf-8");
62876
63006
  const data = JSON.parse(content);
62877
63007
  const project = Project.fromJSON(data);
62878
63008
  const updates = [];
@@ -63146,8 +63276,8 @@ var mediaCompressTool = defineTool({
63146
63276
  ],
63147
63277
  { maxBuffer: 50 * 1024 * 1024 }
63148
63278
  );
63149
- const inputBuf = await readFile44(absInput);
63150
- const outputBuf = await readFile44(outputPath);
63279
+ const inputBuf = await readFile45(absInput);
63280
+ const outputBuf = await readFile45(outputPath);
63151
63281
  const inputSize = inputBuf.length;
63152
63282
  const outputSize = outputBuf.length;
63153
63283
  const reduction = (inputSize - outputSize) / inputSize * 100;