@vibeframe/cli 0.113.10 → 0.113.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;
@@ -30189,7 +30277,7 @@ ${ctx.storyboardGlobal || "(no global direction)"}
30189
30277
 
30190
30278
  ## ${ctx.beat.heading}
30191
30279
  ${formatBeatCues(ctx.beat.cues)}${cueDurationNote}
30192
- ${ctx.beat.body}
30280
+ ${ctx.beat.body}${formatTranscriptSection(ctx.transcript)}
30193
30281
 
30194
30282
  === Output format ===
30195
30283
 
@@ -30209,7 +30297,7 @@ function computeCacheKey(parts) {
30209
30297
  return createHash2("sha256").update(parts.provider).update("\u241E").update(parts.model).update("\u241E").update(parts.systemPrompt).update("\u241E").update(parts.userPrompt).digest("hex");
30210
30298
  }
30211
30299
  function defaultCacheDir() {
30212
- return join6(USER_CACHE_DIR, "compose-scenes");
30300
+ return join7(USER_CACHE_DIR, "compose-scenes");
30213
30301
  }
30214
30302
  function extractHtml(responseText) {
30215
30303
  const fenced = responseText.match(/```html\s*\n([\s\S]*?)\n```/);
@@ -30233,9 +30321,9 @@ async function composeBeatHtml(ctx, overrides) {
30233
30321
  model: settings.model
30234
30322
  });
30235
30323
  const cacheDir = ctx.cacheDir ?? defaultCacheDir();
30236
- const cachePath = join6(cacheDir, `${cacheKey}.html`);
30237
- if (existsSync7(cachePath)) {
30238
- 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");
30239
30327
  return { html: html2, cached: true, cacheKey, model: settings.model, provider: ctx.provider };
30240
30328
  }
