@vibeframe/cli 0.78.0 → 0.79.1

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.
Files changed (2) hide show
  1. package/dist/index.js +207 -146
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -29771,7 +29771,7 @@ __export(remotion_exports, {
29771
29771
  wrapComponentWithVideo: () => wrapComponentWithVideo
29772
29772
  });
29773
29773
  import { writeFile as writeFile10, mkdir as mkdir6, rm, copyFile as copyFile2 } from "node:fs/promises";
29774
- import { existsSync as existsSync8 } from "node:fs";
29774
+ import { existsSync as existsSync9 } from "node:fs";
29775
29775
  import { join as join7 } from "node:path";
29776
29776
  import { tmpdir } from "node:os";
29777
29777
  import { createHash as createHash3 } from "node:crypto";
@@ -29846,7 +29846,7 @@ registerRoot(Root);
29846
29846
  const depsHash = createHash3("md5").update(packageJsonStr).digest("hex").slice(0, 12);
29847
29847
  const cacheMarker = join7(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
29848
29848
  const cachedModules = join7(REMOTION_CACHE_DIR, "node_modules");
29849
- if (existsSync8(cacheMarker) && existsSync8(cachedModules)) {
29849
+ if (existsSync9(cacheMarker) && existsSync9(cachedModules)) {
29850
29850
  const { symlink } = await import("node:fs/promises");
29851
29851
  try {
29852
29852
  await symlink(cachedModules, join7(dir, "node_modules"), "dir");
@@ -29858,7 +29858,7 @@ registerRoot(Root);
29858
29858
  try {
29859
29859
  await mkdir6(REMOTION_CACHE_DIR, { recursive: true });
29860
29860
  await writeFile10(join7(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
29861
- if (existsSync8(join7(dir, "node_modules"))) {
29861
+ if (existsSync9(join7(dir, "node_modules"))) {
29862
29862
  const { rename: rename5, symlink } = await import("node:fs/promises");
29863
29863
  await rename5(join7(dir, "node_modules"), cachedModules).catch(() => {
29864
29864
  });
@@ -30526,7 +30526,7 @@ __export(ai_motion_exports, {
30526
30526
  registerMotionCommand: () => registerMotionCommand
30527
30527
  });
30528
30528
  import { resolve as resolve12 } from "node:path";
30529
- import { existsSync as existsSync9 } from "node:fs";
30529
+ import { existsSync as existsSync10 } from "node:fs";
30530
30530
  import { readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
30531
30531
  async function executeMotion(options) {
30532
30532
  const modelAlias = options.model || "sonnet";
@@ -30574,7 +30574,7 @@ Use this image analysis to inform the color palette, typography placement, and o
30574
30574
  let result;
30575
30575
  if (options.fromTsx) {
30576
30576
  const tsxPath = resolve12(process.cwd(), options.fromTsx);
30577
- if (!existsSync9(tsxPath)) {
30577
+ if (!existsSync10(tsxPath)) {
30578
30578
  return { success: false, error: `TSX file not found: ${tsxPath}` };
30579
30579
  }
30580
30580
  const existingCode = await readFile10(tsxPath, "utf-8");
@@ -30709,6 +30709,33 @@ function registerMotionCommand(aiCommand) {
30709
30709
  if (options.output) {
30710
30710
  validateOutputPath(options.output);
30711
30711
  }
30712
+ if (options.duration !== void 0) {
30713
+ const d = parseFloat(options.duration);
30714
+ if (!Number.isFinite(d) || d <= 0 || d > 60) {
30715
+ exitWithError(usageError(
30716
+ `Invalid --duration: ${options.duration}`,
30717
+ "Must be a positive number \u2264 60 seconds."
30718
+ ));
30719
+ }
30720
+ }
30721
+ if (options.width !== void 0) {
30722
+ const w = parseInt(options.width, 10);
30723
+ if (!Number.isFinite(w) || w < 16 || w > 7680) {
30724
+ exitWithError(usageError(
30725
+ `Invalid --width: ${options.width}`,
30726
+ "Must be an integer between 16 and 7680."
30727
+ ));
30728
+ }
30729
+ }
30730
+ if (options.height !== void 0) {
30731
+ const h = parseInt(options.height, 10);
30732
+ if (!Number.isFinite(h) || h < 16 || h > 4320) {
30733
+ exitWithError(usageError(
30734
+ `Invalid --height: ${options.height}`,
30735
+ "Must be an integer between 16 and 4320."
30736
+ ));
30737
+ }
30738
+ }
30712
30739
  if (options.dryRun) {
30713
30740
  outputSuccess({
30714
30741
  command: "generate motion",
@@ -31778,7 +31805,7 @@ var init_speech = __esm({
31778
31805
  // src/commands/generate/music.ts
31779
31806
  import { resolve as resolve17 } from "node:path";
31780
31807
  import { writeFile as writeFile16 } from "node:fs/promises";
31781
- import { existsSync as existsSync10 } from "node:fs";
31808
+ import { existsSync as existsSync11 } from "node:fs";
31782
31809
  async function executeMusic(options) {
31783
31810
  try {
31784
31811
  const provider = options.provider || "elevenlabs";
@@ -31835,13 +31862,22 @@ async function executeMusic(options) {
31835
31862
  }
31836
31863
  }
31837
31864
  function registerMusicCommand(parent) {
31838
- parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("--melody <file>", "Reference melody audio file for conditioning (Replicate only)").option("--model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large").option("-o, --output <path>", "Output audio file path", "music.mp3").option("--no-wait", "Don't wait for generation to complete (Replicate async mode)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
31865
+ parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("--melody <file>", "Reference melody audio file for conditioning (Replicate only)").option("-m, --model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large").option("-o, --output <path>", "Output audio file path", "music.mp3").option("--no-wait", "Don't wait for generation to complete (Replicate async mode)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
31839
31866
  const startedAt = Date.now();
31840
31867
  try {
31841
31868
  rejectControlChars(prompt3);
31842
31869
  if (options.output) {
31843
31870
  validateOutputPath(options.output);
31844
31871
  }
31872
+ if (options.duration !== void 0) {
31873
+ const d = parseFloat(options.duration);
31874
+ if (!Number.isFinite(d) || d <= 0 || d > 600) {
31875
+ exitWithError(usageError(
31876
+ `Invalid --duration: ${options.duration}`,
31877
+ "Must be a positive number \u2264 600s (ElevenLabs 3-600, Replicate 1-30)."
31878
+ ));
31879
+ }
31880
+ }
31845
31881
  const provider = (options.provider || "elevenlabs").toLowerCase();
31846
31882
  if (options.dryRun) {
31847
31883
  outputSuccess({
@@ -31913,7 +31949,7 @@ function registerMusicCommand(parent) {
31913
31949
  if (options.melody) {
31914
31950
  spinner2.text = "Uploading melody reference...";
31915
31951
  const absPath = resolve17(process.cwd(), options.melody);
31916
- if (!existsSync10(absPath)) {
31952
+ if (!existsSync11(absPath)) {
31917
31953
  spinner2.fail(`Melody file not found: ${options.melody}`);
31918
31954
  exitWithError(notFoundError(options.melody));
31919
31955
  }
@@ -32004,7 +32040,7 @@ __export(ai_image_exports, {
32004
32040
  });
32005
32041
  import { resolve as resolve18, dirname as dirname5 } from "node:path";
32006
32042
  import { readFile as readFile12, writeFile as writeFile17, mkdir as mkdir8 } from "node:fs/promises";
32007
- import { existsSync as existsSync11 } from "node:fs";
32043
+ import { existsSync as existsSync12 } from "node:fs";
32008
32044
  async function executeImageGenerate(options) {
32009
32045
  const {
32010
32046
  prompt: prompt3,
@@ -32148,7 +32184,7 @@ async function executeGeminiEdit(options) {
32148
32184
  const imageBuffers = [];
32149
32185
  for (const imagePath of imagePaths) {
32150
32186
  const absPath = resolve18(process.cwd(), imagePath);
32151
- if (!existsSync11(absPath)) {
32187
+ if (!existsSync12(absPath)) {
32152
32188
  return { success: false, error: `Image not found: ${absPath}` };
32153
32189
  }
32154
32190
  const buffer = await readFile12(absPath);
@@ -32196,7 +32232,7 @@ async function executeThumbnailBestFrame(options) {
32196
32232
  model = "flash",
32197
32233
  apiKey
32198
32234
  } = options;
32199
- if (!existsSync11(videoPath)) {
32235
+ if (!existsSync12(videoPath)) {
32200
32236
  return { success: false, error: `Video not found: ${videoPath}` };
32201
32237
  }
32202
32238
  if (!commandExists("ffmpeg")) {
@@ -32236,7 +32272,7 @@ async function executeThumbnailBestFrame(options) {
32236
32272
  reason = reasonMatch[1];
32237
32273
  }
32238
32274
  await execSafe("ffmpeg", ["-ss", String(timestamp), "-i", videoPath, "-frames:v", "1", "-q:v", "2", outputPath, "-y"], { timeout: 6e4, maxBuffer: 50 * 1024 * 1024 });
32239
- if (!existsSync11(outputPath)) {
32275
+ if (!existsSync12(outputPath)) {
32240
32276
  return { success: false, error: "FFmpeg failed to extract frame" };
32241
32277
  }
32242
32278
  return {
@@ -32262,7 +32298,7 @@ var init_ai_image = __esm({
32262
32298
 
32263
32299
  // src/commands/generate/thumbnail.ts
32264
32300
  import { resolve as resolve19, dirname as dirname6, basename as basename3, extname as extname2 } from "node:path";
32265
- import { existsSync as existsSync12 } from "node:fs";
32301
+ import { existsSync as existsSync13 } from "node:fs";
32266
32302
  import { writeFile as writeFile18, mkdir as mkdir9 } from "node:fs/promises";
32267
32303
  function registerThumbnailCommand(parent) {
32268
32304
  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: flash, latest, pro (default: flash)", "flash").action(async (description, options) => {
@@ -32274,7 +32310,7 @@ function registerThumbnailCommand(parent) {
32274
32310
  }
32275
32311
  if (options.bestFrame) {
32276
32312
  const absVideoPath = resolve19(process.cwd(), options.bestFrame);
32277
- if (!existsSync12(absVideoPath)) {
32313
+ if (!existsSync13(absVideoPath)) {
32278
32314
  exitWithError(notFoundError(absVideoPath));
32279
32315
  }
32280
32316
  if (!commandExists("ffmpeg")) {
@@ -32975,6 +33011,15 @@ Examples:
32975
33011
  if (options.output) {
32976
33012
  validateOutputPath(options.output);
32977
33013
  }
33014
+ if (options.count !== void 0) {
33015
+ const n = parseInt(options.count, 10);
33016
+ if (!Number.isFinite(n) || n < 1 || n > 10) {
33017
+ exitWithError(usageError(
33018
+ `Invalid --count: ${options.count}`,
33019
+ "Must be an integer between 1 and 10."
33020
+ ));
33021
+ }
33022
+ }
32978
33023
  const imageRegistry = getProvidersFor("image");
32979
33024
  const validProviders = [...imageRegistry.map((p) => p.name), "runway"];
32980
33025
  const providerEnvMap = Object.fromEntries(
@@ -34654,7 +34699,7 @@ var init_video_providers = __esm({
34654
34699
  // src/commands/ai-script-pipeline.ts
34655
34700
  import { readFile as readFile13, writeFile as writeFile23, unlink as unlink2, rename as rename2 } from "node:fs/promises";
34656
34701
  import { resolve as resolve24, extname as extname3 } from "node:path";
34657
- import { existsSync as existsSync13 } from "node:fs";
34702
+ import { existsSync as existsSync14 } from "node:fs";
34658
34703
  async function executeRegenerateScene(options) {
34659
34704
  const result = {
34660
34705
  success: false,
@@ -34663,12 +34708,12 @@ async function executeRegenerateScene(options) {
34663
34708
  };
34664
34709
  try {
34665
34710
  const outputDir = resolve24(process.cwd(), options.projectDir);
34666
- if (!existsSync13(outputDir)) {
34711
+ if (!existsSync14(outputDir)) {
34667
34712
  return { ...result, error: `Project directory not found: ${outputDir}` };
34668
34713
  }
34669
34714
  const yamlPath = resolve24(outputDir, "storyboard.yaml");
34670
34715
  const jsonPath = resolve24(outputDir, "storyboard.json");
34671
- const storyboardPath = existsSync13(yamlPath) ? yamlPath : existsSync13(jsonPath) ? jsonPath : null;
34716
+ const storyboardPath = existsSync14(yamlPath) ? yamlPath : existsSync14(jsonPath) ? jsonPath : null;
34672
34717
  if (!storyboardPath) {
34673
34718
  return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
34674
34719
  }
@@ -34761,14 +34806,14 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
34761
34806
  const refSceneNum = options.referenceScene;
34762
34807
  if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
34763
34808
  const refImagePath = resolve24(outputDir, `scene-${refSceneNum}.png`);
34764
- if (existsSync13(refImagePath)) {
34809
+ if (existsSync14(refImagePath)) {
34765
34810
  referenceImageBuffer = await readFile13(refImagePath);
34766
34811
  }
34767
34812
  } else if (!refSceneNum) {
34768
34813
  for (let i = 1; i <= segments.length; i++) {
34769
34814
  if (i !== sceneNum) {
34770
34815
  const otherImagePath = resolve24(outputDir, `scene-${i}.png`);
34771
- if (existsSync13(otherImagePath)) {
34816
+ if (existsSync14(otherImagePath)) {
34772
34817
  referenceImageBuffer = await readFile13(otherImagePath);
34773
34818
  break;
34774
34819
  }
@@ -34857,7 +34902,7 @@ Generate the single-person scene image now.`;
34857
34902
  }
34858
34903
  if (regenerateVideo && videoApiKey) {
34859
34904
  options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
34860
- if (!existsSync13(imagePath)) {
34905
+ if (!existsSync14(imagePath)) {
34861
34906
  result.failedScenes.push(sceneNum);
34862
34907
  continue;
34863
34908
  }
@@ -35119,6 +35164,15 @@ Examples:
35119
35164
  if (options.output) {
35120
35165
  validateOutputPath(options.output);
35121
35166
  }
35167
+ if (options.duration !== void 0) {
35168
+ const d = parseFloat(options.duration);
35169
+ if (!Number.isFinite(d) || d <= 0 || d > 60) {
35170
+ exitWithError(usageError(
35171
+ `Invalid --duration: ${options.duration}`,
35172
+ "Must be a positive number \u2264 60 seconds."
35173
+ ));
35174
+ }
35175
+ }
35122
35176
  const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
35123
35177
  const videoEnvMap = {
35124
35178
  grok: "XAI_API_KEY",
@@ -35631,7 +35685,7 @@ Run 'vibe schema generate.<command>' for structured parameter info.
35631
35685
  });
35632
35686
 
35633
35687
  // src/commands/_shared/edit/silence-cut.ts
35634
- import { existsSync as existsSync14 } from "node:fs";
35688
+ import { existsSync as existsSync15 } from "node:fs";
35635
35689
  import { readFile as readFile15 } from "node:fs/promises";
35636
35690
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
35637
35691
  const totalDuration = await getVideoDuration(videoPath);
@@ -35763,7 +35817,7 @@ async function executeSilenceCut(options) {
35763
35817
  analyzeOnly = false,
35764
35818
  useGemini = false
35765
35819
  } = options;
35766
- if (!existsSync14(videoPath)) {
35820
+ if (!existsSync15(videoPath)) {
35767
35821
  return { success: false, error: `Video not found: ${videoPath}` };
35768
35822
  }
35769
35823
  if (!commandExists("ffmpeg")) {
@@ -35871,7 +35925,7 @@ var init_silence_cut = __esm({
35871
35925
  });
35872
35926
 
35873
35927
  // src/commands/_shared/edit/jump-cut.ts
35874
- import { existsSync as existsSync15 } from "node:fs";
35928
+ import { existsSync as existsSync16 } from "node:fs";
35875
35929
  import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir11 } from "node:fs/promises";
35876
35930
  import { join as join8 } from "node:path";
35877
35931
  async function transcribeWithWords(audioPath, apiKey, language) {
@@ -35934,7 +35988,7 @@ async function executeJumpCut(options) {
35934
35988
  analyzeOnly = false,
35935
35989
  apiKey
35936
35990
  } = options;
35937
- if (!existsSync15(videoPath)) {
35991
+ if (!existsSync16(videoPath)) {
35938
35992
  return { success: false, error: `Video not found: ${videoPath}` };
35939
35993
  }
35940
35994
  if (!commandExists("ffmpeg")) {
@@ -36220,7 +36274,7 @@ var init_helpers = __esm({
36220
36274
  });
36221
36275
 
36222
36276
  // src/commands/_shared/edit/caption.ts
36223
- import { existsSync as existsSync16 } from "node:fs";
36277
+ import { existsSync as existsSync17 } from "node:fs";
36224
36278
  import { readFile as readFile17, writeFile as writeFile26, mkdir as mkdir12 } from "node:fs/promises";
36225
36279
  import { dirname as dirname8, basename as basename5, extname as extname4, join as join9 } from "node:path";
36226
36280
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
@@ -36250,7 +36304,7 @@ async function executeCaption(options) {
36250
36304
  position = "bottom",
36251
36305
  apiKey
36252
36306
  } = options;
36253
- if (!existsSync16(videoPath)) {
36307
+ if (!existsSync17(videoPath)) {
36254
36308
  return { success: false, error: `Video not found: ${videoPath}` };
36255
36309
  }
36256
36310
  if (!commandExists("ffmpeg")) {
@@ -36428,10 +36482,10 @@ var init_caption = __esm({
36428
36482
  });
36429
36483
 
36430
36484
  // src/commands/_shared/edit/noise-reduce.ts
36431
- import { existsSync as existsSync17 } from "node:fs";
36485
+ import { existsSync as existsSync18 } from "node:fs";
36432
36486
  async function executeNoiseReduce(options) {
36433
36487
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
36434
- if (!existsSync17(inputPath)) {
36488
+ if (!existsSync18(inputPath)) {
36435
36489
  return { success: false, error: `File not found: ${inputPath}` };
36436
36490
  }
36437
36491
  if (!commandExists("ffmpeg")) {
@@ -36488,7 +36542,7 @@ var init_noise_reduce = __esm({
36488
36542
  });
36489
36543
 
36490
36544
  // src/commands/_shared/edit/fade.ts
36491
- import { existsSync as existsSync18 } from "node:fs";
36545
+ import { existsSync as existsSync19 } from "node:fs";
36492
36546
  async function executeFade(options) {
36493
36547
  const {
36494
36548
  videoPath,
@@ -36498,7 +36552,7 @@ async function executeFade(options) {
36498
36552
  audioOnly = false,
36499
36553
  videoOnly = false
36500
36554
  } = options;
36501
- if (!existsSync18(videoPath)) {
36555
+ if (!existsSync19(videoPath)) {
36502
36556
  return { success: false, error: `Video not found: ${videoPath}` };
36503
36557
  }
36504
36558
  if (!commandExists("ffmpeg")) {
@@ -36565,7 +36619,7 @@ var init_fade = __esm({
36565
36619
  });
36566
36620
 
36567
36621
  // src/commands/_shared/edit/translate-srt.ts
36568
- import { existsSync as existsSync19 } from "node:fs";
36622
+ import { existsSync as existsSync20 } from "node:fs";
36569
36623
  import { readFile as readFile18, writeFile as writeFile27 } from "node:fs/promises";
36570
36624
  async function executeTranslateSrt(options) {
36571
36625
  const {
@@ -36576,7 +36630,7 @@ async function executeTranslateSrt(options) {
36576
36630
  sourceLanguage,
36577
36631
  apiKey
36578
36632
  } = options;
36579
- if (!existsSync19(srtPath)) {
36633
+ if (!existsSync20(srtPath)) {
36580
36634
  return { success: false, error: `SRT file not found: ${srtPath}` };
36581
36635
  }
36582
36636
  try {
@@ -36693,7 +36747,7 @@ var init_translate_srt = __esm({
36693
36747
  });
36694
36748
 
36695
36749
  // src/commands/_shared/edit/text-overlay.ts
36696
- import { existsSync as existsSync20 } from "node:fs";
36750
+ import { existsSync as existsSync21 } from "node:fs";
36697
36751
  import { resolve as resolve26 } from "node:path";
36698
36752
  function detectSystemFont() {
36699
36753
  const platform = process.platform;
@@ -36704,7 +36758,7 @@ function detectSystemFont() {
36704
36758
  "/Library/Fonts/Arial.ttf"
36705
36759
  ];
36706
36760
  for (const f of candidates) {
36707
- if (existsSync20(f)) return f;
36761
+ if (existsSync21(f)) return f;
36708
36762
  }
36709
36763
  } else if (platform === "linux") {
36710
36764
  const candidates = [
@@ -36713,7 +36767,7 @@ function detectSystemFont() {
36713
36767
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
36714
36768
  ];
36715
36769
  for (const f of candidates) {
36716
- if (existsSync20(f)) return f;
36770
+ if (existsSync21(f)) return f;
36717
36771
  }
36718
36772
  } else if (platform === "win32") {
36719
36773
  const candidates = [
@@ -36721,7 +36775,7 @@ function detectSystemFont() {
36721
36775
  "C:\\Windows\\Fonts\\segoeui.ttf"
36722
36776
  ];
36723
36777
  for (const f of candidates) {
36724
- if (existsSync20(f)) return f;
36778
+ if (existsSync21(f)) return f;
36725
36779
  }
36726
36780
  }
36727
36781
  return null;
@@ -36742,7 +36796,7 @@ async function applyTextOverlays(options) {
36742
36796
  }
36743
36797
  const absVideoPath = resolve26(process.cwd(), videoPath);
36744
36798
  const absOutputPath = resolve26(process.cwd(), outputPath);
36745
- if (!existsSync20(absVideoPath)) {
36799
+ if (!existsSync21(absVideoPath)) {
36746
36800
  return { success: false, error: `Video not found: ${absVideoPath}` };
36747
36801
  }
36748
36802
  if (!commandExists("ffmpeg")) {
@@ -36923,12 +36977,12 @@ var init_ai_edit = __esm({
36923
36977
 
36924
36978
  // src/commands/ai-edit-cli.ts
36925
36979
  import { resolve as resolve27, extname as extname5, basename as basename6 } from "node:path";
36926
- import { existsSync as existsSync21 } from "node:fs";
36980
+ import { existsSync as existsSync22 } from "node:fs";
36927
36981
  function registerEditCommands(aiCommand) {
36928
36982
  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", "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", `
36929
36983
  Examples:
36930
36984
  $ vibe edit silence-cut interview.mp4 -o clean.mp4
36931
- $ vibe ed sc podcast.mp4 -o trimmed.mp4 -n -25 -d 1.0
36985
+ $ vibe ed sc podcast.mp4 -o trimmed.mp4 --noise -25 --min-duration 1.0
36932
36986
  $ vibe ed sc video.mp4 --use-gemini -o smart-cut.mp4 # AI-powered detection
36933
36987
  $ vibe ed sc video.mp4 --analyze-only # Detect only, no cut
36934
36988
  $ vibe ed sc video.mp4 --dry-run --json
@@ -36940,7 +36994,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
36940
36994
  validateOutputPath(options.output);
36941
36995
  }
36942
36996
  const absVideoPath = resolve27(process.cwd(), videoPath);
36943
- if (!existsSync21(absVideoPath)) {
36997
+ if (!existsSync22(absVideoPath)) {
36944
36998
  exitWithError(notFoundError(absVideoPath));
36945
36999
  }
36946
37000
  if (!commandExists("ffmpeg")) {
@@ -37029,9 +37083,9 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
37029
37083
  aiCommand.command("caption").alias("cap").description("Transcribe and burn styled captions onto video (Whisper + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-captioned.<ext>)").option("--style <style>", "Caption style: minimal, bold, outline, karaoke (default: bold)", "bold").option("--font-size <pixels>", "Override auto-calculated font size").option("--color <color>", "Font color (default: white)", "white").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--position <pos>", "Caption position: top, center, bottom (default: bottom)", "bottom").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
37030
37084
  Examples:
37031
37085
  $ vibe edit caption video.mp4 -o captioned.mp4
37032
- $ vibe ed cap video.mp4 -o out.mp4 -s bold --position top
37086
+ $ vibe ed cap video.mp4 -o out.mp4 --style bold --position top
37033
37087
  $ vibe ed cap video.mp4 -o out.mp4 -l ko # Korean transcription
37034
- $ vibe ed cap video.mp4 -o out.mp4 -s karaoke # Karaoke-style
37088
+ $ vibe ed cap video.mp4 -o out.mp4 --style karaoke # Karaoke-style
37035
37089
  $ vibe ed cap video.mp4 --dry-run --json
37036
37090
 
37037
37091
  Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoPath, options) => {
@@ -37041,7 +37095,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37041
37095
  validateOutputPath(options.output);
37042
37096
  }
37043
37097
  const absVideoPath = resolve27(process.cwd(), videoPath);
37044
- if (!existsSync21(absVideoPath)) {
37098
+ if (!existsSync22(absVideoPath)) {
37045
37099
  exitWithError(notFoundError(absVideoPath));
37046
37100
  }
37047
37101
  if (!commandExists("ffmpeg")) {
@@ -37122,7 +37176,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37122
37176
  validateOutputPath(options.output);
37123
37177
  }
37124
37178
  const absInputPath = resolve27(process.cwd(), inputPath);
37125
- if (!existsSync21(absInputPath)) {
37179
+ if (!existsSync22(absInputPath)) {
37126
37180
  exitWithError(notFoundError(absInputPath));
37127
37181
  }
37128
37182
  if (!commandExists("ffmpeg")) {
@@ -37188,7 +37242,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37188
37242
  validateOutputPath(options.output);
37189
37243
  }
37190
37244
  const absVideoPath = resolve27(process.cwd(), videoPath);
37191
- if (!existsSync21(absVideoPath)) {
37245
+ if (!existsSync22(absVideoPath)) {
37192
37246
  exitWithError(notFoundError(absVideoPath));
37193
37247
  }
37194
37248
  if (!commandExists("ffmpeg")) {
@@ -37263,7 +37317,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37263
37317
  exitWithError(usageError("Target language required. Use -t or --target"));
37264
37318
  }
37265
37319
  const absSrtPath = resolve27(process.cwd(), srtPath);
37266
- if (!existsSync21(absSrtPath)) {
37320
+ if (!existsSync22(absSrtPath)) {
37267
37321
  exitWithError(notFoundError(absSrtPath));
37268
37322
  }
37269
37323
  if (options.dryRun) {
@@ -37339,7 +37393,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37339
37393
  }
37340
37394
  if (options.fillers) rejectControlChars(options.fillers);
37341
37395
  const absVideoPath = resolve27(process.cwd(), videoPath);
37342
- if (!existsSync21(absVideoPath)) {
37396
+ if (!existsSync22(absVideoPath)) {
37343
37397
  exitWithError(notFoundError(absVideoPath));
37344
37398
  }
37345
37399
  if (!commandExists("ffmpeg")) {
@@ -37439,7 +37493,7 @@ var init_ai_edit_cli = __esm({
37439
37493
  });
37440
37494
 
37441
37495
  // src/commands/_shared/execute-fill-gaps.ts
37442
- import { existsSync as existsSync22 } from "node:fs";
37496
+ import { existsSync as existsSync23 } from "node:fs";
37443
37497
  import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir13, rename as renameFs } from "node:fs/promises";
37444
37498
  import { resolve as resolve28, dirname as dirname9 } from "node:path";
37445
37499
  function detectVideoGaps(videoClips, totalDuration) {
@@ -37522,7 +37576,7 @@ async function executeFillGaps(options) {
37522
37576
  try {
37523
37577
  onProgress("Loading project...");
37524
37578
  const filePath = resolve28(process.cwd(), options.projectPath);
37525
- if (!existsSync22(filePath)) {
37579
+ if (!existsSync23(filePath)) {
37526
37580
  return {
37527
37581
  success: false,
37528
37582
  error: `Project file not found: ${filePath}`,
@@ -37657,7 +37711,7 @@ async function executeFillGaps(options) {
37657
37711
  }
37658
37712
  const projectDir = dirname9(filePath);
37659
37713
  const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
37660
- if (!existsSync22(footageDir)) {
37714
+ if (!existsSync23(footageDir)) {
37661
37715
  await mkdir13(footageDir, { recursive: true });
37662
37716
  }
37663
37717
  humanLines.push("Generating AI Videos");
@@ -38178,12 +38232,12 @@ var init_edit_cmd = __esm({
38178
38232
  `
38179
38233
  Examples:
38180
38234
  $ vibe edit silence-cut interview.mp4 -o clean.mp4
38181
- $ vibe edit caption video.mp4 -o captioned.mp4 -s bold
38235
+ $ vibe edit caption video.mp4 -o captioned.mp4 --style bold
38182
38236
  $ vibe edit grade video.mp4 -o graded.mp4 --preset cinematic-warm
38183
38237
  $ vibe edit reframe landscape.mp4 -o vertical.mp4 -a 9:16
38184
38238
  $ vibe edit image photo.png "add sunset background" -o edited.png
38185
- $ vibe edit text-overlay video.mp4 -t "Title" -s center-bold -o out.mp4
38186
- $ vibe edit noise-reduce noisy.mp4 -o clean.mp4 -s high
38239
+ $ vibe edit text-overlay video.mp4 --text "Title" --style center-bold -o out.mp4
38240
+ $ vibe edit noise-reduce noisy.mp4 -o clean.mp4 --strength high
38187
38241
  $ vibe edit fade video.mp4 -o faded.mp4 --fade-in 1 --fade-out 1
38188
38242
 
38189
38243
  API Keys (varies by subcommand):
@@ -38930,7 +38984,7 @@ __export(ai_analyze_exports, {
38930
38984
  });
38931
38985
  import { readFile as readFile21 } from "node:fs/promises";
38932
38986
  import { extname as extname6, resolve as resolve30 } from "node:path";
38933
- import { existsSync as existsSync23 } from "node:fs";
38987
+ import { existsSync as existsSync24 } from "node:fs";
38934
38988
  async function executeGeminiVideo(options) {
38935
38989
  try {
38936
38990
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -38949,7 +39003,7 @@ async function executeGeminiVideo(options) {
38949
39003
  videoData = options.source;
38950
39004
  } else {
38951
39005
  const absPath = resolve30(process.cwd(), options.source);
38952
- if (!existsSync23(absPath)) {
39006
+ if (!existsSync24(absPath)) {
38953
39007
  return { success: false, error: `File not found: ${absPath}` };
38954
39008
  }
38955
39009
  videoData = await readFile21(absPath);
@@ -39022,7 +39076,7 @@ async function executeAnalyze(options) {
39022
39076
  imageBuffer = Buffer.from(await response.arrayBuffer());
39023
39077
  } else {
39024
39078
  const absPath = resolve30(process.cwd(), source);
39025
- if (!existsSync23(absPath)) {
39079
+ if (!existsSync24(absPath)) {
39026
39080
  return { success: false, error: `File not found: ${absPath}` };
39027
39081
  }
39028
39082
  imageBuffer = await readFile21(absPath);
@@ -39057,7 +39111,7 @@ async function executeAnalyze(options) {
39057
39111
  videoData = Buffer.from(await response.arrayBuffer());
39058
39112
  } else {
39059
39113
  const absPath = resolve30(process.cwd(), source);
39060
- if (!existsSync23(absPath)) {
39114
+ if (!existsSync24(absPath)) {
39061
39115
  return { success: false, error: `File not found: ${absPath}` };
39062
39116
  }
39063
39117
  videoData = await readFile21(absPath);
@@ -39196,7 +39250,7 @@ __export(ai_review_exports, {
39196
39250
  });
39197
39251
  import { readFile as readFile22, rename as rename3 } from "node:fs/promises";
39198
39252
  import { resolve as resolve31 } from "node:path";
39199
- import { existsSync as existsSync24 } from "node:fs";
39253
+ import { existsSync as existsSync25 } from "node:fs";
39200
39254
  function parseReviewFeedback(response) {
39201
39255
  let cleaned = response.trim();
39202
39256
  if (cleaned.startsWith("```json")) {
@@ -39221,7 +39275,7 @@ function parseReviewFeedback(response) {
39221
39275
  async function executeReview(options) {
39222
39276
  const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
39223
39277
  const absVideoPath = resolve31(process.cwd(), videoPath);
39224
- if (!existsSync24(absVideoPath)) {
39278
+ if (!existsSync25(absVideoPath)) {
39225
39279
  return { success: false, error: `Video not found: ${absVideoPath}` };
39226
39280
  }
39227
39281
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -39231,7 +39285,7 @@ async function executeReview(options) {
39231
39285
  let storyboardContext = "";
39232
39286
  if (storyboardPath) {
39233
39287
  const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
39234
- if (existsSync24(absStoryboardPath)) {
39288
+ if (existsSync25(absStoryboardPath)) {
39235
39289
  const content = await readFile22(absStoryboardPath, "utf-8");
39236
39290
  storyboardContext = `
39237
39291
 
@@ -39437,7 +39491,7 @@ var init_ai_review = __esm({
39437
39491
  // src/commands/ai-highlights.ts
39438
39492
  import { readFile as readFile26, writeFile as writeFile33, mkdir as mkdir15 } from "node:fs/promises";
39439
39493
  import { resolve as resolve35, dirname as dirname12, basename as basename8, extname as extname8 } from "node:path";
39440
- import { existsSync as existsSync27 } from "node:fs";
39494
+ import { existsSync as existsSync28 } from "node:fs";
39441
39495
  function filterHighlights(highlights, options) {
39442
39496
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
39443
39497
  filtered.sort((a, b) => b.confidence - a.confidence);
@@ -39477,7 +39531,7 @@ function truncate(text, maxLength) {
39477
39531
  async function executeHighlights(options) {
39478
39532
  try {
39479
39533
  const absPath = resolve35(process.cwd(), options.media);
39480
- if (!existsSync27(absPath)) {
39534
+ if (!existsSync28(absPath)) {
39481
39535
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
39482
39536
  }
39483
39537
  const ext = extname8(absPath).toLowerCase();
@@ -39593,7 +39647,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
39593
39647
  const audioBuffer = await readFile26(audioPath);
39594
39648
  const audioBlob = new Blob([audioBuffer]);
39595
39649
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
39596
- if (tempAudioPath && existsSync27(tempAudioPath)) {
39650
+ if (tempAudioPath && existsSync28(tempAudioPath)) {
39597
39651
  const { unlink: unlinkFile } = await import("node:fs/promises");
39598
39652
  await unlinkFile(tempAudioPath).catch(() => {
39599
39653
  });
@@ -39680,7 +39734,7 @@ async function executeAutoShorts(options) {
39680
39734
  return { success: false, shorts: [], error: "FFmpeg not found" };
39681
39735
  }
39682
39736
  const absPath = resolve35(process.cwd(), options.video);
39683
- if (!existsSync27(absPath)) {
39737
+ if (!existsSync28(absPath)) {
39684
39738
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
39685
39739
  }
39686
39740
  const targetDuration = options.duration ?? 60;
@@ -39808,7 +39862,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
39808
39862
  };
39809
39863
  }
39810
39864
  const outputDir = options.outputDir ? resolve35(process.cwd(), options.outputDir) : dirname12(absPath);
39811
- if (options.outputDir && !existsSync27(outputDir)) {
39865
+ if (options.outputDir && !existsSync28(outputDir)) {
39812
39866
  await mkdir15(outputDir, { recursive: true });
39813
39867
  }
39814
39868
  const result = {
@@ -39891,7 +39945,7 @@ function registerHighlightsCommands(aiCommand) {
39891
39945
  validateOutputPath(options.output);
39892
39946
  }
39893
39947
  const absPath = resolve35(process.cwd(), mediaPath);
39894
- if (!existsSync27(absPath)) {
39948
+ if (!existsSync28(absPath)) {
39895
39949
  exitWithError(notFoundError(absPath));
39896
39950
  }
39897
39951
  if (options.dryRun) {
@@ -40078,7 +40132,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40078
40132
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
40079
40133
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
40080
40134
  transcribeSpinner.fail("Transcription failed");
40081
- if (tempAudioPath && existsSync27(tempAudioPath)) {
40135
+ if (tempAudioPath && existsSync28(tempAudioPath)) {
40082
40136
  const { unlink: unlinkFile } = await import("node:fs/promises");
40083
40137
  await unlinkFile(tempAudioPath).catch(() => {
40084
40138
  });
@@ -40086,7 +40140,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40086
40140
  exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
40087
40141
  }
40088
40142
  transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
40089
- if (tempAudioPath && existsSync27(tempAudioPath)) {
40143
+ if (tempAudioPath && existsSync28(tempAudioPath)) {
40090
40144
  const { unlink: unlinkFile } = await import("node:fs/promises");
40091
40145
  await unlinkFile(tempAudioPath).catch(() => {
40092
40146
  });
@@ -40192,7 +40246,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40192
40246
  validateOutputPath(options.output);
40193
40247
  }
40194
40248
  const absPath = resolve35(process.cwd(), videoPath);
40195
- if (!existsSync27(absPath)) {
40249
+ if (!existsSync28(absPath)) {
40196
40250
  exitWithError(notFoundError(absPath));
40197
40251
  }
40198
40252
  if (options.dryRun) {
@@ -40385,7 +40439,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
40385
40439
  return;
40386
40440
  }
40387
40441
  const outputDir = options.outputDir ? resolve35(process.cwd(), options.outputDir) : dirname12(absPath);
40388
- if (options.outputDir && !existsSync27(outputDir)) {
40442
+ if (options.outputDir && !existsSync28(outputDir)) {
40389
40443
  await mkdir15(outputDir, { recursive: true });
40390
40444
  }
40391
40445
  for (let i = 0; i < selectedHighlights.length; i++) {
@@ -40402,7 +40456,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
40402
40456
  outputPath = resolve35(outputDir, `${baseName}-short-${i + 1}.mp4`);
40403
40457
  }
40404
40458
  const parentDir = dirname12(outputPath);
40405
- if (!existsSync27(parentDir)) {
40459
+ if (!existsSync28(parentDir)) {
40406
40460
  await mkdir15(parentDir, { recursive: true });
40407
40461
  }
40408
40462
  const { stdout: probeOut } = await execSafe("ffprobe", [
@@ -40477,7 +40531,7 @@ var init_ai_highlights = __esm({
40477
40531
  // src/commands/ai-animated-caption.ts
40478
40532
  import { resolve as resolve36, dirname as dirname13, basename as basename9 } from "node:path";
40479
40533
  import { writeFile as writeFile34, mkdir as mkdir16, rm as rm2 } from "node:fs/promises";
40480
- import { existsSync as existsSync28 } from "node:fs";
40534
+ import { existsSync as existsSync29 } from "node:fs";
40481
40535
  import { tmpdir as tmpdir2 } from "node:os";
40482
40536
  function groupWords(words, options = {}) {
40483
40537
  if (words.length === 0) return [];
@@ -40654,7 +40708,7 @@ async function executeAnimatedCaption(options) {
40654
40708
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
40655
40709
  const absOutputPath = resolve36(process.cwd(), outputPath);
40656
40710
  const outDir = dirname13(absOutputPath);
40657
- if (!existsSync28(outDir)) {
40711
+ if (!existsSync29(outDir)) {
40658
40712
  await mkdir16(outDir, { recursive: true });
40659
40713
  }
40660
40714
  if (tier === "ass") {
@@ -50603,7 +50657,7 @@ var init_detect = __esm({
50603
50657
  });
50604
50658
 
50605
50659
  // src/pipeline/renderers/chrome.ts
50606
- import { existsSync as existsSync31 } from "node:fs";
50660
+ import { existsSync as existsSync32 } from "node:fs";
50607
50661
  import { homedir as homedir5 } from "node:os";
50608
50662
  import { join as join11 } from "node:path";
50609
50663
  function findChrome() {
@@ -50629,7 +50683,7 @@ function findChrome() {
50629
50683
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
50630
50684
  ];
50631
50685
  for (const c of candidates) {
50632
- if (c && existsSync31(c)) return c;
50686
+ if (c && existsSync32(c)) return c;
50633
50687
  }
50634
50688
  return void 0;
50635
50689
  }
@@ -50884,7 +50938,7 @@ __export(scene_render_exports, {
50884
50938
  qualityToCrf: () => qualityToCrf
50885
50939
  });
50886
50940
  import { mkdir as mkdir18, readFile as readFile32, stat as stat4 } from "node:fs/promises";
50887
- import { existsSync as existsSync32 } from "node:fs";
50941
+ import { existsSync as existsSync33 } from "node:fs";
50888
50942
  import { resolve as resolve45, relative as relative6, dirname as dirname16, basename as basename16 } from "node:path";
50889
50943
  import {
50890
50944
  createRenderJob,
@@ -50902,7 +50956,7 @@ function defaultOutputPath(opts) {
50902
50956
  }
50903
50957
  async function readProjectName(projectDir) {
50904
50958
  const cfgPath = resolve45(projectDir, "vibe.project.yaml");
50905
- if (!existsSync32(cfgPath)) return void 0;
50959
+ if (!existsSync33(cfgPath)) return void 0;
50906
50960
  try {
50907
50961
  const raw = await (await import("node:fs/promises")).readFile(cfgPath, "utf-8");
50908
50962
  const parsed = (0, import_yaml7.parse)(raw);
@@ -51032,7 +51086,7 @@ __export(scene_build_exports, {
51032
51086
  executeSceneBuild: () => executeSceneBuild,
51033
51087
  resolveSceneBuildMode: () => resolveSceneBuildMode
51034
51088
  });
51035
- import { existsSync as existsSync33 } from "node:fs";
51089
+ import { existsSync as existsSync34 } from "node:fs";
51036
51090
  import { mkdir as mkdir19, readFile as readFile33, writeFile as writeFile38 } from "node:fs/promises";
51037
51091
  import { dirname as dirname17, join as join12, resolve as resolve46 } from "node:path";
51038
51092
  async function executeSceneBuild(opts) {
@@ -51042,7 +51096,7 @@ async function executeSceneBuild(opts) {
51042
51096
  });
51043
51097
  const mode = resolveSceneBuildMode(opts);
51044
51098
  const storyboardPath = join12(projectDir, "STORYBOARD.md");
51045
- if (!existsSync33(storyboardPath)) {
51099
+ if (!existsSync34(storyboardPath)) {
51046
51100
  return failBeforePrimitives(
51047
51101
  `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
51048
51102
  startedAt
@@ -51078,7 +51132,7 @@ async function executeSceneBuild(opts) {
51078
51132
  if (mode === "agent") {
51079
51133
  const compositionsDir = join12(projectDir, "compositions");
51080
51134
  const missingBeats = parsed.beats.filter(
51081
- (b) => !existsSync33(join12(compositionsDir, `scene-${b.id}.html`))
51135
+ (b) => !existsSync34(join12(compositionsDir, `scene-${b.id}.html`))
51082
51136
  );
51083
51137
  if (missingBeats.length > 0) {
51084
51138
  const plan = await getComposePrompts({ projectDir });
@@ -51126,7 +51180,7 @@ async function executeSceneBuild(opts) {
51126
51180
  }
51127
51181
  composeData = composeResult.data;
51128
51182
  }
51129
- if (!existsSync33(join12(projectDir, "index.html"))) {
51183
+ if (!existsSync34(join12(projectDir, "index.html"))) {
51130
51184
  await scaffoldSceneProject({
51131
51185
  dir: projectDir,
51132
51186
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -51186,7 +51240,7 @@ async function dispatchNarration(beat, ctx) {
51186
51240
  if (!text) return { status: "no-cue" };
51187
51241
  for (const ext of ["mp3", "wav"]) {
51188
51242
  const rel2 = `assets/narration-${beat.id}.${ext}`;
51189
- if (existsSync33(join12(ctx.projectDir, rel2)) && !ctx.force) {
51243
+ if (existsSync34(join12(ctx.projectDir, rel2)) && !ctx.force) {
51190
51244
  ctx.onProgress({ type: "narration-cached", beatId: beat.id, path: rel2 });
51191
51245
  return { status: "cached", path: rel2 };
51192
51246
  }
@@ -51227,7 +51281,7 @@ async function dispatchBackdrop(beat, ctx) {
51227
51281
  }
51228
51282
  const rel = `assets/backdrop-${beat.id}.png`;
51229
51283
  const abs = join12(ctx.projectDir, rel);
51230
- if (existsSync33(abs) && !ctx.force) {
51284
+ if (existsSync34(abs) && !ctx.force) {
51231
51285
  ctx.onProgress({ type: "backdrop-cached", beatId: beat.id, path: rel });
51232
51286
  return { status: "cached", path: rel };
51233
51287
  }
@@ -51265,7 +51319,7 @@ function loadSceneBuildEnv(projectDir) {
51265
51319
  (0, import_dotenv3.config)({ path: resolve46(process.cwd(), ".env"), quiet: true });
51266
51320
  let dir = process.cwd();
51267
51321
  while (dir !== dirname17(dir)) {
51268
- if (existsSync33(join12(dir, "pnpm-workspace.yaml"))) {
51322
+ if (existsSync34(join12(dir, "pnpm-workspace.yaml"))) {
51269
51323
  (0, import_dotenv3.config)({ path: join12(dir, ".env"), quiet: true });
51270
51324
  return;
51271
51325
  }
@@ -51296,7 +51350,7 @@ function resolveSceneBuildMode(opts) {
51296
51350
  }
51297
51351
  async function syncRootClipReferences(beats, projectDir, outcomes) {
51298
51352
  const rootPath = join12(projectDir, "index.html");
51299
- if (!existsSync33(rootPath)) return;
51353
+ if (!existsSync34(rootPath)) return;
51300
51354
  const html = await readFile33(rootPath, "utf-8");
51301
51355
  let cursor = 0;
51302
51356
  const clipLines = [];
@@ -51747,14 +51801,14 @@ __export(ai_audio_exports, {
51747
51801
  });
51748
51802
  import { resolve as resolve53, dirname as dirname19, basename as basename17, extname as extname12 } from "node:path";
51749
51803
  import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
51750
- import { existsSync as existsSync36 } from "node:fs";
51804
+ import { existsSync as existsSync37 } from "node:fs";
51751
51805
  async function executeTranscribe(options) {
51752
51806
  const { audioPath, language, output, format, apiKey } = options;
51753
51807
  try {
51754
51808
  const key = apiKey || process.env.OPENAI_API_KEY;
51755
51809
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
51756
51810
  const absPath = resolve53(process.cwd(), audioPath);
51757
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}` };
51811
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
51758
51812
  const whisper = new WhisperProvider();
51759
51813
  await whisper.initialize({ apiKey: key });
51760
51814
  const audioBuffer = await readFile36(absPath);
@@ -51787,7 +51841,7 @@ async function executeIsolate(options) {
51787
51841
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
51788
51842
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
51789
51843
  const absPath = resolve53(process.cwd(), audioPath);
51790
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}` };
51844
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
51791
51845
  const audioBuffer = await readFile36(absPath);
51792
51846
  const elevenlabs = new ElevenLabsProvider();
51793
51847
  await elevenlabs.initialize({ apiKey: key });
@@ -51813,7 +51867,7 @@ async function executeVoiceClone(options) {
51813
51867
  const audioBuffers = [];
51814
51868
  for (const samplePath of samplePaths) {
51815
51869
  const absPath = resolve53(process.cwd(), samplePath);
51816
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${samplePath}` };
51870
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${samplePath}` };
51817
51871
  const buffer = await readFile36(absPath);
51818
51872
  audioBuffers.push(buffer);
51819
51873
  }
@@ -51835,7 +51889,7 @@ async function executeDub(options) {
51835
51889
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
51836
51890
  try {
51837
51891
  const absPath = resolve53(process.cwd(), mediaPath);
51838
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}` };
51892
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
51839
51893
  const openaiKey = process.env.OPENAI_API_KEY;
51840
51894
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
51841
51895
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -51963,8 +52017,8 @@ async function executeDuck(options) {
51963
52017
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
51964
52018
  const absMusicPath = resolve53(process.cwd(), musicPath);
51965
52019
  const absVoicePath = resolve53(process.cwd(), voicePath);
51966
- if (!existsSync36(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
51967
- if (!existsSync36(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
52020
+ if (!existsSync37(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
52021
+ if (!existsSync37(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
51968
52022
  const defaultOutput = resolve53(dirname19(absMusicPath), `${basename17(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
51969
52023
  const outputPath = resolve53(process.cwd(), output || defaultOutput);
51970
52024
  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`;
@@ -51978,7 +52032,7 @@ async function executeDuck(options) {
51978
52032
  "-y",
51979
52033
  outputPath
51980
52034
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
51981
- if (!existsSync36(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
52035
+ if (!existsSync37(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
51982
52036
  return { success: true, outputPath };
51983
52037
  } catch (error) {
51984
52038
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -51996,7 +52050,7 @@ var init_ai_audio = __esm({
51996
52050
  // src/pipeline/executor.ts
51997
52051
  import { resolve as resolve54 } from "node:path";
51998
52052
  import { readFile as readFile37, writeFile as writeFile41, mkdir as mkdir21 } from "node:fs/promises";
51999
- import { existsSync as existsSync37 } from "node:fs";
52053
+ import { existsSync as existsSync38 } from "node:fs";
52000
52054
  function maxCostFor(action) {
52001
52055
  const cmd = ACTION_METADATA[action]?.command;
52002
52056
  if (!cmd) return 0;
@@ -52213,7 +52267,7 @@ async function ensureActionsRegistered() {
52213
52267
  }
52214
52268
  async function loadPipeline(filePath) {
52215
52269
  const absPath = resolve54(process.cwd(), filePath);
52216
- if (!existsSync37(absPath)) {
52270
+ if (!existsSync38(absPath)) {
52217
52271
  throw new Error(`Pipeline file not found: ${absPath}`);
52218
52272
  }
52219
52273
  const content = await readFile37(absPath, "utf-8");
@@ -52238,7 +52292,7 @@ async function executePipeline(manifest2, options = {}) {
52238
52292
  const startTime = Date.now();
52239
52293
  if (options.resume) {
52240
52294
  const checkpointPath = resolve54(outputDir, CHECKPOINT_FILE);
52241
- if (existsSync37(checkpointPath)) {
52295
+ if (existsSync38(checkpointPath)) {
52242
52296
  const checkpointContent = await readFile37(checkpointPath, "utf-8");
52243
52297
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
52244
52298
  for (const cs of checkpoint.completedSteps) {
@@ -59644,7 +59698,7 @@ __export(project_builder_exports, {
59644
59698
  resolveSourceUrl: () => resolveSourceUrl
59645
59699
  });
59646
59700
  import { mkdtemp, mkdir as mkdir22, copyFile as copyFile3, writeFile as writeFile46, rm as rm4, readdir as readdir3 } from "node:fs/promises";
59647
- import { existsSync as existsSync38 } from "node:fs";
59701
+ import { existsSync as existsSync39 } from "node:fs";
59648
59702
  import { tmpdir as tmpdir4 } from "node:os";
59649
59703
  import { createRequire } from "node:module";
59650
59704
  import * as path from "node:path";
@@ -59656,7 +59710,7 @@ async function buildTempProject(state, projectFileDir) {
59656
59710
  for (const source of state.sources) {
59657
59711
  if (copied.has(source.id)) continue;
59658
59712
  const resolved = resolveSourceUrl(source.url, projectFileDir);
59659
- if (!existsSync38(resolved)) {
59713
+ if (!existsSync39(resolved)) {
59660
59714
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
59661
59715
  }
59662
59716
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -60494,11 +60548,11 @@ var init_export = __esm({
60494
60548
  ).option("--overwrite", "Overwrite output file if exists", false).option("--gap-fill <strategy>", "Gap filling strategy (black, extend)", "extend").option("--backend <name>", "Render backend: ffmpeg (default) | hyperframes (experimental)", "ffmpeg").option("--bitrate <value>", "Video bitrate (e.g. 5000k, 8M) \u2014 overrides preset").option("--fps <number>", "Frames per second (e.g. 24, 30, 60) \u2014 overrides preset").option("--resolution <WxH>", "Output resolution (e.g. 1920x1080) \u2014 overrides preset").option("--codec <codec>", "Video codec: h264 (default) | h265 | vp9 \u2014 overrides preset").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
60495
60549
  Examples:
60496
60550
  $ vibe export project.vibe.json -o output.mp4
60497
- $ vibe export project.vibe.json -o output.mp4 -p high -y
60498
- $ vibe export project.vibe.json -o output.webm -f webm
60499
- $ vibe export project.vibe.json -o output.gif -f gif
60551
+ $ vibe export project.vibe.json -o output.mp4 --preset high --overwrite
60552
+ $ vibe export project.vibe.json -o output.webm --format webm
60553
+ $ vibe export project.vibe.json -o output.gif --format gif
60500
60554
  $ vibe export project.vibe.json -o out.mp4 --bitrate 5000k --fps 24 --codec h265
60501
- $ vibe export project.vibe.json -o out.mp4 -p high --fps 60
60555
+ $ vibe export project.vibe.json -o out.mp4 --preset high --fps 60
60502
60556
 
60503
60557
  Cost: Free (no API keys needed). Requires FFmpeg.
60504
60558
  GIF format: 15fps, no audio, looping. Good for previews and sharing.
@@ -61634,28 +61688,31 @@ narration / backdrop intent.
61634
61688
  ## Quick-draft path
61635
61689
 
61636
61690
  \`\`\`bash
61637
- vibe scene init my-promo -r 16:9 -d 30
61691
+ vibe init my-promo -r 16:9 -d 30
61638
61692
  vibe scene add intro --style announcement \\
61639
61693
  --headline "Ship videos, not clicks"
61640
61694
  vibe scene lint
61641
61695
  vibe render my-promo
61642
61696
  \`\`\`
61643
61697
 
61644
- \`vibe scene init\` is **idempotent** \u2014 running it on an existing
61645
- Hyperframes directory merges \`hyperframes.json\` instead of clobbering it.
61646
- Safe to invoke on user-provided projects.
61698
+ \`vibe init\` is **idempotent** \u2014 running it on an existing Hyperframes
61699
+ directory merges \`hyperframes.json\` instead of clobbering it. Safe to
61700
+ invoke on user-provided projects.
61647
61701
 
61648
61702
  ## Subcommands
61649
61703
 
61650
61704
  \`\`\`bash
61651
- vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61705
+ # Project flow (top-level \u2014 preferred entry points)
61706
+ vibe init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61707
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
61708
+ vibe render [<dir>] [--fps 30] [--quality standard] [--format mp4]
61709
+
61710
+ # Lower-level scene authoring
61652
61711
  vibe scene list-styles [<name>] # list / show vendored visual identities
61653
61712
  vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
61654
61713
  vibe scene add <name> --style <preset> [...]
61655
61714
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
61656
61715
  vibe scene lint [<root>] [--json] [--fix]
61657
- vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
61658
- vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
61659
61716
  \`\`\`
61660
61717
 
61661
61718
  ## Style presets (for \`vibe scene add --style\`)
@@ -61709,7 +61766,7 @@ and surface the error to the user.
61709
61766
  | Generate narration + image, then author scene | \`vibe scene add\` |
61710
61767
  | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
61711
61768
  | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
61712
- | Render the project | \`vibe render\` *or* \`vibe scene render\` for lower-level control |
61769
+ | Render the project | \`vibe render\` (one canonical entry point) |
61713
61770
  | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
61714
61771
 
61715
61772
  The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
@@ -61837,12 +61894,12 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
61837
61894
  ],
61838
61895
  relatedCommands: [
61839
61896
  "vibe init",
61897
+ "vibe build",
61898
+ "vibe render",
61840
61899
  "vibe scene list-styles",
61841
61900
  "vibe scene install-skill",
61842
61901
  "vibe scene compose-prompts",
61843
- "vibe build",
61844
61902
  "vibe scene lint",
61845
- "vibe scene render",
61846
61903
  "vibe scene add"
61847
61904
  ]
61848
61905
  },
@@ -62166,7 +62223,7 @@ init_source();
62166
62223
  // package.json
62167
62224
  var package_default = {
62168
62225
  name: "@vibeframe/cli",
62169
- version: "0.78.0",
62226
+ version: "0.79.1",
62170
62227
  description: "VibeFrame CLI - natural language video editing from the terminal",
62171
62228
  private: false,
62172
62229
  license: "MIT",
@@ -62578,6 +62635,7 @@ init_engine();
62578
62635
  init_validate();
62579
62636
  init_output();
62580
62637
  import { readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
62638
+ import { existsSync as existsSync8 } from "node:fs";
62581
62639
  import { resolve as resolve11, basename as basename2, extname } from "node:path";
62582
62640
  var timelineCommand = new Command("timeline").description("Timeline editing commands").addHelpText("after", `
62583
62641
  Examples:
@@ -62928,6 +62986,9 @@ timelineCommand.command("list").description("List timeline contents").argument("
62928
62986
  const startedAt = Date.now();
62929
62987
  try {
62930
62988
  const filePath = resolve11(process.cwd(), projectPath);
62989
+ if (!existsSync8(filePath)) {
62990
+ exitWithError(notFoundError(projectPath));
62991
+ }
62931
62992
  const content = await readFile9(filePath, "utf-8");
62932
62993
  const data = JSON.parse(content);
62933
62994
  const project = Project.fromJSON(data);
@@ -63548,16 +63609,16 @@ init_output();
63548
63609
  init_validate();
63549
63610
  import { resolve as resolve33, dirname as dirname11, basename as basename7, extname as extname7 } from "node:path";
63550
63611
  import { readFile as readFile24, writeFile as writeFile31 } from "node:fs/promises";
63551
- import { existsSync as existsSync25 } from "node:fs";
63612
+ import { existsSync as existsSync26 } from "node:fs";
63552
63613
  var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
63553
63614
  "after",
63554
63615
  `
63555
63616
  Examples:
63556
- $ vibe audio transcribe interview.mp3 -o transcript.srt -f srt
63617
+ $ vibe audio transcribe interview.mp3 -o transcript.srt --format srt
63557
63618
  $ vibe audio transcribe video.mp4 -l ko # Specify language
63558
63619
  $ vibe audio list-voices # List available voices
63559
63620
  $ vibe audio isolate song.mp3 -o vocals.mp3
63560
- $ vibe audio clone-voice sample.mp3 -n "my-voice"
63621
+ $ vibe audio clone-voice sample.mp3 --name "my-voice"
63561
63622
  $ vibe audio dub video.mp4 -l ko -o dubbed.mp4
63562
63623
  $ vibe audio duck music.mp3 --voice narration.mp3 -o ducked.mp3
63563
63624
 
@@ -63752,7 +63813,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
63752
63813
  const audioBuffers = [];
63753
63814
  for (const samplePath of samples) {
63754
63815
  const absPath = resolve33(process.cwd(), samplePath);
63755
- if (!existsSync25(absPath)) {
63816
+ if (!existsSync26(absPath)) {
63756
63817
  spinner2.fail(`File not found: ${samplePath}`);
63757
63818
  exitWithError(notFoundError(samplePath));
63758
63819
  }
@@ -63813,7 +63874,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
63813
63874
  exitWithError(usageError("Target language is required. Use -l or --language"));
63814
63875
  }
63815
63876
  const absPath = resolve33(process.cwd(), mediaPath);
63816
- if (!existsSync25(absPath)) {
63877
+ if (!existsSync26(absPath)) {
63817
63878
  exitWithError(notFoundError(mediaPath));
63818
63879
  }
63819
63880
  const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
@@ -64056,7 +64117,7 @@ init_esm();
64056
64117
  init_source();
64057
64118
  init_ora();
64058
64119
  import { resolve as resolve37, basename as basename10 } from "node:path";
64059
- import { existsSync as existsSync29 } from "node:fs";
64120
+ import { existsSync as existsSync30 } from "node:fs";
64060
64121
 
64061
64122
  // src/commands/ai-script-pipeline-cli.ts
64062
64123
  init_source();
@@ -64067,19 +64128,19 @@ init_ai_script_pipeline();
64067
64128
  init_output();
64068
64129
  import { readFile as readFile25, writeFile as writeFile32 } from "node:fs/promises";
64069
64130
  import { resolve as resolve34 } from "node:path";
64070
- import { existsSync as existsSync26 } from "node:fs";
64131
+ import { existsSync as existsSync27 } from "node:fs";
64071
64132
  function registerScriptPipelineCommands(aiCommand) {
64072
64133
  aiCommand.command("regenerate-scene").description("Regenerate a specific scene in a script-to-video output directory").argument("<project-dir>", "Path to the script-to-video output directory").requiredOption("--scene <numbers>", "Scene number(s) to regenerate (1-based), e.g., 3 or 3,4,5").option("--video-only", "Only regenerate video").option("--narration-only", "Only regenerate narration").option("--image-only", "Only regenerate image").option("--generator <engine>", "Video generator: grok | kling | runway | veo", "grok").option("-i, --image-provider <provider>", "Image provider: gemini | openai | grok", "gemini").option("--voice <id>", "ElevenLabs voice ID for narration").option("-a, --aspect-ratio <ratio>", "Aspect ratio: 16:9 | 9:16 | 1:1", "16:9").option("--retries <count>", "Number of retries for video generation failures", String(DEFAULT_VIDEO_RETRIES)).option("--reference-scene <num>", "Use another scene's image as reference for character consistency").option("--dry-run", "Preview parameters without executing").action(async (projectDir, options) => {
64073
64134
  const startedAt = Date.now();
64074
64135
  try {
64075
64136
  const outputDir = resolve34(process.cwd(), projectDir);
64076
64137
  const projectPath = resolve34(outputDir, "project.vibe.json");
64077
- if (!existsSync26(outputDir)) {
64138
+ if (!existsSync27(outputDir)) {
64078
64139
  exitWithError(notFoundError(outputDir));
64079
64140
  }
64080
64141
  const yamlPath = resolve34(outputDir, "storyboard.yaml");
64081
64142
  const jsonPath = resolve34(outputDir, "storyboard.json");
64082
- const storyboardPath = existsSync26(yamlPath) ? yamlPath : existsSync26(jsonPath) ? jsonPath : null;
64143
+ const storyboardPath = existsSync27(yamlPath) ? yamlPath : existsSync27(jsonPath) ? jsonPath : null;
64083
64144
  const storyboardIsYaml = storyboardPath === yamlPath;
64084
64145
  if (!storyboardPath) {
64085
64146
  exitWithError(notFoundError(`${outputDir}/storyboard.{yaml,json}`));
@@ -64177,7 +64238,7 @@ function registerScriptPipelineCommands(aiCommand) {
64177
64238
  exitWithError(apiError(result.error || "Scene regeneration failed", true));
64178
64239
  }
64179
64240
  spinner2.succeed(source_default.green(`Regenerated ${result.regeneratedScenes.length} scene(s)`));
64180
- if (existsSync26(projectPath) && result.regeneratedScenes.length > 0) {
64241
+ if (existsSync27(projectPath) && result.regeneratedScenes.length > 0) {
64181
64242
  const updateSpinner = ora("\u{1F4E6} Updating project file...").start();
64182
64243
  try {
64183
64244
  const updatedContent = await readFile25(storyboardPath, "utf-8");
@@ -64191,7 +64252,7 @@ function registerScriptPipelineCommands(aiCommand) {
64191
64252
  const imagePath = resolve34(outputDir, `scene-${sceneNum}.png`);
64192
64253
  const videoSource = projectData.state.sources.find((s) => s.name === `Scene ${sceneNum}`);
64193
64254
  if (videoSource) {
64194
- const hasVideo = existsSync26(videoPath);
64255
+ const hasVideo = existsSync27(videoPath);
64195
64256
  videoSource.url = hasVideo ? videoPath : imagePath;
64196
64257
  videoSource.type = hasVideo ? "video" : "image";
64197
64258
  videoSource.duration = segment.duration;
@@ -64255,9 +64316,9 @@ Two flows \u2014 pick by intent:
64255
64316
 
64256
64317
  Examples (REMIX):
64257
64318
  $ vibe remix highlights long-video.mp4 -o highlights.json -d 60
64258
- $ vibe remix auto-shorts long-video.mp4 -o shorts/ -n 3 --add-captions
64259
- $ vibe remix animated-caption video.mp4 -o captioned.mp4 -s highlight
64260
- $ vibe remix animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64319
+ $ vibe remix auto-shorts long-video.mp4 -o shorts/ --count 3 --add-captions
64320
+ $ vibe remix animated-caption video.mp4 -o captioned.mp4 --style highlight
64321
+ $ vibe remix animated-caption video.mp4 -o out.mp4 --style karaoke-sweep --fast
64261
64322
 
64262
64323
  Provider API Keys:
64263
64324
  highlights: GOOGLE_API_KEY (Gemini analysis)
@@ -64282,8 +64343,8 @@ pipelineCommand.command("animated-caption").description("Add animated captions w
64282
64343
  `
64283
64344
  Examples:
64284
64345
  $ vibe remix animated-caption video.mp4 -o captioned.mp4
64285
- $ vibe remix animated-caption video.mp4 -o out.mp4 -s bounce
64286
- $ vibe remix animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64346
+ $ vibe remix animated-caption video.mp4 -o out.mp4 --style bounce
64347
+ $ vibe remix animated-caption video.mp4 -o out.mp4 --style karaoke-sweep --fast
64287
64348
 
64288
64349
  Styles:
64289
64350
  highlight (default) TikTok-style background highlight on active word (Remotion)
@@ -64299,7 +64360,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
64299
64360
  const startedAt = Date.now();
64300
64361
  try {
64301
64362
  const absVideoPath = resolve37(process.cwd(), videoPath);
64302
- if (!existsSync29(absVideoPath)) {
64363
+ if (!existsSync30(absVideoPath)) {
64303
64364
  exitWithError(notFoundError(absVideoPath));
64304
64365
  }
64305
64366
  if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
@@ -69400,7 +69461,7 @@ init_agent_host_detect();
69400
69461
  init_scene_project();
69401
69462
  init_visual_styles();
69402
69463
  init_install_skill();
69403
- import { existsSync as existsSync30 } from "node:fs";
69464
+ import { existsSync as existsSync31 } from "node:fs";
69404
69465
  import { mkdir as mkdir17, readFile as readFile30, writeFile as writeFile37 } from "node:fs/promises";
69405
69466
  import { basename as basename14, dirname as dirname14, resolve as resolve42 } from "node:path";
69406
69467
 
@@ -69691,7 +69752,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
69691
69752
  const wantsGemini = targetHosts.includes("gemini-cli");
69692
69753
  const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
69693
69754
  const actions = [];
69694
- if (!existsSync30(projectDir)) {
69755
+ if (!existsSync31(projectDir)) {
69695
69756
  if (options.dryRun) {
69696
69757
  actions.push({ path: projectDir, status: "would-write", reason: "(would create directory)" });
69697
69758
  } else {
@@ -69937,7 +69998,7 @@ function resolveTargets(agent) {
69937
69998
  return [agent];
69938
69999
  }
69939
70000
  async function writeIfMissing(absPath, content, force, dryRun) {
69940
- const exists = existsSync30(absPath);
70001
+ const exists = existsSync31(absPath);
69941
70002
  if (exists && !force) {
69942
70003
  return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
69943
70004
  }
@@ -69950,7 +70011,7 @@ async function writeIfMissing(absPath, content, force, dryRun) {
69950
70011
  }
69951
70012
  async function mergeGitignore(absPath, additions, dryRun) {
69952
70013
  const marker = "# VibeFrame";
69953
- if (!existsSync30(absPath)) {
70014
+ if (!existsSync31(absPath)) {
69954
70015
  if (dryRun) return { path: absPath, status: "would-write" };
69955
70016
  await writeFile37(absPath, additions.trimStart(), "utf-8");
69956
70017
  return { path: absPath, status: "wrote" };
@@ -70293,7 +70354,7 @@ init_composer_resolve();
70293
70354
  init_scene_build();
70294
70355
  init_output();
70295
70356
  import { access as access6 } from "node:fs/promises";
70296
- import { existsSync as existsSync34 } from "node:fs";
70357
+ import { existsSync as existsSync35 } from "node:fs";
70297
70358
  import { resolve as resolve49 } from "node:path";
70298
70359
  var COMMAND_KEY_MAP = getCommandKeyMap();
70299
70360
  var FREE_COMMANDS = [
@@ -70335,21 +70396,21 @@ var REQUIRED_FFMPEG_FILTERS = {
70335
70396
  drawtext: {
70336
70397
  commands: ["edit text-overlay", "edit caption"],
70337
70398
  fix: {
70338
- darwin: "brew uninstall ffmpeg && brew install ffmpeg",
70399
+ darwin: "brew tap homebrew-ffmpeg/ffmpeg && brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-freetype (Homebrew core ffmpeg lacks libfreetype)",
70339
70400
  linux: "sudo apt install ffmpeg (or rebuild with --enable-libfreetype)"
70340
70401
  }
70341
70402
  },
70342
70403
  subtitles: {
70343
70404
  commands: ["edit caption"],
70344
70405
  fix: {
70345
- darwin: "brew uninstall ffmpeg && brew install ffmpeg",
70406
+ darwin: "brew tap homebrew-ffmpeg/ffmpeg && brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-libass (Homebrew core ffmpeg lacks libass)",
70346
70407
  linux: "sudo apt install ffmpeg (or rebuild with --enable-libass)"
70347
70408
  }
70348
70409
  },
70349
70410
  afftdn: {
70350
70411
  commands: ["edit noise-reduce"],
70351
70412
  fix: {
70352
- darwin: "brew uninstall ffmpeg && brew install ffmpeg",
70413
+ darwin: "brew install ffmpeg (afftdn ships in the default Homebrew formula; a reinstall is rarely needed)",
70353
70414
  linux: "sudo apt install ffmpeg (or rebuild with --enable-libfftw3)"
70354
70415
  }
70355
70416
  }
@@ -70407,7 +70468,7 @@ async function runDiagnostics() {
70407
70468
  installed = false;
70408
70469
  }
70409
70470
  } else if (toolName === "chrome") {
70410
- const { existsSync: existsSync39 } = await import("node:fs");
70471
+ const { existsSync: existsSync40 } = await import("node:fs");
70411
70472
  const { homedir: homedir6 } = await import("node:os");
70412
70473
  const { join: join16 } = await import("node:path");
70413
70474
  const chromePaths = [
@@ -70420,7 +70481,7 @@ async function runDiagnostics() {
70420
70481
  "/usr/bin/chromium",
70421
70482
  "/usr/bin/chromium-browser"
70422
70483
  ];
70423
- installed = chromePaths.some((p) => p && existsSync39(p));
70484
+ installed = chromePaths.some((p) => p && existsSync40(p));
70424
70485
  } else {
70425
70486
  installed = commandExists(toolName);
70426
70487
  }
@@ -70451,7 +70512,7 @@ async function runDiagnostics() {
70451
70512
  const cwd = process.cwd();
70452
70513
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
70453
70514
  path: rel,
70454
- exists: existsSync34(resolve49(cwd, rel))
70515
+ exists: existsSync35(resolve49(cwd, rel))
70455
70516
  }));
70456
70517
  const projectInitialized = projectFiles.some((f) => f.exists);
70457
70518
  const hosts = detectAgentHosts();
@@ -70466,8 +70527,8 @@ async function runDiagnostics() {
70466
70527
  } catch (err) {
70467
70528
  if (!(err instanceof ComposerResolveError)) throw err;
70468
70529
  }
70469
- const sceneProjectInCwd = existsSync34(resolve49(cwd, "STORYBOARD.md"));
70470
- const skillInstalled = existsSync34(resolve49(cwd, "SKILL.md"));
70530
+ const sceneProjectInCwd = existsSync35(resolve49(cwd, "STORYBOARD.md"));
70531
+ const skillInstalled = existsSync35(resolve49(cwd, "SKILL.md"));
70471
70532
  return {
70472
70533
  system: {
70473
70534
  node: { version: nodeVersion, ok: true },
@@ -70633,7 +70694,7 @@ init_detect();
70633
70694
  init_ai_edit();
70634
70695
  init_output();
70635
70696
  import { resolve as resolve50 } from "node:path";
70636
- import { existsSync as existsSync35 } from "node:fs";
70697
+ import { existsSync as existsSync36 } from "node:fs";
70637
70698
  import { mkdir as mkdir20, rm as rm3 } from "node:fs/promises";
70638
70699
  var DEMO_DIR = resolve50(process.cwd(), ".vibeframe-demo");
70639
70700
  async function generateTestVideo(outputPath) {
@@ -70664,7 +70725,7 @@ async function generateTestVideo(outputPath) {
70664
70725
  "-y",
70665
70726
  outputPath
70666
70727
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
70667
- return existsSync35(outputPath);
70728
+ return existsSync36(outputPath);
70668
70729
  } catch {
70669
70730
  try {
70670
70731
  await execSafe("ffmpeg", [
@@ -70690,7 +70751,7 @@ async function generateTestVideo(outputPath) {
70690
70751
  "-y",
70691
70752
  outputPath
70692
70753
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
70693
- return existsSync35(outputPath);
70754
+ return existsSync36(outputPath);
70694
70755
  } catch {
70695
70756
  return false;
70696
70757
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.78.0",
3
+ "version": "0.79.1",
4
4
  "description": "VibeFrame CLI - natural language video editing from the terminal",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -182,8 +182,8 @@
182
182
  "tsx": "^4.21.0",
183
183
  "typescript": "^5.3.3",
184
184
  "vitest": "^1.2.2",
185
- "@vibeframe/core": "0.78.0",
186
- "@vibeframe/ai-providers": "0.78.0"
185
+ "@vibeframe/ai-providers": "0.79.1",
186
+ "@vibeframe/core": "0.79.1"
187
187
  },
188
188
  "engines": {
189
189
  "node": ">=20"