@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.
package/dist/index.js CHANGED
@@ -11631,15 +11631,15 @@ async function loadKokoroRuntime() {
11631
11631
  }
11632
11632
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
11633
11633
  try {
11634
- const { readFile: readFile56 } = await import("node:fs/promises");
11635
- const { join: join30, resolve: resolve86 } = await import("node:path");
11634
+ const { readFile: readFile57 } = await import("node:fs/promises");
11635
+ const { join: join31, resolve: resolve86 } = await import("node:path");
11636
11636
  const { pathToFileURL } = await import("node:url");
11637
11637
  const pkgDir = resolve86(baseDir, "node_modules", "kokoro-js");
11638
- const pkg = JSON.parse(await readFile56(join30(pkgDir, "package.json"), "utf-8"));
11638
+ const pkg = JSON.parse(await readFile57(join31(pkgDir, "package.json"), "utf-8"));
11639
11639
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
11640
11640
  if (!entry)
11641
11641
  return null;
11642
- const mod = await import(pathToFileURL(join30(pkgDir, entry)).href);
11642
+ const mod = await import(pathToFileURL(join31(pkgDir, entry)).href);
11643
11643
  return mod.KokoroTTS ?? null;
11644
11644
  } catch {
11645
11645
  return null;
@@ -11650,20 +11650,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
11650
11650
  if (!base)
11651
11651
  return null;
11652
11652
  try {
11653
- const { readFile: readFile56, writeFile: writeFile61, mkdir: mkdir31 } = await import("node:fs/promises");
11654
- const { join: join30, resolve: resolve86 } = await import("node:path");
11653
+ const { readFile: readFile57, writeFile: writeFile61, mkdir: mkdir31 } = await import("node:fs/promises");
11654
+ const { join: join31, resolve: resolve86 } = await import("node:path");
11655
11655
  const { pathToFileURL } = await import("node:url");
11656
11656
  const { homedir: homedir8 } = await import("node:os");
11657
11657
  const transformersDir = resolve86(base, "node_modules", "@huggingface", "transformers");
11658
11658
  const ortDistDir = resolve86(base, "node_modules", "onnxruntime-web", "dist");
11659
- const transformers = await import(pathToFileURL(join30(transformersDir, "dist", "transformers.web.js")).href);
11660
- const cacheRoot = join30(homedir8(), ".cache", "vibeframe", "models");
11659
+ const transformers = await import(pathToFileURL(join31(transformersDir, "dist", "transformers.web.js")).href);
11660
+ const cacheRoot = join31(homedir8(), ".cache", "vibeframe", "models");
11661
11661
  transformers.env.allowLocalModels = false;
11662
11662
  transformers.env.useCustomCache = true;
11663
11663
  transformers.env.customCache = {
11664
11664
  async match(key) {
11665
11665
  try {
11666
- return new Response(await readFile56(join30(cacheRoot, encodeURIComponent(String(key)))));
11666
+ return new Response(await readFile57(join31(cacheRoot, encodeURIComponent(String(key)))));
11667
11667
  } catch {
11668
11668
  return void 0;
11669
11669
  }
@@ -11672,7 +11672,7 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
11672
11672
  try {
11673
11673
  const buf = Buffer.from(await response.arrayBuffer());
11674
11674
  await mkdir31(cacheRoot, { recursive: true });
11675
- await writeFile61(join30(cacheRoot, encodeURIComponent(String(key))), buf);
11675
+ await writeFile61(join31(cacheRoot, encodeURIComponent(String(key))), buf);
11676
11676
  } catch {
11677
11677
  }
11678
11678
  }
@@ -29972,10 +29972,57 @@ var init_composer_resolve = __esm({
29972
29972
  }
29973
29973
  });
29974
29974
 
29975
+ // src/commands/_shared/transcribe-narration.ts
29976
+ import { existsSync as existsSync7 } from "node:fs";
29977
+ import { readFile as readFile8 } from "node:fs/promises";
29978
+ import { join as join6 } from "node:path";
29979
+ async function transcribeNarrationWords(audioAbsPath, opts) {
29980
+ try {
29981
+ const whisper = new WhisperProvider();
29982
+ await whisper.initialize({ apiKey: opts.apiKey });
29983
+ const audioBytes = await readFile8(audioAbsPath);
29984
+ const audioBlob = new Blob([new Uint8Array(audioBytes)]);
29985
+ const transcript = await whisper.transcribe(audioBlob, void 0, {
29986
+ granularity: "word",
29987
+ language: opts.language
29988
+ });
29989
+ if (transcript.status === "completed" && transcript.words?.length) {
29990
+ return transcript.words.map((w) => ({ text: w.text, start: w.start, end: w.end }));
29991
+ }
29992
+ return [];
29993
+ } catch {
29994
+ return [];
29995
+ }
29996
+ }
29997
+ function beatTranscriptRelPath(beatId) {
29998
+ return `assets/transcript-${beatId}.json`;
29999
+ }
30000
+ async function readBeatTranscript(projectDir, beatId) {
30001
+ const abs = join6(projectDir, beatTranscriptRelPath(beatId));
30002
+ if (!existsSync7(abs)) return void 0;
30003
+ try {
30004
+ const parsed = JSON.parse(await readFile8(abs, "utf-8"));
30005
+ if (!Array.isArray(parsed)) return void 0;
30006
+ const words = parsed.filter(
30007
+ (w) => typeof w === "object" && w !== null && typeof w.text === "string" && Number.isFinite(w.start) && Number.isFinite(w.end)
30008
+ ).map((w) => ({ text: w.text, start: w.start, end: w.end }));
30009
+ return words.length > 0 ? words : void 0;
30010
+ } catch {
30011
+ return void 0;
30012
+ }
30013
+ }
30014
+ var init_transcribe_narration = __esm({
30015
+ "src/commands/_shared/transcribe-narration.ts"() {
30016
+ "use strict";
30017
+ init_dist2();
30018
+ }
30019
+ });
30020
+
29975
30021
  // src/commands/_shared/compose-scenes-skills.ts
29976
30022
  var compose_scenes_skills_exports = {};
29977
30023
  __export(compose_scenes_skills_exports, {
29978
30024
  ComposeBeatError: () => ComposeBeatError,
30025
+ TRANSCRIPT_PROMPT_MAX_WORDS: () => TRANSCRIPT_PROMPT_MAX_WORDS,
29979
30026
  buildSystemPrompt: () => buildSystemPrompt,
29980
30027
  buildUserPrompt: () => buildUserPrompt,
29981
30028
  composeBeatHtml: () => composeBeatHtml,
@@ -29985,15 +30032,16 @@ __export(compose_scenes_skills_exports, {
29985
30032
  executeComposeScenesWithSkills: () => executeComposeScenesWithSkills,
29986
30033
  extractHtml: () => extractHtml,
29987
30034
  formatLintFeedback: () => formatLintFeedback,
30035
+ formatTranscriptSection: () => formatTranscriptSection,
29988
30036
  lintBeatHtml: () => lintBeatHtml
29989
30037
  });
29990
30038
  import Anthropic from "@anthropic-ai/sdk";
29991
30039
  import OpenAI from "openai";
29992
30040
  import { GoogleGenerativeAI } from "@google/generative-ai";
29993
30041
  import { createHash as createHash2 } from "node:crypto";
29994
- import { existsSync as existsSync7, mkdirSync } from "node:fs";
29995
- import { mkdir as mkdir5, readFile as readFile8, writeFile as writeFile8 } from "node:fs/promises";
29996
- import { dirname as dirname3, join as join6, resolve as resolve11 } from "node:path";
30042
+ import { existsSync as existsSync8, mkdirSync } from "node:fs";
30043
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile8 } from "node:fs/promises";
30044
+ import { dirname as dirname3, join as join7, resolve as resolve11 } from "node:path";
29997
30045
  import { runHyperframeLint as runHyperframeLint2 } from "@hyperframes/producer";
29998
30046
  function buildSystemPrompt(ctx) {
29999
30047
  return `You are a Hyperframes composition author. The skill content below
@@ -30027,6 +30075,46 @@ function formatBeatCues(cues) {
30027
30075
  }
30028
30076
  return lines.join("\n") + "\n";
30029
30077
  }
30078
+ function formatTranscriptSection(transcript, maxWords = TRANSCRIPT_PROMPT_MAX_WORDS) {
30079
+ if (!transcript || transcript.length === 0) return "";
30080
+ const round = (n) => Number(Math.max(0, n).toFixed(2));
30081
+ 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.";
30082
+ if (transcript.length <= maxWords) {
30083
+ const pairs = transcript.map((w) => `[${round(w.start)}, ${JSON.stringify(w.text)}]`).join(", ");
30084
+ return `
30085
+
30086
+ === Narration word timings (seconds) ===
30087
+
30088
+ Whisper word-level start times for this beat's narration. If you author captions
30089
+ or kinetic typography, reveal each word at its start time with absolute-time
30090
+ GSAP tweens, e.g.
30091
+ \`tl.fromTo('.caption .word[data-i="0"]', { opacity: 0, y: 10 }, { opacity: 1, y: 0, duration: 0.18 }, ${round(
30092
+ transcript[0].start
30093
+ )});\`
30094
+ ${noAudioRule}
30095
+
30096
+ word starts (word-level): ${pairs}`;
30097
+ }
30098
+ const phraseCount = Math.max(1, Math.floor(maxWords / 2));
30099
+ const chunkSize = Math.ceil(transcript.length / phraseCount);
30100
+ const anchors = [];
30101
+ for (let i = 0; i < transcript.length; i += chunkSize) {
30102
+ const chunk = transcript.slice(i, i + chunkSize);
30103
+ const text = chunk.map((w) => w.text).join(" ");
30104
+ anchors.push(`[${round(chunk[0].start)}, ${JSON.stringify(text)}]`);
30105
+ }
30106
+ return `
30107
+
30108
+ === Narration timings (approximate, phrase-level) ===
30109
+
30110
+ This beat's narration is long (${transcript.length} words), so timings are
30111
+ grouped into phrase-level anchors (start time of each phrase). Use them to PACE
30112
+ caption / kinetic-type reveals across the beat \u2014 they are approximate, not
30113
+ per-word truth.
30114
+ ${noAudioRule}
30115
+
30116
+ phrase starts (approximate): ${anchors.join(", ")}`;
30117
+ }
30030
30118
  function buildUserPrompt(ctx) {
30031
30119
  const compositionId = `scene-${ctx.beat.id}`;
30032
30120
  const durLiteral = ctx.finalDurationSec !== void 0 ? String(ctx.finalDurationSec) : void 0;
@@ -30117,6 +30205,13 @@ ${finalDurationBullet}
30117
30205
  - If the beat cues include \`narration\`, do not embed an \`<audio>\` tag in the
30118
30206
  scene fragment. The root timeline wires \`assets/narration-${ctx.beat.id}.wav\`
30119
30207
  or \`assets/narration-${ctx.beat.id}.mp3\` separately.
30208
+ - Do NOT invent sound effects. This scene fragment is visual-only: never add
30209
+ an \`<audio>\` tag, and never imply ambient/foley SFX (keyboard clicks, whoosh,
30210
+ applause, room tone) that the beat did not explicitly request. Audio comes
30211
+ only from the beat's explicit \`narration\` and \`music\` cues, wired by the
30212
+ root timeline. If the beat declares an explicit \`sfx\` cue, treat it as intent
30213
+ for the storyboard author \u2014 still do not embed it here \u2014 and only when it is
30214
+ clearly tied to the beat's narration/motion; ignore orphaned or generic SFX.
30120
30215
  - **\`.clip\` elements get visibility control from the framework but NO
30121
30216
  sizing.** Always give \`.clip\` explicit fill via CSS:
30122
30217
  \`{ position: absolute; inset: 0; }\` (or equivalent
@@ -30182,7 +30277,7 @@ ${ctx.storyboardGlobal || "(no global direction)"}
30182
30277
 
30183
30278
  ## ${ctx.beat.heading}
30184
30279
  ${formatBeatCues(ctx.beat.cues)}${cueDurationNote}
30185
- ${ctx.beat.body}
30280
+ ${ctx.beat.body}${formatTranscriptSection(ctx.transcript)}
30186
30281
 
30187
30282
  === Output format ===
30188
30283
 
@@ -30202,7 +30297,7 @@ function computeCacheKey(parts) {
30202
30297
  return createHash2("sha256").update(parts.provider).update("\u241E").update(parts.model).update("\u241E").update(parts.systemPrompt).update("\u241E").update(parts.userPrompt).digest("hex");
30203
30298
  }
30204
30299
  function defaultCacheDir() {
30205
- return join6(USER_CACHE_DIR, "compose-scenes");
30300
+ return join7(USER_CACHE_DIR, "compose-scenes");
30206
30301
  }
30207
30302
  function extractHtml(responseText) {
30208
30303
  const fenced = responseText.match(/```html\s*\n([\s\S]*?)\n```/);
@@ -30226,9 +30321,9 @@ async function composeBeatHtml(ctx, overrides) {
30226
30321
  model: settings.model
30227
30322
  });
30228
30323
  const cacheDir = ctx.cacheDir ?? defaultCacheDir();
30229
- const cachePath = join6(cacheDir, `${cacheKey}.html`);
30230
- if (existsSync7(cachePath)) {
30231
- const html2 = await readFile8(cachePath, "utf-8");
30324
+ const cachePath = join7(cacheDir, `${cacheKey}.html`);
30325
+ if (existsSync8(cachePath)) {
30326
+ const html2 = await readFile9(cachePath, "utf-8");
30232
30327
  return { html: html2, cached: true, cacheKey, model: settings.model, provider: ctx.provider };
30233
30328
  }
30234
30329
  if (!ctx.apiKey) {
@@ -30364,14 +30459,14 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30364
30459
  const projectRoot = params.project ? resolve11(outputDir, params.project) : resolve11(outputDir);
30365
30460
  const designPath = resolve11(projectRoot, params.design ?? "DESIGN.md");
30366
30461
  const storyboardPath2 = resolve11(projectRoot, params.storyboard ?? "STORYBOARD.md");
30367
- if (!existsSync7(designPath)) {
30462
+ if (!existsSync8(designPath)) {
30368
30463
  return { success: false, error: `DESIGN.md not found at ${designPath}` };
30369
30464
  }
30370
- if (!existsSync7(storyboardPath2)) {
30465
+ if (!existsSync8(storyboardPath2)) {
30371
30466
  return { success: false, error: `STORYBOARD.md not found at ${storyboardPath2}` };
30372
30467
  }
30373
- const designMd = await readFile8(designPath, "utf-8");
30374
- const storyboardMd = await readFile8(storyboardPath2, "utf-8");
30468
+ const designMd = await readFile9(designPath, "utf-8");
30469
+ const storyboardMd = await readFile9(storyboardPath2, "utf-8");
30375
30470
  const { global: storyboardGlobal, beats } = parseStoryboard(storyboardMd);
30376
30471
  if (beats.length === 0) {
30377
30472
  return {
@@ -30391,7 +30486,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30391
30486
  provider = params.composer ?? "claude";
30392
30487
  apiKey = "";
30393
30488
  }
30394
- const compositionsDir = join6(projectRoot, "compositions");
30489
+ const compositionsDir = join7(projectRoot, "compositions");
30395
30490
  await mkdir5(compositionsDir, { recursive: true });
30396
30491
  let finalDurations = /* @__PURE__ */ new Map();
30397
30492
  try {
@@ -30405,6 +30500,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30405
30500
  async (beat, beatIndex) => {
30406
30501
  onProgress({ type: "beat-start", beatId: beat.id, beatIndex, totalBeats });
30407
30502
  try {
30503
+ const transcript = await readBeatTranscript(projectRoot, beat.id);
30408
30504
  const result = await composeBeatWithRetry(
30409
30505
  {
30410
30506
  beat,
@@ -30415,11 +30511,12 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30415
30511
  apiKey,
30416
30512
  effort: params.effort,
30417
30513
  cacheDir: params.cacheDir,
30418
- finalDurationSec: finalDurations.get(beat.id)
30514
+ finalDurationSec: finalDurations.get(beat.id),
30515
+ transcript
30419
30516
  },
30420
30517
  overrides
30421
30518
  );
30422
- const compositionPath = join6(compositionsDir, `scene-${beat.id}.html`);
30519
+ const compositionPath = join7(compositionsDir, `scene-${beat.id}.html`);
30423
30520
  await mkdir5(dirname3(compositionPath), { recursive: true });
30424
30521
  await writeFile8(compositionPath, result.html, "utf-8");
30425
30522
  if (result.cached) {
@@ -30498,7 +30595,7 @@ ${failures.map((f) => ` - ${f.beatId}: ${f.error}`).join("\n")}`,
30498
30595
  data: aggregateData
30499
30596
  };
30500
30597
  }
30501
- var MODEL_SETTINGS_BY_PROVIDER, ComposeBeatError, defaultCallLLM, CONFIG_PROVIDER_BY_COMPOSER;
30598
+ var MODEL_SETTINGS_BY_PROVIDER, TRANSCRIPT_PROMPT_MAX_WORDS, ComposeBeatError, defaultCallLLM, CONFIG_PROVIDER_BY_COMPOSER;
30502
30599
  var init_compose_scenes_skills = __esm({
30503
30600
  "src/commands/_shared/compose-scenes-skills.ts"() {
30504
30601
  "use strict";
@@ -30510,6 +30607,7 @@ var init_compose_scenes_skills = __esm({
30510
30607
  init_root_sync();
30511
30608
  init_composer_resolve();
30512
30609
  init_config();
30610
+ init_transcribe_narration();
30513
30611
  MODEL_SETTINGS_BY_PROVIDER = {
30514
30612
  // Anthropic Claude Sonnet 4.6 — v0.69 baseline. ~9 s/beat, $3/$15 per MTok.
30515
30613
  claude: {
@@ -30533,6 +30631,7 @@ var init_compose_scenes_skills = __esm({
30533
30631
  high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
30534
30632
  }
30535
30633
  };
30634
+ TRANSCRIPT_PROMPT_MAX_WORDS = 120;
30536
30635
  ComposeBeatError = class extends Error {
30537
30636
  constructor(code, message, cause) {
30538
30637
  super(message);
@@ -30618,22 +30717,22 @@ var init_compose_scenes_skills = __esm({
30618
30717
  });
30619
30718
 
30620
30719
  // src/commands/_shared/compose-prompts.ts
30621
- import { existsSync as existsSync8 } from "node:fs";
30622
- import { readFile as readFile9 } from "node:fs/promises";
30623
- import { join as join7, relative as relative4, resolve as resolve12 } from "node:path";
30720
+ import { existsSync as existsSync9 } from "node:fs";
30721
+ import { readFile as readFile10 } from "node:fs/promises";
30722
+ import { join as join8, relative as relative4, resolve as resolve12 } from "node:path";
30624
30723
  async function getComposePrompts(opts) {
30625
30724
  const projectDir = resolve12(opts.projectDir);
30626
- const designPath = join7(projectDir, "DESIGN.md");
30627
- const storyboardPath2 = join7(projectDir, "STORYBOARD.md");
30628
- const skillPath = join7(projectDir, "SKILL.md");
30629
- const compositionsDir = join7(projectDir, "compositions");
30725
+ const designPath = join8(projectDir, "DESIGN.md");
30726
+ const storyboardPath2 = join8(projectDir, "STORYBOARD.md");
30727
+ const skillPath = join8(projectDir, "SKILL.md");
30728
+ const compositionsDir = join8(projectDir, "compositions");
30630
30729
  const warnings = [];
30631
30730
  const baseError = (msg) => ({
30632
30731
  success: false,
30633
30732
  projectDir,
30634
30733
  designReference: "DESIGN.md",
30635
30734
  storyboardReference: "STORYBOARD.md",
30636
- skillReference: existsSync8(skillPath) ? "SKILL.md" : null,
30735
+ skillReference: existsSync9(skillPath) ? "SKILL.md" : null,
30637
30736
  compositionsDir: "compositions",
30638
30737
  beats: [],
30639
30738
  instructions: [],
@@ -30641,18 +30740,18 @@ async function getComposePrompts(opts) {
30641
30740
  warnings,
30642
30741
  error: msg
30643
30742
  });
30644
- if (!existsSync8(designPath)) {
30743
+ if (!existsSync9(designPath)) {
30645
30744
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
30646
30745
  }
30647
- if (!existsSync8(storyboardPath2)) {
30746
+ if (!existsSync9(storyboardPath2)) {
30648
30747
  return baseError(`STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
30649
30748
  }
30650
- if (!existsSync8(skillPath)) {
30749
+ if (!existsSync9(skillPath)) {
30651
30750
  warnings.push(
30652
30751
  "SKILL.md not installed \u2014 host agent won't have Hyperframes rules in context. Run `vibe scene install-skill` to install it."
30653
30752
  );
30654
30753
  }
30655
- const storyboardMd = await readFile9(storyboardPath2, "utf-8");
30754
+ const storyboardMd = await readFile10(storyboardPath2, "utf-8");
30656
30755
  const parsed = parseStoryboard(storyboardMd);
30657
30756
  if (parsed.beats.length === 0) {
30658
30757
  return baseError(`STORYBOARD.md has no \`## Beat \u2026\` headings.`);
@@ -30674,28 +30773,33 @@ async function getComposePrompts(opts) {
30674
30773
  finalDurations = await resolveProjectBeatDurations(projectDir);
30675
30774
  } catch {
30676
30775
  }
30677
- const result = beats.map((beat) => {
30678
- const outputPathAbs = join7(compositionsDir, `scene-${beat.id}.html`);
30679
- const outputPathRel = relative4(projectDir, outputPathAbs);
30680
- const finalDurationSec = finalDurations.get(beat.id);
30681
- const userPrompt = buildUserPrompt({
30682
- beat,
30683
- storyboardGlobal: parsed.global,
30684
- finalDurationSec
30685
- });
30686
- return {
30687
- id: beat.id,
30688
- heading: beat.heading,
30689
- outputPath: outputPathRel,
30690
- duration: beat.duration,
30691
- finalDurationSec,
30692
- cues: beat.cues,
30693
- body: beat.body,
30694
- userPrompt,
30695
- exists: existsSync8(outputPathAbs)
30696
- };
30697
- });
30698
- const skillRef = existsSync8(skillPath) ? "SKILL.md" : null;
30776
+ const result = await Promise.all(
30777
+ beats.map(async (beat) => {
30778
+ const outputPathAbs = join8(compositionsDir, `scene-${beat.id}.html`);
30779
+ const outputPathRel = relative4(projectDir, outputPathAbs);
30780
+ const finalDurationSec = finalDurations.get(beat.id);
30781
+ const transcript = await readBeatTranscript(projectDir, beat.id);
30782
+ const userPrompt = buildUserPrompt({
30783
+ beat,
30784
+ storyboardGlobal: parsed.global,
30785
+ finalDurationSec,
30786
+ transcript
30787
+ });
30788
+ return {
30789
+ id: beat.id,
30790
+ heading: beat.heading,
30791
+ outputPath: outputPathRel,
30792
+ duration: beat.duration,
30793
+ finalDurationSec,
30794
+ cues: beat.cues,
30795
+ body: beat.body,
30796
+ userPrompt,
30797
+ transcriptPath: transcript ? beatTranscriptRelPath(beat.id) : void 0,
30798
+ exists: existsSync9(outputPathAbs)
30799
+ };
30800
+ })
30801
+ );
30802
+ const skillRef = existsSync9(skillPath) ? "SKILL.md" : null;
30699
30803
  const instructions = buildInstructions({
30700
30804
  skillRef,
30701
30805
  beatCount: result.length,
@@ -30743,13 +30847,14 @@ var init_compose_prompts = __esm({
30743
30847
  init_compose_scenes_skills();
30744
30848
  init_root_sync();
30745
30849
  init_bundle();
30850
+ init_transcribe_narration();
30746
30851
  }
30747
30852
  });
30748
30853
 
30749
30854
  // src/commands/_shared/scene-submit.ts
30750
- import { existsSync as existsSync9 } from "node:fs";
30751
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
30752
- import { join as join8, resolve as resolve13 } from "node:path";
30855
+ import { existsSync as existsSync10 } from "node:fs";
30856
+ import { mkdir as mkdir6, readFile as readFile11, writeFile as writeFile9 } from "node:fs/promises";
30857
+ import { join as join9, resolve as resolve13 } from "node:path";
30753
30858
  async function executeSceneSubmit(opts) {
30754
30859
  const projectDir = resolve13(opts.projectDir);
30755
30860
  const beatId = opts.beatId.trim();
@@ -30763,15 +30868,15 @@ async function executeSceneSubmit(opts) {
30763
30868
  warnings: [],
30764
30869
  retryWith: []
30765
30870
  };
30766
- const storyboardPath2 = join8(projectDir, "STORYBOARD.md");
30767
- if (!existsSync9(storyboardPath2)) {
30871
+ const storyboardPath2 = join9(projectDir, "STORYBOARD.md");
30872
+ if (!existsSync10(storyboardPath2)) {
30768
30873
  return {
30769
30874
  ...base,
30770
30875
  success: false,
30771
30876
  error: `STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe init <dir>\` first.`
30772
30877
  };
30773
30878
  }
30774
- const parsed = parseStoryboard(await readFile10(storyboardPath2, "utf-8"));
30879
+ const parsed = parseStoryboard(await readFile11(storyboardPath2, "utf-8"));
30775
30880
  const beat = parsed.beats.find((b) => b.id === beatId);
30776
30881
  if (!beat) {
30777
30882
  const available = parsed.beats.map((b) => b.id).join(", ") || "(none)";
@@ -30841,8 +30946,8 @@ async function executeSceneSubmit(opts) {
30841
30946
  if (opts.validateOnly) {
30842
30947
  return { ...base, success: true };
30843
30948
  }
30844
- const absPath = join8(projectDir, scenePath);
30845
- await mkdir6(join8(projectDir, "compositions"), { recursive: true });
30949
+ const absPath = join9(projectDir, scenePath);
30950
+ await mkdir6(join9(projectDir, "compositions"), { recursive: true });
30846
30951
  await writeFile9(absPath, html.endsWith("\n") ? html : `${html}
30847
30952
  `, "utf-8");
30848
30953
  return {
@@ -30866,9 +30971,9 @@ var init_scene_submit = __esm({
30866
30971
  });
30867
30972
 
30868
30973
  // src/commands/_shared/sub-comp-duration-sync.ts
30869
- import { existsSync as existsSync10 } from "node:fs";
30870
- import { readFile as readFile11 } from "node:fs/promises";
30871
- import { join as join9, resolve as resolve14 } from "node:path";
30974
+ import { existsSync as existsSync11 } from "node:fs";
30975
+ import { readFile as readFile12 } from "node:fs/promises";
30976
+ import { join as join10, resolve as resolve14 } from "node:path";
30872
30977
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
30873
30978
  const compositionId = `scene-${beatId}`;
30874
30979
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -30960,15 +31065,15 @@ async function createSubCompDurationSyncPlans(opts) {
30960
31065
  const results = [];
30961
31066
  for (const beat of opts.beats) {
30962
31067
  const fileRel = `compositions/scene-${beat.id}.html`;
30963
- const filePath = join9(projectDir, fileRel);
30964
- if (!existsSync10(filePath)) continue;
31068
+ const filePath = join10(projectDir, fileRel);
31069
+ if (!existsSync11(filePath)) continue;
30965
31070
  const newDurationSec = await resolveSyncedBeatDuration({
30966
31071
  projectDir,
30967
31072
  beatDuration: beat.duration,
30968
31073
  narrationPath: beat.narrationPath,
30969
31074
  sceneDurationSec: beat.sceneDurationSec
30970
31075
  });
30971
- const html = await readFile11(filePath, "utf-8");
31076
+ const html = await readFile12(filePath, "utf-8");
30972
31077
  const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
30973
31078
  results.push({ ...outcome, file: fileRel, beatId: beat.id });
30974
31079
  }
@@ -30993,7 +31098,7 @@ var init_sub_comp_duration_sync = __esm({
30993
31098
 
30994
31099
  // src/commands/_shared/review-report.ts
30995
31100
  import { writeFile as writeFile10 } from "node:fs/promises";
30996
- import { join as join10, resolve as resolve15 } from "node:path";
31101
+ import { join as join11, resolve as resolve15 } from "node:path";
30997
31102
  function statusFromIssues(issues) {
30998
31103
  if (issues.some((issue) => issue.severity === "error")) return "fail";
30999
31104
  if (issues.some((issue) => issue.severity === "warning")) return "warn";
@@ -31171,7 +31276,7 @@ function buildReviewReport(opts) {
31171
31276
  });
31172
31277
  }
31173
31278
  function defaultReviewReportPath(projectDir) {
31174
- return join10(resolve15(projectDir), "review-report.json");
31279
+ return join11(resolve15(projectDir), "review-report.json");
31175
31280
  }
31176
31281
  async function writeReviewReport(path2, report) {
31177
31282
  await writeFile10(path2, JSON.stringify(report, null, 2) + "\n", "utf-8");
@@ -31486,7 +31591,7 @@ var init_review_report = __esm({
31486
31591
  });
31487
31592
 
31488
31593
  // src/commands/_shared/scene-repair.ts
31489
- import { readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
31594
+ import { readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
31490
31595
  import { relative as relative5, resolve as resolve16 } from "node:path";
31491
31596
  import { runHyperframeLint as runHyperframeLint3 } from "@hyperframes/producer";
31492
31597
  async function executeSceneRepair(opts) {
@@ -31532,7 +31637,7 @@ async function executeSceneRepair(opts) {
31532
31637
  }
31533
31638
  for (const target of targets) {
31534
31639
  const rel = relative5(projectDir, target.abs) || target.abs;
31535
- const html = await readFile12(target.abs, "utf-8");
31640
+ const html = await readFile13(target.abs, "utf-8");
31536
31641
  const findings = lintHtml(html, rel, target.isSub);
31537
31642
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
31538
31643
  if (fixedCodes.length > 0) {
@@ -31905,8 +32010,8 @@ var init_storyboard_edit = __esm({
31905
32010
 
31906
32011
  // src/commands/scene.ts
31907
32012
  import { resolve as resolve17, relative as relative6, dirname as dirname4 } from "node:path";
31908
- import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile12, access as access5, copyFile } from "node:fs/promises";
31909
- import { existsSync as existsSync11 } from "node:fs";
32013
+ import { mkdir as mkdir7, readFile as readFile14, writeFile as writeFile12, access as access5, copyFile } from "node:fs/promises";
32014
+ import { existsSync as existsSync12 } from "node:fs";
31910
32015
  function validateDuration(value) {
31911
32016
  const n = parseFloat(value);
31912
32017
  if (!Number.isFinite(n) || n <= 0 || n > 3600) {
@@ -31970,8 +32075,8 @@ function readClipStart(rootHtml, beatId) {
31970
32075
  async function resolveNarrationText(value) {
31971
32076
  if (!value) return void 0;
31972
32077
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
31973
- if (looksLikePath && existsSync11(value)) {
31974
- return (await readFile13(value, "utf-8")).trim();
32078
+ if (looksLikePath && existsSync12(value)) {
32079
+ return (await readFile14(value, "utf-8")).trim();
31975
32080
  }
31976
32081
  return value.trim();
31977
32082
  }
@@ -32010,7 +32115,7 @@ async function executeSceneAdd(opts) {
32010
32115
  if (!opts.force && await pathExists2(scenePath)) {
32011
32116
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
32012
32117
  }
32013
- const rootHtmlBefore = await readFile13(rootPath, "utf-8");
32118
+ const rootHtmlBefore = await readFile14(rootPath, "utf-8");
32014
32119
  let dims = readRootDims(rootHtmlBefore);
32015
32120
  if (!dims) {
32016
32121
  const cfg2 = await loadVibeProjectConfig(projectDir);
@@ -32086,31 +32191,20 @@ async function executeSceneAdd(opts) {
32086
32191
  );
32087
32192
  } else {
32088
32193
  opts.onProgress?.("Transcribing narration (Whisper word-level)...");
32089
- try {
32090
- const whisper = new WhisperProvider();
32091
- await whisper.initialize({ apiKey: whisperKey });
32092
- const audioBytes = await readFile13(audioAbsPath);
32093
- const audioBlob = new Blob([new Uint8Array(audioBytes)]);
32094
- const transcript = await whisper.transcribe(audioBlob, void 0, {
32095
- granularity: "word",
32096
- language: opts.transcribeLanguage
32097
- });
32098
- if (transcript.status === "completed" && transcript.words?.length) {
32099
- transcriptRelPath = `assets/transcript-${id}.json`;
32100
- const transcriptAbs = resolve17(projectDir, transcriptRelPath);
32101
- await writeFile12(transcriptAbs, JSON.stringify(transcript.words, null, 2), "utf-8");
32102
- transcriptWordCount = transcript.words.length;
32103
- transcriptWords = transcript.words.map((w) => ({
32104
- text: w.text,
32105
- start: w.start,
32106
- end: w.end
32107
- }));
32108
- } else if (transcript.status === "failed") {
32109
- opts.onProgress?.(`Transcribe failed: ${transcript.error ?? "unknown error"}`);
32110
- }
32111
- } catch (error) {
32112
- const msg = error instanceof Error ? error.message : String(error);
32113
- opts.onProgress?.(`Transcribe failed: ${msg}`);
32194
+ const words = await transcribeNarrationWords(audioAbsPath, {
32195
+ apiKey: whisperKey,
32196
+ language: opts.transcribeLanguage
32197
+ });
32198
+ if (words.length > 0) {
32199
+ transcriptRelPath = beatTranscriptRelPath(id);
32200
+ const transcriptAbs = resolve17(projectDir, transcriptRelPath);
32201
+ await writeFile12(transcriptAbs, JSON.stringify(words, null, 2), "utf-8");
32202
+ transcriptWordCount = words.length;
32203
+ transcriptWords = words;
32204
+ } else {
32205
+ opts.onProgress?.(
32206
+ "Transcribe produced no word timings \u2014 narration plays without word-sync"
32207
+ );
32114
32208
  }
32115
32209
  }
32116
32210
  }
@@ -32219,7 +32313,7 @@ async function executeSceneAdd(opts) {
32219
32313
  const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
32220
32314
  if (shouldSyncStoryboard) {
32221
32315
  opts.onProgress?.("Syncing STORYBOARD.md...");
32222
- const before = await readFile13(storyboardAbsPath, "utf-8");
32316
+ const before = await readFile14(storyboardAbsPath, "utf-8");
32223
32317
  const synced = upsertStoryboardBeat(before, {
32224
32318
  beatId: id,
32225
32319
  title: opts.headline,
@@ -32261,7 +32355,7 @@ async function executeSceneAdd(opts) {
32261
32355
  });
32262
32356
  const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
32263
32357
  if (blockingIssue) return errResult(blockingIssue.message);
32264
- const rootHtmlAfterSync = await readFile13(rootPath, "utf-8");
32358
+ const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
32265
32359
  start = readClipStart(rootHtmlAfterSync, id) ?? 0;
32266
32360
  } else {
32267
32361
  start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
@@ -32293,7 +32387,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
32293
32387
  const root = rootArg || "index.html";
32294
32388
  const project = projectArg || ".";
32295
32389
  const defaultProject = project === ".";
32296
- if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync11(resolve17(root, "index.html"))) {
32390
+ if (defaultProject && root !== "index.html" && !root.toLowerCase().endsWith(".html") && existsSync12(resolve17(root, "index.html"))) {
32297
32391
  return { projectDir: resolve17(root), rootRel: "index.html" };
32298
32392
  }
32299
32393
  return { projectDir: resolve17(project), rootRel: root };
@@ -32324,6 +32418,7 @@ var init_scene = __esm({
32324
32418
  init_agent_host_detect();
32325
32419
  init_install_skill();
32326
32420
  init_compose_prompts();
32421
+ init_transcribe_narration();
32327
32422
  init_scene_submit();
32328
32423
  init_scene_repair();
32329
32424
  init_root_sync();
@@ -32493,7 +32588,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
32493
32588
  }
32494
32589
  html = Buffer.concat(chunks).toString("utf-8");
32495
32590
  } else if (options.file) {
32496
- html = await readFile13(resolve17(String(options.file)), "utf-8");
32591
+ html = await readFile14(resolve17(String(options.file)), "utf-8");
32497
32592
  } else {
32498
32593
  exitWithError(
32499
32594
  usageError(
@@ -32947,8 +33042,8 @@ __export(remotion_exports, {
32947
33042
  wrapComponentWithVideo: () => wrapComponentWithVideo
32948
33043
  });
32949
33044
  import { writeFile as writeFile14, mkdir as mkdir8, rm, copyFile as copyFile2 } from "node:fs/promises";
32950
- import { existsSync as existsSync13 } from "node:fs";
32951
- import { join as join11 } from "node:path";
33045
+ import { existsSync as existsSync14 } from "node:fs";
33046
+ import { join as join12 } from "node:path";
32952
33047
  import { tmpdir } from "node:os";
32953
33048
  import { createHash as createHash3 } from "node:crypto";
32954
33049
  async function ensureRemotionInstalled() {
@@ -32967,7 +33062,7 @@ async function ensureRemotionInstalled() {
32967
33062
  }
32968
33063
  }
32969
33064
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
32970
- const dir = join11(tmpdir(), `vibe_motion_${Date.now()}`);
33065
+ const dir = join12(tmpdir(), `vibe_motion_${Date.now()}`);
32971
33066
  await mkdir8(dir, { recursive: true });
32972
33067
  const deps = {
32973
33068
  remotion: REMOTION_VERSION,
@@ -32986,7 +33081,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
32986
33081
  dependencies: deps
32987
33082
  };
32988
33083
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
32989
- await writeFile14(join11(dir, "package.json"), packageJsonStr);
33084
+ await writeFile14(join12(dir, "package.json"), packageJsonStr);
32990
33085
  const tsconfig = {
32991
33086
  compilerOptions: {
32992
33087
  target: "ES2020",
@@ -32998,8 +33093,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
32998
33093
  skipLibCheck: true
32999
33094
  }
33000
33095
  };
33001
- await writeFile14(join11(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
33002
- await writeFile14(join11(dir, "Component.tsx"), componentCode);
33096
+ await writeFile14(join12(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
33097
+ await writeFile14(join12(dir, "Component.tsx"), componentCode);
33003
33098
  const rootCode = `import { registerRoot, Composition } from "remotion";
33004
33099
  import { ${componentName} } from "./Component";
33005
33100
 
@@ -33018,14 +33113,14 @@ const Root = () => {
33018
33113
 
33019
33114
  registerRoot(Root);
33020
33115
  `;
33021
- await writeFile14(join11(dir, "Root.tsx"), rootCode);
33116
+ await writeFile14(join12(dir, "Root.tsx"), rootCode);
33022
33117
  const depsHash = createHash3("md5").update(packageJsonStr).digest("hex").slice(0, 12);
33023
- const cacheMarker = join11(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
33024
- const cachedModules = join11(REMOTION_CACHE_DIR, "node_modules");
33025
- if (existsSync13(cacheMarker) && existsSync13(cachedModules)) {
33118
+ const cacheMarker = join12(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
33119
+ const cachedModules = join12(REMOTION_CACHE_DIR, "node_modules");
33120
+ if (existsSync14(cacheMarker) && existsSync14(cachedModules)) {
33026
33121
  const { symlink } = await import("node:fs/promises");
33027
33122
  try {
33028
- await symlink(cachedModules, join11(dir, "node_modules"), "dir");
33123
+ await symlink(cachedModules, join12(dir, "node_modules"), "dir");
33029
33124
  } catch {
33030
33125
  await this_npmInstall(dir);
33031
33126
  }
@@ -33033,12 +33128,12 @@ registerRoot(Root);
33033
33128
  await this_npmInstall(dir);
33034
33129
  try {
33035
33130
  await mkdir8(REMOTION_CACHE_DIR, { recursive: true });
33036
- await writeFile14(join11(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
33037
- if (existsSync13(join11(dir, "node_modules"))) {
33131
+ await writeFile14(join12(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
33132
+ if (existsSync14(join12(dir, "node_modules"))) {
33038
33133
  const { rename: rename5, symlink } = await import("node:fs/promises");
33039
- await rename5(join11(dir, "node_modules"), cachedModules).catch(() => {
33134
+ await rename5(join12(dir, "node_modules"), cachedModules).catch(() => {
33040
33135
  });
33041
- await symlink(cachedModules, join11(dir, "node_modules"), "dir").catch(() => {
33136
+ await symlink(cachedModules, join12(dir, "node_modules"), "dir").catch(() => {
33042
33137
  });
33043
33138
  await writeFile14(cacheMarker, depsHash);
33044
33139
  }
@@ -33075,7 +33170,7 @@ async function renderMotion(options) {
33075
33170
  }
33076
33171
  );
33077
33172
  try {
33078
- const entryPoint = join11(dir, "Root.tsx");
33173
+ const entryPoint = join12(dir, "Root.tsx");
33079
33174
  const { execFile: execFile3 } = await import("node:child_process");
33080
33175
  const { promisify: promisify3 } = await import("node:util");
33081
33176
  const execFileAsync3 = promisify3(execFile3);
@@ -33208,10 +33303,10 @@ async function renderWithEmbeddedImage(options) {
33208
33303
  }
33209
33304
  );
33210
33305
  try {
33211
- const publicDir = join11(dir, "public");
33306
+ const publicDir = join12(dir, "public");
33212
33307
  await mkdir8(publicDir, { recursive: true });
33213
- await copyFile2(options.imagePath, join11(publicDir, options.imageFileName));
33214
- const entryPoint = join11(dir, "Root.tsx");
33308
+ await copyFile2(options.imagePath, join12(publicDir, options.imageFileName));
33309
+ const entryPoint = join12(dir, "Root.tsx");
33215
33310
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
33216
33311
  const { execFile: execFileImg } = await import("node:child_process");
33217
33312
  const { promisify: promisifyImg } = await import("node:util");
@@ -33272,10 +33367,10 @@ async function renderWithEmbeddedVideo(options) {
33272
33367
  }
33273
33368
  );
33274
33369
  try {
33275
- const publicDir = join11(dir, "public");
33370
+ const publicDir = join12(dir, "public");
33276
33371
  await mkdir8(publicDir, { recursive: true });
33277
- await copyFile2(options.videoPath, join11(publicDir, options.videoFileName));
33278
- const entryPoint = join11(dir, "Root.tsx");
33372
+ await copyFile2(options.videoPath, join12(publicDir, options.videoFileName));
33373
+ const entryPoint = join12(dir, "Root.tsx");
33279
33374
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
33280
33375
  const { execFile: execFileVid } = await import("node:child_process");
33281
33376
  const { promisify: promisifyVid } = await import("node:util");
@@ -33691,7 +33786,7 @@ var init_remotion = __esm({
33691
33786
  "use strict";
33692
33787
  init_exec_safe();
33693
33788
  REMOTION_VERSION = "4.0.447";
33694
- REMOTION_CACHE_DIR = join11(tmpdir(), "vibe_remotion_cache");
33789
+ REMOTION_CACHE_DIR = join12(tmpdir(), "vibe_remotion_cache");
33695
33790
  }
33696
33791
  });
33697
33792
 
@@ -33702,8 +33797,8 @@ __export(ai_motion_exports, {
33702
33797
  registerMotionCommand: () => registerMotionCommand
33703
33798
  });
33704
33799
  import { resolve as resolve19 } from "node:path";
33705
- import { existsSync as existsSync14 } from "node:fs";
33706
- import { readFile as readFile15, writeFile as writeFile15 } from "node:fs/promises";
33800
+ import { existsSync as existsSync15 } from "node:fs";
33801
+ import { readFile as readFile16, writeFile as writeFile15 } from "node:fs/promises";
33707
33802
  async function getOptionalGoogleApiKey() {
33708
33803
  const configKey = await getApiKeyFromConfig("google");
33709
33804
  if (configKey) return configKey;
@@ -33750,7 +33845,7 @@ async function executeMotion(options) {
33750
33845
  };
33751
33846
  }
33752
33847
  const imagePath = resolve19(process.cwd(), options.image);
33753
- const imageBuffer = await readFile15(imagePath);
33848
+ const imageBuffer = await readFile16(imagePath);
33754
33849
  const gemini = new GeminiProvider();
33755
33850
  await gemini.initialize({ apiKey: geminiApiKey });
33756
33851
  const analysisResult = await gemini.analyzeImage(
@@ -33787,7 +33882,7 @@ Use this image analysis to inform the color palette, typography placement, and o
33787
33882
  }
33788
33883
  if (geminiApiKey) {
33789
33884
  const videoPath = resolve19(process.cwd(), options.video);
33790
- const videoBuffer = await readFile15(videoPath);
33885
+ const videoBuffer = await readFile16(videoPath);
33791
33886
  const gemini = new GeminiProvider();
33792
33887
  await gemini.initialize({ apiKey: geminiApiKey });
33793
33888
  const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
@@ -33818,10 +33913,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
33818
33913
  let result;
33819
33914
  if (options.fromTsx) {
33820
33915
  const tsxPath = resolve19(process.cwd(), options.fromTsx);
33821
- if (!existsSync14(tsxPath)) {
33916
+ if (!existsSync15(tsxPath)) {
33822
33917
  return { success: false, error: `TSX file not found: ${tsxPath}` };
33823
33918
  }
33824
- const existingCode = await readFile15(tsxPath, "utf-8");
33919
+ const existingCode = await readFile16(tsxPath, "utf-8");
33825
33920
  if (useGemini) {
33826
33921
  const gemini = new GeminiProvider();
33827
33922
  await gemini.initialize({ apiKey });
@@ -34598,7 +34693,7 @@ var init_ai_helpers = __esm({
34598
34693
 
34599
34694
  // src/commands/generate/storyboard.ts
34600
34695
  import { resolve as resolve22 } from "node:path";
34601
- import { readFile as readFile16, writeFile as writeFile18 } from "node:fs/promises";
34696
+ import { readFile as readFile17, writeFile as writeFile18 } from "node:fs/promises";
34602
34697
  async function executeStoryboard(options) {
34603
34698
  const { content, duration, creativity = "low", output, apiKey } = options;
34604
34699
  try {
@@ -34646,7 +34741,7 @@ function registerStoryboardCommand(parent) {
34646
34741
  let textContent = content;
34647
34742
  if (options.file) {
34648
34743
  const filePath = resolve22(process.cwd(), content);
34649
- textContent = await readFile16(filePath, "utf-8");
34744
+ textContent = await readFile17(filePath, "utf-8");
34650
34745
  }
34651
34746
  if (options.dryRun) {
34652
34747
  outputSuccess({
@@ -35718,7 +35813,7 @@ __export(ai_video_exports, {
35718
35813
  executeVideoGenerate: () => executeVideoGenerate,
35719
35814
  executeVideoStatus: () => executeVideoStatus
35720
35815
  });
35721
- import { readFile as readFile17, writeFile as writeFile21 } from "node:fs/promises";
35816
+ import { readFile as readFile18, writeFile as writeFile21 } from "node:fs/promises";
35722
35817
  import { resolve as resolve25 } from "node:path";
35723
35818
  async function executeVideoGenerate(options) {
35724
35819
  const {
@@ -35758,7 +35853,7 @@ async function executeVideoGenerate(options) {
35758
35853
  let referenceImageMimeType;
35759
35854
  if (image) {
35760
35855
  const imagePath = resolve25(process.cwd(), image);
35761
- const imageBuffer = await readFile17(imagePath);
35856
+ const imageBuffer = await readFile18(imagePath);
35762
35857
  const ext = image.toLowerCase().split(".").pop();
35763
35858
  const mimeTypes2 = {
35764
35859
  jpg: "image/jpeg",
@@ -36006,7 +36101,7 @@ async function fileInputToUrlOrDataUri(input, fallbackMimeType) {
36006
36101
  return input;
36007
36102
  }
36008
36103
  const absPath = resolve25(process.cwd(), input);
36009
- const buffer = await readFile17(absPath);
36104
+ const buffer = await readFile18(absPath);
36010
36105
  return `data:${mimeTypeForPath(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
36011
36106
  }
36012
36107
  function mimeTypeForPath(path2, fallback2) {
@@ -36258,15 +36353,15 @@ var init_ai_video = __esm({
36258
36353
  });
36259
36354
 
36260
36355
  // src/commands/_shared/build-asset-metadata.ts
36261
- import { existsSync as existsSync15, readFileSync as readFileSync2 } from "node:fs";
36356
+ import { existsSync as existsSync16, readFileSync as readFileSync2 } from "node:fs";
36262
36357
  import { mkdir as mkdir10, writeFile as writeFile22 } from "node:fs/promises";
36263
- import { dirname as dirname6, join as join12 } from "node:path";
36358
+ import { dirname as dirname6, join as join13 } from "node:path";
36264
36359
  function assetMetadataPath(kind, beatId) {
36265
36360
  return `.vibeframe/assets/${kind}-${beatId}.json`;
36266
36361
  }
36267
36362
  function readAssetMetadata(projectDir, kind, beatId) {
36268
- const path2 = join12(projectDir, assetMetadataPath(kind, beatId));
36269
- if (!existsSync15(path2)) return null;
36363
+ const path2 = join13(projectDir, assetMetadataPath(kind, beatId));
36364
+ if (!existsSync16(path2)) return null;
36270
36365
  try {
36271
36366
  const parsed = JSON.parse(readFileSync2(path2, "utf-8"));
36272
36367
  if (parsed.schemaVersion !== "1" || parsed.kind !== kind || parsed.beatId !== beatId || typeof parsed.cacheKey !== "string") {
@@ -36295,7 +36390,7 @@ function isFreshCanonicalAsset(opts) {
36295
36390
  }
36296
36391
  async function writeAssetMetadata(opts) {
36297
36392
  const rel = assetMetadataPath(opts.kind, opts.beatId);
36298
- const abs = join12(opts.projectDir, rel);
36393
+ const abs = join13(opts.projectDir, rel);
36299
36394
  const metadata = {
36300
36395
  schemaVersion: "1",
36301
36396
  kind: opts.kind,
@@ -36319,20 +36414,20 @@ var init_build_asset_metadata = __esm({
36319
36414
 
36320
36415
  // src/commands/_shared/status-jobs.ts
36321
36416
  import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
36322
- import { existsSync as existsSync16, statSync as statSync2 } from "node:fs";
36323
- import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile18, readdir as readdir2, stat as stat4, writeFile as writeFile23 } from "node:fs/promises";
36324
- import { dirname as dirname7, join as join13, parse as parse2, relative as relative7, resolve as resolve26 } from "node:path";
36417
+ import { existsSync as existsSync17, statSync as statSync2 } from "node:fs";
36418
+ import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile19, readdir as readdir2, stat as stat4, writeFile as writeFile23 } from "node:fs/promises";
36419
+ import { dirname as dirname7, join as join14, parse as parse2, relative as relative7, resolve as resolve26 } from "node:path";
36325
36420
  function isLocalJob(record) {
36326
36421
  return record.jobType === "build" || record.jobType === "render";
36327
36422
  }
36328
36423
  function findProjectRoot(start = process.cwd()) {
36329
36424
  let dir = resolve26(start);
36330
36425
  try {
36331
- if (existsSync16(dir) && statSync2(dir).isFile()) dir = dirname7(dir);
36426
+ if (existsSync17(dir) && statSync2(dir).isFile()) dir = dirname7(dir);
36332
36427
  } catch {
36333
36428
  }
36334
36429
  for (; ; ) {
36335
- if (existsSync16(join13(dir, "vibe.config.json")) || existsSync16(join13(dir, "STORYBOARD.md"))) {
36430
+ if (existsSync17(join14(dir, "vibe.config.json")) || existsSync17(join14(dir, "STORYBOARD.md"))) {
36336
36431
  return dir;
36337
36432
  }
36338
36433
  const parent = dirname7(dir);
@@ -36341,7 +36436,7 @@ function findProjectRoot(start = process.cwd()) {
36341
36436
  }
36342
36437
  }
36343
36438
  function jobsDir(projectDir) {
36344
- return join13(resolve26(projectDir), ".vibeframe", "jobs");
36439
+ return join14(resolve26(projectDir), ".vibeframe", "jobs");
36345
36440
  }
36346
36441
  function normalizeJobStatus(status) {
36347
36442
  const value = String(status ?? "").toLowerCase();
@@ -36413,18 +36508,18 @@ async function readJobRecord(jobId, projectDir) {
36413
36508
  const id = normalizeJobId(jobId);
36414
36509
  const project = resolve26(projectDir ?? findProjectRoot());
36415
36510
  const path2 = jobRecordPath(id, project);
36416
- if (!existsSync16(path2)) return null;
36417
- return parseJobRecord(await readFile18(path2, "utf-8"));
36511
+ if (!existsSync17(path2)) return null;
36512
+ return parseJobRecord(await readFile19(path2, "utf-8"));
36418
36513
  }
36419
36514
  async function listJobRecords(projectDir) {
36420
36515
  const dir = jobsDir(projectDir);
36421
- if (!existsSync16(dir)) return [];
36516
+ if (!existsSync17(dir)) return [];
36422
36517
  const entries = await readdir2(dir, { withFileTypes: true });
36423
36518
  const records = [];
36424
36519
  for (const entry of entries) {
36425
36520
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
36426
36521
  try {
36427
- const parsed = parseJobRecord(await readFile18(join13(dir, entry.name), "utf-8"));
36522
+ const parsed = parseJobRecord(await readFile19(join14(dir, entry.name), "utf-8"));
36428
36523
  if (parsed) records.push(parsed);
36429
36524
  } catch {
36430
36525
  }
@@ -36518,7 +36613,7 @@ async function refreshLocalJobRecord(record, opts) {
36518
36613
  const heartbeatAge = record.heartbeatAt ? Date.now() - new Date(record.heartbeatAt).getTime() : Number.POSITIVE_INFINITY;
36519
36614
  const orphaned = !isPidAlive(record.pid) || heartbeatAge > LOCAL_JOB_ORPHAN_AFTER_MS;
36520
36615
  if (orphaned) {
36521
- const hint = `The process that ran this ${record.jobType} job is gone. Check ${join13(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
36616
+ const hint = `The process that ran this ${record.jobType} job is gone. Check ${join14(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
36522
36617
  warnings.push(hint);
36523
36618
  updated = stripUndefined2({
36524
36619
  ...record,
@@ -36681,7 +36776,7 @@ function normalizeJobId(jobId) {
36681
36776
  return id;
36682
36777
  }
36683
36778
  function jobRecordPath(jobId, projectDir) {
36684
- return join13(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
36779
+ return join14(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
36685
36780
  }
36686
36781
  function parseJobRecord(raw) {
36687
36782
  const parsed = JSON.parse(raw);
@@ -36748,7 +36843,7 @@ async function maybeCacheOutput(record) {
36748
36843
  }
36749
36844
  async function maybeWriteCompletedAssetMetadata(record) {
36750
36845
  const kind = assetKindForJob(record);
36751
- if (record.status !== "completed" || !kind || !record.beatId || !record.assetCue || !record.cacheKey || !record.outputPath || !existsSync16(record.outputPath)) {
36846
+ if (record.status !== "completed" || !kind || !record.beatId || !record.assetCue || !record.cacheKey || !record.outputPath || !existsSync17(record.outputPath)) {
36752
36847
  return;
36753
36848
  }
36754
36849
  try {
@@ -36767,7 +36862,7 @@ async function maybeWriteCompletedAssetMetadata(record) {
36767
36862
  }
36768
36863
  }
36769
36864
  async function refreshBuildReportFromJobs(projectDir, records) {
36770
- const reportPath = join13(projectDir, "build-report.json");
36865
+ const reportPath = join14(projectDir, "build-report.json");
36771
36866
  const report = await readJson(reportPath);
36772
36867
  if (!report || !Array.isArray(report.beats)) return;
36773
36868
  let changed = false;
@@ -36820,7 +36915,7 @@ async function refreshBuildReportFromJobs(projectDir, records) {
36820
36915
  }
36821
36916
  }
36822
36917
  async function readBuildSummary(projectDir) {
36823
- const reportPath = join13(projectDir, "build-report.json");
36918
+ const reportPath = join14(projectDir, "build-report.json");
36824
36919
  const report = await readJson(reportPath);
36825
36920
  if (!report) return null;
36826
36921
  const info = await statOrNull(reportPath);
@@ -36843,7 +36938,7 @@ async function readBuildSummary(projectDir) {
36843
36938
  });
36844
36939
  }
36845
36940
  async function readReviewSummary(projectDir) {
36846
- const reportPath = join13(projectDir, "review-report.json");
36941
+ const reportPath = join14(projectDir, "review-report.json");
36847
36942
  const report = await readJson(reportPath);
36848
36943
  if (!report) return null;
36849
36944
  const info = await statOrNull(reportPath);
@@ -36932,16 +37027,16 @@ async function readProjectBeatReadiness(projectDir, build, records) {
36932
37027
  if (fromBeats) return fromBeats;
36933
37028
  if (build.beats) return augmentBeatReadinessFromJobs(build.beats, records, report?.jobs);
36934
37029
  }
36935
- const storyboardPath2 = join13(projectDir, "STORYBOARD.md");
36936
- if (!existsSync16(storyboardPath2)) {
37030
+ const storyboardPath2 = join14(projectDir, "STORYBOARD.md");
37031
+ if (!existsSync17(storyboardPath2)) {
36937
37032
  return { total: 0, assetsReady: 0, compositionsReady: 0, needsAuthor: [] };
36938
37033
  }
36939
37034
  try {
36940
- const parsed = parseStoryboard(await readFile18(storyboardPath2, "utf-8"));
37035
+ const parsed = parseStoryboard(await readFile19(storyboardPath2, "utf-8"));
36941
37036
  const needsAuthor = [];
36942
37037
  let compositionsReady = 0;
36943
37038
  for (const beat of parsed.beats) {
36944
- if (existsSync16(join13(projectDir, "compositions", `scene-${beat.id}.html`))) {
37039
+ if (existsSync17(join14(projectDir, "compositions", `scene-${beat.id}.html`))) {
36945
37040
  compositionsReady += 1;
36946
37041
  } else {
36947
37042
  needsAuthor.push(beat.id);
@@ -36959,7 +37054,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
36959
37054
  }
36960
37055
  function deriveProjectWorkflow(opts) {
36961
37056
  const retryWith = [];
36962
- const hasStoryboard = existsSync16(join13(opts.project, "STORYBOARD.md"));
37057
+ const hasStoryboard = existsSync17(join14(opts.project, "STORYBOARD.md"));
36963
37058
  if (!hasStoryboard && !opts.build) {
36964
37059
  retryWith.push(`vibe init ${opts.project} --from "<brief>" --json`);
36965
37060
  return { status: "empty", currentStage: "init", retryWith };
@@ -37055,7 +37150,7 @@ function beatReadinessFromBuildBeats(projectDir, beats, records = [], reportJobs
37055
37150
  if (buildBeatAssetsReady(record) || (id ? buildBeatJobsReady(id, records, reportJobs) : false))
37056
37151
  assetsReady += 1;
37057
37152
  const compositionPath = typeof record.compositionPath === "string" ? record.compositionPath : id ? `compositions/scene-${id}.html` : void 0;
37058
- if (compositionPath && existsSync16(resolve26(projectDir, compositionPath))) {
37153
+ if (compositionPath && existsSync17(resolve26(projectDir, compositionPath))) {
37059
37154
  compositionsReady += 1;
37060
37155
  } else if (id) {
37061
37156
  needsAuthor.push(id);
@@ -37163,7 +37258,7 @@ function projectRelativePath(projectDir, path2) {
37163
37258
  }
37164
37259
  async function readJson(path2) {
37165
37260
  try {
37166
- return JSON.parse(await readFile18(path2, "utf-8"));
37261
+ return JSON.parse(await readFile19(path2, "utf-8"));
37167
37262
  } catch {
37168
37263
  return null;
37169
37264
  }
@@ -37202,7 +37297,7 @@ var init_status_jobs = __esm({
37202
37297
  // src/commands/generate/music.ts
37203
37298
  import { resolve as resolve27 } from "node:path";
37204
37299
  import { writeFile as writeFile24 } from "node:fs/promises";
37205
- import { existsSync as existsSync17 } from "node:fs";
37300
+ import { existsSync as existsSync18 } from "node:fs";
37206
37301
  async function executeMusic(options) {
37207
37302
  try {
37208
37303
  const provider = options.provider || "elevenlabs";
@@ -37355,7 +37450,7 @@ function registerMusicCommand(parent) {
37355
37450
  if (options.melody) {
37356
37451
  spinner2.text = "Uploading melody reference...";
37357
37452
  const absPath = resolve27(process.cwd(), options.melody);
37358
- if (!existsSync17(absPath)) {
37453
+ if (!existsSync18(absPath)) {
37359
37454
  spinner2.fail(`Melody file not found: ${options.melody}`);
37360
37455
  exitWithError(notFoundError(options.melody));
37361
37456
  }
@@ -37476,8 +37571,8 @@ __export(ai_image_exports, {
37476
37571
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
37477
37572
  });
37478
37573
  import { resolve as resolve28, dirname as dirname8 } from "node:path";
37479
- import { readFile as readFile19, writeFile as writeFile25, mkdir as mkdir12 } from "node:fs/promises";
37480
- import { existsSync as existsSync18 } from "node:fs";
37574
+ import { readFile as readFile20, writeFile as writeFile25, mkdir as mkdir12 } from "node:fs/promises";
37575
+ import { existsSync as existsSync19 } from "node:fs";
37481
37576
  async function executeImageGenerate(options) {
37482
37577
  const {
37483
37578
  prompt: prompt3,
@@ -37621,10 +37716,10 @@ async function executeGeminiEdit(options) {
37621
37716
  const imageBuffers = [];
37622
37717
  for (const imagePath of imagePaths) {
37623
37718
  const absPath = resolve28(process.cwd(), imagePath);
37624
- if (!existsSync18(absPath)) {
37719
+ if (!existsSync19(absPath)) {
37625
37720
  return { success: false, error: `Image not found: ${absPath}` };
37626
37721
  }
37627
- const buffer = await readFile19(absPath);
37722
+ const buffer = await readFile20(absPath);
37628
37723
  imageBuffers.push(buffer);
37629
37724
  }
37630
37725
  const gemini = new GeminiProvider();
@@ -37669,7 +37764,7 @@ async function executeThumbnailBestFrame(options) {
37669
37764
  model = "flash",
37670
37765
  apiKey
37671
37766
  } = options;
37672
- if (!existsSync18(videoPath)) {
37767
+ if (!existsSync19(videoPath)) {
37673
37768
  return { success: false, error: `Video not found: ${videoPath}` };
37674
37769
  }
37675
37770
  if (!commandExists("ffmpeg")) {
@@ -37682,7 +37777,7 @@ async function executeThumbnailBestFrame(options) {
37682
37777
  try {
37683
37778
  const gemini = new GeminiProvider();
37684
37779
  await gemini.initialize({ apiKey: googleKey });
37685
- const videoData = await readFile19(videoPath);
37780
+ const videoData = await readFile20(videoPath);
37686
37781
  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>"}';
37687
37782
  const modelId = resolveGeminiTextModel(model);
37688
37783
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
@@ -37703,7 +37798,7 @@ async function executeThumbnailBestFrame(options) {
37703
37798
  reason = reasonMatch[1];
37704
37799
  }
37705
37800
  await execSafe("ffmpeg", ["-ss", String(timestamp2), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
37706
- if (!existsSync18(outputPath)) {
37801
+ if (!existsSync19(outputPath)) {
37707
37802
  return { success: false, error: "FFmpeg failed to extract frame" };
37708
37803
  }
37709
37804
  return {
@@ -37729,7 +37824,7 @@ var init_ai_image = __esm({
37729
37824
 
37730
37825
  // src/commands/generate/thumbnail.ts
37731
37826
  import { resolve as resolve29, dirname as dirname9, basename as basename5, extname as extname3 } from "node:path";
37732
- import { existsSync as existsSync19 } from "node:fs";
37827
+ import { existsSync as existsSync20 } from "node:fs";
37733
37828
  import { writeFile as writeFile26, mkdir as mkdir13 } from "node:fs/promises";
37734
37829
  function registerThumbnailCommand(parent) {
37735
37830
  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) => {
@@ -37741,7 +37836,7 @@ function registerThumbnailCommand(parent) {
37741
37836
  }
37742
37837
  if (options.bestFrame) {
37743
37838
  const absVideoPath = resolve29(process.cwd(), options.bestFrame);
37744
- if (!existsSync19(absVideoPath)) {
37839
+ if (!existsSync20(absVideoPath)) {
37745
37840
  exitWithError(notFoundError(absVideoPath));
37746
37841
  }
37747
37842
  if (!commandExists("ffmpeg")) {
@@ -39925,7 +40020,7 @@ var init_dist3 = __esm({
39925
40020
 
39926
40021
  // src/commands/generate/video.ts
39927
40022
  import { resolve as resolve33 } from "node:path";
39928
- import { readFile as readFile20, writeFile as writeFile30 } from "node:fs/promises";
40023
+ import { readFile as readFile21, writeFile as writeFile30 } from "node:fs/promises";
39929
40024
  function registerVideoCommand(parent) {
39930
40025
  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(
39931
40026
  "-p, --provider <provider>",
@@ -40036,7 +40131,7 @@ Examples:
40036
40131
  let isImageToVideo = false;
40037
40132
  if (options.image) {
40038
40133
  const imagePath = resolve33(process.cwd(), options.image);
40039
- const imageBuffer = await readFile20(imagePath);
40134
+ const imageBuffer = await readFile21(imagePath);
40040
40135
  const ext = options.image.toLowerCase().split(".").pop();
40041
40136
  const mimeTypes2 = {
40042
40137
  jpg: "image/jpeg",
@@ -40276,7 +40371,7 @@ Examples:
40276
40371
  let lastFrame;
40277
40372
  if (options.lastFrame) {
40278
40373
  const lastFramePath = resolve33(process.cwd(), options.lastFrame);
40279
- const lastFrameBuffer = await readFile20(lastFramePath);
40374
+ const lastFrameBuffer = await readFile21(lastFramePath);
40280
40375
  const ext = options.lastFrame.toLowerCase().split(".").pop();
40281
40376
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
40282
40377
  lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
@@ -40286,7 +40381,7 @@ Examples:
40286
40381
  refImages = [];
40287
40382
  for (const refPath of options.refImages.slice(0, 3)) {
40288
40383
  const absRefPath = resolve33(process.cwd(), refPath);
40289
- const refBuffer = await readFile20(absRefPath);
40384
+ const refBuffer = await readFile21(absRefPath);
40290
40385
  const ext = refPath.toLowerCase().split(".").pop();
40291
40386
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
40292
40387
  refImages.push({ base64: refBuffer.toString("base64"), mimeType });
@@ -40418,7 +40513,7 @@ Examples:
40418
40513
  if (seedanceEndImagePath && seedanceReferences.length === 0) {
40419
40514
  try {
40420
40515
  const absEndImagePath = resolve33(process.cwd(), seedanceEndImagePath);
40421
- const endImageBuffer = await readFile20(absEndImagePath);
40516
+ const endImageBuffer = await readFile21(absEndImagePath);
40422
40517
  const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
40423
40518
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
40424
40519
  const uploadHost = await resolveUploadHost();
@@ -40535,7 +40630,7 @@ async function fileInputToUrlOrDataUri2(input, fallbackMimeType) {
40535
40630
  return input;
40536
40631
  }
40537
40632
  const absPath = resolve33(process.cwd(), input);
40538
- const buffer = await readFile20(absPath);
40633
+ const buffer = await readFile21(absPath);
40539
40634
  return `data:${mimeTypeForPath2(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
40540
40635
  }
40541
40636
  function mimeTypeForPath2(path2, fallback2) {
@@ -40708,8 +40803,8 @@ Run 'vibe schema generate.<command>' for structured parameter info.
40708
40803
  });
40709
40804
 
40710
40805
  // src/commands/_shared/edit/silence-cut.ts
40711
- import { existsSync as existsSync20 } from "node:fs";
40712
- import { readFile as readFile21 } from "node:fs/promises";
40806
+ import { existsSync as existsSync21 } from "node:fs";
40807
+ import { readFile as readFile22 } from "node:fs/promises";
40713
40808
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
40714
40809
  const totalDuration = await getVideoDuration(videoPath);
40715
40810
  const { stdout, stderr } = await execSafe(
@@ -40759,7 +40854,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
40759
40854
  }
40760
40855
  const gemini = new GeminiProvider();
40761
40856
  await gemini.initialize({ apiKey: geminiApiKey });
40762
- const videoBuffer = await readFile21(videoPath);
40857
+ const videoBuffer = await readFile22(videoPath);
40763
40858
  const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
40764
40859
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
40765
40860
 
@@ -40835,7 +40930,7 @@ async function executeSilenceCut(options) {
40835
40930
  analyzeOnly = false,
40836
40931
  useGemini = false
40837
40932
  } = options;
40838
- if (!existsSync20(videoPath)) {
40933
+ if (!existsSync21(videoPath)) {
40839
40934
  return { success: false, error: `Video not found: ${videoPath}` };
40840
40935
  }
40841
40936
  if (!commandExists("ffmpeg")) {
@@ -40943,11 +41038,11 @@ var init_silence_cut = __esm({
40943
41038
  });
40944
41039
 
40945
41040
  // src/commands/_shared/edit/jump-cut.ts
40946
- import { existsSync as existsSync21 } from "node:fs";
40947
- import { readFile as readFile22, writeFile as writeFile31, mkdir as mkdir15 } from "node:fs/promises";
40948
- import { join as join14 } from "node:path";
41041
+ import { existsSync as existsSync22 } from "node:fs";
41042
+ import { readFile as readFile23, writeFile as writeFile31, mkdir as mkdir15 } from "node:fs/promises";
41043
+ import { join as join15 } from "node:path";
40949
41044
  async function transcribeWithWords(audioPath, apiKey, language) {
40950
- const audioBuffer = await readFile22(audioPath);
41045
+ const audioBuffer = await readFile23(audioPath);
40951
41046
  const audioBlob = new Blob([audioBuffer]);
40952
41047
  const formData = new FormData();
40953
41048
  formData.append("file", audioBlob, "audio.wav");
@@ -41006,7 +41101,7 @@ async function executeJumpCut(options) {
41006
41101
  analyzeOnly = false,
41007
41102
  apiKey
41008
41103
  } = options;
41009
- if (!existsSync21(videoPath)) {
41104
+ if (!existsSync22(videoPath)) {
41010
41105
  return { success: false, error: `Video not found: ${videoPath}` };
41011
41106
  }
41012
41107
  if (!commandExists("ffmpeg")) {
@@ -41025,7 +41120,7 @@ async function executeJumpCut(options) {
41025
41120
  try {
41026
41121
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
41027
41122
  await mkdir15(tmpDir, { recursive: true });
41028
- const audioPath = join14(tmpDir, "audio.wav");
41123
+ const audioPath = join15(tmpDir, "audio.wav");
41029
41124
  try {
41030
41125
  await execSafe(
41031
41126
  "ffmpeg",
@@ -41079,7 +41174,7 @@ async function executeJumpCut(options) {
41079
41174
  const segmentPaths = [];
41080
41175
  for (let i = 0; i < segments.length; i++) {
41081
41176
  const seg = segments[i];
41082
- const segPath = join14(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
41177
+ const segPath = join15(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
41083
41178
  const duration = seg.end - seg.start;
41084
41179
  await execSafe(
41085
41180
  "ffmpeg",
@@ -41102,7 +41197,7 @@ async function executeJumpCut(options) {
41102
41197
  segmentPaths.push(segPath);
41103
41198
  }
41104
41199
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
41105
- const listPath = join14(tmpDir, "concat.txt");
41200
+ const listPath = join15(tmpDir, "concat.txt");
41106
41201
  await writeFile31(listPath, concatList);
41107
41202
  await execSafe(
41108
41203
  "ffmpeg",
@@ -41292,9 +41387,9 @@ var init_helpers = __esm({
41292
41387
  });
41293
41388
 
41294
41389
  // src/commands/_shared/edit/caption.ts
41295
- import { existsSync as existsSync22 } from "node:fs";
41296
- import { readFile as readFile23, writeFile as writeFile32, mkdir as mkdir16 } from "node:fs/promises";
41297
- import { dirname as dirname11, basename as basename6, extname as extname4, join as join15 } from "node:path";
41390
+ import { existsSync as existsSync23 } from "node:fs";
41391
+ import { readFile as readFile24, writeFile as writeFile32, mkdir as mkdir16 } from "node:fs/promises";
41392
+ import { dirname as dirname11, basename as basename6, extname as extname4, join as join16 } from "node:path";
41298
41393
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
41299
41394
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
41300
41395
  const marginV = position === "center" ? 0 : 30;
@@ -41322,7 +41417,7 @@ async function executeCaption(options) {
41322
41417
  position = "bottom",
41323
41418
  apiKey
41324
41419
  } = options;
41325
- if (!existsSync22(videoPath)) {
41420
+ if (!existsSync23(videoPath)) {
41326
41421
  return { success: false, error: `Video not found: ${videoPath}` };
41327
41422
  }
41328
41423
  if (!commandExists("ffmpeg")) {
@@ -41341,8 +41436,8 @@ async function executeCaption(options) {
41341
41436
  try {
41342
41437
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
41343
41438
  await mkdir16(tmpDir, { recursive: true });
41344
- const audioPath = join15(tmpDir, "audio.wav");
41345
- const srtPath = join15(tmpDir, "captions.srt");
41439
+ const audioPath = join16(tmpDir, "audio.wav");
41440
+ const srtPath = join16(tmpDir, "captions.srt");
41346
41441
  try {
41347
41442
  await execSafe(
41348
41443
  "ffmpeg",
@@ -41363,7 +41458,7 @@ async function executeCaption(options) {
41363
41458
  );
41364
41459
  const whisper = new WhisperProvider();
41365
41460
  await whisper.initialize({ apiKey: openaiKey });
41366
- const audioBuffer = await readFile23(audioPath);
41461
+ const audioBuffer = await readFile24(audioPath);
41367
41462
  const audioBlob = new Blob([audioBuffer]);
41368
41463
  const transcriptResult = await whisper.transcribe(audioBlob, language);
41369
41464
  if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
@@ -41411,7 +41506,7 @@ async function executeCaption(options) {
41411
41506
  const remotionErr = await ensureRemotionInstalled2();
41412
41507
  if (remotionErr) {
41413
41508
  const outputDir2 = dirname11(outputPath);
41414
- const outputSrtPath2 = join15(
41509
+ const outputSrtPath2 = join16(
41415
41510
  outputDir2,
41416
41511
  basename6(outputPath, extname4(outputPath)) + ".srt"
41417
41512
  );
@@ -41450,7 +41545,7 @@ SRT saved to: ${outputSrtPath2}` };
41450
41545
  });
41451
41546
  if (!renderResult.success) {
41452
41547
  const outputDir2 = dirname11(outputPath);
41453
- const outputSrtPath2 = join15(
41548
+ const outputSrtPath2 = join16(
41454
41549
  outputDir2,
41455
41550
  basename6(outputPath, extname4(outputPath)) + ".srt"
41456
41551
  );
@@ -41463,7 +41558,7 @@ SRT saved to: ${outputSrtPath2}`
41463
41558
  }
41464
41559
  }
41465
41560
  const outputDir = dirname11(outputPath);
41466
- const outputSrtPath = join15(
41561
+ const outputSrtPath = join16(
41467
41562
  outputDir,
41468
41563
  basename6(outputPath, extname4(outputPath)) + ".srt"
41469
41564
  );
@@ -41500,10 +41595,10 @@ var init_caption = __esm({
41500
41595
  });
41501
41596
 
41502
41597
  // src/commands/_shared/edit/noise-reduce.ts
41503
- import { existsSync as existsSync23 } from "node:fs";
41598
+ import { existsSync as existsSync24 } from "node:fs";
41504
41599
  async function executeNoiseReduce(options) {
41505
41600
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
41506
- if (!existsSync23(inputPath)) {
41601
+ if (!existsSync24(inputPath)) {
41507
41602
  return { success: false, error: `File not found: ${inputPath}` };
41508
41603
  }
41509
41604
  if (!commandExists("ffmpeg")) {
@@ -41560,7 +41655,7 @@ var init_noise_reduce = __esm({
41560
41655
  });
41561
41656
 
41562
41657
  // src/commands/_shared/edit/fade.ts
41563
- import { existsSync as existsSync24 } from "node:fs";
41658
+ import { existsSync as existsSync25 } from "node:fs";
41564
41659
  async function executeFade(options) {
41565
41660
  const {
41566
41661
  videoPath,
@@ -41570,7 +41665,7 @@ async function executeFade(options) {
41570
41665
  audioOnly = false,
41571
41666
  videoOnly = false
41572
41667
  } = options;
41573
- if (!existsSync24(videoPath)) {
41668
+ if (!existsSync25(videoPath)) {
41574
41669
  return { success: false, error: `Video not found: ${videoPath}` };
41575
41670
  }
41576
41671
  if (!commandExists("ffmpeg")) {
@@ -41637,8 +41732,8 @@ var init_fade = __esm({
41637
41732
  });
41638
41733
 
41639
41734
  // src/commands/_shared/edit/translate-srt.ts
41640
- import { existsSync as existsSync25 } from "node:fs";
41641
- import { readFile as readFile24, writeFile as writeFile33 } from "node:fs/promises";
41735
+ import { existsSync as existsSync26 } from "node:fs";
41736
+ import { readFile as readFile25, writeFile as writeFile33 } from "node:fs/promises";
41642
41737
  async function executeTranslateSrt(options) {
41643
41738
  const {
41644
41739
  srtPath,
@@ -41648,11 +41743,11 @@ async function executeTranslateSrt(options) {
41648
41743
  sourceLanguage,
41649
41744
  apiKey
41650
41745
  } = options;
41651
- if (!existsSync25(srtPath)) {
41746
+ if (!existsSync26(srtPath)) {
41652
41747
  return { success: false, error: `SRT file not found: ${srtPath}` };
41653
41748
  }
41654
41749
  try {
41655
- const srtContent = await readFile24(srtPath, "utf-8");
41750
+ const srtContent = await readFile25(srtPath, "utf-8");
41656
41751
  const segments = parseSRT(srtContent);
41657
41752
  if (segments.length === 0) {
41658
41753
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -41765,7 +41860,7 @@ var init_translate_srt = __esm({
41765
41860
  });
41766
41861
 
41767
41862
  // src/commands/_shared/edit/text-overlay.ts
41768
- import { existsSync as existsSync26 } from "node:fs";
41863
+ import { existsSync as existsSync27 } from "node:fs";
41769
41864
  import { resolve as resolve34 } from "node:path";
41770
41865
  function detectSystemFont() {
41771
41866
  const platform3 = process.platform;
@@ -41776,7 +41871,7 @@ function detectSystemFont() {
41776
41871
  "/Library/Fonts/Arial.ttf"
41777
41872
  ];
41778
41873
  for (const f of candidates) {
41779
- if (existsSync26(f)) return f;
41874
+ if (existsSync27(f)) return f;
41780
41875
  }
41781
41876
  } else if (platform3 === "linux") {
41782
41877
  const candidates = [
@@ -41785,7 +41880,7 @@ function detectSystemFont() {
41785
41880
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
41786
41881
  ];
41787
41882
  for (const f of candidates) {
41788
- if (existsSync26(f)) return f;
41883
+ if (existsSync27(f)) return f;
41789
41884
  }
41790
41885
  } else if (platform3 === "win32") {
41791
41886
  const candidates = [
@@ -41793,7 +41888,7 @@ function detectSystemFont() {
41793
41888
  "C:\\Windows\\Fonts\\segoeui.ttf"
41794
41889
  ];
41795
41890
  for (const f of candidates) {
41796
- if (existsSync26(f)) return f;
41891
+ if (existsSync27(f)) return f;
41797
41892
  }
41798
41893
  }
41799
41894
  return null;
@@ -41814,7 +41909,7 @@ async function applyTextOverlays(options) {
41814
41909
  }
41815
41910
  const absVideoPath = resolve34(process.cwd(), videoPath);
41816
41911
  const absOutputPath = resolve34(process.cwd(), outputPath);
41817
- if (!existsSync26(absVideoPath)) {
41912
+ if (!existsSync27(absVideoPath)) {
41818
41913
  return { success: false, error: `Video not found: ${absVideoPath}` };
41819
41914
  }
41820
41915
  if (!commandExists("ffmpeg")) {
@@ -41995,7 +42090,7 @@ var init_ai_edit = __esm({
41995
42090
 
41996
42091
  // src/commands/ai-edit-cli.ts
41997
42092
  import { resolve as resolve35, extname as extname5, basename as basename7 } from "node:path";
41998
- import { existsSync as existsSync27 } from "node:fs";
42093
+ import { existsSync as existsSync28 } from "node:fs";
41999
42094
  function registerEditCommands(aiCommand) {
42000
42095
  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", `
42001
42096
  Examples:
@@ -42012,7 +42107,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
42012
42107
  validateOutputPath(options.output);
42013
42108
  }
42014
42109
  const absVideoPath = resolve35(process.cwd(), videoPath);
42015
- if (!existsSync27(absVideoPath)) {
42110
+ if (!existsSync28(absVideoPath)) {
42016
42111
  exitWithError(notFoundError(absVideoPath));
42017
42112
  }
42018
42113
  if (!commandExists("ffmpeg")) {
@@ -42120,7 +42215,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42120
42215
  validateOutputPath(options.output);
42121
42216
  }
42122
42217
  const absVideoPath = resolve35(process.cwd(), videoPath);
42123
- if (!existsSync27(absVideoPath)) {
42218
+ if (!existsSync28(absVideoPath)) {
42124
42219
  exitWithError(notFoundError(absVideoPath));
42125
42220
  }
42126
42221
  if (!commandExists("ffmpeg")) {
@@ -42201,7 +42296,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42201
42296
  validateOutputPath(options.output);
42202
42297
  }
42203
42298
  const absInputPath = resolve35(process.cwd(), inputPath);
42204
- if (!existsSync27(absInputPath)) {
42299
+ if (!existsSync28(absInputPath)) {
42205
42300
  exitWithError(notFoundError(absInputPath));
42206
42301
  }
42207
42302
  if (!commandExists("ffmpeg")) {
@@ -42267,7 +42362,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42267
42362
  validateOutputPath(options.output);
42268
42363
  }
42269
42364
  const absVideoPath = resolve35(process.cwd(), videoPath);
42270
- if (!existsSync27(absVideoPath)) {
42365
+ if (!existsSync28(absVideoPath)) {
42271
42366
  exitWithError(notFoundError(absVideoPath));
42272
42367
  }
42273
42368
  if (!commandExists("ffmpeg")) {
@@ -42342,7 +42437,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42342
42437
  exitWithError(usageError("Target language required. Use -t or --target"));
42343
42438
  }
42344
42439
  const absSrtPath = resolve35(process.cwd(), srtPath);
42345
- if (!existsSync27(absSrtPath)) {
42440
+ if (!existsSync28(absSrtPath)) {
42346
42441
  exitWithError(notFoundError(absSrtPath));
42347
42442
  }
42348
42443
  if (options.dryRun) {
@@ -42418,7 +42513,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42418
42513
  }
42419
42514
  if (options.fillers) rejectControlChars(options.fillers);
42420
42515
  const absVideoPath = resolve35(process.cwd(), videoPath);
42421
- if (!existsSync27(absVideoPath)) {
42516
+ if (!existsSync28(absVideoPath)) {
42422
42517
  exitWithError(notFoundError(absVideoPath));
42423
42518
  }
42424
42519
  if (!commandExists("ffmpeg")) {
@@ -42518,8 +42613,8 @@ var init_ai_edit_cli = __esm({
42518
42613
  });
42519
42614
 
42520
42615
  // src/commands/_shared/execute-fill-gaps.ts
42521
- import { existsSync as existsSync28 } from "node:fs";
42522
- import { readFile as readFile25, writeFile as writeFile34, mkdir as mkdir17, rename as renameFs } from "node:fs/promises";
42616
+ import { existsSync as existsSync29 } from "node:fs";
42617
+ import { readFile as readFile26, writeFile as writeFile34, mkdir as mkdir17, rename as renameFs } from "node:fs/promises";
42523
42618
  import { resolve as resolve36, dirname as dirname12 } from "node:path";
42524
42619
  function detectVideoGaps(videoClips, totalDuration) {
42525
42620
  const gaps = [];
@@ -42576,7 +42671,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
42576
42671
  }
42577
42672
  async function uploadFrameToImgbb(framePath, imgbbApiKey) {
42578
42673
  try {
42579
- const frameBuffer = await readFile25(framePath);
42674
+ const frameBuffer = await readFile26(framePath);
42580
42675
  const frameBase64 = frameBuffer.toString("base64");
42581
42676
  const formData = new FormData();
42582
42677
  formData.append("key", imgbbApiKey);
@@ -42601,14 +42696,14 @@ async function executeFillGaps(options) {
42601
42696
  try {
42602
42697
  onProgress("Loading timeline...");
42603
42698
  const filePath = await resolveTimelineFile(options.projectPath);
42604
- if (!existsSync28(filePath)) {
42699
+ if (!existsSync29(filePath)) {
42605
42700
  return {
42606
42701
  success: false,
42607
42702
  error: `Timeline file not found: ${filePath}`,
42608
42703
  humanLines
42609
42704
  };
42610
42705
  }
42611
- const content = await readFile25(filePath, "utf-8");
42706
+ const content = await readFile26(filePath, "utf-8");
42612
42707
  const data = JSON.parse(content);
42613
42708
  const project = Project.fromJSON(data);
42614
42709
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -42736,7 +42831,7 @@ async function executeFillGaps(options) {
42736
42831
  }
42737
42832
  const projectDir = dirname12(filePath);
42738
42833
  const footageDir = options.dir ? resolve36(process.cwd(), options.dir) : resolve36(projectDir, "footage");
42739
- if (!existsSync28(footageDir)) {
42834
+ if (!existsSync29(footageDir)) {
42740
42835
  await mkdir17(footageDir, { recursive: true });
42741
42836
  }
42742
42837
  humanLines.push("Generating AI Videos");
@@ -43050,15 +43145,15 @@ var init_ai_fill_gaps = __esm({
43050
43145
  });
43051
43146
 
43052
43147
  // src/pipeline/renderers/chrome.ts
43053
- import { existsSync as existsSync29 } from "node:fs";
43148
+ import { existsSync as existsSync30 } from "node:fs";
43054
43149
  import { homedir as homedir5 } from "node:os";
43055
- import { join as join16 } from "node:path";
43150
+ import { join as join17 } from "node:path";
43056
43151
  function findChrome() {
43057
43152
  const candidates = [
43058
43153
  process.env.HYPERFRAMES_CHROME_PATH,
43059
43154
  process.env.CHROME_PATH,
43060
43155
  // puppeteer auto-downloaded headless shell
43061
- join16(
43156
+ join17(
43062
43157
  homedir5(),
43063
43158
  ".cache",
43064
43159
  "puppeteer",
@@ -43076,7 +43171,7 @@ function findChrome() {
43076
43171
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
43077
43172
  ];
43078
43173
  for (const c of candidates) {
43079
- if (c && existsSync29(c)) return c;
43174
+ if (c && existsSync30(c)) return c;
43080
43175
  }
43081
43176
  return void 0;
43082
43177
  }
@@ -43322,7 +43417,7 @@ __export(project_builder_exports, {
43322
43417
  resolveSourceUrl: () => resolveSourceUrl
43323
43418
  });
43324
43419
  import { mkdtemp, mkdir as mkdir18, copyFile as copyFile4, writeFile as writeFile35, rm as rm2, readdir as readdir3 } from "node:fs/promises";
43325
- import { existsSync as existsSync30 } from "node:fs";
43420
+ import { existsSync as existsSync31 } from "node:fs";
43326
43421
  import { tmpdir as tmpdir2 } from "node:os";
43327
43422
  import { createRequire } from "node:module";
43328
43423
  import * as path from "node:path";
@@ -43334,7 +43429,7 @@ async function buildTempProject(state, projectFileDir) {
43334
43429
  for (const source of state.sources) {
43335
43430
  if (copied.has(source.id)) continue;
43336
43431
  const resolved = resolveSourceUrl(source.url, projectFileDir);
43337
- if (!existsSync30(resolved)) {
43432
+ if (!existsSync31(resolved)) {
43338
43433
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
43339
43434
  }
43340
43435
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -43471,7 +43566,7 @@ __export(motion_overlay_exports, {
43471
43566
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
43472
43567
  });
43473
43568
  import { mkdir as mkdir19 } from "node:fs/promises";
43474
- import { existsSync as existsSync31 } from "node:fs";
43569
+ import { existsSync as existsSync32 } from "node:fs";
43475
43570
  import { basename as basename9, dirname as dirname14, extname as extname6, resolve as resolve38 } from "node:path";
43476
43571
  function normalizeUnderstand2(value) {
43477
43572
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
@@ -43602,11 +43697,11 @@ async function executeMotionOverlay(options) {
43602
43697
  const validationError = assertValidRequest(options);
43603
43698
  if (validationError) return { success: false, error: validationError };
43604
43699
  const absVideo = resolve38(process.cwd(), options.videoPath);
43605
- if (!existsSync31(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
43700
+ if (!existsSync32(absVideo)) return { success: false, error: `Video file not found: ${absVideo}` };
43606
43701
  const outputPath = options.output ? resolve38(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
43607
43702
  if (options.asset) {
43608
43703
  const absAsset = resolve38(process.cwd(), options.asset);
43609
- if (!existsSync31(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
43704
+ if (!existsSync32(absAsset)) return { success: false, error: `Lottie asset not found: ${absAsset}` };
43610
43705
  const [videoDuration, size] = await Promise.all([
43611
43706
  ffprobeDuration(absVideo),
43612
43707
  ffprobeVideoSize(absVideo)
@@ -43773,7 +43868,7 @@ __export(edit_cmd_exports, {
43773
43868
  executeUpscale: () => executeUpscale
43774
43869
  });
43775
43870
  import { resolve as resolve39, dirname as dirname15 } from "node:path";
43776
- import { readFile as readFile26, writeFile as writeFile36, mkdir as mkdir20 } from "node:fs/promises";
43871
+ import { readFile as readFile27, writeFile as writeFile36, mkdir as mkdir20 } from "node:fs/promises";
43777
43872
  async function executeGrade(options) {
43778
43873
  const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
43779
43874
  try {
@@ -43814,7 +43909,7 @@ async function executeSpeedRamp(options) {
43814
43909
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
43815
43910
  const whisper = new WhisperProvider();
43816
43911
  await whisper.initialize({ apiKey: openaiKey });
43817
- const audioBuffer = await readFile26(tempAudio);
43912
+ const audioBuffer = await readFile27(tempAudio);
43818
43913
  const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
43819
43914
  try {
43820
43915
  const { unlink: unlink5 } = await import("node:fs/promises");
@@ -44208,7 +44303,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
44208
44303
  spinner2.text = "Transcribing audio...";
44209
44304
  const whisper = new WhisperProvider();
44210
44305
  await whisper.initialize({ apiKey: openaiApiKey });
44211
- const audioBuffer = await readFile26(tempAudio);
44306
+ const audioBuffer = await readFile27(tempAudio);
44212
44307
  const audioBlob = new Blob([audioBuffer]);
44213
44308
  const transcript = await whisper.transcribe(audioBlob, options.language);
44214
44309
  if (!transcript.segments || transcript.segments.length === 0) {
@@ -44341,7 +44436,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
44341
44436
  for (let i = 1; i <= numKeyframes && i <= 30; i++) {
44342
44437
  const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
44343
44438
  try {
44344
- const frameBuffer = await readFile26(framePath);
44439
+ const frameBuffer = await readFile27(framePath);
44345
44440
  const frameBase64 = frameBuffer.toString("base64");
44346
44441
  const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
44347
44442
  focusMode: options.focus,
@@ -44470,7 +44565,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
44470
44565
  const imageBuffers = [];
44471
44566
  for (const imagePath of imagePaths) {
44472
44567
  const absPath = resolve39(process.cwd(), imagePath);
44473
- const buffer = await readFile26(absPath);
44568
+ const buffer = await readFile27(absPath);
44474
44569
  imageBuffers.push(buffer);
44475
44570
  }
44476
44571
  let result;
@@ -44746,9 +44841,9 @@ __export(ai_analyze_exports, {
44746
44841
  executeGeminiVideo: () => executeGeminiVideo,
44747
44842
  registerAnalyzeCommands: () => registerAnalyzeCommands
44748
44843
  });
44749
- import { readFile as readFile27 } from "node:fs/promises";
44844
+ import { readFile as readFile28 } from "node:fs/promises";
44750
44845
  import { extname as extname7, resolve as resolve40 } from "node:path";
44751
- import { existsSync as existsSync32 } from "node:fs";
44846
+ import { existsSync as existsSync33 } from "node:fs";
44752
44847
  async function executeGeminiVideo(options) {
44753
44848
  try {
44754
44849
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -44762,10 +44857,10 @@ async function executeGeminiVideo(options) {
44762
44857
  videoData = options.source;
44763
44858
  } else {
44764
44859
  const absPath = resolve40(process.cwd(), options.source);
44765
- if (!existsSync32(absPath)) {
44860
+ if (!existsSync33(absPath)) {
44766
44861
  return { success: false, error: `File not found: ${absPath}` };
44767
44862
  }
44768
- videoData = await readFile27(absPath);
44863
+ videoData = await readFile28(absPath);
44769
44864
  }
44770
44865
  const gemini = new GeminiProvider();
44771
44866
  await gemini.initialize({ apiKey });
@@ -44830,10 +44925,10 @@ async function executeAnalyze(options) {
44830
44925
  imageBuffer = Buffer.from(await response.arrayBuffer());
44831
44926
  } else {
44832
44927
  const absPath = resolve40(process.cwd(), source);
44833
- if (!existsSync32(absPath)) {
44928
+ if (!existsSync33(absPath)) {
44834
44929
  return { success: false, error: `File not found: ${absPath}` };
44835
44930
  }
44836
- imageBuffer = await readFile27(absPath);
44931
+ imageBuffer = await readFile28(absPath);
44837
44932
  }
44838
44933
  const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
44839
44934
  model: modelId,
@@ -44865,10 +44960,10 @@ async function executeAnalyze(options) {
44865
44960
  videoData = Buffer.from(await response.arrayBuffer());
44866
44961
  } else {
44867
44962
  const absPath = resolve40(process.cwd(), source);
44868
- if (!existsSync32(absPath)) {
44963
+ if (!existsSync33(absPath)) {
44869
44964
  return { success: false, error: `File not found: ${absPath}` };
44870
44965
  }
44871
- videoData = await readFile27(absPath);
44966
+ videoData = await readFile28(absPath);
44872
44967
  }
44873
44968
  const result = await gemini.analyzeVideo(videoData, options.prompt, {
44874
44969
  model: modelId,
@@ -45002,9 +45097,9 @@ __export(ai_review_exports, {
45002
45097
  executeReview: () => executeReview,
45003
45098
  registerReviewCommand: () => registerReviewCommand
45004
45099
  });
45005
- import { readFile as readFile28, rename as rename2 } from "node:fs/promises";
45100
+ import { readFile as readFile29, rename as rename2 } from "node:fs/promises";
45006
45101
  import { resolve as resolve41 } from "node:path";
45007
- import { existsSync as existsSync33 } from "node:fs";
45102
+ import { existsSync as existsSync34 } from "node:fs";
45008
45103
  function parseReviewFeedback(response) {
45009
45104
  let cleaned = response.trim();
45010
45105
  if (cleaned.startsWith("```json")) {
@@ -45036,7 +45131,7 @@ async function executeReview(options) {
45036
45131
  model = "flash"
45037
45132
  } = options;
45038
45133
  const absVideoPath = resolve41(process.cwd(), videoPath);
45039
- if (!existsSync33(absVideoPath)) {
45134
+ if (!existsSync34(absVideoPath)) {
45040
45135
  return { success: false, error: `Video not found: ${absVideoPath}` };
45041
45136
  }
45042
45137
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -45049,8 +45144,8 @@ async function executeReview(options) {
45049
45144
  let storyboardContext = "";
45050
45145
  if (storyboardPath2) {
45051
45146
  const absStoryboardPath = resolve41(process.cwd(), storyboardPath2);
45052
- if (existsSync33(absStoryboardPath)) {
45053
- const content = await readFile28(absStoryboardPath, "utf-8");
45147
+ if (existsSync34(absStoryboardPath)) {
45148
+ const content = await readFile29(absStoryboardPath, "utf-8");
45054
45149
  storyboardContext = `
45055
45150
 
45056
45151
  Original storyboard for reference:
@@ -45092,7 +45187,7 @@ ${projectContext}` : "";
45092
45187
  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}`;
45093
45188
  const gemini = new GeminiProvider();
45094
45189
  await gemini.initialize({ apiKey });
45095
- const videoData = await readFile28(absVideoPath);
45190
+ const videoData = await readFile29(absVideoPath);
45096
45191
  const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
45097
45192
  model: modelId
45098
45193
  });
@@ -45147,7 +45242,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
45147
45242
  }
45148
45243
  }
45149
45244
  if (verify && result.outputPath) {
45150
- const verifyVideoData = await readFile28(result.outputPath);
45245
+ const verifyVideoData = await readFile29(result.outputPath);
45151
45246
  const verifyResult = await gemini.analyzeVideo(
45152
45247
  verifyVideoData,
45153
45248
  'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
@@ -45275,9 +45370,9 @@ var init_ai_review = __esm({
45275
45370
  });
45276
45371
 
45277
45372
  // src/commands/_shared/scene-inspect.ts
45278
- import { existsSync as existsSync34 } from "node:fs";
45279
- import { readdir as readdir4, readFile as readFile29, stat as stat5 } from "node:fs/promises";
45280
- import { basename as basename10, isAbsolute as isAbsolute3, join as join18, relative as relative8, resolve as resolve42 } from "node:path";
45373
+ import { existsSync as existsSync35 } from "node:fs";
45374
+ import { readdir as readdir4, readFile as readFile30, stat as stat5 } from "node:fs/promises";
45375
+ import { basename as basename10, isAbsolute as isAbsolute3, join as join19, relative as relative8, resolve as resolve42 } from "node:path";
45281
45376
  async function inspectProject(opts) {
45282
45377
  const projectDir = resolve42(opts.projectDir);
45283
45378
  const issues = [];
@@ -45291,7 +45386,7 @@ async function inspectProject(opts) {
45291
45386
  assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
45292
45387
  rootSync: { ok: null, issueCount: 0, issues: [] }
45293
45388
  };
45294
- if (!existsSync34(projectDir) || !await isDirectory(projectDir)) {
45389
+ if (!existsSync35(projectDir) || !await isDirectory(projectDir)) {
45295
45390
  issues.push({
45296
45391
  severity: "error",
45297
45392
  code: "PROJECT_NOT_FOUND",
@@ -45306,15 +45401,15 @@ async function inspectProject(opts) {
45306
45401
  );
45307
45402
  }
45308
45403
  const coreFiles = {
45309
- storyboard: join18(projectDir, "STORYBOARD.md"),
45310
- design: join18(projectDir, "DESIGN.md"),
45311
- config: join18(projectDir, "vibe.config.json"),
45312
- legacyConfig: join18(projectDir, "vibe.project.yaml"),
45313
- buildReport: join18(projectDir, "build-report.json"),
45314
- root: join18(projectDir, "index.html")
45404
+ storyboard: join19(projectDir, "STORYBOARD.md"),
45405
+ design: join19(projectDir, "DESIGN.md"),
45406
+ config: join19(projectDir, "vibe.config.json"),
45407
+ legacyConfig: join19(projectDir, "vibe.project.yaml"),
45408
+ buildReport: join19(projectDir, "build-report.json"),
45409
+ root: join19(projectDir, "index.html")
45315
45410
  };
45316
45411
  for (const [name, path2] of Object.entries(coreFiles)) {
45317
- checks.files[name] = existsSync34(path2);
45412
+ checks.files[name] = existsSync35(path2);
45318
45413
  }
45319
45414
  let beatIds = [];
45320
45415
  let inspectedBeatIds = [];
@@ -45328,7 +45423,7 @@ async function inspectProject(opts) {
45328
45423
  });
45329
45424
  retryWith.push(`vibe init ${projectDir} --from "<brief>" --json`);
45330
45425
  } else {
45331
- const storyboard = await readFile29(coreFiles.storyboard, "utf-8");
45426
+ const storyboard = await readFile30(coreFiles.storyboard, "utf-8");
45332
45427
  const validation = validateStoryboardMarkdown(storyboard);
45333
45428
  storyboardBeats = validation.beats;
45334
45429
  beatIds = validation.beats.map((beat) => beat.id);
@@ -45370,7 +45465,7 @@ async function inspectProject(opts) {
45370
45465
  suggestedFix: "Create DESIGN.md or rerun `vibe init --from` in a new project."
45371
45466
  });
45372
45467
  } else {
45373
- const design = await readFile29(coreFiles.design, "utf-8");
45468
+ const design = await readFile30(coreFiles.design, "utf-8");
45374
45469
  for (const issue of designPlaceholderIssues(design)) {
45375
45470
  issues.push(issue);
45376
45471
  }
@@ -45395,20 +45490,20 @@ async function inspectProject(opts) {
45395
45490
  });
45396
45491
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
45397
45492
  }
45398
- const compositionsDir = join18(projectDir, "compositions");
45399
- const existingComps = existsSync34(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
45493
+ const compositionsDir = join19(projectDir, "compositions");
45494
+ const existingComps = existsSync35(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
45400
45495
  checks.compositions.expected = inspectedBeatIds.length;
45401
45496
  for (const beatId of inspectedBeatIds) {
45402
45497
  const file = `scene-${beatId}.html`;
45403
45498
  if (existingComps.has(file)) {
45404
45499
  checks.compositions.found++;
45405
45500
  } else {
45406
- checks.compositions.missing.push(join18("compositions", file));
45501
+ checks.compositions.missing.push(join19("compositions", file));
45407
45502
  issues.push({
45408
45503
  severity: "error",
45409
45504
  code: "MISSING_COMPOSITION",
45410
45505
  message: `Composition for beat "${beatId}" is missing.`,
45411
- file: join18("compositions", file),
45506
+ file: join19("compositions", file),
45412
45507
  scene: beatId,
45413
45508
  beatId,
45414
45509
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -45470,7 +45565,7 @@ async function inspectProject(opts) {
45470
45565
  try {
45471
45566
  const lint = await runProjectLint({ projectDir });
45472
45567
  const lintFiles = opts.beatId ? lint.files.filter(
45473
- (file) => file.file === "index.html" || file.file === join18("compositions", `scene-${opts.beatId}.html`)
45568
+ (file) => file.file === "index.html" || file.file === join19("compositions", `scene-${opts.beatId}.html`)
45474
45569
  ) : lint.files;
45475
45570
  const lintCounts = countLintFindings(lintFiles);
45476
45571
  checks.lint = {
@@ -45616,7 +45711,7 @@ function projectSourceReports(checks) {
45616
45711
  async function inspectBuildReport(projectDir, reportPath, beatId, checks, issues, retryWith, storyboardBeats) {
45617
45712
  checks.buildReport.exists = true;
45618
45713
  try {
45619
- const report = JSON.parse(await readFile29(reportPath, "utf-8"));
45714
+ const report = JSON.parse(await readFile30(reportPath, "utf-8"));
45620
45715
  if (typeof report.outputPath === "string") checks.buildReport.outputPath = report.outputPath;
45621
45716
  const reportBeats = report.beats ?? [];
45622
45717
  const selectedReportBeats = reportBeats.filter((item) => !beatId || item.id === beatId);
@@ -45753,7 +45848,7 @@ function inspectReportedAsset(opts) {
45753
45848
  opts.checks.assets.checked++;
45754
45849
  if (isExternalRef(opts.value)) return;
45755
45850
  const abs = isAbsolute3(opts.value) ? opts.value : resolve42(opts.projectDir, opts.value);
45756
- if (existsSync34(abs)) return;
45851
+ if (existsSync35(abs)) return;
45757
45852
  opts.checks.assets.missing.push(opts.value);
45758
45853
  opts.issues.push({
45759
45854
  severity: "warning",
@@ -45852,9 +45947,9 @@ var init_scene_inspect = __esm({
45852
45947
  });
45853
45948
 
45854
45949
  // src/commands/_shared/render-inspect.ts
45855
- import { existsSync as existsSync35 } from "node:fs";
45856
- import { readdir as readdir5, readFile as readFile30, stat as stat6 } from "node:fs/promises";
45857
- import { basename as basename11, isAbsolute as isAbsolute4, join as join19, relative as relative9, resolve as resolve43 } from "node:path";
45950
+ import { existsSync as existsSync36 } from "node:fs";
45951
+ import { readdir as readdir5, readFile as readFile31, stat as stat6 } from "node:fs/promises";
45952
+ import { basename as basename11, isAbsolute as isAbsolute4, join as join20, relative as relative9, resolve as resolve43 } from "node:path";
45858
45953
  function parseBlackdetectOutput(output) {
45859
45954
  const ranges = [];
45860
45955
  const regex2 = /black_start:(-?\d+(?:\.\d+)?)\s+black_end:(-?\d+(?:\.\d+)?)\s+black_duration:(-?\d+(?:\.\d+)?)/g;
@@ -46340,11 +46435,11 @@ async function inspectRender(opts) {
46340
46435
  }
46341
46436
  async function renderExpectsAudio(projectDir, beatId) {
46342
46437
  const root = resolve43(projectDir);
46343
- const rootHtml = await readTextIfExists(join19(root, "index.html"));
46438
+ const rootHtml = await readTextIfExists(join20(root, "index.html"));
46344
46439
  if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
46345
- const buildReport = await readJsonIfExists(join19(root, "build-report.json"));
46440
+ const buildReport = await readJsonIfExists(join20(root, "build-report.json"));
46346
46441
  if (buildReportExpectsAudio(buildReport, beatId)) return true;
46347
- const storyboard = await readTextIfExists(join19(root, "STORYBOARD.md"));
46442
+ const storyboard = await readTextIfExists(join20(root, "STORYBOARD.md"));
46348
46443
  if (!storyboard) return false;
46349
46444
  try {
46350
46445
  const parsed = parseStoryboard(storyboard);
@@ -46355,7 +46450,7 @@ async function renderExpectsAudio(projectDir, beatId) {
46355
46450
  `assets/narration-${beat.id}.wav`,
46356
46451
  `assets/music-${beat.id}.mp3`,
46357
46452
  `assets/music-${beat.id}.wav`
46358
- ].some((asset) => existsSync35(join19(root, asset)))
46453
+ ].some((asset) => existsSync36(join20(root, asset)))
46359
46454
  );
46360
46455
  } catch {
46361
46456
  return false;
@@ -46463,8 +46558,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
46463
46558
  }
46464
46559
  function renderSourceReports(projectDir, videoPath, checks) {
46465
46560
  const reports = [];
46466
- if (existsSync35(join19(projectDir, "build-report.json"))) reports.push("build-report.json");
46467
- if (existsSync35(join19(projectDir, "render-report.json"))) reports.push("render-report.json");
46561
+ if (existsSync36(join20(projectDir, "build-report.json"))) reports.push("build-report.json");
46562
+ if (existsSync36(join20(projectDir, "render-report.json"))) reports.push("render-report.json");
46468
46563
  if (videoPath) reports.push(displayPath(videoPath));
46469
46564
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
46470
46565
  reports.push("ffprobe");
@@ -46477,31 +46572,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
46477
46572
  async function resolveRenderVideoPath(projectDir, explicit, beatId) {
46478
46573
  if (explicit) {
46479
46574
  const candidate = resolve43(process.cwd(), explicit);
46480
- return existsSync35(candidate) ? candidate : null;
46575
+ return existsSync36(candidate) ? candidate : null;
46481
46576
  }
46482
- const renderReportPath = join19(projectDir, "render-report.json");
46483
- if (existsSync35(renderReportPath)) {
46577
+ const renderReportPath = join20(projectDir, "render-report.json");
46578
+ if (existsSync36(renderReportPath)) {
46484
46579
  try {
46485
- const report = JSON.parse(await readFile30(renderReportPath, "utf-8"));
46580
+ const report = JSON.parse(await readFile31(renderReportPath, "utf-8"));
46486
46581
  const reportBeat = typeof report.beat === "string" ? report.beat : null;
46487
46582
  if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
46488
46583
  const candidate = resolveReportedPath(projectDir, report.outputPath);
46489
- if (candidate && existsSync35(candidate)) return candidate;
46584
+ if (candidate && existsSync36(candidate)) return candidate;
46490
46585
  }
46491
46586
  } catch {
46492
46587
  }
46493
46588
  }
46494
- const reportPath = join19(projectDir, "build-report.json");
46495
- if (!beatId && existsSync35(reportPath)) {
46589
+ const reportPath = join20(projectDir, "build-report.json");
46590
+ if (!beatId && existsSync36(reportPath)) {
46496
46591
  try {
46497
- const report = JSON.parse(await readFile30(reportPath, "utf-8"));
46592
+ const report = JSON.parse(await readFile31(reportPath, "utf-8"));
46498
46593
  const candidate = resolveReportedPath(projectDir, report.outputPath);
46499
- if (candidate && existsSync35(candidate)) return candidate;
46594
+ if (candidate && existsSync36(candidate)) return candidate;
46500
46595
  } catch {
46501
46596
  }
46502
46597
  }
46503
- const rendersDir = join19(projectDir, "renders");
46504
- if (!existsSync35(rendersDir)) return null;
46598
+ const rendersDir = join20(projectDir, "renders");
46599
+ if (!existsSync36(rendersDir)) return null;
46505
46600
  const entries = await readdir5(rendersDir, { withFileTypes: true });
46506
46601
  const candidates = [];
46507
46602
  for (const entry of entries) {
@@ -46509,7 +46604,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
46509
46604
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
46510
46605
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
46511
46606
  if (beatId && !entry.name.includes(sanitizeFileSegment(beatId))) continue;
46512
- const full = join19(rendersDir, entry.name);
46607
+ const full = join20(rendersDir, entry.name);
46513
46608
  const info = await stat6(full);
46514
46609
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
46515
46610
  }
@@ -46527,14 +46622,14 @@ async function runAiRenderReview(projectDir, videoPath, model) {
46527
46622
  });
46528
46623
  }
46529
46624
  function resolveStoryboardPath(projectDir) {
46530
- const storyboardPath2 = join19(projectDir, "STORYBOARD.md");
46531
- return existsSync35(storyboardPath2) ? storyboardPath2 : null;
46625
+ const storyboardPath2 = join20(projectDir, "STORYBOARD.md");
46626
+ return existsSync36(storyboardPath2) ? storyboardPath2 : null;
46532
46627
  }
46533
46628
  async function expectedDurationFromBuildReport(projectDir, beatId) {
46534
- const reportPath = join19(projectDir, "build-report.json");
46535
- if (!existsSync35(reportPath)) return void 0;
46629
+ const reportPath = join20(projectDir, "build-report.json");
46630
+ if (!existsSync36(reportPath)) return void 0;
46536
46631
  try {
46537
- const report = JSON.parse(await readFile30(reportPath, "utf-8"));
46632
+ const report = JSON.parse(await readFile31(reportPath, "utf-8"));
46538
46633
  const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
46539
46634
  const durations = beats.map(
46540
46635
  (beat) => parseOptionalNumber(beat.sceneDurationSec) ?? parseOptionalNumber(beat.narration?.sceneDurationSec)
@@ -46546,10 +46641,10 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
46546
46641
  }
46547
46642
  }
46548
46643
  async function beatTimingsFromBuildReport(projectDir, beatId) {
46549
- const reportPath = join19(projectDir, "build-report.json");
46550
- if (!existsSync35(reportPath)) return [];
46644
+ const reportPath = join20(projectDir, "build-report.json");
46645
+ if (!existsSync36(reportPath)) return [];
46551
46646
  try {
46552
- const report = JSON.parse(await readFile30(reportPath, "utf-8"));
46647
+ const report = JSON.parse(await readFile31(reportPath, "utf-8"));
46553
46648
  const source = report.beats ?? [];
46554
46649
  const selected = beatId ? source.filter((beat) => beat.id === beatId) : source;
46555
46650
  let cursor = 0;
@@ -46575,17 +46670,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
46575
46670
  }
46576
46671
  async function buildRenderReviewProjectContext(projectDir) {
46577
46672
  const parts = [];
46578
- const storyboard = await readTextIfExists(join19(projectDir, "STORYBOARD.md"));
46673
+ const storyboard = await readTextIfExists(join20(projectDir, "STORYBOARD.md"));
46579
46674
  if (storyboard) {
46580
46675
  parts.push(`STORYBOARD.md:
46581
46676
  ${truncateText(storyboard, 4e3)}`);
46582
46677
  parts.push(`Storyboard beat cue summary:
46583
46678
  ${storyboardBeatCueSummary(storyboard)}`);
46584
46679
  }
46585
- const design = await readTextIfExists(join19(projectDir, "DESIGN.md"));
46680
+ const design = await readTextIfExists(join20(projectDir, "DESIGN.md"));
46586
46681
  if (design) parts.push(`DESIGN.md:
46587
46682
  ${truncateText(design, 4e3)}`);
46588
- const buildReport = await readTextIfExists(join19(projectDir, "build-report.json"));
46683
+ const buildReport = await readTextIfExists(join20(projectDir, "build-report.json"));
46589
46684
  if (buildReport) parts.push(`build-report.json:
46590
46685
  ${truncateText(buildReport, 6e3)}`);
46591
46686
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -46613,9 +46708,9 @@ function storyboardBeatCueSummary(storyboard) {
46613
46708
  }
46614
46709
  }
46615
46710
  async function readTextIfExists(path2) {
46616
- if (!existsSync35(path2)) return null;
46711
+ if (!existsSync36(path2)) return null;
46617
46712
  try {
46618
- return await readFile30(path2, "utf-8");
46713
+ return await readFile31(path2, "utf-8");
46619
46714
  } catch {
46620
46715
  return null;
46621
46716
  }
@@ -46909,9 +47004,9 @@ var init_video_providers = __esm({
46909
47004
  });
46910
47005
 
46911
47006
  // src/commands/ai-script-pipeline.ts
46912
- import { readFile as readFile33, writeFile as writeFile39, unlink as unlink2, rename as rename3 } from "node:fs/promises";
47007
+ import { readFile as readFile34, writeFile as writeFile39, unlink as unlink2, rename as rename3 } from "node:fs/promises";
46913
47008
  import { resolve as resolve46, extname as extname9 } from "node:path";
46914
- import { existsSync as existsSync37 } from "node:fs";
47009
+ import { existsSync as existsSync38 } from "node:fs";
46915
47010
  async function executeRegenerateScene(options) {
46916
47011
  const result = {
46917
47012
  success: false,
@@ -46920,16 +47015,16 @@ async function executeRegenerateScene(options) {
46920
47015
  };
46921
47016
  try {
46922
47017
  const outputDir = resolve46(process.cwd(), options.projectDir);
46923
- if (!existsSync37(outputDir)) {
47018
+ if (!existsSync38(outputDir)) {
46924
47019
  return { ...result, error: `Project directory not found: ${outputDir}` };
46925
47020
  }
46926
47021
  const yamlPath = resolve46(outputDir, "storyboard.yaml");
46927
47022
  const jsonPath = resolve46(outputDir, "storyboard.json");
46928
- const storyboardPath2 = existsSync37(yamlPath) ? yamlPath : existsSync37(jsonPath) ? jsonPath : null;
47023
+ const storyboardPath2 = existsSync38(yamlPath) ? yamlPath : existsSync38(jsonPath) ? jsonPath : null;
46929
47024
  if (!storyboardPath2) {
46930
47025
  return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
46931
47026
  }
46932
- const storyboardContent = await readFile33(storyboardPath2, "utf-8");
47027
+ const storyboardContent = await readFile34(storyboardPath2, "utf-8");
46933
47028
  const segments = storyboardPath2.endsWith(".yaml") ? (0, import_yaml6.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
46934
47029
  for (const sceneNum of options.scenes) {
46935
47030
  if (sceneNum < 1 || sceneNum > segments.length) {
@@ -47018,15 +47113,15 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
47018
47113
  const refSceneNum = options.referenceScene;
47019
47114
  if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
47020
47115
  const refImagePath = resolve46(outputDir, `scene-${refSceneNum}.png`);
47021
- if (existsSync37(refImagePath)) {
47022
- referenceImageBuffer = await readFile33(refImagePath);
47116
+ if (existsSync38(refImagePath)) {
47117
+ referenceImageBuffer = await readFile34(refImagePath);
47023
47118
  }
47024
47119
  } else if (!refSceneNum) {
47025
47120
  for (let i = 1; i <= segments.length; i++) {
47026
47121
  if (i !== sceneNum) {
47027
47122
  const otherImagePath = resolve46(outputDir, `scene-${i}.png`);
47028
- if (existsSync37(otherImagePath)) {
47029
- referenceImageBuffer = await readFile33(otherImagePath);
47123
+ if (existsSync38(otherImagePath)) {
47124
+ referenceImageBuffer = await readFile34(otherImagePath);
47030
47125
  break;
47031
47126
  }
47032
47127
  }
@@ -47114,11 +47209,11 @@ Generate the single-person scene image now.`;
47114
47209
  }
47115
47210
  if (regenerateVideo && videoApiKey) {
47116
47211
  options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
47117
- if (!existsSync37(imagePath)) {
47212
+ if (!existsSync38(imagePath)) {
47118
47213
  result.failedScenes.push(sceneNum);
47119
47214
  continue;
47120
47215
  }
47121
- const imageBuffer = await readFile33(imagePath);
47216
+ const imageBuffer = await readFile34(imagePath);
47122
47217
  const videoDuration = segment.duration > 5 ? 10 : 5;
47123
47218
  const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
47124
47219
  if (options.generator === "grok") {
@@ -47340,9 +47435,9 @@ var init_ai_script_pipeline = __esm({
47340
47435
  });
47341
47436
 
47342
47437
  // src/commands/ai-highlights.ts
47343
- import { readFile as readFile35, writeFile as writeFile41, mkdir as mkdir21 } from "node:fs/promises";
47438
+ import { readFile as readFile36, writeFile as writeFile41, mkdir as mkdir21 } from "node:fs/promises";
47344
47439
  import { resolve as resolve48, dirname as dirname17, basename as basename13, extname as extname10 } from "node:path";
47345
- import { existsSync as existsSync39 } from "node:fs";
47440
+ import { existsSync as existsSync40 } from "node:fs";
47346
47441
  function filterHighlights(highlights, options) {
47347
47442
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
47348
47443
  filtered.sort((a, b) => b.confidence - a.confidence);
@@ -47382,7 +47477,7 @@ function truncate(text, maxLength) {
47382
47477
  async function executeHighlights(options) {
47383
47478
  try {
47384
47479
  const absPath = resolve48(process.cwd(), options.media);
47385
- if (!existsSync39(absPath)) {
47480
+ if (!existsSync40(absPath)) {
47386
47481
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
47387
47482
  }
47388
47483
  const ext = extname10(absPath).toLowerCase();
@@ -47401,7 +47496,7 @@ async function executeHighlights(options) {
47401
47496
  sourceDuration = await ffprobeDuration(absPath);
47402
47497
  const gemini = new GeminiProvider();
47403
47498
  await gemini.initialize({ apiKey: geminiApiKey });
47404
- const videoBuffer = await readFile35(absPath);
47499
+ const videoBuffer = await readFile36(absPath);
47405
47500
  const criteriaText = options.criteria === "all" || !options.criteria ? "emotional, informative, and funny moments" : `${options.criteria} moments`;
47406
47501
  const durationText = targetDuration ? `Target a total highlight duration of ${targetDuration} seconds.` : "";
47407
47502
  const countText = maxCount ? `Find up to ${maxCount} highlights.` : "";
@@ -47495,10 +47590,10 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
47495
47590
  }
47496
47591
  const whisper = new WhisperProvider();
47497
47592
  await whisper.initialize({ apiKey: openaiApiKey });
47498
- const audioBuffer = await readFile35(audioPath);
47593
+ const audioBuffer = await readFile36(audioPath);
47499
47594
  const audioBlob = new Blob([audioBuffer]);
47500
47595
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
47501
- if (tempAudioPath && existsSync39(tempAudioPath)) {
47596
+ if (tempAudioPath && existsSync40(tempAudioPath)) {
47502
47597
  const { unlink: unlinkFile } = await import("node:fs/promises");
47503
47598
  await unlinkFile(tempAudioPath).catch(() => {
47504
47599
  });
@@ -47585,7 +47680,7 @@ async function executeAutoShorts(options) {
47585
47680
  return { success: false, shorts: [], error: "FFmpeg not found" };
47586
47681
  }
47587
47682
  const absPath = resolve48(process.cwd(), options.video);
47588
- if (!existsSync39(absPath)) {
47683
+ if (!existsSync40(absPath)) {
47589
47684
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
47590
47685
  }
47591
47686
  const targetDuration = options.duration ?? 60;
@@ -47598,7 +47693,7 @@ async function executeAutoShorts(options) {
47598
47693
  }
47599
47694
  const gemini = new GeminiProvider();
47600
47695
  await gemini.initialize({ apiKey: geminiApiKey });
47601
- const videoBuffer = await readFile35(absPath);
47696
+ const videoBuffer = await readFile36(absPath);
47602
47697
  const geminiPrompt = `Analyze this video to find the BEST moments for short-form vertical video content (TikTok, YouTube Shorts, Instagram Reels).
47603
47698
 
47604
47699
  Find ${shortCount * 3} potential clips that are ${targetDuration} seconds or shorter each.
@@ -47675,7 +47770,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
47675
47770
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
47676
47771
  const whisper = new WhisperProvider();
47677
47772
  await whisper.initialize({ apiKey: openaiApiKey });
47678
- const audioBuffer = await readFile35(tempAudio);
47773
+ const audioBuffer = await readFile36(tempAudio);
47679
47774
  const audioBlob = new Blob([audioBuffer]);
47680
47775
  const transcript = await whisper.transcribe(audioBlob, options.language);
47681
47776
  try {
@@ -47713,7 +47808,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
47713
47808
  };
47714
47809
  }
47715
47810
  const outputDir = options.outputDir ? resolve48(process.cwd(), options.outputDir) : dirname17(absPath);
47716
- if (options.outputDir && !existsSync39(outputDir)) {
47811
+ if (options.outputDir && !existsSync40(outputDir)) {
47717
47812
  await mkdir21(outputDir, { recursive: true });
47718
47813
  }
47719
47814
  const result = {
@@ -47796,7 +47891,7 @@ function registerHighlightsCommands(aiCommand) {
47796
47891
  validateOutputPath(options.output);
47797
47892
  }
47798
47893
  const absPath = resolve48(process.cwd(), mediaPath);
47799
- if (!existsSync39(absPath)) {
47894
+ if (!existsSync40(absPath)) {
47800
47895
  exitWithError(notFoundError(absPath));
47801
47896
  }
47802
47897
  if (options.dryRun) {
@@ -47853,7 +47948,7 @@ function registerHighlightsCommands(aiCommand) {
47853
47948
  const geminiSpinner = ora("\u{1F3AC} Analyzing video with Gemini (visual + audio)...").start();
47854
47949
  const gemini = new GeminiProvider();
47855
47950
  await gemini.initialize({ apiKey: geminiApiKey });
47856
- const videoBuffer = await readFile35(absPath);
47951
+ const videoBuffer = await readFile36(absPath);
47857
47952
  const criteriaText = options.criteria === "all" ? "emotional, informative, and funny moments" : `${options.criteria} moments`;
47858
47953
  const durationText = targetDuration ? `Target a total highlight duration of ${targetDuration} seconds.` : "";
47859
47954
  const countText = maxCount ? `Find up to ${maxCount} highlights.` : "";
@@ -47978,12 +48073,12 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
47978
48073
  const transcribeSpinner = ora("\u{1F4DD} Transcribing with Whisper...").start();
47979
48074
  const whisper = new WhisperProvider();
47980
48075
  await whisper.initialize({ apiKey: openaiApiKey });
47981
- const audioBuffer = await readFile35(audioPath);
48076
+ const audioBuffer = await readFile36(audioPath);
47982
48077
  const audioBlob = new Blob([audioBuffer]);
47983
48078
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
47984
48079
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
47985
48080
  transcribeSpinner.fail("Transcription failed");
47986
- if (tempAudioPath && existsSync39(tempAudioPath)) {
48081
+ if (tempAudioPath && existsSync40(tempAudioPath)) {
47987
48082
  const { unlink: unlinkFile } = await import("node:fs/promises");
47988
48083
  await unlinkFile(tempAudioPath).catch(() => {
47989
48084
  });
@@ -47991,7 +48086,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
47991
48086
  exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
47992
48087
  }
47993
48088
  transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
47994
- if (tempAudioPath && existsSync39(tempAudioPath)) {
48089
+ if (tempAudioPath && existsSync40(tempAudioPath)) {
47995
48090
  const { unlink: unlinkFile } = await import("node:fs/promises");
47996
48091
  await unlinkFile(tempAudioPath).catch(() => {
47997
48092
  });
@@ -48097,7 +48192,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
48097
48192
  validateOutputPath(options.output);
48098
48193
  }
48099
48194
  const absPath = resolve48(process.cwd(), videoPath);
48100
- if (!existsSync39(absPath)) {
48195
+ if (!existsSync40(absPath)) {
48101
48196
  exitWithError(notFoundError(absPath));
48102
48197
  }
48103
48198
  if (options.dryRun) {
@@ -48147,7 +48242,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
48147
48242
  const spinner2 = ora("\u{1F3AC} Analyzing video with Gemini (visual + audio)...").start();
48148
48243
  const gemini = new GeminiProvider();
48149
48244
  await gemini.initialize({ apiKey: geminiApiKey });
48150
- const videoBuffer = await readFile35(absPath);
48245
+ const videoBuffer = await readFile36(absPath);
48151
48246
  const geminiPrompt = `Analyze this video to find the BEST moments for short-form vertical video content (TikTok, YouTube Shorts, Instagram Reels).
48152
48247
 
48153
48248
  Find ${shortCount * 3} potential clips that are ${targetDuration} seconds or shorter each.
@@ -48244,7 +48339,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
48244
48339
  spinner2.text = "Transcribing audio...";
48245
48340
  const whisper = new WhisperProvider();
48246
48341
  await whisper.initialize({ apiKey: openaiApiKey });
48247
- const audioBuffer = await readFile35(tempAudio);
48342
+ const audioBuffer = await readFile36(tempAudio);
48248
48343
  const audioBlob = new Blob([audioBuffer]);
48249
48344
  const transcript = await whisper.transcribe(audioBlob, options.language);
48250
48345
  try {
@@ -48290,7 +48385,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
48290
48385
  return;
48291
48386
  }
48292
48387
  const outputDir = options.outputDir ? resolve48(process.cwd(), options.outputDir) : dirname17(absPath);
48293
- if (options.outputDir && !existsSync39(outputDir)) {
48388
+ if (options.outputDir && !existsSync40(outputDir)) {
48294
48389
  await mkdir21(outputDir, { recursive: true });
48295
48390
  }
48296
48391
  for (let i = 0; i < selectedHighlights.length; i++) {
@@ -48307,7 +48402,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
48307
48402
  outputPath = resolve48(outputDir, `${baseName}-short-${i + 1}.mp4`);
48308
48403
  }
48309
48404
  const parentDir = dirname17(outputPath);
48310
- if (!existsSync39(parentDir)) {
48405
+ if (!existsSync40(parentDir)) {
48311
48406
  await mkdir21(parentDir, { recursive: true });
48312
48407
  }
48313
48408
  const { stdout: probeOut } = await execSafe("ffprobe", [
@@ -48382,7 +48477,7 @@ var init_ai_highlights = __esm({
48382
48477
  // src/commands/ai-animated-caption.ts
48383
48478
  import { resolve as resolve49, dirname as dirname18, basename as basename14 } from "node:path";
48384
48479
  import { writeFile as writeFile42, mkdir as mkdir22, rm as rm3 } from "node:fs/promises";
48385
- import { existsSync as existsSync40 } from "node:fs";
48480
+ import { existsSync as existsSync41 } from "node:fs";
48386
48481
  import { tmpdir as tmpdir3 } from "node:os";
48387
48482
  function groupWords(words, options = {}) {
48388
48483
  if (words.length === 0) return [];
@@ -48559,7 +48654,7 @@ async function executeAnimatedCaption(options) {
48559
48654
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
48560
48655
  const absOutputPath = resolve49(process.cwd(), outputPath);
48561
48656
  const outDir = dirname18(absOutputPath);
48562
- if (!existsSync40(outDir)) {
48657
+ if (!existsSync41(outDir)) {
48563
48658
  await mkdir22(outDir, { recursive: true });
48564
48659
  }
48565
48660
  if (tier === "ass") {
@@ -58242,7 +58337,7 @@ __export(detect_exports, {
58242
58337
  executeDetectScenes: () => executeDetectScenes,
58243
58338
  executeDetectSilence: () => executeDetectSilence
58244
58339
  });
58245
- import { readFile as readFile37, writeFile as writeFile44 } from "node:fs/promises";
58340
+ import { readFile as readFile38, writeFile as writeFile44 } from "node:fs/promises";
58246
58341
  import { resolve as resolve53, basename as basename18 } from "node:path";
58247
58342
  async function executeDetectScenes(options) {
58248
58343
  try {
@@ -58486,7 +58581,7 @@ var init_detect = __esm({
58486
58581
  }
58487
58582
  if (options.project) {
58488
58583
  const projectPath = resolve53(process.cwd(), options.project);
58489
- const content = await readFile37(projectPath, "utf-8");
58584
+ const content = await readFile38(projectPath, "utf-8");
58490
58585
  const data = JSON.parse(content);
58491
58586
  const project = Project.fromJSON(data);
58492
58587
  let source = project.getSources().find((s) => s.url === absPath);
@@ -60177,14 +60272,14 @@ var init_adapters = __esm({
60177
60272
  });
60178
60273
 
60179
60274
  // src/commands/_shared/storyboard-revise.ts
60180
- import { existsSync as existsSync44 } from "node:fs";
60181
- import { readFile as readFile41, writeFile as writeFile47 } from "node:fs/promises";
60182
- import { join as join22, resolve as resolve57 } from "node:path";
60275
+ import { existsSync as existsSync45 } from "node:fs";
60276
+ import { readFile as readFile42, writeFile as writeFile47 } from "node:fs/promises";
60277
+ import { join as join23, resolve as resolve57 } from "node:path";
60183
60278
  async function executeStoryboardRevision(opts) {
60184
60279
  const projectDir = resolve57(opts.projectDir);
60185
- const storyboardPath2 = join22(projectDir, "STORYBOARD.md");
60280
+ const storyboardPath2 = join23(projectDir, "STORYBOARD.md");
60186
60281
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
60187
- if (!existsSync44(storyboardPath2)) {
60282
+ if (!existsSync45(storyboardPath2)) {
60188
60283
  return failureResult({
60189
60284
  projectDir,
60190
60285
  storyboardPath: storyboardPath2,
@@ -60194,7 +60289,7 @@ async function executeStoryboardRevision(opts) {
60194
60289
  retryWith: [`vibe init ${projectDir} --from "<brief>" --json`]
60195
60290
  });
60196
60291
  }
60197
- const currentStoryboard = await readFile41(storyboardPath2, "utf-8");
60292
+ const currentStoryboard = await readFile42(storyboardPath2, "utf-8");
60198
60293
  const projectConfig = await readProjectConfig(projectDir);
60199
60294
  const explicitComposer = opts.composer ?? projectConfig.config.providers.composer ?? void 0;
60200
60295
  let provider = explicitComposer ?? "claude";
@@ -60417,18 +60512,18 @@ function diffChangedBeats(before, after) {
60417
60512
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
60418
60513
  }
60419
60514
  async function readRevisionContext(projectDir) {
60420
- const designMd = await readOptionalText(join22(projectDir, "DESIGN.md"));
60421
- const buildReportJson = await readOptionalJsonText(join22(projectDir, "build-report.json"));
60515
+ const designMd = await readOptionalText(join23(projectDir, "DESIGN.md"));
60516
+ const buildReportJson = await readOptionalJsonText(join23(projectDir, "build-report.json"));
60422
60517
  return { designMd, buildReportJson };
60423
60518
  }
60424
60519
  async function readOptionalText(path2) {
60425
- if (!existsSync44(path2)) return null;
60426
- return readFile41(path2, "utf-8");
60520
+ if (!existsSync45(path2)) return null;
60521
+ return readFile42(path2, "utf-8");
60427
60522
  }
60428
60523
  async function readOptionalJsonText(path2) {
60429
- if (!existsSync44(path2)) return null;
60524
+ if (!existsSync45(path2)) return null;
60430
60525
  try {
60431
- const parsed = JSON.parse(await readFile41(path2, "utf-8"));
60526
+ const parsed = JSON.parse(await readFile42(path2, "utf-8"));
60432
60527
  return JSON.stringify(parsed, null, 2);
60433
60528
  } catch {
60434
60529
  return null;
@@ -60476,7 +60571,7 @@ var init_storyboard_revise = __esm({
60476
60571
  });
60477
60572
 
60478
60573
  // src/commands/_shared/build-asset-reference.ts
60479
- import { existsSync as existsSync46 } from "node:fs";
60574
+ import { existsSync as existsSync47 } from "node:fs";
60480
60575
  import { extname as extname13, isAbsolute as isAbsolute5, relative as relative10, resolve as resolve59, sep } from "node:path";
60481
60576
  function resolveTypedAssetReference(projectDir, kind, value) {
60482
60577
  const raw = stringOrUndefined2(value);
@@ -60507,7 +60602,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
60507
60602
  const absPath = isAbsolute5(raw) ? resolve59(raw) : resolve59(projectDir, raw);
60508
60603
  const rel = relative10(resolve59(projectDir), absPath);
60509
60604
  const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute5(rel);
60510
- const exists = withinProject && existsSync46(absPath);
60605
+ const exists = withinProject && existsSync47(absPath);
60511
60606
  let error = forcedError;
60512
60607
  if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
60513
60608
  error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
@@ -60694,9 +60789,9 @@ var init_build_backdrop_prompt = __esm({
60694
60789
  });
60695
60790
 
60696
60791
  // src/commands/_shared/build-plan.ts
60697
- import { existsSync as existsSync47 } from "node:fs";
60698
- import { readFile as readFile43 } from "node:fs/promises";
60699
- import { dirname as dirname21, join as join23, resolve as resolve60 } from "node:path";
60792
+ import { existsSync as existsSync48 } from "node:fs";
60793
+ import { readFile as readFile44 } from "node:fs/promises";
60794
+ import { dirname as dirname21, join as join24, resolve as resolve60 } from "node:path";
60700
60795
  function backdropCostUsd(quality) {
60701
60796
  return quality === "hd" ? 0.2 : 0.05;
60702
60797
  }
@@ -60705,11 +60800,11 @@ async function createBuildPlan(opts) {
60705
60800
  loadPlanEnv(projectDir);
60706
60801
  const stage = opts.stage ?? "all";
60707
60802
  const config2 = await readProjectConfig(projectDir);
60708
- const storyboardPath2 = join23(projectDir, "STORYBOARD.md");
60803
+ const storyboardPath2 = join24(projectDir, "STORYBOARD.md");
60709
60804
  const warnings = [];
60710
60805
  const retryWith = [];
60711
60806
  const providerResolution = [];
60712
- if (!existsSync47(storyboardPath2)) {
60807
+ if (!existsSync48(storyboardPath2)) {
60713
60808
  const validation2 = {
60714
60809
  ok: false,
60715
60810
  issues: [
@@ -60738,7 +60833,7 @@ async function createBuildPlan(opts) {
60738
60833
  validation: validation2
60739
60834
  });
60740
60835
  }
60741
- const storyboardMd = await readFile43(storyboardPath2, "utf-8");
60836
+ const storyboardMd = await readFile44(storyboardPath2, "utf-8");
60742
60837
  const validation = validateStoryboardMarkdown(storyboardMd);
60743
60838
  const validationIssues = [...validation.issues];
60744
60839
  const parsed = parseStoryboard(storyboardMd);
@@ -60764,7 +60859,7 @@ async function createBuildPlan(opts) {
60764
60859
  const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
60765
60860
  const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
60766
60861
  const imageRatio = imageRatioForSize(imageSize2);
60767
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync47(join23(projectDir, `compositions/scene-${beat.id}.html`)));
60862
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync48(join24(projectDir, `compositions/scene-${beat.id}.html`)));
60768
60863
  const resolved = await resolvePlanProviders({
60769
60864
  projectDir,
60770
60865
  config: config2,
@@ -60912,7 +61007,7 @@ async function createBuildPlan(opts) {
60912
61007
  active: includeAssets
60913
61008
  }) : null;
60914
61009
  const compositionPath = `compositions/scene-${beat.id}.html`;
60915
- const compositionExists = existsSync47(join23(projectDir, compositionPath));
61010
+ const compositionExists = existsSync48(join24(projectDir, compositionPath));
60916
61011
  for (const asset of [narration, backdrop, keyframe, video, music]) {
60917
61012
  if (!asset) continue;
60918
61013
  if (asset.referenceError) {
@@ -60981,7 +61076,7 @@ async function createBuildPlan(opts) {
60981
61076
  }
60982
61077
  }
60983
61078
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
60984
- if (!existsSync47(join23(projectDir, config2.config.composition.entry))) {
61079
+ if (!existsSync48(join24(projectDir, config2.config.composition.entry))) {
60985
61080
  missing.add("root-composition");
60986
61081
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
60987
61082
  }
@@ -61015,7 +61110,7 @@ async function createBuildPlan(opts) {
61015
61110
  }
61016
61111
  if (referenced.size > 0) {
61017
61112
  const toGenerate = resolveCharacters(parsed.frontmatter).filter(
61018
- (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync47(join23(projectDir, `assets/character-${c.name}.png`)))
61113
+ (c) => referenced.has(c.name) && c.prompt && (opts.force || !existsSync48(join24(projectDir, `assets/character-${c.name}.png`)))
61019
61114
  );
61020
61115
  if (toGenerate.length > 0) {
61021
61116
  estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
@@ -61128,8 +61223,8 @@ function assetPlan(opts) {
61128
61223
  reason: ready ? "referenced-asset" : "invalid-reference"
61129
61224
  };
61130
61225
  }
61131
- const canonicalExists = existsSync47(join23(opts.projectDir, opts.path));
61132
- const cacheHit = opts.cache ? existsSync47(join23(opts.projectDir, opts.cache.path)) : false;
61226
+ const canonicalExists = existsSync48(join24(opts.projectDir, opts.path));
61227
+ const cacheHit = opts.cache ? existsSync48(join24(opts.projectDir, opts.cache.path)) : false;
61133
61228
  const freshness = assetFreshnessFromMetadata({
61134
61229
  projectDir: opts.projectDir,
61135
61230
  kind: opts.kind,
@@ -61327,7 +61422,7 @@ async function firstConfiguredComposer(projectDir) {
61327
61422
  }
61328
61423
  function firstExisting2(projectDir, paths) {
61329
61424
  for (const path2 of paths) {
61330
- if (existsSync47(join23(projectDir, path2))) return path2;
61425
+ if (existsSync48(join24(projectDir, path2))) return path2;
61331
61426
  }
61332
61427
  return null;
61333
61428
  }
@@ -61368,12 +61463,12 @@ function unique3(items) {
61368
61463
  return [...new Set(items.filter((item) => item.length > 0))];
61369
61464
  }
61370
61465
  function loadPlanEnv(projectDir) {
61371
- (0, import_dotenv3.config)({ path: join23(projectDir, ".env"), quiet: true });
61466
+ (0, import_dotenv3.config)({ path: join24(projectDir, ".env"), quiet: true });
61372
61467
  (0, import_dotenv3.config)({ path: resolve60(process.cwd(), ".env"), quiet: true });
61373
61468
  let dir = process.cwd();
61374
61469
  while (dir !== dirname21(dir)) {
61375
- if (existsSync47(join23(dir, "pnpm-workspace.yaml"))) {
61376
- (0, import_dotenv3.config)({ path: join23(dir, ".env"), quiet: true });
61470
+ if (existsSync48(join24(dir, "pnpm-workspace.yaml"))) {
61471
+ (0, import_dotenv3.config)({ path: join24(dir, ".env"), quiet: true });
61377
61472
  return;
61378
61473
  }
61379
61474
  dir = dirname21(dir);
@@ -61454,7 +61549,7 @@ var init_concurrency = __esm({
61454
61549
  });
61455
61550
 
61456
61551
  // src/commands/_shared/scene-audio-scan.ts
61457
- import { readFile as readFile44 } from "node:fs/promises";
61552
+ import { readFile as readFile45 } from "node:fs/promises";
61458
61553
  import { resolve as resolve62 } from "node:path";
61459
61554
  function parseRootClips(rootHtml) {
61460
61555
  const clips = [];
@@ -61495,7 +61590,7 @@ function makeFsCompositionReader(projectDir) {
61495
61590
  return async (compositionSrcRel) => {
61496
61591
  const abs = resolve62(projectDir, compositionSrcRel);
61497
61592
  try {
61498
- return await readFile44(abs, "utf-8");
61593
+ return await readFile45(abs, "utf-8");
61499
61594
  } catch {
61500
61595
  return null;
61501
61596
  }
@@ -61695,9 +61790,9 @@ __export(scene_render_exports, {
61695
61790
  qualityToCrf: () => qualityToCrf2
61696
61791
  });
61697
61792
  import { execFile as execFile2 } from "node:child_process";
61698
- import { existsSync as existsSync48 } from "node:fs";
61699
- import { mkdir as mkdir25, readFile as readFile45, stat as stat8, writeFile as writeFile49 } from "node:fs/promises";
61700
- import { resolve as resolve64, relative as relative11, dirname as dirname23, basename as basename21, join as join24, isAbsolute as isAbsolute6 } from "node:path";
61793
+ import { existsSync as existsSync49 } from "node:fs";
61794
+ import { mkdir as mkdir25, readFile as readFile46, stat as stat8, writeFile as writeFile49 } from "node:fs/promises";
61795
+ import { resolve as resolve64, relative as relative11, dirname as dirname23, basename as basename21, join as join25, isAbsolute as isAbsolute6 } from "node:path";
61701
61796
  import { promisify as promisify2 } from "node:util";
61702
61797
  import {
61703
61798
  createRenderJob,
@@ -61789,7 +61884,7 @@ async function executeSceneRender(opts = {}) {
61789
61884
  }
61790
61885
  let autoSyncApplied = false;
61791
61886
  let autoSyncWarning;
61792
- if (!opts.beatId && existsSync48(join24(projectDir, "STORYBOARD.md"))) {
61887
+ if (!opts.beatId && existsSync49(join25(projectDir, "STORYBOARD.md"))) {
61793
61888
  try {
61794
61889
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
61795
61890
  const subPlans = await createSubCompDurationSyncPlans({
@@ -61881,7 +61976,7 @@ async function executeSceneRender(opts = {}) {
61881
61976
  let audioMuxWarning;
61882
61977
  try {
61883
61978
  opts.onProgress?.(0.95, "Mixing audio");
61884
- const rootHtml = await readFile45(resolve64(projectDir, root), "utf-8");
61979
+ const rootHtml = await readFile46(resolve64(projectDir, root), "utf-8");
61885
61980
  const audios = await scanSceneAudio({ projectDir, rootHtml });
61886
61981
  audioCount = audios.length;
61887
61982
  if (audios.length > 0) {
@@ -61966,11 +62061,11 @@ async function safeStat(p) {
61966
62061
  }
61967
62062
  async function prepareBeatRenderRoot(opts) {
61968
62063
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment2(opts.beatId)}.html`;
61969
- const reportPath = join24(opts.projectDir, "build-report.json");
62064
+ const reportPath = join25(opts.projectDir, "build-report.json");
61970
62065
  const retryWith = [
61971
62066
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
61972
62067
  ];
61973
- if (!existsSync48(reportPath)) {
62068
+ if (!existsSync49(reportPath)) {
61974
62069
  return {
61975
62070
  success: false,
61976
62071
  root,
@@ -61981,7 +62076,7 @@ async function prepareBeatRenderRoot(opts) {
61981
62076
  }
61982
62077
  let report;
61983
62078
  try {
61984
- report = JSON.parse(await readFile45(reportPath, "utf-8"));
62079
+ report = JSON.parse(await readFile46(reportPath, "utf-8"));
61985
62080
  } catch (error) {
61986
62081
  return {
61987
62082
  success: false,
@@ -62050,8 +62145,8 @@ async function prepareBeatRenderRoot(opts) {
62050
62145
  durationSec,
62051
62146
  narrationPath
62052
62147
  });
62053
- await mkdir25(dirname23(join24(opts.projectDir, root)), { recursive: true });
62054
- await writeFile49(join24(opts.projectDir, root), html, "utf-8");
62148
+ await mkdir25(dirname23(join25(opts.projectDir, root)), { recursive: true });
62149
+ await writeFile49(join25(opts.projectDir, root), html, "utf-8");
62055
62150
  return {
62056
62151
  success: true,
62057
62152
  root,
@@ -62115,7 +62210,7 @@ function reportedBeatAssetPaths(beat) {
62115
62210
  ]);
62116
62211
  }
62117
62212
  async function writeRenderReport(projectDir, result) {
62118
- const reportPath = join24(projectDir, "render-report.json");
62213
+ const reportPath = join25(projectDir, "render-report.json");
62119
62214
  try {
62120
62215
  await writeFile49(
62121
62216
  reportPath,
@@ -62157,7 +62252,7 @@ async function writeRenderReport(projectDir, result) {
62157
62252
  }
62158
62253
  function pathExists3(projectDir, value) {
62159
62254
  const abs = isAbsolute6(value) ? value : resolve64(projectDir, value);
62160
- return existsSync48(abs);
62255
+ return existsSync49(abs);
62161
62256
  }
62162
62257
  function stringValue(value) {
62163
62258
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -62216,9 +62311,9 @@ __export(scene_build_exports, {
62216
62311
  executeSceneBuild: () => executeSceneBuild,
62217
62312
  resolveSceneBuildMode: () => resolveSceneBuildMode
62218
62313
  });
62219
- import { existsSync as existsSync49, readFileSync as readFileSync3 } from "node:fs";
62220
- import { copyFile as copyFile5, mkdir as mkdir26, readFile as readFile46, rm as rm4, writeFile as writeFile50 } from "node:fs/promises";
62221
- import { dirname as dirname24, join as join25, resolve as resolve65 } from "node:path";
62314
+ import { existsSync as existsSync50, readFileSync as readFileSync3 } from "node:fs";
62315
+ import { copyFile as copyFile5, mkdir as mkdir26, readFile as readFile47, rm as rm4, writeFile as writeFile50 } from "node:fs/promises";
62316
+ import { dirname as dirname24, join as join26, resolve as resolve65 } from "node:path";
62222
62317
  async function executeSceneBuild(opts) {
62223
62318
  const startedAt = Date.now();
62224
62319
  const projectDir = resolve65(opts.projectDir);
@@ -62237,7 +62332,7 @@ async function executeSceneBuild(opts) {
62237
62332
  );
62238
62333
  }
62239
62334
  let sceneRepair = skippedSceneRepairSummary();
62240
- const storyboardPath2 = join25(projectDir, "STORYBOARD.md");
62335
+ const storyboardPath2 = join26(projectDir, "STORYBOARD.md");
62241
62336
  const buildPlan = await createBuildPlan({
62242
62337
  projectDir,
62243
62338
  stage: selectedStage,
@@ -62288,8 +62383,8 @@ async function executeSceneBuild(opts) {
62288
62383
  }
62289
62384
  if (!buildPlan.validation.ok) {
62290
62385
  let invalidBeats = [];
62291
- if (existsSync49(storyboardPath2)) {
62292
- invalidBeats = parseStoryboard(await readFile46(storyboardPath2, "utf-8")).beats;
62386
+ if (existsSync50(storyboardPath2)) {
62387
+ invalidBeats = parseStoryboard(await readFile47(storyboardPath2, "utf-8")).beats;
62293
62388
  }
62294
62389
  return finishBuildResult({
62295
62390
  success: false,
@@ -62313,7 +62408,7 @@ async function executeSceneBuild(opts) {
62313
62408
  totalLatencyMs: Date.now() - startedAt
62314
62409
  });
62315
62410
  }
62316
- if (!existsSync49(storyboardPath2)) {
62411
+ if (!existsSync50(storyboardPath2)) {
62317
62412
  return finishBuildResult({
62318
62413
  success: false,
62319
62414
  phase: "failed",
@@ -62327,7 +62422,7 @@ async function executeSceneBuild(opts) {
62327
62422
  totalLatencyMs: Date.now() - startedAt
62328
62423
  });
62329
62424
  }
62330
- const storyboardMd = await readFile46(storyboardPath2, "utf-8");
62425
+ const storyboardMd = await readFile47(storyboardPath2, "utf-8");
62331
62426
  const parsed = parseStoryboard(storyboardMd);
62332
62427
  if (parsed.beats.length === 0) {
62333
62428
  return finishBuildResult({
@@ -62434,6 +62529,7 @@ async function executeSceneBuild(opts) {
62434
62529
  skipVideo: opts.skipVideo ?? false,
62435
62530
  skipKeyframe: opts.skipKeyframe ?? false,
62436
62531
  skipMusic: opts.skipMusic ?? false,
62532
+ skipTranscript: opts.skipTranscript ?? false,
62437
62533
  force: opts.force ?? false,
62438
62534
  onProgress,
62439
62535
  characterPaths
@@ -62521,9 +62617,9 @@ async function executeSceneBuild(opts) {
62521
62617
  let composeData;
62522
62618
  if (shouldRunStage(selectedStage, "compose")) {
62523
62619
  if (mode === "agent") {
62524
- const compositionsDir = join25(projectDir, "compositions");
62620
+ const compositionsDir = join26(projectDir, "compositions");
62525
62621
  const missingBeats = activeBeats.filter(
62526
- (b) => !existsSync49(join25(compositionsDir, `scene-${b.id}.html`))
62622
+ (b) => !existsSync50(join26(compositionsDir, `scene-${b.id}.html`))
62527
62623
  );
62528
62624
  if (missingBeats.length > 0) {
62529
62625
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -62612,8 +62708,8 @@ async function executeSceneBuild(opts) {
62612
62708
  if (stageReports.compose.status === "done") {
62613
62709
  for (const beat of activeBeats) {
62614
62710
  const videoRel = `assets/video-${beat.id}.mp4`;
62615
- const compositionAbs = join25(projectDir, "compositions", `scene-${beat.id}.html`);
62616
- if (!existsSync49(join25(projectDir, videoRel)) || !existsSync49(compositionAbs)) continue;
62711
+ const compositionAbs = join26(projectDir, "compositions", `scene-${beat.id}.html`);
62712
+ if (!existsSync50(join26(projectDir, videoRel)) || !existsSync50(compositionAbs)) continue;
62617
62713
  const html = readFileSync3(compositionAbs, "utf-8");
62618
62714
  if (!html.includes(videoRel)) {
62619
62715
  warnings.push(
@@ -62671,7 +62767,7 @@ async function executeSceneBuild(opts) {
62671
62767
  });
62672
62768
  }
62673
62769
  if (shouldRunStage(selectedStage, "sync")) {
62674
- if (!existsSync49(join25(projectDir, "index.html"))) {
62770
+ if (!existsSync50(join26(projectDir, "index.html"))) {
62675
62771
  await scaffoldSceneProject({
62676
62772
  dir: projectDir,
62677
62773
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -62816,6 +62912,9 @@ async function buildBeatPrimitives(beat, ctx) {
62816
62912
  ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx, keyframe),
62817
62913
  ctx.skipMusic ? skipped("music", beat.id, "--skip-music", ctx) : dispatchMusic(beat, ctx)
62818
62914
  ]);
62915
+ if (!ctx.skipTranscript) {
62916
+ await dispatchTranscript(beat, narration, ctx);
62917
+ }
62819
62918
  return {
62820
62919
  outcome: {
62821
62920
  beatId: beat.id,
@@ -62933,7 +63032,7 @@ async function dispatchNarration(beat, ctx) {
62933
63032
  return {
62934
63033
  status: "cached",
62935
63034
  path: existingRel,
62936
- durationSec: await safeAudioDuration(join25(ctx.projectDir, existingRel)),
63035
+ durationSec: await safeAudioDuration(join26(ctx.projectDir, existingRel)),
62937
63036
  provider: metadata?.provider,
62938
63037
  cachePath: metadata?.cachePath,
62939
63038
  cacheKey: metadata?.cacheKey,
@@ -62957,9 +63056,9 @@ async function dispatchNarration(beat, ctx) {
62957
63056
  voice,
62958
63057
  ext: resolution.audioExtension
62959
63058
  });
62960
- const cacheAbs = join25(ctx.projectDir, cache.path);
63059
+ const cacheAbs = join26(ctx.projectDir, cache.path);
62961
63060
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
62962
- const abs = join25(ctx.projectDir, rel);
63061
+ const abs = join26(ctx.projectDir, rel);
62963
63062
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
62964
63063
  projectDir: ctx.projectDir,
62965
63064
  kind: "narration",
@@ -62973,7 +63072,7 @@ async function dispatchNarration(beat, ctx) {
62973
63072
  return {
62974
63073
  status: "cached",
62975
63074
  path: existingRel,
62976
- durationSec: await safeAudioDuration(join25(ctx.projectDir, existingRel)),
63075
+ durationSec: await safeAudioDuration(join26(ctx.projectDir, existingRel)),
62977
63076
  provider: resolution.provider,
62978
63077
  cachePath: cache.path,
62979
63078
  cacheKey: cache.key,
@@ -62981,7 +63080,7 @@ async function dispatchNarration(beat, ctx) {
62981
63080
  freshness: "fresh"
62982
63081
  };
62983
63082
  }
62984
- if (existsSync49(cacheAbs) && !ctx.force) {
63083
+ if (existsSync50(cacheAbs) && !ctx.force) {
62985
63084
  await mkdir26(dirname24(abs), { recursive: true });
62986
63085
  await copyFile5(cacheAbs, abs);
62987
63086
  await writeAssetMetadata({
@@ -63016,8 +63115,8 @@ async function dispatchNarration(beat, ctx) {
63016
63115
  await mkdir26(dirname24(abs), { recursive: true });
63017
63116
  await writeFile50(abs, result.audioBuffer);
63018
63117
  for (const otherExt of ["mp3", "wav"]) {
63019
- const sibling = join25(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
63020
- if (sibling !== abs && existsSync49(sibling)) await rm4(sibling, { force: true });
63118
+ const sibling = join26(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
63119
+ if (sibling !== abs && existsSync50(sibling)) await rm4(sibling, { force: true });
63021
63120
  }
63022
63121
  await mkdir26(dirname24(cacheAbs), { recursive: true });
63023
63122
  await writeFile50(cacheAbs, result.audioBuffer);
@@ -63049,6 +63148,42 @@ async function dispatchNarration(beat, ctx) {
63049
63148
  freshness: "fresh"
63050
63149
  };
63051
63150
  }
63151
+ async function dispatchTranscript(beat, narration, ctx) {
63152
+ if (!narration.path) return;
63153
+ const relPath = beatTranscriptRelPath(beat.id);
63154
+ const abs = join26(ctx.projectDir, relPath);
63155
+ const narrationFresh = narration.status === "generated";
63156
+ if (existsSync50(abs) && !ctx.force && !narrationFresh) {
63157
+ ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
63158
+ return;
63159
+ }
63160
+ const apiKey = await getConfiguredApiKey("OPENAI_API_KEY", void 0, { cwd: ctx.projectDir });
63161
+ if (!apiKey) {
63162
+ ctx.onProgress({
63163
+ type: "transcript-skipped",
63164
+ beatId: beat.id,
63165
+ reason: "OPENAI_API_KEY not set \u2014 narration plays but word-sync timings unavailable"
63166
+ });
63167
+ return;
63168
+ }
63169
+ const words = await transcribeNarrationWords(join26(ctx.projectDir, narration.path), { apiKey });
63170
+ if (words.length === 0) {
63171
+ ctx.onProgress({
63172
+ type: "transcript-skipped",
63173
+ beatId: beat.id,
63174
+ reason: "transcription returned no word timings"
63175
+ });
63176
+ return;
63177
+ }
63178
+ await mkdir26(dirname24(abs), { recursive: true });
63179
+ await writeFile50(abs, JSON.stringify(words, null, 2), "utf-8");
63180
+ ctx.onProgress({
63181
+ type: "transcript-generated",
63182
+ beatId: beat.id,
63183
+ path: relPath,
63184
+ wordCount: words.length
63185
+ });
63186
+ }
63052
63187
  function characterSheetPrompt(name, description) {
63053
63188
  return [
63054
63189
  `Character reference sheet of an original fictional character named ${name}: ${description}.`,
@@ -63066,7 +63201,7 @@ async function buildCharacters(characters, ctx) {
63066
63201
  const size = ctx.imageSize ?? "1536x1024";
63067
63202
  for (const character of characters) {
63068
63203
  if (character.imagePath) {
63069
- if (existsSync49(join25(ctx.projectDir, character.imagePath))) {
63204
+ if (existsSync50(join26(ctx.projectDir, character.imagePath))) {
63070
63205
  paths.set(character.name, character.imagePath);
63071
63206
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
63072
63207
  } else {
@@ -63079,7 +63214,7 @@ async function buildCharacters(characters, ctx) {
63079
63214
  if (!character.prompt) continue;
63080
63215
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
63081
63216
  const rel = `assets/character-${character.name}.png`;
63082
- const abs = join25(ctx.projectDir, rel);
63217
+ const abs = join26(ctx.projectDir, rel);
63083
63218
  const metadataOptions = { quality: ctx.imageQuality, size };
63084
63219
  const cache = characterCacheDescriptor({
63085
63220
  name: character.name,
@@ -63088,7 +63223,7 @@ async function buildCharacters(characters, ctx) {
63088
63223
  quality: ctx.imageQuality,
63089
63224
  size
63090
63225
  });
63091
- if (existsSync49(abs) && !ctx.force) {
63226
+ if (existsSync50(abs) && !ctx.force) {
63092
63227
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
63093
63228
  if (!metadata || isFreshCanonicalAsset({
63094
63229
  projectDir: ctx.projectDir,
@@ -63104,8 +63239,8 @@ async function buildCharacters(characters, ctx) {
63104
63239
  continue;
63105
63240
  }
63106
63241
  }
63107
- const cacheAbs = join25(ctx.projectDir, cache.path);
63108
- if (existsSync49(cacheAbs) && !ctx.force) {
63242
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63243
+ if (existsSync50(cacheAbs) && !ctx.force) {
63109
63244
  await mkdir26(dirname24(abs), { recursive: true });
63110
63245
  await copyFile5(cacheAbs, abs);
63111
63246
  await writeAssetMetadata({
@@ -63171,7 +63306,7 @@ async function dispatchBackdrop(beat, ctx) {
63171
63306
  if (!cue) return { status: "no-cue" };
63172
63307
  const prompt3 = augmentBackdropPrompt(cue);
63173
63308
  const rel = `assets/backdrop-${beat.id}.png`;
63174
- const abs = join25(ctx.projectDir, rel);
63309
+ const abs = join26(ctx.projectDir, rel);
63175
63310
  const size = ctx.imageSize ?? "1536x1024";
63176
63311
  const ratio = imageRatioForSize(size);
63177
63312
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -63184,7 +63319,7 @@ async function dispatchBackdrop(beat, ctx) {
63184
63319
  ratio
63185
63320
  });
63186
63321
  const metadataPath = assetMetadataPath("backdrop", beat.id);
63187
- if (existsSync49(abs) && !ctx.force) {
63322
+ if (existsSync50(abs) && !ctx.force) {
63188
63323
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
63189
63324
  if (!metadata || isFreshCanonicalAsset({
63190
63325
  projectDir: ctx.projectDir,
@@ -63207,8 +63342,8 @@ async function dispatchBackdrop(beat, ctx) {
63207
63342
  };
63208
63343
  }
63209
63344
  }
63210
- const cacheAbs = join25(ctx.projectDir, cache.path);
63211
- if (existsSync49(cacheAbs) && !ctx.force) {
63345
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63346
+ if (existsSync50(cacheAbs) && !ctx.force) {
63212
63347
  await mkdir26(dirname24(abs), { recursive: true });
63213
63348
  await copyFile5(cacheAbs, abs);
63214
63349
  await writeAssetMetadata({
@@ -63340,7 +63475,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
63340
63475
  }
63341
63476
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63342
63477
  const rel = `assets/keyframe-${beat.id}.png`;
63343
- const abs = join25(ctx.projectDir, rel);
63478
+ const abs = join26(ctx.projectDir, rel);
63344
63479
  const size = ctx.imageSize ?? "1536x1024";
63345
63480
  const ratio = imageRatioForSize(size);
63346
63481
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -63353,7 +63488,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63353
63488
  ratio,
63354
63489
  characters: characterRefsRel
63355
63490
  });
63356
- if (existsSync49(abs) && !ctx.force) {
63491
+ if (existsSync50(abs) && !ctx.force) {
63357
63492
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
63358
63493
  if (!metadata || isFreshCanonicalAsset({
63359
63494
  projectDir: ctx.projectDir,
@@ -63367,8 +63502,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63367
63502
  return { status: "cached", rel };
63368
63503
  }
63369
63504
  }
63370
- const cacheAbs = join25(ctx.projectDir, cache.path);
63371
- if (existsSync49(cacheAbs) && !ctx.force) {
63505
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63506
+ if (existsSync50(cacheAbs) && !ctx.force) {
63372
63507
  await mkdir26(dirname24(abs), { recursive: true });
63373
63508
  await copyFile5(cacheAbs, abs);
63374
63509
  await writeAssetMetadata({
@@ -63386,8 +63521,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63386
63521
  }
63387
63522
  const sheetBuffers = [];
63388
63523
  for (const relPath of characterRefsRel) {
63389
- const sheetAbs = join25(ctx.projectDir, relPath);
63390
- if (existsSync49(sheetAbs)) sheetBuffers.push(await readFile46(sheetAbs));
63524
+ const sheetAbs = join26(ctx.projectDir, relPath);
63525
+ if (existsSync50(sheetAbs)) sheetBuffers.push(await readFile47(sheetAbs));
63391
63526
  }
63392
63527
  const imgCtx = {
63393
63528
  projectDir: ctx.projectDir,
@@ -63456,9 +63591,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
63456
63591
  const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
63457
63592
  if (!prompt3) return { status: "no-cue" };
63458
63593
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
63459
- const characterRefsAbs = characterRefsRel.map((p) => join25(ctx.projectDir, p));
63594
+ const characterRefsAbs = characterRefsRel.map((p) => join26(ctx.projectDir, p));
63460
63595
  const rel = `assets/video-${beat.id}.mp4`;
63461
- const abs = join25(ctx.projectDir, rel);
63596
+ const abs = join26(ctx.projectDir, rel);
63462
63597
  const cache = videoCacheDescriptor({
63463
63598
  beatId: beat.id,
63464
63599
  cue: prompt3,
@@ -63470,7 +63605,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
63470
63605
  keyframe: keyframeCue
63471
63606
  });
63472
63607
  const metadataPath = assetMetadataPath("video", beat.id);
63473
- if (existsSync49(abs) && !ctx.force) {
63608
+ if (existsSync50(abs) && !ctx.force) {
63474
63609
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
63475
63610
  if (!metadata || isFreshCanonicalAsset({
63476
63611
  projectDir: ctx.projectDir,
@@ -63493,8 +63628,8 @@ async function dispatchVideo(beat, ctx, keyframe) {
63493
63628
  };
63494
63629
  }
63495
63630
  }
63496
- const cacheAbs = join25(ctx.projectDir, cache.path);
63497
- if (existsSync49(cacheAbs) && !ctx.force) {
63631
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63632
+ if (existsSync50(cacheAbs) && !ctx.force) {
63498
63633
  await mkdir26(dirname24(abs), { recursive: true });
63499
63634
  await copyFile5(cacheAbs, abs);
63500
63635
  await writeAssetMetadata({
@@ -63525,7 +63660,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
63525
63660
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
63526
63661
  return { status: "skipped" };
63527
63662
  }
63528
- const keyframeImageAbs = keyframeImageRel ? join25(ctx.projectDir, keyframeImageRel) : void 0;
63663
+ const keyframeImageAbs = keyframeImageRel ? join26(ctx.projectDir, keyframeImageRel) : void 0;
63529
63664
  loadSceneBuildEnv(ctx.projectDir);
63530
63665
  const result = await executeVideoGenerate({
63531
63666
  prompt: prompt3,
@@ -63549,7 +63684,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
63549
63684
  ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
63550
63685
  return { status: "failed", error };
63551
63686
  }
63552
- if (result.status === "completed" && existsSync49(abs)) {
63687
+ if (result.status === "completed" && existsSync50(abs)) {
63553
63688
  await mkdir26(dirname24(cacheAbs), { recursive: true });
63554
63689
  await copyFile5(abs, cacheAbs);
63555
63690
  await writeAssetMetadata({
@@ -63617,7 +63752,7 @@ async function dispatchMusic(beat, ctx) {
63617
63752
  const prompt3 = stringOrUndefined4(beat.cues?.music);
63618
63753
  if (!prompt3) return { status: "no-cue" };
63619
63754
  const rel = `assets/music-${beat.id}.mp3`;
63620
- const abs = join25(ctx.projectDir, rel);
63755
+ const abs = join26(ctx.projectDir, rel);
63621
63756
  const duration = normalizeMusicDuration(beat.duration);
63622
63757
  const cache = musicCacheDescriptor({
63623
63758
  beatId: beat.id,
@@ -63626,7 +63761,7 @@ async function dispatchMusic(beat, ctx) {
63626
63761
  duration
63627
63762
  });
63628
63763
  const metadataPath = assetMetadataPath("music", beat.id);
63629
- if (existsSync49(abs) && !ctx.force) {
63764
+ if (existsSync50(abs) && !ctx.force) {
63630
63765
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
63631
63766
  if (!metadata || isFreshCanonicalAsset({
63632
63767
  projectDir: ctx.projectDir,
@@ -63650,8 +63785,8 @@ async function dispatchMusic(beat, ctx) {
63650
63785
  };
63651
63786
  }
63652
63787
  }
63653
- const cacheAbs = join25(ctx.projectDir, cache.path);
63654
- if (existsSync49(cacheAbs) && !ctx.force) {
63788
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63789
+ if (existsSync50(cacheAbs) && !ctx.force) {
63655
63790
  await mkdir26(dirname24(abs), { recursive: true });
63656
63791
  await copyFile5(cacheAbs, abs);
63657
63792
  await writeAssetMetadata({
@@ -63725,7 +63860,7 @@ async function dispatchMusic(beat, ctx) {
63725
63860
  metadataPath
63726
63861
  };
63727
63862
  }
63728
- if (!existsSync49(abs)) {
63863
+ if (!existsSync50(abs)) {
63729
63864
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
63730
63865
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
63731
63866
  return { status: "failed", error };
@@ -63761,12 +63896,12 @@ async function dispatchMusic(beat, ctx) {
63761
63896
  };
63762
63897
  }
63763
63898
  function loadSceneBuildEnv(projectDir) {
63764
- (0, import_dotenv4.config)({ path: join25(projectDir, ".env"), quiet: true });
63899
+ (0, import_dotenv4.config)({ path: join26(projectDir, ".env"), quiet: true });
63765
63900
  (0, import_dotenv4.config)({ path: resolve65(process.cwd(), ".env"), quiet: true });
63766
63901
  let dir = process.cwd();
63767
63902
  while (dir !== dirname24(dir)) {
63768
- if (existsSync49(join25(dir, "pnpm-workspace.yaml"))) {
63769
- (0, import_dotenv4.config)({ path: join25(dir, ".env"), quiet: true });
63903
+ if (existsSync50(join26(dir, "pnpm-workspace.yaml"))) {
63904
+ (0, import_dotenv4.config)({ path: join26(dir, ".env"), quiet: true });
63770
63905
  return;
63771
63906
  }
63772
63907
  dir = dirname24(dir);
@@ -63982,7 +64117,7 @@ function mergeBeatOutcomes(base, updates) {
63982
64117
  }
63983
64118
  function firstExisting3(projectDir, relPaths) {
63984
64119
  for (const rel of relPaths) {
63985
- if (existsSync49(join25(projectDir, rel))) return rel;
64120
+ if (existsSync50(join26(projectDir, rel))) return rel;
63986
64121
  }
63987
64122
  return null;
63988
64123
  }
@@ -64013,7 +64148,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
64013
64148
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
64014
64149
  }
64015
64150
  async function finalizeBuildResult(projectDir, startedAt, result) {
64016
- const reportPath = join25(projectDir, "build-report.json");
64151
+ const reportPath = join26(projectDir, "build-report.json");
64017
64152
  if (result.stageReports) {
64018
64153
  for (const report of Object.values(result.stageReports)) {
64019
64154
  if (report.status === "pending") report.status = "skipped";
@@ -64172,7 +64307,7 @@ function normalizeReportDuration(value) {
64172
64307
  }
64173
64308
  function buildReportComposition(projectDir, result, beatId) {
64174
64309
  const path2 = `compositions/scene-${beatId}.html`;
64175
- const exists = existsSync49(join25(projectDir, path2));
64310
+ const exists = existsSync50(join26(projectDir, path2));
64176
64311
  const written = composeWrittenEntry(result, beatId);
64177
64312
  if (written) {
64178
64313
  return {
@@ -64229,8 +64364,8 @@ function summarizeBuildBeats(projectDir, beats) {
64229
64364
  let compositionsReady = 0;
64230
64365
  for (const beat of beats) {
64231
64366
  if (beatAssetsReady(beat)) assetsReady += 1;
64232
- const compositionPath = join25(projectDir, "compositions", `scene-${beat.beatId}.html`);
64233
- if (existsSync49(compositionPath)) {
64367
+ const compositionPath = join26(projectDir, "compositions", `scene-${beat.beatId}.html`);
64368
+ if (existsSync50(compositionPath)) {
64234
64369
  compositionsReady += 1;
64235
64370
  } else {
64236
64371
  needsAuthor.push(beat.beatId);
@@ -64272,6 +64407,8 @@ var init_scene_build = __esm({
64272
64407
  init_compose_scenes_skills();
64273
64408
  init_compose_prompts();
64274
64409
  init_config();
64410
+ init_api_key();
64411
+ init_transcribe_narration();
64275
64412
  init_scene_render();
64276
64413
  init_storyboard_parse();
64277
64414
  init_scene_project();
@@ -64362,18 +64499,18 @@ __export(ai_audio_exports, {
64362
64499
  executeVoiceClone: () => executeVoiceClone
64363
64500
  });
64364
64501
  import { resolve as resolve73, dirname as dirname26, basename as basename22, extname as extname15 } from "node:path";
64365
- import { readFile as readFile48, writeFile as writeFile51 } from "node:fs/promises";
64366
- import { existsSync as existsSync54 } from "node:fs";
64502
+ import { readFile as readFile49, writeFile as writeFile51 } from "node:fs/promises";
64503
+ import { existsSync as existsSync55 } from "node:fs";
64367
64504
  async function executeTranscribe(options) {
64368
64505
  const { audioPath, language, output, format, apiKey } = options;
64369
64506
  try {
64370
64507
  const key = apiKey || process.env.OPENAI_API_KEY;
64371
64508
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
64372
64509
  const absPath = resolve73(process.cwd(), audioPath);
64373
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${absPath}` };
64510
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
64374
64511
  const whisper = new WhisperProvider();
64375
64512
  await whisper.initialize({ apiKey: key });
64376
- const audioBuffer = await readFile48(absPath);
64513
+ const audioBuffer = await readFile49(absPath);
64377
64514
  const audioBlob = new Blob([audioBuffer]);
64378
64515
  const result = await whisper.transcribe(audioBlob, language);
64379
64516
  if (result.status === "failed") {
@@ -64403,8 +64540,8 @@ async function executeIsolate(options) {
64403
64540
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
64404
64541
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
64405
64542
  const absPath = resolve73(process.cwd(), audioPath);
64406
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${absPath}` };
64407
- const audioBuffer = await readFile48(absPath);
64543
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
64544
+ const audioBuffer = await readFile49(absPath);
64408
64545
  const elevenlabs = new ElevenLabsProvider();
64409
64546
  await elevenlabs.initialize({ apiKey: key });
64410
64547
  const result = await elevenlabs.isolateVocals(audioBuffer);
@@ -64429,8 +64566,8 @@ async function executeVoiceClone(options) {
64429
64566
  const audioBuffers = [];
64430
64567
  for (const samplePath of samplePaths) {
64431
64568
  const absPath = resolve73(process.cwd(), samplePath);
64432
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${samplePath}` };
64433
- const buffer = await readFile48(absPath);
64569
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${samplePath}` };
64570
+ const buffer = await readFile49(absPath);
64434
64571
  audioBuffers.push(buffer);
64435
64572
  }
64436
64573
  const elevenlabs = new ElevenLabsProvider();
@@ -64451,7 +64588,7 @@ async function executeDub(options) {
64451
64588
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
64452
64589
  try {
64453
64590
  const absPath = resolve73(process.cwd(), mediaPath);
64454
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${absPath}` };
64591
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
64455
64592
  const openaiKey = process.env.OPENAI_API_KEY;
64456
64593
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
64457
64594
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -64468,7 +64605,7 @@ async function executeDub(options) {
64468
64605
  }
64469
64606
  const whisper = new WhisperProvider();
64470
64607
  await whisper.initialize({ apiKey: openaiKey });
64471
- const audioBuffer = await readFile48(audioPath);
64608
+ const audioBuffer = await readFile49(audioPath);
64472
64609
  const audioBlob = new Blob([audioBuffer]);
64473
64610
  const transcriptResult = await whisper.transcribe(audioBlob, source);
64474
64611
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -64579,8 +64716,8 @@ async function executeDuck(options) {
64579
64716
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
64580
64717
  const absMusicPath = resolve73(process.cwd(), musicPath);
64581
64718
  const absVoicePath = resolve73(process.cwd(), voicePath);
64582
- if (!existsSync54(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
64583
- if (!existsSync54(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
64719
+ if (!existsSync55(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
64720
+ if (!existsSync55(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
64584
64721
  const defaultOutput = resolve73(dirname26(absMusicPath), `${basename22(absMusicPath, extname15(absMusicPath))}-ducked${extname15(absMusicPath)}`);
64585
64722
  const outputPath = resolve73(process.cwd(), output || defaultOutput);
64586
64723
  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`;
@@ -64594,7 +64731,7 @@ async function executeDuck(options) {
64594
64731
  "-y",
64595
64732
  outputPath
64596
64733
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
64597
- if (!existsSync54(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
64734
+ if (!existsSync55(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
64598
64735
  return { success: true, outputPath };
64599
64736
  } catch (error) {
64600
64737
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -64611,8 +64748,8 @@ var init_ai_audio = __esm({
64611
64748
 
64612
64749
  // src/pipeline/executor.ts
64613
64750
  import { resolve as resolve74 } from "node:path";
64614
- import { readFile as readFile49, writeFile as writeFile52, mkdir as mkdir28 } from "node:fs/promises";
64615
- import { existsSync as existsSync55 } from "node:fs";
64751
+ import { readFile as readFile50, writeFile as writeFile52, mkdir as mkdir28 } from "node:fs/promises";
64752
+ import { existsSync as existsSync56 } from "node:fs";
64616
64753
  function maxCostFor(action) {
64617
64754
  const cmd = ACTION_METADATA[action]?.command;
64618
64755
  if (!cmd) return 0;
@@ -64927,10 +65064,10 @@ async function ensureActionsRegistered() {
64927
65064
  }
64928
65065
  async function loadPipeline(filePath) {
64929
65066
  const absPath = resolve74(process.cwd(), filePath);
64930
- if (!existsSync55(absPath)) {
65067
+ if (!existsSync56(absPath)) {
64931
65068
  throw new Error(`Pipeline file not found: ${absPath}`);
64932
65069
  }
64933
- const content = await readFile49(absPath, "utf-8");
65070
+ const content = await readFile50(absPath, "utf-8");
64934
65071
  const manifest2 = (0, import_yaml8.parse)(content);
64935
65072
  if (!manifest2.name) throw new Error("Pipeline missing 'name' field");
64936
65073
  if (!manifest2.steps || !Array.isArray(manifest2.steps)) throw new Error("Pipeline missing 'steps' array");
@@ -64952,8 +65089,8 @@ async function executePipeline(manifest2, options = {}) {
64952
65089
  const startTime = Date.now();
64953
65090
  if (options.resume) {
64954
65091
  const checkpointPath = resolve74(outputDir, CHECKPOINT_FILE);
64955
- if (existsSync55(checkpointPath)) {
64956
- const checkpointContent = await readFile49(checkpointPath, "utf-8");
65092
+ if (existsSync56(checkpointPath)) {
65093
+ const checkpointContent = await readFile50(checkpointPath, "utf-8");
64957
65094
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
64958
65095
  for (const cs of checkpoint.completedSteps) {
64959
65096
  completedSteps.set(cs.id, {
@@ -70883,14 +71020,14 @@ var init_edit = __esm({
70883
71020
  });
70884
71021
 
70885
71022
  // src/commands/ai-suggest-edit.ts
70886
- import { readFile as readFile50, writeFile as writeFile53 } from "node:fs/promises";
71023
+ import { readFile as readFile51, writeFile as writeFile53 } from "node:fs/promises";
70887
71024
  import { resolve as resolve76 } from "node:path";
70888
71025
  async function executeSuggestEdit(options) {
70889
71026
  try {
70890
71027
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
70891
71028
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
70892
71029
  const filePath = resolve76(process.cwd(), options.projectPath);
70893
- const content = await readFile50(filePath, "utf-8");
71030
+ const content = await readFile51(filePath, "utf-8");
70894
71031
  const data = JSON.parse(content);
70895
71032
  const project = Project.fromJSON(data);
70896
71033
  const gemini = new GeminiProvider();
@@ -71655,13 +71792,13 @@ var init_generate2 = __esm({
71655
71792
  });
71656
71793
 
71657
71794
  // src/tools/manifest/storyboard.ts
71658
- import { existsSync as existsSync56 } from "node:fs";
71659
- import { readFile as readFile51, writeFile as writeFile54 } from "node:fs/promises";
71795
+ import { existsSync as existsSync57 } from "node:fs";
71796
+ import { readFile as readFile52, writeFile as writeFile54 } from "node:fs/promises";
71660
71797
  import { resolve as resolve78 } from "node:path";
71661
71798
  async function readStoryboard2(projectDir) {
71662
71799
  const path2 = resolve78(projectDir, "STORYBOARD.md");
71663
- if (!existsSync56(path2)) return null;
71664
- return { path: path2, markdown: await readFile51(path2, "utf-8") };
71800
+ if (!existsSync57(path2)) return null;
71801
+ return { path: path2, markdown: await readFile52(path2, "utf-8") };
71665
71802
  }
71666
71803
  var PROJECT_DIR_DESCRIPTION3, projectDirSchema, storyboardListTool, storyboardValidateTool, storyboardGetTool, storyboardSetTool, storyboardMoveTool, storyboardReviseTool, planTool, storyboardTools;
71667
71804
  var init_storyboard2 = __esm({
@@ -71936,7 +72073,7 @@ var init_storyboard2 = __esm({
71936
72073
  // src/tools/manifest/remix.ts
71937
72074
  import { writeFile as writeFile55 } from "node:fs/promises";
71938
72075
  import { tmpdir as tmpdir4 } from "node:os";
71939
- import { join as join27 } from "node:path";
72076
+ import { join as join28 } from "node:path";
71940
72077
  var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, remixTools;
71941
72078
  var init_remix = __esm({
71942
72079
  "src/tools/manifest/remix.ts"() {
@@ -72050,7 +72187,7 @@ var init_remix = __esm({
72050
72187
  let resolvedPath = args.pipelinePath;
72051
72188
  let tempPath;
72052
72189
  if (args.pipelineYaml) {
72053
- tempPath = join27(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
72190
+ tempPath = join28(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
72054
72191
  await writeFile55(tempPath, args.pipelineYaml, "utf-8");
72055
72192
  resolvedPath = tempPath;
72056
72193
  }
@@ -72226,10 +72363,10 @@ var init_detect2 = __esm({
72226
72363
  });
72227
72364
 
72228
72365
  // src/tools/manifest/_project-io.ts
72229
- import { readFile as readFile52, writeFile as writeFile56 } from "node:fs/promises";
72366
+ import { readFile as readFile53, writeFile as writeFile56 } from "node:fs/promises";
72230
72367
  async function loadProject2(projectPath, cwd) {
72231
72368
  const absPath = await resolveTimelineFile(projectPath, cwd);
72232
- const content = await readFile52(absPath, "utf-8");
72369
+ const content = await readFile53(absPath, "utf-8");
72233
72370
  const data = JSON.parse(content);
72234
72371
  return { project: Project.fromJSON(data), absPath };
72235
72372
  }
@@ -72593,12 +72730,12 @@ var init_timeline = __esm({
72593
72730
  });
72594
72731
 
72595
72732
  // src/commands/_shared/project-list.ts
72596
- import { readdir as readdir7, readFile as readFile53, stat as stat9 } from "node:fs/promises";
72597
- import { existsSync as existsSync57 } from "node:fs";
72598
- import { join as join28, resolve as resolve80 } from "node:path";
72733
+ import { readdir as readdir7, readFile as readFile54, stat as stat9 } from "node:fs/promises";
72734
+ import { existsSync as existsSync58 } from "node:fs";
72735
+ import { join as join29, resolve as resolve80 } from "node:path";
72599
72736
  async function executeProjectList(opts = {}) {
72600
72737
  const workspaceDir = resolve80(opts.workspaceDir ?? ".");
72601
- if (!existsSync57(workspaceDir)) {
72738
+ if (!existsSync58(workspaceDir)) {
72602
72739
  return {
72603
72740
  success: false,
72604
72741
  workspaceDir,
@@ -72615,11 +72752,11 @@ async function executeProjectList(opts = {}) {
72615
72752
  if (entry.name === "node_modules") continue;
72616
72753
  if (entry.name.startsWith(".")) continue;
72617
72754
  if (entry.name.startsWith("_")) {
72618
- archivedCount += await countProjects(join28(workspaceDir, entry.name));
72755
+ archivedCount += await countProjects(join29(workspaceDir, entry.name));
72619
72756
  continue;
72620
72757
  }
72621
- const projectDir = join28(workspaceDir, entry.name);
72622
- if (!existsSync57(join28(projectDir, "STORYBOARD.md"))) continue;
72758
+ const projectDir = join29(workspaceDir, entry.name);
72759
+ if (!existsSync58(join29(projectDir, "STORYBOARD.md"))) continue;
72623
72760
  projects.push(await describeProject(entry.name, projectDir));
72624
72761
  }
72625
72762
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -72629,7 +72766,7 @@ async function countProjects(dir) {
72629
72766
  try {
72630
72767
  const entries = await readdir7(dir, { withFileTypes: true });
72631
72768
  return entries.filter(
72632
- (e) => e.isDirectory() && existsSync57(join28(dir, e.name, "STORYBOARD.md"))
72769
+ (e) => e.isDirectory() && existsSync58(join29(dir, e.name, "STORYBOARD.md"))
72633
72770
  ).length;
72634
72771
  } catch {
72635
72772
  return 0;
@@ -72640,8 +72777,8 @@ async function describeProject(name, projectDir) {
72640
72777
  let storyboardDurationSec = 0;
72641
72778
  const timestamps = [];
72642
72779
  try {
72643
- const storyboardPath2 = join28(projectDir, "STORYBOARD.md");
72644
- const parsed = parseStoryboard(await readFile53(storyboardPath2, "utf-8"));
72780
+ const storyboardPath2 = join29(projectDir, "STORYBOARD.md");
72781
+ const parsed = parseStoryboard(await readFile54(storyboardPath2, "utf-8"));
72645
72782
  beats = parsed.beats.length;
72646
72783
  storyboardDurationSec = Number(
72647
72784
  parsed.beats.reduce((sum, beat) => sum + (beat.duration ?? 0), 0).toFixed(2)
@@ -72652,8 +72789,8 @@ async function describeProject(name, projectDir) {
72652
72789
  let buildStatus;
72653
72790
  let buildUpdatedAt;
72654
72791
  try {
72655
- const reportPath = join28(projectDir, "build-report.json");
72656
- const report = JSON.parse(await readFile53(reportPath, "utf-8"));
72792
+ const reportPath = join29(projectDir, "build-report.json");
72793
+ const report = JSON.parse(await readFile54(reportPath, "utf-8"));
72657
72794
  buildStatus = report.status ?? report.phase;
72658
72795
  buildUpdatedAt = report.updatedAt;
72659
72796
  timestamps.push((await stat9(reportPath)).mtimeMs);
@@ -72661,12 +72798,12 @@ async function describeProject(name, projectDir) {
72661
72798
  }
72662
72799
  let latestRender;
72663
72800
  try {
72664
- const rendersDir = join28(projectDir, "renders");
72801
+ const rendersDir = join29(projectDir, "renders");
72665
72802
  const files = await readdir7(rendersDir);
72666
72803
  let newest;
72667
72804
  for (const file of files) {
72668
72805
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
72669
- const { mtimeMs } = await stat9(join28(rendersDir, file));
72806
+ const { mtimeMs } = await stat9(join29(rendersDir, file));
72670
72807
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
72671
72808
  }
72672
72809
  if (newest) {
@@ -72808,7 +72945,7 @@ var init_project2 = __esm({
72808
72945
  });
72809
72946
 
72810
72947
  // src/commands/export.ts
72811
- import { readFile as readFile54, access as access7 } from "node:fs/promises";
72948
+ import { readFile as readFile55, access as access7 } from "node:fs/promises";
72812
72949
  import { resolve as resolve82, basename as basename24 } from "node:path";
72813
72950
  import { spawn as spawn3 } from "node:child_process";
72814
72951
  function timelineBaseName(path2) {
@@ -72877,7 +73014,7 @@ async function runExport(projectPath, outputPath, options = {}) {
72877
73014
  };
72878
73015
  }
72879
73016
  const filePath = await resolveTimelineFile(projectPath);
72880
- const content = await readFile54(filePath, "utf-8");
73017
+ const content = await readFile55(filePath, "utf-8");
72881
73018
  const data = JSON.parse(content);
72882
73019
  const project = Project.fromJSON(data);
72883
73020
  const summary = project.getSummary();
@@ -73443,14 +73580,14 @@ function getPresetSettings(preset, aspectRatio2) {
73443
73580
  }
73444
73581
  async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
73445
73582
  spinner2.text = "Loading project...";
73446
- const { readFile: readFile56 } = await import("node:fs/promises");
73583
+ const { readFile: readFile57 } = await import("node:fs/promises");
73447
73584
  const { resolve: resolve86 } = await import("node:path");
73448
73585
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
73449
73586
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
73450
73587
  const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
73451
73588
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
73452
73589
  const filePath = await resolveTimelineFile(projectPath);
73453
- const content = await readFile56(filePath, "utf-8");
73590
+ const content = await readFile57(filePath, "utf-8");
73454
73591
  const project = Project2.fromJSON(JSON.parse(content));
73455
73592
  const state = project.getState();
73456
73593
  const outputPath = options.output ? resolve86(process.cwd(), options.output) : resolve86(process.cwd(), `${timelineBaseName(projectPath)}.${options.format ?? "mp4"}`);
@@ -73572,7 +73709,7 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
73572
73709
  }
73573
73710
  spinner2.text = "Loading project...";
73574
73711
  const filePath = await resolveTimelineFile(projectPath);
73575
- const content = await readFile54(filePath, "utf-8");
73712
+ const content = await readFile55(filePath, "utf-8");
73576
73713
  const data = JSON.parse(content);
73577
73714
  const project = Project.fromJSON(data);
73578
73715
  const summary = project.getSummary();
@@ -73707,8 +73844,8 @@ var init_export2 = __esm({
73707
73844
  });
73708
73845
 
73709
73846
  // src/tools/manifest/agent-only.ts
73710
- import { readFile as readFile55, writeFile as writeFile59, readdir as readdir8, stat as stat10, access as access8, unlink as unlink4 } from "node:fs/promises";
73711
- import { resolve as resolve84, join as join29, basename as basename25, extname as extname16 } from "node:path";
73847
+ import { readFile as readFile56, writeFile as writeFile59, readdir as readdir8, stat as stat10, access as access8, unlink as unlink4 } from "node:fs/promises";
73848
+ import { resolve as resolve84, join as join30, basename as basename25, extname as extname16 } from "node:path";
73712
73849
  function matchPattern(filename, pattern) {
73713
73850
  const regex2 = new RegExp(
73714
73851
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -73793,7 +73930,7 @@ var init_agent_only = __esm({
73793
73930
  const results = [];
73794
73931
  for (const entry of entries) {
73795
73932
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
73796
- const fullPath = join29(absPath, entry.name);
73933
+ const fullPath = join30(absPath, entry.name);
73797
73934
  const stats = await stat10(fullPath);
73798
73935
  if (entry.isDirectory()) {
73799
73936
  results.push(`[DIR] ${entry.name}/`);
@@ -73822,7 +73959,7 @@ var init_agent_only = __esm({
73822
73959
  async execute(args, ctx) {
73823
73960
  try {
73824
73961
  const absPath = resolve84(ctx.workingDirectory, args.path);
73825
- const content = await readFile55(absPath, "utf-8");
73962
+ const content = await readFile56(absPath, "utf-8");
73826
73963
  const maxLength = 4e3;
73827
73964
  const truncated = content.length > maxLength;
73828
73965
  const output = truncated ? content.substring(0, maxLength) + "\n... (truncated)" : content;
@@ -73895,7 +74032,7 @@ var init_agent_only = __esm({
73895
74032
  async execute(args, ctx) {
73896
74033
  try {
73897
74034
  const filePath = resolve84(ctx.workingDirectory, args.project);
73898
- const content = await readFile55(filePath, "utf-8");
74035
+ const content = await readFile56(filePath, "utf-8");
73899
74036
  const data = JSON.parse(content);
73900
74037
  const project = Project.fromJSON(data);
73901
74038
  const dirPath = resolve84(ctx.workingDirectory, args.directory);
@@ -73906,7 +74043,7 @@ var init_agent_only = __esm({
73906
74043
  const scanDir = async (dir) => {
73907
74044
  const entries = await readdir8(dir, { withFileTypes: true });
73908
74045
  for (const entry of entries) {
73909
- const entryPath = join29(dir, entry.name);
74046
+ const entryPath = join30(dir, entry.name);
73910
74047
  if (entry.isDirectory() && recursive) {
73911
74048
  await scanDir(entryPath);
73912
74049
  } else if (entry.isFile()) {
@@ -73967,7 +74104,7 @@ var init_agent_only = __esm({
73967
74104
  async execute(args, ctx) {
73968
74105
  try {
73969
74106
  const filePath = resolve84(ctx.workingDirectory, args.project);
73970
- const content = await readFile55(filePath, "utf-8");
74107
+ const content = await readFile56(filePath, "utf-8");
73971
74108
  const data = JSON.parse(content);
73972
74109
  const project = Project.fromJSON(data);
73973
74110
  const sourceIds = args.sourceIds ?? [];
@@ -74038,7 +74175,7 @@ var init_agent_only = __esm({
74038
74175
  async execute(args, ctx) {
74039
74176
  try {
74040
74177
  const filePath = resolve84(ctx.workingDirectory, args.project);
74041
- const content = await readFile55(filePath, "utf-8");
74178
+ const content = await readFile56(filePath, "utf-8");
74042
74179
  const data = JSON.parse(content);
74043
74180
  const project = Project.fromJSON(data);
74044
74181
  const clipIds = args.clipIds ?? [];
@@ -74093,7 +74230,7 @@ var init_agent_only = __esm({
74093
74230
  async execute(args, ctx) {
74094
74231
  try {
74095
74232
  const filePath = await resolveTimelineFile(args.project, ctx.workingDirectory);
74096
- const content = await readFile55(filePath, "utf-8");
74233
+ const content = await readFile56(filePath, "utf-8");
74097
74234
  const data = JSON.parse(content);
74098
74235
  const project = Project.fromJSON(data);
74099
74236
  const what = args.what ?? "clips";
@@ -74170,7 +74307,7 @@ var init_agent_only = __esm({
74170
74307
  async execute(args, ctx) {
74171
74308
  try {
74172
74309
  const filePath = await resolveTimelineFile(args.projectPath, ctx.workingDirectory);
74173
- const content = await readFile55(filePath, "utf-8");
74310
+ const content = await readFile56(filePath, "utf-8");
74174
74311
  const data = JSON.parse(content);
74175
74312
  const project = Project.fromJSON(data);
74176
74313
  const updates = [];
@@ -74444,8 +74581,8 @@ Audio:`);
74444
74581
  ],
74445
74582
  { maxBuffer: 50 * 1024 * 1024 }
74446
74583
  );
74447
- const inputBuf = await readFile55(absInput);
74448
- const outputBuf = await readFile55(outputPath);
74584
+ const inputBuf = await readFile56(absInput);
74585
+ const outputBuf = await readFile56(outputPath);
74449
74586
  const inputSize = inputBuf.length;
74450
74587
  const outputSize = outputBuf.length;
74451
74588
  const reduction = (inputSize - outputSize) / inputSize * 100;
@@ -74996,7 +75133,7 @@ init_source();
74996
75133
  // package.json
74997
75134
  var package_default = {
74998
75135
  name: "@vibeframe/cli",
74999
- version: "0.113.9",
75136
+ version: "0.113.11",
75000
75137
  description: "VibeFrame CLI - agentic video workflow runtime",
75001
75138
  private: false,
75002
75139
  license: "MIT",
@@ -75340,8 +75477,8 @@ init_validate();
75340
75477
  init_output();
75341
75478
  init_cost_tier();
75342
75479
  init_project_resolver();
75343
- import { readFile as readFile14, writeFile as writeFile13 } from "node:fs/promises";
75344
- import { existsSync as existsSync12 } from "node:fs";
75480
+ import { readFile as readFile15, writeFile as writeFile13 } from "node:fs/promises";
75481
+ import { existsSync as existsSync13 } from "node:fs";
75345
75482
  import { resolve as resolve18, basename as basename3, extname } from "node:path";
75346
75483
  var timelineCommand = new Command("timeline").description("Low-level timeline JSON commands").addHelpText(
75347
75484
  "after",
@@ -75390,7 +75527,7 @@ timelineCommand.command("add-source").description("Add a media source to the tim
75390
75527
  return;
75391
75528
  }
75392
75529
  const filePath = await resolveTimelineFile(projectPath);
75393
- const content = await readFile14(filePath, "utf-8");
75530
+ const content = await readFile15(filePath, "utf-8");
75394
75531
  const data = JSON.parse(content);
75395
75532
  const project = Project.fromJSON(data);
75396
75533
  const absMediaPath = resolve18(process.cwd(), mediaPath);
@@ -75455,7 +75592,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
75455
75592
  return;
75456
75593
  }
75457
75594
  const filePath = await resolveTimelineFile(projectPath);
75458
- const content = await readFile14(filePath, "utf-8");
75595
+ const content = await readFile15(filePath, "utf-8");
75459
75596
  const data = JSON.parse(content);
75460
75597
  const project = Project.fromJSON(data);
75461
75598
  const source = project.getSource(sourceId);
@@ -75532,7 +75669,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
75532
75669
  return;
75533
75670
  }
75534
75671
  const filePath = await resolveTimelineFile(projectPath);
75535
- const content = await readFile14(filePath, "utf-8");
75672
+ const content = await readFile15(filePath, "utf-8");
75536
75673
  const data = JSON.parse(content);
75537
75674
  const project = Project.fromJSON(data);
75538
75675
  const existingTracks = project.getTracksByType(type);
@@ -75596,7 +75733,7 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
75596
75733
  return;
75597
75734
  }
75598
75735
  const filePath = await resolveTimelineFile(projectPath);
75599
- const content = await readFile14(filePath, "utf-8");
75736
+ const content = await readFile15(filePath, "utf-8");
75600
75737
  const data = JSON.parse(content);
75601
75738
  const project = Project.fromJSON(data);
75602
75739
  const clip = project.getClip(clipId);
@@ -75666,7 +75803,7 @@ timelineCommand.command("trim-clip").description("Trim a clip").argument("<proje
75666
75803
  return;
75667
75804
  }
75668
75805
  const filePath = await resolveTimelineFile(projectPath);
75669
- const content = await readFile14(filePath, "utf-8");
75806
+ const content = await readFile15(filePath, "utf-8");
75670
75807
  const data = JSON.parse(content);
75671
75808
  const project = Project.fromJSON(data);
75672
75809
  const clip = project.getClip(clipId);
@@ -75708,10 +75845,10 @@ timelineCommand.command("list").description("List timeline contents").argument("
75708
75845
  const startedAt = Date.now();
75709
75846
  try {
75710
75847
  const filePath = await resolveTimelineFile(projectPath);
75711
- if (!existsSync12(filePath)) {
75848
+ if (!existsSync13(filePath)) {
75712
75849
  exitWithError(notFoundError(projectPath));
75713
75850
  }
75714
- const content = await readFile14(filePath, "utf-8");
75851
+ const content = await readFile15(filePath, "utf-8");
75715
75852
  const data = JSON.parse(content);
75716
75853
  const project = Project.fromJSON(data);
75717
75854
  const showAll = !options.sources && !options.tracks && !options.clips;
@@ -75832,7 +75969,7 @@ timelineCommand.command("split-clip").description("Split a clip at a specific ti
75832
75969
  return;
75833
75970
  }
75834
75971
  const filePath = await resolveTimelineFile(projectPath);
75835
- const content = await readFile14(filePath, "utf-8");
75972
+ const content = await readFile15(filePath, "utf-8");
75836
75973
  const data = JSON.parse(content);
75837
75974
  const project = Project.fromJSON(data);
75838
75975
  const clip = project.getClip(clipId);
@@ -75894,7 +76031,7 @@ timelineCommand.command("duplicate-clip").description("Duplicate a clip").argume
75894
76031
  return;
75895
76032
  }
75896
76033
  const filePath = await resolveTimelineFile(projectPath);
75897
- const content = await readFile14(filePath, "utf-8");
76034
+ const content = await readFile15(filePath, "utf-8");
75898
76035
  const data = JSON.parse(content);
75899
76036
  const project = Project.fromJSON(data);
75900
76037
  const clip = project.getClip(clipId);
@@ -75952,7 +76089,7 @@ timelineCommand.command("delete-clip").description("Delete a clip from the timel
75952
76089
  return;
75953
76090
  }
75954
76091
  const filePath = await resolveTimelineFile(projectPath);
75955
- const content = await readFile14(filePath, "utf-8");
76092
+ const content = await readFile15(filePath, "utf-8");
75956
76093
  const data = JSON.parse(content);
75957
76094
  const project = Project.fromJSON(data);
75958
76095
  const clip = project.getClip(clipId);
@@ -76007,7 +76144,7 @@ timelineCommand.command("move-clip").description("Move a clip to a new position"
76007
76144
  return;
76008
76145
  }
76009
76146
  const filePath = await resolveTimelineFile(projectPath);
76010
- const content = await readFile14(filePath, "utf-8");
76147
+ const content = await readFile15(filePath, "utf-8");
76011
76148
  const data = JSON.parse(content);
76012
76149
  const project = Project.fromJSON(data);
76013
76150
  const clip = project.getClip(clipId);
@@ -76093,7 +76230,7 @@ init_validate();
76093
76230
  init_cost_tier();
76094
76231
  init_scene_inspect();
76095
76232
  init_render_inspect();
76096
- import { readFile as readFile31, writeFile as writeFile37 } from "node:fs/promises";
76233
+ import { readFile as readFile32, writeFile as writeFile37 } from "node:fs/promises";
76097
76234
  import { resolve as resolve44 } from "node:path";
76098
76235
  var VALID_RENDER_AI_MODELS = [
76099
76236
  "flash",
@@ -76446,7 +76583,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
76446
76583
  const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
76447
76584
  const spinner2 = ora("Initializing Gemini...").start();
76448
76585
  const filePath = resolve44(process.cwd(), projectPath);
76449
- const content = await readFile31(filePath, "utf-8");
76586
+ const content = await readFile32(filePath, "utf-8");
76450
76587
  const data = JSON.parse(content);
76451
76588
  const project = Project.fromJSON(data);
76452
76589
  const gemini = new GeminiProvider();
@@ -76515,8 +76652,8 @@ init_ai_helpers();
76515
76652
  init_output();
76516
76653
  init_validate();
76517
76654
  import { resolve as resolve45, dirname as dirname16, basename as basename12, extname as extname8 } from "node:path";
76518
- import { readFile as readFile32, writeFile as writeFile38 } from "node:fs/promises";
76519
- import { existsSync as existsSync36 } from "node:fs";
76655
+ import { readFile as readFile33, writeFile as writeFile38 } from "node:fs/promises";
76656
+ import { existsSync as existsSync37 } from "node:fs";
76520
76657
  var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, dub, duck, isolate, voice clone)").addHelpText(
76521
76658
  "after",
76522
76659
  `
@@ -76553,7 +76690,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
76553
76690
  await whisper.initialize({ apiKey });
76554
76691
  spinner2.text = "Reading audio file...";
76555
76692
  const absPath = resolve45(process.cwd(), audioPath);
76556
- const audioBuffer = await readFile32(absPath);
76693
+ const audioBuffer = await readFile33(absPath);
76557
76694
  const audioBlob = new Blob([audioBuffer]);
76558
76695
  spinner2.text = "Transcribing...";
76559
76696
  const result = await whisper.transcribe(audioBlob, options.language);
@@ -76657,7 +76794,7 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
76657
76794
  const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
76658
76795
  const spinner2 = ora("Reading audio file...").start();
76659
76796
  const absPath = resolve45(process.cwd(), audioPath);
76660
- const audioBuffer = await readFile32(absPath);
76797
+ const audioBuffer = await readFile33(absPath);
76661
76798
  spinner2.text = "Isolating vocals...";
76662
76799
  const elevenlabs = new ElevenLabsProvider();
76663
76800
  await elevenlabs.initialize({ apiKey });
@@ -76728,11 +76865,11 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
76728
76865
  const audioBuffers = [];
76729
76866
  for (const samplePath of samples) {
76730
76867
  const absPath = resolve45(process.cwd(), samplePath);
76731
- if (!existsSync36(absPath)) {
76868
+ if (!existsSync37(absPath)) {
76732
76869
  spinner2.fail(`File not found: ${samplePath}`);
76733
76870
  exitWithError(notFoundError(samplePath));
76734
76871
  }
76735
- const buffer = await readFile32(absPath);
76872
+ const buffer = await readFile33(absPath);
76736
76873
  audioBuffers.push(buffer);
76737
76874
  }
76738
76875
  spinner2.text = `Cloning voice from ${audioBuffers.length} sample(s)...`;
@@ -76796,7 +76933,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
76796
76933
  exitWithError(usageError("Target language is required. Use -l or --language"));
76797
76934
  }
76798
76935
  const absPath = resolve45(process.cwd(), mediaPath);
76799
- if (!existsSync36(absPath)) {
76936
+ if (!existsSync37(absPath)) {
76800
76937
  exitWithError(notFoundError(mediaPath));
76801
76938
  }
76802
76939
  const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
@@ -76821,7 +76958,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
76821
76958
  spinner2.text = "Transcribing audio...";
76822
76959
  const whisper = new WhisperProvider();
76823
76960
  await whisper.initialize({ apiKey: openaiKey });
76824
- const audioBuffer = await readFile32(audioPath);
76961
+ const audioBuffer = await readFile33(audioPath);
76825
76962
  const audioBlob = new Blob([audioBuffer]);
76826
76963
  const transcriptResult = await whisper.transcribe(audioBlob, options.source);
76827
76964
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -77077,7 +77214,7 @@ init_esm();
77077
77214
  init_source();
77078
77215
  init_ora();
77079
77216
  import { resolve as resolve50, basename as basename15 } from "node:path";
77080
- import { existsSync as existsSync41 } from "node:fs";
77217
+ import { existsSync as existsSync42 } from "node:fs";
77081
77218
 
77082
77219
  // src/commands/ai-script-pipeline-cli.ts
77083
77220
  init_source();
@@ -77086,9 +77223,9 @@ var import_yaml7 = __toESM(require_dist2(), 1);
77086
77223
  init_api_key();
77087
77224
  init_ai_script_pipeline();
77088
77225
  init_output();
77089
- import { readFile as readFile34, writeFile as writeFile40 } from "node:fs/promises";
77226
+ import { readFile as readFile35, writeFile as writeFile40 } from "node:fs/promises";
77090
77227
  import { resolve as resolve47 } from "node:path";
77091
- import { existsSync as existsSync38 } from "node:fs";
77228
+ import { existsSync as existsSync39 } from "node:fs";
77092
77229
  function registerScriptPipelineCommands(aiCommand) {
77093
77230
  aiCommand.command("regenerate-scene", { hidden: true }).description("Regenerate a specific scene in a script-to-video output directory").argument("<project-dir>", "Path to the script-to-video output directory").requiredOption(
77094
77231
  "--scene <numbers>",
@@ -77105,12 +77242,12 @@ function registerScriptPipelineCommands(aiCommand) {
77105
77242
  try {
77106
77243
  const outputDir = resolve47(process.cwd(), projectDir);
77107
77244
  const projectPath = resolve47(outputDir, "timeline.json");
77108
- if (!existsSync38(outputDir)) {
77245
+ if (!existsSync39(outputDir)) {
77109
77246
  exitWithError(notFoundError(outputDir));
77110
77247
  }
77111
77248
  const yamlPath = resolve47(outputDir, "storyboard.yaml");
77112
77249
  const jsonPath = resolve47(outputDir, "storyboard.json");
77113
- const storyboardPath2 = existsSync38(yamlPath) ? yamlPath : existsSync38(jsonPath) ? jsonPath : null;
77250
+ const storyboardPath2 = existsSync39(yamlPath) ? yamlPath : existsSync39(jsonPath) ? jsonPath : null;
77114
77251
  const storyboardIsYaml = storyboardPath2 === yamlPath;
77115
77252
  if (!storyboardPath2) {
77116
77253
  exitWithError(notFoundError(`${outputDir}/storyboard.{yaml,json}`));
@@ -77146,7 +77283,7 @@ function registerScriptPipelineCommands(aiCommand) {
77146
77283
  });
77147
77284
  return;
77148
77285
  }
77149
- const content = await readFile34(storyboardPath2, "utf-8");
77286
+ const content = await readFile35(storyboardPath2, "utf-8");
77150
77287
  const segments = storyboardIsYaml ? (0, import_yaml7.parse)(content).scenes : JSON.parse(content);
77151
77288
  for (const sceneNum of sceneNums) {
77152
77289
  if (sceneNum > segments.length) {
@@ -77229,12 +77366,12 @@ function registerScriptPipelineCommands(aiCommand) {
77229
77366
  exitWithError(apiError(result.error || "Scene regeneration failed", true));
77230
77367
  }
77231
77368
  spinner2.succeed(source_default.green(`Regenerated ${result.regeneratedScenes.length} scene(s)`));
77232
- if (existsSync38(projectPath) && result.regeneratedScenes.length > 0) {
77369
+ if (existsSync39(projectPath) && result.regeneratedScenes.length > 0) {
77233
77370
  const updateSpinner = ora("Updating timeline file...").start();
77234
77371
  try {
77235
- const updatedContent = await readFile34(storyboardPath2, "utf-8");
77372
+ const updatedContent = await readFile35(storyboardPath2, "utf-8");
77236
77373
  const updatedSegments = storyboardIsYaml ? (0, import_yaml7.parse)(updatedContent).scenes : JSON.parse(updatedContent);
77237
- const projectContent = await readFile34(projectPath, "utf-8");
77374
+ const projectContent = await readFile35(projectPath, "utf-8");
77238
77375
  const projectData = JSON.parse(projectContent);
77239
77376
  for (const sceneNum of result.regeneratedScenes) {
77240
77377
  const segment = updatedSegments[sceneNum - 1];
@@ -77245,7 +77382,7 @@ function registerScriptPipelineCommands(aiCommand) {
77245
77382
  (s) => s.name === `Scene ${sceneNum}`
77246
77383
  );
77247
77384
  if (videoSource) {
77248
- const hasVideo = existsSync38(videoPath);
77385
+ const hasVideo = existsSync39(videoPath);
77249
77386
  videoSource.url = hasVideo ? videoPath : imagePath;
77250
77387
  videoSource.type = hasVideo ? "video" : "image";
77251
77388
  videoSource.duration = segment.duration;
@@ -77370,7 +77507,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
77370
77507
  const startedAt = Date.now();
77371
77508
  try {
77372
77509
  const absVideoPath = resolve50(process.cwd(), videoPath);
77373
- if (!existsSync41(absVideoPath)) {
77510
+ if (!existsSync42(absVideoPath)) {
77374
77511
  exitWithError(notFoundError(absVideoPath));
77375
77512
  }
77376
77513
  if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
@@ -81753,8 +81890,8 @@ init_ora();
81753
81890
  init_engine();
81754
81891
  init_output();
81755
81892
  init_cost_tier();
81756
- import { readFile as readFile36, writeFile as writeFile43, readdir as readdir6 } from "node:fs/promises";
81757
- import { resolve as resolve52, basename as basename17, extname as extname12, join as join20 } from "node:path";
81893
+ import { readFile as readFile37, writeFile as writeFile43, readdir as readdir6 } from "node:fs/promises";
81894
+ import { resolve as resolve52, basename as basename17, extname as extname12, join as join21 } from "node:path";
81758
81895
  var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
81759
81896
  function detectMediaType3(filePath) {
81760
81897
  const ext = extname12(filePath).toLowerCase();
@@ -81812,7 +81949,7 @@ batchCommand.command("import").description("Import multiple media files from a d
81812
81949
  return;
81813
81950
  }
81814
81951
  const filePath = resolve52(process.cwd(), projectPath);
81815
- const content = await readFile36(filePath, "utf-8");
81952
+ const content = await readFile37(filePath, "utf-8");
81816
81953
  const data = JSON.parse(content);
81817
81954
  const project = Project.fromJSON(data);
81818
81955
  const dirPath = resolve52(process.cwd(), directory);
@@ -81821,7 +81958,7 @@ batchCommand.command("import").description("Import multiple media files from a d
81821
81958
  const scanDir = async (dir) => {
81822
81959
  const entries = await readdir6(dir, { withFileTypes: true });
81823
81960
  for (const entry of entries) {
81824
- const entryPath = join20(dir, entry.name);
81961
+ const entryPath = join21(dir, entry.name);
81825
81962
  if (entry.isDirectory() && options.recursive) {
81826
81963
  await scanDir(entryPath);
81827
81964
  } else if (entry.isFile()) {
@@ -81890,7 +82027,7 @@ batchCommand.command("concat").description("Concatenate multiple sources into se
81890
82027
  return;
81891
82028
  }
81892
82029
  const filePath = resolve52(process.cwd(), projectPath);
81893
- const content = await readFile36(filePath, "utf-8");
82030
+ const content = await readFile37(filePath, "utf-8");
81894
82031
  const data = JSON.parse(content);
81895
82032
  const project = Project.fromJSON(data);
81896
82033
  const sources = project.getSources();
@@ -81969,7 +82106,7 @@ batchCommand.command("apply-effect").description("Apply an effect to multiple cl
81969
82106
  return;
81970
82107
  }
81971
82108
  const filePath = resolve52(process.cwd(), projectPath);
81972
- const content = await readFile36(filePath, "utf-8");
82109
+ const content = await readFile37(filePath, "utf-8");
81973
82110
  const data = JSON.parse(content);
81974
82111
  const project = Project.fromJSON(data);
81975
82112
  const clips = project.getClips();
@@ -82037,7 +82174,7 @@ batchCommand.command("remove-clips").description("Remove multiple clips from the
82037
82174
  return;
82038
82175
  }
82039
82176
  const filePath = resolve52(process.cwd(), projectPath);
82040
- const content = await readFile36(filePath, "utf-8");
82177
+ const content = await readFile37(filePath, "utf-8");
82041
82178
  const data = JSON.parse(content);
82042
82179
  const project = Project.fromJSON(data);
82043
82180
  const clips = project.getClips();
@@ -82075,7 +82212,7 @@ batchCommand.command("info").description("Show batch processing statistics").arg
82075
82212
  const spinner2 = ora("Loading project...").start();
82076
82213
  try {
82077
82214
  const filePath = resolve52(process.cwd(), projectPath);
82078
- const content = await readFile36(filePath, "utf-8");
82215
+ const content = await readFile37(filePath, "utf-8");
82079
82216
  const data = JSON.parse(content);
82080
82217
  const project = Project.fromJSON(data);
82081
82218
  const sources = project.getSources();
@@ -82135,7 +82272,7 @@ init_output();
82135
82272
  init_tty();
82136
82273
  init_api_key();
82137
82274
  import { resolve as resolve54 } from "node:path";
82138
- import { access as access6, readFile as readFile38 } from "node:fs/promises";
82275
+ import { access as access6, readFile as readFile39 } from "node:fs/promises";
82139
82276
  import { homedir as homedir6 } from "node:os";
82140
82277
 
82141
82278
  // src/utils/key-format.ts
@@ -83336,7 +83473,7 @@ async function showConfig(opts = { verbose: false }) {
83336
83473
  let dotenvKeys = {};
83337
83474
  if (hasCwdEnv) {
83338
83475
  try {
83339
- const envContent = await readFile38(cwdEnvPath, "utf-8");
83476
+ const envContent = await readFile39(cwdEnvPath, "utf-8");
83340
83477
  dotenvKeys = (0, import_dotenv2.parse)(Buffer.from(envContent));
83341
83478
  } catch {
83342
83479
  }
@@ -83433,8 +83570,8 @@ init_source();
83433
83570
  init_agent_host_detect();
83434
83571
  init_scene_project();
83435
83572
  init_visual_styles();
83436
- import { existsSync as existsSync43 } from "node:fs";
83437
- import { mkdir as mkdir24, readFile as readFile40, writeFile as writeFile46 } from "node:fs/promises";
83573
+ import { existsSync as existsSync44 } from "node:fs";
83574
+ import { mkdir as mkdir24, readFile as readFile41, writeFile as writeFile46 } from "node:fs/promises";
83438
83575
  import { basename as basename19, dirname as dirname20, resolve as resolve56 } from "node:path";
83439
83576
 
83440
83577
  // src/commands/_shared/storyboard-draft.ts
@@ -83597,10 +83734,10 @@ init_project_config();
83597
83734
  init_install_skill();
83598
83735
 
83599
83736
  // src/commands/_shared/host-integration.ts
83600
- import { existsSync as existsSync42 } from "node:fs";
83601
- import { mkdir as mkdir23, readFile as readFile39, writeFile as writeFile45 } from "node:fs/promises";
83737
+ import { existsSync as existsSync43 } from "node:fs";
83738
+ import { mkdir as mkdir23, readFile as readFile40, writeFile as writeFile45 } from "node:fs/promises";
83602
83739
  import { homedir as homedir7 } from "node:os";
83603
- import { dirname as dirname19, join as join21, resolve as resolve55 } from "node:path";
83740
+ import { dirname as dirname19, join as join22, resolve as resolve55 } from "node:path";
83604
83741
  var HOST_SELECTIONS = [
83605
83742
  "codex",
83606
83743
  "claude",
@@ -83627,7 +83764,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
83627
83764
  label: "OpenAI Codex",
83628
83765
  surfaces: ["shell", "project-guidance", "mcp"],
83629
83766
  guidanceFiles: ["AGENTS.md"],
83630
- configPath: (dir) => join21(resolve55(dir), ".codex", "config.toml"),
83767
+ configPath: (dir) => join22(resolve55(dir), ".codex", "config.toml"),
83631
83768
  configKind: "codex-toml",
83632
83769
  notes: [
83633
83770
  "Codex reads AGENTS.md and can also load project-scoped .codex/config.toml after the project is trusted.",
@@ -83640,7 +83777,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
83640
83777
  label: "Claude Code",
83641
83778
  surfaces: ["shell", "project-guidance", "mcp"],
83642
83779
  guidanceFiles: ["AGENTS.md", "CLAUDE.md", ".claude/skills/hyperframes/"],
83643
- configPath: (dir) => join21(resolve55(dir), ".mcp.json"),
83780
+ configPath: (dir) => join22(resolve55(dir), ".mcp.json"),
83644
83781
  configKind: "mcp-json",
83645
83782
  notes: [
83646
83783
  "Claude Code can drive vibe directly through shell plus AGENTS.md/CLAUDE.md.",
@@ -83665,7 +83802,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
83665
83802
  label: "Cursor",
83666
83803
  surfaces: ["shell", "project-guidance", "mcp"],
83667
83804
  guidanceFiles: ["AGENTS.md", ".cursor/rules/hyperframes.mdc"],
83668
- configPath: (dir) => join21(resolve55(dir), ".cursor", "mcp.json"),
83805
+ configPath: (dir) => join22(resolve55(dir), ".cursor", "mcp.json"),
83669
83806
  configKind: "mcp-json",
83670
83807
  notes: [
83671
83808
  "Cursor can use AGENTS.md/rules for shell-driven work and .cursor/mcp.json for typed MCP tools."
@@ -83706,11 +83843,11 @@ async function planHostSetup(host, projectDir, opts) {
83706
83843
  const warnings = [];
83707
83844
  const nextSteps = hostNextSteps(host);
83708
83845
  for (const relPath of host.guidanceFiles.filter((p) => !p.endsWith("/"))) {
83709
- const absPath = join21(resolve55(projectDir), relPath);
83846
+ const absPath = join22(resolve55(projectDir), relPath);
83710
83847
  files.push({
83711
83848
  path: absPath,
83712
- status: existsSync42(absPath) ? "skipped-exists" : "missing",
83713
- reason: existsSync42(absPath) ? "already present" : "run vibe init --agent all first"
83849
+ status: existsSync43(absPath) ? "skipped-exists" : "missing",
83850
+ reason: existsSync43(absPath) ? "already present" : "run vibe init --agent all first"
83714
83851
  });
83715
83852
  }
83716
83853
  if (!opts.write || opts.dryRun) {
@@ -83754,17 +83891,17 @@ async function inspectHost(host, projectDir) {
83754
83891
  const warnings = [];
83755
83892
  const nextSteps = hostNextSteps(host);
83756
83893
  const guidanceFiles = host.guidanceFiles.map((relPath) => {
83757
- const absPath = relPath.endsWith("/") ? join21(resolve55(projectDir), relPath) : join21(resolve55(projectDir), relPath);
83894
+ const absPath = relPath.endsWith("/") ? join22(resolve55(projectDir), relPath) : join22(resolve55(projectDir), relPath);
83758
83895
  return {
83759
83896
  path: absPath,
83760
- status: existsSync42(absPath) ? "skipped-exists" : "missing",
83761
- reason: existsSync42(absPath) ? "present" : "missing"
83897
+ status: existsSync43(absPath) ? "skipped-exists" : "missing",
83898
+ reason: existsSync43(absPath) ? "present" : "missing"
83762
83899
  };
83763
83900
  });
83764
83901
  let configured = false;
83765
- if (existsSync42(configPath)) {
83902
+ if (existsSync43(configPath)) {
83766
83903
  try {
83767
- const content = await readFile39(configPath, "utf-8");
83904
+ const content = await readFile40(configPath, "utf-8");
83768
83905
  if (host.configKind === "codex-toml") {
83769
83906
  configured = /\[mcp_servers\.vibeframe\]/.test(content);
83770
83907
  } else {
@@ -83785,9 +83922,9 @@ async function inspectHost(host, projectDir) {
83785
83922
  }
83786
83923
  async function mergeMcpJson(configPath, opts) {
83787
83924
  let root = {};
83788
- const exists = existsSync42(configPath);
83925
+ const exists = existsSync43(configPath);
83789
83926
  if (exists) {
83790
- const raw = await readFile39(configPath, "utf-8");
83927
+ const raw = await readFile40(configPath, "utf-8");
83791
83928
  try {
83792
83929
  root = raw.trim() ? JSON.parse(raw) : {};
83793
83930
  } catch (error) {
@@ -83808,7 +83945,7 @@ async function mergeMcpJson(configPath, opts) {
83808
83945
  if (exists && opts.backup) {
83809
83946
  await writeFile45(
83810
83947
  `${configPath}.bak-${timestamp()}`,
83811
- await readFile39(configPath, "utf-8"),
83948
+ await readFile40(configPath, "utf-8"),
83812
83949
  "utf-8"
83813
83950
  );
83814
83951
  }
@@ -83830,7 +83967,7 @@ async function mergeMcpJson(configPath, opts) {
83830
83967
  if (exists && opts.backup) {
83831
83968
  await writeFile45(
83832
83969
  `${configPath}.bak-${timestamp()}`,
83833
- await readFile39(configPath, "utf-8"),
83970
+ await readFile40(configPath, "utf-8"),
83834
83971
  "utf-8"
83835
83972
  );
83836
83973
  }
@@ -83838,7 +83975,7 @@ async function mergeMcpJson(configPath, opts) {
83838
83975
  return { path: configPath, status: exists ? "merged" : "wrote" };
83839
83976
  }
83840
83977
  async function mergeCodexToml(configPath, opts) {
83841
- const exists = existsSync42(configPath);
83978
+ const exists = existsSync43(configPath);
83842
83979
  const block = `${CODEX_MANAGED_START}
83843
83980
  ${CODEX_MCP_BLOCK}${CODEX_MANAGED_END}`;
83844
83981
  if (!exists) {
@@ -83853,7 +83990,7 @@ ${block}
83853
83990
  );
83854
83991
  return { path: configPath, status: "wrote" };
83855
83992
  }
83856
- const raw = await readFile39(configPath, "utf-8");
83993
+ const raw = await readFile40(configPath, "utf-8");
83857
83994
  if (raw.includes(CODEX_MANAGED_START)) {
83858
83995
  const updated = raw.replace(
83859
83996
  new RegExp(
@@ -83942,16 +84079,16 @@ function hostNextSteps(host) {
83942
84079
  function claudeDesktopConfigPath() {
83943
84080
  const home = homedir7();
83944
84081
  if (process.platform === "darwin") {
83945
- return join21(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
84082
+ return join22(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
83946
84083
  }
83947
84084
  if (process.platform === "win32") {
83948
- return join21(
83949
- process.env.APPDATA ?? join21(home, "AppData", "Roaming"),
84085
+ return join22(
84086
+ process.env.APPDATA ?? join22(home, "AppData", "Roaming"),
83950
84087
  "Claude",
83951
84088
  "claude_desktop_config.json"
83952
84089
  );
83953
84090
  }
83954
- return join21(home, ".config", "Claude", "claude_desktop_config.json");
84091
+ return join22(home, ".config", "Claude", "claude_desktop_config.json");
83955
84092
  }
83956
84093
  function timestamp() {
83957
84094
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -84404,7 +84541,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
84404
84541
  const wantsGemini = targetHosts.includes("gemini-cli");
84405
84542
  const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
84406
84543
  const actions = [];
84407
- if (!existsSync43(projectDir)) {
84544
+ if (!existsSync44(projectDir)) {
84408
84545
  if (options.dryRun) {
84409
84546
  actions.push({
84410
84547
  path: projectDir,
@@ -84619,11 +84756,11 @@ async function runSceneInit(projectDirArg, options, startedAt) {
84619
84756
  });
84620
84757
  const storyboardPath2 = resolve56(projectDir, "STORYBOARD.md");
84621
84758
  const designPath = resolve56(projectDir, "DESIGN.md");
84622
- if (options.force || result.created.includes(storyboardPath2) || !existsSync43(storyboardPath2)) {
84759
+ if (options.force || result.created.includes(storyboardPath2) || !existsSync44(storyboardPath2)) {
84623
84760
  await writeFile46(storyboardPath2, draft.storyboardMd, "utf-8");
84624
84761
  if (!result.created.includes(storyboardPath2)) result.created.push(storyboardPath2);
84625
84762
  }
84626
- if (options.force || result.created.includes(designPath) || !existsSync43(designPath)) {
84763
+ if (options.force || result.created.includes(designPath) || !existsSync44(designPath)) {
84627
84764
  await writeFile46(designPath, draft.designMd, "utf-8");
84628
84765
  if (!result.created.includes(designPath)) result.created.push(designPath);
84629
84766
  }
@@ -84747,8 +84884,8 @@ function printGroup(label, files, projectDir) {
84747
84884
  }
84748
84885
  async function readBriefOrLiteral(value, cwd) {
84749
84886
  const candidate = resolve56(cwd, value);
84750
- if (existsSync43(candidate)) {
84751
- return readFile40(candidate, "utf-8");
84887
+ if (existsSync44(candidate)) {
84888
+ return readFile41(candidate, "utf-8");
84752
84889
  }
84753
84890
  return value;
84754
84891
  }
@@ -84801,7 +84938,7 @@ function normalizeHostAction(status) {
84801
84938
  return "would-write";
84802
84939
  }
84803
84940
  async function writeIfMissing(absPath, content, force, dryRun) {
84804
- const exists = existsSync43(absPath);
84941
+ const exists = existsSync44(absPath);
84805
84942
  if (exists && !force) {
84806
84943
  return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
84807
84944
  }
@@ -84814,12 +84951,12 @@ async function writeIfMissing(absPath, content, force, dryRun) {
84814
84951
  }
84815
84952
  async function mergeGitignore(absPath, additions, dryRun) {
84816
84953
  const marker = "# VibeFrame";
84817
- if (!existsSync43(absPath)) {
84954
+ if (!existsSync44(absPath)) {
84818
84955
  if (dryRun) return { path: absPath, status: "would-write" };
84819
84956
  await writeFile46(absPath, additions.trimStart(), "utf-8");
84820
84957
  return { path: absPath, status: "wrote" };
84821
84958
  }
84822
- const existing = await readFile40(absPath, "utf-8");
84959
+ const existing = await readFile41(absPath, "utf-8");
84823
84960
  if (existing.includes(marker)) {
84824
84961
  return { path: absPath, status: "skipped-exists", reason: "(already has VibeFrame block)" };
84825
84962
  }
@@ -84852,8 +84989,8 @@ init_storyboard_revise();
84852
84989
  init_composer_resolve();
84853
84990
  init_cost_tier();
84854
84991
  init_output();
84855
- import { readFile as readFile42, writeFile as writeFile48 } from "node:fs/promises";
84856
- import { existsSync as existsSync45 } from "node:fs";
84992
+ import { readFile as readFile43, writeFile as writeFile48 } from "node:fs/promises";
84993
+ import { existsSync as existsSync46 } from "node:fs";
84857
84994
  import { resolve as resolve58 } from "node:path";
84858
84995
  var storyboardCommand = new Command("storyboard").description("Read, validate, and safely mutate STORYBOARD.md cue blocks").addHelpText("after", `
84859
84996
  Examples:
@@ -85036,10 +85173,10 @@ storyboardCommand.command("validate").description("Validate cue blocks and beat
85036
85173
  });
85037
85174
  async function readStoryboard(projectDir) {
85038
85175
  const path2 = storyboardPath(projectDir);
85039
- if (!existsSync45(path2)) {
85176
+ if (!existsSync46(path2)) {
85040
85177
  exitWithError(generalError(`STORYBOARD.md not found at ${path2}`, `Run 'vibe init ${projectDir} --from "<brief>"' first.`));
85041
85178
  }
85042
- return readFile42(path2, "utf-8");
85179
+ return readFile43(path2, "utf-8");
85043
85180
  }
85044
85181
  function storyboardPath(projectDir) {
85045
85182
  return resolve58(projectDir, "STORYBOARD.md");
@@ -85051,8 +85188,8 @@ function parseComposer(value) {
85051
85188
  }
85052
85189
  async function readBrief(value, cwd) {
85053
85190
  const candidate = resolve58(cwd, value);
85054
- if (existsSync45(candidate)) {
85055
- return readFile42(candidate, "utf-8");
85191
+ if (existsSync46(candidate)) {
85192
+ return readFile43(candidate, "utf-8");
85056
85193
  }
85057
85194
  return value;
85058
85195
  }
@@ -85278,8 +85415,8 @@ init_scene_build();
85278
85415
  init_build_plan();
85279
85416
  init_storyboard_parse();
85280
85417
  init_output();
85281
- import { resolve as resolve66, join as join26 } from "node:path";
85282
- import { readFileSync as readFileSync4, existsSync as existsSync50 } from "node:fs";
85418
+ import { resolve as resolve66, join as join27 } from "node:path";
85419
+ import { readFileSync as readFileSync4, existsSync as existsSync51 } from "node:fs";
85283
85420
  var VALID_MODES2 = ["agent", "batch", "auto"];
85284
85421
  var VALID_STAGES2 = ["assets", "compose", "sync", "render", "all"];
85285
85422
  var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
@@ -85288,7 +85425,10 @@ var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
85288
85425
  var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-video", "Don't dispatch video generation even when beats declare video cues").option(
85289
85426
  "--skip-keyframe",
85290
85427
  "Don't generate keyframe stills (review keyframes first with --skip-video, then build)"
85291
- ).option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
85428
+ ).option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option(
85429
+ "--skip-transcript",
85430
+ "Don't transcribe narration for word-sync (default: transcribe when narration + OpenAI key exist)"
85431
+ ).option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
85292
85432
  "after",
85293
85433
  `
85294
85434
  Examples:
@@ -85349,6 +85489,7 @@ Advanced equivalent: \`vibe scene build\`.`
85349
85489
  skipVideo: options.skipVideo ?? false,
85350
85490
  skipKeyframe: options.skipKeyframe ?? false,
85351
85491
  skipMusic: options.skipMusic ?? false,
85492
+ skipTranscript: options.skipTranscript ?? false,
85352
85493
  skipRender: options.skipRender ?? false,
85353
85494
  ttsProvider: options.tts,
85354
85495
  voice: options.voice,
@@ -85370,6 +85511,7 @@ Advanced equivalent: \`vibe scene build\`.`
85370
85511
  skipVideo: options.skipVideo,
85371
85512
  skipKeyframe: options.skipKeyframe,
85372
85513
  skipMusic: options.skipMusic,
85514
+ skipTranscript: options.skipTranscript,
85373
85515
  ttsProvider: options.tts,
85374
85516
  voice: options.voice,
85375
85517
  imageProvider,
@@ -85461,6 +85603,7 @@ Advanced equivalent: \`vibe scene build\`.`
85461
85603
  skipVideo: options.skipVideo,
85462
85604
  skipKeyframe: options.skipKeyframe,
85463
85605
  skipMusic: options.skipMusic,
85606
+ skipTranscript: options.skipTranscript,
85464
85607
  skipRender: options.skipRender,
85465
85608
  ttsProvider: options.tts,
85466
85609
  voice: options.voice,
@@ -85473,6 +85616,8 @@ Advanced equivalent: \`vibe scene build\`.`
85473
85616
  onProgress: (e) => {
85474
85617
  if (!spinner2) return;
85475
85618
  if (e.type === "phase-start") spinner2.text = `Phase: ${e.phase}...`;
85619
+ else if (e.type === "transcript-generated")
85620
+ spinner2.text = `Transcribed narration: ${e.beatId} (${e.wordCount} words)`;
85476
85621
  else if (e.type === "render-done") spinner2.text = `Rendered: ${e.outputPath}`;
85477
85622
  }
85478
85623
  });
@@ -85648,8 +85793,8 @@ function buildPlanValidationFailureData(plan) {
85648
85793
  };
85649
85794
  }
85650
85795
  function estimateBuildCost(params) {
85651
- const storyboardPath2 = join26(params.projectDir, "STORYBOARD.md");
85652
- if (!existsSync50(storyboardPath2)) return null;
85796
+ const storyboardPath2 = join27(params.projectDir, "STORYBOARD.md");
85797
+ if (!existsSync51(storyboardPath2)) return null;
85653
85798
  let beats = 0;
85654
85799
  try {
85655
85800
  const md = readFileSync4(storyboardPath2, "utf-8");
@@ -86094,7 +86239,7 @@ init_composer_resolve();
86094
86239
  init_cost_tier();
86095
86240
  init_scene_build_mode();
86096
86241
  init_output();
86097
- import { existsSync as existsSync51 } from "node:fs";
86242
+ import { existsSync as existsSync52 } from "node:fs";
86098
86243
  import { resolve as resolve69 } from "node:path";
86099
86244
  var COMMAND_KEY_MAP = getCommandKeyMap();
86100
86245
  var FREE_COMMANDS = [
@@ -86222,13 +86367,13 @@ async function runDiagnostics() {
86222
86367
  installed = false;
86223
86368
  }
86224
86369
  } else if (toolName === "chrome") {
86225
- const { existsSync: existsSync58 } = await import("node:fs");
86370
+ const { existsSync: existsSync59 } = await import("node:fs");
86226
86371
  const { homedir: homedir8 } = await import("node:os");
86227
- const { join: join30 } = await import("node:path");
86372
+ const { join: join31 } = await import("node:path");
86228
86373
  const chromePaths = [
86229
86374
  process.env.HYPERFRAMES_CHROME_PATH,
86230
86375
  process.env.CHROME_PATH,
86231
- join30(
86376
+ join31(
86232
86377
  homedir8(),
86233
86378
  ".cache",
86234
86379
  "puppeteer",
@@ -86243,7 +86388,7 @@ async function runDiagnostics() {
86243
86388
  "/usr/bin/chromium",
86244
86389
  "/usr/bin/chromium-browser"
86245
86390
  ];
86246
- installed = chromePaths.some((p) => p && existsSync58(p));
86391
+ installed = chromePaths.some((p) => p && existsSync59(p));
86247
86392
  } else {
86248
86393
  installed = commandExists(toolName);
86249
86394
  }
@@ -86283,7 +86428,7 @@ async function runDiagnostics() {
86283
86428
  }
86284
86429
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
86285
86430
  path: rel,
86286
- exists: existsSync51(resolve69(cwd, rel))
86431
+ exists: existsSync52(resolve69(cwd, rel))
86287
86432
  }));
86288
86433
  const projectInitialized = projectFiles.some((f) => f.exists);
86289
86434
  const nearestProjectConfigPath = await findProjectConfigPath(cwd);
@@ -86302,8 +86447,8 @@ async function runDiagnostics() {
86302
86447
  } catch (err) {
86303
86448
  if (!(err instanceof ComposerResolveError)) throw err;
86304
86449
  }
86305
- const sceneProjectInCwd = existsSync51(resolve69(cwd, "STORYBOARD.md"));
86306
- const skillInstalled = existsSync51(resolve69(cwd, "SKILL.md"));
86450
+ const sceneProjectInCwd = existsSync52(resolve69(cwd, "STORYBOARD.md"));
86451
+ const skillInstalled = existsSync52(resolve69(cwd, "SKILL.md"));
86307
86452
  return {
86308
86453
  system: {
86309
86454
  node: { version: nodeVersion, ok: true },
@@ -86851,7 +86996,7 @@ init_detect();
86851
86996
  init_ai_edit();
86852
86997
  init_output();
86853
86998
  import { resolve as resolve71 } from "node:path";
86854
- import { existsSync as existsSync52 } from "node:fs";
86999
+ import { existsSync as existsSync53 } from "node:fs";
86855
87000
  import { mkdir as mkdir27, rm as rm5 } from "node:fs/promises";
86856
87001
  var DEMO_DIR = resolve71(process.cwd(), ".vibeframe-demo");
86857
87002
  async function generateTestVideo(outputPath) {
@@ -86882,7 +87027,7 @@ async function generateTestVideo(outputPath) {
86882
87027
  "-y",
86883
87028
  outputPath
86884
87029
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
86885
- return existsSync52(outputPath);
87030
+ return existsSync53(outputPath);
86886
87031
  } catch {
86887
87032
  try {
86888
87033
  await execSafe("ffmpeg", [
@@ -86908,7 +87053,7 @@ async function generateTestVideo(outputPath) {
86908
87053
  "-y",
86909
87054
  outputPath
86910
87055
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
86911
- return existsSync52(outputPath);
87056
+ return existsSync53(outputPath);
86912
87057
  } catch {
86913
87058
  return false;
86914
87059
  }
@@ -87050,8 +87195,8 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
87050
87195
 
87051
87196
  // src/commands/context.ts
87052
87197
  init_esm();
87053
- import { existsSync as existsSync53 } from "node:fs";
87054
- import { readFile as readFile47 } from "node:fs/promises";
87198
+ import { existsSync as existsSync54 } from "node:fs";
87199
+ import { readFile as readFile48 } from "node:fs/promises";
87055
87200
  import { resolve as resolve72, dirname as dirname25 } from "node:path";
87056
87201
  import { fileURLToPath as fileURLToPath2 } from "node:url";
87057
87202
  var __filename = fileURLToPath2(import.meta.url);
@@ -87063,7 +87208,7 @@ function resolveContextPath(baseDir = __dirname) {
87063
87208
  // Source/dev layout: packages/cli/src/commands/context.ts -> packages/cli/CONTEXT.md
87064
87209
  resolve72(baseDir, "../../CONTEXT.md")
87065
87210
  ];
87066
- return candidates.find((candidate) => existsSync53(candidate)) ?? null;
87211
+ return candidates.find((candidate) => existsSync54(candidate)) ?? null;
87067
87212
  }
87068
87213
  var contextCommand = new Command("context").description("Print CLI context/guidelines for AI agent integration").option("--format <format>", "Output format: markdown | json", "markdown").action(async (_options, cmd) => {
87069
87214
  const options = {
@@ -87169,7 +87314,7 @@ var contextCommand = new Command("context").description("Print CLI context/guide
87169
87314
  try {
87170
87315
  const contextPath = resolveContextPath();
87171
87316
  if (!contextPath) throw new Error("CONTEXT.md not found");
87172
- const content = await readFile47(contextPath, "utf-8");
87317
+ const content = await readFile48(contextPath, "utf-8");
87173
87318
  console.log(
87174
87319
  content.replace(
87175
87320
  "## Mental model",