@vibeframe/cli 0.113.9 → 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;
@@ -31341,6 +31429,13 @@ ${finalDurationBullet}
31341
31429
  - If the beat cues include \`narration\`, do not embed an \`<audio>\` tag in the
31342
31430
  scene fragment. The root timeline wires \`assets/narration-${ctx.beat.id}.wav\`
31343
31431
  or \`assets/narration-${ctx.beat.id}.mp3\` separately.
31432
+ - Do NOT invent sound effects. This scene fragment is visual-only: never add
31433
+ an \`<audio>\` tag, and never imply ambient/foley SFX (keyboard clicks, whoosh,
31434
+ applause, room tone) that the beat did not explicitly request. Audio comes
31435
+ only from the beat's explicit \`narration\` and \`music\` cues, wired by the
31436
+ root timeline. If the beat declares an explicit \`sfx\` cue, treat it as intent
31437
+ for the storyboard author \u2014 still do not embed it here \u2014 and only when it is
31438
+ clearly tied to the beat's narration/motion; ignore orphaned or generic SFX.
31344
31439
  - **\`.clip\` elements get visibility control from the framework but NO
31345
31440
  sizing.** Always give \`.clip\` explicit fill via CSS:
31346
31441
  \`{ position: absolute; inset: 0; }\` (or equivalent
@@ -31406,7 +31501,7 @@ ${ctx.storyboardGlobal || "(no global direction)"}
31406
31501
 
31407
31502
  ## ${ctx.beat.heading}
31408
31503
  ${formatBeatCues(ctx.beat.cues)}${cueDurationNote}
31409
- ${ctx.beat.body}
31504
+ ${ctx.beat.body}${formatTranscriptSection(ctx.transcript)}
31410
31505
 
31411
31506
  === Output format ===
31412
31507
 
@@ -31426,7 +31521,7 @@ function computeCacheKey(parts) {
31426
31521
  return createHash4("sha256").update(parts.provider).update("\u241E").update(parts.model).update("\u241E").update(parts.systemPrompt).update("\u241E").update(parts.userPrompt).digest("hex");
31427
31522
  }
31428
31523
  function defaultCacheDir() {
31429
- return join9(USER_CACHE_DIR, "compose-scenes");
31524
+ return join10(USER_CACHE_DIR, "compose-scenes");
31430
31525
  }
31431
31526
  function extractHtml(responseText) {
31432
31527
  const fenced = responseText.match(/```html\s*\n([\s\S]*?)\n```/);
@@ -31450,9 +31545,9 @@ async function composeBeatHtml(ctx, overrides) {
31450
31545
  model: settings.model
31451
31546
  });
31452
31547
  const cacheDir = ctx.cacheDir ?? defaultCacheDir();
31453
- const cachePath = join9(cacheDir, `${cacheKey}.html`);
31454
- if (existsSync9(cachePath)) {
31455
- 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");
31456
31551
  return { html: html2, cached: true, cacheKey, model: settings.model, provider: ctx.provider };
31457
31552
  }
31458
31553
  if (!ctx.apiKey) {
@@ -31588,14 +31683,14 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31588
31683
  const projectRoot = params.project ? resolve12(outputDir, params.project) : resolve12(outputDir);
31589
31684
  const designPath = resolve12(projectRoot, params.design ?? "DESIGN.md");
31590
31685
  const storyboardPath = resolve12(projectRoot, params.storyboard ?? "STORYBOARD.md");
31591
- if (!existsSync9(designPath)) {
31686
+ if (!existsSync10(designPath)) {
31592
31687
  return { success: false, error: `DESIGN.md not found at ${designPath}` };
31593
31688
  }
31594
- if (!existsSync9(storyboardPath)) {
31689
+ if (!existsSync10(storyboardPath)) {
31595
31690
  return { success: false, error: `STORYBOARD.md not found at ${storyboardPath}` };
31596
31691
  }
31597
- const designMd = await readFile9(designPath, "utf-8");
31598
- const storyboardMd = await readFile9(storyboardPath, "utf-8");
31692
+ const designMd = await readFile10(designPath, "utf-8");
31693
+ const storyboardMd = await readFile10(storyboardPath, "utf-8");
31599
31694
  const { global: storyboardGlobal, beats } = parseStoryboard(storyboardMd);
31600
31695
  if (beats.length === 0) {
31601
31696
  return {
@@ -31615,7 +31710,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31615
31710
  provider = params.composer ?? "claude";
31616
31711
  apiKey = "";
31617
31712
  }
31618
- const compositionsDir = join9(projectRoot, "compositions");
31713
+ const compositionsDir = join10(projectRoot, "compositions");
31619
31714
  await mkdir6(compositionsDir, { recursive: true });
31620
31715
  let finalDurations = /* @__PURE__ */ new Map();
31621
31716
  try {
@@ -31629,6 +31724,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31629
31724
  async (beat, beatIndex) => {
31630
31725
  onProgress({ type: "beat-start", beatId: beat.id, beatIndex, totalBeats });
31631
31726
  try {
31727
+ const transcript = await readBeatTranscript(projectRoot, beat.id);
31632
31728
  const result = await composeBeatWithRetry(
31633
31729
  {
31634
31730
  beat,
@@ -31639,11 +31735,12 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
31639
31735
  apiKey,
31640
31736
  effort: params.effort,
31641
31737
  cacheDir: params.cacheDir,
31642
- finalDurationSec: finalDurations.get(beat.id)
31738
+ finalDurationSec: finalDurations.get(beat.id),
31739
+ transcript
31643
31740
  },
31644
31741
  overrides
31645
31742
  );
31646
- const compositionPath = join9(compositionsDir, `scene-${beat.id}.html`);
31743
+ const compositionPath = join10(compositionsDir, `scene-${beat.id}.html`);
31647
31744
  await mkdir6(dirname5(compositionPath), { recursive: true });
31648
31745
  await writeFile12(compositionPath, result.html, "utf-8");
31649
31746
  if (result.cached) {
@@ -31722,7 +31819,7 @@ ${failures.map((f) => ` - ${f.beatId}: ${f.error}`).join("\n")}`,
31722
31819
  data: aggregateData
31723
31820
  };
31724
31821
  }