30241
30329
  if (!ctx.apiKey) {
@@ -30371,14 +30459,14 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30371
30459
  const projectRoot = params.project ? resolve11(outputDir, params.project) : resolve11(outputDir);
30372
30460
  const designPath = resolve11(projectRoot, params.design ?? "DESIGN.md");
30373
30461
  const storyboardPath2 = resolve11(projectRoot, params.storyboard ?? "STORYBOARD.md");
30374
- if (!existsSync7(designPath)) {
30462
+ if (!existsSync8(designPath)) {
30375
30463
  return { success: false, error: `DESIGN.md not found at ${designPath}` };
30376
30464
  }
30377
- if (!existsSync7(storyboardPath2)) {
30465
+ if (!existsSync8(storyboardPath2)) {
30378
30466
  return { success: false, error: `STORYBOARD.md not found at ${storyboardPath2}` };
30379
30467
  }
30380
- const designMd = await readFile8(designPath, "utf-8");
30381
- const storyboardMd = await readFile8(storyboardPath2, "utf-8");
30468
+ const designMd = await readFile9(designPath, "utf-8");
30469
+ const storyboardMd = await readFile9(storyboardPath2, "utf-8");
30382
30470
  const { global: storyboardGlobal, beats } = parseStoryboard(storyboardMd);
30383
30471
  if (beats.length === 0) {
30384
30472
  return {
@@ -30398,7 +30486,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30398
30486
  provider = params.composer ?? "claude";
30399
30487
  apiKey = "";
30400
30488
  }
30401
- const compositionsDir = join6(projectRoot, "compositions");
30489
+ const compositionsDir = join7(projectRoot, "compositions");
30402
30490
  await mkdir5(compositionsDir, { recursive: true });
30403
30491
  let finalDurations = /* @__PURE__ */ new Map();
30404
30492
  try {
@@ -30412,6 +30500,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30412
30500
  async (beat, beatIndex) => {
30413
30501
  onProgress({ type: "beat-start", beatId: beat.id, beatIndex, totalBeats });
30414
30502
  try {
30503
+ const transcript = await readBeatTranscript(projectRoot, beat.id);
30415
30504
  const result = await composeBeatWithRetry(
30416
30505
  {
30417
30506
  beat,
@@ -30422,11 +30511,12 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
30422
30511
  apiKey,
30423
30512
  effort: params.effort,
30424
30513
  cacheDir: params.cacheDir,
30425
- finalDurationSec: finalDurations.get(beat.id)
30514
+ finalDurationSec: finalDurations.get(beat.id),
30515
+ transcript
30426
30516
  },
30427
30517
  overrides
30428
30518
  );
30429
- const compositionPath = join6(compositionsDir, `scene-${beat.id}.html`);
30519
+ const compositionPath = join7(compositionsDir, `scene-${beat.id}.html`);
30430
30520
  await mkdir5(dirname3(compositionPath), { recursive: true });
30431
30521
  await writeFile8(compositionPath, result.html, "utf-8");
30432
30522
  if (result.cached) {
@@ -30505,7 +30595,7 @@ ${failures.map((f) => ` - ${f.beatId}: ${f.error}`).join("\n")}`,
30505
30595
  data: aggregateData
30506
30596
  };
30507
30597
  }
30508
- 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;
30509
30599
  var init_compose_scenes_skills = __esm({
30510
30600
  "src/commands/_shared/compose-scenes-skills.ts"() {
30511
30601
  "use strict";
@@ -30517,6 +30607,7 @@ var init_compose_scenes_skills = __esm({
30517
30607
  init_root_sync();
30518
30608
  init_composer_resolve();
30519
30609
  init_config();
30610
+ init_transcribe_narration();
30520
30611
  MODEL_SETTINGS_BY_PROVIDER = {
30521
30612
  // Anthropic Claude Sonnet 4.6 — v0.69 baseline. ~9 s/beat, $3/$15 per MTok.
30522
30613
  claude: {
@@ -30540,6 +30631,7 @@ var init_compose_scenes_skills = __esm({
30540
30631
  high: { model: GEMINI_DEFAULT_TEXT_MODEL, maxTokens: 8e3, costPerMTokIn: 1.5, costPerMTokOut: 9 }
30541
30632
  }
30542
30633
  };
30634
+ TRANSCRIPT_PROMPT_MAX_WORDS = 120;
30543
30635
  ComposeBeatError = class extends Error {
30544
30636
  constructor(code, message, cause) {
30545
30637
  super(message);
@@ -30625,22 +30717,22 @@ var init_compose_scenes_skills = __esm({
30625
30717
  });
30626
30718
 
30627
30719
  // src/commands/_shared/compose-prompts.ts
30628
- import { existsSync as existsSync8 } from "node:fs";
30629
- import { readFile as readFile9 } from "node:fs/promises";
30630
- 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";
30631
30723
  async function getComposePrompts(opts) {
30632
30724
  const projectDir = resolve12(opts.projectDir);
30633
- const designPath = join7(projectDir, "DESIGN.md");
30634
- const storyboardPath2 = join7(projectDir, "STORYBOARD.md");
30635
- const skillPath = join7(projectDir, "SKILL.md");
30636
- 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");
30637
30729
  const warnings = [];
30638
30730
  const baseError = (msg) => ({
30639
30731
  success: false,
30640
30732
  projectDir,
30641
30733
  designReference: "DESIGN.md",
30642
30734
  storyboardReference: "STORYBOARD.md",
30643
- skillReference: existsSync8(skillPath) ? "SKILL.md" : null,
30735
+ skillReference: existsSync9(skillPath) ? "SKILL.md" : null,
30644
30736
  compositionsDir: "compositions",
30645
30737
  beats: [],
30646
30738
  instructions: [],
@@ -30648,18 +30740,18 @@ async function getComposePrompts(opts) {
30648
30740
  warnings,
30649
30741
  error: msg
30650
30742
  });
30651
- if (!existsSync8(designPath)) {
30743
+ if (!existsSync9(designPath)) {
30652
30744
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
30653
30745
  }
30654
- if (!existsSync8(storyboardPath2)) {
30746
+ if (!existsSync9(storyboardPath2)) {
30655
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.`);
30656
30748
  }
30657
- if (!existsSync8(skillPath)) {
30749
+ if (!existsSync9(skillPath)) {
30658
30750
  warnings.push(
30659
30751
  "SKILL.md not installed \u2014 host agent won't have Hyperframes rules in context. Run `vibe scene install-skill` to install it."
30660
30752
  );
30661
30753
  }
30662
- const storyboardMd = await readFile9(storyboardPath2, "utf-8");
30754
+ const storyboardMd = await readFile10(storyboardPath2, "utf-8");
30663
30755
  const parsed = parseStoryboard(storyboardMd);
30664
30756
  if (parsed.beats.length === 0) {
30665
30757
  return baseError(`STORYBOARD.md has no \`## Beat \u2026\` headings.`);
@@ -30681,28 +30773,33 @@ async function getComposePrompts(opts) {
30681
30773
  finalDurations = await resolveProjectBeatDurations(projectDir);
30682
30774
  } catch {
30683
30775
  }
30684
- const result = beats.map((beat) => {
30685
- const outputPathAbs = join7(compositionsDir, `scene-${beat.id}.html`);
30686
- const outputPathRel = relative4(projectDir, outputPathAbs);
30687
- const finalDurationSec = finalDurations.get(beat.id);
30688
- const userPrompt = buildUserPrompt({
30689
- beat,
30690
- storyboardGlobal: parsed.global,
30691
- finalDurationSec
30692
- });
30693
- return {
30694
- id: beat.id,
30695
- heading: beat.heading,
30696
- outputPath: outputPathRel,
30697
- duration: beat.duration,
30698
- finalDurationSec,
30699
- cues: beat.cues,
30700
- body: beat.body,
30701
- userPrompt,
30702
- exists: existsSync8(outputPathAbs)
30703
- };
30704
- });
30705
- 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;
30706
30803
  const instructions = buildInstructions({
30707
30804
  skillRef,
30708
30805
  beatCount: result.length,
@@ -30750,13 +30847,14 @@ var init_compose_prompts = __esm({
30750
30847
  init_compose_scenes_skills();
30751
30848
  init_root_sync();
30752
30849
  init_bundle();
30850
+ init_transcribe_narration();
30753
30851
  }
30754
30852
  });
30755
30853
 
30756
30854
  // src/commands/_shared/scene-submit.ts
30757
- import { existsSync as existsSync9 } from "node:fs";
30758
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile9 } from "node:fs/promises";
30759
- 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";
30760
30858
  async function executeSceneSubmit(opts) {
30761
30859
  const projectDir = resolve13(opts.projectDir);
30762
30860
  const beatId = opts.beatId.trim();
@@ -30770,15 +30868,15 @@ async function executeSceneSubmit(opts) {
30770
30868
  warnings: [],
30771
30869
  retryWith: []
30772
30870
  };
30773
- const storyboardPath2 = join8(projectDir, "STORYBOARD.md");
30774
- if (!existsSync9(storyboardPath2)) {
30871
+ const storyboardPath2 = join9(projectDir, "STORYBOARD.md");
30872
+ if (!existsSync10(storyboardPath2)) {
30775
30873
  return {
30776
30874
  ...base,
30777
30875
  success: false,
30778
30876
  error: `STORYBOARD.md not found at ${storyboardPath2}. Run \`vibe init <dir>\` first.`
30779
30877
  };
30780
30878
  }
30781
- const parsed = parseStoryboard(await readFile10(storyboardPath2, "utf-8"));
30879
+ const parsed = parseStoryboard(await readFile11(storyboardPath2, "utf-8"));
30782
30880
  const beat = parsed.beats.find((b) => b.id === beatId);
30783
30881
  if (!beat) {
30784
30882
  const available = parsed.beats.map((b) => b.id).join(", ") || "(none)";
@@ -30848,8 +30946,8 @@ async function executeSceneSubmit(opts) {
30848
30946
  if (opts.validateOnly) {
30849
30947
  return { ...base, success: true };
30850
30948
  }
30851
- const absPath = join8(projectDir, scenePath);
30852
- await mkdir6(join8(projectDir, "compositions"), { recursive: true });
30949
+ const absPath = join9(projectDir, scenePath);
30950
+ await mkdir6(join9(projectDir, "compositions"), { recursive: true });
30853
30951
  await writeFile9(absPath, html.endsWith("\n") ? html : `${html}
30854
30952
  `, "utf-8");
30855
30953
  return {
@@ -30873,9 +30971,9 @@ var init_scene_submit = __esm({
30873
30971
  });
30874
30972
 
30875
30973
  // src/commands/_shared/sub-comp-duration-sync.ts
30876
- import { existsSync as existsSync10 } from "node:fs";
30877
- import { readFile as readFile11 } from "node:fs/promises";
30878
- 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";
30879
30977
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
30880
30978
  const compositionId = `scene-${beatId}`;
30881
30979
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -30967,15 +31065,15 @@ async function createSubCompDurationSyncPlans(opts) {
30967
31065
  const results = [];
30968
31066
  for (const beat of opts.beats) {
30969
31067
  const fileRel = `compositions/scene-${beat.id}.html`;
30970
- const filePath = join9(projectDir, fileRel);
30971
- if (!existsSync10(filePath)) continue;
31068
+ const filePath = join10(projectDir, fileRel);
31069
+ if (!existsSync11(filePath)) continue;
30972
31070
  const newDurationSec = await resolveSyncedBeatDuration({
30973
31071
  projectDir,
30974
31072
  beatDuration: beat.duration,
30975
31073
  narrationPath: beat.narrationPath,
30976
31074
  sceneDurationSec: beat.sceneDurationSec
30977
31075
  });
30978
- const html = await readFile11(filePath, "utf-8");
31076
+ const html = await readFile12(filePath, "utf-8");
30979
31077
  const outcome = syncSubCompositionDurationHtml(html, beat.id, newDurationSec, fileRel);
30980
31078
  results.push({ ...outcome, file: fileRel, beatId: beat.id });
30981
31079
  }
@@ -31000,7 +31098,7 @@ var init_sub_comp_duration_sync = __esm({
31000
31098
 
31001
31099
  // src/commands/_shared/review-report.ts
31002
31100
  import { writeFile as writeFile10 } from "node:fs/promises";
31003
- import { join as join10, resolve as resolve15 } from "node:path";
31101
+ import { join as join11, resolve as resolve15 } from "node:path";
31004
31102
  function statusFromIssues(issues) {
31005
31103
  if (issues.some((issue) => issue.severity === "error")) return "fail";
31006
31104
  if (issues.some((issue) => issue.severity === "warning")) return "warn";
@@ -31178,7 +31276,7 @@ function buildReviewReport(opts) {
31178
31276
  });
31179
31277
  }
31180
31278
  function defaultReviewReportPath(projectDir) {
31181
- return join10(resolve15(projectDir), "review-report.json");
31279
+ return join11(resolve15(projectDir), "review-report.json");
31182
31280
  }
31183
31281
  async function writeReviewReport(path2, report) {
31184
31282
  await writeFile10(path2, JSON.stringify(report, null, 2) + "\n", "utf-8");
@@ -31493,7 +31591,7 @@ var init_review_report = __esm({
31493
31591
  });
31494
31592
 
31495
31593
  // src/commands/_shared/scene-repair.ts
31496
- import { readFile as readFile12, writeFile as writeFile11 } from "node:fs/promises";
31594
+ import { readFile as readFile13, writeFile as writeFile11 } from "node:fs/promises";
31497
31595
  import { relative as relative5, resolve as resolve16 } from "node:path";
31498
31596
  import { runHyperframeLint as runHyperframeLint3 } from "@hyperframes/producer";
31499
31597
  async function executeSceneRepair(opts) {
@@ -31539,7 +31637,7 @@ async function executeSceneRepair(opts) {
31539
31637
  }
31540
31638
  for (const target of targets) {
31541
31639
  const rel = relative5(projectDir, target.abs) || target.abs;
31542
- const html = await readFile12(target.abs, "utf-8");
31640
+ const html = await readFile13(target.abs, "utf-8");
31543
31641
  const findings = lintHtml(html, rel, target.isSub);
31544
31642
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
31545
31643
  if (fixedCodes.length > 0) {
@@ -31912,8 +32010,8 @@ var init_storyboard_edit = __esm({
31912
32010
 
31913
32011
  // src/commands/scene.ts
31914
32012
  import { resolve as resolve17, relative as relative6, dirname as dirname4 } from "node:path";
31915
- import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile12, access as access5, copyFile } from "node:fs/promises";
31916
- 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";
31917
32015
  function validateDuration(value) {
31918
32016
  const n = parseFloat(value);
31919
32017
  if (!Number.isFinite(n) || n <= 0 || n > 3600) {
@@ -31977,8 +32075,8 @@ function readClipStart(rootHtml, beatId) {
31977
32075
  async function resolveNarrationText(value) {
31978
32076
  if (!value) return void 0;
31979
32077
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
31980
- if (looksLikePath && existsSync11(value)) {
31981
- return (await readFile13(value, "utf-8")).trim();
32078
+ if (looksLikePath && existsSync12(value)) {
32079
+ return (await readFile14(value, "utf-8")).trim();
31982
32080
  }
31983
32081
  return value.trim();
31984
32082
  }
@@ -32017,7 +32115,7 @@ async function executeSceneAdd(opts) {
32017
32115
  if (!opts.force && await pathExists2(scenePath)) {
32018
32116
  return errResult(`Scene already exists: ${sceneRel}. Re-run with --force to overwrite.`);
32019
32117
  }
32020
- const rootHtmlBefore = await readFile13(rootPath, "utf-8");
32118
+ const rootHtmlBefore = await readFile14(rootPath, "utf-8");
32021
32119
  let dims = readRootDims(rootHtmlBefore);
32022
32120
  if (!dims) {
32023
32121
  const cfg2 = await loadVibeProjectConfig(projectDir);
@@ -32093,31 +32191,20 @@ async function executeSceneAdd(opts) {
32093
32191
  );
32094
32192
  } else {
32095
32193
  opts.onProgress?.("Transcribing narration (Whisper word-level)...");
32096
- try {
32097
- const whisper = new WhisperProvider();
32098
- await whisper.initialize({ apiKey: whisperKey });
32099
- const audioBytes = await readFile13(audioAbsPath);
32100
- const audioBlob = new Blob([new Uint8Array(audioBytes)]);
32101
- const transcript = await whisper.transcribe(audioBlob, void 0, {
32102
- granularity: "word",
32103
- language: opts.transcribeLanguage
32104
- });
32105
- if (transcript.status === "completed" && transcript.words?.length) {
32106
- transcriptRelPath = `assets/transcript-${id}.json`;
32107
- const transcriptAbs = resolve17(projectDir, transcriptRelPath);
32108
- await writeFile12(transcriptAbs, JSON.stringify(transcript.words, null, 2), "utf-8");
32109
- transcriptWordCount = transcript.words.length;
32110
- transcriptWords = transcript.words.map((w) => ({
32111
- text: w.text,
32112
- start: w.start,
32113
- end: w.end
32114
- }));
32115
- } else if (transcript.status === "failed") {
32116
- opts.onProgress?.(`Transcribe failed: ${transcript.error ?? "unknown error"}`);
32117
- }
32118
- } catch (error) {
32119
- const msg = error instanceof Error ? error.message : String(error);
32120
- 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
+ );
32121
32208
  }
32122
32209
  }
32123
32210
  }
@@ -32226,7 +32313,7 @@ async function executeSceneAdd(opts) {
32226
32313
  const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
32227
32314
  if (shouldSyncStoryboard) {
32228
32315
  opts.onProgress?.("Syncing STORYBOARD.md...");
32229
- const before = await readFile13(storyboardAbsPath, "utf-8");
32316
+ const before = await readFile14(storyboardAbsPath, "utf-8");
32230
32317
  const synced = upsertStoryboardBeat(before, {
32231
32318
  beatId: id,
32232
32319
  title: opts.headline,
@@ -32268,7 +32355,7 @@ async function executeSceneAdd(opts) {
32268
32355
  });
32269
32356
  const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
32270
32357
  if (blockingIssue) return errResult(blockingIssue.message);
32271
- const rootHtmlAfterSync = await readFile13(rootPath, "utf-8");
32358
+ const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
32272
32359
  start = readClipStart(rootHtmlAfterSync, id) ?? 0;
32273
32360
  } else {
32274
32361
  start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
@@ -32300,7 +32387,7 @@ async function resolveSceneRepairTarget(rootArg = "index.html", projectArg = "."
32300
32387
  const root = rootArg || "index.html";
32301
32388
  const project = projectArg || ".";
32302
32389
  const defaultProject = project === ".";
32303
- 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"))) {
32304
32391
  return { projectDir: resolve17(root), rootRel: "index.html" };
32305
32392
  }
32306
32393
  return { projectDir: resolve17(project), rootRel: root };
@@ -32331,6 +32418,7 @@ var init_scene = __esm({
32331
32418
  init_agent_host_detect();
32332
32419
  init_install_skill();
32333
32420
  init_compose_prompts();
32421
+ init_transcribe_narration();
32334
32422
  init_scene_submit();
32335
32423
  init_scene_repair();
32336
32424
  init_root_sync();
@@ -32500,7 +32588,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
32500
32588
  }
32501
32589
  html = Buffer.concat(chunks).toString("utf-8");
32502
32590
  } else if (options.file) {
32503
- html = await readFile13(resolve17(String(options.file)), "utf-8");
32591
+ html = await readFile14(resolve17(String(options.file)), "utf-8");
32504
32592
  } else {
32505
32593
  exitWithError(
32506
32594
  usageError(
@@ -32954,8 +33042,8 @@ __export(remotion_exports, {
32954
33042
  wrapComponentWithVideo: () => wrapComponentWithVideo
32955
33043
  });
32956
33044
  import { writeFile as writeFile14, mkdir as mkdir8, rm, copyFile as copyFile2 } from "node:fs/promises";
32957
- import { existsSync as existsSync13 } from "node:fs";
32958
- import { join as join11 } from "node:path";
33045
+ import { existsSync as existsSync14 } from "node:fs";
33046
+ import { join as join12 } from "node:path";
32959
33047
  import { tmpdir } from "node:os";
32960
33048
  import { createHash as createHash3 } from "node:crypto";
32961
33049
  async function ensureRemotionInstalled() {
@@ -32974,7 +33062,7 @@ async function ensureRemotionInstalled() {
32974
33062
  }
32975
33063
  }
32976
33064
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
32977
- const dir = join11(tmpdir(), `vibe_motion_${Date.now()}`);
33065
+ const dir = join12(tmpdir(), `vibe_motion_${Date.now()}`);
32978
33066
  await mkdir8(dir, { recursive: true });
32979
33067
  const deps = {
32980
33068
  remotion: REMOTION_VERSION,
@@ -32993,7 +33081,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
32993
33081
  dependencies: deps
32994
33082
  };
32995
33083
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
32996
- await writeFile14(join11(dir, "package.json"), packageJsonStr);
33084
+ await writeFile14(join12(dir, "package.json"), packageJsonStr);
32997
33085
  const tsconfig = {
32998
33086
  compilerOptions: {
32999
33087
  target: "ES2020",
@@ -33005,8 +33093,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
33005
33093
  skipLibCheck: true
33006
33094
  }
33007
33095
  };
33008
- await writeFile14(join11(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
33009
- 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);
33010
33098
  const rootCode = `import { registerRoot, Composition } from "remotion";
33011
33099
  import { ${componentName} } from "./Component";
33012
33100
 
@@ -33025,14 +33113,14 @@ const Root = () => {
33025
33113
 
33026
33114
  registerRoot(Root);
33027
33115
  `;
33028
- await writeFile14(join11(dir, "Root.tsx"), rootCode);
33116
+ await writeFile14(join12(dir, "Root.tsx"), rootCode);
33029
33117
  const depsHash = createHash3("md5").update(packageJsonStr).digest("hex").slice(0, 12);
33030
- const cacheMarker = join11(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
33031
- const cachedModules = join11(REMOTION_CACHE_DIR, "node_modules");
33032
- 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)) {
33033
33121
  const { symlink } = await import("node:fs/promises");
33034
33122
  try {
33035
- await symlink(cachedModules, join11(dir, "node_modules"), "dir");
33123
+ await symlink(cachedModules, join12(dir, "node_modules"), "dir");
33036
33124
  } catch {
33037
33125
  await this_npmInstall(dir);
33038
33126
  }
@@ -33040,12 +33128,12 @@ registerRoot(Root);
33040
33128
  await this_npmInstall(dir);
33041
33129
  try {
33042
33130
  await mkdir8(REMOTION_CACHE_DIR, { recursive: true });
33043
- await writeFile14(join11(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
33044
- if (existsSync13(join11(dir, "node_modules"))) {
33131
+ await writeFile14(join12(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
33132
+ if (existsSync14(join12(dir, "node_modules"))) {
33045
33133
  const { rename: rename5, symlink } = await import("node:fs/promises");
33046
- await rename5(join11(dir, "node_modules"), cachedModules).catch(() => {
33134
+ await rename5(join12(dir, "node_modules"), cachedModules).catch(() => {
33047
33135
  });
33048
- await symlink(cachedModules, join11(dir, "node_modules"), "dir").catch(() => {
33136
+ await symlink(cachedModules, join12(dir, "node_modules"), "dir").catch(() => {
33049
33137
  });
33050
33138
  await writeFile14(cacheMarker, depsHash);
33051
33139
  }
@@ -33082,7 +33170,7 @@ async function renderMotion(options) {
33082
33170
  }
33083
33171
  );
33084
33172
  try {
33085
- const entryPoint = join11(dir, "Root.tsx");
33173
+ const entryPoint = join12(dir, "Root.tsx");
33086
33174
  const { execFile: execFile3 } = await import("node:child_process");
33087
33175
  const { promisify: promisify3 } = await import("node:util");
33088
33176
  const execFileAsync3 = promisify3(execFile3);
@@ -33215,10 +33303,10 @@ async function renderWithEmbeddedImage(options) {
33215
33303
  }
33216
33304
  );
33217
33305
  try {
33218
- const publicDir = join11(dir, "public");
33306
+ const publicDir = join12(dir, "public");
33219
33307
  await mkdir8(publicDir, { recursive: true });
33220
- await copyFile2(options.imagePath, join11(publicDir, options.imageFileName));
33221
- const entryPoint = join11(dir, "Root.tsx");
33308
+ await copyFile2(options.imagePath, join12(publicDir, options.imageFileName));
33309
+ const entryPoint = join12(dir, "Root.tsx");
33222
33310
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
33223
33311
  const { execFile: execFileImg } = await import("node:child_process");
33224
33312
  const { promisify: promisifyImg } = await import("node:util");
@@ -33279,10 +33367,10 @@ async function renderWithEmbeddedVideo(options) {
33279
33367
  }
33280
33368
  );
33281
33369
  try {
33282
- const publicDir = join11(dir, "public");
33370
+ const publicDir = join12(dir, "public");
33283
33371
  await mkdir8(publicDir, { recursive: true });
33284
- await copyFile2(options.videoPath, join11(publicDir, options.videoFileName));
33285
- const entryPoint = join11(dir, "Root.tsx");
33372
+ await copyFile2(options.videoPath, join12(publicDir, options.videoFileName));
33373
+ const entryPoint = join12(dir, "Root.tsx");
33286
33374
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
33287
33375
  const { execFile: execFileVid } = await import("node:child_process");
33288
33376
  const { promisify: promisifyVid } = await import("node:util");
@@ -33698,7 +33786,7 @@ var init_remotion = __esm({
33698
33786
  "use strict";
33699
33787
  init_exec_safe();
33700
33788
  REMOTION_VERSION = "4.0.447";
33701
- REMOTION_CACHE_DIR = join11(tmpdir(), "vibe_remotion_cache");
33789
+ REMOTION_CACHE_DIR = join12(tmpdir(), "vibe_remotion_cache");
33702
33790
  }
33703
33791
  });
33704
33792
 
@@ -33709,8 +33797,8 @@ __export(ai_motion_exports, {
33709
33797
  registerMotionCommand: () => registerMotionCommand
33710
33798
  });
33711
33799
  import { resolve as resolve19 } from "node:path";
33712
- import { existsSync as existsSync14 } from "node:fs";
33713
- 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";
33714
33802
  async function getOptionalGoogleApiKey() {
33715
33803
  const configKey = await getApiKeyFromConfig("google");
33716
33804
  if (configKey) return configKey;
@@ -33757,7 +33845,7 @@ async function executeMotion(options) {
33757
33845
  };
33758
33846
  }
33759
33847
  const imagePath = resolve19(process.cwd(), options.image);
33760
- const imageBuffer = await readFile15(imagePath);
33848
+ const imageBuffer = await readFile16(imagePath);
33761
33849
  const gemini = new GeminiProvider();
33762
33850
  await gemini.initialize({ apiKey: geminiApiKey });
33763
33851
  const analysisResult = await gemini.analyzeImage(
@@ -33794,7 +33882,7 @@ Use this image analysis to inform the color palette, typography placement, and o
33794
33882
  }
33795
33883
  if (geminiApiKey) {
33796
33884
  const videoPath = resolve19(process.cwd(), options.video);
33797
- const videoBuffer = await readFile15(videoPath);
33885
+ const videoBuffer = await readFile16(videoPath);
33798
33886
  const gemini = new GeminiProvider();
33799
33887
  await gemini.initialize({ apiKey: geminiApiKey });
33800
33888
  const analysisPrompt = options.understandingPrompt || `Analyze this video for motion graphics composition. Return concise production guidance:
@@ -33825,10 +33913,10 @@ Use this video understanding to place motion graphics in safe zones, match the c
33825
33913
  let result;
33826
33914
  if (options.fromTsx) {
33827
33915
  const tsxPath = resolve19(process.cwd(), options.fromTsx);
33828
- if (!existsSync14(tsxPath)) {
33916
+ if (!existsSync15(tsxPath)) {
33829
33917
  return { success: false, error: `TSX file not found: ${tsxPath}` };
33830
33918
  }
33831
- const existingCode = await readFile15(tsxPath, "utf-8");
33919
+ const existingCode = await readFile16(tsxPath, "utf-8");
33832
33920
  if (useGemini) {
33833
33921
  const gemini = new GeminiProvider();
33834
33922
  await gemini.initialize({ apiKey });
@@ -34605,7 +34693,7 @@ var init_ai_helpers = __esm({
34605
34693
 
34606
34694
  // src/commands/generate/storyboard.ts
34607
34695
  import { resolve as resolve22 } from "node:path";
34608
- import { readFile as readFile16, writeFile as writeFile18 } from "node:fs/promises";
34696
+ import { readFile as readFile17, writeFile as writeFile18 } from "node:fs/promises";
34609
34697
  async function executeStoryboard(options) {
34610
34698
  const { content, duration, creativity = "low", output, apiKey } = options;
34611
34699
  try {
@@ -34653,7 +34741,7 @@ function registerStoryboardCommand(parent) {
34653
34741
  let textContent = content;
34654
34742
  if (options.file) {
34655
34743
  const filePath = resolve22(process.cwd(), content);
34656
- textContent = await readFile16(filePath, "utf-8");
34744
+ textContent = await readFile17(filePath, "utf-8");
34657
34745
  }
34658
34746
  if (options.dryRun) {
34659
34747
  outputSuccess({
@@ -35725,7 +35813,7 @@ __export(ai_video_exports, {
35725
35813
  executeVideoGenerate: () => executeVideoGenerate,
35726
35814
  executeVideoStatus: () => executeVideoStatus
35727
35815
  });
35728
- import { readFile as readFile17, writeFile as writeFile21 } from "node:fs/promises";
35816
+ import { readFile as readFile18, writeFile as writeFile21 } from "node:fs/promises";
35729
35817
  import { resolve as resolve25 } from "node:path";
35730
35818
  async function executeVideoGenerate(options) {
35731
35819
  const {
@@ -35765,7 +35853,7 @@ async function executeVideoGenerate(options) {
35765
35853
  let referenceImageMimeType;
35766
35854
  if (image) {
35767
35855
  const imagePath = resolve25(process.cwd(), image);
35768
- const imageBuffer = await readFile17(imagePath);
35856
+ const imageBuffer = await readFile18(imagePath);
35769
35857
  const ext = image.toLowerCase().split(".").pop();
35770
35858
  const mimeTypes2 = {
35771
35859
  jpg: "image/jpeg",
@@ -36013,7 +36101,7 @@ async function fileInputToUrlOrDataUri(input, fallbackMimeType) {
36013
36101
  return input;
36014
36102
  }
36015
36103
  const absPath = resolve25(process.cwd(), input);
36016
- const buffer = await readFile17(absPath);
36104
+ const buffer = await readFile18(absPath);
36017
36105
  return `data:${mimeTypeForPath(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
36018
36106
  }
36019
36107
  function mimeTypeForPath(path2, fallback2) {
@@ -36265,15 +36353,15 @@ var init_ai_video = __esm({
36265
36353
  });
36266
36354
 
36267
36355
  // src/commands/_shared/build-asset-metadata.ts
36268
- import { existsSync as existsSync15, readFileSync as readFileSync2 } from "node:fs";
36356
+ import { existsSync as existsSync16, readFileSync as readFileSync2 } from "node:fs";
36269
36357
  import { mkdir as mkdir10, writeFile as writeFile22 } from "node:fs/promises";
36270
- import { dirname as dirname6, join as join12 } from "node:path";
36358
+ import { dirname as dirname6, join as join13 } from "node:path";
36271
36359
  function assetMetadataPath(kind, beatId) {
36272
36360
  return `.vibeframe/assets/${kind}-${beatId}.json`;
36273
36361
  }
36274
36362
  function readAssetMetadata(projectDir, kind, beatId) {
36275
- const path2 = join12(projectDir, assetMetadataPath(kind, beatId));
36276
- if (!existsSync15(path2)) return null;
36363
+ const path2 = join13(projectDir, assetMetadataPath(kind, beatId));
36364
+ if (!existsSync16(path2)) return null;
36277
36365
  try {
36278
36366
  const parsed = JSON.parse(readFileSync2(path2, "utf-8"));
36279
36367
  if (parsed.schemaVersion !== "1" || parsed.kind !== kind || parsed.beatId !== beatId || typeof parsed.cacheKey !== "string") {
@@ -36302,7 +36390,7 @@ function isFreshCanonicalAsset(opts) {
36302
36390
  }
36303
36391
  async function writeAssetMetadata(opts) {
36304
36392
  const rel = assetMetadataPath(opts.kind, opts.beatId);
36305
- const abs = join12(opts.projectDir, rel);
36393
+ const abs = join13(opts.projectDir, rel);
36306
36394
  const metadata = {
36307
36395
  schemaVersion: "1",
36308
36396
  kind: opts.kind,
@@ -36326,20 +36414,20 @@ var init_build_asset_metadata = __esm({
36326
36414
 
36327
36415
  // src/commands/_shared/status-jobs.ts
36328
36416
  import { createHash as createHash5, randomUUID as randomUUID2 } from "node:crypto";
36329
- import { existsSync as existsSync16, statSync as statSync2 } from "node:fs";
36330
- import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile18, readdir as readdir2, stat as stat4, writeFile as writeFile23 } from "node:fs/promises";
36331
- 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";
36332
36420
  function isLocalJob(record) {
36333
36421
  return record.jobType === "build" || record.jobType === "render";
36334
36422
  }
36335
36423
  function findProjectRoot(start = process.cwd()) {
36336
36424
  let dir = resolve26(start);
36337
36425
  try {
36338
- if (existsSync16(dir) && statSync2(dir).isFile()) dir = dirname7(dir);
36426
+ if (existsSync17(dir) && statSync2(dir).isFile()) dir = dirname7(dir);
36339
36427
  } catch {
36340
36428
  }
36341
36429
  for (; ; ) {
36342
- 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"))) {
36343
36431
  return dir;
36344
36432
  }
36345
36433
  const parent = dirname7(dir);
@@ -36348,7 +36436,7 @@ function findProjectRoot(start = process.cwd()) {
36348
36436
  }
36349
36437
  }
36350
36438
  function jobsDir(projectDir) {
36351
- return join13(resolve26(projectDir), ".vibeframe", "jobs");
36439
+ return join14(resolve26(projectDir), ".vibeframe", "jobs");
36352
36440
  }
36353
36441
  function normalizeJobStatus(status) {
36354
36442
  const value = String(status ?? "").toLowerCase();
@@ -36420,18 +36508,18 @@ async function readJobRecord(jobId, projectDir) {
36420
36508
  const id = normalizeJobId(jobId);
36421
36509
  const project = resolve26(projectDir ?? findProjectRoot());
36422
36510
  const path2 = jobRecordPath(id, project);
36423
- if (!existsSync16(path2)) return null;
36424
- return parseJobRecord(await readFile18(path2, "utf-8"));
36511
+ if (!existsSync17(path2)) return null;
36512
+ return parseJobRecord(await readFile19(path2, "utf-8"));
36425
36513
  }
36426
36514
  async function listJobRecords(projectDir) {
36427
36515
  const dir = jobsDir(projectDir);
36428
- if (!existsSync16(dir)) return [];
36516
+ if (!existsSync17(dir)) return [];
36429
36517
  const entries = await readdir2(dir, { withFileTypes: true });
36430
36518
  const records = [];
36431
36519
  for (const entry of entries) {
36432
36520
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
36433
36521
  try {
36434
- const parsed = parseJobRecord(await readFile18(join13(dir, entry.name), "utf-8"));
36522
+ const parsed = parseJobRecord(await readFile19(join14(dir, entry.name), "utf-8"));
36435
36523
  if (parsed) records.push(parsed);
36436
36524
  } catch {
36437
36525
  }
@@ -36525,7 +36613,7 @@ async function refreshLocalJobRecord(record, opts) {
36525
36613
  const heartbeatAge = record.heartbeatAt ? Date.now() - new Date(record.heartbeatAt).getTime() : Number.POSITIVE_INFINITY;
36526
36614
  const orphaned = !isPidAlive(record.pid) || heartbeatAge > LOCAL_JOB_ORPHAN_AFTER_MS;
36527
36615
  if (orphaned) {
36528
- 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.`;
36529
36617
  warnings.push(hint);
36530
36618
  updated = stripUndefined2({
36531
36619
  ...record,
@@ -36688,7 +36776,7 @@ function normalizeJobId(jobId) {
36688
36776
  return id;
36689
36777
  }
36690
36778
  function jobRecordPath(jobId, projectDir) {
36691
- return join13(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
36779
+ return join14(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
36692
36780
  }
36693
36781
  function parseJobRecord(raw) {
36694
36782
  const parsed = JSON.parse(raw);
@@ -36755,7 +36843,7 @@ async function maybeCacheOutput(record) {
36755
36843
  }
36756
36844
  async function maybeWriteCompletedAssetMetadata(record) {
36757
36845
  const kind = assetKindForJob(record);
36758
- 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)) {
36759
36847
  return;
36760
36848
  }
36761
36849
  try {
@@ -36774,7 +36862,7 @@ async function maybeWriteCompletedAssetMetadata(record) {
36774
36862
  }
36775
36863
  }
36776
36864
  async function refreshBuildReportFromJobs(projectDir, records) {
36777
- const reportPath = join13(projectDir, "build-report.json");
36865
+ const reportPath = join14(projectDir, "build-report.json");
36778
36866
  const report = await readJson(reportPath);
36779
36867
  if (!report || !Array.isArray(report.beats)) return;
36780
36868
  let changed = false;
@@ -36827,7 +36915,7 @@ async function refreshBuildReportFromJobs(projectDir, records) {
36827
36915
  }
36828
36916
  }
36829
36917
  async function readBuildSummary(projectDir) {
36830
- const reportPath = join13(projectDir, "build-report.json");
36918
+ const reportPath = join14(projectDir, "build-report.json");
36831
36919
  const report = await readJson(reportPath);
36832
36920
  if (!report) return null;
36833
36921
  const info = await statOrNull(reportPath);
@@ -36850,7 +36938,7 @@ async function readBuildSummary(projectDir) {
36850
36938
  });
36851
36939
  }
36852
36940
  async function readReviewSummary(projectDir) {
36853
- const reportPath = join13(projectDir, "review-report.json");
36941
+ const reportPath = join14(projectDir, "review-report.json");
36854
36942
  const report = await readJson(reportPath);
36855
36943
  if (!report) return null;
36856
36944
  const info = await statOrNull(reportPath);
@@ -36939,16 +37027,16 @@ async function readProjectBeatReadiness(projectDir, build, records) {
36939
37027
  if (fromBeats) return fromBeats;
36940
37028
  if (build.beats) return augmentBeatReadinessFromJobs(build.beats, records, report?.jobs);
36941
37029
  }
36942
- const storyboardPath2 = join13(projectDir, "STORYBOARD.md");
36943
- if (!existsSync16(storyboardPath2)) {
37030
+ const storyboardPath2 = join14(projectDir, "STORYBOARD.md");
37031
+ if (!existsSync17(storyboardPath2)) {
36944
37032
  return { total: 0, assetsReady: 0, compositionsReady: 0, needsAuthor: [] };
36945
37033
  }
36946
37034
  try {
36947
- const parsed = parseStoryboard(await readFile18(storyboardPath2, "utf-8"));
37035
+ const parsed = parseStoryboard(await readFile19(storyboardPath2, "utf-8"));
36948
37036
  const needsAuthor = [];
36949
37037
  let compositionsReady = 0;
36950
37038
  for (const beat of parsed.beats) {
36951
- if (existsSync16(join13(projectDir, "compositions", `scene-${beat.id}.html`))) {
37039
+ if (existsSync17(join14(projectDir, "compositions", `scene-${beat.id}.html`))) {
36952
37040
  compositionsReady += 1;
36953
37041
  } else {
36954
37042
  needsAuthor.push(beat.id);
@@ -36966,7 +37054,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
36966
37054
  }
36967
37055
  function deriveProjectWorkflow(opts) {
36968
37056
  const retryWith = [];
36969
- const hasStoryboard = existsSync16(join13(opts.project, "STORYBOARD.md"));
37057
+ const hasStoryboard = existsSync17(join14(opts.project, "STORYBOARD.md"));
36970
37058
  if (!hasStoryboard && !opts.build) {
36971
37059
  retryWith.push(`vibe init ${opts.project} --from "<brief>" --json`);
36972
37060
  return { status: "empty", currentStage: "init", retryWith };
@@ -37062,7 +37150,7 @@ function beatReadinessFromBuildBeats(projectDir, beats, records = [], reportJobs
37062
37150
  if (buildBeatAssetsReady(record) || (id ? buildBeatJobsReady(id, records, reportJobs) : false))
37063
37151
  assetsReady += 1;
37064
37152
  const compositionPath = typeof record.compositionPath === "string" ? record.compositionPath : id ? `compositions/scene-${id}.html` : void 0;
37065
- if (compositionPath && existsSync16(resolve26(projectDir, compositionPath))) {
37153
+ if (compositionPath && existsSync17(resolve26(projectDir, compositionPath))) {
37066
37154
  compositionsReady += 1;
37067
37155
  } else if (id) {
37068
37156
  needsAuthor.push(id);
@@ -37170,7 +37258,7 @@ function projectRelativePath(projectDir, path2) {
37170
37258
  }
37171
37259
  async function readJson(path2) {
37172
37260
  try {
37173
- return JSON.parse(await readFile18(path2, "utf-8"));
37261
+ return JSON.parse(await readFile19(path2, "utf-8"));
37174
37262
  } catch {
37175
37263
  return null;
37176
37264
  }
@@ -37209,7 +37297,7 @@ var init_status_jobs = __esm({
37209
37297
  // src/commands/generate/music.ts
37210
37298
  import { resolve as resolve27 } from "node:path";
37211
37299
  import { writeFile as writeFile24 } from "node:fs/promises";
37212
- import { existsSync as existsSync17 } from "node:fs";
37300
+ import { existsSync as existsSync18 } from "node:fs";
37213
37301
  async function executeMusic(options) {
37214
37302
  try {
37215
37303
  const provider = options.provider || "elevenlabs";
@@ -37362,7 +37450,7 @@ function registerMusicCommand(parent) {
37362
37450
  if (options.melody) {
37363
37451
  spinner2.text = "Uploading melody reference...";
37364
37452
  const absPath = resolve27(process.cwd(), options.melody);
37365
- if (!existsSync17(absPath)) {
37453
+ if (!existsSync18(absPath)) {
37366
37454
  spinner2.fail(`Melody file not found: ${options.melody}`);
37367
37455
  exitWithError(notFoundError(options.melody));
37368
37456
  }
@@ -37483,8 +37571,8 @@ __export(ai_image_exports, {
37483
37571
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
37484
37572
  });
37485
37573
  import { resolve as resolve28, dirname as dirname8 } from "node:path";
37486
- import { readFile as readFile19, writeFile as writeFile25, mkdir as mkdir12 } from "node:fs/promises";
37487
- 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";
37488
37576
  async function executeImageGenerate(options) {
37489
37577
  const {
37490
37578
  prompt: prompt3,
@@ -37628,10 +37716,10 @@ async function executeGeminiEdit(options) {
37628
37716
  const imageBuffers = [];
37629
37717
  for (const imagePath of imagePaths) {
37630
37718
  const absPath = resolve28(process.cwd(), imagePath);
37631
- if (!existsSync18(absPath)) {
37719
+ if (!existsSync19(absPath)) {
37632
37720
  return { success: false, error: `Image not found: ${absPath}` };
37633
37721
  }
37634
- const buffer = await readFile19(absPath);
37722
+ const buffer = await readFile20(absPath);
37635
37723
  imageBuffers.push(buffer);
37636
37724
  }
37637
37725
  const gemini = new GeminiProvider();
@@ -37676,7 +37764,7 @@ async function executeThumbnailBestFrame(options) {
37676
37764
  model = "flash",
37677
37765
  apiKey
37678
37766
  } = options;
37679
- if (!existsSync18(videoPath)) {
37767
+ if (!existsSync19(videoPath)) {
37680
37768
  return { success: false, error: `Video not found: ${videoPath}` };
37681
37769
  }
37682
37770
  if (!commandExists("ffmpeg")) {
@@ -37689,7 +37777,7 @@ async function executeThumbnailBestFrame(options) {
37689
37777
  try {
37690
37778
  const gemini = new GeminiProvider();
37691
37779
  await gemini.initialize({ apiKey: googleKey });
37692
- const videoData = await readFile19(videoPath);
37780
+ const videoData = await readFile20(videoPath);
37693
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>"}';
37694
37782
  const modelId = resolveGeminiTextModel(model);
37695
37783
  const result = await gemini.analyzeVideo(videoData, analysisPrompt, {
@@ -37710,7 +37798,7 @@ async function executeThumbnailBestFrame(options) {
37710
37798
  reason = reasonMatch[1];
37711
37799
  }
37712
37800
  await execSafe("ffmpeg", ["-ss", String(timestamp2), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
37713
- if (!existsSync18(outputPath)) {
37801
+ if (!existsSync19(outputPath)) {
37714
37802
  return { success: false, error: "FFmpeg failed to extract frame" };
37715
37803
  }
37716
37804
  return {
@@ -37736,7 +37824,7 @@ var init_ai_image = __esm({
37736
37824
 
37737
37825
  // src/commands/generate/thumbnail.ts
37738
37826
  import { resolve as resolve29, dirname as dirname9, basename as basename5, extname as extname3 } from "node:path";
37739
- import { existsSync as existsSync19 } from "node:fs";
37827
+ import { existsSync as existsSync20 } from "node:fs";
37740
37828
  import { writeFile as writeFile26, mkdir as mkdir13 } from "node:fs/promises";
37741
37829
  function registerThumbnailCommand(parent) {
37742
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) => {
@@ -37748,7 +37836,7 @@ function registerThumbnailCommand(parent) {
37748
37836
  }
37749
37837
  if (options.bestFrame) {
37750
37838
  const absVideoPath = resolve29(process.cwd(), options.bestFrame);
37751
- if (!existsSync19(absVideoPath)) {
37839
+ if (!existsSync20(absVideoPath)) {
37752
37840
  exitWithError(notFoundError(absVideoPath));
37753
37841
  }
37754
37842
  if (!commandExists("ffmpeg")) {
@@ -39932,7 +40020,7 @@ var init_dist3 = __esm({
39932
40020
 
39933
40021
  // src/commands/generate/video.ts
39934
40022
  import { resolve as resolve33 } from "node:path";
39935
- import { readFile as readFile20, writeFile as writeFile30 } from "node:fs/promises";
40023
+ import { readFile as readFile21, writeFile as writeFile30 } from "node:fs/promises";
39936
40024
  function registerVideoCommand(parent) {
39937
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(
39938
40026
  "-p, --provider <provider>",
@@ -40043,7 +40131,7 @@ Examples:
40043
40131
  let isImageToVideo = false;
40044
40132
  if (options.image) {
40045
40133
  const imagePath = resolve33(process.cwd(), options.image);
40046
- const imageBuffer = await readFile20(imagePath);
40134
+ const imageBuffer = await readFile21(imagePath);
40047
40135
  const ext = options.image.toLowerCase().split(".").pop();
40048
40136
  const mimeTypes2 = {
40049
40137
  jpg: "image/jpeg",
@@ -40283,7 +40371,7 @@ Examples:
40283
40371
  let lastFrame;
40284
40372
  if (options.lastFrame) {
40285
40373
  const lastFramePath = resolve33(process.cwd(), options.lastFrame);
40286
- const lastFrameBuffer = await readFile20(lastFramePath);
40374
+ const lastFrameBuffer = await readFile21(lastFramePath);
40287
40375
  const ext = options.lastFrame.toLowerCase().split(".").pop();
40288
40376
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
40289
40377
  lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
@@ -40293,7 +40381,7 @@ Examples:
40293
40381
  refImages = [];
40294
40382
  for (const refPath of options.refImages.slice(0, 3)) {
40295
40383
  const absRefPath = resolve33(process.cwd(), refPath);
40296
- const refBuffer = await readFile20(absRefPath);
40384
+ const refBuffer = await readFile21(absRefPath);
40297
40385
  const ext = refPath.toLowerCase().split(".").pop();
40298
40386
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
40299
40387
  refImages.push({ base64: refBuffer.toString("base64"), mimeType });
@@ -40425,7 +40513,7 @@ Examples:
40425
40513
  if (seedanceEndImagePath && seedanceReferences.length === 0) {
40426
40514
  try {
40427
40515
  const absEndImagePath = resolve33(process.cwd(), seedanceEndImagePath);
40428
- const endImageBuffer = await readFile20(absEndImagePath);
40516
+ const endImageBuffer = await readFile21(absEndImagePath);
40429
40517
  const ext = seedanceEndImagePath.toLowerCase().split(".").pop();
40430
40518
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
40431
40519
  const uploadHost = await resolveUploadHost();
@@ -40542,7 +40630,7 @@ async function fileInputToUrlOrDataUri2(input, fallbackMimeType) {
40542
40630
  return input;
40543
40631
  }
40544
40632
  const absPath = resolve33(process.cwd(), input);
40545
- const buffer = await readFile20(absPath);
40633
+ const buffer = await readFile21(absPath);
40546
40634
  return `data:${mimeTypeForPath2(input, fallbackMimeType)};base64,${buffer.toString("base64")}`;
40547
40635
  }
40548
40636
  function mimeTypeForPath2(path2, fallback2) {
@@ -40715,8 +40803,8 @@ Run 'vibe schema generate.<command>' for structured parameter info.
40715
40803
  });
40716
40804
 
40717
40805
  // src/commands/_shared/edit/silence-cut.ts
40718
- import { existsSync as existsSync20 } from "node:fs";
40719
- 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";
40720
40808
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
40721
40809
  const totalDuration = await getVideoDuration(videoPath);
40722
40810
  const { stdout, stderr } = await execSafe(
@@ -40766,7 +40854,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
40766
40854
  }
40767
40855
  const gemini = new GeminiProvider();
40768
40856
  await gemini.initialize({ apiKey: geminiApiKey });
40769
- const videoBuffer = await readFile21(videoPath);
40857
+ const videoBuffer = await readFile22(videoPath);
40770
40858
  const modelId = options.model ? resolveGeminiTextModel(options.model) : void 0;
40771
40859
  const prompt3 = `Analyze this video and identify all silent or dead segments where there is NO meaningful content.
40772
40860
 
@@ -40842,7 +40930,7 @@ async function executeSilenceCut(options) {
40842
40930
  analyzeOnly = false,
40843
40931
  useGemini = false
40844
40932
  } = options;
40845
- if (!existsSync20(videoPath)) {
40933
+ if (!existsSync21(videoPath)) {
40846
40934
  return { success: false, error: `Video not found: ${videoPath}` };
40847
40935
  }
40848
40936
  if (!commandExists("ffmpeg")) {
@@ -40950,11 +41038,11 @@ var init_silence_cut = __esm({
40950
41038
  });
40951
41039
 
40952
41040
  // src/commands/_shared/edit/jump-cut.ts
40953
- import { existsSync as existsSync21 } from "node:fs";
40954
- import { readFile as readFile22, writeFile as writeFile31, mkdir as mkdir15 } from "node:fs/promises";
40955
- 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";
40956
41044
  async function transcribeWithWords(audioPath, apiKey, language) {
40957
- const audioBuffer = await readFile22(audioPath);
41045
+ const audioBuffer = await readFile23(audioPath);
40958
41046
  const audioBlob = new Blob([audioBuffer]);
40959
41047
  const formData = new FormData();
40960
41048
  formData.append("file", audioBlob, "audio.wav");
@@ -41013,7 +41101,7 @@ async function executeJumpCut(options) {
41013
41101
  analyzeOnly = false,
41014
41102
  apiKey
41015
41103
  } = options;
41016
- if (!existsSync21(videoPath)) {
41104
+ if (!existsSync22(videoPath)) {
41017
41105
  return { success: false, error: `Video not found: ${videoPath}` };
41018
41106
  }
41019
41107
  if (!commandExists("ffmpeg")) {
@@ -41032,7 +41120,7 @@ async function executeJumpCut(options) {
41032
41120
  try {
41033
41121
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
41034
41122
  await mkdir15(tmpDir, { recursive: true });
41035
- const audioPath = join14(tmpDir, "audio.wav");
41123
+ const audioPath = join15(tmpDir, "audio.wav");
41036
41124
  try {
41037
41125
  await execSafe(
41038
41126
  "ffmpeg",
@@ -41086,7 +41174,7 @@ async function executeJumpCut(options) {
41086
41174
  const segmentPaths = [];
41087
41175
  for (let i = 0; i < segments.length; i++) {
41088
41176
  const seg = segments[i];
41089
- const segPath = join14(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
41177
+ const segPath = join15(tmpDir, `seg-${i.toString().padStart(4, "0")}.ts`);
41090
41178
  const duration = seg.end - seg.start;
41091
41179
  await execSafe(
41092
41180
  "ffmpeg",
@@ -41109,7 +41197,7 @@ async function executeJumpCut(options) {
41109
41197
  segmentPaths.push(segPath);
41110
41198
  }
41111
41199
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
41112
- const listPath = join14(tmpDir, "concat.txt");
41200
+ const listPath = join15(tmpDir, "concat.txt");
41113
41201
  await writeFile31(listPath, concatList);
41114
41202
  await execSafe(
41115
41203
  "ffmpeg",
@@ -41299,9 +41387,9 @@ var init_helpers = __esm({
41299
41387
  });
41300
41388
 
41301
41389
  // src/commands/_shared/edit/caption.ts
41302
- import { existsSync as existsSync22 } from "node:fs";
41303
- import { readFile as readFile23, writeFile as writeFile32, mkdir as mkdir16 } from "node:fs/promises";
41304
- 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";
41305
41393
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
41306
41394
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
41307
41395
  const marginV = position === "center" ? 0 : 30;
@@ -41329,7 +41417,7 @@ async function executeCaption(options) {
41329
41417
  position = "bottom",
41330
41418
  apiKey
41331
41419
  } = options;
41332
- if (!existsSync22(videoPath)) {
41420
+ if (!existsSync23(videoPath)) {
41333
41421
  return { success: false, error: `Video not found: ${videoPath}` };
41334
41422
  }
41335
41423
  if (!commandExists("ffmpeg")) {
@@ -41348,8 +41436,8 @@ async function executeCaption(options) {
41348
41436
  try {
41349
41437
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
41350
41438
  await mkdir16(tmpDir, { recursive: true });
41351
- const audioPath = join15(tmpDir, "audio.wav");
41352
- const srtPath = join15(tmpDir, "captions.srt");
41439
+ const audioPath = join16(tmpDir, "audio.wav");
41440
+ const srtPath = join16(tmpDir, "captions.srt");
41353
41441
  try {
41354
41442
  await execSafe(
41355
41443
  "ffmpeg",
@@ -41370,7 +41458,7 @@ async function executeCaption(options) {
41370
41458
  );
41371
41459
  const whisper = new WhisperProvider();
41372
41460
  await whisper.initialize({ apiKey: openaiKey });
41373
- const audioBuffer = await readFile23(audioPath);
41461
+ const audioBuffer = await readFile24(audioPath);
41374
41462
  const audioBlob = new Blob([audioBuffer]);
41375
41463
  const transcriptResult = await whisper.transcribe(audioBlob, language);
41376
41464
  if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
@@ -41418,7 +41506,7 @@ async function executeCaption(options) {
41418
41506
  const remotionErr = await ensureRemotionInstalled2();
41419
41507
  if (remotionErr) {
41420
41508
  const outputDir2 = dirname11(outputPath);
41421
- const outputSrtPath2 = join15(
41509
+ const outputSrtPath2 = join16(
41422
41510
  outputDir2,
41423
41511
  basename6(outputPath, extname4(outputPath)) + ".srt"
41424
41512
  );
@@ -41457,7 +41545,7 @@ SRT saved to: ${outputSrtPath2}` };
41457
41545
  });
41458
41546
  if (!renderResult.success) {
41459
41547
  const outputDir2 = dirname11(outputPath);
41460
- const outputSrtPath2 = join15(
41548
+ const outputSrtPath2 = join16(
41461
41549
  outputDir2,
41462
41550
  basename6(outputPath, extname4(outputPath)) + ".srt"
41463
41551
  );
@@ -41470,7 +41558,7 @@ SRT saved to: ${outputSrtPath2}`
41470
41558
  }
41471
41559
  }
41472
41560
  const outputDir = dirname11(outputPath);
41473
- const outputSrtPath = join15(
41561
+ const outputSrtPath = join16(
41474
41562
  outputDir,
41475
41563
  basename6(outputPath, extname4(outputPath)) + ".srt"
41476
41564
  );
@@ -41507,10 +41595,10 @@ var init_caption = __esm({
41507
41595
  });
41508
41596
 
41509
41597
  // src/commands/_shared/edit/noise-reduce.ts
41510
- import { existsSync as existsSync23 } from "node:fs";
41598
+ import { existsSync as existsSync24 } from "node:fs";
41511
41599
  async function executeNoiseReduce(options) {
41512
41600
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
41513
- if (!existsSync23(inputPath)) {
41601
+ if (!existsSync24(inputPath)) {
41514
41602
  return { success: false, error: `File not found: ${inputPath}` };
41515
41603
  }
41516
41604
  if (!commandExists("ffmpeg")) {
@@ -41567,7 +41655,7 @@ var init_noise_reduce = __esm({
41567
41655
  });
41568
41656
 
41569
41657
  // src/commands/_shared/edit/fade.ts
41570
- import { existsSync as existsSync24 } from "node:fs";
41658
+ import { existsSync as existsSync25 } from "node:fs";
41571
41659
  async function executeFade(options) {
41572
41660
  const {
41573
41661
  videoPath,
@@ -41577,7 +41665,7 @@ async function executeFade(options) {
41577
41665
  audioOnly = false,
41578
41666
  videoOnly = false
41579
41667
  } = options;
41580
- if (!existsSync24(videoPath)) {
41668
+ if (!existsSync25(videoPath)) {
41581
41669
  return { success: false, error: `Video not found: ${videoPath}` };
41582
41670
  }
41583
41671
  if (!commandExists("ffmpeg")) {
@@ -41644,8 +41732,8 @@ var init_fade = __esm({
41644
41732
  });
41645
41733
 
41646
41734
  // src/commands/_shared/edit/translate-srt.ts
41647
- import { existsSync as existsSync25 } from "node:fs";
41648
- 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";
41649
41737
  async function executeTranslateSrt(options) {
41650
41738
  const {
41651
41739
  srtPath,
@@ -41655,11 +41743,11 @@ async function executeTranslateSrt(options) {
41655
41743
  sourceLanguage,
41656
41744
  apiKey
41657
41745
  } = options;
41658
- if (!existsSync25(srtPath)) {
41746
+ if (!existsSync26(srtPath)) {
41659
41747
  return { success: false, error: `SRT file not found: ${srtPath}` };
41660
41748
  }
41661
41749
  try {
41662
- const srtContent = await readFile24(srtPath, "utf-8");
41750
+ const srtContent = await readFile25(srtPath, "utf-8");
41663
41751
  const segments = parseSRT(srtContent);
41664
41752
  if (segments.length === 0) {
41665
41753
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -41772,7 +41860,7 @@ var init_translate_srt = __esm({
41772
41860
  });
41773
41861
 
41774
41862
  // src/commands/_shared/edit/text-overlay.ts
41775
- import { existsSync as existsSync26 } from "node:fs";
41863
+ import { existsSync as existsSync27 } from "node:fs";
41776
41864
  import { resolve as resolve34 } from "node:path";
41777
41865
  function detectSystemFont() {
41778
41866
  const platform3 = process.platform;
@@ -41783,7 +41871,7 @@ function detectSystemFont() {
41783
41871
  "/Library/Fonts/Arial.ttf"
41784
41872
  ];
41785
41873
  for (const f of candidates) {
41786
- if (existsSync26(f)) return f;
41874
+ if (existsSync27(f)) return f;
41787
41875
  }
41788
41876
  } else if (platform3 === "linux") {
41789
41877
  const candidates = [
@@ -41792,7 +41880,7 @@ function detectSystemFont() {
41792
41880
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
41793
41881
  ];
41794
41882
  for (const f of candidates) {
41795
- if (existsSync26(f)) return f;
41883
+ if (existsSync27(f)) return f;
41796
41884
  }
41797
41885
  } else if (platform3 === "win32") {
41798
41886
  const candidates = [
@@ -41800,7 +41888,7 @@ function detectSystemFont() {
41800
41888
  "C:\\Windows\\Fonts\\segoeui.ttf"
41801
41889
  ];
41802
41890
  for (const f of candidates) {
41803
- if (existsSync26(f)) return f;
41891
+ if (existsSync27(f)) return f;
41804
41892
  }
41805
41893
  }
41806
41894
  return null;
@@ -41821,7 +41909,7 @@ async function applyTextOverlays(options) {
41821
41909
  }
41822
41910
  const absVideoPath = resolve34(process.cwd(), videoPath);
41823
41911
  const absOutputPath = resolve34(process.cwd(), outputPath);
41824
- if (!existsSync26(absVideoPath)) {
41912
+ if (!existsSync27(absVideoPath)) {
41825
41913
  return { success: false, error: `Video not found: ${absVideoPath}` };
41826
41914
  }
41827
41915
  if (!commandExists("ffmpeg")) {
@@ -42002,7 +42090,7 @@ var init_ai_edit = __esm({
42002
42090
 
42003
42091
  // src/commands/ai-edit-cli.ts
42004
42092
  import { resolve as resolve35, extname as extname5, basename as basename7 } from "node:path";
42005
- import { existsSync as existsSync27 } from "node:fs";
42093
+ import { existsSync as existsSync28 } from "node:fs";
42006
42094
  function registerEditCommands(aiCommand) {
42007
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", `
42008
42096
  Examples:
@@ -42019,7 +42107,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
42019
42107
  validateOutputPath(options.output);
42020
42108
  }
42021
42109
  const absVideoPath = resolve35(process.cwd(), videoPath);
42022
- if (!existsSync27(absVideoPath)) {
42110
+ if (!existsSync28(absVideoPath)) {
42023
42111
  exitWithError(notFoundError(absVideoPath));
42024
42112
  }
42025
42113
  if (!commandExists("ffmpeg")) {
@@ -42127,7 +42215,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42127
42215
  validateOutputPath(options.output);
42128
42216
  }
42129
42217
  const absVideoPath = resolve35(process.cwd(), videoPath);
42130
- if (!existsSync27(absVideoPath)) {
42218
+ if (!existsSync28(absVideoPath)) {
42131
42219
  exitWithError(notFoundError(absVideoPath));
42132
42220
  }
42133
42221
  if (!commandExists("ffmpeg")) {
@@ -42208,7 +42296,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42208
42296
  validateOutputPath(options.output);
42209
42297
  }
42210
42298
  const absInputPath = resolve35(process.cwd(), inputPath);
42211
- if (!existsSync27(absInputPath)) {
42299
+ if (!existsSync28(absInputPath)) {
42212
42300
  exitWithError(notFoundError(absInputPath));
42213
42301
  }
42214
42302
  if (!commandExists("ffmpeg")) {
@@ -42274,7 +42362,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42274
42362
  validateOutputPath(options.output);
42275
42363
  }
42276
42364
  const absVideoPath = resolve35(process.cwd(), videoPath);
42277
- if (!existsSync27(absVideoPath)) {
42365
+ if (!existsSync28(absVideoPath)) {
42278
42366
  exitWithError(notFoundError(absVideoPath));
42279
42367
  }
42280
42368
  if (!commandExists("ffmpeg")) {
@@ -42349,7 +42437,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42349
42437
  exitWithError(usageError("Target language required. Use -t or --target"));
42350
42438
  }
42351
42439
  const absSrtPath = resolve35(process.cwd(), srtPath);
42352
- if (!existsSync27(absSrtPath)) {
42440
+ if (!existsSync28(absSrtPath)) {
42353
42441
  exitWithError(notFoundError(absSrtPath));
42354
42442
  }
42355
42443
  if (options.dryRun) {
@@ -42425,7 +42513,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
42425
42513
  }
42426
42514
  if (options.fillers) rejectControlChars(options.fillers);
42427
42515
  const absVideoPath = resolve35(process.cwd(), videoPath);
42428
- if (!existsSync27(absVideoPath)) {
42516
+ if (!existsSync28(absVideoPath)) {
42429
42517
  exitWithError(notFoundError(absVideoPath));
42430
42518
  }
42431
42519
  if (!commandExists("ffmpeg")) {
@@ -42525,8 +42613,8 @@ var init_ai_edit_cli = __esm({
42525
42613
  });
42526
42614
 
42527
42615
  // src/commands/_shared/execute-fill-gaps.ts
42528
- import { existsSync as existsSync28 } from "node:fs";
42529
- 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";
42530
42618
  import { resolve as resolve36, dirname as dirname12 } from "node:path";
42531
42619
  function detectVideoGaps(videoClips, totalDuration) {
42532
42620
  const gaps = [];
@@ -42583,7 +42671,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
42583
42671
  }
42584
42672
  async function uploadFrameToImgbb(framePath, imgbbApiKey) {
42585
42673
  try {
42586
- const frameBuffer = await readFile25(framePath);
42674
+ const frameBuffer = await readFile26(framePath);
42587
42675
  const frameBase64 = frameBuffer.toString("base64");
42588
42676
  const formData = new FormData();
42589
42677
  formData.append("key", imgbbApiKey);
@@ -42608,14 +42696,14 @@ async function executeFillGaps(options) {
42608
42696
  try {
42609
42697
  onProgress("Loading timeline...");
42610
42698
  const filePath = await resolveTimelineFile(options.projectPath);
42611
- if (!existsSync28(filePath)) {
42699
+ if (!existsSync29(filePath)) {
42612
42700
  return {
42613
42701
  success: false,
42614
42702
  error: `Timeline file not found: ${filePath}`,
42615
42703
  humanLines
42616
42704
  };
42617
42705
  }
42618
- const content = await readFile25(filePath, "utf-8");
42706
+ const content = await readFile26(filePath, "utf-8");
42619
42707
  const data = JSON.parse(content);
42620
42708
  const project = Project.fromJSON(data);
42621
42709
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -42743,7 +42831,7 @@ async function executeFillGaps(options) {
42743
42831
  }
42744
42832
  const projectDir = dirname12(filePath);
42745
42833
  const footageDir = options.dir ? resolve36(process.cwd(), options.dir) : resolve36(projectDir, "footage");
42746
- if (!existsSync28(footageDir)) {
42834
+ if (!existsSync29(footageDir)) {
42747
42835
  await mkdir17(footageDir, { recursive: true });
42748
42836
  }
42749
42837
  humanLines.push("Generating AI Videos");
@@ -43057,15 +43145,15 @@ var init_ai_fill_gaps = __esm({
43057
43145
  });
43058
43146
 
43059
43147
  // src/pipeline/renderers/chrome.ts
43060
- import { existsSync as existsSync29 } from "node:fs";
43148
+ import { existsSync as existsSync30 } from "node:fs";
43061
43149
  import { homedir as homedir5 } from "node:os";
43062
- import { join as join16 } from "node:path";
43150
+ import { join as join17 } from "node:path";
43063
43151
  function findChrome() {
43064
43152
  const candidates = [
43065
43153
  process.env.HYPERFRAMES_CHROME_PATH,
43066
43154
  process.env.CHROME_PATH,
43067
43155
  // puppeteer auto-downloaded headless shell
43068
- join16(
43156
+ join17(
43069
43157
  homedir5(),
43070
43158
  ".cache",
43071
43159
  "puppeteer",
@@ -43083,7 +43171,7 @@ function findChrome() {
43083
43171
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
43084
43172
  ];
43085
43173
  for (const c of candidates) {
43086
- if (c && existsSync29(c)) return c;
43174
+ if (c && existsSync30(c)) return c;
43087
43175
  }
43088
43176
  return void 0;
43089
43177
  }
@@ -43329,7 +43417,7 @@ __export(project_builder_exports, {
43329
43417
  resolveSourceUrl: () => resolveSourceUrl
43330
43418
  });
43331
43419
  import { mkdtemp, mkdir as mkdir18, copyFile as copyFile4, writeFile as writeFile35, rm as rm2, readdir as readdir3 } from "node:fs/promises";
43332
- import { existsSync as existsSync30 } from "node:fs";
43420
+ import { existsSync as existsSync31 } from "node:fs";
43333
43421
  import { tmpdir as tmpdir2 } from "node:os";
43334
43422
  import { createRequire } from "node:module";
43335
43423
  import * as path from "node:path";
@@ -43341,7 +43429,7 @@ async function buildTempProject(state, projectFileDir) {
43341
43429
  for (const source of state.sources) {
43342
43430
  if (copied.has(source.id)) continue;
43343
43431
  const resolved = resolveSourceUrl(source.url, projectFileDir);
43344
- if (!existsSync30(resolved)) {
43432
+ if (!existsSync31(resolved)) {
43345
43433
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
43346
43434
  }
43347
43435
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -43478,7 +43566,7 @@ __export(motion_overlay_exports, {
43478
43566
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
43479
43567
  });
43480
43568
  import { mkdir as mkdir19 } from "node:fs/promises";
43481
- import { existsSync as existsSync31 } from "node:fs";
43569
+ import { existsSync as existsSync32 } from "node:fs";
43482
43570
  import { basename as basename9, dirname as dirname14, extname as extname6, resolve as resolve38 } from "node:path";
43483
43571
  function normalizeUnderstand2(value) {
43484
43572
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
@@ -43609,11 +43697,11 @@ async function executeMotionOverlay(options) {
43609
43697
  const validationError = assertValidRequest(options);
43610
43698
  if (validationError) return { success: false, error: validationError };
43611
43699
  const absVideo = resolve38(process.cwd(), options.videoPath);
43612
- 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}` };
43613
43701
  const outputPath = options.output ? resolve38(process.cwd(), options.output) : defaultOverlayOutput(options.videoPath);
43614
43702
  if (options.asset) {
43615
43703
  const absAsset = resolve38(process.cwd(), options.asset);
43616
- 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}` };
43617
43705
  const [videoDuration, size] = await Promise.all([
43618
43706
  ffprobeDuration(absVideo),
43619
43707
  ffprobeVideoSize(absVideo)
@@ -43780,7 +43868,7 @@ __export(edit_cmd_exports, {
43780
43868
  executeUpscale: () => executeUpscale
43781
43869
  });
43782
43870
  import { resolve as resolve39, dirname as dirname15 } from "node:path";
43783
- 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";
43784
43872
  async function executeGrade(options) {
43785
43873
  const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
43786
43874
  try {
@@ -43821,7 +43909,7 @@ async function executeSpeedRamp(options) {
43821
43909
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
43822
43910
  const whisper = new WhisperProvider();
43823
43911
  await whisper.initialize({ apiKey: openaiKey });
43824
- const audioBuffer = await readFile26(tempAudio);
43912
+ const audioBuffer = await readFile27(tempAudio);
43825
43913
  const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
43826
43914
  try {
43827
43915
  const { unlink: unlink5 } = await import("node:fs/promises");
@@ -44215,7 +44303,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
44215
44303
  spinner2.text = "Transcribing audio...";
44216
44304
  const whisper = new WhisperProvider();
44217
44305
  await whisper.initialize({ apiKey: openaiApiKey });
44218
- const audioBuffer = await readFile26(tempAudio);
44306
+ const audioBuffer = await readFile27(tempAudio);
44219
44307
  const audioBlob = new Blob([audioBuffer]);
44220
44308
  const transcript = await whisper.transcribe(audioBlob, options.language);
44221
44309
  if (!transcript.segments || transcript.segments.length === 0) {
@@ -44348,7 +44436,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
44348
44436
  for (let i = 1; i <= numKeyframes && i <= 30; i++) {
44349
44437
  const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
44350
44438
  try {
44351
- const frameBuffer = await readFile26(framePath);
44439
+ const frameBuffer = await readFile27(framePath);
44352
44440
  const frameBase64 = frameBuffer.toString("base64");
44353
44441
  const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
44354
44442
  focusMode: options.focus,
@@ -44477,7 +44565,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
44477
44565
  const imageBuffers = [];
44478
44566
  for (const imagePath of imagePaths) {
44479
44567
  const absPath = resolve39(process.cwd(), imagePath);
44480
- const buffer = await readFile26(absPath);
44568
+ const buffer = await readFile27(absPath);
44481
44569
  imageBuffers.push(buffer);
44482
44570
  }
44483
44571
  let result;
@@ -44753,9 +44841,9 @@ __export(ai_analyze_exports, {
44753
44841
  executeGeminiVideo: () => executeGeminiVideo,
44754
44842
  registerAnalyzeCommands: () => registerAnalyzeCommands
44755
44843
  });
44756
- import { readFile as readFile27 } from "node:fs/promises";
44844
+ import { readFile as readFile28 } from "node:fs/promises";
44757
44845
  import { extname as extname7, resolve as resolve40 } from "node:path";
44758
- import { existsSync as existsSync32 } from "node:fs";
44846
+ import { existsSync as existsSync33 } from "node:fs";
44759
44847
  async function executeGeminiVideo(options) {
44760
44848
  try {
44761
44849
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -44769,10 +44857,10 @@ async function executeGeminiVideo(options) {
44769
44857
  videoData = options.source;
44770
44858
  } else {
44771
44859
  const absPath = resolve40(process.cwd(), options.source);
44772
- if (!existsSync32(absPath)) {
44860
+ if (!existsSync33(absPath)) {
44773
44861
  return { success: false, error: `File not found: ${absPath}` };
44774
44862
  }
44775
- videoData = await readFile27(absPath);
44863
+ videoData = await readFile28(absPath);
44776
44864
  }
44777
44865
  const gemini = new GeminiProvider();
44778
44866
  await gemini.initialize({ apiKey });
@@ -44837,10 +44925,10 @@ async function executeAnalyze(options) {
44837
44925
  imageBuffer = Buffer.from(await response.arrayBuffer());
44838
44926
  } else {
44839
44927
  const absPath = resolve40(process.cwd(), source);
44840
- if (!existsSync32(absPath)) {
44928
+ if (!existsSync33(absPath)) {
44841
44929
  return { success: false, error: `File not found: ${absPath}` };
44842
44930
  }
44843
- imageBuffer = await readFile27(absPath);
44931
+ imageBuffer = await readFile28(absPath);
44844
44932
  }
44845
44933
  const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
44846
44934
  model: modelId,
@@ -44872,10 +44960,10 @@ async function executeAnalyze(options) {
44872
44960
  videoData = Buffer.from(await response.arrayBuffer());
44873
44961
  } else {
44874
44962
  const absPath = resolve40(process.cwd(), source);
44875
- if (!existsSync32(absPath)) {
44963
+ if (!existsSync33(absPath)) {
44876
44964
  return { success: false, error: `File not found: ${absPath}` };
44877
44965
  }
44878
- videoData = await readFile27(absPath);
44966
+ videoData = await readFile28(absPath);
44879
44967
  }
44880
44968
  const result = await gemini.analyzeVideo(videoData, options.prompt, {
44881
44969
  model: modelId,
@@ -45009,9 +45097,9 @@ __export(ai_review_exports, {
45009
45097
  executeReview: () => executeReview,
45010
45098
  registerReviewCommand: () => registerReviewCommand
45011
45099
  });
45012
- import { readFile as readFile28, rename as rename2 } from "node:fs/promises";
45100
+ import { readFile as readFile29, rename as rename2 } from "node:fs/promises";
45013
45101
  import { resolve as resolve41 } from "node:path";
45014
- import { existsSync as existsSync33 } from "node:fs";
45102
+ import { existsSync as existsSync34 } from "node:fs";
45015
45103
  function parseReviewFeedback(response) {
45016
45104
  let cleaned = response.trim();
45017
45105
  if (cleaned.startsWith("```json")) {
@@ -45043,7 +45131,7 @@ async function executeReview(options) {
45043
45131
  model = "flash"
45044
45132
  } = options;
45045
45133
  const absVideoPath = resolve41(process.cwd(), videoPath);
45046
- if (!existsSync33(absVideoPath)) {
45134
+ if (!existsSync34(absVideoPath)) {
45047
45135
  return { success: false, error: `Video not found: ${absVideoPath}` };
45048
45136
  }
45049
45137
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -45056,8 +45144,8 @@ async function executeReview(options) {
45056
45144
  let storyboardContext = "";
45057
45145
  if (storyboardPath2) {
45058
45146
  const absStoryboardPath = resolve41(process.cwd(), storyboardPath2);
45059
- if (existsSync33(absStoryboardPath)) {
45060
- const content = await readFile28(absStoryboardPath, "utf-8");
45147
+ if (existsSync34(absStoryboardPath)) {
45148
+ const content = await readFile29(absStoryboardPath, "utf-8");
45061
45149
  storyboardContext = `
45062
45150
 
45063
45151
  Original storyboard for reference:
@@ -45099,7 +45187,7 @@ ${projectContext}` : "";
45099
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}`;
45100
45188
  const gemini = new GeminiProvider();
45101
45189
  await gemini.initialize({ apiKey });
45102
- const videoData = await readFile28(absVideoPath);
45190
+ const videoData = await readFile29(absVideoPath);
45103
45191
  const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
45104
45192
  model: modelId
45105
45193
  });
@@ -45154,7 +45242,7 @@ Score each category 1-10. Prefer beatIssues when you can map a problem to a stor
45154
45242
  }
45155
45243
  }
45156
45244
  if (verify && result.outputPath) {
45157
- const verifyVideoData = await readFile28(result.outputPath);
45245
+ const verifyVideoData = await readFile29(result.outputPath);
45158
45246
  const verifyResult = await gemini.analyzeVideo(
45159
45247
  verifyVideoData,
45160
45248
  'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
@@ -45282,9 +45370,9 @@ var init_ai_review = __esm({
45282
45370
  });
45283
45371
 
45284
45372
  // src/commands/_shared/scene-inspect.ts
45285
- import { existsSync as existsSync34 } from "node:fs";
45286
- import { readdir as readdir4, readFile as readFile29, stat as stat5 } from "node:fs/promises";
45287
- 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";
45288
45376
  async function inspectProject(opts) {
45289
45377
  const projectDir = resolve42(opts.projectDir);
45290
45378
  const issues = [];
@@ -45298,7 +45386,7 @@ async function inspectProject(opts) {
45298
45386
  assets: { checked: 0, missing: [], stale: [], unknownFreshness: [] },
45299
45387
  rootSync: { ok: null, issueCount: 0, issues: [] }
45300
45388
  };
45301
- if (!existsSync34(projectDir) || !await isDirectory(projectDir)) {
45389
+ if (!existsSync35(projectDir) || !await isDirectory(projectDir)) {
45302
45390
  issues.push({
45303
45391
  severity: "error",
45304
45392
  code: "PROJECT_NOT_FOUND",
@@ -45313,15 +45401,15 @@ async function inspectProject(opts) {
45313
45401
  );
45314
45402
  }
45315
45403
  const coreFiles = {
45316
- storyboard: join18(projectDir, "STORYBOARD.md"),
45317
- design: join18(projectDir, "DESIGN.md"),
45318
- config: join18(projectDir, "vibe.config.json"),
45319
- legacyConfig: join18(projectDir, "vibe.project.yaml"),
45320
- buildReport: join18(projectDir, "build-report.json"),
45321
- 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")
45322
45410
  };
45323
45411
  for (const [name, path2] of Object.entries(coreFiles)) {
45324
- checks.files[name] = existsSync34(path2);
45412
+ checks.files[name] = existsSync35(path2);
45325
45413
  }
45326
45414
  let beatIds = [];
45327
45415
  let inspectedBeatIds = [];
@@ -45335,7 +45423,7 @@ async function inspectProject(opts) {
45335
45423
  });
45336
45424
  retryWith.push(`vibe init ${projectDir} --from "<brief>" --json`);
45337
45425
  } else {
45338
- const storyboard = await readFile29(coreFiles.storyboard, "utf-8");
45426
+ const storyboard = await readFile30(coreFiles.storyboard, "utf-8");
45339
45427
  const validation = validateStoryboardMarkdown(storyboard);
45340
45428
  storyboardBeats = validation.beats;
45341
45429
  beatIds = validation.beats.map((beat) => beat.id);
@@ -45377,7 +45465,7 @@ async function inspectProject(opts) {
45377
45465
  suggestedFix: "Create DESIGN.md or rerun `vibe init --from` in a new project."
45378
45466
  });
45379
45467
  } else {
45380
- const design = await readFile29(coreFiles.design, "utf-8");
45468
+ const design = await readFile30(coreFiles.design, "utf-8");
45381
45469
  for (const issue of designPlaceholderIssues(design)) {
45382
45470
  issues.push(issue);
45383
45471
  }
@@ -45402,20 +45490,20 @@ async function inspectProject(opts) {
45402
45490
  });
45403
45491
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
45404
45492
  }
45405
- const compositionsDir = join18(projectDir, "compositions");
45406
- 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();
45407
45495
  checks.compositions.expected = inspectedBeatIds.length;
45408
45496
  for (const beatId of inspectedBeatIds) {
45409
45497
  const file = `scene-${beatId}.html`;
45410
45498
  if (existingComps.has(file)) {
45411
45499
  checks.compositions.found++;
45412
45500
  } else {
45413
- checks.compositions.missing.push(join18("compositions", file));
45501
+ checks.compositions.missing.push(join19("compositions", file));
45414
45502
  issues.push({
45415
45503
  severity: "error",
45416
45504
  code: "MISSING_COMPOSITION",
45417
45505
  message: `Composition for beat "${beatId}" is missing.`,
45418
- file: join18("compositions", file),
45506
+ file: join19("compositions", file),
45419
45507
  scene: beatId,
45420
45508
  beatId,
45421
45509
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -45477,7 +45565,7 @@ async function inspectProject(opts) {
45477
45565
  try {
45478
45566
  const lint = await runProjectLint({ projectDir });
45479
45567
  const lintFiles = opts.beatId ? lint.files.filter(
45480
- (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`)
45481
45569
  ) : lint.files;
45482
45570
  const lintCounts = countLintFindings(lintFiles);
45483
45571
  checks.lint = {
@@ -45623,7 +45711,7 @@ function projectSourceReports(checks) {
45623
45711
  async function inspectBuildReport(projectDir, reportPath, beatId, checks, issues, retryWith, storyboardBeats) {
45624
45712
  checks.buildReport.exists = true;
45625
45713
  try {
45626
- const report = JSON.parse(await readFile29(reportPath, "utf-8"));
45714
+ const report = JSON.parse(await readFile30(reportPath, "utf-8"));
45627
45715
  if (typeof report.outputPath === "string") checks.buildReport.outputPath = report.outputPath;
45628
45716
  const reportBeats = report.beats ?? [];
45629
45717
  const selectedReportBeats = reportBeats.filter((item) => !beatId || item.id === beatId);
@@ -45760,7 +45848,7 @@ function inspectReportedAsset(opts) {
45760
45848
  opts.checks.assets.checked++;
45761
45849
  if (isExternalRef(opts.value)) return;
45762
45850
  const abs = isAbsolute3(opts.value) ? opts.value : resolve42(opts.projectDir, opts.value);
45763
- if (existsSync34(abs)) return;
45851
+ if (existsSync35(abs)) return;
45764
45852
  opts.checks.assets.missing.push(opts.value);
45765
45853
  opts.issues.push({
45766
45854
  severity: "warning",
@@ -45859,9 +45947,9 @@ var init_scene_inspect = __esm({
45859
45947
  });
45860
45948
 
45861
45949
  // src/commands/_shared/render-inspect.ts
45862
- import { existsSync as existsSync35 } from "node:fs";
45863
- import { readdir as readdir5, readFile as readFile30, stat as stat6 } from "node:fs/promises";
45864
- 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";
45865
45953
  function parseBlackdetectOutput(output) {
45866
45954
  const ranges = [];
45867
45955
  const regex2 = /black_start:(-?\d+(?:\.\d+)?)\s+black_end:(-?\d+(?:\.\d+)?)\s+black_duration:(-?\d+(?:\.\d+)?)/g;
@@ -46347,11 +46435,11 @@ async function inspectRender(opts) {
46347
46435
  }
46348
46436
  async function renderExpectsAudio(projectDir, beatId) {
46349
46437
  const root = resolve43(projectDir);
46350
- const rootHtml = await readTextIfExists(join19(root, "index.html"));
46438
+ const rootHtml = await readTextIfExists(join20(root, "index.html"));
46351
46439
  if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
46352
- const buildReport = await readJsonIfExists(join19(root, "build-report.json"));
46440
+ const buildReport = await readJsonIfExists(join20(root, "build-report.json"));
46353
46441
  if (buildReportExpectsAudio(buildReport, beatId)) return true;
46354
- const storyboard = await readTextIfExists(join19(root, "STORYBOARD.md"));
46442
+ const storyboard = await readTextIfExists(join20(root, "STORYBOARD.md"));
46355
46443
  if (!storyboard) return false;
46356
46444
  try {
46357
46445
  const parsed = parseStoryboard(storyboard);
@@ -46362,7 +46450,7 @@ async function renderExpectsAudio(projectDir, beatId) {
46362
46450
  `assets/narration-${beat.id}.wav`,
46363
46451
  `assets/music-${beat.id}.mp3`,
46364
46452
  `assets/music-${beat.id}.wav`
46365
- ].some((asset) => existsSync35(join19(root, asset)))
46453
+ ].some((asset) => existsSync36(join20(root, asset)))
46366
46454
  );
46367
46455
  } catch {
46368
46456
  return false;
@@ -46470,8 +46558,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
46470
46558
  }
46471
46559
  function renderSourceReports(projectDir, videoPath, checks) {
46472
46560
  const reports = [];
46473
- if (existsSync35(join19(projectDir, "build-report.json"))) reports.push("build-report.json");
46474
- 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");
46475
46563
  if (videoPath) reports.push(displayPath(videoPath));
46476
46564
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
46477
46565
  reports.push("ffprobe");
@@ -46484,31 +46572,31 @@ function renderSourceReports(projectDir, videoPath, checks) {
46484
46572
  async function resolveRenderVideoPath(projectDir, explicit, beatId) {
46485
46573
  if (explicit) {
46486
46574
  const candidate = resolve43(process.cwd(), explicit);
46487
- return existsSync35(candidate) ? candidate : null;
46575
+ return existsSync36(candidate) ? candidate : null;
46488
46576
  }
46489
- const renderReportPath = join19(projectDir, "render-report.json");
46490
- if (existsSync35(renderReportPath)) {
46577
+ const renderReportPath = join20(projectDir, "render-report.json");
46578
+ if (existsSync36(renderReportPath)) {
46491
46579
  try {
46492
- const report = JSON.parse(await readFile30(renderReportPath, "utf-8"));
46580
+ const report = JSON.parse(await readFile31(renderReportPath, "utf-8"));
46493
46581
  const reportBeat = typeof report.beat === "string" ? report.beat : null;
46494
46582
  if (!beatId && reportBeat === null || beatId && reportBeat === beatId) {
46495
46583
  const candidate = resolveReportedPath(projectDir, report.outputPath);
46496
- if (candidate && existsSync35(candidate)) return candidate;
46584
+ if (candidate && existsSync36(candidate)) return candidate;
46497
46585
  }
46498
46586
  } catch {
46499
46587
  }
46500
46588
  }
46501
- const reportPath = join19(projectDir, "build-report.json");
46502
- if (!beatId && existsSync35(reportPath)) {
46589
+ const reportPath = join20(projectDir, "build-report.json");
46590
+ if (!beatId && existsSync36(reportPath)) {
46503
46591
  try {
46504
- const report = JSON.parse(await readFile30(reportPath, "utf-8"));
46592
+ const report = JSON.parse(await readFile31(reportPath, "utf-8"));
46505
46593
  const candidate = resolveReportedPath(projectDir, report.outputPath);
46506
- if (candidate && existsSync35(candidate)) return candidate;
46594
+ if (candidate && existsSync36(candidate)) return candidate;
46507
46595
  } catch {
46508
46596
  }
46509
46597
  }
46510
- const rendersDir = join19(projectDir, "renders");
46511
- if (!existsSync35(rendersDir)) return null;
46598
+ const rendersDir = join20(projectDir, "renders");
46599
+ if (!existsSync36(rendersDir)) return null;
46512
46600
  const entries = await readdir5(rendersDir, { withFileTypes: true });
46513
46601
  const candidates = [];
46514
46602
  for (const entry of entries) {
@@ -46516,7 +46604,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
46516
46604
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
46517
46605
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
46518
46606
  if (beatId && !entry.name.includes(sanitizeFileSegment(beatId))) continue;
46519
- const full = join19(rendersDir, entry.name);
46607
+ const full = join20(rendersDir, entry.name);
46520
46608
  const info = await stat6(full);
46521
46609
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
46522
46610
  }
@@ -46534,14 +46622,14 @@ async function runAiRenderReview(projectDir, videoPath, model) {
46534
46622
  });
46535
46623
  }
46536
46624
  function resolveStoryboardPath(projectDir) {
46537
- const storyboardPath2 = join19(projectDir, "STORYBOARD.md");
46538
- return existsSync35(storyboardPath2) ? storyboardPath2 : null;
46625
+ const storyboardPath2 = join20(projectDir, "STORYBOARD.md");
46626
+ return existsSync36(storyboardPath2) ? storyboardPath2 : null;
46539
46627
  }
46540
46628
  async function expectedDurationFromBuildReport(projectDir, beatId) {
46541
- const reportPath = join19(projectDir, "build-report.json");
46542
- if (!existsSync35(reportPath)) return void 0;
46629
+ const reportPath = join20(projectDir, "build-report.json");
46630
+ if (!existsSync36(reportPath)) return void 0;
46543
46631
  try {
46544
- const report = JSON.parse(await readFile30(reportPath, "utf-8"));
46632
+ const report = JSON.parse(await readFile31(reportPath, "utf-8"));
46545
46633
  const beats = beatId ? (report.beats ?? []).filter((beat) => beat.id === beatId) : report.beats ?? [];
46546
46634
  const durations = beats.map(
46547
46635
  (beat) => parseOptionalNumber(beat.sceneDurationSec) ?? parseOptionalNumber(beat.narration?.sceneDurationSec)
@@ -46553,10 +46641,10 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
46553
46641
  }
46554
46642
  }
46555
46643
  async function beatTimingsFromBuildReport(projectDir, beatId) {
46556
- const reportPath = join19(projectDir, "build-report.json");
46557
- if (!existsSync35(reportPath)) return [];
46644
+ const reportPath = join20(projectDir, "build-report.json");
46645
+ if (!existsSync36(reportPath)) return [];
46558
46646
  try {
46559
- const report = JSON.parse(await readFile30(reportPath, "utf-8"));
46647
+ const report = JSON.parse(await readFile31(reportPath, "utf-8"));
46560
46648
  const source = report.beats ?? [];
46561
46649
  const selected = beatId ? source.filter((beat) => beat.id === beatId) : source;
46562
46650
  let cursor = 0;
@@ -46582,17 +46670,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
46582
46670
  }
46583
46671
  async function buildRenderReviewProjectContext(projectDir) {
46584
46672
  const parts = [];
46585
- const storyboard = await readTextIfExists(join19(projectDir, "STORYBOARD.md"));
46673
+ const storyboard = await readTextIfExists(join20(projectDir, "STORYBOARD.md"));
46586
46674
  if (storyboard) {
46587
46675
  parts.push(`STORYBOARD.md:
46588
46676
  ${truncateText(storyboard, 4e3)}`);
46589
46677
  parts.push(`Storyboard beat cue summary:
46590
46678
  ${storyboardBeatCueSummary(storyboard)}`);
46591
46679
  }
46592
- const design = await readTextIfExists(join19(projectDir, "DESIGN.md"));
46680
+ const design = await readTextIfExists(join20(projectDir, "DESIGN.md"));
46593
46681
  if (design) parts.push(`DESIGN.md:
46594
46682
  ${truncateText(design, 4e3)}`);
46595
- const buildReport = await readTextIfExists(join19(projectDir, "build-report.json"));
46683
+ const buildReport = await readTextIfExists(join20(projectDir, "build-report.json"));
46596
46684
  if (buildReport) parts.push(`build-report.json:
46597
46685
  ${truncateText(buildReport, 6e3)}`);
46598
46686
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -46620,9 +46708,9 @@ function storyboardBeatCueSummary(storyboard) {
46620
46708
  }
46621
46709
  }
46622
46710
  async function readTextIfExists(path2) {
46623
- if (!existsSync35(path2)) return null;
46711
+ if (!existsSync36(path2)) return null;
46624
46712
  try {
46625
- return await readFile30(path2, "utf-8");
46713
+ return await readFile31(path2, "utf-8");
46626
46714
  } catch {
46627
46715
  return null;
46628
46716
  }
@@ -46916,9 +47004,9 @@ var init_video_providers = __esm({
46916
47004
  });
46917
47005
 
46918
47006
  // src/commands/ai-script-pipeline.ts
46919
- 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";
46920
47008
  import { resolve as resolve46, extname as extname9 } from "node:path";
46921
- import { existsSync as existsSync37 } from "node:fs";
47009
+ import { existsSync as existsSync38 } from "node:fs";
46922
47010
  async function executeRegenerateScene(options) {
46923
47011
  const result = {
46924
47012
  success: false,
@@ -46927,16 +47015,16 @@ async function executeRegenerateScene(options) {
46927
47015
  };
46928
47016
  try {
46929
47017
  const outputDir = resolve46(process.cwd(), options.projectDir);
46930
- if (!existsSync37(outputDir)) {
47018
+ if (!existsSync38(outputDir)) {
46931
47019
  return { ...result, error: `Project directory not found: ${outputDir}` };
46932
47020
  }
46933
47021
  const yamlPath = resolve46(outputDir, "storyboard.yaml");
46934
47022
  const jsonPath = resolve46(outputDir, "storyboard.json");
46935
- const storyboardPath2 = existsSync37(yamlPath) ? yamlPath : existsSync37(jsonPath) ? jsonPath : null;
47023
+ const storyboardPath2 = existsSync38(yamlPath) ? yamlPath : existsSync38(jsonPath) ? jsonPath : null;
46936
47024
  if (!storyboardPath2) {
46937
47025
  return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
46938
47026
  }
46939
- const storyboardContent = await readFile33(storyboardPath2, "utf-8");
47027
+ const storyboardContent = await readFile34(storyboardPath2, "utf-8");
46940
47028
  const segments = storyboardPath2.endsWith(".yaml") ? (0, import_yaml6.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
46941
47029
  for (const sceneNum of options.scenes) {
46942
47030
  if (sceneNum < 1 || sceneNum > segments.length) {
@@ -47025,15 +47113,15 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
47025
47113
  const refSceneNum = options.referenceScene;
47026
47114
  if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
47027
47115
  const refImagePath = resolve46(outputDir, `scene-${refSceneNum}.png`);
47028
- if (existsSync37(refImagePath)) {
47029
- referenceImageBuffer = await readFile33(refImagePath);
47116
+ if (existsSync38(refImagePath)) {
47117
+ referenceImageBuffer = await readFile34(refImagePath);
47030
47118
  }
47031
47119
  } else if (!refSceneNum) {
47032
47120
  for (let i = 1; i <= segments.length; i++) {
47033
47121
  if (i !== sceneNum) {
47034
47122
  const otherImagePath = resolve46(outputDir, `scene-${i}.png`);
47035
- if (existsSync37(otherImagePath)) {
47036
- referenceImageBuffer = await readFile33(otherImagePath);
47123
+ if (existsSync38(otherImagePath)) {
47124
+ referenceImageBuffer = await readFile34(otherImagePath);
47037
47125
  break;
47038
47126
  }
47039
47127
  }
@@ -47121,11 +47209,11 @@ Generate the single-person scene image now.`;
47121
47209
  }
47122
47210
  if (regenerateVideo && videoApiKey) {
47123
47211
  options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
47124
- if (!existsSync37(imagePath)) {
47212
+ if (!existsSync38(imagePath)) {
47125
47213
  result.failedScenes.push(sceneNum);
47126
47214
  continue;
47127
47215
  }
47128
- const imageBuffer = await readFile33(imagePath);
47216
+ const imageBuffer = await readFile34(imagePath);
47129
47217
  const videoDuration = segment.duration > 5 ? 10 : 5;
47130
47218
  const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
47131
47219
  if (options.generator === "grok") {
@@ -47347,9 +47435,9 @@ var init_ai_script_pipeline = __esm({
47347
47435
  });
47348
47436
 
47349
47437
  // src/commands/ai-highlights.ts
47350
- 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";
47351
47439
  import { resolve as resolve48, dirname as dirname17, basename as basename13, extname as extname10 } from "node:path";
47352
- import { existsSync as existsSync39 } from "node:fs";
47440
+ import { existsSync as existsSync40 } from "node:fs";
47353
47441
  function filterHighlights(highlights, options) {
47354
47442
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
47355
47443
  filtered.sort((a, b) => b.confidence - a.confidence);
@@ -47389,7 +47477,7 @@ function truncate(text, maxLength) {
47389
47477
  async function executeHighlights(options) {
47390
47478
  try {
47391
47479
  const absPath = resolve48(process.cwd(), options.media);
47392
- if (!existsSync39(absPath)) {
47480
+ if (!existsSync40(absPath)) {
47393
47481
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
47394
47482
  }
47395
47483
  const ext = extname10(absPath).toLowerCase();
@@ -47408,7 +47496,7 @@ async function executeHighlights(options) {
47408
47496
  sourceDuration = await ffprobeDuration(absPath);
47409
47497
  const gemini = new GeminiProvider();
47410
47498
  await gemini.initialize({ apiKey: geminiApiKey });
47411
- const videoBuffer = await readFile35(absPath);
47499
+ const videoBuffer = await readFile36(absPath);
47412
47500
  const criteriaText = options.criteria === "all" || !options.criteria ? "emotional, informative, and funny moments" : `${options.criteria} moments`;
47413
47501
  const durationText = targetDuration ? `Target a total highlight duration of ${targetDuration} seconds.` : "";
47414
47502
  const countText = maxCount ? `Find up to ${maxCount} highlights.` : "";
@@ -47502,10 +47590,10 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
47502
47590
  }
47503
47591
  const whisper = new WhisperProvider();
47504
47592
  await whisper.initialize({ apiKey: openaiApiKey });
47505
- const audioBuffer = await readFile35(audioPath);
47593
+ const audioBuffer = await readFile36(audioPath);
47506
47594
  const audioBlob = new Blob([audioBuffer]);
47507
47595
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
47508
- if (tempAudioPath && existsSync39(tempAudioPath)) {
47596
+ if (tempAudioPath && existsSync40(tempAudioPath)) {
47509
47597
  const { unlink: unlinkFile } = await import("node:fs/promises");
47510
47598
  await unlinkFile(tempAudioPath).catch(() => {
47511
47599
  });
@@ -47592,7 +47680,7 @@ async function executeAutoShorts(options) {
47592
47680
  return { success: false, shorts: [], error: "FFmpeg not found" };
47593
47681
  }
47594
47682
  const absPath = resolve48(process.cwd(), options.video);
47595
- if (!existsSync39(absPath)) {
47683
+ if (!existsSync40(absPath)) {
47596
47684
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
47597
47685
  }
47598
47686
  const targetDuration = options.duration ?? 60;
@@ -47605,7 +47693,7 @@ async function executeAutoShorts(options) {
47605
47693
  }
47606
47694
  const gemini = new GeminiProvider();
47607
47695
  await gemini.initialize({ apiKey: geminiApiKey });
47608
- const videoBuffer = await readFile35(absPath);
47696
+ const videoBuffer = await readFile36(absPath);
47609
47697
  const geminiPrompt = `Analyze this video to find the BEST moments for short-form vertical video content (TikTok, YouTube Shorts, Instagram Reels).
47610
47698
 
47611
47699
  Find ${shortCount * 3} potential clips that are ${targetDuration} seconds or shorter each.
@@ -47682,7 +47770,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
47682
47770
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
47683
47771
  const whisper = new WhisperProvider();
47684
47772
  await whisper.initialize({ apiKey: openaiApiKey });
47685
- const audioBuffer = await readFile35(tempAudio);
47773
+ const audioBuffer = await readFile36(tempAudio);
47686
47774
  const audioBlob = new Blob([audioBuffer]);
47687
47775
  const transcript = await whisper.transcribe(audioBlob, options.language);
47688
47776
  try {
@@ -47720,7 +47808,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
47720
47808
  };
47721
47809
  }
47722
47810
  const outputDir = options.outputDir ? resolve48(process.cwd(), options.outputDir) : dirname17(absPath);
47723
- if (options.outputDir && !existsSync39(outputDir)) {
47811
+ if (options.outputDir && !existsSync40(outputDir)) {
47724
47812
  await mkdir21(outputDir, { recursive: true });
47725
47813
  }
47726
47814
  const result = {
@@ -47803,7 +47891,7 @@ function registerHighlightsCommands(aiCommand) {
47803
47891
  validateOutputPath(options.output);
47804
47892
  }
47805
47893
  const absPath = resolve48(process.cwd(), mediaPath);
47806
- if (!existsSync39(absPath)) {
47894
+ if (!existsSync40(absPath)) {
47807
47895
  exitWithError(notFoundError(absPath));
47808
47896
  }
47809
47897
  if (options.dryRun) {
@@ -47860,7 +47948,7 @@ function registerHighlightsCommands(aiCommand) {
47860
47948
  const geminiSpinner = ora("\u{1F3AC} Analyzing video with Gemini (visual + audio)...").start();
47861
47949
  const gemini = new GeminiProvider();
47862
47950
  await gemini.initialize({ apiKey: geminiApiKey });
47863
- const videoBuffer = await readFile35(absPath);
47951
+ const videoBuffer = await readFile36(absPath);
47864
47952
  const criteriaText = options.criteria === "all" ? "emotional, informative, and funny moments" : `${options.criteria} moments`;
47865
47953
  const durationText = targetDuration ? `Target a total highlight duration of ${targetDuration} seconds.` : "";
47866
47954
  const countText = maxCount ? `Find up to ${maxCount} highlights.` : "";
@@ -47985,12 +48073,12 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
47985
48073
  const transcribeSpinner = ora("\u{1F4DD} Transcribing with Whisper...").start();
47986
48074
  const whisper = new WhisperProvider();
47987
48075
  await whisper.initialize({ apiKey: openaiApiKey });
47988
- const audioBuffer = await readFile35(audioPath);
48076
+ const audioBuffer = await readFile36(audioPath);
47989
48077
  const audioBlob = new Blob([audioBuffer]);
47990
48078
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
47991
48079
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
47992
48080
  transcribeSpinner.fail("Transcription failed");
47993
- if (tempAudioPath && existsSync39(tempAudioPath)) {
48081
+ if (tempAudioPath && existsSync40(tempAudioPath)) {
47994
48082
  const { unlink: unlinkFile } = await import("node:fs/promises");
47995
48083
  await unlinkFile(tempAudioPath).catch(() => {
47996
48084
  });
@@ -47998,7 +48086,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
47998
48086
  exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
47999
48087
  }
48000
48088
  transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
48001
- if (tempAudioPath && existsSync39(tempAudioPath)) {
48089
+ if (tempAudioPath && existsSync40(tempAudioPath)) {
48002
48090
  const { unlink: unlinkFile } = await import("node:fs/promises");
48003
48091
  await unlinkFile(tempAudioPath).catch(() => {
48004
48092
  });
@@ -48104,7 +48192,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
48104
48192
  validateOutputPath(options.output);
48105
48193
  }
48106
48194
  const absPath = resolve48(process.cwd(), videoPath);
48107
- if (!existsSync39(absPath)) {
48195
+ if (!existsSync40(absPath)) {
48108
48196
  exitWithError(notFoundError(absPath));
48109
48197
  }
48110
48198
  if (options.dryRun) {
@@ -48154,7 +48242,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
48154
48242
  const spinner2 = ora("\u{1F3AC} Analyzing video with Gemini (visual + audio)...").start();
48155
48243
  const gemini = new GeminiProvider();
48156
48244
  await gemini.initialize({ apiKey: geminiApiKey });
48157
- const videoBuffer = await readFile35(absPath);
48245
+ const videoBuffer = await readFile36(absPath);
48158
48246
  const geminiPrompt = `Analyze this video to find the BEST moments for short-form vertical video content (TikTok, YouTube Shorts, Instagram Reels).
48159
48247
 
48160
48248
  Find ${shortCount * 3} potential clips that are ${targetDuration} seconds or shorter each.
@@ -48251,7 +48339,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
48251
48339
  spinner2.text = "Transcribing audio...";
48252
48340
  const whisper = new WhisperProvider();
48253
48341
  await whisper.initialize({ apiKey: openaiApiKey });
48254
- const audioBuffer = await readFile35(tempAudio);
48342
+ const audioBuffer = await readFile36(tempAudio);
48255
48343
  const audioBlob = new Blob([audioBuffer]);
48256
48344
  const transcript = await whisper.transcribe(audioBlob, options.language);
48257
48345
  try {
@@ -48297,7 +48385,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
48297
48385
  return;
48298
48386
  }
48299
48387
  const outputDir = options.outputDir ? resolve48(process.cwd(), options.outputDir) : dirname17(absPath);
48300
- if (options.outputDir && !existsSync39(outputDir)) {
48388
+ if (options.outputDir && !existsSync40(outputDir)) {
48301
48389
  await mkdir21(outputDir, { recursive: true });
48302
48390
  }
48303
48391
  for (let i = 0; i < selectedHighlights.length; i++) {
@@ -48314,7 +48402,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
48314
48402
  outputPath = resolve48(outputDir, `${baseName}-short-${i + 1}.mp4`);
48315
48403
  }
48316
48404
  const parentDir = dirname17(outputPath);
48317
- if (!existsSync39(parentDir)) {
48405
+ if (!existsSync40(parentDir)) {
48318
48406
  await mkdir21(parentDir, { recursive: true });
48319
48407
  }
48320
48408
  const { stdout: probeOut } = await execSafe("ffprobe", [
@@ -48389,7 +48477,7 @@ var init_ai_highlights = __esm({
48389
48477
  // src/commands/ai-animated-caption.ts
48390
48478
  import { resolve as resolve49, dirname as dirname18, basename as basename14 } from "node:path";
48391
48479
  import { writeFile as writeFile42, mkdir as mkdir22, rm as rm3 } from "node:fs/promises";
48392
- import { existsSync as existsSync40 } from "node:fs";
48480
+ import { existsSync as existsSync41 } from "node:fs";
48393
48481
  import { tmpdir as tmpdir3 } from "node:os";
48394
48482
  function groupWords(words, options = {}) {
48395
48483
  if (words.length === 0) return [];
@@ -48566,7 +48654,7 @@ async function executeAnimatedCaption(options) {
48566
48654
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
48567
48655
  const absOutputPath = resolve49(process.cwd(), outputPath);
48568
48656
  const outDir = dirname18(absOutputPath);
48569
- if (!existsSync40(outDir)) {
48657
+ if (!existsSync41(outDir)) {
48570
48658
  await mkdir22(outDir, { recursive: true });
48571
48659
  }
48572
48660
  if (tier === "ass") {
@@ -58249,7 +58337,7 @@ __export(detect_exports, {
58249
58337
  executeDetectScenes: () => executeDetectScenes,
58250
58338
  executeDetectSilence: () => executeDetectSilence
58251
58339
  });
58252
- import { readFile as readFile37, writeFile as writeFile44 } from "node:fs/promises";
58340
+ import { readFile as readFile38, writeFile as writeFile44 } from "node:fs/promises";
58253
58341
  import { resolve as resolve53, basename as basename18 } from "node:path";
58254
58342
  async function executeDetectScenes(options) {
58255
58343
  try {
@@ -58493,7 +58581,7 @@ var init_detect = __esm({
58493
58581
  }
58494
58582
  if (options.project) {
58495
58583
  const projectPath = resolve53(process.cwd(), options.project);
58496
- const content = await readFile37(projectPath, "utf-8");
58584
+ const content = await readFile38(projectPath, "utf-8");
58497
58585
  const data = JSON.parse(content);
58498
58586
  const project = Project.fromJSON(data);
58499
58587
  let source = project.getSources().find((s) => s.url === absPath);
@@ -60184,14 +60272,14 @@ var init_adapters = __esm({
60184
60272
  });
60185
60273
 
60186
60274
  // src/commands/_shared/storyboard-revise.ts
60187
- import { existsSync as existsSync44 } from "node:fs";
60188
- import { readFile as readFile41, writeFile as writeFile47 } from "node:fs/promises";
60189
- 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";
60190
60278
  async function executeStoryboardRevision(opts) {
60191
60279
  const projectDir = resolve57(opts.projectDir);
60192
- const storyboardPath2 = join22(projectDir, "STORYBOARD.md");
60280
+ const storyboardPath2 = join23(projectDir, "STORYBOARD.md");
60193
60281
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
60194
- if (!existsSync44(storyboardPath2)) {
60282
+ if (!existsSync45(storyboardPath2)) {
60195
60283
  return failureResult({
60196
60284
  projectDir,
60197
60285
  storyboardPath: storyboardPath2,
@@ -60201,7 +60289,7 @@ async function executeStoryboardRevision(opts) {
60201
60289
  retryWith: [`vibe init ${projectDir} --from "<brief>" --json`]
60202
60290
  });
60203
60291
  }
60204
- const currentStoryboard = await readFile41(storyboardPath2, "utf-8");
60292
+ const currentStoryboard = await readFile42(storyboardPath2, "utf-8");
60205
60293
  const projectConfig = await readProjectConfig(projectDir);
60206
60294
  const explicitComposer = opts.composer ?? projectConfig.config.providers.composer ?? void 0;
60207
60295
  let provider = explicitComposer ?? "claude";
@@ -60424,18 +60512,18 @@ function diffChangedBeats(before, after) {
60424
60512
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
60425
60513
  }
60426
60514
  async function readRevisionContext(projectDir) {
60427
- const designMd = await readOptionalText(join22(projectDir, "DESIGN.md"));
60428
- 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"));
60429
60517
  return { designMd, buildReportJson };
60430
60518
  }
60431
60519
  async function readOptionalText(path2) {
60432
- if (!existsSync44(path2)) return null;
60433
- return readFile41(path2, "utf-8");
60520
+ if (!existsSync45(path2)) return null;
60521
+ return readFile42(path2, "utf-8");
60434
60522
  }
60435
60523
  async function readOptionalJsonText(path2) {
60436
- if (!existsSync44(path2)) return null;
60524
+ if (!existsSync45(path2)) return null;
60437
60525
  try {
60438
- const parsed = JSON.parse(await readFile41(path2, "utf-8"));
60526
+ const parsed = JSON.parse(await readFile42(path2, "utf-8"));
60439
60527
  return JSON.stringify(parsed, null, 2);
60440
60528
  } catch {
60441
60529
  return null;
@@ -60483,7 +60571,7 @@ var init_storyboard_revise = __esm({
60483
60571
  });
60484
60572
 
60485
60573
  // src/commands/_shared/build-asset-reference.ts
60486
- import { existsSync as existsSync46 } from "node:fs";
60574
+ import { existsSync as existsSync47 } from "node:fs";
60487
60575
  import { extname as extname13, isAbsolute as isAbsolute5, relative as relative10, resolve as resolve59, sep } from "node:path";
60488
60576
  function resolveTypedAssetReference(projectDir, kind, value) {
60489
60577
  const raw = stringOrUndefined2(value);
@@ -60514,7 +60602,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
60514
60602
  const absPath = isAbsolute5(raw) ? resolve59(raw) : resolve59(projectDir, raw);
60515
60603
  const rel = relative10(resolve59(projectDir), absPath);
60516
60604
  const withinProject = rel.length > 0 && !rel.startsWith("..") && !isAbsolute5(rel);
60517
- const exists = withinProject && existsSync46(absPath);
60605
+ const exists = withinProject && existsSync47(absPath);
60518
60606
  let error = forcedError;
60519
60607
  if (!error && inferred && inferred !== kind && !isNarrationAudioReference(kind, ext)) {
60520
60608
  error = `Asset reference "${raw}" is a ${inferred} asset, not ${kind}.`;
@@ -60701,9 +60789,9 @@ var init_build_backdrop_prompt = __esm({
60701
60789
  });
60702
60790
 
60703
60791
  // src/commands/_shared/build-plan.ts
60704
- import { existsSync as existsSync47 } from "node:fs";
60705
- import { readFile as readFile43 } from "node:fs/promises";
60706
- 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";
60707
60795
  function backdropCostUsd(quality) {
60708
60796
  return quality === "hd" ? 0.2 : 0.05;
60709
60797
  }
@@ -60712,11 +60800,11 @@ async function createBuildPlan(opts) {
60712
60800
  loadPlanEnv(projectDir);
60713
60801
  const stage = opts.stage ?? "all";
60714
60802
  const config2 = await readProjectConfig(projectDir);
60715
- const storyboardPath2 = join23(projectDir, "STORYBOARD.md");
60803
+ const storyboardPath2 = join24(projectDir, "STORYBOARD.md");
60716
60804
  const warnings = [];
60717
60805
  const retryWith = [];
60718
60806
  const providerResolution = [];
60719
- if (!existsSync47(storyboardPath2)) {
60807
+ if (!existsSync48(storyboardPath2)) {
60720
60808
  const validation2 = {
60721
60809
  ok: false,
60722
60810
  issues: [
@@ -60745,7 +60833,7 @@ async function createBuildPlan(opts) {
60745
60833
  validation: validation2
60746
60834
  });
60747
60835
  }
60748
- const storyboardMd = await readFile43(storyboardPath2, "utf-8");
60836
+ const storyboardMd = await readFile44(storyboardPath2, "utf-8");
60749
60837
  const validation = validateStoryboardMarkdown(storyboardMd);
60750
60838
  const validationIssues = [...validation.issues];
60751
60839
  const parsed = parseStoryboard(storyboardMd);
@@ -60771,7 +60859,7 @@ async function createBuildPlan(opts) {
60771
60859
  const imageQuality = opts.imageQuality ?? config2.config.build.imageQuality ?? "hd";
60772
60860
  const imageSize2 = opts.imageSize ?? config2.config.build.imageSize ?? "1536x1024";
60773
60861
  const imageRatio = imageRatioForSize(imageSize2);
60774
- 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`)));
60775
60863
  const resolved = await resolvePlanProviders({
60776
60864
  projectDir,
60777
60865
  config: config2,
@@ -60919,7 +61007,7 @@ async function createBuildPlan(opts) {
60919
61007
  active: includeAssets
60920
61008
  }) : null;
60921
61009
  const compositionPath = `compositions/scene-${beat.id}.html`;
60922
- const compositionExists = existsSync47(join23(projectDir, compositionPath));
61010
+ const compositionExists = existsSync48(join24(projectDir, compositionPath));
60923
61011
  for (const asset of [narration, backdrop, keyframe, video, music]) {
60924
61012
  if (!asset) continue;
60925
61013
  if (asset.referenceError) {
@@ -60988,7 +61076,7 @@ async function createBuildPlan(opts) {
60988
61076
  }
60989
61077
  }
60990
61078
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
60991
- if (!existsSync47(join23(projectDir, config2.config.composition.entry))) {
61079
+ if (!existsSync48(join24(projectDir, config2.config.composition.entry))) {
60992
61080
  missing.add("root-composition");
60993
61081
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
60994
61082
  }
@@ -61022,7 +61110,7 @@ async function createBuildPlan(opts) {
61022
61110
  }
61023
61111
  if (referenced.size > 0) {
61024
61112
  const toGenerate = resolveCharacters(parsed.frontmatter).filter(
61025
- (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`)))
61026
61114
  );
61027
61115
  if (toGenerate.length > 0) {
61028
61116
  estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
@@ -61135,8 +61223,8 @@ function assetPlan(opts) {
61135
61223
  reason: ready ? "referenced-asset" : "invalid-reference"
61136
61224
  };
61137
61225
  }
61138
- const canonicalExists = existsSync47(join23(opts.projectDir, opts.path));
61139
- 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;
61140
61228
  const freshness = assetFreshnessFromMetadata({
61141
61229
  projectDir: opts.projectDir,
61142
61230
  kind: opts.kind,
@@ -61334,7 +61422,7 @@ async function firstConfiguredComposer(projectDir) {
61334
61422
  }
61335
61423
  function firstExisting2(projectDir, paths) {
61336
61424
  for (const path2 of paths) {
61337
- if (existsSync47(join23(projectDir, path2))) return path2;
61425
+ if (existsSync48(join24(projectDir, path2))) return path2;
61338
61426
  }
61339
61427
  return null;
61340
61428
  }
@@ -61375,12 +61463,12 @@ function unique3(items) {
61375
61463
  return [...new Set(items.filter((item) => item.length > 0))];
61376
61464
  }
61377
61465
  function loadPlanEnv(projectDir) {
61378
- (0, import_dotenv3.config)({ path: join23(projectDir, ".env"), quiet: true });
61466
+ (0, import_dotenv3.config)({ path: join24(projectDir, ".env"), quiet: true });
61379
61467
  (0, import_dotenv3.config)({ path: resolve60(process.cwd(), ".env"), quiet: true });
61380
61468
  let dir = process.cwd();
61381
61469
  while (dir !== dirname21(dir)) {
61382
- if (existsSync47(join23(dir, "pnpm-workspace.yaml"))) {
61383
- (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 });
61384
61472
  return;
61385
61473
  }
61386
61474
  dir = dirname21(dir);
@@ -61461,7 +61549,7 @@ var init_concurrency = __esm({
61461
61549
  });
61462
61550
 
61463
61551
  // src/commands/_shared/scene-audio-scan.ts
61464
- import { readFile as readFile44 } from "node:fs/promises";
61552
+ import { readFile as readFile45 } from "node:fs/promises";
61465
61553
  import { resolve as resolve62 } from "node:path";
61466
61554
  function parseRootClips(rootHtml) {
61467
61555
  const clips = [];
@@ -61502,7 +61590,7 @@ function makeFsCompositionReader(projectDir) {
61502
61590
  return async (compositionSrcRel) => {
61503
61591
  const abs = resolve62(projectDir, compositionSrcRel);
61504
61592
  try {
61505
- return await readFile44(abs, "utf-8");
61593
+ return await readFile45(abs, "utf-8");
61506
61594
  } catch {
61507
61595
  return null;
61508
61596
  }
@@ -61702,9 +61790,9 @@ __export(scene_render_exports, {
61702
61790
  qualityToCrf: () => qualityToCrf2
61703
61791
  });
61704
61792
  import { execFile as execFile2 } from "node:child_process";
61705
- import { existsSync as existsSync48 } from "node:fs";
61706
- import { mkdir as mkdir25, readFile as readFile45, stat as stat8, writeFile as writeFile49 } from "node:fs/promises";
61707
- 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";
61708
61796
  import { promisify as promisify2 } from "node:util";
61709
61797
  import {
61710
61798
  createRenderJob,
@@ -61796,7 +61884,7 @@ async function executeSceneRender(opts = {}) {
61796
61884
  }
61797
61885
  let autoSyncApplied = false;
61798
61886
  let autoSyncWarning;
61799
- if (!opts.beatId && existsSync48(join24(projectDir, "STORYBOARD.md"))) {
61887
+ if (!opts.beatId && existsSync49(join25(projectDir, "STORYBOARD.md"))) {
61800
61888
  try {
61801
61889
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
61802
61890
  const subPlans = await createSubCompDurationSyncPlans({
@@ -61888,7 +61976,7 @@ async function executeSceneRender(opts = {}) {
61888
61976
  let audioMuxWarning;
61889
61977
  try {
61890
61978
  opts.onProgress?.(0.95, "Mixing audio");
61891
- const rootHtml = await readFile45(resolve64(projectDir, root), "utf-8");
61979
+ const rootHtml = await readFile46(resolve64(projectDir, root), "utf-8");
61892
61980
  const audios = await scanSceneAudio({ projectDir, rootHtml });
61893
61981
  audioCount = audios.length;
61894
61982
  if (audios.length > 0) {
@@ -61973,11 +62061,11 @@ async function safeStat(p) {
61973
62061
  }
61974
62062
  async function prepareBeatRenderRoot(opts) {
61975
62063
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment2(opts.beatId)}.html`;
61976
- const reportPath = join24(opts.projectDir, "build-report.json");
62064
+ const reportPath = join25(opts.projectDir, "build-report.json");
61977
62065
  const retryWith = [
61978
62066
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
61979
62067
  ];
61980
- if (!existsSync48(reportPath)) {
62068
+ if (!existsSync49(reportPath)) {
61981
62069
  return {
61982
62070
  success: false,
61983
62071
  root,
@@ -61988,7 +62076,7 @@ async function prepareBeatRenderRoot(opts) {
61988
62076
  }
61989
62077
  let report;
61990
62078
  try {
61991
- report = JSON.parse(await readFile45(reportPath, "utf-8"));
62079
+ report = JSON.parse(await readFile46(reportPath, "utf-8"));
61992
62080
  } catch (error) {
61993
62081
  return {
61994
62082
  success: false,
@@ -62057,8 +62145,8 @@ async function prepareBeatRenderRoot(opts) {
62057
62145
  durationSec,
62058
62146
  narrationPath
62059
62147
  });
62060
- await mkdir25(dirname23(join24(opts.projectDir, root)), { recursive: true });
62061
- 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");
62062
62150
  return {
62063
62151
  success: true,
62064
62152
  root,
@@ -62122,7 +62210,7 @@ function reportedBeatAssetPaths(beat) {
62122
62210
  ]);
62123
62211
  }
62124
62212
  async function writeRenderReport(projectDir, result) {
62125
- const reportPath = join24(projectDir, "render-report.json");
62213
+ const reportPath = join25(projectDir, "render-report.json");
62126
62214
  try {
62127
62215
  await writeFile49(
62128
62216
  reportPath,
@@ -62164,7 +62252,7 @@ async function writeRenderReport(projectDir, result) {
62164
62252
  }
62165
62253
  function pathExists3(projectDir, value) {
62166
62254
  const abs = isAbsolute6(value) ? value : resolve64(projectDir, value);
62167
- return existsSync48(abs);
62255
+ return existsSync49(abs);
62168
62256
  }
62169
62257
  function stringValue(value) {
62170
62258
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -62223,9 +62311,9 @@ __export(scene_build_exports, {
62223
62311
  executeSceneBuild: () => executeSceneBuild,
62224
62312
  resolveSceneBuildMode: () => resolveSceneBuildMode
62225
62313
  });
62226
- import { existsSync as existsSync49, readFileSync as readFileSync3 } from "node:fs";
62227
- import { copyFile as copyFile5, mkdir as mkdir26, readFile as readFile46, rm as rm4, writeFile as writeFile50 } from "node:fs/promises";
62228
- 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";
62229
62317
  async function executeSceneBuild(opts) {
62230
62318
  const startedAt = Date.now();
62231
62319
  const projectDir = resolve65(opts.projectDir);
@@ -62244,7 +62332,7 @@ async function executeSceneBuild(opts) {
62244
62332
  );
62245
62333
  }
62246
62334
  let sceneRepair = skippedSceneRepairSummary();
62247
- const storyboardPath2 = join25(projectDir, "STORYBOARD.md");
62335
+ const storyboardPath2 = join26(projectDir, "STORYBOARD.md");
62248
62336
  const buildPlan = await createBuildPlan({
62249
62337
  projectDir,
62250
62338
  stage: selectedStage,
@@ -62295,8 +62383,8 @@ async function executeSceneBuild(opts) {
62295
62383
  }
62296
62384
  if (!buildPlan.validation.ok) {
62297
62385
  let invalidBeats = [];
62298
- if (existsSync49(storyboardPath2)) {
62299
- invalidBeats = parseStoryboard(await readFile46(storyboardPath2, "utf-8")).beats;
62386
+ if (existsSync50(storyboardPath2)) {
62387
+ invalidBeats = parseStoryboard(await readFile47(storyboardPath2, "utf-8")).beats;
62300
62388
  }
62301
62389
  return finishBuildResult({
62302
62390
  success: false,
@@ -62320,7 +62408,7 @@ async function executeSceneBuild(opts) {
62320
62408
  totalLatencyMs: Date.now() - startedAt
62321
62409
  });
62322
62410
  }
62323
- if (!existsSync49(storyboardPath2)) {
62411
+ if (!existsSync50(storyboardPath2)) {
62324
62412
  return finishBuildResult({
62325
62413
  success: false,
62326
62414
  phase: "failed",
@@ -62334,7 +62422,7 @@ async function executeSceneBuild(opts) {
62334
62422
  totalLatencyMs: Date.now() - startedAt
62335
62423
  });
62336
62424
  }
62337
- const storyboardMd = await readFile46(storyboardPath2, "utf-8");
62425
+ const storyboardMd = await readFile47(storyboardPath2, "utf-8");
62338
62426
  const parsed = parseStoryboard(storyboardMd);
62339
62427
  if (parsed.beats.length === 0) {
62340
62428
  return finishBuildResult({
@@ -62441,6 +62529,7 @@ async function executeSceneBuild(opts) {
62441
62529
  skipVideo: opts.skipVideo ?? false,
62442
62530
  skipKeyframe: opts.skipKeyframe ?? false,
62443
62531
  skipMusic: opts.skipMusic ?? false,
62532
+ skipTranscript: opts.skipTranscript ?? false,
62444
62533
  force: opts.force ?? false,
62445
62534
  onProgress,
62446
62535
  characterPaths
@@ -62528,9 +62617,9 @@ async function executeSceneBuild(opts) {
62528
62617
  let composeData;
62529
62618
  if (shouldRunStage(selectedStage, "compose")) {
62530
62619
  if (mode === "agent") {
62531
- const compositionsDir = join25(projectDir, "compositions");
62620
+ const compositionsDir = join26(projectDir, "compositions");
62532
62621
  const missingBeats = activeBeats.filter(
62533
- (b) => !existsSync49(join25(compositionsDir, `scene-${b.id}.html`))
62622
+ (b) => !existsSync50(join26(compositionsDir, `scene-${b.id}.html`))
62534
62623
  );
62535
62624
  if (missingBeats.length > 0) {
62536
62625
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -62619,8 +62708,8 @@ async function executeSceneBuild(opts) {
62619
62708
  if (stageReports.compose.status === "done") {
62620
62709
  for (const beat of activeBeats) {
62621
62710
  const videoRel = `assets/video-${beat.id}.mp4`;
62622
- const compositionAbs = join25(projectDir, "compositions", `scene-${beat.id}.html`);
62623
- 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;
62624
62713
  const html = readFileSync3(compositionAbs, "utf-8");
62625
62714
  if (!html.includes(videoRel)) {
62626
62715
  warnings.push(
@@ -62678,7 +62767,7 @@ async function executeSceneBuild(opts) {
62678
62767
  });
62679
62768
  }
62680
62769
  if (shouldRunStage(selectedStage, "sync")) {
62681
- if (!existsSync49(join25(projectDir, "index.html"))) {
62770
+ if (!existsSync50(join26(projectDir, "index.html"))) {
62682
62771
  await scaffoldSceneProject({
62683
62772
  dir: projectDir,
62684
62773
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -62823,6 +62912,9 @@ async function buildBeatPrimitives(beat, ctx) {
62823
62912
  ctx.skipVideo ? skipped("video", beat.id, "--skip-video", ctx) : dispatchVideo(beat, ctx, keyframe),
62824
62913
  ctx.skipMusic ? skipped("music", beat.id, "--skip-music", ctx) : dispatchMusic(beat, ctx)
62825
62914
  ]);
62915
+ if (!ctx.skipTranscript) {
62916
+ await dispatchTranscript(beat, narration, ctx);
62917
+ }
62826
62918
  return {
62827
62919
  outcome: {
62828
62920
  beatId: beat.id,
@@ -62940,7 +63032,7 @@ async function dispatchNarration(beat, ctx) {
62940
63032
  return {
62941
63033
  status: "cached",
62942
63034
  path: existingRel,
62943
- durationSec: await safeAudioDuration(join25(ctx.projectDir, existingRel)),
63035
+ durationSec: await safeAudioDuration(join26(ctx.projectDir, existingRel)),
62944
63036
  provider: metadata?.provider,
62945
63037
  cachePath: metadata?.cachePath,
62946
63038
  cacheKey: metadata?.cacheKey,
@@ -62964,9 +63056,9 @@ async function dispatchNarration(beat, ctx) {
62964
63056
  voice,
62965
63057
  ext: resolution.audioExtension
62966
63058
  });
62967
- const cacheAbs = join25(ctx.projectDir, cache.path);
63059
+ const cacheAbs = join26(ctx.projectDir, cache.path);
62968
63060
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
62969
- const abs = join25(ctx.projectDir, rel);
63061
+ const abs = join26(ctx.projectDir, rel);
62970
63062
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
62971
63063
  projectDir: ctx.projectDir,
62972
63064
  kind: "narration",
@@ -62980,7 +63072,7 @@ async function dispatchNarration(beat, ctx) {
62980
63072
  return {
62981
63073
  status: "cached",
62982
63074
  path: existingRel,
62983
- durationSec: await safeAudioDuration(join25(ctx.projectDir, existingRel)),
63075
+ durationSec: await safeAudioDuration(join26(ctx.projectDir, existingRel)),
62984
63076
  provider: resolution.provider,
62985
63077
  cachePath: cache.path,
62986
63078
  cacheKey: cache.key,
@@ -62988,7 +63080,7 @@ async function dispatchNarration(beat, ctx) {
62988
63080
  freshness: "fresh"
62989
63081
  };
62990
63082
  }
62991
- if (existsSync49(cacheAbs) && !ctx.force) {
63083
+ if (existsSync50(cacheAbs) && !ctx.force) {
62992
63084
  await mkdir26(dirname24(abs), { recursive: true });
62993
63085
  await copyFile5(cacheAbs, abs);
62994
63086
  await writeAssetMetadata({
@@ -63023,8 +63115,8 @@ async function dispatchNarration(beat, ctx) {
63023
63115
  await mkdir26(dirname24(abs), { recursive: true });
63024
63116
  await writeFile50(abs, result.audioBuffer);
63025
63117
  for (const otherExt of ["mp3", "wav"]) {
63026
- const sibling = join25(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
63027
- 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 });
63028
63120
  }
63029
63121
  await mkdir26(dirname24(cacheAbs), { recursive: true });
63030
63122
  await writeFile50(cacheAbs, result.audioBuffer);
@@ -63056,6 +63148,42 @@ async function dispatchNarration(beat, ctx) {
63056
63148
  freshness: "fresh"
63057
63149
  };
63058
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
+ }
63059
63187
  function characterSheetPrompt(name, description) {
63060
63188
  return [
63061
63189
  `Character reference sheet of an original fictional character named ${name}: ${description}.`,
@@ -63073,7 +63201,7 @@ async function buildCharacters(characters, ctx) {
63073
63201
  const size = ctx.imageSize ?? "1536x1024";
63074
63202
  for (const character of characters) {
63075
63203
  if (character.imagePath) {
63076
- if (existsSync49(join25(ctx.projectDir, character.imagePath))) {
63204
+ if (existsSync50(join26(ctx.projectDir, character.imagePath))) {
63077
63205
  paths.set(character.name, character.imagePath);
63078
63206
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
63079
63207
  } else {
@@ -63086,7 +63214,7 @@ async function buildCharacters(characters, ctx) {
63086
63214
  if (!character.prompt) continue;
63087
63215
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
63088
63216
  const rel = `assets/character-${character.name}.png`;
63089
- const abs = join25(ctx.projectDir, rel);
63217
+ const abs = join26(ctx.projectDir, rel);
63090
63218
  const metadataOptions = { quality: ctx.imageQuality, size };
63091
63219
  const cache = characterCacheDescriptor({
63092
63220
  name: character.name,
@@ -63095,7 +63223,7 @@ async function buildCharacters(characters, ctx) {
63095
63223
  quality: ctx.imageQuality,
63096
63224
  size
63097
63225
  });
63098
- if (existsSync49(abs) && !ctx.force) {
63226
+ if (existsSync50(abs) && !ctx.force) {
63099
63227
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
63100
63228
  if (!metadata || isFreshCanonicalAsset({
63101
63229
  projectDir: ctx.projectDir,
@@ -63111,8 +63239,8 @@ async function buildCharacters(characters, ctx) {
63111
63239
  continue;
63112
63240
  }
63113
63241
  }
63114
- const cacheAbs = join25(ctx.projectDir, cache.path);
63115
- if (existsSync49(cacheAbs) && !ctx.force) {
63242
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63243
+ if (existsSync50(cacheAbs) && !ctx.force) {
63116
63244
  await mkdir26(dirname24(abs), { recursive: true });
63117
63245
  await copyFile5(cacheAbs, abs);
63118
63246
  await writeAssetMetadata({
@@ -63178,7 +63306,7 @@ async function dispatchBackdrop(beat, ctx) {
63178
63306
  if (!cue) return { status: "no-cue" };
63179
63307
  const prompt3 = augmentBackdropPrompt(cue);
63180
63308
  const rel = `assets/backdrop-${beat.id}.png`;
63181
- const abs = join25(ctx.projectDir, rel);
63309
+ const abs = join26(ctx.projectDir, rel);
63182
63310
  const size = ctx.imageSize ?? "1536x1024";
63183
63311
  const ratio = imageRatioForSize(size);
63184
63312
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -63191,7 +63319,7 @@ async function dispatchBackdrop(beat, ctx) {
63191
63319
  ratio
63192
63320
  });
63193
63321
  const metadataPath = assetMetadataPath("backdrop", beat.id);
63194
- if (existsSync49(abs) && !ctx.force) {
63322
+ if (existsSync50(abs) && !ctx.force) {
63195
63323
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
63196
63324
  if (!metadata || isFreshCanonicalAsset({
63197
63325
  projectDir: ctx.projectDir,
@@ -63214,8 +63342,8 @@ async function dispatchBackdrop(beat, ctx) {
63214
63342
  };
63215
63343
  }
63216
63344
  }
63217
- const cacheAbs = join25(ctx.projectDir, cache.path);
63218
- if (existsSync49(cacheAbs) && !ctx.force) {
63345
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63346
+ if (existsSync50(cacheAbs) && !ctx.force) {
63219
63347
  await mkdir26(dirname24(abs), { recursive: true });
63220
63348
  await copyFile5(cacheAbs, abs);
63221
63349
  await writeAssetMetadata({
@@ -63347,7 +63475,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
63347
63475
  }
63348
63476
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63349
63477
  const rel = `assets/keyframe-${beat.id}.png`;
63350
- const abs = join25(ctx.projectDir, rel);
63478
+ const abs = join26(ctx.projectDir, rel);
63351
63479
  const size = ctx.imageSize ?? "1536x1024";
63352
63480
  const ratio = imageRatioForSize(size);
63353
63481
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -63360,7 +63488,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63360
63488
  ratio,
63361
63489
  characters: characterRefsRel
63362
63490
  });
63363
- if (existsSync49(abs) && !ctx.force) {
63491
+ if (existsSync50(abs) && !ctx.force) {
63364
63492
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
63365
63493
  if (!metadata || isFreshCanonicalAsset({
63366
63494
  projectDir: ctx.projectDir,
@@ -63374,8 +63502,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63374
63502
  return { status: "cached", rel };
63375
63503
  }
63376
63504
  }
63377
- const cacheAbs = join25(ctx.projectDir, cache.path);
63378
- if (existsSync49(cacheAbs) && !ctx.force) {
63505
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63506
+ if (existsSync50(cacheAbs) && !ctx.force) {
63379
63507
  await mkdir26(dirname24(abs), { recursive: true });
63380
63508
  await copyFile5(cacheAbs, abs);
63381
63509
  await writeAssetMetadata({
@@ -63393,8 +63521,8 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
63393
63521
  }
63394
63522
  const sheetBuffers = [];
63395
63523
  for (const relPath of characterRefsRel) {
63396
- const sheetAbs = join25(ctx.projectDir, relPath);
63397
- if (existsSync49(sheetAbs)) sheetBuffers.push(await readFile46(sheetAbs));
63524
+ const sheetAbs = join26(ctx.projectDir, relPath);
63525
+ if (existsSync50(sheetAbs)) sheetBuffers.push(await readFile47(sheetAbs));
63398
63526
  }
63399
63527
  const imgCtx = {
63400
63528
  projectDir: ctx.projectDir,
@@ -63463,9 +63591,9 @@ async function dispatchVideo(beat, ctx, keyframe) {
63463
63591
  const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
63464
63592
  if (!prompt3) return { status: "no-cue" };
63465
63593
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
63466
- const characterRefsAbs = characterRefsRel.map((p) => join25(ctx.projectDir, p));
63594
+ const characterRefsAbs = characterRefsRel.map((p) => join26(ctx.projectDir, p));
63467
63595
  const rel = `assets/video-${beat.id}.mp4`;
63468
- const abs = join25(ctx.projectDir, rel);
63596
+ const abs = join26(ctx.projectDir, rel);
63469
63597
  const cache = videoCacheDescriptor({
63470
63598
  beatId: beat.id,
63471
63599
  cue: prompt3,
@@ -63477,7 +63605,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
63477
63605
  keyframe: keyframeCue
63478
63606
  });
63479
63607
  const metadataPath = assetMetadataPath("video", beat.id);
63480
- if (existsSync49(abs) && !ctx.force) {
63608
+ if (existsSync50(abs) && !ctx.force) {
63481
63609
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
63482
63610
  if (!metadata || isFreshCanonicalAsset({
63483
63611
  projectDir: ctx.projectDir,
@@ -63500,8 +63628,8 @@ async function dispatchVideo(beat, ctx, keyframe) {
63500
63628
  };
63501
63629
  }
63502
63630
  }
63503
- const cacheAbs = join25(ctx.projectDir, cache.path);
63504
- if (existsSync49(cacheAbs) && !ctx.force) {
63631
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63632
+ if (existsSync50(cacheAbs) && !ctx.force) {
63505
63633
  await mkdir26(dirname24(abs), { recursive: true });
63506
63634
  await copyFile5(cacheAbs, abs);
63507
63635
  await writeAssetMetadata({
@@ -63532,7 +63660,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
63532
63660
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
63533
63661
  return { status: "skipped" };
63534
63662
  }
63535
- const keyframeImageAbs = keyframeImageRel ? join25(ctx.projectDir, keyframeImageRel) : void 0;
63663
+ const keyframeImageAbs = keyframeImageRel ? join26(ctx.projectDir, keyframeImageRel) : void 0;
63536
63664
  loadSceneBuildEnv(ctx.projectDir);
63537
63665
  const result = await executeVideoGenerate({
63538
63666
  prompt: prompt3,
@@ -63556,7 +63684,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
63556
63684
  ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
63557
63685
  return { status: "failed", error };
63558
63686
  }
63559
- if (result.status === "completed" && existsSync49(abs)) {
63687
+ if (result.status === "completed" && existsSync50(abs)) {
63560
63688
  await mkdir26(dirname24(cacheAbs), { recursive: true });
63561
63689
  await copyFile5(abs, cacheAbs);
63562
63690
  await writeAssetMetadata({
@@ -63624,7 +63752,7 @@ async function dispatchMusic(beat, ctx) {
63624
63752
  const prompt3 = stringOrUndefined4(beat.cues?.music);
63625
63753
  if (!prompt3) return { status: "no-cue" };
63626
63754
  const rel = `assets/music-${beat.id}.mp3`;
63627
- const abs = join25(ctx.projectDir, rel);
63755
+ const abs = join26(ctx.projectDir, rel);
63628
63756
  const duration = normalizeMusicDuration(beat.duration);
63629
63757
  const cache = musicCacheDescriptor({
63630
63758
  beatId: beat.id,
@@ -63633,7 +63761,7 @@ async function dispatchMusic(beat, ctx) {
63633
63761
  duration
63634
63762
  });
63635
63763
  const metadataPath = assetMetadataPath("music", beat.id);
63636
- if (existsSync49(abs) && !ctx.force) {
63764
+ if (existsSync50(abs) && !ctx.force) {
63637
63765
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
63638
63766
  if (!metadata || isFreshCanonicalAsset({
63639
63767
  projectDir: ctx.projectDir,
@@ -63657,8 +63785,8 @@ async function dispatchMusic(beat, ctx) {
63657
63785
  };
63658
63786
  }
63659
63787
  }
63660
- const cacheAbs = join25(ctx.projectDir, cache.path);
63661
- if (existsSync49(cacheAbs) && !ctx.force) {
63788
+ const cacheAbs = join26(ctx.projectDir, cache.path);
63789
+ if (existsSync50(cacheAbs) && !ctx.force) {
63662
63790
  await mkdir26(dirname24(abs), { recursive: true });
63663
63791
  await copyFile5(cacheAbs, abs);
63664
63792
  await writeAssetMetadata({
@@ -63732,7 +63860,7 @@ async function dispatchMusic(beat, ctx) {
63732
63860
  metadataPath
63733
63861
  };
63734
63862
  }
63735
- if (!existsSync49(abs)) {
63863
+ if (!existsSync50(abs)) {
63736
63864
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
63737
63865
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
63738
63866
  return { status: "failed", error };
@@ -63768,12 +63896,12 @@ async function dispatchMusic(beat, ctx) {
63768
63896
  };
63769
63897
  }
63770
63898
  function loadSceneBuildEnv(projectDir) {
63771
- (0, import_dotenv4.config)({ path: join25(projectDir, ".env"), quiet: true });
63899
+ (0, import_dotenv4.config)({ path: join26(projectDir, ".env"), quiet: true });
63772
63900
  (0, import_dotenv4.config)({ path: resolve65(process.cwd(), ".env"), quiet: true });
63773
63901
  let dir = process.cwd();
63774
63902
  while (dir !== dirname24(dir)) {
63775
- if (existsSync49(join25(dir, "pnpm-workspace.yaml"))) {
63776
- (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 });
63777
63905
  return;
63778
63906
  }
63779
63907
  dir = dirname24(dir);
@@ -63989,7 +64117,7 @@ function mergeBeatOutcomes(base, updates) {
63989
64117
  }
63990
64118
  function firstExisting3(projectDir, relPaths) {
63991
64119
  for (const rel of relPaths) {
63992
- if (existsSync49(join25(projectDir, rel))) return rel;
64120
+ if (existsSync50(join26(projectDir, rel))) return rel;
63993
64121
  }
63994
64122
  return null;
63995
64123
  }
@@ -64020,7 +64148,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
64020
64148
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
64021
64149
  }
64022
64150
  async function finalizeBuildResult(projectDir, startedAt, result) {
64023
- const reportPath = join25(projectDir, "build-report.json");
64151
+ const reportPath = join26(projectDir, "build-report.json");
64024
64152
  if (result.stageReports) {
64025
64153
  for (const report of Object.values(result.stageReports)) {
64026
64154
  if (report.status === "pending") report.status = "skipped";
@@ -64179,7 +64307,7 @@ function normalizeReportDuration(value) {
64179
64307
  }
64180
64308
  function buildReportComposition(projectDir, result, beatId) {
64181
64309
  const path2 = `compositions/scene-${beatId}.html`;
64182
- const exists = existsSync49(join25(projectDir, path2));
64310
+ const exists = existsSync50(join26(projectDir, path2));
64183
64311
  const written = composeWrittenEntry(result, beatId);
64184
64312
  if (written) {
64185
64313
  return {
@@ -64236,8 +64364,8 @@ function summarizeBuildBeats(projectDir, beats) {
64236
64364
  let compositionsReady = 0;
64237
64365
  for (const beat of beats) {
64238
64366
  if (beatAssetsReady(beat)) assetsReady += 1;
64239
- const compositionPath = join25(projectDir, "compositions", `scene-${beat.beatId}.html`);
64240
- if (existsSync49(compositionPath)) {
64367
+ const compositionPath = join26(projectDir, "compositions", `scene-${beat.beatId}.html`);
64368
+ if (existsSync50(compositionPath)) {
64241
64369
  compositionsReady += 1;
64242
64370
  } else {
64243
64371
  needsAuthor.push(beat.beatId);
@@ -64279,6 +64407,8 @@ var init_scene_build = __esm({
64279
64407
  init_compose_scenes_skills();
64280
64408
  init_compose_prompts();
64281
64409
  init_config();
64410
+ init_api_key();
64411
+ init_transcribe_narration();
64282
64412
  init_scene_render();
64283
64413
  init_storyboard_parse();
64284
64414
  init_scene_project();
@@ -64369,18 +64499,18 @@ __export(ai_audio_exports, {
64369
64499
  executeVoiceClone: () => executeVoiceClone
64370
64500
  });
64371
64501
  import { resolve as resolve73, dirname as dirname26, basename as basename22, extname as extname15 } from "node:path";
64372
- import { readFile as readFile48, writeFile as writeFile51 } from "node:fs/promises";
64373
- 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";
64374
64504
  async function executeTranscribe(options) {
64375
64505
  const { audioPath, language, output, format, apiKey } = options;
64376
64506
  try {
64377
64507
  const key = apiKey || process.env.OPENAI_API_KEY;
64378
64508
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
64379
64509
  const absPath = resolve73(process.cwd(), audioPath);
64380
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${absPath}` };
64510
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
64381
64511
  const whisper = new WhisperProvider();
64382
64512
  await whisper.initialize({ apiKey: key });
64383
- const audioBuffer = await readFile48(absPath);
64513
+ const audioBuffer = await readFile49(absPath);
64384
64514
  const audioBlob = new Blob([audioBuffer]);
64385
64515
  const result = await whisper.transcribe(audioBlob, language);
64386
64516
  if (result.status === "failed") {
@@ -64410,8 +64540,8 @@ async function executeIsolate(options) {
64410
64540
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
64411
64541
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
64412
64542
  const absPath = resolve73(process.cwd(), audioPath);
64413
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${absPath}` };
64414
- const audioBuffer = await readFile48(absPath);
64543
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
64544
+ const audioBuffer = await readFile49(absPath);
64415
64545
  const elevenlabs = new ElevenLabsProvider();
64416
64546
  await elevenlabs.initialize({ apiKey: key });
64417
64547
  const result = await elevenlabs.isolateVocals(audioBuffer);
@@ -64436,8 +64566,8 @@ async function executeVoiceClone(options) {
64436
64566
  const audioBuffers = [];
64437
64567
  for (const samplePath of samplePaths) {
64438
64568
  const absPath = resolve73(process.cwd(), samplePath);
64439
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${samplePath}` };
64440
- const buffer = await readFile48(absPath);
64569
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${samplePath}` };
64570
+ const buffer = await readFile49(absPath);
64441
64571
  audioBuffers.push(buffer);
64442
64572
  }
64443
64573
  const elevenlabs = new ElevenLabsProvider();
@@ -64458,7 +64588,7 @@ async function executeDub(options) {
64458
64588
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
64459
64589
  try {
64460
64590
  const absPath = resolve73(process.cwd(), mediaPath);
64461
- if (!existsSync54(absPath)) return { success: false, error: `File not found: ${absPath}` };
64591
+ if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
64462
64592
  const openaiKey = process.env.OPENAI_API_KEY;
64463
64593
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
64464
64594
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -64475,7 +64605,7 @@ async function executeDub(options) {
64475
64605
  }
64476
64606
  const whisper = new WhisperProvider();
64477
64607
  await whisper.initialize({ apiKey: openaiKey });
64478
- const audioBuffer = await readFile48(audioPath);
64608
+ const audioBuffer = await readFile49(audioPath);
64479
64609
  const audioBlob = new Blob([audioBuffer]);
64480
64610
  const transcriptResult = await whisper.transcribe(audioBlob, source);
64481
64611
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -64586,8 +64716,8 @@ async function executeDuck(options) {
64586
64716
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
64587
64717
  const absMusicPath = resolve73(process.cwd(), musicPath);
64588
64718
  const absVoicePath = resolve73(process.cwd(), voicePath);
64589
- if (!existsSync54(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
64590
- 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}` };
64591
64721
  const defaultOutput = resolve73(dirname26(absMusicPath), `${basename22(absMusicPath, extname15(absMusicPath))}-ducked${extname15(absMusicPath)}`);
64592
64722
  const outputPath = resolve73(process.cwd(), output || defaultOutput);
64593
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`;
@@ -64601,7 +64731,7 @@ async function executeDuck(options) {
64601
64731
  "-y",
64602
64732
  outputPath
64603
64733
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
64604
- 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" };
64605
64735
  return { success: true, outputPath };
64606
64736
  } catch (error) {
64607
64737
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -64618,8 +64748,8 @@ var init_ai_audio = __esm({
64618
64748
 
64619
64749
  // src/pipeline/executor.ts
64620
64750
  import { resolve as resolve74 } from "node:path";
64621
- import { readFile as readFile49, writeFile as writeFile52, mkdir as mkdir28 } from "node:fs/promises";
64622
- 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";
64623
64753
  function maxCostFor(action) {
64624
64754
  const cmd = ACTION_METADATA[action]?.command;
64625
64755
  if (!cmd) return 0;
@@ -64934,10 +65064,10 @@ async function ensureActionsRegistered() {
64934
65064
  }
64935
65065
  async function loadPipeline(filePath) {
64936
65066
  const absPath = resolve74(process.cwd(), filePath);
64937
- if (!existsSync55(absPath)) {
65067
+ if (!existsSync56(absPath)) {
64938
65068
  throw new Error(`Pipeline file not found: ${absPath}`);
64939
65069
  }
64940
- const content = await readFile49(absPath, "utf-8");
65070
+ const content = await readFile50(absPath, "utf-8");
64941
65071
  const manifest2 = (0, import_yaml8.parse)(content);
64942
65072
  if (!manifest2.name) throw new Error("Pipeline missing 'name' field");
64943
65073
  if (!manifest2.steps || !Array.isArray(manifest2.steps)) throw new Error("Pipeline missing 'steps' array");
@@ -64959,8 +65089,8 @@ async function executePipeline(manifest2, options = {}) {
64959
65089
  const startTime = Date.now();
64960
65090
  if (options.resume) {
64961
65091
  const checkpointPath = resolve74(outputDir, CHECKPOINT_FILE);
64962
- if (existsSync55(checkpointPath)) {
64963
- const checkpointContent = await readFile49(checkpointPath, "utf-8");
65092
+ if (existsSync56(checkpointPath)) {
65093
+ const checkpointContent = await readFile50(checkpointPath, "utf-8");
64964
65094
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
64965
65095
  for (const cs of checkpoint.completedSteps) {
64966
65096
  completedSteps.set(cs.id, {
@@ -70890,14 +71020,14 @@ var init_edit = __esm({
70890
71020
  });
70891
71021
 
70892
71022
  // src/commands/ai-suggest-edit.ts
70893
- import { readFile as readFile50, writeFile as writeFile53 } from "node:fs/promises";
71023
+ import { readFile as readFile51, writeFile as writeFile53 } from "node:fs/promises";
70894
71024
  import { resolve as resolve76 } from "node:path";
70895
71025
  async function executeSuggestEdit(options) {
70896
71026
  try {
70897
71027
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
70898
71028
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
70899
71029
  const filePath = resolve76(process.cwd(), options.projectPath);
70900
- const content = await readFile50(filePath, "utf-8");
71030
+ const content = await readFile51(filePath, "utf-8");
70901
71031
  const data = JSON.parse(content);
70902
71032
  const project = Project.fromJSON(data);
70903
71033
  const gemini = new GeminiProvider();
@@ -71662,13 +71792,13 @@ var init_generate2 = __esm({
71662
71792
  });
71663
71793
 
71664
71794
  // src/tools/manifest/storyboard.ts
71665
- import { existsSync as existsSync56 } from "node:fs";
71666
- 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";
71667
71797
  import { resolve as resolve78 } from "node:path";
71668
71798
  async function readStoryboard2(projectDir) {
71669
71799
  const path2 = resolve78(projectDir, "STORYBOARD.md");
71670
- if (!existsSync56(path2)) return null;
71671
- 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") };
71672
71802
  }
71673
71803
  var PROJECT_DIR_DESCRIPTION3, projectDirSchema, storyboardListTool, storyboardValidateTool, storyboardGetTool, storyboardSetTool, storyboardMoveTool, storyboardReviseTool, planTool, storyboardTools;
71674
71804
  var init_storyboard2 = __esm({
@@ -71943,7 +72073,7 @@ var init_storyboard2 = __esm({
71943
72073
  // src/tools/manifest/remix.ts
71944
72074
  import { writeFile as writeFile55 } from "node:fs/promises";
71945
72075
  import { tmpdir as tmpdir4 } from "node:os";
71946
- import { join as join27 } from "node:path";
72076
+ import { join as join28 } from "node:path";
71947
72077
  var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, remixTools;
71948
72078
  var init_remix = __esm({
71949
72079
  "src/tools/manifest/remix.ts"() {
@@ -72057,7 +72187,7 @@ var init_remix = __esm({
72057
72187
  let resolvedPath = args.pipelinePath;
72058
72188
  let tempPath;
72059
72189
  if (args.pipelineYaml) {
72060
- tempPath = join27(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
72190
+ tempPath = join28(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
72061
72191
  await writeFile55(tempPath, args.pipelineYaml, "utf-8");
72062
72192
  resolvedPath = tempPath;
72063
72193
  }
@@ -72233,10 +72363,10 @@ var init_detect2 = __esm({
72233
72363
  });
72234
72364
 
72235
72365
  // src/tools/manifest/_project-io.ts
72236
- import { readFile as readFile52, writeFile as writeFile56 } from "node:fs/promises";
72366
+ import { readFile as readFile53, writeFile as writeFile56 } from "node:fs/promises";
72237
72367
  async function loadProject2(projectPath, cwd) {
72238
72368
  const absPath = await resolveTimelineFile(projectPath, cwd);
72239
- const content = await readFile52(absPath, "utf-8");
72369
+ const content = await readFile53(absPath, "utf-8");
72240
72370
  const data = JSON.parse(content);
72241
72371
  return { project: Project.fromJSON(data), absPath };
72242
72372
  }
@@ -72600,12 +72730,12 @@ var init_timeline = __esm({
72600
72730
  });
72601
72731
 
72602
72732
  // src/commands/_shared/project-list.ts
72603
- import { readdir as readdir7, readFile as readFile53, stat as stat9 } from "node:fs/promises";
72604
- import { existsSync as existsSync57 } from "node:fs";
72605
- 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";
72606
72736
  async function executeProjectList(opts = {}) {
72607
72737
  const workspaceDir = resolve80(opts.workspaceDir ?? ".");
72608
- if (!existsSync57(workspaceDir)) {
72738
+ if (!existsSync58(workspaceDir)) {
72609
72739
  return {
72610
72740
  success: false,
72611
72741
  workspaceDir,
@@ -72622,11 +72752,11 @@ async function executeProjectList(opts = {}) {
72622
72752
  if (entry.name === "node_modules") continue;
72623
72753
  if (entry.name.startsWith(".")) continue;
72624
72754
  if (entry.name.startsWith("_")) {
72625
- archivedCount += await countProjects(join28(workspaceDir, entry.name));
72755
+ archivedCount += await countProjects(join29(workspaceDir, entry.name));
72626
72756
  continue;
72627
72757
  }
72628
- const projectDir = join28(workspaceDir, entry.name);
72629
- if (!existsSync57(join28(projectDir, "STORYBOARD.md"))) continue;
72758
+ const projectDir = join29(workspaceDir, entry.name);
72759
+ if (!existsSync58(join29(projectDir, "STORYBOARD.md"))) continue;
72630
72760
  projects.push(await describeProject(entry.name, projectDir));
72631
72761
  }
72632
72762
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -72636,7 +72766,7 @@ async function countProjects(dir) {
72636
72766
  try {
72637
72767
  const entries = await readdir7(dir, { withFileTypes: true });
72638
72768
  return entries.filter(
72639
- (e) => e.isDirectory() && existsSync57(join28(dir, e.name, "STORYBOARD.md"))
72769
+ (e) => e.isDirectory() && existsSync58(join29(dir, e.name, "STORYBOARD.md"))
72640
72770
  ).length;
72641
72771
  } catch {
72642
72772
  return 0;
@@ -72647,8 +72777,8 @@ async function describeProject(name, projectDir) {
72647
72777
  let storyboardDurationSec = 0;
72648
72778
  const timestamps = [];
72649
72779
  try {
72650
- const storyboardPath2 = join28(projectDir, "STORYBOARD.md");
72651
- const parsed = parseStoryboard(await readFile53(storyboardPath2, "utf-8"));
72780
+ const storyboardPath2 = join29(projectDir, "STORYBOARD.md");
72781
+ const parsed = parseStoryboard(await readFile54(storyboardPath2, "utf-8"));
72652
72782
  beats = parsed.beats.length;
72653
72783
  storyboardDurationSec = Number(
72654
72784
  parsed.beats.reduce((sum, beat) => sum + (beat.duration ?? 0), 0).toFixed(2)
@@ -72659,8 +72789,8 @@ async function describeProject(name, projectDir) {
72659
72789
  let buildStatus;
72660
72790
  let buildUpdatedAt;
72661
72791
  try {
72662
- const reportPath = join28(projectDir, "build-report.json");
72663
- 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"));
72664
72794
  buildStatus = report.status ?? report.phase;
72665
72795
  buildUpdatedAt = report.updatedAt;
72666
72796
  timestamps.push((await stat9(reportPath)).mtimeMs);
@@ -72668,12 +72798,12 @@ async function describeProject(name, projectDir) {
72668
72798
  }
72669
72799
  let latestRender;
72670
72800
  try {
72671
- const rendersDir = join28(projectDir, "renders");
72801
+ const rendersDir = join29(projectDir, "renders");
72672
72802
  const files = await readdir7(rendersDir);
72673
72803
  let newest;
72674
72804
  for (const file of files) {
72675
72805
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
72676
- const { mtimeMs } = await stat9(join28(rendersDir, file));
72806
+ const { mtimeMs } = await stat9(join29(rendersDir, file));
72677
72807
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
72678
72808
  }
72679
72809
  if (newest) {
@@ -72815,7 +72945,7 @@ var init_project2 = __esm({
72815
72945
  });
72816
72946
 
72817
72947
  // src/commands/export.ts
72818
- import { readFile as readFile54, access as access7 } from "node:fs/promises";
72948
+ import { readFile as readFile55, access as access7 } from "node:fs/promises";
72819
72949
  import { resolve as resolve82, basename as basename24 } from "node:path";
72820
72950
  import { spawn as spawn3 } from "node:child_process";
72821
72951
  function timelineBaseName(path2) {
@@ -72884,7 +73014,7 @@ async function runExport(projectPath, outputPath, options = {}) {
72884
73014
  };
72885
73015
  }
72886
73016
  const filePath = await resolveTimelineFile(projectPath);
72887
- const content = await readFile54(filePath, "utf-8");
73017
+ const content = await readFile55(filePath, "utf-8");
72888
73018
  const data = JSON.parse(content);
72889
73019
  const project = Project.fromJSON(data);
72890
73020
  const summary = project.getSummary();
@@ -73450,14 +73580,14 @@ function getPresetSettings(preset, aspectRatio2) {
73450
73580
  }
73451
73581
  async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
73452
73582
  spinner2.text = "Loading project...";
73453
- const { readFile: readFile56 } = await import("node:fs/promises");
73583
+ const { readFile: readFile57 } = await import("node:fs/promises");
73454
73584
  const { resolve: resolve86 } = await import("node:path");
73455
73585
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
73456
73586
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
73457
73587
  const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
73458
73588
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
73459
73589
  const filePath = await resolveTimelineFile(projectPath);
73460
- const content = await readFile56(filePath, "utf-8");
73590
+ const content = await readFile57(filePath, "utf-8");
73461
73591
  const project = Project2.fromJSON(JSON.parse(content));
73462
73592
  const state = project.getState();
73463
73593
  const outputPath = options.output ? resolve86(process.cwd(), options.output) : resolve86(process.cwd(), `${timelineBaseName(projectPath)}.${options.format ?? "mp4"}`);
@@ -73579,7 +73709,7 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
73579
73709
  }
73580
73710
  spinner2.text = "Loading project...";
73581
73711
  const filePath = await resolveTimelineFile(projectPath);
73582
- const content = await readFile54(filePath, "utf-8");
73712
+ const content = await readFile55(filePath, "utf-8");
73583
73713
  const data = JSON.parse(content);
73584
73714
  const project = Project.fromJSON(data);
73585
73715
  const summary = project.getSummary();
@@ -73714,8 +73844,8 @@ var init_export2 = __esm({
73714
73844
  });
73715
73845
 
73716
73846
  // src/tools/manifest/agent-only.ts
73717
- import { readFile as readFile55, writeFile as writeFile59, readdir as readdir8, stat as stat10, access as access8, unlink as unlink4 } from "node:fs/promises";
73718
- 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";
73719
73849
  function matchPattern(filename, pattern) {
73720
73850
  const regex2 = new RegExp(
73721
73851
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -73800,7 +73930,7 @@ var init_agent_only = __esm({
73800
73930
  const results = [];
73801
73931
  for (const entry of entries) {
73802
73932
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
73803
- const fullPath = join29(absPath, entry.name);
73933
+ const fullPath = join30(absPath, entry.name);
73804
73934
  const stats = await stat10(fullPath);
73805
73935
  if (entry.isDirectory()) {
73806
73936
  results.push(`[DIR] ${entry.name}/`);
@@ -73829,7 +73959,7 @@ var init_agent_only = __esm({
73829
73959
  async execute(args, ctx) {
73830
73960
  try {
73831
73961
  const absPath = resolve84(ctx.workingDirectory, args.path);
73832
- const content = await readFile55(absPath, "utf-8");
73962
+ const content = await readFile56(absPath, "utf-8");
73833
73963
  const maxLength = 4e3;
73834
73964
  const truncated = content.length > maxLength;
73835
73965
  const output = truncated ? content.substring(0, maxLength) + "\n... (truncated)" : content;
@@ -73902,7 +74032,7 @@ var init_agent_only = __esm({
73902
74032
  async execute(args, ctx) {
73903
74033
  try {
73904
74034
  const filePath = resolve84(ctx.workingDirectory, args.project);
73905
- const content = await readFile55(filePath, "utf-8");
74035
+ const content = await readFile56(filePath, "utf-8");
73906
74036
  const data = JSON.parse(content);
73907
74037
  const project = Project.fromJSON(data);
73908
74038
  const dirPath = resolve84(ctx.workingDirectory, args.directory);
@@ -73913,7 +74043,7 @@ var init_agent_only = __esm({
73913
74043
  const scanDir = async (dir) => {
73914
74044
  const entries = await readdir8(dir, { withFileTypes: true });
73915
74045
  for (const entry of entries) {
73916
- const entryPath = join29(dir, entry.name);
74046
+ const entryPath = join30(dir, entry.name);
73917
74047
  if (entry.isDirectory() && recursive) {
73918
74048
  await scanDir(entryPath);
73919
74049
  } else if (entry.isFile()) {
@@ -73974,7 +74104,7 @@ var init_agent_only = __esm({
73974
74104
  async execute(args, ctx) {
73975
74105
  try {
73976
74106
  const filePath = resolve84(ctx.workingDirectory, args.project);
73977
- const content = await readFile55(filePath, "utf-8");
74107
+ const content = await readFile56(filePath, "utf-8");
73978
74108
  const data = JSON.parse(content);
73979
74109
  const project = Project.fromJSON(data);
73980
74110
  const sourceIds = args.sourceIds ?? [];
@@ -74045,7 +74175,7 @@ var init_agent_only = __esm({
74045
74175
  async execute(args, ctx) {
74046
74176
  try {
74047
74177
  const filePath = resolve84(ctx.workingDirectory, args.project);
74048
- const content = await readFile55(filePath, "utf-8");
74178
+ const content = await readFile56(filePath, "utf-8");
74049
74179
  const data = JSON.parse(content);
74050
74180
  const project = Project.fromJSON(data);
74051
74181
  const clipIds = args.clipIds ?? [];
@@ -74100,7 +74230,7 @@ var init_agent_only = __esm({
74100
74230
  async execute(args, ctx) {
74101
74231
  try {
74102
74232
  const filePath = await resolveTimelineFile(args.project, ctx.workingDirectory);
74103
- const content = await readFile55(filePath, "utf-8");
74233
+ const content = await readFile56(filePath, "utf-8");
74104
74234
  const data = JSON.parse(content);
74105
74235
  const project = Project.fromJSON(data);
74106
74236
  const what = args.what ?? "clips";
@@ -74177,7 +74307,7 @@ var init_agent_only = __esm({
74177
74307
  async execute(args, ctx) {
74178
74308
  try {
74179
74309
  const filePath = await resolveTimelineFile(args.projectPath, ctx.workingDirectory);
74180
- const content = await readFile55(filePath, "utf-8");
74310
+ const content = await readFile56(filePath, "utf-8");
74181
74311
  const data = JSON.parse(content);
74182
74312
  const project = Project.fromJSON(data);
74183
74313
  const updates = [];
@@ -74451,8 +74581,8 @@ Audio:`);
74451
74581
  ],
74452
74582
  { maxBuffer: 50 * 1024 * 1024 }
74453
74583
  );
74454
- const inputBuf = await readFile55(absInput);
74455
- const outputBuf = await readFile55(outputPath);
74584
+ const inputBuf = await readFile56(absInput);
74585
+ const outputBuf = await readFile56(outputPath);
74456
74586
  const inputSize = inputBuf.length;
74457
74587
  const outputSize = outputBuf.length;
74458
74588
  const reduction = (inputSize - outputSize) / inputSize * 100;
@@ -75003,7 +75133,7 @@ init_source();
75003
75133
  // package.json
75004
75134
  var package_default = {
75005
75135
  name: "@vibeframe/cli",
75006
- version: "0.113.10",
75136
+ version: "0.113.11",
75007
75137
  description: "VibeFrame CLI - agentic video workflow runtime",
75008
75138
  private: false,
75009
75139
  license: "MIT",
@@ -75347,8 +75477,8 @@ init_validate();
75347
75477
  init_output();
75348
75478
  init_cost_tier();
75349
75479
  init_project_resolver();
75350
- import { readFile as readFile14, writeFile as writeFile13 } from "node:fs/promises";
75351
- 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";
75352
75482
  import { resolve as resolve18, basename as basename3, extname } from "node:path";
75353
75483
  var timelineCommand = new Command("timeline").description("Low-level timeline JSON commands").addHelpText(
75354
75484
  "after",
@@ -75397,7 +75527,7 @@ timelineCommand.command("add-source").description("Add a media source to the tim
75397
75527
  return;
75398
75528
  }
75399
75529
  const filePath = await resolveTimelineFile(projectPath);
75400
- const content = await readFile14(filePath, "utf-8");
75530
+ const content = await readFile15(filePath, "utf-8");
75401
75531
  const data = JSON.parse(content);
75402
75532
  const project = Project.fromJSON(data);
75403
75533
  const absMediaPath = resolve18(process.cwd(), mediaPath);
@@ -75462,7 +75592,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
75462
75592
  return;
75463
75593
  }
75464
75594
  const filePath = await resolveTimelineFile(projectPath);
75465
- const content = await readFile14(filePath, "utf-8");
75595
+ const content = await readFile15(filePath, "utf-8");
75466
75596
  const data = JSON.parse(content);
75467
75597
  const project = Project.fromJSON(data);
75468
75598
  const source = project.getSource(sourceId);
@@ -75539,7 +75669,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
75539
75669
  return;
75540
75670
  }
75541
75671
  const filePath = await resolveTimelineFile(projectPath);
75542
- const content = await readFile14(filePath, "utf-8");
75672
+ const content = await readFile15(filePath, "utf-8");
75543
75673
  const data = JSON.parse(content);
75544
75674
  const project = Project.fromJSON(data);
75545
75675
  const existingTracks = project.getTracksByType(type);
@@ -75603,7 +75733,7 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
75603
75733
  return;
75604
75734
  }
75605
75735
  const filePath = await resolveTimelineFile(projectPath);
75606
- const content = await readFile14(filePath, "utf-8");
75736
+ const content = await readFile15(filePath, "utf-8");
75607
75737
  const data = JSON.parse(content);
75608
75738
  const project = Project.fromJSON(data);
75609
75739
  const clip = project.getClip(clipId);
@@ -75673,7 +75803,7 @@ timelineCommand.command("trim-clip").description("Trim a clip").argument("<proje
75673
75803
  return;
75674
75804
  }
75675
75805
  const filePath = await resolveTimelineFile(projectPath);
75676
- const content = await readFile14(filePath, "utf-8");
75806
+ const content = await readFile15(filePath, "utf-8");
75677
75807
  const data = JSON.parse(content);
75678
75808
  const project = Project.fromJSON(data);
75679
75809
  const clip = project.getClip(clipId);
@@ -75715,10 +75845,10 @@ timelineCommand.command("list").description("List timeline contents").argument("
75715
75845
  const startedAt = Date.now();
75716
75846
  try {
75717
75847
  const filePath = await resolveTimelineFile(projectPath);
75718
- if (!existsSync12(filePath)) {
75848
+ if (!existsSync13(filePath)) {
75719
75849
  exitWithError(notFoundError(projectPath));
75720
75850
  }
75721
- const content = await readFile14(filePath, "utf-8");
75851
+ const content = await readFile15(filePath, "utf-8");
75722
75852
  const data = JSON.parse(content);
75723
75853
  const project = Project.fromJSON(data);
75724
75854
  const showAll = !options.sources && !options.tracks && !options.clips;
@@ -75839,7 +75969,7 @@ timelineCommand.command("split-clip").description("Split a clip at a specific ti
75839
75969
  return;
75840
75970
  }
75841
75971
  const filePath = await resolveTimelineFile(projectPath);
75842
- const content = await readFile14(filePath, "utf-8");
75972
+ const content = await readFile15(filePath, "utf-8");
75843
75973
  const data = JSON.parse(content);
75844
75974
  const project = Project.fromJSON(data);
75845
75975
  const clip = project.getClip(clipId);
@@ -75901,7 +76031,7 @@ timelineCommand.command("duplicate-clip").description("Duplicate a clip").argume
75901
76031
  return;
75902
76032
  }
75903
76033
  const filePath = await resolveTimelineFile(projectPath);
75904
- const content = await readFile14(filePath, "utf-8");
76034
+ const content = await readFile15(filePath, "utf-8");
75905
76035
  const data = JSON.parse(content);
75906
76036
  const project = Project.fromJSON(data);
75907
76037
  const clip = project.getClip(clipId);
@@ -75959,7 +76089,7 @@ timelineCommand.command("delete-clip").description("Delete a clip from the timel
75959
76089
  return;
75960
76090
  }
75961
76091
  const filePath = await resolveTimelineFile(projectPath);
75962
- const content = await readFile14(filePath, "utf-8");
76092
+ const content = await readFile15(filePath, "utf-8");
75963
76093
  const data = JSON.parse(content);
75964
76094
  const project = Project.fromJSON(data);
75965
76095
  const clip = project.getClip(clipId);
@@ -76014,7 +76144,7 @@ timelineCommand.command("move-clip").description("Move a clip to a new position"
76014
76144
  return;
76015
76145
  }
76016
76146
  const filePath = await resolveTimelineFile(projectPath);
76017
- const content = await readFile14(filePath, "utf-8");
76147
+ const content = await readFile15(filePath, "utf-8");
76018
76148
  const data = JSON.parse(content);
76019
76149
  const project = Project.fromJSON(data);
76020
76150
  const clip = project.getClip(clipId);
@@ -76100,7 +76230,7 @@ init_validate();
76100
76230
  init_cost_tier();
76101
76231
  init_scene_inspect();
76102
76232
  init_render_inspect();
76103
- import { readFile as readFile31, writeFile as writeFile37 } from "node:fs/promises";
76233
+ import { readFile as readFile32, writeFile as writeFile37 } from "node:fs/promises";
76104
76234
  import { resolve as resolve44 } from "node:path";
76105
76235
  var VALID_RENDER_AI_MODELS = [
76106
76236
  "flash",
@@ -76453,7 +76583,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
76453
76583
  const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
76454
76584
  const spinner2 = ora("Initializing Gemini...").start();
76455
76585
  const filePath = resolve44(process.cwd(), projectPath);
76456
- const content = await readFile31(filePath, "utf-8");
76586
+ const content = await readFile32(filePath, "utf-8");
76457
76587
  const data = JSON.parse(content);
76458
76588
  const project = Project.fromJSON(data);
76459
76589
  const gemini = new GeminiProvider();
@@ -76522,8 +76652,8 @@ init_ai_helpers();
76522
76652
  init_output();
76523
76653
  init_validate();
76524
76654
  import { resolve as resolve45, dirname as dirname16, basename as basename12, extname as extname8 } from "node:path";
76525
- import { readFile as readFile32, writeFile as writeFile38 } from "node:fs/promises";
76526
- 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";
76527
76657
  var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, dub, duck, isolate, voice clone)").addHelpText(
76528
76658
  "after",
76529
76659
  `
@@ -76560,7 +76690,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
76560
76690
  await whisper.initialize({ apiKey });
76561
76691
  spinner2.text = "Reading audio file...";
76562
76692
  const absPath = resolve45(process.cwd(), audioPath);
76563
- const audioBuffer = await readFile32(absPath);
76693
+ const audioBuffer = await readFile33(absPath);
76564
76694
  const audioBlob = new Blob([audioBuffer]);
76565
76695
  spinner2.text = "Transcribing...";
76566
76696
  const result = await whisper.transcribe(audioBlob, options.language);
@@ -76664,7 +76794,7 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
76664
76794
  const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
76665
76795
  const spinner2 = ora("Reading audio file...").start();
76666
76796
  const absPath = resolve45(process.cwd(), audioPath);
76667
- const audioBuffer = await readFile32(absPath);
76797
+ const audioBuffer = await readFile33(absPath);
76668
76798
  spinner2.text = "Isolating vocals...";
76669
76799
  const elevenlabs = new ElevenLabsProvider();
76670
76800
  await elevenlabs.initialize({ apiKey });
@@ -76735,11 +76865,11 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
76735
76865
  const audioBuffers = [];
76736
76866
  for (const samplePath of samples) {
76737
76867
  const absPath = resolve45(process.cwd(), samplePath);
76738
- if (!existsSync36(absPath)) {
76868
+ if (!existsSync37(absPath)) {
76739
76869
  spinner2.fail(`File not found: ${samplePath}`);
76740
76870
  exitWithError(notFoundError(samplePath));
76741
76871
  }
76742
- const buffer = await readFile32(absPath);
76872
+ const buffer = await readFile33(absPath);
76743
76873
  audioBuffers.push(buffer);
76744
76874
  }
76745
76875
  spinner2.text = `Cloning voice from ${audioBuffers.length} sample(s)...`;
@@ -76803,7 +76933,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
76803
76933
  exitWithError(usageError("Target language is required. Use -l or --language"));
76804
76934
  }
76805
76935
  const absPath = resolve45(process.cwd(), mediaPath);
76806
- if (!existsSync36(absPath)) {
76936
+ if (!existsSync37(absPath)) {
76807
76937
  exitWithError(notFoundError(mediaPath));
76808
76938
  }
76809
76939
  const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
@@ -76828,7 +76958,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
76828
76958
  spinner2.text = "Transcribing audio...";
76829
76959
  const whisper = new WhisperProvider();
76830
76960
  await whisper.initialize({ apiKey: openaiKey });
76831
- const audioBuffer = await readFile32(audioPath);
76961
+ const audioBuffer = await readFile33(audioPath);
76832
76962
  const audioBlob = new Blob([audioBuffer]);
76833
76963
  const transcriptResult = await whisper.transcribe(audioBlob, options.source);
76834
76964
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -77084,7 +77214,7 @@ init_esm();
77084
77214
  init_source();
77085
77215
  init_ora();
77086
77216
  import { resolve as resolve50, basename as basename15 } from "node:path";
77087
- import { existsSync as existsSync41 } from "node:fs";
77217
+ import { existsSync as existsSync42 } from "node:fs";
77088
77218
 
77089
77219
  // src/commands/ai-script-pipeline-cli.ts
77090
77220
  init_source();
@@ -77093,9 +77223,9 @@ var import_yaml7 = __toESM(require_dist2(), 1);
77093
77223
  init_api_key();
77094
77224
  init_ai_script_pipeline();
77095
77225
  init_output();
77096
- import { readFile as readFile34, writeFile as writeFile40 } from "node:fs/promises";
77226
+ import { readFile as readFile35, writeFile as writeFile40 } from "node:fs/promises";
77097
77227
  import { resolve as resolve47 } from "node:path";
77098
- import { existsSync as existsSync38 } from "node:fs";
77228
+ import { existsSync as existsSync39 } from "node:fs";
77099
77229
  function registerScriptPipelineCommands(aiCommand) {
77100
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(
77101
77231
  "--scene <numbers>",
@@ -77112,12 +77242,12 @@ function registerScriptPipelineCommands(aiCommand) {
77112
77242
  try {
77113
77243
  const outputDir = resolve47(process.cwd(), projectDir);
77114
77244
  const projectPath = resolve47(outputDir, "timeline.json");
77115
- if (!existsSync38(outputDir)) {
77245
+ if (!existsSync39(outputDir)) {
77116
77246
  exitWithError(notFoundError(outputDir));
77117
77247
  }
77118
77248
  const yamlPath = resolve47(outputDir, "storyboard.yaml");
77119
77249
  const jsonPath = resolve47(outputDir, "storyboard.json");
77120
- const storyboardPath2 = existsSync38(yamlPath) ? yamlPath : existsSync38(jsonPath) ? jsonPath : null;
77250
+ const storyboardPath2 = existsSync39(yamlPath) ? yamlPath : existsSync39(jsonPath) ? jsonPath : null;
77121
77251
  const storyboardIsYaml = storyboardPath2 === yamlPath;
77122
77252
  if (!storyboardPath2) {
77123
77253
  exitWithError(notFoundError(`${outputDir}/storyboard.{yaml,json}`));
@@ -77153,7 +77283,7 @@ function registerScriptPipelineCommands(aiCommand) {
77153
77283
  });
77154
77284
  return;
77155
77285
  }
77156
- const content = await readFile34(storyboardPath2, "utf-8");
77286
+ const content = await readFile35(storyboardPath2, "utf-8");
77157
77287
  const segments = storyboardIsYaml ? (0, import_yaml7.parse)(content).scenes : JSON.parse(content);
77158
77288
  for (const sceneNum of sceneNums) {
77159
77289
  if (sceneNum > segments.length) {
@@ -77236,12 +77366,12 @@ function registerScriptPipelineCommands(aiCommand) {
77236
77366
  exitWithError(apiError(result.error || "Scene regeneration failed", true));
77237
77367
  }
77238
77368
  spinner2.succeed(source_default.green(`Regenerated ${result.regeneratedScenes.length} scene(s)`));
77239
- if (existsSync38(projectPath) && result.regeneratedScenes.length > 0) {
77369
+ if (existsSync39(projectPath) && result.regeneratedScenes.length > 0) {
77240
77370
  const updateSpinner = ora("Updating timeline file...").start();
77241
77371
  try {
77242
- const updatedContent = await readFile34(storyboardPath2, "utf-8");
77372
+ const updatedContent = await readFile35(storyboardPath2, "utf-8");
77243
77373
  const updatedSegments = storyboardIsYaml ? (0, import_yaml7.parse)(updatedContent).scenes : JSON.parse(updatedContent);
77244
- const projectContent = await readFile34(projectPath, "utf-8");
77374
+ const projectContent = await readFile35(projectPath, "utf-8");
77245
77375
  const projectData = JSON.parse(projectContent);
77246
77376
  for (const sceneNum of result.regeneratedScenes) {
77247
77377
  const segment = updatedSegments[sceneNum - 1];
@@ -77252,7 +77382,7 @@ function registerScriptPipelineCommands(aiCommand) {
77252
77382
  (s) => s.name === `Scene ${sceneNum}`
77253
77383
  );
77254
77384
  if (videoSource) {
77255
- const hasVideo = existsSync38(videoPath);
77385
+ const hasVideo = existsSync39(videoPath);
77256
77386
  videoSource.url = hasVideo ? videoPath : imagePath;
77257
77387
  videoSource.type = hasVideo ? "video" : "image";
77258
77388
  videoSource.duration = segment.duration;
@@ -77377,7 +77507,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
77377
77507
  const startedAt = Date.now();
77378
77508
  try {
77379
77509
  const absVideoPath = resolve50(process.cwd(), videoPath);
77380
- if (!existsSync41(absVideoPath)) {
77510
+ if (!existsSync42(absVideoPath)) {
77381
77511
  exitWithError(notFoundError(absVideoPath));
77382
77512
  }
77383
77513
  if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
@@ -81760,8 +81890,8 @@ init_ora();
81760
81890
  init_engine();
81761
81891
  init_output();
81762
81892
  init_cost_tier();
81763
- import { readFile as readFile36, writeFile as writeFile43, readdir as readdir6 } from "node:fs/promises";
81764
- 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";
81765
81895
  var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
81766
81896
  function detectMediaType3(filePath) {
81767
81897
  const ext = extname12(filePath).toLowerCase();
@@ -81819,7 +81949,7 @@ batchCommand.command("import").description("Import multiple media files from a d
81819
81949
  return;
81820
81950
  }
81821
81951
  const filePath = resolve52(process.cwd(), projectPath);
81822
- const content = await readFile36(filePath, "utf-8");
81952
+ const content = await readFile37(filePath, "utf-8");
81823
81953
  const data = JSON.parse(content);
81824
81954
  const project = Project.fromJSON(data);
81825
81955
  const dirPath = resolve52(process.cwd(), directory);
@@ -81828,7 +81958,7 @@ batchCommand.command("import").description("Import multiple media files from a d
81828
81958
  const scanDir = async (dir) => {
81829
81959
  const entries = await readdir6(dir, { withFileTypes: true });
81830
81960
  for (const entry of entries) {
81831
- const entryPath = join20(dir, entry.name);
81961
+ const entryPath = join21(dir, entry.name);
81832
81962
  if (entry.isDirectory() && options.recursive) {
81833
81963
  await scanDir(entryPath);
81834
81964
  } else if (entry.isFile()) {
@@ -81897,7 +82027,7 @@ batchCommand.command("concat").description("Concatenate multiple sources into se
81897
82027
  return;
81898
82028
  }
81899
82029
  const filePath = resolve52(process.cwd(), projectPath);
81900
- const content = await readFile36(filePath, "utf-8");
82030
+ const content = await readFile37(filePath, "utf-8");
81901
82031
  const data = JSON.parse(content);
81902
82032
  const project = Project.fromJSON(data);
81903
82033
  const sources = project.getSources();
@@ -81976,7 +82106,7 @@ batchCommand.command("apply-effect").description("Apply an effect to multiple cl
81976
82106
  return;
81977
82107
  }
81978
82108
  const filePath = resolve52(process.cwd(), projectPath);
81979
- const content = await readFile36(filePath, "utf-8");
82109
+ const content = await readFile37(filePath, "utf-8");
81980
82110
  const data = JSON.parse(content);
81981
82111
  const project = Project.fromJSON(data);
81982
82112
  const clips = project.getClips();
@@ -82044,7 +82174,7 @@ batchCommand.command("remove-clips").description("Remove multiple clips from the
82044
82174
  return;
82045
82175
  }
82046
82176
  const filePath = resolve52(process.cwd(), projectPath);
82047
- const content = await readFile36(filePath, "utf-8");
82177
+ const content = await readFile37(filePath, "utf-8");
82048
82178
  const data = JSON.parse(content);
82049
82179
  const project = Project.fromJSON(data);
82050
82180
  const clips = project.getClips();
@@ -82082,7 +82212,7 @@ batchCommand.command("info").description("Show batch processing statistics").arg
82082
82212
  const spinner2 = ora("Loading project...").start();
82083
82213
  try {
82084
82214
  const filePath = resolve52(process.cwd(), projectPath);
82085
- const content = await readFile36(filePath, "utf-8");
82215
+ const content = await readFile37(filePath, "utf-8");
82086
82216
  const data = JSON.parse(content);
82087
82217
  const project = Project.fromJSON(data);
82088
82218
  const sources = project.getSources();
@@ -82142,7 +82272,7 @@ init_output();
82142
82272
  init_tty();
82143
82273
  init_api_key();
82144
82274
  import { resolve as resolve54 } from "node:path";
82145
- import { access as access6, readFile as readFile38 } from "node:fs/promises";
82275
+ import { access as access6, readFile as readFile39 } from "node:fs/promises";
82146
82276
  import { homedir as homedir6 } from "node:os";
82147
82277
 
82148
82278
  // src/utils/key-format.ts
@@ -83343,7 +83473,7 @@ async function showConfig(opts = { verbose: false }) {
83343
83473
  let dotenvKeys = {};
83344
83474
  if (hasCwdEnv) {
83345
83475
  try {
83346
- const envContent = await readFile38(cwdEnvPath, "utf-8");
83476
+ const envContent = await readFile39(cwdEnvPath, "utf-8");
83347
83477
  dotenvKeys = (0, import_dotenv2.parse)(Buffer.from(envContent));
83348
83478
  } catch {
83349
83479
  }
@@ -83440,8 +83570,8 @@ init_source();
83440
83570
  init_agent_host_detect();
83441
83571
  init_scene_project();
83442
83572
  init_visual_styles();
83443
- import { existsSync as existsSync43 } from "node:fs";
83444
- 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";
83445
83575
  import { basename as basename19, dirname as dirname20, resolve as resolve56 } from "node:path";
83446
83576
 
83447
83577
  // src/commands/_shared/storyboard-draft.ts
@@ -83604,10 +83734,10 @@ init_project_config();
83604
83734
  init_install_skill();
83605
83735
 
83606
83736
  // src/commands/_shared/host-integration.ts
83607
- import { existsSync as existsSync42 } from "node:fs";
83608
- 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";
83609
83739
  import { homedir as homedir7 } from "node:os";
83610
- 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";
83611
83741
  var HOST_SELECTIONS = [
83612
83742
  "codex",
83613
83743
  "claude",
@@ -83634,7 +83764,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
83634
83764
  label: "OpenAI Codex",
83635
83765
  surfaces: ["shell", "project-guidance", "mcp"],
83636
83766
  guidanceFiles: ["AGENTS.md"],
83637
- configPath: (dir) => join21(resolve55(dir), ".codex", "config.toml"),
83767
+ configPath: (dir) => join22(resolve55(dir), ".codex", "config.toml"),
83638
83768
  configKind: "codex-toml",
83639
83769
  notes: [
83640
83770
  "Codex reads AGENTS.md and can also load project-scoped .codex/config.toml after the project is trusted.",
@@ -83647,7 +83777,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
83647
83777
  label: "Claude Code",
83648
83778
  surfaces: ["shell", "project-guidance", "mcp"],
83649
83779
  guidanceFiles: ["AGENTS.md", "CLAUDE.md", ".claude/skills/hyperframes/"],
83650
- configPath: (dir) => join21(resolve55(dir), ".mcp.json"),
83780
+ configPath: (dir) => join22(resolve55(dir), ".mcp.json"),
83651
83781
  configKind: "mcp-json",
83652
83782
  notes: [
83653
83783
  "Claude Code can drive vibe directly through shell plus AGENTS.md/CLAUDE.md.",
@@ -83672,7 +83802,7 @@ function hostDefinitions(_projectDir = process.cwd()) {
83672
83802
  label: "Cursor",
83673
83803
  surfaces: ["shell", "project-guidance", "mcp"],
83674
83804
  guidanceFiles: ["AGENTS.md", ".cursor/rules/hyperframes.mdc"],
83675
- configPath: (dir) => join21(resolve55(dir), ".cursor", "mcp.json"),
83805
+ configPath: (dir) => join22(resolve55(dir), ".cursor", "mcp.json"),
83676
83806
  configKind: "mcp-json",
83677
83807
  notes: [
83678
83808
  "Cursor can use AGENTS.md/rules for shell-driven work and .cursor/mcp.json for typed MCP tools."
@@ -83713,11 +83843,11 @@ async function planHostSetup(host, projectDir, opts) {
83713
83843
  const warnings = [];
83714
83844
  const nextSteps = hostNextSteps(host);
83715
83845
  for (const relPath of host.guidanceFiles.filter((p) => !p.endsWith("/"))) {
83716
- const absPath = join21(resolve55(projectDir), relPath);
83846
+ const absPath = join22(resolve55(projectDir), relPath);
83717
83847
  files.push({
83718
83848
  path: absPath,
83719
- status: existsSync42(absPath) ? "skipped-exists" : "missing",
83720
- 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"
83721
83851
  });
83722
83852
  }
83723
83853
  if (!opts.write || opts.dryRun) {
@@ -83761,17 +83891,17 @@ async function inspectHost(host, projectDir) {
83761
83891
  const warnings = [];
83762
83892
  const nextSteps = hostNextSteps(host);
83763
83893
  const guidanceFiles = host.guidanceFiles.map((relPath) => {
83764
- 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);
83765
83895
  return {
83766
83896
  path: absPath,
83767
- status: existsSync42(absPath) ? "skipped-exists" : "missing",
83768
- reason: existsSync42(absPath) ? "present" : "missing"
83897
+ status: existsSync43(absPath) ? "skipped-exists" : "missing",
83898
+ reason: existsSync43(absPath) ? "present" : "missing"
83769
83899
  };
83770
83900
  });
83771
83901
  let configured = false;
83772
- if (existsSync42(configPath)) {
83902
+ if (existsSync43(configPath)) {
83773
83903
  try {
83774
- const content = await readFile39(configPath, "utf-8");
83904
+ const content = await readFile40(configPath, "utf-8");
83775
83905
  if (host.configKind === "codex-toml") {
83776
83906
  configured = /\[mcp_servers\.vibeframe\]/.test(content);
83777
83907
  } else {
@@ -83792,9 +83922,9 @@ async function inspectHost(host, projectDir) {
83792
83922
  }
83793
83923
  async function mergeMcpJson(configPath, opts) {
83794
83924
  let root = {};
83795
- const exists = existsSync42(configPath);
83925
+ const exists = existsSync43(configPath);
83796
83926
  if (exists) {
83797
- const raw = await readFile39(configPath, "utf-8");
83927
+ const raw = await readFile40(configPath, "utf-8");
83798
83928
  try {
83799
83929
  root = raw.trim() ? JSON.parse(raw) : {};
83800
83930
  } catch (error) {
@@ -83815,7 +83945,7 @@ async function mergeMcpJson(configPath, opts) {
83815
83945
  if (exists && opts.backup) {
83816
83946
  await writeFile45(
83817
83947
  `${configPath}.bak-${timestamp()}`,
83818
- await readFile39(configPath, "utf-8"),
83948
+ await readFile40(configPath, "utf-8"),
83819
83949
  "utf-8"
83820
83950
  );
83821
83951
  }
@@ -83837,7 +83967,7 @@ async function mergeMcpJson(configPath, opts) {
83837
83967
  if (exists && opts.backup) {
83838
83968
  await writeFile45(
83839
83969
  `${configPath}.bak-${timestamp()}`,
83840
- await readFile39(configPath, "utf-8"),
83970
+ await readFile40(configPath, "utf-8"),
83841
83971
  "utf-8"
83842
83972
  );
83843
83973
  }
@@ -83845,7 +83975,7 @@ async function mergeMcpJson(configPath, opts) {
83845
83975
  return { path: configPath, status: exists ? "merged" : "wrote" };
83846
83976
  }
83847
83977
  async function mergeCodexToml(configPath, opts) {
83848
- const exists = existsSync42(configPath);
83978
+ const exists = existsSync43(configPath);
83849
83979
  const block = `${CODEX_MANAGED_START}
83850
83980
  ${CODEX_MCP_BLOCK}${CODEX_MANAGED_END}`;
83851
83981
  if (!exists) {
@@ -83860,7 +83990,7 @@ ${block}
83860
83990
  );
83861
83991
  return { path: configPath, status: "wrote" };
83862
83992
  }
83863
- const raw = await readFile39(configPath, "utf-8");
83993
+ const raw = await readFile40(configPath, "utf-8");
83864
83994
  if (raw.includes(CODEX_MANAGED_START)) {
83865
83995
  const updated = raw.replace(
83866
83996
  new RegExp(
@@ -83949,16 +84079,16 @@ function hostNextSteps(host) {
83949
84079
  function claudeDesktopConfigPath() {
83950
84080
  const home = homedir7();
83951
84081
  if (process.platform === "darwin") {
83952
- return join21(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
84082
+ return join22(home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
83953
84083
  }
83954
84084
  if (process.platform === "win32") {
83955
- return join21(
83956
- process.env.APPDATA ?? join21(home, "AppData", "Roaming"),
84085
+ return join22(
84086
+ process.env.APPDATA ?? join22(home, "AppData", "Roaming"),
83957
84087
  "Claude",
83958
84088
  "claude_desktop_config.json"
83959
84089
  );
83960
84090
  }
83961
- return join21(home, ".config", "Claude", "claude_desktop_config.json");
84091
+ return join22(home, ".config", "Claude", "claude_desktop_config.json");
83962
84092
  }
83963
84093
  function timestamp() {
83964
84094
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -84411,7 +84541,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
84411
84541
  const wantsGemini = targetHosts.includes("gemini-cli");
84412
84542
  const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
84413
84543
  const actions = [];
84414
- if (!existsSync43(projectDir)) {
84544
+ if (!existsSync44(projectDir)) {
84415
84545
  if (options.dryRun) {
84416
84546
  actions.push({
84417
84547
  path: projectDir,
@@ -84626,11 +84756,11 @@ async function runSceneInit(projectDirArg, options, startedAt) {
84626
84756
  });
84627
84757
  const storyboardPath2 = resolve56(projectDir, "STORYBOARD.md");
84628
84758
  const designPath = resolve56(projectDir, "DESIGN.md");
84629
- if (options.force || result.created.includes(storyboardPath2) || !existsSync43(storyboardPath2)) {
84759
+ if (options.force || result.created.includes(storyboardPath2) || !existsSync44(storyboardPath2)) {
84630
84760
  await writeFile46(storyboardPath2, draft.storyboardMd, "utf-8");
84631
84761
  if (!result.created.includes(storyboardPath2)) result.created.push(storyboardPath2);
84632
84762
  }
84633
- if (options.force || result.created.includes(designPath) || !existsSync43(designPath)) {
84763
+ if (options.force || result.created.includes(designPath) || !existsSync44(designPath)) {
84634
84764
  await writeFile46(designPath, draft.designMd, "utf-8");
84635
84765
  if (!result.created.includes(designPath)) result.created.push(designPath);
84636
84766
  }
@@ -84754,8 +84884,8 @@ function printGroup(label, files, projectDir) {
84754
84884
  }
84755
84885
  async function readBriefOrLiteral(value, cwd) {
84756
84886
  const candidate = resolve56(cwd, value);
84757
- if (existsSync43(candidate)) {
84758
- return readFile40(candidate, "utf-8");
84887
+ if (existsSync44(candidate)) {
84888
+ return readFile41(candidate, "utf-8");
84759
84889
  }
84760
84890
  return value;
84761
84891
  }
@@ -84808,7 +84938,7 @@ function normalizeHostAction(status) {
84808
84938
  return "would-write";
84809
84939
  }
84810
84940
  async function writeIfMissing(absPath, content, force, dryRun) {
84811
- const exists = existsSync43(absPath);
84941
+ const exists = existsSync44(absPath);
84812
84942
  if (exists && !force) {
84813
84943
  return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
84814
84944
  }
@@ -84821,12 +84951,12 @@ async function writeIfMissing(absPath, content, force, dryRun) {
84821
84951
  }
84822
84952
  async function mergeGitignore(absPath, additions, dryRun) {
84823
84953
  const marker = "# VibeFrame";
84824
- if (!existsSync43(absPath)) {
84954
+ if (!existsSync44(absPath)) {
84825
84955
  if (dryRun) return { path: absPath, status: "would-write" };
84826
84956
  await writeFile46(absPath, additions.trimStart(), "utf-8");
84827
84957
  return { path: absPath, status: "wrote" };
84828
84958
  }
84829
- const existing = await readFile40(absPath, "utf-8");
84959
+ const existing = await readFile41(absPath, "utf-8");
84830
84960
  if (existing.includes(marker)) {
84831
84961
  return { path: absPath, status: "skipped-exists", reason: "(already has VibeFrame block)" };
84832
84962
  }
@@ -84859,8 +84989,8 @@ init_storyboard_revise();
84859
84989
  init_composer_resolve();
84860
84990
  init_cost_tier();
84861
84991
  init_output();
84862
- import { readFile as readFile42, writeFile as writeFile48 } from "node:fs/promises";
84863
- 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";
84864
84994
  import { resolve as resolve58 } from "node:path";
84865
84995
  var storyboardCommand = new Command("storyboard").description("Read, validate, and safely mutate STORYBOARD.md cue blocks").addHelpText("after", `
84866
84996
  Examples:
@@ -85043,10 +85173,10 @@ storyboardCommand.command("validate").description("Validate cue blocks and beat
85043
85173
  });
85044
85174
  async function readStoryboard(projectDir) {
85045
85175
  const path2 = storyboardPath(projectDir);
85046
- if (!existsSync45(path2)) {
85176
+ if (!existsSync46(path2)) {
85047
85177
  exitWithError(generalError(`STORYBOARD.md not found at ${path2}`, `Run 'vibe init ${projectDir} --from "<brief>"' first.`));
85048
85178
  }
85049
- return readFile42(path2, "utf-8");
85179
+ return readFile43(path2, "utf-8");
85050
85180
  }
85051
85181
  function storyboardPath(projectDir) {
85052
85182
  return resolve58(projectDir, "STORYBOARD.md");
@@ -85058,8 +85188,8 @@ function parseComposer(value) {
85058
85188
  }
85059
85189
  async function readBrief(value, cwd) {
85060
85190
  const candidate = resolve58(cwd, value);
85061
- if (existsSync45(candidate)) {
85062
- return readFile42(candidate, "utf-8");
85191
+ if (existsSync46(candidate)) {
85192
+ return readFile43(candidate, "utf-8");
85063
85193
  }
85064
85194
  return value;
85065
85195
  }
@@ -85285,8 +85415,8 @@ init_scene_build();
85285
85415
  init_build_plan();
85286
85416
  init_storyboard_parse();
85287
85417
  init_output();
85288
- import { resolve as resolve66, join as join26 } from "node:path";
85289
- 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";
85290
85420
  var VALID_MODES2 = ["agent", "batch", "auto"];
85291
85421
  var VALID_STAGES2 = ["assets", "compose", "sync", "render", "all"];
85292
85422
  var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
@@ -85295,7 +85425,10 @@ var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
85295
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(
85296
85426
  "--skip-keyframe",
85297
85427
  "Don't generate keyframe stills (review keyframes first with --skip-video, then build)"
85298
- ).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(
85299
85432
  "after",
85300
85433
  `
85301
85434
  Examples:
@@ -85356,6 +85489,7 @@ Advanced equivalent: \`vibe scene build\`.`
85356
85489
  skipVideo: options.skipVideo ?? false,
85357
85490
  skipKeyframe: options.skipKeyframe ?? false,
85358
85491
  skipMusic: options.skipMusic ?? false,
85492
+ skipTranscript: options.skipTranscript ?? false,
85359
85493
  skipRender: options.skipRender ?? false,
85360
85494
  ttsProvider: options.tts,
85361
85495
  voice: options.voice,
@@ -85377,6 +85511,7 @@ Advanced equivalent: \`vibe scene build\`.`
85377
85511
  skipVideo: options.skipVideo,
85378
85512
  skipKeyframe: options.skipKeyframe,
85379
85513
  skipMusic: options.skipMusic,
85514
+ skipTranscript: options.skipTranscript,
85380
85515
  ttsProvider: options.tts,
85381
85516
  voice: options.voice,
85382
85517
  imageProvider,
@@ -85468,6 +85603,7 @@ Advanced equivalent: \`vibe scene build\`.`
85468
85603
  skipVideo: options.skipVideo,
85469
85604
  skipKeyframe: options.skipKeyframe,
85470
85605
  skipMusic: options.skipMusic,
85606
+ skipTranscript: options.skipTranscript,
85471
85607
  skipRender: options.skipRender,
85472
85608
  ttsProvider: options.tts,
85473
85609
  voice: options.voice,
@@ -85480,6 +85616,8 @@ Advanced equivalent: \`vibe scene build\`.`
85480
85616
  onProgress: (e) => {
85481
85617
  if (!spinner2) return;
85482
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)`;
85483
85621
  else if (e.type === "render-done") spinner2.text = `Rendered: ${e.outputPath}`;
85484
85622
  }
85485
85623
  });
@@ -85655,8 +85793,8 @@ function buildPlanValidationFailureData(plan) {
85655
85793
  };
85656
85794
  }
85657
85795
  function estimateBuildCost(params) {
85658
- const storyboardPath2 = join26(params.projectDir, "STORYBOARD.md");
85659
- if (!existsSync50(storyboardPath2)) return null;
85796
+ const storyboardPath2 = join27(params.projectDir, "STORYBOARD.md");
85797
+ if (!existsSync51(storyboardPath2)) return null;
85660
85798
  let beats = 0;
85661
85799
  try {
85662
85800
  const md = readFileSync4(storyboardPath2, "utf-8");
@@ -86101,7 +86239,7 @@ init_composer_resolve();
86101
86239
  init_cost_tier();
86102
86240
  init_scene_build_mode();
86103
86241
  init_output();
86104
- import { existsSync as existsSync51 } from "node:fs";
86242
+ import { existsSync as existsSync52 } from "node:fs";
86105
86243
  import { resolve as resolve69 } from "node:path";
86106
86244
  var COMMAND_KEY_MAP = getCommandKeyMap();
86107
86245
  var FREE_COMMANDS = [
@@ -86229,13 +86367,13 @@ async function runDiagnostics() {
86229
86367
  installed = false;
86230
86368
  }
86231
86369
  } else if (toolName === "chrome") {
86232
- const { existsSync: existsSync58 } = await import("node:fs");
86370
+ const { existsSync: existsSync59 } = await import("node:fs");
86233
86371
  const { homedir: homedir8 } = await import("node:os");
86234
- const { join: join30 } = await import("node:path");
86372
+ const { join: join31 } = await import("node:path");
86235
86373
  const chromePaths = [
86236
86374
  process.env.HYPERFRAMES_CHROME_PATH,
86237
86375
  process.env.CHROME_PATH,
86238
- join30(
86376
+ join31(
86239
86377
  homedir8(),
86240
86378
  ".cache",
86241
86379
  "puppeteer",
@@ -86250,7 +86388,7 @@ async function runDiagnostics() {
86250
86388
  "/usr/bin/chromium",
86251
86389
  "/usr/bin/chromium-browser"
86252
86390
  ];
86253
- installed = chromePaths.some((p) => p && existsSync58(p));
86391
+ installed = chromePaths.some((p) => p && existsSync59(p));
86254
86392
  } else {
86255
86393
  installed = commandExists(toolName);
86256
86394
  }
@@ -86290,7 +86428,7 @@ async function runDiagnostics() {
86290
86428
  }
86291
86429
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
86292
86430
  path: rel,
86293
- exists: existsSync51(resolve69(cwd, rel))
86431
+ exists: existsSync52(resolve69(cwd, rel))
86294
86432
  }));
86295
86433
  const projectInitialized = projectFiles.some((f) => f.exists);
86296
86434
  const nearestProjectConfigPath = await findProjectConfigPath(cwd);
@@ -86309,8 +86447,8 @@ async function runDiagnostics() {
86309
86447
  } catch (err) {
86310
86448
  if (!(err instanceof ComposerResolveError)) throw err;
86311
86449
  }
86312
- const sceneProjectInCwd = existsSync51(resolve69(cwd, "STORYBOARD.md"));
86313
- const skillInstalled = existsSync51(resolve69(cwd, "SKILL.md"));
86450
+ const sceneProjectInCwd = existsSync52(resolve69(cwd, "STORYBOARD.md"));
86451
+ const skillInstalled = existsSync52(resolve69(cwd, "SKILL.md"));
86314
86452
  return {
86315
86453
  system: {
86316
86454
  node: { version: nodeVersion, ok: true },
@@ -86858,7 +86996,7 @@ init_detect();
86858
86996
  init_ai_edit();
86859
86997
  init_output();
86860
86998
  import { resolve as resolve71 } from "node:path";
86861
- import { existsSync as existsSync52 } from "node:fs";
86999
+ import { existsSync as existsSync53 } from "node:fs";
86862
87000
  import { mkdir as mkdir27, rm as rm5 } from "node:fs/promises";
86863
87001
  var DEMO_DIR = resolve71(process.cwd(), ".vibeframe-demo");
86864
87002
  async function generateTestVideo(outputPath) {
@@ -86889,7 +87027,7 @@ async function generateTestVideo(outputPath) {
86889
87027
  "-y",
86890
87028
  outputPath
86891
87029
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
86892
- return existsSync52(outputPath);
87030
+ return existsSync53(outputPath);
86893
87031
  } catch {
86894
87032
  try {
86895
87033
  await execSafe("ffmpeg", [
@@ -86915,7 +87053,7 @@ async function generateTestVideo(outputPath) {
86915
87053
  "-y",
86916
87054
  outputPath
86917
87055
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
86918
- return existsSync52(outputPath);
87056
+ return existsSync53(outputPath);
86919
87057
  } catch {
86920
87058
  return false;
86921
87059
  }
@@ -87057,8 +87195,8 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
87057
87195
 
87058
87196
  // src/commands/context.ts
87059
87197
  init_esm();
87060
- import { existsSync as existsSync53 } from "node:fs";
87061
- 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";
87062
87200
  import { resolve as resolve72, dirname as dirname25 } from "node:path";
87063
87201
  import { fileURLToPath as fileURLToPath2 } from "node:url";
87064
87202
  var __filename = fileURLToPath2(import.meta.url);
@@ -87070,7 +87208,7 @@ function resolveContextPath(baseDir = __dirname) {
87070
87208
  // Source/dev layout: packages/cli/src/commands/context.ts -> packages/cli/CONTEXT.md
87071
87209
  resolve72(baseDir, "../../CONTEXT.md")
87072
87210
  ];
87073
- return candidates.find((candidate) => existsSync53(candidate)) ?? null;
87211
+ return candidates.find((candidate) => existsSync54(candidate)) ?? null;
87074
87212
  }
87075
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) => {
87076
87214
  const options = {
@@ -87176,7 +87314,7 @@ var contextCommand = new Command("context").description("Print CLI context/guide
87176
87314
  try {
87177
87315
  const contextPath = resolveContextPath();
87178
87316
  if (!contextPath) throw new Error("CONTEXT.md not found");
87179
- const content = await readFile47(contextPath, "utf-8");
87317
+ const content = await readFile48(contextPath, "utf-8");
87180
87318
  console.log(
87181
87319
  content.replace(
87182
87320
  "## Mental model",