31725
- 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;
31726
31823
  var init_compose_scenes_skills = __esm({
31727
31824
  "src/commands/_shared/compose-scenes-skills.ts"() {
31728
31825
  "use strict";
@@ -31734,6 +31831,7 @@ var init_compose_scenes_skills = __esm({
31734
31831
  init_root_sync();
31735
31832
  init_composer_resolve();
31736
31833
  init_config();
31834
+ init_transcribe_narration();
31737
31835
  MODEL_SETTINGS_BY_PROVIDER = {
31738
31836
  // Anthropic Claude Sonnet 4.6 — v0.69 baseline. ~9 s/beat, $3/$15 per MTok.
31739
31837
  claude: {
@@ -31757,6 +31855,7 @@ var init_compose_scenes_skills = __esm({
31757
31855
  high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
31758
31856
  }
31759
31857
  };
31858
+ TRANSCRIPT_PROMPT_MAX_WORDS = 120;
31760
31859
  ComposeBeatError = class extends Error {
31761
31860
  constructor(code, message, cause) {
31762
31861
  super(message);
@@ -31842,22 +31941,22 @@ var init_compose_scenes_skills = __esm({
31842
31941
  });
31843
31942
 
31844
31943
  // src/commands/_shared/compose-prompts.ts
31845
- import { existsSync as existsSync10 } from "node:fs";
31846
- import { readFile as readFile10 } from "node:fs/promises";
31847
- 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";
31848
31947
  async function getComposePrompts(opts) {
31849
31948
  const projectDir = resolve13(opts.projectDir);
31850
- const designPath = join10(projectDir, "DESIGN.md");
31851
- const storyboardPath = join10(projectDir, "STORYBOARD.md");
31852
- const skillPath = join10(projectDir, "SKILL.md");
31853
- 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");
31854
31953
  const warnings = [];
31855
31954
  const baseError = (msg) => ({
31856
31955
  success: false,
31857
31956
  projectDir,
31858
31957
  designReference: "DESIGN.md",
31859
31958
  storyboardReference: "STORYBOARD.md",
31860
- skillReference: existsSync10(skillPath) ? "SKILL.md" : null,
31959
+ skillReference: existsSync11(skillPath) ? "SKILL.md" : null,
31861
31960
  compositionsDir: "compositions",
31862
31961
  beats: [],
31863
31962
  instructions: [],
@@ -31865,18 +31964,18 @@ async function getComposePrompts(opts) {
31865
31964
  warnings,
31866
31965
  error: msg
31867
31966
  });
31868
- if (!existsSync10(designPath)) {
31967
+ if (!existsSync11(designPath)) {
31869
31968
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
31870
31969
  }
31871
- if (!existsSync10(storyboardPath)) {
31970
+ if (!existsSync11(storyboardPath)) {
31872
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.`);
31873
31972
  }
31874
- if (!existsSync10(skillPath)) {
31973
+ if (!existsSync11(skillPath)) {
31875
31974
  warnings.push(
31876
31975
  "SKILL.md not installed \u2014 host agent won't have Hyperframes rules in context. Run `vibe scene install-skill` to install it."
31877
31976
  );
31878
31977
  }
31879
- const storyboardMd = await readFile10(storyboardPath, "utf-8");
31978
+ const storyboardMd = await readFile11(storyboardPath, "utf-8");
31880
31979
  const parsed = parseStoryboard(storyboardMd);
31881
31980
  if (parsed.beats.length === 0) {
31882
31981
  return baseError(`STORYBOARD.md has no \`## Beat \u2026\` headings.`);
@@ -31898,28 +31997,33 @@ async function getComposePrompts(opts) {
31898
31997
  finalDurations = await resolveProjectBeatDurations(projectDir);
31899
31998
  } catch {
31900
31999
  }
31901
- const result = beats.map((beat) => {
31902
- const outputPathAbs = join10(compositionsDir, `scene-${beat.id}.html`);
31903
- const outputPathRel = relative4(projectDir, outputPathAbs);
31904
- const finalDurationSec = finalDurations.get(beat.id);
31905
- const userPrompt = buildUserPrompt({
31906
- beat,
31907
- storyboardGlobal: parsed.global,
31908
- finalDurationSec
31909
- });
31910
- return {
31911
- id: beat.id,
31912
- heading: beat.heading,
31913
- outputPath: outputPathRel,
31914
- duration: beat.duration,
31915
- finalDurationSec,
31916
- cues: beat.cues,
31917
- body: beat.body,
31918
- userPrompt,
31919
- exists: existsSync10(outputPathAbs)
31920
- };
31921
- });
31922
- 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;
31923
32027
  const instructions = buildInstructions({
31924
32028
  skillRef,
31925
32029
  beatCount: result.length,
@@ -31967,13 +32071,14 @@ var init_compose_prompts = __esm({
31967
32071
  init_compose_scenes_skills();
31968
32072
  init_root_sync();
31969
32073
  init_bundle();
32074
+ init_transcribe_narration();
31970
32075
  }
31971
32076
  });
31972
32077
 
31973
32078
  // src/commands/_shared/sub-comp-duration-sync.ts
31974
- import { existsSync as existsSync12 } from "node:fs";
31975
- import { readFile as readFile12 } from "node:fs/promises";
31976
- 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";
31977
32082
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
31978
32083
  const compositionId = `scene-${beatId}`;
31979
32084
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -32065,15 +32170,15 @@ async function createSubCompDurationSyncPlans(opts) {
32065
32170
  const results = [];
32066
32171
  for (const beat of opts.beats) {
32067
32172
  const fileRel = `compositions/scene-${beat.id}.html`;
32068
- const filePath = join12(projectDir, fileRel);
32069
- if (!existsSync12(filePath)) continue;
32173
+ const filePath = join13(projectDir, fileRel);
32174
+ if (!existsSync13(filePath)) continue;
32070
32175
  const newDurationSec = await resolveSyncedBeatDuration({
32071
32176
  projectDir,
32072
32177
  beatDuration: beat.duration,
32073
32178
  narrationPath: beat.narrationPath,
32074
32179
  sceneDurationSec: beat.sceneDurationSec
32075
32180
  });
32076
- const html = await readFile12(filePath, "utf-8");
32181
+ const html = await readFile13(filePath, "utf-8");
32077
32182
  const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
32078
32183
  results.push({ ...outcome, file: fileRel, beatId: beat.id });
32079
32184
  }
@@ -32097,7 +32202,7 @@ var init_sub_comp_duration_sync = __esm({
32097
32202
  });
32098
32203
 
32099
32204
  // src/commands/_shared/scene-repair.ts
32100
- import { readFile as readFile13, writeFile as writeFile14 } from "node:fs/promises";
32205
+ import { readFile as readFile14, writeFile as writeFile14 } from "node:fs/promises";
32101
32206
  import { relative as relative5, resolve as resolve16 } from "node:path";
32102
32207
  import { runHyperframeLint as runHyperframeLint3 } from "@hyperframes/producer";
32103
32208
  async function executeSceneRepair(opts) {
@@ -32143,7 +32248,7 @@ async function executeSceneRepair(opts) {
32143
32248
  }
32144
32249
  for (const target of targets) {
32145
32250
  const rel = relative5(projectDir, target.abs) || target.abs;
32146
- const html = await readFile13(target.abs, "utf-8");
32251
+ const html = await readFile14(target.abs, "utf-8");
32147
32252
  const findings = lintHtml(html, rel, target.isSub);
32148
32253
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
32149
32254
  if (fixedCodes.length > 0) {
@@ -32515,15 +32620,15 @@ var init_storyboard_edit = __esm({
32515
32620
  });
32516
32621
 
32517
32622
  // src/pipeline/renderers/chrome.ts
32518
- import { existsSync as existsSync14 } from "node:fs";
32623
+ import { existsSync as existsSync15 } from "node:fs";
32519
32624
  import { homedir as homedir5 } from "node:os";
32520
- import { join as join13 } from "node:path";
32625
+ import { join as join14 } from "node:path";
32521
32626
  function findChrome() {
32522
32627
  const candidates = [
32523
32628
  process.env.HYPERFRAMES_CHROME_PATH,
32524
32629
  process.env.CHROME_PATH,
32525
32630
  // puppeteer auto-downloaded headless shell
32526
- join13(
32631
+ join14(
32527
32632
  homedir5(),
32528
32633
  ".cache",
32529
32634
  "puppeteer",
@@ -32541,7 +32646,7 @@ function findChrome() {
32541
32646
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
32542
32647
  ];
32543
32648
  for (const c of candidates) {
32544
- if (c && existsSync14(c)) return c;
32649
+ if (c && existsSync15(c)) return c;
32545
32650
  }
32546
32651
  return void 0;
32547
32652
  }
@@ -32573,7 +32678,7 @@ var init_ffmpeg_gate = __esm({
32573
32678
  });
32574
32679
 
32575
32680
  // src/commands/_shared/scene-audio-scan.ts
32576
- import { readFile as readFile15 } from "node:fs/promises";
32681
+ import { readFile as readFile16 } from "node:fs/promises";
32577
32682
  import { resolve as resolve18 } from "node:path";
32578
32683
  function parseRootClips(rootHtml) {
32579
32684
  const clips = [];
@@ -32614,7 +32719,7 @@ function makeFsCompositionReader(projectDir) {
32614
32719
  return async (compositionSrcRel) => {
32615
32720
  const abs = resolve18(projectDir, compositionSrcRel);
32616
32721
  try {
32617
- return await readFile15(abs, "utf-8");
32722
+ return await readFile16(abs, "utf-8");
32618
32723
  } catch {
32619
32724
  return null;
32620
32725
  }
@@ -32814,9 +32919,9 @@ __export(scene_render_exports, {
32814
32919
  qualityToCrf: () => qualityToCrf
32815
32920
  });
32816
32921
  import { execFile as execFile2 } from "node:child_process";
32817
- import { existsSync as existsSync15 } from "node:fs";
32818
- import { mkdir as mkdir9, readFile as readFile16, stat as stat3, writeFile as writeFile16 } from "node:fs/promises";
32819
- 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";
32820
32925
  import { promisify as promisify2 } from "node:util";
32821
32926
  import {
32822
32927
  createRenderJob,
@@ -32908,7 +33013,7 @@ async function executeSceneRender(opts = {}) {
32908
33013
  }
32909
33014
  let autoSyncApplied = false;
32910
33015
  let autoSyncWarning;
32911
- if (!opts.beatId && existsSync15(join14(projectDir, "STORYBOARD.md"))) {
33016
+ if (!opts.beatId && existsSync16(join15(projectDir, "STORYBOARD.md"))) {
32912
33017
  try {
32913
33018
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
32914
33019
  const subPlans = await createSubCompDurationSyncPlans({
@@ -33000,7 +33105,7 @@ async function executeSceneRender(opts = {}) {
33000
33105
  let audioMuxWarning;
33001
33106
  try {
33002
33107
  opts.onProgress?.(0.95, "Mixing audio");
33003
- const rootHtml = await readFile16(resolve20(projectDir, root), "utf-8");
33108
+ const rootHtml = await readFile17(resolve20(projectDir, root), "utf-8");
33004
33109
  const audios = await scanSceneAudio({ projectDir, rootHtml });
33005
33110
  audioCount = audios.length;
33006
33111
  if (audios.length > 0) {
@@ -33085,11 +33190,11 @@ async function safeStat(p) {
33085
33190
  }
33086
33191
  async function prepareBeatRenderRoot(opts) {
33087
33192
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
33088
- const reportPath = join14(opts.projectDir, "build-report.json");
33193
+ const reportPath = join15(opts.projectDir, "build-report.json");
33089
33194
  const retryWith = [
33090
33195
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
33091
33196
  ];
33092
- if (!existsSync15(reportPath)) {
33197
+ if (!existsSync16(reportPath)) {
33093
33198
  return {
33094
33199
  success: false,
33095
33200
  root,
@@ -33100,7 +33205,7 @@ async function prepareBeatRenderRoot(opts) {
33100
33205
  }
33101
33206
  let report;
33102
33207
  try {
33103
- report = JSON.parse(await readFile16(reportPath, "utf-8"));
33208
+ report = JSON.parse(await readFile17(reportPath, "utf-8"));
33104
33209
  } catch (error) {
33105
33210
  return {
33106
33211
  success: false,
@@ -33169,8 +33274,8 @@ async function prepareBeatRenderRoot(opts) {
33169
33274
  durationSec,
33170
33275
  narrationPath
33171
33276
  });
33172
- await mkdir9(dirname8(join14(opts.projectDir, root)), { recursive: true });
33173
- 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");
33174
33279
  return {
33175
33280
  success: true,
33176
33281
  root,
@@ -33234,7 +33339,7 @@ function reportedBeatAssetPaths(beat) {
33234
33339
  ]);
33235
33340
  }
33236
33341
  async function writeRenderReport(projectDir, result) {
33237
- const reportPath = join14(projectDir, "render-report.json");
33342
+ const reportPath = join15(projectDir, "render-report.json");
33238
33343
  try {
33239
33344
  await writeFile16(
33240
33345
  reportPath,
@@ -33276,7 +33381,7 @@ async function writeRenderReport(projectDir, result) {
33276
33381
  }
33277
33382
  function pathExists3(projectDir, value) {
33278
33383
  const abs = isAbsolute(value) ? value : resolve20(projectDir, value);
33279
- return existsSync15(abs);
33384
+ return existsSync16(abs);
33280
33385
  }
33281
33386
  function stringValue(value) {
33282
33387
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -33349,7 +33454,7 @@ var init_concurrency = __esm({
33349
33454
  });
33350
33455
 
33351
33456
  // src/commands/_shared/build-asset-reference.ts
33352
- import { existsSync as existsSync16 } from "node:fs";
33457
+ import { existsSync as existsSync17 } from "node:fs";
33353
33458
  import { extname as extname3, isAbsolute as isAbsolute2, relative as relative8, resolve as resolve21, sep } from "node:path";
33354
33459
  function resolveTypedAssetReference(projectDir, kind, value) {
33355
33460
  const raw = stringOrUndefined2(value);
@@ -33380,7 +33485,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
33380
33485
  const absPath = isAbsolute2(raw) ? resolve21(raw) : resolve21(projectDir, raw);
33381
33486
  const rel = relative8(resolve21(projectDir), absPath);
33382
33487
  const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute2(rel);
33383
- const exists = withinProject && existsSync16(absPath);
33488
+ const exists = withinProject && existsSync17(absPath);
33384
33489
  let error = forcedError;
33385
33490
  if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
33386
33491
  error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
@@ -33567,9 +33672,9 @@ var init_build_backdrop_prompt = __esm({
33567
33672
  });
33568
33673
 
33569
33674
  // src/commands/_shared/build-plan.ts
33570
- import { existsSync as existsSync17 } from "node:fs";
33571
- import { readFile as readFile17 } from "node:fs/promises";
33572
- 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";
33573
33678
  function backdropCostUsd(quality) {
33574
33679
  return quality === "hd" ? 0.2 : 0.05;
33575
33680
  }
@@ -33578,11 +33683,11 @@ async function createBuildPlan(opts) {
33578
33683
  loadPlanEnv(projectDir);
33579
33684
  const stage = opts.stage ?? "all";
33580
33685
  const config2 = await readProjectConfig(projectDir);
33581
- const storyboardPath = join15(projectDir, "STORYBOARD.md");
33686
+ const storyboardPath = join16(projectDir, "STORYBOARD.md");
33582
33687
  const warnings = [];
33583
33688
  const retryWith = [];
33584
33689
  const providerResolution = [];
33585
- if (!existsSync17(storyboardPath)) {
33690
+ if (!existsSync18(storyboardPath)) {
33586
33691
  const validation2 = {
33587
33692
  ok: false,
33588
33693
  issues: [
@@ -33611,7 +33716,7 @@ async function createBuildPlan(opts) {
33611
33716
  validation: validation2
33612
33717
  });
33613
33718
  }
33614
- const storyboardMd = await readFile17(storyboardPath, "utf-8");
33719
+ const storyboardMd = await readFile18(storyboardPath, "utf-8");
33615
33720
  const validation = validateStoryboardMarkdown(storyboardMd);
33616
33721
  const validationIssues = [...validation.issues];
33617
33722
  const parsed = parseStoryboard(storyboardMd);
@@ -33637,7 +33742,7 @@ async function createBuildPlan(opts) {
33637
33742
  const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
33638
33743
  const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
33639
33744
  const imageRatio = imageRatioForSize(imageSize2);
33640
- 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`)));
33641
33746
  const resolved = await resolvePlanProviders({
33642
33747
  projectDir,
33643
33748
  config: config2,
@@ -33785,7 +33890,7 @@ async function createBuildPlan(opts) {
33785
33890
  active: includeAssets
33786
33891
  }) : null;
33787
33892
  const compositionPath = `compositions/scene-${beat.id}.html`;
33788
- const compositionExists = existsSync17(join15(projectDir, compositionPath));
33893
+ const compositionExists = existsSync18(join16(projectDir, compositionPath));
33789
33894
  for (const asset of [narration, backdrop, keyframe, video, music]) {
33790
33895
  if (!asset) continue;
33791
33896
  if (asset.referenceError) {
@@ -33854,7 +33959,7 @@ async function createBuildPlan(opts) {
33854
33959
  }
33855
33960
  }
33856
33961
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
33857
- if (!existsSync17(join15(projectDir, config2.config.composition.entry))) {
33962
+ if (!existsSync18(join16(projectDir, config2.config.composition.entry))) {
33858
33963
  missing.add("root-composition");
33859
33964
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
33860
33965
  }
@@ -33888,7 +33993,7 @@ async function createBuildPlan(opts) {
33888
33993
  }
33889
33994
  if (referenced.size > 0) {
33890
33995
  const toGenerate = resolveCharacters(parsed.frontmatter).filter(
33891
- (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`)))
33892
33997
  );
33893
33998
  if (toGenerate.length > 0) {
33894
33999
  estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
@@ -34001,8 +34106,8 @@ function assetPlan(opts) {
34001
34106
  reason: ready ? "referenced-asset" : "invalid-reference"
34002
34107
  };
34003
34108
  }
34004
- const canonicalExists = existsSync17(join15(opts.projectDir, opts.path));
34005
- 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;
34006
34111
  const freshness = assetFreshnessFromMetadata({
34007
34112
  projectDir: opts.projectDir,
34008
34113
  kind: opts.kind,
@@ -34200,7 +34305,7 @@ async function firstConfiguredComposer(projectDir) {
34200
34305
  }
34201
34306
  function firstExisting2(projectDir, paths) {
34202
34307
  for (const path2 of paths) {
34203
- if (existsSync17(join15(projectDir, path2))) return path2;
34308
+ if (existsSync18(join16(projectDir, path2))) return path2;
34204
34309
  }
34205
34310
  return null;
34206
34311
  }
@@ -34241,12 +34346,12 @@ function unique3(items) {
34241
34346
  return [...new Set(items.filter((item) => item.length > 0))];
34242
34347
  }
34243
34348
  function loadPlanEnv(projectDir) {
34244
- (0, import_dotenv2.config)({ path: join15(projectDir, ".env"), quiet: true });
34349
+ (0, import_dotenv2.config)({ path: join16(projectDir, ".env"), quiet: true });
34245
34350
  (0, import_dotenv2.config)({ path: resolve22(process.cwd(), ".env"), quiet: true });
34246
34351
  let dir = process.cwd();
34247
34352
  while (dir !== dirname9(dir)) {
34248
- if (existsSync17(join15(dir, "pnpm-workspace.yaml"))) {
34249
- (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 });
34250
34355
  return;
34251
34356
  }
34252
34357
  dir = dirname9(dir);
@@ -34308,7 +34413,7 @@ var init_validate = __esm({
34308
34413
  // src/commands/generate/music.ts
34309
34414
  import { resolve as resolve24 } from "node:path";
34310
34415
  import { writeFile as writeFile17 } from "node:fs/promises";
34311
- import { existsSync as existsSync18 } from "node:fs";
34416
+ import { existsSync as existsSync19 } from "node:fs";
34312
34417
  async function executeMusic(options) {
34313
34418
  try {
34314
34419
  const provider = options.provider || "elevenlabs";
@@ -34461,7 +34566,7 @@ function registerMusicCommand(parent) {
34461
34566
  if (options.melody) {
34462
34567
  spinner2.text = "Uploading melody reference...";
34463
34568
  const absPath = resolve24(process.cwd(), options.melody);
34464
- if (!existsSync18(absPath)) {
34569
+ if (!existsSync19(absPath)) {
34465
34570
  spinner2.fail(`Melody file not found: ${options.melody}`);
34466
34571
  exitWithError(notFoundError(options.melody));
34467
34572
  }
@@ -34594,9 +34699,9 @@ __export(scene_build_exports, {
34594
34699
  executeSceneBuild: () => executeSceneBuild,
34595
34700
  resolveSceneBuildMode: () => resolveSceneBuildMode
34596
34701
  });
34597
- import { existsSync as existsSync19, readFileSync as readFileSync3 } from "node:fs";
34598
- import { copyFile as copyFile3, mkdir as mkdir10, readFile as readFile18, rm, writeFile as writeFile18 } from "node:fs/promises";
34599
- 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";
34600
34705
  async function executeSceneBuild(opts) {
34601
34706
  const startedAt = Date.now();
34602
34707
  const projectDir = resolve25(opts.projectDir);
@@ -34615,7 +34720,7 @@ async function executeSceneBuild(opts) {
34615
34720
  );
34616
34721
  }
34617
34722
  let sceneRepair = skippedSceneRepairSummary();
34618
- const storyboardPath = join16(projectDir, "STORYBOARD.md");
34723
+ const storyboardPath = join17(projectDir, "STORYBOARD.md");
34619
34724
  const buildPlan = await createBuildPlan({
34620
34725
  projectDir,
34621
34726
  stage: selectedStage,
@@ -34666,8 +34771,8 @@ async function executeSceneBuild(opts) {
34666
34771
  }
34667
34772
  if (!buildPlan.validation.ok) {
34668
34773
  let invalidBeats = [];
34669
- if (existsSync19(storyboardPath)) {
34670
- invalidBeats = parseStoryboard(await readFile18(storyboardPath, "utf-8")).beats;
34774
+ if (existsSync20(storyboardPath)) {
34775
+ invalidBeats = parseStoryboard(await readFile19(storyboardPath, "utf-8")).beats;
34671
34776
  }
34672
34777
  return finishBuildResult({
34673
34778
  success: false,
@@ -34691,7 +34796,7 @@ async function executeSceneBuild(opts) {
34691
34796
  totalLatencyMs: Date.now() - startedAt
34692
34797
  });
34693
34798
  }
34694
- if (!existsSync19(storyboardPath)) {
34799
+ if (!existsSync20(storyboardPath)) {
34695
34800
  return finishBuildResult({
34696
34801
  success: false,
34697
34802
  phase: "failed",
@@ -34705,7 +34810,7 @@ async function executeSceneBuild(opts) {
34705
34810
  totalLatencyMs: Date.now() - startedAt
34706
34811
  });
34707
34812
  }
34708
- const storyboardMd = await readFile18(storyboardPath, "utf-8");
34813
+ const storyboardMd = await readFile19(storyboardPath, "utf-8");
34709
34814
  const parsed = parseStoryboard(storyboardMd);
34710
34815
  if (parsed.beats.length === 0) {
34711
34816
  return finishBuildResult({
@@ -34812,6 +34917,7 @@ async function executeSceneBuild(opts) {
34812
34917
  skipVideo: opts.skipVideo ?? false,
34813
34918
  skipKeyframe: opts.skipKeyframe ?? false,
34814
34919
  skipMusic: opts.skipMusic ?? false,
34920
+ skipTranscript: opts.skipTranscript ?? false,
34815
34921
  force: opts.force ?? false,
34816
34922
  onProgress,
34817
34923
  characterPaths
@@ -34899,9 +35005,9 @@ async function executeSceneBuild(opts) {
34899
35005
  let composeData;
34900
35006
  if (shouldRunStage(selectedStage, "compose")) {
34901
35007
  if (mode === "agent") {
34902
- const compositionsDir = join16(projectDir, "compositions");
35008
+ const compositionsDir = join17(projectDir, "compositions");
34903
35009
  const missingBeats = activeBeats.filter(
34904
- (b) => !existsSync19(join16(compositionsDir, `scene-${b.id}.html`))
35010
+ (b) => !existsSync20(join17(compositionsDir, `scene-${b.id}.html`))
34905
35011
  );
34906
35012
  if (missingBeats.length > 0) {
34907
35013
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -34990,8 +35096,8 @@ async function executeSceneBuild(opts) {
34990
35096
  if (stageReports.compose.status === "done") {
34991
35097
  for (const beat of activeBeats) {
34992
35098
  const videoRel = `assets/video-${beat.id}.mp4`;
34993
- const compositionAbs = join16(projectDir, "compositions", `scene-${beat.id}.html`);
34994
- 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;
34995
35101
  const html = readFileSync3(compositionAbs, "utf-8");
34996
35102
  if (!html.includes(videoRel)) {
34997
35103
  warnings.push(
@@ -35049,7 +35155,7 @@ async function executeSceneBuild(opts) {
35049
35155
  });
35050
35156
  }
35051
35157
  if (shouldRunStage(selectedStage, "sync")) {
35052
- if (!existsSync19(join16(projectDir, "index.html"))) {
35158
+ if (!existsSync20(join17(projectDir, "index.html"))) {
35053
35159
  await scaffoldSceneProject({
35054
35160
  dir: projectDir,
35055
35161
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -35194,6 +35300,9 @@ async function buildBeatPrimitives(beat, ctx) {
35194
35300
  ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx, keyframe),
35195
35301
  ctx.skipMusic ? skipped("music", beat.id, "--skip-music", ctx) : dispatchMusic(beat, ctx)
35196
35302
  ]);
35303
+ if (!ctx.skipTranscript) {
35304
+ await dispatchTranscript(beat, narration, ctx);
35305
+ }
35197
35306
  return {
35198
35307
  outcome: {
35199
35308
  beatId: beat.id,
@@ -35311,7 +35420,7 @@ async function dispatchNarration(beat, ctx) {
35311
35420
  return {
35312
35421
  status: "cached",
35313
35422
  path: existingRel,
35314
- durationSec: await safeAudioDuration(join16(ctx.projectDir, existingRel)),
35423
+ durationSec: await safeAudioDuration(join17(ctx.projectDir, existingRel)),
35315
35424
  provider: metadata?.provider,
35316
35425
  cachePath: metadata?.cachePath,
35317
35426
  cacheKey: metadata?.cacheKey,
@@ -35335,9 +35444,9 @@ async function dispatchNarration(beat, ctx) {
35335
35444
  voice,
35336
35445
  ext: resolution.audioExtension
35337
35446
  });
35338
- const cacheAbs = join16(ctx.projectDir, cache.path);
35447
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35339
35448
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
35340
- const abs = join16(ctx.projectDir, rel);
35449
+ const abs = join17(ctx.projectDir, rel);
35341
35450
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
35342
35451
  projectDir: ctx.projectDir,
35343
35452
  kind: "narration",
@@ -35351,7 +35460,7 @@ async function dispatchNarration(beat, ctx) {
35351
35460
  return {
35352
35461
  status: "cached",
35353
35462
  path: existingRel,
35354
- durationSec: await safeAudioDuration(join16(ctx.projectDir, existingRel)),
35463
+ durationSec: await safeAudioDuration(join17(ctx.projectDir, existingRel)),
35355
35464
  provider: resolution.provider,
35356
35465
  cachePath: cache.path,
35357
35466
  cacheKey: cache.key,
@@ -35359,7 +35468,7 @@ async function dispatchNarration(beat, ctx) {
35359
35468
  freshness: "fresh"
35360
35469
  };
35361
35470
  }
35362
- if (existsSync19(cacheAbs) && !ctx.force) {
35471
+ if (existsSync20(cacheAbs) && !ctx.force) {
35363
35472
  await mkdir10(dirname10(abs), { recursive: true });
35364
35473
  await copyFile3(cacheAbs, abs);
35365
35474
  await writeAssetMetadata({
@@ -35394,8 +35503,8 @@ async function dispatchNarration(beat, ctx) {
35394
35503
  await mkdir10(dirname10(abs), { recursive: true });
35395
35504
  await writeFile18(abs, result.audioBuffer);
35396
35505
  for (const otherExt of ["mp3", "wav"]) {
35397
- const sibling = join16(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
35398
- 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 });
35399
35508
  }
35400
35509
  await mkdir10(dirname10(cacheAbs), { recursive: true });
35401
35510
  await writeFile18(cacheAbs, result.audioBuffer);
@@ -35427,6 +35536,42 @@ async function dispatchNarration(beat, ctx) {
35427
35536
  freshness: "fresh"
35428
35537
  };
35429
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
+ }
35430
35575
  function characterSheetPrompt(name, description) {
35431
35576
  return [
35432
35577
  `Character reference sheet of an original fictional character named ${name}: ${description}.`,
@@ -35444,7 +35589,7 @@ async function buildCharacters(characters, ctx) {
35444
35589
  const size = ctx.imageSize ?? "1536x1024";
35445
35590
  for (const character of characters) {
35446
35591
  if (character.imagePath) {
35447
- if (existsSync19(join16(ctx.projectDir, character.imagePath))) {
35592
+ if (existsSync20(join17(ctx.projectDir, character.imagePath))) {
35448
35593
  paths.set(character.name, character.imagePath);
35449
35594
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
35450
35595
  } else {
@@ -35457,7 +35602,7 @@ async function buildCharacters(characters, ctx) {
35457
35602
  if (!character.prompt) continue;
35458
35603
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
35459
35604
  const rel = `assets/character-${character.name}.png`;
35460
- const abs = join16(ctx.projectDir, rel);
35605
+ const abs = join17(ctx.projectDir, rel);
35461
35606
  const metadataOptions = { quality: ctx.imageQuality, size };
35462
35607
  const cache = characterCacheDescriptor({
35463
35608
  name: character.name,
@@ -35466,7 +35611,7 @@ async function buildCharacters(characters, ctx) {
35466
35611
  quality: ctx.imageQuality,
35467
35612
  size
35468
35613
  });
35469
- if (existsSync19(abs) && !ctx.force) {
35614
+ if (existsSync20(abs) && !ctx.force) {
35470
35615
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
35471
35616
  if (!metadata || isFreshCanonicalAsset({
35472
35617
  projectDir: ctx.projectDir,
@@ -35482,8 +35627,8 @@ async function buildCharacters(characters, ctx) {
35482
35627
  continue;
35483
35628
  }
35484
35629
  }
35485
- const cacheAbs = join16(ctx.projectDir, cache.path);
35486
- if (existsSync19(cacheAbs) && !ctx.force) {
35630
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35631
+ if (existsSync20(cacheAbs) && !ctx.force) {
35487
35632
  await mkdir10(dirname10(abs), { recursive: true });
35488
35633
  await copyFile3(cacheAbs, abs);
35489
35634
  await writeAssetMetadata({
@@ -35549,7 +35694,7 @@ async function dispatchBackdrop(beat, ctx) {
35549
35694
  if (!cue) return { status: "no-cue" };
35550
35695
  const prompt3 = augmentBackdropPrompt(cue);
35551
35696
  const rel = `assets/backdrop-${beat.id}.png`;
35552
- const abs = join16(ctx.projectDir, rel);
35697
+ const abs = join17(ctx.projectDir, rel);
35553
35698
  const size = ctx.imageSize ?? "1536x1024";
35554
35699
  const ratio = imageRatioForSize(size);
35555
35700
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -35562,7 +35707,7 @@ async function dispatchBackdrop(beat, ctx) {
35562
35707
  ratio
35563
35708
  });
35564
35709
  const metadataPath = assetMetadataPath("backdrop", beat.id);
35565
- if (existsSync19(abs) && !ctx.force) {
35710
+ if (existsSync20(abs) && !ctx.force) {
35566
35711
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
35567
35712
  if (!metadata || isFreshCanonicalAsset({
35568
35713
  projectDir: ctx.projectDir,
@@ -35585,8 +35730,8 @@ async function dispatchBackdrop(beat, ctx) {
35585
35730
  };
35586
35731
  }
35587
35732
  }
35588
- const cacheAbs = join16(ctx.projectDir, cache.path);
35589
- if (existsSync19(cacheAbs) && !ctx.force) {
35733
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35734
+ if (existsSync20(cacheAbs) && !ctx.force) {
35590
35735
  await mkdir10(dirname10(abs), { recursive: true });
35591
35736
  await copyFile3(cacheAbs, abs);
35592
35737
  await writeAssetMetadata({
@@ -35718,7 +35863,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
35718
35863
  }
35719
35864
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35720
35865
  const rel = `assets/keyframe-${beat.id}.png`;
35721
- const abs = join16(ctx.projectDir, rel);
35866
+ const abs = join17(ctx.projectDir, rel);
35722
35867
  const size = ctx.imageSize ?? "1536x1024";
35723
35868
  const ratio = imageRatioForSize(size);
35724
35869
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -35731,7 +35876,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35731
35876
  ratio,
35732
35877
  characters: characterRefsRel
35733
35878
  });
35734
- if (existsSync19(abs) && !ctx.force) {
35879
+ if (existsSync20(abs) && !ctx.force) {
35735
35880
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
35736
35881
  if (!metadata || isFreshCanonicalAsset({
35737
35882
  projectDir: ctx.projectDir,
@@ -35745,8 +35890,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35745
35890
  return { status: "cached", rel };
35746
35891
  }
35747
35892
  }
35748
- const cacheAbs = join16(ctx.projectDir, cache.path);
35749
- if (existsSync19(cacheAbs) && !ctx.force) {
35893
+ const cacheAbs = join17(ctx.projectDir, cache.path);
35894
+ if (existsSync20(cacheAbs) && !ctx.force) {
35750
35895
  await mkdir10(dirname10(abs), { recursive: true });
35751
35896
  await copyFile3(cacheAbs, abs);
35752
35897
  await writeAssetMetadata({
@@ -35764,8 +35909,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35764
35909
  }
35765
35910
  const sheetBuffers = [];
35766
35911
  for (const relPath of characterRefsRel) {
35767
- const sheetAbs = join16(ctx.projectDir, relPath);
35768
- if (existsSync19(sheetAbs)) sheetBuffers.push(await readFile18(sheetAbs));
35912
+ const sheetAbs = join17(ctx.projectDir, relPath);
35913
+ if (existsSync20(sheetAbs)) sheetBuffers.push(await readFile19(sheetAbs));
35769
35914
  }
35770
35915
  const imgCtx = {
35771
35916
  projectDir: ctx.projectDir,
@@ -35834,9 +35979,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
35834
35979
  const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
35835
35980
  if (!prompt3) return { status: "no-cue" };
35836
35981
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
35837
- const characterRefsAbs = characterRefsRel.map((p) => join16(ctx.projectDir, p));
35982
+ const characterRefsAbs = characterRefsRel.map((p) => join17(ctx.projectDir, p));
35838
35983
  const rel = `assets/video-${beat.id}.mp4`;
35839
- const abs = join16(ctx.projectDir, rel);
35984
+ const abs = join17(ctx.projectDir, rel);
35840
35985
  const cache = videoCacheDescriptor({
35841
35986
  beatId: beat.id,
35842
35987
  cue: prompt3,
@@ -35848,7 +35993,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35848
35993
  keyframe: keyframeCue
35849
35994
  });
35850
35995
  const metadataPath = assetMetadataPath("video", beat.id);
35851
- if (existsSync19(abs) && !ctx.force) {
35996
+ if (existsSync20(abs) && !ctx.force) {
35852
35997
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
35853
35998
  if (!metadata || isFreshCanonicalAsset({
35854
35999
  projectDir: ctx.projectDir,
@@ -35871,8 +36016,8 @@ async function dispatchVideo(beat, ctx, keyframe) {
35871
36016
  };
35872
36017
  }
35873
36018
  }
35874
- const cacheAbs = join16(ctx.projectDir, cache.path);
35875
- if (existsSync19(cacheAbs) && !ctx.force) {
36019
+ const cacheAbs = join17(ctx.projectDir, cache.path);
36020
+ if (existsSync20(cacheAbs) && !ctx.force) {
35876
36021
  await mkdir10(dirname10(abs), { recursive: true });
35877
36022
  await copyFile3(cacheAbs, abs);
35878
36023
  await writeAssetMetadata({
@@ -35903,7 +36048,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35903
36048
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
35904
36049
  return { status: "skipped" };
35905
36050
  }
35906
- const keyframeImageAbs = keyframeImageRel ? join16(ctx.projectDir, keyframeImageRel) : void 0;
36051
+ const keyframeImageAbs = keyframeImageRel ? join17(ctx.projectDir, keyframeImageRel) : void 0;
35907
36052
  loadSceneBuildEnv(ctx.projectDir);
35908
36053
  const result = await executeVideoGenerate({
35909
36054
  prompt: prompt3,
@@ -35927,7 +36072,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35927
36072
  ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
35928
36073
  return { status: "failed", error };
35929
36074
  }
35930
- if (result.status === "completed" && existsSync19(abs)) {
36075
+ if (result.status === "completed" && existsSync20(abs)) {
35931
36076
  await mkdir10(dirname10(cacheAbs), { recursive: true });
35932
36077
  await copyFile3(abs, cacheAbs);
35933
36078
  await writeAssetMetadata({
@@ -35995,7 +36140,7 @@ async function dispatchMusic(beat, ctx) {
35995
36140
  const prompt3 = stringOrUndefined4(beat.cues?.music);
35996
36141
  if (!prompt3) return { status: "no-cue" };
35997
36142
  const rel = `assets/music-${beat.id}.mp3`;
35998
- const abs = join16(ctx.projectDir, rel);
36143
+ const abs = join17(ctx.projectDir, rel);
35999
36144
  const duration = normalizeMusicDuration(beat.duration);
36000
36145
  const cache = musicCacheDescriptor({
36001
36146
  beatId: beat.id,
@@ -36004,7 +36149,7 @@ async function dispatchMusic(beat, ctx) {
36004
36149
  duration
36005
36150
  });
36006
36151
  const metadataPath = assetMetadataPath("music", beat.id);
36007
- if (existsSync19(abs) && !ctx.force) {
36152
+ if (existsSync20(abs) && !ctx.force) {
36008
36153
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
36009
36154
  if (!metadata || isFreshCanonicalAsset({
36010
36155
  projectDir: ctx.projectDir,
@@ -36028,8 +36173,8 @@ async function dispatchMusic(beat, ctx) {
36028
36173
  };
36029
36174
  }
36030
36175
  }
36031
- const cacheAbs = join16(ctx.projectDir, cache.path);
36032
- if (existsSync19(cacheAbs) && !ctx.force) {
36176
+ const cacheAbs = join17(ctx.projectDir, cache.path);
36177
+ if (existsSync20(cacheAbs) && !ctx.force) {
36033
36178
  await mkdir10(dirname10(abs), { recursive: true });
36034
36179
  await copyFile3(cacheAbs, abs);
36035
36180
  await writeAssetMetadata({
@@ -36103,7 +36248,7 @@ async function dispatchMusic(beat, ctx) {
36103
36248
  metadataPath
36104
36249
  };
36105
36250
  }
36106
- if (!existsSync19(abs)) {
36251
+ if (!existsSync20(abs)) {
36107
36252
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
36108
36253
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
36109
36254
  return { status: "failed", error };
@@ -36139,12 +36284,12 @@ async function dispatchMusic(beat, ctx) {
36139
36284
  };
36140
36285
  }
36141
36286
  function loadSceneBuildEnv(projectDir) {
36142
- (0, import_dotenv3.config)({ path: join16(projectDir, ".env"), quiet: true });
36287
+ (0, import_dotenv3.config)({ path: join17(projectDir, ".env"), quiet: true });
36143
36288
  (0, import_dotenv3.config)({ path: resolve25(process.cwd(), ".env"), quiet: true });
36144
36289
  let dir = process.cwd();
36145
36290
  while (dir !== dirname10(dir)) {
36146
- if (existsSync19(join16(dir, "pnpm-workspace.yaml"))) {
36147
- (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 });
36148
36293
  return;
36149
36294
  }
36150
36295
  dir = dirname10(dir);
@@ -36360,7 +36505,7 @@ function mergeBeatOutcomes(base, updates) {
36360
36505
  }
36361
36506
  function firstExisting3(projectDir, relPaths) {
36362
36507
  for (const rel of relPaths) {
36363
- if (existsSync19(join16(projectDir, rel))) return rel;
36508
+ if (existsSync20(join17(projectDir, rel))) return rel;
36364
36509
  }
36365
36510
  return null;
36366
36511
  }
@@ -36391,7 +36536,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
36391
36536
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
36392
36537
  }
36393
36538
  async function finalizeBuildResult(projectDir, startedAt, result) {
36394
- const reportPath = join16(projectDir, "build-report.json");
36539
+ const reportPath = join17(projectDir, "build-report.json");
36395
36540
  if (result.stageReports) {
36396
36541
  for (const report of Object.values(result.stageReports)) {
36397
36542
  if (report.status === "pending") report.status = "skipped";
@@ -36550,7 +36695,7 @@ function normalizeReportDuration(value) {
36550
36695
  }
36551
36696
  function buildReportComposition(projectDir, result, beatId) {
36552
36697
  const path2 = `compositions/scene-${beatId}.html`;
36553
- const exists = existsSync19(join16(projectDir, path2));
36698
+ const exists = existsSync20(join17(projectDir, path2));
36554
36699
  const written = composeWrittenEntry(result, beatId);
36555
36700
  if (written) {
36556
36701
  return {
@@ -36607,8 +36752,8 @@ function summarizeBuildBeats(projectDir, beats) {
36607
36752
  let compositionsReady = 0;
36608
36753
  for (const beat of beats) {
36609
36754
  if (beatAssetsReady(beat)) assetsReady += 1;
36610
- const compositionPath = join16(projectDir, "compositions", `scene-${beat.beatId}.html`);
36611
- if (existsSync19(compositionPath)) {
36755
+ const compositionPath = join17(projectDir, "compositions", `scene-${beat.beatId}.html`);
36756
+ if (existsSync20(compositionPath)) {
36612
36757
  compositionsReady += 1;
36613
36758
  } else {
36614
36759
  needsAuthor.push(beat.beatId);
@@ -36650,6 +36795,8 @@ var init_scene_build = __esm({
36650
36795
  init_compose_scenes_skills();
36651
36796
  init_compose_prompts();
36652
36797
  init_config();
36798
+ init_api_key();
36799
+ init_transcribe_narration();
36653
36800
  init_scene_render();
36654
36801
  init_storyboard_parse();
36655
36802
  init_scene_project();
@@ -36778,18 +36925,18 @@ __export(ai_audio_exports, {
36778
36925
  executeVoiceClone: () => executeVoiceClone
36779
36926
  });
36780
36927
  import { resolve as resolve27, dirname as dirname11, basename as basename5, extname as extname4 } from "node:path";
36781
- import { readFile as readFile19, writeFile as writeFile19 } from "node:fs/promises";
36782
- 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";
36783
36930
  async function executeTranscribe(options) {
36784
36931
  const { audioPath, language, output, format, apiKey } = options;
36785
36932
  try {
36786
36933
  const key = apiKey || process.env.OPENAI_API_KEY;
36787
36934
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
36788
36935
  const absPath = resolve27(process.cwd(), audioPath);
36789
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${absPath}` };
36936
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${absPath}` };
36790
36937
  const whisper = new WhisperProvider();
36791
36938
  await whisper.initialize({ apiKey: key });
36792
- const audioBuffer = await readFile19(absPath);
36939
+ const audioBuffer = await readFile20(absPath);
36793
36940
  const audioBlob = new Blob([audioBuffer]);
36794
36941
  const result = await whisper.transcribe(audioBlob, language);
36795
36942
  if (result.status === "failed") {
@@ -36819,8 +36966,8 @@ async function executeIsolate(options) {
36819
36966
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
36820
36967
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
36821
36968
  const absPath = resolve27(process.cwd(), audioPath);
36822
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${absPath}` };
36823
- const audioBuffer = await readFile19(absPath);
36969
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${absPath}` };
36970
+ const audioBuffer = await readFile20(absPath);
36824
36971
  const elevenlabs = new ElevenLabsProvider();
36825
36972
  await elevenlabs.initialize({ apiKey: key });
36826
36973
  const result = await elevenlabs.isolateVocals(audioBuffer);
@@ -36845,8 +36992,8 @@ async function executeVoiceClone(options) {
36845
36992
  const audioBuffers = [];
36846
36993
  for (const samplePath of samplePaths) {
36847
36994
  const absPath = resolve27(process.cwd(), samplePath);
36848
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${samplePath}` };
36849
- const buffer = await readFile19(absPath);
36995
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${samplePath}` };
36996
+ const buffer = await readFile20(absPath);
36850
36997
  audioBuffers.push(buffer);
36851
36998
  }
36852
36999
  const elevenlabs = new ElevenLabsProvider();
@@ -36867,7 +37014,7 @@ async function executeDub(options) {
36867
37014
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
36868
37015
  try {
36869
37016
  const absPath = resolve27(process.cwd(), mediaPath);
36870
- if (!existsSync20(absPath)) return { success: false, error: `File not found: ${absPath}` };
37017
+ if (!existsSync21(absPath)) return { success: false, error: `File not found: ${absPath}` };
36871
37018
  const openaiKey = process.env.OPENAI_API_KEY;
36872
37019
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
36873
37020
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -36884,7 +37031,7 @@ async function executeDub(options) {
36884
37031
  }
36885
37032
  const whisper = new WhisperProvider();
36886
37033
  await whisper.initialize({ apiKey: openaiKey });
36887
- const audioBuffer = await readFile19(audioPath);
37034
+ const audioBuffer = await readFile20(audioPath);
36888
37035
  const audioBlob = new Blob([audioBuffer]);
36889
37036
  const transcriptResult = await whisper.transcribe(audioBlob, source);
36890
37037
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -36995,8 +37142,8 @@ async function executeDuck(options) {
36995
37142
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
36996
37143
  const absMusicPath = resolve27(process.cwd(), musicPath);
36997
37144
  const absVoicePath = resolve27(process.cwd(), voicePath);
36998
- if (!existsSync20(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
36999
- 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}` };
37000
37147
  const defaultOutput = resolve27(dirname11(absMusicPath), `${basename5(absMusicPath, extname4(absMusicPath))}-ducked${extname4(absMusicPath)}`);
37001
37148
  const outputPath = resolve27(process.cwd(), output || defaultOutput);
37002
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`;
@@ -37010,7 +37157,7 @@ async function executeDuck(options) {
37010
37157
  "-y",
37011
37158
  outputPath
37012
37159
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
37013
- 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" };
37014
37161
  return { success: true, outputPath };
37015
37162
  } catch (error) {
37016
37163
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -37026,8 +37173,8 @@ var init_ai_audio = __esm({
37026
37173
  });
37027
37174
 
37028
37175
  // src/commands/_shared/edit/silence-cut.ts
37029
- import { existsSync as existsSync21 } from "node:fs";
37030
- 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";
37031
37178
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
37032
37179
  const totalDuration = await getVideoDuration(videoPath);
37033
37180
  const { stdout, stderr } = await execSafe(
@@ -37077,7 +37224,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
37077
37224
  }
37078
37225
  const gemini = new GeminiProvider();
37079
37226
  await gemini.initialize({ apiKey: geminiApiKey });
37080
- const videoBuffer = await readFile20(videoPath);
37227
+ const videoBuffer = await readFile21(videoPath);
37081
37228
  const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
37082
37229
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
37083
37230
 
@@ -37153,7 +37300,7 @@ async function executeSilenceCut(options) {
37153
37300
  analyzeOnly = false,
37154
37301
  useGemini = false
37155
37302
  } = options;
37156
- if (!existsSync21(videoPath)) {
37303
+ if (!existsSync22(videoPath)) {
37157
37304
  return { success: false, error: `Video not found: ${videoPath}` };
37158
37305
  }
37159
37306
  if (!commandExists("ffmpeg")) {
@@ -37261,11 +37408,11 @@ var init_silence_cut = __esm({
37261
37408
  });
37262
37409
 
37263
37410
  // src/commands/_shared/edit/jump-cut.ts
37264
- import { existsSync as existsSync22 } from "node:fs";
37265
- import { readFile as readFile21, writeFile as writeFile20, mkdir as mkdir11 } from "node:fs/promises";
37266
- 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";
37267
37414
  async function transcribeWithWords(audioPath, apiKey, language) {
37268
- const audioBuffer = await readFile21(audioPath);
37415
+ const audioBuffer = await readFile22(audioPath);
37269
37416
  const audioBlob = new Blob([audioBuffer]);
37270
37417
  const formData = new FormData();
37271
37418
  formData.append("file", audioBlob, "audio.wav");
@@ -37324,7 +37471,7 @@ async function executeJumpCut(options) {
37324
37471
  analyzeOnly = false,
37325
37472
  apiKey
37326
37473
  } = options;
37327
- if (!existsSync22(videoPath)) {
37474
+ if (!existsSync23(videoPath)) {
37328
37475
  return { success: false, error: `Video not found: ${videoPath}` };
37329
37476
  }
37330
37477
  if (!commandExists("ffmpeg")) {
@@ -37343,7 +37490,7 @@ async function executeJumpCut(options) {
37343
37490
  try {
37344
37491
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
37345
37492
  await mkdir11(tmpDir, { recursive: true });
37346
- const audioPath = join17(tmpDir, "audio.wav");
37493
+ const audioPath = join18(tmpDir, "audio.wav");
37347
37494
  try {
37348
37495
  await execSafe(
37349
37496
  "ffmpeg",
@@ -37397,7 +37544,7 @@ async function executeJumpCut(options) {
37397
37544
  const segmentPaths = [];
37398
37545
  for (let i = 0; i < segments.length; i++) {
37399
37546
  const seg = segments[i];
37400
- const segPath = join17(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
37547
+ const segPath = join18(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
37401
37548
  const duration = seg.end - seg.start;
37402
37549
  await execSafe(
37403
37550
  "ffmpeg",
@@ -37420,7 +37567,7 @@ async function executeJumpCut(options) {
37420
37567
  segmentPaths.push(segPath);
37421
37568
  }
37422
37569
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
37423
- const listPath = join17(tmpDir, "concat.txt");
37570
+ const listPath = join18(tmpDir, "concat.txt");
37424
37571
  await writeFile20(listPath, concatList);
37425
37572
  await execSafe(
37426
37573
  "ffmpeg",
@@ -37527,8 +37674,8 @@ __export(remotion_exports, {
37527
37674
  wrapComponentWithVideo: () => wrapComponentWithVideo
37528
37675
  });
37529
37676
  import { writeFile as writeFile21, mkdir as mkdir12, rm as rm2, copyFile as copyFile4 } from "node:fs/promises";
37530
- import { existsSync as existsSync23 } from "node:fs";
37531
- import { join as join18 } from "node:path";
37677
+ import { existsSync as existsSync24 } from "node:fs";
37678
+ import { join as join19 } from "node:path";
37532
37679
  import { tmpdir } from "node:os";
37533
37680
  import { createHash as createHash6 } from "node:crypto";
37534
37681
  async function ensureRemotionInstalled() {
@@ -37547,7 +37694,7 @@ async function ensureRemotionInstalled() {
37547
37694
  }
37548
37695
  }
37549
37696
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
37550
- const dir = join18(tmpdir(), `vibe_motion_${Date.now()}`);
37697
+ const dir = join19(tmpdir(), `vibe_motion_${Date.now()}`);
37551
37698
  await mkdir12(dir, { recursive: true });
37552
37699
  const deps = {
37553
37700
  remotion: REMOTION_VERSION,
@@ -37566,7 +37713,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37566
37713
  dependencies: deps
37567
37714
  };
37568
37715
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
37569
- await writeFile21(join18(dir, "package.json"), packageJsonStr);
37716
+ await writeFile21(join19(dir, "package.json"), packageJsonStr);
37570
37717
  const tsconfig = {
37571
37718
  compilerOptions: {
37572
37719
  target: "ES2020",
@@ -37578,8 +37725,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37578
37725
  skipLibCheck: true
37579
37726
  }
37580
37727
  };
37581
- await writeFile21(join18(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
37582
- 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);
37583
37730
  const rootCode = `import { registerRoot, Composition } from "remotion";
37584
37731
  import { ${componentName} } from "./Component";
37585
37732
 
@@ -37598,14 +37745,14 @@ const Root = () => {
37598
37745
 
37599
37746
  registerRoot(Root);
37600
37747
  `;
37601
- await writeFile21(join18(dir, "Root.tsx"), rootCode);
37748
+ await writeFile21(join19(dir, "Root.tsx"), rootCode);
37602
37749
  const depsHash = createHash6("md5").update(packageJsonStr).digest("hex").slice(0, 12);
37603
- const cacheMarker = join18(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
37604
- const cachedModules = join18(REMOTION_CACHE_DIR, "node_modules");
37605
- 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)) {
37606
37753
  const { symlink } = await import("node:fs/promises");
37607
37754
  try {
37608
- await symlink(cachedModules, join18(dir, "node_modules"), "dir");
37755
+ await symlink(cachedModules, join19(dir, "node_modules"), "dir");
37609
37756
  } catch {
37610
37757
  await this_npmInstall(dir);
37611
37758
  }
@@ -37613,12 +37760,12 @@ registerRoot(Root);
37613
37760
  await this_npmInstall(dir);
37614
37761
  try {
37615
37762
  await mkdir12(REMOTION_CACHE_DIR, { recursive: true });
37616
- await writeFile21(join18(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
37617
- if (existsSync23(join18(dir, "node_modules"))) {
37763
+ await writeFile21(join19(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
37764
+ if (existsSync24(join19(dir, "node_modules"))) {
37618
37765
  const { rename: rename5, symlink } = await import("node:fs/promises");
37619
- await rename5(join18(dir, "node_modules"), cachedModules).catch(() => {
37766
+ await rename5(join19(dir, "node_modules"), cachedModules).catch(() => {
37620
37767
  });
37621
- await symlink(cachedModules, join18(dir, "node_modules"), "dir").catch(() => {
37768
+ await symlink(cachedModules, join19(dir, "node_modules"), "dir").catch(() => {
37622
37769
  });
37623
37770
  await writeFile21(cacheMarker, depsHash);
37624
37771
  }
@@ -37655,7 +37802,7 @@ async function renderMotion(options) {
37655
37802
  }
37656
37803
  );
37657
37804
  try {
37658
- const entryPoint = join18(dir, "Root.tsx");
37805
+ const entryPoint = join19(dir, "Root.tsx");
37659
37806
  const { execFile: execFile3 } = await import("node:child_process");
37660
37807
  const { promisify: promisify3 } = await import("node:util");
37661
37808
  const execFileAsync3 = promisify3(execFile3);
@@ -37788,10 +37935,10 @@ async function renderWithEmbeddedImage(options) {
37788
37935
  }
37789
37936
  );
37790
37937
  try {
37791
- const publicDir = join18(dir, "public");
37938
+ const publicDir = join19(dir, "public");
37792
37939
  await mkdir12(publicDir, { recursive: true });
37793
- await copyFile4(options.imagePath, join18(publicDir, options.imageFileName));
37794
- const entryPoint = join18(dir, "Root.tsx");
37940
+ await copyFile4(options.imagePath, join19(publicDir, options.imageFileName));
37941
+ const entryPoint = join19(dir, "Root.tsx");
37795
37942
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
37796
37943
  const { execFile: execFileImg } = await import("node:child_process");
37797
37944
  const { promisify: promisifyImg } = await import("node:util");
@@ -37852,10 +37999,10 @@ async function renderWithEmbeddedVideo(options) {
37852
37999
  }
37853
38000
  );
37854
38001
  try {
37855
- const publicDir = join18(dir, "public");
38002
+ const publicDir = join19(dir, "public");
37856
38003
  await mkdir12(publicDir, { recursive: true });
37857
- await copyFile4(options.videoPath, join18(publicDir, options.videoFileName));
37858
- const entryPoint = join18(dir, "Root.tsx");
38004
+ await copyFile4(options.videoPath, join19(publicDir, options.videoFileName));
38005
+ const entryPoint = join19(dir, "Root.tsx");
37859
38006
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
37860
38007
  const { execFile: execFileVid } = await import("node:child_process");
37861
38008
  const { promisify: promisifyVid } = await import("node:util");
@@ -38271,14 +38418,14 @@ var init_remotion = __esm({
38271
38418
  "use strict";
38272
38419
  init_exec_safe();
38273
38420
  REMOTION_VERSION = "4.0.447";
38274
- REMOTION_CACHE_DIR = join18(tmpdir(), "vibe_remotion_cache");
38421
+ REMOTION_CACHE_DIR = join19(tmpdir(), "vibe_remotion_cache");
38275
38422
  }
38276
38423
  });
38277
38424
 
38278
38425
  // src/commands/_shared/edit/caption.ts
38279
- import { existsSync as existsSync24 } from "node:fs";
38280
- import { readFile as readFile22, writeFile as writeFile22, mkdir as mkdir13 } from "node:fs/promises";
38281
- 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";
38282
38429
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
38283
38430
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
38284
38431
  const marginV = position === "center" ? 0 : 30;
@@ -38306,7 +38453,7 @@ async function executeCaption(options) {
38306
38453
  position = "bottom",
38307
38454
  apiKey
38308
38455
  } = options;
38309
- if (!existsSync24(videoPath)) {
38456
+ if (!existsSync25(videoPath)) {
38310
38457
  return { success: false, error: `Video not found: ${videoPath}` };
38311
38458
  }
38312
38459
  if (!commandExists("ffmpeg")) {
@@ -38325,8 +38472,8 @@ async function executeCaption(options) {
38325
38472
  try {
38326
38473
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
38327
38474
  await mkdir13(tmpDir, { recursive: true });
38328
- const audioPath = join19(tmpDir, "audio.wav");
38329
- const srtPath = join19(tmpDir, "captions.srt");
38475
+ const audioPath = join20(tmpDir, "audio.wav");
38476
+ const srtPath = join20(tmpDir, "captions.srt");
38330
38477
  try {
38331
38478
  await execSafe(
38332
38479
  "ffmpeg",
@@ -38347,7 +38494,7 @@ async function executeCaption(options) {
38347
38494
  );
38348
38495
  const whisper = new WhisperProvider();
38349
38496
  await whisper.initialize({ apiKey: openaiKey });
38350
- const audioBuffer = await readFile22(audioPath);
38497
+ const audioBuffer = await readFile23(audioPath);
38351
38498
  const audioBlob = new Blob([audioBuffer]);
38352
38499
  const transcriptResult = await whisper.transcribe(audioBlob, language);
38353
38500
  if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
@@ -38395,7 +38542,7 @@ async function executeCaption(options) {
38395
38542
  const remotionErr = await ensureRemotionInstalled2();
38396
38543
  if (remotionErr) {
38397
38544
  const outputDir2 = dirname12(outputPath);
38398
- const outputSrtPath2 = join19(
38545
+ const outputSrtPath2 = join20(
38399
38546
  outputDir2,
38400
38547
  basename6(outputPath, extname5(outputPath)) + ".srt"
38401
38548
  );
@@ -38434,7 +38581,7 @@ SRT saved to: ${outputSrtPath2}` };
38434
38581
  });
38435
38582
  if (!renderResult.success) {
38436
38583
  const outputDir2 = dirname12(outputPath);
38437
- const outputSrtPath2 = join19(
38584
+ const outputSrtPath2 = join20(
38438
38585
  outputDir2,
38439
38586
  basename6(outputPath, extname5(outputPath)) + ".srt"
38440
38587
  );
@@ -38447,7 +38594,7 @@ SRT saved to: ${outputSrtPath2}`
38447
38594
  }
38448
38595
  }
38449
38596
  const outputDir = dirname12(outputPath);
38450
- const outputSrtPath = join19(
38597
+ const outputSrtPath = join20(
38451
38598
  outputDir,
38452
38599
  basename6(outputPath, extname5(outputPath)) + ".srt"
38453
38600
  );
@@ -38484,10 +38631,10 @@ var init_caption = __esm({
38484
38631
  });
38485
38632
 
38486
38633
  // src/commands/_shared/edit/noise-reduce.ts
38487
- import { existsSync as existsSync25 } from "node:fs";
38634
+ import { existsSync as existsSync26 } from "node:fs";
38488
38635
  async function executeNoiseReduce(options) {
38489
38636
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
38490
- if (!existsSync25(inputPath)) {
38637
+ if (!existsSync26(inputPath)) {
38491
38638
  return { success: false, error: `File not found: ${inputPath}` };
38492
38639
  }
38493
38640
  if (!commandExists("ffmpeg")) {
@@ -38544,7 +38691,7 @@ var init_noise_reduce = __esm({
38544
38691
  });
38545
38692
 
38546
38693
  // src/commands/_shared/edit/fade.ts
38547
- import { existsSync as existsSync26 } from "node:fs";
38694
+ import { existsSync as existsSync27 } from "node:fs";
38548
38695
  async function executeFade(options) {
38549
38696
  const {
38550
38697
  videoPath,
@@ -38554,7 +38701,7 @@ async function executeFade(options) {
38554
38701
  audioOnly = false,
38555
38702
  videoOnly = false
38556
38703
  } = options;
38557
- if (!existsSync26(videoPath)) {
38704
+ if (!existsSync27(videoPath)) {
38558
38705
  return { success: false, error: `Video not found: ${videoPath}` };
38559
38706
  }
38560
38707
  if (!commandExists("ffmpeg")) {
@@ -38621,8 +38768,8 @@ var init_fade = __esm({
38621
38768
  });
38622
38769
 
38623
38770
  // src/commands/_shared/edit/translate-srt.ts
38624
- import { existsSync as existsSync27 } from "node:fs";
38625
- 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";
38626
38773
  async function executeTranslateSrt(options) {
38627
38774
  const {
38628
38775
  srtPath,
@@ -38632,11 +38779,11 @@ async function executeTranslateSrt(options) {
38632
38779
  sourceLanguage,
38633
38780
  apiKey
38634
38781
  } = options;
38635
- if (!existsSync27(srtPath)) {
38782
+ if (!existsSync28(srtPath)) {
38636
38783
  return { success: false, error: `SRT file not found: ${srtPath}` };
38637
38784
  }
38638
38785
  try {
38639
- const srtContent = await readFile23(srtPath, "utf-8");
38786
+ const srtContent = await readFile24(srtPath, "utf-8");
38640
38787
  const segments = parseSRT(srtContent);
38641
38788
  if (segments.length === 0) {
38642
38789
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -38749,7 +38896,7 @@ var init_translate_srt = __esm({
38749
38896
  });
38750
38897
 
38751
38898
  // src/commands/_shared/edit/text-overlay.ts
38752
- import { existsSync as existsSync28 } from "node:fs";
38899
+ import { existsSync as existsSync29 } from "node:fs";
38753
38900
  import { resolve as resolve28 } from "node:path";
38754
38901
  function detectSystemFont() {
38755
38902
  const platform = process.platform;
@@ -38760,7 +38907,7 @@ function detectSystemFont() {
38760
38907
  "/Library/Fonts/Arial.ttf"
38761
38908
  ];
38762
38909
  for (const f of candidates) {
38763
- if (existsSync28(f)) return f;
38910
+ if (existsSync29(f)) return f;
38764
38911
  }
38765
38912
  } else if (platform === "linux") {
38766
38913
  const candidates = [
@@ -38769,7 +38916,7 @@ function detectSystemFont() {
38769
38916
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
38770
38917
  ];
38771
38918
  for (const f of candidates) {
38772
- if (existsSync28(f)) return f;
38919
+ if (existsSync29(f)) return f;
38773
38920
  }
38774
38921
  } else if (platform === "win32") {
38775
38922
  const candidates = [
@@ -38777,7 +38924,7 @@ function detectSystemFont() {
38777
38924
  "C:\\Windows\\Fonts\\segoeui.ttf"
38778
38925
  ];
38779
38926
  for (const f of candidates) {
38780
- if (existsSync28(f)) return f;
38927
+ if (existsSync29(f)) return f;
38781
38928
  }
38782
38929
  }
38783
38930
  return null;
@@ -38798,7 +38945,7 @@ async function applyTextOverlays(options) {
38798
38945
  }
38799
38946
  const absVideoPath = resolve28(process.cwd(), videoPath);
38800
38947
  const absOutputPath = resolve28(process.cwd(), outputPath);
38801
- if (!existsSync28(absVideoPath)) {
38948
+ if (!existsSync29(absVideoPath)) {
38802
38949
  return { success: false, error: `Video not found: ${absVideoPath}` };
38803
38950
  }
38804
38951
  if (!commandExists("ffmpeg")) {
@@ -38979,7 +39126,7 @@ var init_ai_edit = __esm({
38979
39126
 
38980
39127
  // src/commands/ai-edit-cli.ts
38981
39128
  import { resolve as resolve29, extname as extname6, basename as basename7 } from "node:path";
38982
- import { existsSync as existsSync29 } from "node:fs";
39129
+ import { existsSync as existsSync30 } from "node:fs";
38983
39130
  function registerEditCommands(aiCommand) {
38984
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", `
38985
39132
  Examples:
@@ -38996,7 +39143,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
38996
39143
  validateOutputPath(options.output);
38997
39144
  }
38998
39145
  const absVideoPath = resolve29(process.cwd(), videoPath);
38999
- if (!existsSync29(absVideoPath)) {
39146
+ if (!existsSync30(absVideoPath)) {
39000
39147
  exitWithError(notFoundError(absVideoPath));
39001
39148
  }
39002
39149
  if (!commandExists("ffmpeg")) {
@@ -39104,7 +39251,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39104
39251
  validateOutputPath(options.output);
39105
39252
  }
39106
39253
  const absVideoPath = resolve29(process.cwd(), videoPath);
39107
- if (!existsSync29(absVideoPath)) {
39254
+ if (!existsSync30(absVideoPath)) {
39108
39255
  exitWithError(notFoundError(absVideoPath));
39109
39256
  }
39110
39257
  if (!commandExists("ffmpeg")) {
@@ -39185,7 +39332,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39185
39332
  validateOutputPath(options.output);
39186
39333
  }
39187
39334
  const absInputPath = resolve29(process.cwd(), inputPath);
39188
- if (!existsSync29(absInputPath)) {
39335
+ if (!existsSync30(absInputPath)) {
39189
39336
  exitWithError(notFoundError(absInputPath));
39190
39337
  }
39191
39338
  if (!commandExists("ffmpeg")) {
@@ -39251,7 +39398,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39251
39398
  validateOutputPath(options.output);
39252
39399
  }
39253
39400
  const absVideoPath = resolve29(process.cwd(), videoPath);
39254
- if (!existsSync29(absVideoPath)) {
39401
+ if (!existsSync30(absVideoPath)) {
39255
39402
  exitWithError(notFoundError(absVideoPath));
39256
39403
  }
39257
39404
  if (!commandExists("ffmpeg")) {
@@ -39326,7 +39473,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39326
39473
  exitWithError(usageError("Target language required. Use -t or --target"));
39327
39474
  }
39328
39475
  const absSrtPath = resolve29(process.cwd(), srtPath);
39329
- if (!existsSync29(absSrtPath)) {
39476
+ if (!existsSync30(absSrtPath)) {
39330
39477
  exitWithError(notFoundError(absSrtPath));
39331
39478
  }
39332
39479
  if (options.dryRun) {
@@ -39402,7 +39549,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39402
39549
  }
39403
39550
  if (options.fillers) rejectControlChars(options.fillers);
39404
39551
  const absVideoPath = resolve29(process.cwd(), videoPath);
39405
- if (!existsSync29(absVideoPath)) {
39552
+ if (!existsSync30(absVideoPath)) {
39406
39553
  exitWithError(notFoundError(absVideoPath));
39407
39554
  }
39408
39555
  if (!commandExists("ffmpeg")) {
@@ -39895,8 +40042,8 @@ var init_project_resolver = __esm({
39895
40042
  });
39896
40043
 
39897
40044
  // src/commands/_shared/execute-fill-gaps.ts
39898
- import { existsSync as existsSync30 } from "node:fs";
39899
- 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";
39900
40047
  import { resolve as resolve31, dirname as dirname13 } from "node:path";
39901
40048
  function detectVideoGaps(videoClips, totalDuration) {
39902
40049
  const gaps = [];
@@ -39953,7 +40100,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
39953
40100
  }
39954
40101
  async function uploadFrameToImgbb(framePath, imgbbApiKey) {
39955
40102
  try {
39956
- const frameBuffer = await readFile24(framePath);
40103
+ const frameBuffer = await readFile25(framePath);
39957
40104
  const frameBase64 = frameBuffer.toString("base64");
39958
40105
  const formData = new FormData();
39959
40106
  formData.append("key", imgbbApiKey);
@@ -39978,14 +40125,14 @@ async function executeFillGaps(options) {
39978
40125
  try {
39979
40126
  onProgress("Loading timeline...");
39980
40127
  const filePath = await resolveTimelineFile(options.projectPath);
39981
- if (!existsSync30(filePath)) {
40128
+ if (!existsSync31(filePath)) {
39982
40129
  return {
39983
40130
  success: false,
39984
40131
  error: `Timeline file not found: ${filePath}`,
39985
40132
  humanLines
39986
40133
  };
39987
40134
  }
39988
- const content = await readFile24(filePath, "utf-8");
40135
+ const content = await readFile25(filePath, "utf-8");
39989
40136
  const data = JSON.parse(content);
39990
40137
  const project = Project.fromJSON(data);
39991
40138
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -40113,7 +40260,7 @@ async function executeFillGaps(options) {
40113
40260
  }
40114
40261
  const projectDir = dirname13(filePath);
40115
40262
  const footageDir = options.dir ? resolve31(process.cwd(), options.dir) : resolve31(projectDir, "footage");
40116
- if (!existsSync30(footageDir)) {
40263
+ if (!existsSync31(footageDir)) {
40117
40264
  await mkdir14(footageDir, { recursive: true });
40118
40265
  }
40119
40266
  humanLines.push("Generating AI Videos");
@@ -40433,8 +40580,8 @@ __export(ai_motion_exports, {
40433
40580
  registerMotionCommand: () => registerMotionCommand
40434
40581
  });
40435
40582
  import { resolve as resolve32 } from "node:path";
40436
- import { existsSync as existsSync31 } from "node:fs";
40437
- 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";
40438
40585
  async function getOptionalGoogleApiKey() {
40439
40586
  const configKey = await getApiKeyFromConfig("google");
40440
40587
  if (configKey) return configKey;
@@ -40481,7 +40628,7 @@ async function executeMotion(options) {
40481
40628
  };
40482
40629
  }
40483
40630
  const imagePath = resolve32(process.cwd(), options.image);
40484
- const imageBuffer = await readFile25(imagePath);
40631
+ const imageBuffer = await readFile26(imagePath);
40485
40632
  const gemini = new GeminiProvider();
40486
40633
  await gemini.initialize({ apiKey: geminiApiKey });
40487
40634
  const analysisResult = await gemini.analyzeImage(
@@ -40518,7 +40665,7 @@ Use this image analysis to inform the color palette, typography placement, and o
40518
40665
  }
40519
40666
  if (geminiApiKey) {
40520
40667
  const videoPath = resolve32(process.cwd(), options.video);
40521
- const videoBuffer = await readFile25(videoPath);
40668
+ const videoBuffer = await readFile26(videoPath);
40522
40669
  const gemini = new GeminiProvider();
40523
40670
  await gemini.initialize({ apiKey: geminiApiKey });
40524
40671
  const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
@@ -40549,10 +40696,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
40549
40696
  let result;
40550
40697
  if (options.fromTsx) {
40551
40698
  const tsxPath = resolve32(process.cwd(), options.fromTsx);
40552
- if (!existsSync31(tsxPath)) {
40699
+ if (!existsSync32(tsxPath)) {
40553
40700
  return { success: false, error: `TSX file not found: ${tsxPath}` };
40554
40701
  }
40555
- const existingCode = await readFile25(tsxPath, "utf-8");
40702
+ const existingCode = await readFile26(tsxPath, "utf-8");
40556
40703
  if (useGemini) {
40557
40704
  const gemini = new GeminiProvider();
40558
40705
  await gemini.initialize({ apiKey });
@@ -41081,7 +41228,7 @@ __export(project_builder_exports, {
41081
41228
  resolveSourceUrl: () => resolveSourceUrl
41082
41229
  });
41083
41230
  import { mkdtemp, mkdir as mkdir15, copyFile as copyFile5, writeFile as writeFile26, rm as rm3, readdir as readdir3 } from "node:fs/promises";
41084
- import { existsSync as existsSync32 } from "node:fs";
41231
+ import { existsSync as existsSync33 } from "node:fs";
41085
41232
  import { tmpdir as tmpdir2 } from "node:os";
41086
41233
  import { createRequire } from "node:module";
41087
41234
  import * as path from "node:path";
@@ -41093,7 +41240,7 @@ async function buildTempProject(state, projectFileDir) {
41093
41240
  for (const source of state.sources) {
41094
41241
  if (copied.has(source.id)) continue;
41095
41242
  const resolved = resolveSourceUrl(source.url, projectFileDir);
41096
- if (!existsSync32(resolved)) {
41243
+ if (!existsSync33(resolved)) {
41097
41244
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
41098
41245
  }
41099
41246
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -41230,7 +41377,7 @@ __export(motion_overlay_exports, {
41230
41377
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
41231
41378
  });
41232
41379
  import { mkdir as mkdir16 } from "node:fs/promises";
41233
- import { existsSync as existsSync33 } from "node:fs";
41380
+ import { existsSync as existsSync34 } from "node:fs";
41234
41381
  import { basename as basename9, dirname as dirname15, extname as extname7, resolve as resolve34 } from "node:path";
41235
41382
  function normalizeUnderstand2(value) {
41236
41383
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
@@ -41361,11 +41508,11 @@ async function executeMotionOverlay(options) {
41361
41508
  const validationError = assertValidRequest(options);
41362
41509
  if (validationError) return { success: false, error: validationError };
41363
41510
  const absVideo = resolve34(process.cwd(), options.videoPath);
41364
- 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}` };
41365
41512
  const outputPath = options.output ? resolve34(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
41366
41513
  if (options.asset) {
41367
41514
  const absAsset = resolve34(process.cwd(), options.asset);
41368
- 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}` };
41369
41516
  const [videoDuration, size] = await Promise.all([
41370
41517
  ffprobeDuration(absVideo),
41371
41518
  ffprobeVideoSize(absVideo)
@@ -41532,7 +41679,7 @@ __export(edit_cmd_exports, {
41532
41679
  executeUpscale: () => executeUpscale
41533
41680
  });
41534
41681
  import { resolve as resolve35, dirname as dirname16 } from "node:path";
41535
- 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";
41536
41683
  async function executeGrade(options) {
41537
41684
  const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
41538
41685
  try {
@@ -41573,7 +41720,7 @@ async function executeSpeedRamp(options) {
41573
41720
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
41574
41721
  const whisper = new WhisperProvider();
41575
41722
  await whisper.initialize({ apiKey: openaiKey });
41576
- const audioBuffer = await readFile26(tempAudio);
41723
+ const audioBuffer = await readFile27(tempAudio);
41577
41724
  const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
41578
41725
  try {
41579
41726
  const { unlink: unlink5 } = await import("node:fs/promises");
@@ -41967,7 +42114,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
41967
42114
  spinner2.text = "Transcribing audio...";
41968
42115
  const whisper = new WhisperProvider();
41969
42116
  await whisper.initialize({ apiKey: openaiApiKey });
41970
- const audioBuffer = await readFile26(tempAudio);
42117
+ const audioBuffer = await readFile27(tempAudio);
41971
42118
  const audioBlob = new Blob([audioBuffer]);
41972
42119
  const transcript = await whisper.transcribe(audioBlob, options.language);
41973
42120
  if (!transcript.segments || transcript.segments.length === 0) {
@@ -42100,7 +42247,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42100
42247
  for (let i = 1; i <= numKeyframes && i <= 30; i++) {
42101
42248
  const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
42102
42249
  try {
42103
- const frameBuffer = await readFile26(framePath);
42250
+ const frameBuffer = await readFile27(framePath);
42104
42251
  const frameBase64 = frameBuffer.toString("base64");
42105
42252
  const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
42106
42253
  focusMode: options.focus,
@@ -42229,7 +42376,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42229
42376
  const imageBuffers = [];
42230
42377
  for (const imagePath of imagePaths) {
42231
42378
  const absPath = resolve35(process.cwd(), imagePath);
42232
- const buffer = await readFile26(absPath);
42379
+ const buffer = await readFile27(absPath);
42233
42380
  imageBuffers.push(buffer);
42234
42381
  }
42235
42382
  let result;
@@ -42506,8 +42653,8 @@ __export(ai_image_exports, {
42506
42653
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
42507
42654
  });
42508
42655
  import { resolve as resolve37, dirname as dirname18 } from "node:path";
42509
- import { readFile as readFile27, writeFile as writeFile29, mkdir as mkdir19 } from "node:fs/promises";
42510
- 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";
42511
42658
  async function executeImageGenerate(options) {
42512
42659
  const {
42513
42660
  prompt: prompt3,
@@ -42651,10 +42798,10 @@ async function executeGeminiEdit(options) {
42651
42798
  const imageBuffers = [];
42652
42799
  for (const imagePath of imagePaths) {
42653
42800
  const absPath = resolve37(process.cwd(), imagePath);
42654
- if (!existsSync35(absPath)) {
42801
+ if (!existsSync36(absPath)) {
42655
42802
  return { success: false, error: `Image not found: ${absPath}` };
42656
42803
  }
42657
- const buffer = await readFile27(absPath);
42804
+ const buffer = await readFile28(absPath);
42658
42805
  imageBuffers.push(buffer);
42659
42806
  }
42660
42807
  const gemini = new GeminiProvider();
@@ -42699,7 +42846,7 @@ async function executeThumbnailBestFrame(options) {
42699
42846
  model = "flash",
42700
42847
  apiKey
42701
42848
  } = options;
42702
- if (!existsSync35(videoPath)) {
42849
+ if (!existsSync36(videoPath)) {
42703
42850
  return { success: false, error: `Video not found: ${videoPath}` };
42704
42851
  }
42705
42852
  if (!commandExists("ffmpeg")) {
@@ -42712,7 +42859,7 @@ async function executeThumbnailBestFrame(options) {
42712
42859
  try {
42713
42860
  const gemini = new GeminiProvider();
42714
42861
  await gemini.initialize({ apiKey: googleKey });
42715
- const videoData = await readFile27(videoPath);
42862
+ const videoData = await readFile28(videoPath);
42716
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>"}';
42717
42864
  const modelId = resolveGeminiTextModel(model);
42718
42865
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
@@ -42733,7 +42880,7 @@ async function executeThumbnailBestFrame(options) {
42733
42880
  reason = reasonMatch[1];
42734
42881
  }
42735
42882
  await execSafe("ffmpeg", ["-ss", String(timestamp), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
42736
- if (!existsSync35(outputPath)) {
42883
+ if (!existsSync36(outputPath)) {
42737
42884
  return { success: false, error: "FFmpeg failed to extract frame" };
42738
42885
  }
42739
42886
  return {
@@ -42764,9 +42911,9 @@ __export(ai_analyze_exports, {
42764
42911
  executeGeminiVideo: () => executeGeminiVideo,
42765
42912
  registerAnalyzeCommands: () => registerAnalyzeCommands
42766
42913
  });
42767
- import { readFile as readFile28 } from "node:fs/promises";
42914
+ import { readFile as readFile29 } from "node:fs/promises";
42768
42915
  import { extname as extname8, resolve as resolve38 } from "node:path";
42769
- import { existsSync as existsSync36 } from "node:fs";
42916
+ import { existsSync as existsSync37 } from "node:fs";
42770
42917
  async function executeGeminiVideo(options) {
42771
42918
  try {
42772
42919
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -42780,10 +42927,10 @@ async function executeGeminiVideo(options) {
42780
42927
  videoData = options.source;
42781
42928
  } else {
42782
42929
  const absPath = resolve38(process.cwd(), options.source);
42783
- if (!existsSync36(absPath)) {
42930
+ if (!existsSync37(absPath)) {
42784
42931
  return { success: false, error: `File not found: ${absPath}` };
42785
42932
  }
42786
- videoData = await readFile28(absPath);
42933
+ videoData = await readFile29(absPath);
42787
42934
  }
42788
42935
  const gemini = new GeminiProvider();
42789
42936
  await gemini.initialize({ apiKey });
@@ -42848,10 +42995,10 @@ async function executeAnalyze(options) {
42848
42995
  imageBuffer = Buffer.from(await response.arrayBuffer());
42849
42996
  } else {
42850
42997
  const absPath = resolve38(process.cwd(), source);
42851
- if (!existsSync36(absPath)) {
42998
+ if (!existsSync37(absPath)) {
42852
42999
  return { success: false, error: `File not found: ${absPath}` };
42853
43000
  }
42854
- imageBuffer = await readFile28(absPath);
43001
+ imageBuffer = await readFile29(absPath);
42855
43002
  }
42856
43003
  const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
42857
43004
  model: modelId,
@@ -42883,10 +43030,10 @@ async function executeAnalyze(options) {
42883
43030
  videoData = Buffer.from(await response.arrayBuffer());
42884
43031
  } else {
42885
43032
  const absPath = resolve38(process.cwd(), source);
42886
- if (!existsSync36(absPath)) {
43033
+ if (!existsSync37(absPath)) {
42887
43034
  return { success: false, error: `File not found: ${absPath}` };
42888
43035
  }
42889
- videoData = await readFile28(absPath);
43036
+ videoData = await readFile29(absPath);
42890
43037
  }
42891
43038
  const result = await gemini.analyzeVideo(videoData, options.prompt, {
42892
43039
  model: modelId,
@@ -43020,9 +43167,9 @@ __export(ai_review_exports, {
43020
43167
  executeReview: () => executeReview,
43021
43168
  registerReviewCommand: () => registerReviewCommand
43022
43169
  });
43023
- import { readFile as readFile29, rename as rename3 } from "node:fs/promises";
43170
+ import { readFile as readFile30, rename as rename3 } from "node:fs/promises";
43024
43171
  import { resolve as resolve39 } from "node:path";
43025
- import { existsSync as existsSync37 } from "node:fs";
43172
+ import { existsSync as existsSync38 } from "node:fs";
43026
43173
  function parseReviewFeedback(response) {
43027
43174
  let cleaned = response.trim();
43028
43175
  if (cleaned.startsWith("```json")) {
@@ -43054,7 +43201,7 @@ async function executeReview(options) {
43054
43201
  model = "flash"
43055
43202
  } = options;
43056
43203
  const absVideoPath = resolve39(process.cwd(), videoPath);
43057
- if (!existsSync37(absVideoPath)) {
43204
+ if (!existsSync38(absVideoPath)) {
43058
43205
  return { success: false, error: `Video not found: ${absVideoPath}` };
43059
43206
  }
43060
43207
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -43067,8 +43214,8 @@ async function executeReview(options) {
43067
43214
  let storyboardContext = "";
43068
43215
  if (storyboardPath) {
43069
43216
  const absStoryboardPath = resolve39(process.cwd(), storyboardPath);
43070
- if (existsSync37(absStoryboardPath)) {
43071
- const content = await readFile29(absStoryboardPath, "utf-8");
43217
+ if (existsSync38(absStoryboardPath)) {
43218
+ const content = await readFile30(absStoryboardPath, "utf-8");
43072
43219
  storyboardContext = `
43073
43220
 
43074
43221
  Original storyboard for reference:
@@ -43110,7 +43257,7 @@ ${projectContext}` : "";
43110
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}`;
43111
43258
  const gemini = new GeminiProvider();
43112
43259
  await gemini.initialize({ apiKey });
43113
- const videoData = await readFile29(absVideoPath);
43260
+ const videoData = await readFile30(absVideoPath);
43114
43261
  const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
43115
43262
  model: modelId
43116
43263
  });
@@ -43165,7 +43312,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
43165
43312
  }
43166
43313
  }
43167
43314
  if (verify && result.outputPath) {
43168
- const verifyVideoData = await readFile29(result.outputPath);
43315
+ const verifyVideoData = await readFile30(result.outputPath);
43169
43316
  const verifyResult = await gemini.analyzeVideo(
43170
43317
  verifyVideoData,
43171
43318
  'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
@@ -43629,7 +43776,7 @@ var init_sanitize = __esm({
43629
43776
 
43630
43777
  // src/commands/generate/storyboard.ts
43631
43778
  import { resolve as resolve46 } from "node:path";
43632
- import { readFile as readFile33, writeFile as writeFile33 } from "node:fs/promises";
43779
+ import { readFile as readFile34, writeFile as writeFile33 } from "node:fs/promises";
43633
43780
  async function executeStoryboard(options) {
43634
43781
  const { content, duration, creativity = "low", output, apiKey } = options;
43635
43782
  try {
@@ -43677,7 +43824,7 @@ function registerStoryboardCommand(parent) {
43677
43824
  let textContent = content;
43678
43825
  if (options.file) {
43679
43826
  const filePath = resolve46(process.cwd(), content);
43680
- textContent = await readFile33(filePath, "utf-8");
43827
+ textContent = await readFile34(filePath, "utf-8");
43681
43828
  }
43682
43829
  if (options.dryRun) {
43683
43830
  outputSuccess({
@@ -44070,7 +44217,7 @@ var init_speech = __esm({
44070
44217
 
44071
44218
  // src/commands/generate/thumbnail.ts
44072
44219
  import { resolve as resolve48, dirname as dirname20, basename as basename13, extname as extname9 } from "node:path";
44073
- import { existsSync as existsSync40 } from "node:fs";
44220
+ import { existsSync as existsSync41 } from "node:fs";
44074
44221
  import { writeFile as writeFile35, mkdir as mkdir21 } from "node:fs/promises";
44075
44222
  function registerThumbnailCommand(parent) {
44076
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) => {
@@ -44082,7 +44229,7 @@ function registerThumbnailCommand(parent) {
44082
44229
  }
44083
44230
  if (options.bestFrame) {
44084
44231
  const absVideoPath = resolve48(process.cwd(), options.bestFrame);
44085
- if (!existsSync40(absVideoPath)) {
44232
+ if (!existsSync41(absVideoPath)) {
44086
44233
  exitWithError(notFoundError(absVideoPath));
44087
44234
  }
44088
44235
  if (!commandExists("ffmpeg")) {
@@ -46266,7 +46413,7 @@ var init_dist3 = __esm({
46266
46413
 
46267
46414
  // src/commands/generate/video.ts
46268
46415
  import { resolve as resolve52 } from "node:path";
46269
- import { readFile as readFile34, writeFile as writeFile39 } from "node:fs/promises";
46416
+ import { readFile as readFile35, writeFile as writeFile39 } from "node:fs/promises";
46270
46417
  function registerVideoCommand(parent) {
46271
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(
46272
46419
  "-p, --provider <provider>",
@@ -46377,7 +46524,7 @@ Examples:
46377
46524
  let isImageToVideo = false;
46378
46525
  if (options.image) {
46379
46526
  const imagePath = resolve52(process.cwd(), options.image);
46380
- const imageBuffer = await readFile34(imagePath);
46527
+ const imageBuffer = await readFile35(imagePath);
46381
46528
  const ext = options.image.toLowerCase().split(".").pop();
46382
46529
  const mimeTypes = {
46383
46530
  jpg: "image/jpeg",
@@ -46617,7 +46764,7 @@ Examples:
46617
46764
  let lastFrame;
46618
46765
  if (options.lastFrame) {
46619
46766
  const lastFramePath = resolve52(process.cwd(), options.lastFrame);
46620
- const lastFrameBuffer = await readFile34(lastFramePath);
46767
+ const lastFrameBuffer = await readFile35(lastFramePath);
46621
46768
  const ext = options.lastFrame.toLowerCase().split(".").pop();
46622
46769
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46623
46770
  lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
@@ -46627,7 +46774,7 @@ Examples:
46627
46774
  refImages = [];
46628
46775
  for (const refPath of options.refImages.slice(0, 3)) {
46629
46776
  const absRefPath = resolve52(process.cwd(), refPath);
46630
- const refBuffer = await readFile34(absRefPath);
46777
+ const refBuffer = await readFile35(absRefPath);
46631
46778
  const ext = refPath.toLowerCase().split(".").pop();
46632
46779
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46633
46780
  refImages.push({ base64: refBuffer.toString("base64"), mimeType });
@@ -46759,7 +46906,7 @@ Examples:
46759
46906
  if (seedanceEndImagePath && seedanceReferences.length === 0) {
46760
46907
  try {
46761
46908
  const absEndImagePath = resolve52(process.cwd(), seedanceEndImagePath);
46762
- const endImageBuffer = await readFile34(absEndImagePath);
46909
+ const endImageBuffer = await readFile35(absEndImagePath);
46763
46910
  const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
46764
46911
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46765
46912
  const uploadHost = await resolveUploadHost();
@@ -46876,7 +47023,7 @@ async function fileInputToUrlOrDataUri2(input, fallbackMimeType) {
46876
47023
  return input;
46877
47024
  }
46878
47025
  const absPath = resolve52(process.cwd(), input);
46879
- const buffer = await readFile34(absPath);
47026
+ const buffer = await readFile35(absPath);
46880
47027
  return `data:${mimeTypeForPath2(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
46881
47028
  }
46882
47029
  function mimeTypeForPath2(path2, fallback2) {
@@ -47884,7 +48031,7 @@ __export(detect_exports, {
47884
48031
  executeDetectScenes: () => executeDetectScenes,
47885
48032
  executeDetectSilence: () => executeDetectSilence
47886
48033
  });
47887
- import { readFile as readFile39, writeFile as writeFile44 } from "node:fs/promises";
48034
+ import { readFile as readFile40, writeFile as writeFile44 } from "node:fs/promises";
47888
48035
  import { resolve as resolve57, basename as basename15 } from "node:path";
47889
48036
  async function executeDetectScenes(options) {
47890
48037
  try {
@@ -48128,7 +48275,7 @@ var init_detect = __esm({
48128
48275
  }
48129
48276
  if (options.project) {
48130
48277
  const projectPath = resolve57(process.cwd(), options.project);
48131
- const content = await readFile39(projectPath, "utf-8");
48278
+ const content = await readFile40(projectPath, "utf-8");
48132
48279
  const data = JSON.parse(content);
48133
48280
  const project = Project.fromJSON(data);
48134
48281
  let source = project.getSources().find((s) => s.url === absPath);
@@ -52979,8 +53126,8 @@ init_scene_project();
52979
53126
  init_project_config();
52980
53127
  init_cost_tier();
52981
53128
  import { resolve as resolve17, relative as relative6, dirname as dirname6 } from "node:path";
52982
- import { mkdir as mkdir8, readFile as readFile14, writeFile as writeFile15, access as access4, copyFile as copyFile2 } from "node:fs/promises";
52983
- 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";
52984
53131
 
52985
53132
  // src/commands/_shared/scene-html-emit.ts
52986
53133
  var SCENE_PRESETS = [
@@ -53549,15 +53696,16 @@ function deriveInstallHosts(detected) {
53549
53696
 
53550
53697
  // src/commands/scene.ts
53551
53698
  init_compose_prompts();
53699
+ init_transcribe_narration();
53552
53700
 
53553
53701
  // src/commands/_shared/scene-submit.ts
53554
53702
  init_compose_scenes_skills();
53555
53703
  init_scene_lint();
53556
53704
  init_storyboard_parse();
53557
53705
  init_root_sync();
53558
- import { existsSync as existsSync11 } from "node:fs";
53559
- import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile13 } from "node:fs/promises";
53560
- 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";
53561
53709
  async function executeSceneSubmit(opts) {
53562
53710
  const projectDir = resolve14(opts.projectDir);
53563
53711
  const beatId = opts.beatId.trim();
@@ -53571,15 +53719,15 @@ async function executeSceneSubmit(opts) {
53571
53719
  warnings: [],
53572
53720
  retryWith: []
53573
53721
  };
53574
- const storyboardPath = join11(projectDir, "STORYBOARD.md");
53575
- if (!existsSync11(storyboardPath)) {
53722
+ const storyboardPath = join12(projectDir, "STORYBOARD.md");
53723
+ if (!existsSync12(storyboardPath)) {
53576
53724
  return {
53577
53725
  ...base,
53578
53726
  success: false,
53579
53727
  error: `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe init <dir>\` first.`
53580
53728
  };
53581
53729
  }
53582
- const parsed = parseStoryboard(await readFile11(storyboardPath, "utf-8"));
53730
+ const parsed = parseStoryboard(await readFile12(storyboardPath, "utf-8"));
53583
53731
  const beat = parsed.beats.find((b) => b.id === beatId);
53584
53732
  if (!beat) {
53585
53733
  const available = parsed.beats.map((b) => b.id).join(", ") || "(none)";
@@ -53649,8 +53797,8 @@ async function executeSceneSubmit(opts) {
53649
53797
  if (opts.validateOnly) {
53650
53798
  return { ...base, success: true };
53651
53799
  }
53652
- const absPath = join11(projectDir, scenePath);
53653
- await mkdir7(join11(projectDir, "compositions"), { recursive: true });
53800
+ const absPath = join12(projectDir, scenePath);
53801
+ await mkdir7(join12(projectDir, "compositions"), { recursive: true });
53654
53802
  await writeFile13(absPath, html.endsWith("\n") ? html : `${html}
53655
53803
  `, "utf-8");
53656
53804
  return {
@@ -53851,7 +53999,7 @@ sceneCommand.command("submit").description(
53851
53999
  }
53852
54000
  html = Buffer.concat(chunks).toString("utf-8");
53853
54001
  } else if (options.file) {
53854
- html = await readFile14(resolve17(String(options.file)), "utf-8");
54002
+ html = await readFile15(resolve17(String(options.file)), "utf-8");
53855
54003
  } else {
53856
54004
  exitWithError(
53857
54005
  usageError(
@@ -54181,8 +54329,8 @@ function readClipStart(rootHtml, beatId) {
54181
54329
  async function resolveNarrationText(value) {
54182
54330
  if (!value) return void 0;
54183
54331
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
54184
- if (looksLikePath && existsSync13(value)) {
54185
- return (await readFile14(value, "utf-8")).trim();
54332
+ if (looksLikePath && existsSync14(value)) {
54333
+ return (await readFile15(value, "utf-8")).trim();
54186
54334
  }
54187
54335
  return value.trim();
54188
54336
  }
@@ -54221,7 +54369,7 @@ async function executeSceneAdd(opts) {
54221
54369
  if (!opts.force && await pathExists2(scenePath)) {
54222
54370
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
54223
54371
  }
54224
- const rootHtmlBefore = await readFile14(rootPath, "utf-8");
54372
+ const rootHtmlBefore = await readFile15(rootPath, "utf-8");
54225
54373
  let dims = readRootDims(rootHtmlBefore);
54226
54374
  if (!dims) {
54227
54375
  const cfg2 = await loadVibeProjectConfig(projectDir);
@@ -54297,31 +54445,20 @@ async function executeSceneAdd(opts) {
54297
54445
  );
54298
54446
  } else {
54299
54447
  opts.onProgress?.("Transcribing narration (Whisper word-level)...");
54300
- try {
54301
- const whisper = new WhisperProvider();
54302
- await whisper.initialize({ apiKey: whisperKey });
54303
- const audioBytes = await readFile14(audioAbsPath);
54304
- const audioBlob = new Blob([new Uint8Array(audioBytes)]);
54305
- const transcript = await whisper.transcribe(audioBlob, void 0, {
54306
- granularity: "word",
54307
- language: opts.transcribeLanguage
54308
- });
54309
- if (transcript.status === "completed" && transcript.words?.length) {
54310
- transcriptRelPath = `assets/transcript-${id}.json`;
54311
- const transcriptAbs = resolve17(projectDir, transcriptRelPath);
54312
- await writeFile15(transcriptAbs, JSON.stringify(transcript.words, null, 2), "utf-8");
54313
- transcriptWordCount = transcript.words.length;
54314
- transcriptWords = transcript.words.map((w) => ({
54315
- text: w.text,
54316
- start: w.start,
54317
- end: w.end
54318
- }));
54319
- } else if (transcript.status === "failed") {
54320
- opts.onProgress?.(`Transcribe failed: ${transcript.error ?? "unknown error"}`);
54321
- }
54322
- } catch (error) {
54323
- const msg = error instanceof Error ? error.message : String(error);
54324
- 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
+ );
54325
54462
  }
54326
54463
  }
54327
54464
  }
@@ -54430,7 +54567,7 @@ async function executeSceneAdd(opts) {
54430
54567
  const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
54431
54568
  if (shouldSyncStoryboard) {
54432
54569
  opts.onProgress?.("Syncing STORYBOARD.md...");
54433
- const before = await readFile14(storyboardAbsPath, "utf-8");
54570
+ const before = await readFile15(storyboardAbsPath, "utf-8");
54434
54571
  const synced = upsertStoryboardBeat(before, {
54435
54572
  beatId: id,
54436
54573
  title: opts.headline,
@@ -54472,7 +54609,7 @@ async function executeSceneAdd(opts) {
54472
54609
  });
54473
54610
  const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
54474
54611
  if (blockingIssue) return errResult(blockingIssue.message);
54475
- const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
54612
+ const rootHtmlAfterSync = await readFile15(rootPath, "utf-8");
54476
54613
  start = readClipStart(rootHtmlAfterSync, id) ?? 0;
54477
54614
  } else {
54478
54615
  start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
@@ -54646,7 +54783,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
54646
54783
  const root = rootArg || "index.html";
54647
54784
  const project = projectArg || ".";
54648
54785
  const defaultProject = project === ".";
54649
- 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"))) {
54650
54787
  return { projectDir: resolve17(root), rootRel: "index.html" };
54651
54788
  }
54652
54789
  return { projectDir: resolve17(project), rootRel: root };
@@ -55515,7 +55652,7 @@ init_exec_safe();
55515
55652
  init_remotion();
55516
55653
  import { resolve as resolve36, dirname as dirname17, basename as basename10 } from "node:path";
55517
55654
  import { writeFile as writeFile28, mkdir as mkdir18, rm as rm4 } from "node:fs/promises";
55518
- import { existsSync as existsSync34 } from "node:fs";
55655
+ import { existsSync as existsSync35 } from "node:fs";
55519
55656
  import { tmpdir as tmpdir3 } from "node:os";
55520
55657
  var ASS_STYLES = ["karaoke-sweep", "typewriter"];
55521
55658
  var SENTENCE_BREAKS = /[.!?]/;
@@ -55696,7 +55833,7 @@ async function executeAnimatedCaption(options) {
55696
55833
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
55697
55834
  const absOutputPath = resolve36(process.cwd(), outputPath);
55698
55835
  const outDir = dirname17(absOutputPath);
55699
- if (!existsSync34(outDir)) {
55836
+ if (!existsSync35(outDir)) {
55700
55837
  await mkdir18(outDir, { recursive: true });
55701
55838
  }
55702
55839
  if (tier === "ass") {
@@ -56243,14 +56380,14 @@ init_ai_review();
56243
56380
  init_dist2();
56244
56381
  init_engine();
56245
56382
  init_ai_helpers();
56246
- import { readFile as readFile30, writeFile as writeFile30 } from "node:fs/promises";
56383
+ import { readFile as readFile31, writeFile as writeFile30 } from "node:fs/promises";
56247
56384
  import { resolve as resolve40 } from "node:path";
56248
56385
  async function executeSuggestEdit(options) {
56249
56386
  try {
56250
56387
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
56251
56388
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
56252
56389
  const filePath = resolve40(process.cwd(), options.projectPath);
56253
- const content = await readFile30(filePath, "utf-8");
56390
+ const content = await readFile31(filePath, "utf-8");
56254
56391
  const data = JSON.parse(content);
56255
56392
  const project = Project.fromJSON(data);
56256
56393
  const gemini = new GeminiProvider();
@@ -56287,9 +56424,9 @@ init_project_config();
56287
56424
  init_scene_lint();
56288
56425
  init_root_sync();
56289
56426
  init_review_report();
56290
- import { existsSync as existsSync38 } from "node:fs";
56291
- import { readdir as readdir4, readFile as readFile31, stat as stat5 } from "node:fs/promises";
56292
- 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";
56293
56430
  async function inspectProject(opts) {
56294
56431
  const projectDir = resolve41(opts.projectDir);
56295
56432
  const issues = [];
@@ -56303,7 +56440,7 @@ async function inspectProject(opts) {
56303
56440
  assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
56304
56441
  rootSync: { ok: null, issueCount: 0, issues: [] }
56305
56442
  };
56306
- if (!existsSync38(projectDir) || !await isDirectory(projectDir)) {
56443
+ if (!existsSync39(projectDir) || !await isDirectory(projectDir)) {
56307
56444
  issues.push({
56308
56445
  severity: "error",
56309
56446
  code: "PROJECT_NOT_FOUND",
@@ -56318,15 +56455,15 @@ async function inspectProject(opts) {
56318
56455
  );
56319
56456
  }
56320
56457
  const coreFiles = {
56321
- storyboard: join21(projectDir, "STORYBOARD.md"),
56322
- design: join21(projectDir, "DESIGN.md"),
56323
- config: join21(projectDir, "vibe.config.json"),
56324
- legacyConfig: join21(projectDir, "vibe.project.yaml"),
56325
- buildReport: join21(projectDir, "build-report.json"),
56326
- 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")
56327
56464
  };
56328
56465
  for (const [name, path2] of Object.entries(coreFiles)) {
56329
- checks.files[name] = existsSync38(path2);
56466
+ checks.files[name] = existsSync39(path2);
56330
56467
  }
56331
56468
  let beatIds = [];
56332
56469
  let inspectedBeatIds = [];
@@ -56340,7 +56477,7 @@ async function inspectProject(opts) {
56340
56477
  });
56341
56478
  retryWith.push(`vibe init ${projectDir} --from "<brief>" --json`);
56342
56479
  } else {
56343
- const storyboard = await readFile31(coreFiles.storyboard, "utf-8");
56480
+ const storyboard = await readFile32(coreFiles.storyboard, "utf-8");
56344
56481
  const validation = validateStoryboardMarkdown(storyboard);
56345
56482
  storyboardBeats = validation.beats;
56346
56483
  beatIds = validation.beats.map((beat) => beat.id);
@@ -56382,7 +56519,7 @@ async function inspectProject(opts) {
56382
56519
  suggestedFix: "Create DESIGN.md or rerun `vibe init --from` in a new project."
56383
56520
  });
56384
56521
  } else {
56385
- const design = await readFile31(coreFiles.design, "utf-8");
56522
+ const design = await readFile32(coreFiles.design, "utf-8");
56386
56523
  for (const issue of designPlaceholderIssues(design)) {
56387
56524
  issues.push(issue);
56388
56525
  }
@@ -56407,20 +56544,20 @@ async function inspectProject(opts) {
56407
56544
  });
56408
56545
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
56409
56546
  }
56410
- const compositionsDir = join21(projectDir, "compositions");
56411
- 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();
56412
56549
  checks.compositions.expected = inspectedBeatIds.length;
56413
56550
  for (const beatId of inspectedBeatIds) {
56414
56551
  const file = `scene-${beatId}.html`;
56415
56552
  if (existingComps.has(file)) {
56416
56553
  checks.compositions.found++;
56417
56554
  } else {
56418
- checks.compositions.missing.push(join21("compositions", file));
56555
+ checks.compositions.missing.push(join22("compositions", file));
56419
56556
  issues.push({
56420
56557
  severity: "error",
56421
56558
  code: "MISSING_COMPOSITION",
56422
56559
  message: `Composition for beat "${beatId}" is missing.`,
56423
- file: join21("compositions", file),
56560
+ file: join22("compositions", file),
56424
56561
  scene: beatId,
56425
56562
  beatId,
56426
56563
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -56482,7 +56619,7 @@ async function inspectProject(opts) {
56482
56619
  try {
56483
56620
  const lint = await runProjectLint({ projectDir });
56484
56621
  const lintFiles = opts.beatId ? lint.files.filter(
56485
- (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`)
56486
56623
  ) : lint.files;
56487
56624
  const lintCounts = countLintFindings(lintFiles);
56488
56625
  checks.lint = {
@@ -56628,7 +56765,7 @@ function projectSourceReports(checks) {
56628
56765
  async function inspectBuildReport(projectDir, reportPath, beatId, checks, issues, retryWith, storyboardBeats) {
56629
56766
  checks.buildReport.exists = true;
56630
56767
  try {
56631
- const report = JSON.parse(await readFile31(reportPath, "utf-8"));
56768
+ const report = JSON.parse(await readFile32(reportPath, "utf-8"));
56632
56769
  if (typeof report.outputPath === "string") checks.buildReport.outputPath = report.outputPath;
56633
56770
  const reportBeats = report.beats ?? [];
56634
56771
  const selectedReportBeats = reportBeats.filter((item) => !beatId || item.id === beatId);
@@ -56765,7 +56902,7 @@ function inspectReportedAsset(opts) {
56765
56902
  opts.checks.assets.checked++;
56766
56903
  if (isExternalRef2(opts.value)) return;
56767
56904
  const abs = isAbsolute5(opts.value) ? opts.value : resolve41(opts.projectDir, opts.value);
56768
- if (existsSync38(abs)) return;
56905
+ if (existsSync39(abs)) return;
56769
56906
  opts.checks.assets.missing.push(opts.value);
56770
56907
  opts.issues.push({
56771
56908
  severity: "warning",
@@ -56859,9 +56996,9 @@ init_exec_safe();
56859
56996
  init_project_config();
56860
56997
  init_storyboard_parse();
56861
56998
  init_review_report();
56862
- import { existsSync as existsSync39 } from "node:fs";
56863
- import { readdir as readdir5, readFile as readFile32, stat as stat6 } from "node:fs/promises";
56864
- 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";
56865
57002
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
56866
57003
  var DEFAULT_AI_MODEL = "flash";
56867
57004
  var BLACK_FRAME_MIN_DURATION_SEC = 1;
@@ -57362,11 +57499,11 @@ async function inspectRender(opts) {
57362
57499
  }
57363
57500
  async function renderExpectsAudio(projectDir, beatId) {
57364
57501
  const root = resolve42(projectDir);
57365
- const rootHtml = await readTextIfExists(join22(root, "index.html"));
57502
+ const rootHtml = await readTextIfExists(join23(root, "index.html"));
57366
57503
  if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
57367
- const buildReport = await readJsonIfExists(join22(root, "build-report.json"));
57504
+ const buildReport = await readJsonIfExists(join23(root, "build-report.json"));
57368
57505
  if (buildReportExpectsAudio(buildReport, beatId)) return true;
57369
- const storyboard = await readTextIfExists(join22(root, "STORYBOARD.md"));
57506
+ const storyboard = await readTextIfExists(join23(root, "STORYBOARD.md"));
57370
57507
  if (!storyboard) return false;
57371
57508
  try {
57372
57509
  const parsed = parseStoryboard(storyboard);
@@ -57377,7 +57514,7 @@ async function renderExpectsAudio(projectDir, beatId) {
57377
57514
  `assets/narration-${beat.id}.wav`,
57378
57515
  `assets/music-${beat.id}.mp3`,
57379
57516
  `assets/music-${beat.id}.wav`
57380
- ].some((asset) => existsSync39(join22(root, asset)))
57517
+ ].some((asset) => existsSync40(join23(root, asset)))
57381
57518
  );
57382
57519
  } catch {
57383
57520
  return false;
@@ -57485,8 +57622,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
57485
57622
  }
57486
57623
  function renderSourceReports(projectDir, videoPath, checks) {
57487
57624
  const reports = [];
57488
- if (existsSync39(join22(projectDir, "build-report.json"))) reports.push("build-report.json");
57489
- 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");
57490
57627
  if (videoPath) reports.push(displayPath(videoPath));
57491
57628
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
57492
57629
  reports.push("ffprobe");
@@ -57499,31 +57636,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
57499
57636
  async function resolveRenderVideoPath(projectDir, explicit, beatId) {
57500
57637
  if (explicit) {
57501
57638
  const candidate = resolve42(process.cwd(), explicit);
57502
- return existsSync39(candidate) ? candidate : null;
57639
+ return existsSync40(candidate) ? candidate : null;
57503
57640
  }
57504
- const renderReportPath = join22(projectDir, "render-report.json");
57505
- if (existsSync39(renderReportPath)) {
57641
+ const renderReportPath = join23(projectDir, "render-report.json");
57642
+ if (existsSync40(renderReportPath)) {
57506
57643
  try {
57507
- const report = JSON.parse(await readFile32(renderReportPath, "utf-8"));
57644
+ const report = JSON.parse(await readFile33(renderReportPath, "utf-8"));
57508
57645
  const reportBeat = typeof report.beat === "string" ? report.beat : null;
57509
57646
  if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
57510
57647
  const candidate = resolveReportedPath(projectDir, report.outputPath);
57511
- if (candidate && existsSync39(candidate)) return candidate;
57648
+ if (candidate && existsSync40(candidate)) return candidate;
57512
57649
  }
57513
57650
  } catch {
57514
57651
  }
57515
57652
  }
57516
- const reportPath = join22(projectDir, "build-report.json");
57517
- if (!beatId && existsSync39(reportPath)) {
57653
+ const reportPath = join23(projectDir, "build-report.json");
57654
+ if (!beatId && existsSync40(reportPath)) {
57518
57655
  try {
57519
- const report = JSON.parse(await readFile32(reportPath, "utf-8"));
57656
+ const report = JSON.parse(await readFile33(reportPath, "utf-8"));
57520
57657
  const candidate = resolveReportedPath(projectDir, report.outputPath);
57521
- if (candidate && existsSync39(candidate)) return candidate;
57658
+ if (candidate && existsSync40(candidate)) return candidate;
57522
57659
  } catch {
57523
57660
  }
57524
57661
  }
57525
- const rendersDir = join22(projectDir, "renders");
57526
- if (!existsSync39(rendersDir)) return null;
57662
+ const rendersDir = join23(projectDir, "renders");
57663
+ if (!existsSync40(rendersDir)) return null;
57527
57664
  const entries = await readdir5(rendersDir, { withFileTypes: true });
57528
57665
  const candidates = [];
57529
57666
  for (const entry of entries) {
@@ -57531,7 +57668,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
57531
57668
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
57532
57669
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
57533
57670
  if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
57534
- const full = join22(rendersDir, entry.name);
57671
+ const full = join23(rendersDir, entry.name);
57535
57672
  const info = await stat6(full);
57536
57673
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
57537
57674
  }
@@ -57549,14 +57686,14 @@ async function runAiRenderReview(projectDir, videoPath, model) {
57549
57686
  });
57550
57687
  }
57551
57688
  function resolveStoryboardPath(projectDir) {
57552
- const storyboardPath = join22(projectDir, "STORYBOARD.md");
57553
- return existsSync39(storyboardPath) ? storyboardPath : null;
57689
+ const storyboardPath = join23(projectDir, "STORYBOARD.md");
57690
+ return existsSync40(storyboardPath) ? storyboardPath : null;
57554
57691
  }
57555
57692
  async function expectedDurationFromBuildReport(projectDir, beatId) {
57556
- const reportPath = join22(projectDir, "build-report.json");
57557
- if (!existsSync39(reportPath)) return void 0;
57693
+ const reportPath = join23(projectDir, "build-report.json");
57694
+ if (!existsSync40(reportPath)) return void 0;
57558
57695
  try {
57559
- const report = JSON.parse(await readFile32(reportPath, "utf-8"));
57696
+ const report = JSON.parse(await readFile33(reportPath, "utf-8"));
57560
57697
  const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
57561
57698
  const durations = beats.map(
57562
57699
  (beat) => parseOptionalNumber2(beat.sceneDurationSec) ?? parseOptionalNumber2(beat.narration?.sceneDurationSec)
@@ -57568,10 +57705,10 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
57568
57705
  }
57569
57706
  }
57570
57707
  async function beatTimingsFromBuildReport(projectDir, beatId) {
57571
- const reportPath = join22(projectDir, "build-report.json");
57572
- if (!existsSync39(reportPath)) return [];
57708
+ const reportPath = join23(projectDir, "build-report.json");
57709
+ if (!existsSync40(reportPath)) return [];
57573
57710
  try {
57574
- const report = JSON.parse(await readFile32(reportPath, "utf-8"));
57711
+ const report = JSON.parse(await readFile33(reportPath, "utf-8"));
57575
57712
  const source = report.beats ?? [];
57576
57713
  const selected = beatId ? source.filter((beat) => beat.id === beatId) : source;
57577
57714
  let cursor = 0;
@@ -57597,17 +57734,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
57597
57734
  }
57598
57735
  async function buildRenderReviewProjectContext(projectDir) {
57599
57736
  const parts = [];
57600
- const storyboard = await readTextIfExists(join22(projectDir, "STORYBOARD.md"));
57737
+ const storyboard = await readTextIfExists(join23(projectDir, "STORYBOARD.md"));
57601
57738
  if (storyboard) {
57602
57739
  parts.push(`STORYBOARD.md:
57603
57740
  ${truncateText(storyboard, 4e3)}`);
57604
57741
  parts.push(`Storyboard beat cue summary:
57605
57742
  ${storyboardBeatCueSummary(storyboard)}`);
57606
57743
  }
57607
- const design = await readTextIfExists(join22(projectDir, "DESIGN.md"));
57744
+ const design = await readTextIfExists(join23(projectDir, "DESIGN.md"));
57608
57745
  if (design) parts.push(`DESIGN.md:
57609
57746
  ${truncateText(design, 4e3)}`);
57610
- const buildReport = await readTextIfExists(join22(projectDir, "build-report.json"));
57747
+ const buildReport = await readTextIfExists(join23(projectDir, "build-report.json"));
57611
57748
  if (buildReport) parts.push(`build-report.json:
57612
57749
  ${truncateText(buildReport, 6e3)}`);
57613
57750
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -57635,9 +57772,9 @@ function storyboardBeatCueSummary(storyboard) {
57635
57772
  }
57636
57773
  }
57637
57774
  async function readTextIfExists(path2) {
57638
- if (!existsSync39(path2)) return null;
57775
+ if (!existsSync40(path2)) return null;
57639
57776
  try {
57640
- return await readFile32(path2, "utf-8");
57777
+ return await readFile33(path2, "utf-8");
57641
57778
  } catch {
57642
57779
  return null;
57643
57780
  }
@@ -58455,16 +58592,16 @@ var generateTools = [
58455
58592
  ];
58456
58593
 
58457
58594
  // src/tools/manifest/storyboard.ts
58458
- import { existsSync as existsSync42 } from "node:fs";
58459
- 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";
58460
58597
  import { resolve as resolve54 } from "node:path";
58461
58598
  init_storyboard_parse();
58462
58599
  init_storyboard_edit();
58463
58600
 
58464
58601
  // src/commands/_shared/storyboard-revise.ts
58465
- import { existsSync as existsSync41 } from "node:fs";
58466
- import { readFile as readFile35, writeFile as writeFile40 } from "node:fs/promises";
58467
- 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";
58468
58605
 
58469
58606
  // src/agent/adapters/index.ts
58470
58607
  init_openai2();
@@ -58516,9 +58653,9 @@ init_storyboard_parse();
58516
58653
  init_storyboard_edit();
58517
58654
  async function executeStoryboardRevision(opts) {
58518
58655
  const projectDir = resolve53(opts.projectDir);
58519
- const storyboardPath = join23(projectDir, "STORYBOARD.md");
58656
+ const storyboardPath = join24(projectDir, "STORYBOARD.md");
58520
58657
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
58521
- if (!existsSync41(storyboardPath)) {
58658
+ if (!existsSync42(storyboardPath)) {
58522
58659
  return failureResult({
58523
58660
  projectDir,
58524
58661
  storyboardPath,
@@ -58528,7 +58665,7 @@ async function executeStoryboardRevision(opts) {
58528
58665
  retryWith: [`vibe init ${projectDir} --from "<brief>" --json`]
58529
58666
  });
58530
58667
  }
58531
- const currentStoryboard = await readFile35(storyboardPath, "utf-8");
58668
+ const currentStoryboard = await readFile36(storyboardPath, "utf-8");
58532
58669
  const projectConfig = await readProjectConfig(projectDir);
58533
58670
  const explicitComposer = opts.composer ?? projectConfig.config.providers.composer ?? void 0;
58534
58671
  let provider = explicitComposer ?? "claude";
@@ -58751,18 +58888,18 @@ function diffChangedBeats(before, after) {
58751
58888
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
58752
58889
  }
58753
58890
  async function readRevisionContext(projectDir) {
58754
- const designMd = await readOptionalText(join23(projectDir, "DESIGN.md"));
58755
- 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"));
58756
58893
  return { designMd, buildReportJson };
58757
58894
  }
58758
58895
  async function readOptionalText(path2) {
58759
- if (!existsSync41(path2)) return null;
58760
- return readFile35(path2, "utf-8");
58896
+ if (!existsSync42(path2)) return null;
58897
+ return readFile36(path2, "utf-8");
58761
58898
  }
58762
58899
  async function readOptionalJsonText(path2) {
58763
- if (!existsSync41(path2)) return null;
58900
+ if (!existsSync42(path2)) return null;
58764
58901
  try {
58765
- const parsed = JSON.parse(await readFile35(path2, "utf-8"));
58902
+ const parsed = JSON.parse(await readFile36(path2, "utf-8"));
58766
58903
  return JSON.stringify(parsed, null, 2);
58767
58904
  } catch {
58768
58905
  return null;
@@ -58807,8 +58944,8 @@ var projectDirSchema = external_exports.object({
58807
58944
  });
58808
58945
  async function readStoryboard(projectDir) {
58809
58946
  const path2 = resolve54(projectDir, "STORYBOARD.md");
58810
- if (!existsSync42(path2)) return null;
58811
- 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") };
58812
58949
  }
58813
58950
  var storyboardListTool = defineTool({
58814
58951
  name: "storyboard_list",
@@ -59067,7 +59204,7 @@ var storyboardTools = [
59067
59204
  // src/tools/manifest/remix.ts
59068
59205
  import { writeFile as writeFile46 } from "node:fs/promises";
59069
59206
  import { tmpdir as tmpdir4 } from "node:os";
59070
- import { join as join24 } from "node:path";
59207
+ import { join as join25 } from "node:path";
59071
59208
 
59072
59209
  // src/commands/ai-script-pipeline.ts
59073
59210
  var import_yaml6 = __toESM(require_dist2(), 1);
@@ -59077,9 +59214,9 @@ init_config();
59077
59214
  init_audio();
59078
59215
  init_ai_helpers();
59079
59216
  init_video_utils();
59080
- 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";
59081
59218
  import { resolve as resolve55, extname as extname10 } from "node:path";
59082
- import { existsSync as existsSync43 } from "node:fs";
59219
+ import { existsSync as existsSync44 } from "node:fs";
59083
59220
 
59084
59221
  // src/commands/_shared/video-providers.ts
59085
59222
  init_source();
@@ -59238,16 +59375,16 @@ async function executeRegenerateScene(options) {
59238
59375
  };
59239
59376
  try {
59240
59377
  const outputDir = resolve55(process.cwd(), options.projectDir);
59241
- if (!existsSync43(outputDir)) {
59378
+ if (!existsSync44(outputDir)) {
59242
59379
  return { ...result, error: `Project directory not found: ${outputDir}` };
59243
59380
  }
59244
59381
  const yamlPath = resolve55(outputDir, "storyboard.yaml");
59245
59382
  const jsonPath = resolve55(outputDir, "storyboard.json");
59246
- const storyboardPath = existsSync43(yamlPath) ? yamlPath : existsSync43(jsonPath) ? jsonPath : null;
59383
+ const storyboardPath = existsSync44(yamlPath) ? yamlPath : existsSync44(jsonPath) ? jsonPath : null;
59247
59384
  if (!storyboardPath) {
59248
59385
  return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
59249
59386
  }
59250
- const storyboardContent = await readFile37(storyboardPath, "utf-8");
59387
+ const storyboardContent = await readFile38(storyboardPath, "utf-8");
59251
59388
  const segments = storyboardPath.endsWith(".yaml") ? (0, import_yaml6.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
59252
59389
  for (const sceneNum of options.scenes) {
59253
59390
  if (sceneNum < 1 || sceneNum > segments.length) {
@@ -59336,15 +59473,15 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
59336
59473
  const refSceneNum = options.referenceScene;
59337
59474
  if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
59338
59475
  const refImagePath = resolve55(outputDir, `scene-${refSceneNum}.png`);
59339
- if (existsSync43(refImagePath)) {
59340
- referenceImageBuffer = await readFile37(refImagePath);
59476
+ if (existsSync44(refImagePath)) {
59477
+ referenceImageBuffer = await readFile38(refImagePath);
59341
59478
  }
59342
59479
  } else if (!refSceneNum) {
59343
59480
  for (let i = 1; i <= segments.length; i++) {
59344
59481
  if (i !== sceneNum) {
59345
59482
  const otherImagePath = resolve55(outputDir, `scene-${i}.png`);
59346
- if (existsSync43(otherImagePath)) {
59347
- referenceImageBuffer = await readFile37(otherImagePath);
59483
+ if (existsSync44(otherImagePath)) {
59484
+ referenceImageBuffer = await readFile38(otherImagePath);
59348
59485
  break;
59349
59486
  }
59350
59487
  }
@@ -59432,11 +59569,11 @@ Generate the single-person scene image now.`;
59432
59569
  }
59433
59570
  if (regenerateVideo && videoApiKey) {
59434
59571
  options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
59435
- if (!existsSync43(imagePath)) {
59572
+ if (!existsSync44(imagePath)) {
59436
59573
  result.failedScenes.push(sceneNum);
59437
59574
  continue;
59438
59575
  }
59439
- const imageBuffer = await readFile37(imagePath);
59576
+ const imageBuffer = await readFile38(imagePath);
59440
59577
  const videoDuration = segment.duration > 5 ? 10 : 5;
59441
59578
  const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
59442
59579
  if (options.generator === "grok") {
@@ -59644,9 +59781,9 @@ Generate the single-person scene image now.`;
59644
59781
  }
59645
59782
 
59646
59783
  // src/commands/ai-highlights.ts
59647
- 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";
59648
59785
  import { resolve as resolve56, dirname as dirname22, basename as basename14, extname as extname11 } from "node:path";
59649
- import { existsSync as existsSync44 } from "node:fs";
59786
+ import { existsSync as existsSync45 } from "node:fs";
59650
59787
  init_dist2();
59651
59788
  init_engine();
59652
59789
  init_api_key();
@@ -59677,7 +59814,7 @@ function filterHighlights(highlights, options) {
59677
59814
  async function executeHighlights(options) {
59678
59815
  try {
59679
59816
  const absPath = resolve56(process.cwd(), options.media);
59680
- if (!existsSync44(absPath)) {
59817
+ if (!existsSync45(absPath)) {
59681
59818
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
59682
59819
  }
59683
59820
  const ext = extname11(absPath).toLowerCase();
@@ -59696,7 +59833,7 @@ async function executeHighlights(options) {
59696
59833
  sourceDuration = await ffprobeDuration(absPath);
59697
59834
  const gemini = new GeminiProvider();
59698
59835
  await gemini.initialize({ apiKey: geminiApiKey });
59699
- const videoBuffer = await readFile38(absPath);
59836
+ const videoBuffer = await readFile39(absPath);
59700
59837
  const criteriaText = options.criteria === "all" || !options.criteria ? "emotional, informative, and funny moments" : `${options.criteria} moments`;
59701
59838
  const durationText = targetDuration ? `Target a total highlight duration of ${targetDuration} seconds.` : "";
59702
59839
  const countText = maxCount ? `Find up to ${maxCount} highlights.` : "";
@@ -59790,10 +59927,10 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
59790
59927
  }
59791
59928
  const whisper = new WhisperProvider();
59792
59929
  await whisper.initialize({ apiKey: openaiApiKey });
59793
- const audioBuffer = await readFile38(audioPath);
59930
+ const audioBuffer = await readFile39(audioPath);
59794
59931
  const audioBlob = new Blob([audioBuffer]);
59795
59932
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
59796
- if (tempAudioPath && existsSync44(tempAudioPath)) {
59933
+ if (tempAudioPath && existsSync45(tempAudioPath)) {
59797
59934
  const { unlink: unlinkFile } = await import("node:fs/promises");
59798
59935
  await unlinkFile(tempAudioPath).catch(() => {
59799
59936
  });
@@ -59880,7 +60017,7 @@ async function executeAutoShorts(options) {
59880
60017
  return { success: false, shorts: [], error: "FFmpeg not found" };
59881
60018
  }
59882
60019
  const absPath = resolve56(process.cwd(), options.video);
59883
- if (!existsSync44(absPath)) {
60020
+ if (!existsSync45(absPath)) {
59884
60021
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
59885
60022
  }
59886
60023
  const targetDuration = options.duration ?? 60;
@@ -59893,7 +60030,7 @@ async function executeAutoShorts(options) {
59893
60030
  }
59894
60031
  const gemini = new GeminiProvider();
59895
60032
  await gemini.initialize({ apiKey: geminiApiKey });
59896
- const videoBuffer = await readFile38(absPath);
60033
+ const videoBuffer = await readFile39(absPath);
59897
60034
  const geminiPrompt = `Analyze this video to find the BEST moments for short-form vertical video content (TikTok, YouTube Shorts, Instagram Reels).
59898
60035
 
59899
60036
  Find ${shortCount * 3} potential clips that are ${targetDuration} seconds or shorter each.
@@ -59970,7 +60107,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
59970
60107
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
59971
60108
  const whisper = new WhisperProvider();
59972
60109
  await whisper.initialize({ apiKey: openaiApiKey });
59973
- const audioBuffer = await readFile38(tempAudio);
60110
+ const audioBuffer = await readFile39(tempAudio);
59974
60111
  const audioBlob = new Blob([audioBuffer]);
59975
60112
  const transcript = await whisper.transcribe(audioBlob, options.language);
59976
60113
  try {
@@ -60008,7 +60145,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
60008
60145
  };
60009
60146
  }
60010
60147
  const outputDir = options.outputDir ? resolve56(process.cwd(), options.outputDir) : dirname22(absPath);
60011
- if (options.outputDir && !existsSync44(outputDir)) {
60148
+ if (options.outputDir && !existsSync45(outputDir)) {
60012
60149
  await mkdir23(outputDir, { recursive: true });
60013
60150
  }
60014
60151
  const result = {
@@ -60087,8 +60224,8 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
60087
60224
  // src/pipeline/executor.ts
60088
60225
  var import_yaml7 = __toESM(require_dist2(), 1);
60089
60226
  import { resolve as resolve58 } from "node:path";
60090
- import { readFile as readFile40, writeFile as writeFile45, mkdir as mkdir24 } from "node:fs/promises";
60091
- 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";
60092
60229
 
60093
60230
  // src/pipeline/resolver.ts
60094
60231
  function resolveStepParams(params, completedSteps) {
@@ -60501,10 +60638,10 @@ async function ensureActionsRegistered() {
60501
60638
  }
60502
60639
  async function loadPipeline(filePath) {
60503
60640
  const absPath = resolve58(process.cwd(), filePath);
60504
- if (!existsSync45(absPath)) {
60641
+ if (!existsSync46(absPath)) {
60505
60642
  throw new Error(`Pipeline file not found: ${absPath}`);
60506
60643
  }
60507
- const content = await readFile40(absPath, "utf-8");
60644
+ const content = await readFile41(absPath, "utf-8");
60508
60645
  const manifest2 = (0, import_yaml7.parse)(content);
60509
60646
  if (!manifest2.name) throw new Error("Pipeline missing 'name' field");
60510
60647
  if (!manifest2.steps || !Array.isArray(manifest2.steps)) throw new Error("Pipeline missing 'steps' array");
@@ -60527,8 +60664,8 @@ async function executePipeline(manifest2, options = {}) {
60527
60664
  const startTime = Date.now();
60528
60665
  if (options.resume) {
60529
60666
  const checkpointPath = resolve58(outputDir, CHECKPOINT_FILE);
60530
- if (existsSync45(checkpointPath)) {
60531
- const checkpointContent = await readFile40(checkpointPath, "utf-8");
60667
+ if (existsSync46(checkpointPath)) {
60668
+ const checkpointContent = await readFile41(checkpointPath, "utf-8");
60532
60669
  const checkpoint = (0, import_yaml7.parse)(checkpointContent);
60533
60670
  for (const cs of checkpoint.completedSteps) {
60534
60671
  completedSteps.set(cs.id, {
@@ -60800,7 +60937,7 @@ var pipelineRunTool = defineTool({
60800
60937
  let resolvedPath = args.pipelinePath;
60801
60938
  let tempPath;
60802
60939
  if (args.pipelineYaml) {
60803
- tempPath = join24(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
60940
+ tempPath = join25(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
60804
60941
  await writeFile46(tempPath, args.pipelineYaml, "utf-8");
60805
60942
  resolvedPath = tempPath;
60806
60943
  }
@@ -60974,10 +61111,10 @@ init_project_resolver();
60974
61111
  // src/tools/manifest/_project-io.ts
60975
61112
  init_engine();
60976
61113
  init_project_resolver();
60977
- import { readFile as readFile41, writeFile as writeFile47 } from "node:fs/promises";
61114
+ import { readFile as readFile42, writeFile as writeFile47 } from "node:fs/promises";
60978
61115
  async function loadProject(projectPath, cwd) {
60979
61116
  const absPath = await resolveTimelineFile(projectPath, cwd);
60980
- const content = await readFile41(absPath, "utf-8");
61117
+ const content = await readFile42(absPath, "utf-8");
60981
61118
  const data = JSON.parse(content);
60982
61119
  return { project: Project.fromJSON(data), absPath };
60983
61120
  }
@@ -61327,12 +61464,12 @@ init_engine();
61327
61464
 
61328
61465
  // src/commands/_shared/project-list.ts
61329
61466
  init_storyboard_parse();
61330
- import { readdir as readdir6, readFile as readFile42, stat as stat7 } from "node:fs/promises";
61331
- import { existsSync as existsSync46 } from "node:fs";
61332
- 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";
61333
61470
  async function executeProjectList(opts = {}) {
61334
61471
  const workspaceDir = resolve60(opts.workspaceDir ?? ".");
61335
- if (!existsSync46(workspaceDir)) {
61472
+ if (!existsSync47(workspaceDir)) {
61336
61473
  return {
61337
61474
  success: false,
61338
61475
  workspaceDir,
@@ -61349,11 +61486,11 @@ async function executeProjectList(opts = {}) {
61349
61486
  if (entry.name === "node_modules") continue;
61350
61487
  if (entry.name.startsWith(".")) continue;
61351
61488
  if (entry.name.startsWith("_")) {
61352
- archivedCount += await countProjects(join25(workspaceDir, entry.name));
61489
+ archivedCount += await countProjects(join26(workspaceDir, entry.name));
61353
61490
  continue;
61354
61491
  }
61355
- const projectDir = join25(workspaceDir, entry.name);
61356
- if (!existsSync46(join25(projectDir, "STORYBOARD.md"))) continue;
61492
+ const projectDir = join26(workspaceDir, entry.name);
61493
+ if (!existsSync47(join26(projectDir, "STORYBOARD.md"))) continue;
61357
61494
  projects.push(await describeProject(entry.name, projectDir));
61358
61495
  }
61359
61496
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -61363,7 +61500,7 @@ async function countProjects(dir) {
61363
61500
  try {
61364
61501
  const entries = await readdir6(dir, { withFileTypes: true });
61365
61502
  return entries.filter(
61366
- (e) => e.isDirectory() && existsSync46(join25(dir, e.name, "STORYBOARD.md"))
61503
+ (e) => e.isDirectory() && existsSync47(join26(dir, e.name, "STORYBOARD.md"))
61367
61504
  ).length;
61368
61505
  } catch {
61369
61506
  return 0;
@@ -61374,8 +61511,8 @@ async function describeProject(name, projectDir) {
61374
61511
  let storyboardDurationSec = 0;
61375
61512
  const timestamps = [];
61376
61513
  try {
61377
- const storyboardPath = join25(projectDir, "STORYBOARD.md");
61378
- const parsed = parseStoryboard(await readFile42(storyboardPath, "utf-8"));
61514
+ const storyboardPath = join26(projectDir, "STORYBOARD.md");
61515
+ const parsed = parseStoryboard(await readFile43(storyboardPath, "utf-8"));
61379
61516
  beats = parsed.beats.length;
61380
61517
  storyboardDurationSec = Number(
61381
61518
  parsed.beats.reduce((sum, beat) => sum + (beat.duration ?? 0), 0).toFixed(2)
@@ -61386,8 +61523,8 @@ async function describeProject(name, projectDir) {
61386
61523
  let buildStatus;
61387
61524
  let buildUpdatedAt;
61388
61525
  try {
61389
- const reportPath = join25(projectDir, "build-report.json");
61390
- 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"));
61391
61528
  buildStatus = report.status ?? report.phase;
61392
61529
  buildUpdatedAt = report.updatedAt;
61393
61530
  timestamps.push((await stat7(reportPath)).mtimeMs);
@@ -61395,12 +61532,12 @@ async function describeProject(name, projectDir) {
61395
61532
  }
61396
61533
  let latestRender;
61397
61534
  try {
61398
- const rendersDir = join25(projectDir, "renders");
61535
+ const rendersDir = join26(projectDir, "renders");
61399
61536
  const files = await readdir6(rendersDir);
61400
61537
  let newest;
61401
61538
  for (const file of files) {
61402
61539
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
61403
- const { mtimeMs } = await stat7(join25(rendersDir, file));
61540
+ const { mtimeMs } = await stat7(join26(rendersDir, file));
61404
61541
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
61405
61542
  }
61406
61543
  if (newest) {
@@ -61534,7 +61671,7 @@ init_exec_safe();
61534
61671
  init_output();
61535
61672
  init_validate();
61536
61673
  init_project_resolver();
61537
- import { readFile as readFile43, access as access6 } from "node:fs/promises";
61674
+ import { readFile as readFile44, access as access6 } from "node:fs/promises";
61538
61675
  import { resolve as resolve62, basename as basename17 } from "node:path";
61539
61676
  import { spawn } from "node:child_process";
61540
61677
  function timelineBaseName(path2) {
@@ -61611,7 +61748,7 @@ async function runExport(projectPath, outputPath, options = {}) {
61611
61748
  };
61612
61749
  }
61613
61750
  const filePath = await resolveTimelineFile(projectPath);
61614
- const content = await readFile43(filePath, "utf-8");
61751
+ const content = await readFile44(filePath, "utf-8");
61615
61752
  const data = JSON.parse(content);
61616
61753
  const project = Project.fromJSON(data);
61617
61754
  const summary = project.getSummary();
@@ -61735,7 +61872,7 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
61735
61872
  }
61736
61873
  spinner2.text = "Loading project...";
61737
61874
  const filePath = await resolveTimelineFile(projectPath);
61738
- const content = await readFile43(filePath, "utf-8");
61875
+ const content = await readFile44(filePath, "utf-8");
61739
61876
  const data = JSON.parse(content);
61740
61877
  const project = Project.fromJSON(data);
61741
61878
  const summary = project.getSummary();
@@ -62333,14 +62470,14 @@ function getPresetSettings(preset, aspectRatio2) {
62333
62470
  }
62334
62471
  async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
62335
62472
  spinner2.text = "Loading project...";
62336
- const { readFile: readFile45 } = await import("node:fs/promises");
62473
+ const { readFile: readFile46 } = await import("node:fs/promises");
62337
62474
  const { resolve: resolve66 } = await import("node:path");
62338
62475
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
62339
62476
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
62340
62477
  const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
62341
62478
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
62342
62479
  const filePath = await resolveTimelineFile(projectPath);
62343
- const content = await readFile45(filePath, "utf-8");
62480
+ const content = await readFile46(filePath, "utf-8");
62344
62481
  const project = Project2.fromJSON(JSON.parse(content));
62345
62482
  const state = project.getState();
62346
62483
  const outputPath = options.output ? resolve66(process.cwd(), options.output) : resolve66(process.cwd(), `${timelineBaseName(projectPath)}.${options.format ?? "mp4"}`);
@@ -62409,8 +62546,8 @@ var exportVideoTool = defineTool({
62409
62546
  var exportTools = [exportVideoTool];
62410
62547
 
62411
62548
  // src/tools/manifest/agent-only.ts
62412
- import { readFile as readFile44, writeFile as writeFile50, readdir as readdir7, stat as stat8, access as access7, unlink as unlink4 } from "node:fs/promises";
62413
- 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";
62414
62551
  init_engine();
62415
62552
  init_exec_safe();
62416
62553
  init_project_resolver();
@@ -62488,7 +62625,7 @@ var fsListTool = defineTool({
62488
62625
  const results = [];
62489
62626
  for (const entry of entries) {
62490
62627
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
62491
- const fullPath = join26(absPath, entry.name);
62628
+ const fullPath = join27(absPath, entry.name);
62492
62629
  const stats = await stat8(fullPath);
62493
62630
  if (entry.isDirectory()) {
62494
62631
  results.push(`[DIR] ${entry.name}/`);
@@ -62517,7 +62654,7 @@ var fsReadTool = defineTool({
62517
62654
  async execute(args, ctx) {
62518
62655
  try {
62519
62656
  const absPath = resolve64(ctx.workingDirectory, args.path);
62520
- const content = await readFile44(absPath, "utf-8");
62657
+ const content = await readFile45(absPath, "utf-8");
62521
62658
  const maxLength = 4e3;
62522
62659
  const truncated = content.length > maxLength;
62523
62660
  const output = truncated ? content.substring(0, maxLength) + "\n... (truncated)" : content;
@@ -62590,7 +62727,7 @@ var batchImportTool = defineTool({
62590
62727
  async execute(args, ctx) {
62591
62728
  try {
62592
62729
  const filePath = resolve64(ctx.workingDirectory, args.project);
62593
- const content = await readFile44(filePath, "utf-8");
62730
+ const content = await readFile45(filePath, "utf-8");
62594
62731
  const data = JSON.parse(content);
62595
62732
  const project = Project.fromJSON(data);
62596
62733
  const dirPath = resolve64(ctx.workingDirectory, args.directory);
@@ -62601,7 +62738,7 @@ var batchImportTool = defineTool({
62601
62738
  const scanDir = async (dir) => {
62602
62739
  const entries = await readdir7(dir, { withFileTypes: true });
62603
62740
  for (const entry of entries) {
62604
- const entryPath = join26(dir, entry.name);
62741
+ const entryPath = join27(dir, entry.name);
62605
62742
  if (entry.isDirectory() && recursive) {
62606
62743
  await scanDir(entryPath);
62607
62744
  } else if (entry.isFile()) {
@@ -62662,7 +62799,7 @@ var batchConcatTool = defineTool({
62662
62799
  async execute(args, ctx) {
62663
62800
  try {
62664
62801
  const filePath = resolve64(ctx.workingDirectory, args.project);
62665
- const content = await readFile44(filePath, "utf-8");
62802
+ const content = await readFile45(filePath, "utf-8");
62666
62803
  const data = JSON.parse(content);
62667
62804
  const project = Project.fromJSON(data);
62668
62805
  const sourceIds = args.sourceIds ?? [];
@@ -62733,7 +62870,7 @@ var batchApplyEffectTool = defineTool({
62733
62870
  async execute(args, ctx) {
62734
62871
  try {
62735
62872
  const filePath = resolve64(ctx.workingDirectory, args.project);
62736
- const content = await readFile44(filePath, "utf-8");
62873
+ const content = await readFile45(filePath, "utf-8");
62737
62874
  const data = JSON.parse(content);
62738
62875
  const project = Project.fromJSON(data);
62739
62876
  const clipIds = args.clipIds ?? [];
@@ -62788,7 +62925,7 @@ var timelineClearTool = defineTool({
62788
62925
  async execute(args, ctx) {
62789
62926
  try {
62790
62927
  const filePath = await resolveTimelineFile(args.project, ctx.workingDirectory);
62791
- const content = await readFile44(filePath, "utf-8");
62928
+ const content = await readFile45(filePath, "utf-8");
62792
62929
  const data = JSON.parse(content);
62793
62930
  const project = Project.fromJSON(data);
62794
62931
  const what = args.what ?? "clips";
@@ -62865,7 +63002,7 @@ var projectSetTool = defineTool({
62865
63002
  async execute(args, ctx) {
62866
63003
  try {
62867
63004
  const filePath = await resolveTimelineFile(args.projectPath, ctx.workingDirectory);
62868
- const content = await readFile44(filePath, "utf-8");
63005
+ const content = await readFile45(filePath, "utf-8");
62869
63006
  const data = JSON.parse(content);
62870
63007
  const project = Project.fromJSON(data);
62871
63008
  const updates = [];
@@ -63139,8 +63276,8 @@ var mediaCompressTool = defineTool({
63139
63276
  ],
63140
63277
  { maxBuffer: 50 * 1024 * 1024 }
63141
63278
  );
63142
- const inputBuf = await readFile44(absInput);
63143
- const outputBuf = await readFile44(outputPath);
63279
+ const inputBuf = await readFile45(absInput);
63280
+ const outputBuf = await readFile45(outputPath);
63144
63281
  const inputSize = inputBuf.length;
63145
63282
  const outputSize = outputBuf.length;
63146
63283
  const reduction = (inputSize - outputSize) / inputSize * 100;