@vibeframe/cli 0.78.0 → 0.79.0

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 +168 -143
  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");
@@ -31778,7 +31778,7 @@ var init_speech = __esm({
31778
31778
  // src/commands/generate/music.ts
31779
31779
  import { resolve as resolve17 } from "node:path";
31780
31780
  import { writeFile as writeFile16 } from "node:fs/promises";
31781
- import { existsSync as existsSync10 } from "node:fs";
31781
+ import { existsSync as existsSync11 } from "node:fs";
31782
31782
  async function executeMusic(options) {
31783
31783
  try {
31784
31784
  const provider = options.provider || "elevenlabs";
@@ -31835,7 +31835,7 @@ async function executeMusic(options) {
31835
31835
  }
31836
31836
  }
31837
31837
  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) => {
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("-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
31839
  const startedAt = Date.now();
31840
31840
  try {
31841
31841
  rejectControlChars(prompt3);
@@ -31913,7 +31913,7 @@ function registerMusicCommand(parent) {
31913
31913
  if (options.melody) {
31914
31914
  spinner2.text = "Uploading melody reference...";
31915
31915
  const absPath = resolve17(process.cwd(), options.melody);
31916
- if (!existsSync10(absPath)) {
31916
+ if (!existsSync11(absPath)) {
31917
31917
  spinner2.fail(`Melody file not found: ${options.melody}`);
31918
31918
  exitWithError(notFoundError(options.melody));
31919
31919
  }
@@ -32004,7 +32004,7 @@ __export(ai_image_exports, {
32004
32004
  });
32005
32005
  import { resolve as resolve18, dirname as dirname5 } from "node:path";
32006
32006
  import { readFile as readFile12, writeFile as writeFile17, mkdir as mkdir8 } from "node:fs/promises";
32007
- import { existsSync as existsSync11 } from "node:fs";
32007
+ import { existsSync as existsSync12 } from "node:fs";
32008
32008
  async function executeImageGenerate(options) {
32009
32009
  const {
32010
32010
  prompt: prompt3,
@@ -32148,7 +32148,7 @@ async function executeGeminiEdit(options) {
32148
32148
  const imageBuffers = [];
32149
32149
  for (const imagePath of imagePaths) {
32150
32150
  const absPath = resolve18(process.cwd(), imagePath);
32151
- if (!existsSync11(absPath)) {
32151
+ if (!existsSync12(absPath)) {
32152
32152
  return { success: false, error: `Image not found: ${absPath}` };
32153
32153
  }
32154
32154
  const buffer = await readFile12(absPath);
@@ -32196,7 +32196,7 @@ async function executeThumbnailBestFrame(options) {
32196
32196
  model = "flash",
32197
32197
  apiKey
32198
32198
  } = options;
32199
- if (!existsSync11(videoPath)) {
32199
+ if (!existsSync12(videoPath)) {
32200
32200
  return { success: false, error: `Video not found: ${videoPath}` };
32201
32201
  }
32202
32202
  if (!commandExists("ffmpeg")) {
@@ -32236,7 +32236,7 @@ async function executeThumbnailBestFrame(options) {
32236
32236
  reason = reasonMatch[1];
32237
32237
  }
32238
32238
  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)) {
32239
+ if (!existsSync12(outputPath)) {
32240
32240
  return { success: false, error: "FFmpeg failed to extract frame" };
32241
32241
  }
32242
32242
  return {
@@ -32262,7 +32262,7 @@ var init_ai_image = __esm({
32262
32262
 
32263
32263
  // src/commands/generate/thumbnail.ts
32264
32264
  import { resolve as resolve19, dirname as dirname6, basename as basename3, extname as extname2 } from "node:path";
32265
- import { existsSync as existsSync12 } from "node:fs";
32265
+ import { existsSync as existsSync13 } from "node:fs";
32266
32266
  import { writeFile as writeFile18, mkdir as mkdir9 } from "node:fs/promises";
32267
32267
  function registerThumbnailCommand(parent) {
32268
32268
  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 +32274,7 @@ function registerThumbnailCommand(parent) {
32274
32274
  }
32275
32275
  if (options.bestFrame) {
32276
32276
  const absVideoPath = resolve19(process.cwd(), options.bestFrame);
32277
- if (!existsSync12(absVideoPath)) {
32277
+ if (!existsSync13(absVideoPath)) {
32278
32278
  exitWithError(notFoundError(absVideoPath));
32279
32279
  }
32280
32280
  if (!commandExists("ffmpeg")) {
@@ -32975,6 +32975,15 @@ Examples:
32975
32975
  if (options.output) {
32976
32976
  validateOutputPath(options.output);
32977
32977
  }
32978
+ if (options.count !== void 0) {
32979
+ const n = parseInt(options.count, 10);
32980
+ if (!Number.isFinite(n) || n < 1 || n > 10) {
32981
+ exitWithError(usageError(
32982
+ `Invalid --count: ${options.count}`,
32983
+ "Must be an integer between 1 and 10."
32984
+ ));
32985
+ }
32986
+ }
32978
32987
  const imageRegistry = getProvidersFor("image");
32979
32988
  const validProviders = [...imageRegistry.map((p) => p.name), "runway"];
32980
32989
  const providerEnvMap = Object.fromEntries(
@@ -34654,7 +34663,7 @@ var init_video_providers = __esm({
34654
34663
  // src/commands/ai-script-pipeline.ts
34655
34664
  import { readFile as readFile13, writeFile as writeFile23, unlink as unlink2, rename as rename2 } from "node:fs/promises";
34656
34665
  import { resolve as resolve24, extname as extname3 } from "node:path";
34657
- import { existsSync as existsSync13 } from "node:fs";
34666
+ import { existsSync as existsSync14 } from "node:fs";
34658
34667
  async function executeRegenerateScene(options) {
34659
34668
  const result = {
34660
34669
  success: false,
@@ -34663,12 +34672,12 @@ async function executeRegenerateScene(options) {
34663
34672
  };
34664
34673
  try {
34665
34674
  const outputDir = resolve24(process.cwd(), options.projectDir);
34666
- if (!existsSync13(outputDir)) {
34675
+ if (!existsSync14(outputDir)) {
34667
34676
  return { ...result, error: `Project directory not found: ${outputDir}` };
34668
34677
  }
34669
34678
  const yamlPath = resolve24(outputDir, "storyboard.yaml");
34670
34679
  const jsonPath = resolve24(outputDir, "storyboard.json");
34671
- const storyboardPath = existsSync13(yamlPath) ? yamlPath : existsSync13(jsonPath) ? jsonPath : null;
34680
+ const storyboardPath = existsSync14(yamlPath) ? yamlPath : existsSync14(jsonPath) ? jsonPath : null;
34672
34681
  if (!storyboardPath) {
34673
34682
  return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
34674
34683
  }
@@ -34761,14 +34770,14 @@ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
34761
34770
  const refSceneNum = options.referenceScene;
34762
34771
  if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
34763
34772
  const refImagePath = resolve24(outputDir, `scene-${refSceneNum}.png`);
34764
- if (existsSync13(refImagePath)) {
34773
+ if (existsSync14(refImagePath)) {
34765
34774
  referenceImageBuffer = await readFile13(refImagePath);
34766
34775
  }
34767
34776
  } else if (!refSceneNum) {
34768
34777
  for (let i = 1; i <= segments.length; i++) {
34769
34778
  if (i !== sceneNum) {
34770
34779
  const otherImagePath = resolve24(outputDir, `scene-${i}.png`);
34771
- if (existsSync13(otherImagePath)) {
34780
+ if (existsSync14(otherImagePath)) {
34772
34781
  referenceImageBuffer = await readFile13(otherImagePath);
34773
34782
  break;
34774
34783
  }
@@ -34857,7 +34866,7 @@ Generate the single-person scene image now.`;
34857
34866
  }
34858
34867
  if (regenerateVideo && videoApiKey) {
34859
34868
  options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
34860
- if (!existsSync13(imagePath)) {
34869
+ if (!existsSync14(imagePath)) {
34861
34870
  result.failedScenes.push(sceneNum);
34862
34871
  continue;
34863
34872
  }
@@ -35119,6 +35128,15 @@ Examples:
35119
35128
  if (options.output) {
35120
35129
  validateOutputPath(options.output);
35121
35130
  }
35131
+ if (options.duration !== void 0) {
35132
+ const d = parseFloat(options.duration);
35133
+ if (!Number.isFinite(d) || d <= 0 || d > 60) {
35134
+ exitWithError(usageError(
35135
+ `Invalid --duration: ${options.duration}`,
35136
+ "Must be a positive number \u2264 60 seconds."
35137
+ ));
35138
+ }
35139
+ }
35122
35140
  const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
35123
35141
  const videoEnvMap = {
35124
35142
  grok: "XAI_API_KEY",
@@ -35631,7 +35649,7 @@ Run 'vibe schema generate.<command>' for structured parameter info.
35631
35649
  });
35632
35650
 
35633
35651
  // src/commands/_shared/edit/silence-cut.ts
35634
- import { existsSync as existsSync14 } from "node:fs";
35652
+ import { existsSync as existsSync15 } from "node:fs";
35635
35653
  import { readFile as readFile15 } from "node:fs/promises";
35636
35654
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
35637
35655
  const totalDuration = await getVideoDuration(videoPath);
@@ -35763,7 +35781,7 @@ async function executeSilenceCut(options) {
35763
35781
  analyzeOnly = false,
35764
35782
  useGemini = false
35765
35783
  } = options;
35766
- if (!existsSync14(videoPath)) {
35784
+ if (!existsSync15(videoPath)) {
35767
35785
  return { success: false, error: `Video not found: ${videoPath}` };
35768
35786
  }
35769
35787
  if (!commandExists("ffmpeg")) {
@@ -35871,7 +35889,7 @@ var init_silence_cut = __esm({
35871
35889
  });
35872
35890
 
35873
35891
  // src/commands/_shared/edit/jump-cut.ts
35874
- import { existsSync as existsSync15 } from "node:fs";
35892
+ import { existsSync as existsSync16 } from "node:fs";
35875
35893
  import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir11 } from "node:fs/promises";
35876
35894
  import { join as join8 } from "node:path";
35877
35895
  async function transcribeWithWords(audioPath, apiKey, language) {
@@ -35934,7 +35952,7 @@ async function executeJumpCut(options) {
35934
35952
  analyzeOnly = false,
35935
35953
  apiKey
35936
35954
  } = options;
35937
- if (!existsSync15(videoPath)) {
35955
+ if (!existsSync16(videoPath)) {
35938
35956
  return { success: false, error: `Video not found: ${videoPath}` };
35939
35957
  }
35940
35958
  if (!commandExists("ffmpeg")) {
@@ -36220,7 +36238,7 @@ var init_helpers = __esm({
36220
36238
  });
36221
36239
 
36222
36240
  // src/commands/_shared/edit/caption.ts
36223
- import { existsSync as existsSync16 } from "node:fs";
36241
+ import { existsSync as existsSync17 } from "node:fs";
36224
36242
  import { readFile as readFile17, writeFile as writeFile26, mkdir as mkdir12 } from "node:fs/promises";
36225
36243
  import { dirname as dirname8, basename as basename5, extname as extname4, join as join9 } from "node:path";
36226
36244
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
@@ -36250,7 +36268,7 @@ async function executeCaption(options) {
36250
36268
  position = "bottom",
36251
36269
  apiKey
36252
36270
  } = options;
36253
- if (!existsSync16(videoPath)) {
36271
+ if (!existsSync17(videoPath)) {
36254
36272
  return { success: false, error: `Video not found: ${videoPath}` };
36255
36273
  }
36256
36274
  if (!commandExists("ffmpeg")) {
@@ -36428,10 +36446,10 @@ var init_caption = __esm({
36428
36446
  });
36429
36447
 
36430
36448
  // src/commands/_shared/edit/noise-reduce.ts
36431
- import { existsSync as existsSync17 } from "node:fs";
36449
+ import { existsSync as existsSync18 } from "node:fs";
36432
36450
  async function executeNoiseReduce(options) {
36433
36451
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
36434
- if (!existsSync17(inputPath)) {
36452
+ if (!existsSync18(inputPath)) {
36435
36453
  return { success: false, error: `File not found: ${inputPath}` };
36436
36454
  }
36437
36455
  if (!commandExists("ffmpeg")) {
@@ -36488,7 +36506,7 @@ var init_noise_reduce = __esm({
36488
36506
  });
36489
36507
 
36490
36508
  // src/commands/_shared/edit/fade.ts
36491
- import { existsSync as existsSync18 } from "node:fs";
36509
+ import { existsSync as existsSync19 } from "node:fs";
36492
36510
  async function executeFade(options) {
36493
36511
  const {
36494
36512
  videoPath,
@@ -36498,7 +36516,7 @@ async function executeFade(options) {
36498
36516
  audioOnly = false,
36499
36517
  videoOnly = false
36500
36518
  } = options;
36501
- if (!existsSync18(videoPath)) {
36519
+ if (!existsSync19(videoPath)) {
36502
36520
  return { success: false, error: `Video not found: ${videoPath}` };
36503
36521
  }
36504
36522
  if (!commandExists("ffmpeg")) {
@@ -36565,7 +36583,7 @@ var init_fade = __esm({
36565
36583
  });
36566
36584
 
36567
36585
  // src/commands/_shared/edit/translate-srt.ts
36568
- import { existsSync as existsSync19 } from "node:fs";
36586
+ import { existsSync as existsSync20 } from "node:fs";
36569
36587
  import { readFile as readFile18, writeFile as writeFile27 } from "node:fs/promises";
36570
36588
  async function executeTranslateSrt(options) {
36571
36589
  const {
@@ -36576,7 +36594,7 @@ async function executeTranslateSrt(options) {
36576
36594
  sourceLanguage,
36577
36595
  apiKey
36578
36596
  } = options;
36579
- if (!existsSync19(srtPath)) {
36597
+ if (!existsSync20(srtPath)) {
36580
36598
  return { success: false, error: `SRT file not found: ${srtPath}` };
36581
36599
  }
36582
36600
  try {
@@ -36693,7 +36711,7 @@ var init_translate_srt = __esm({
36693
36711
  });
36694
36712
 
36695
36713
  // src/commands/_shared/edit/text-overlay.ts
36696
- import { existsSync as existsSync20 } from "node:fs";
36714
+ import { existsSync as existsSync21 } from "node:fs";
36697
36715
  import { resolve as resolve26 } from "node:path";
36698
36716
  function detectSystemFont() {
36699
36717
  const platform = process.platform;
@@ -36704,7 +36722,7 @@ function detectSystemFont() {
36704
36722
  "/Library/Fonts/Arial.ttf"
36705
36723
  ];
36706
36724
  for (const f of candidates) {
36707
- if (existsSync20(f)) return f;
36725
+ if (existsSync21(f)) return f;
36708
36726
  }
36709
36727
  } else if (platform === "linux") {
36710
36728
  const candidates = [
@@ -36713,7 +36731,7 @@ function detectSystemFont() {
36713
36731
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
36714
36732
  ];
36715
36733
  for (const f of candidates) {
36716
- if (existsSync20(f)) return f;
36734
+ if (existsSync21(f)) return f;
36717
36735
  }
36718
36736
  } else if (platform === "win32") {
36719
36737
  const candidates = [
@@ -36721,7 +36739,7 @@ function detectSystemFont() {
36721
36739
  "C:\\Windows\\Fonts\\segoeui.ttf"
36722
36740
  ];
36723
36741
  for (const f of candidates) {
36724
- if (existsSync20(f)) return f;
36742
+ if (existsSync21(f)) return f;
36725
36743
  }
36726
36744
  }
36727
36745
  return null;
@@ -36742,7 +36760,7 @@ async function applyTextOverlays(options) {
36742
36760
  }
36743
36761
  const absVideoPath = resolve26(process.cwd(), videoPath);
36744
36762
  const absOutputPath = resolve26(process.cwd(), outputPath);
36745
- if (!existsSync20(absVideoPath)) {
36763
+ if (!existsSync21(absVideoPath)) {
36746
36764
  return { success: false, error: `Video not found: ${absVideoPath}` };
36747
36765
  }
36748
36766
  if (!commandExists("ffmpeg")) {
@@ -36923,12 +36941,12 @@ var init_ai_edit = __esm({
36923
36941
 
36924
36942
  // src/commands/ai-edit-cli.ts
36925
36943
  import { resolve as resolve27, extname as extname5, basename as basename6 } from "node:path";
36926
- import { existsSync as existsSync21 } from "node:fs";
36944
+ import { existsSync as existsSync22 } from "node:fs";
36927
36945
  function registerEditCommands(aiCommand) {
36928
36946
  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
36947
  Examples:
36930
36948
  $ vibe edit silence-cut interview.mp4 -o clean.mp4
36931
- $ vibe ed sc podcast.mp4 -o trimmed.mp4 -n -25 -d 1.0
36949
+ $ vibe ed sc podcast.mp4 -o trimmed.mp4 --noise -25 --min-duration 1.0
36932
36950
  $ vibe ed sc video.mp4 --use-gemini -o smart-cut.mp4 # AI-powered detection
36933
36951
  $ vibe ed sc video.mp4 --analyze-only # Detect only, no cut
36934
36952
  $ vibe ed sc video.mp4 --dry-run --json
@@ -36940,7 +36958,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
36940
36958
  validateOutputPath(options.output);
36941
36959
  }
36942
36960
  const absVideoPath = resolve27(process.cwd(), videoPath);
36943
- if (!existsSync21(absVideoPath)) {
36961
+ if (!existsSync22(absVideoPath)) {
36944
36962
  exitWithError(notFoundError(absVideoPath));
36945
36963
  }
36946
36964
  if (!commandExists("ffmpeg")) {
@@ -37029,9 +37047,9 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
37029
37047
  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
37048
  Examples:
37031
37049
  $ vibe edit caption video.mp4 -o captioned.mp4
37032
- $ vibe ed cap video.mp4 -o out.mp4 -s bold --position top
37050
+ $ vibe ed cap video.mp4 -o out.mp4 --style bold --position top
37033
37051
  $ 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
37052
+ $ vibe ed cap video.mp4 -o out.mp4 --style karaoke # Karaoke-style
37035
37053
  $ vibe ed cap video.mp4 --dry-run --json
37036
37054
 
37037
37055
  Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoPath, options) => {
@@ -37041,7 +37059,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37041
37059
  validateOutputPath(options.output);
37042
37060
  }
37043
37061
  const absVideoPath = resolve27(process.cwd(), videoPath);
37044
- if (!existsSync21(absVideoPath)) {
37062
+ if (!existsSync22(absVideoPath)) {
37045
37063
  exitWithError(notFoundError(absVideoPath));
37046
37064
  }
37047
37065
  if (!commandExists("ffmpeg")) {
@@ -37122,7 +37140,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37122
37140
  validateOutputPath(options.output);
37123
37141
  }
37124
37142
  const absInputPath = resolve27(process.cwd(), inputPath);
37125
- if (!existsSync21(absInputPath)) {
37143
+ if (!existsSync22(absInputPath)) {
37126
37144
  exitWithError(notFoundError(absInputPath));
37127
37145
  }
37128
37146
  if (!commandExists("ffmpeg")) {
@@ -37188,7 +37206,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37188
37206
  validateOutputPath(options.output);
37189
37207
  }
37190
37208
  const absVideoPath = resolve27(process.cwd(), videoPath);
37191
- if (!existsSync21(absVideoPath)) {
37209
+ if (!existsSync22(absVideoPath)) {
37192
37210
  exitWithError(notFoundError(absVideoPath));
37193
37211
  }
37194
37212
  if (!commandExists("ffmpeg")) {
@@ -37263,7 +37281,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37263
37281
  exitWithError(usageError("Target language required. Use -t or --target"));
37264
37282
  }
37265
37283
  const absSrtPath = resolve27(process.cwd(), srtPath);
37266
- if (!existsSync21(absSrtPath)) {
37284
+ if (!existsSync22(absSrtPath)) {
37267
37285
  exitWithError(notFoundError(absSrtPath));
37268
37286
  }
37269
37287
  if (options.dryRun) {
@@ -37339,7 +37357,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37339
37357
  }
37340
37358
  if (options.fillers) rejectControlChars(options.fillers);
37341
37359
  const absVideoPath = resolve27(process.cwd(), videoPath);
37342
- if (!existsSync21(absVideoPath)) {
37360
+ if (!existsSync22(absVideoPath)) {
37343
37361
  exitWithError(notFoundError(absVideoPath));
37344
37362
  }
37345
37363
  if (!commandExists("ffmpeg")) {
@@ -37439,7 +37457,7 @@ var init_ai_edit_cli = __esm({
37439
37457
  });
37440
37458
 
37441
37459
  // src/commands/_shared/execute-fill-gaps.ts
37442
- import { existsSync as existsSync22 } from "node:fs";
37460
+ import { existsSync as existsSync23 } from "node:fs";
37443
37461
  import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir13, rename as renameFs } from "node:fs/promises";
37444
37462
  import { resolve as resolve28, dirname as dirname9 } from "node:path";
37445
37463
  function detectVideoGaps(videoClips, totalDuration) {
@@ -37522,7 +37540,7 @@ async function executeFillGaps(options) {
37522
37540
  try {
37523
37541
  onProgress("Loading project...");
37524
37542
  const filePath = resolve28(process.cwd(), options.projectPath);
37525
- if (!existsSync22(filePath)) {
37543
+ if (!existsSync23(filePath)) {
37526
37544
  return {
37527
37545
  success: false,
37528
37546
  error: `Project file not found: ${filePath}`,
@@ -37657,7 +37675,7 @@ async function executeFillGaps(options) {
37657
37675
  }
37658
37676
  const projectDir = dirname9(filePath);
37659
37677
  const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
37660
- if (!existsSync22(footageDir)) {
37678
+ if (!existsSync23(footageDir)) {
37661
37679
  await mkdir13(footageDir, { recursive: true });
37662
37680
  }
37663
37681
  humanLines.push("Generating AI Videos");
@@ -38178,12 +38196,12 @@ var init_edit_cmd = __esm({
38178
38196
  `
38179
38197
  Examples:
38180
38198
  $ vibe edit silence-cut interview.mp4 -o clean.mp4
38181
- $ vibe edit caption video.mp4 -o captioned.mp4 -s bold
38199
+ $ vibe edit caption video.mp4 -o captioned.mp4 --style bold
38182
38200
  $ vibe edit grade video.mp4 -o graded.mp4 --preset cinematic-warm
38183
38201
  $ vibe edit reframe landscape.mp4 -o vertical.mp4 -a 9:16
38184
38202
  $ 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
38203
+ $ vibe edit text-overlay video.mp4 --text "Title" --style center-bold -o out.mp4
38204
+ $ vibe edit noise-reduce noisy.mp4 -o clean.mp4 --strength high
38187
38205
  $ vibe edit fade video.mp4 -o faded.mp4 --fade-in 1 --fade-out 1
38188
38206
 
38189
38207
  API Keys (varies by subcommand):
@@ -38930,7 +38948,7 @@ __export(ai_analyze_exports, {
38930
38948
  });
38931
38949
  import { readFile as readFile21 } from "node:fs/promises";
38932
38950
  import { extname as extname6, resolve as resolve30 } from "node:path";
38933
- import { existsSync as existsSync23 } from "node:fs";
38951
+ import { existsSync as existsSync24 } from "node:fs";
38934
38952
  async function executeGeminiVideo(options) {
38935
38953
  try {
38936
38954
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -38949,7 +38967,7 @@ async function executeGeminiVideo(options) {
38949
38967
  videoData = options.source;
38950
38968
  } else {
38951
38969
  const absPath = resolve30(process.cwd(), options.source);
38952
- if (!existsSync23(absPath)) {
38970
+ if (!existsSync24(absPath)) {
38953
38971
  return { success: false, error: `File not found: ${absPath}` };
38954
38972
  }
38955
38973
  videoData = await readFile21(absPath);
@@ -39022,7 +39040,7 @@ async function executeAnalyze(options) {
39022
39040
  imageBuffer = Buffer.from(await response.arrayBuffer());
39023
39041
  } else {
39024
39042
  const absPath = resolve30(process.cwd(), source);
39025
- if (!existsSync23(absPath)) {
39043
+ if (!existsSync24(absPath)) {
39026
39044
  return { success: false, error: `File not found: ${absPath}` };
39027
39045
  }
39028
39046
  imageBuffer = await readFile21(absPath);
@@ -39057,7 +39075,7 @@ async function executeAnalyze(options) {
39057
39075
  videoData = Buffer.from(await response.arrayBuffer());
39058
39076
  } else {
39059
39077
  const absPath = resolve30(process.cwd(), source);
39060
- if (!existsSync23(absPath)) {
39078
+ if (!existsSync24(absPath)) {
39061
39079
  return { success: false, error: `File not found: ${absPath}` };
39062
39080
  }
39063
39081
  videoData = await readFile21(absPath);
@@ -39196,7 +39214,7 @@ __export(ai_review_exports, {
39196
39214
  });
39197
39215
  import { readFile as readFile22, rename as rename3 } from "node:fs/promises";
39198
39216
  import { resolve as resolve31 } from "node:path";
39199
- import { existsSync as existsSync24 } from "node:fs";
39217
+ import { existsSync as existsSync25 } from "node:fs";
39200
39218
  function parseReviewFeedback(response) {
39201
39219
  let cleaned = response.trim();
39202
39220
  if (cleaned.startsWith("```json")) {
@@ -39221,7 +39239,7 @@ function parseReviewFeedback(response) {
39221
39239
  async function executeReview(options) {
39222
39240
  const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
39223
39241
  const absVideoPath = resolve31(process.cwd(), videoPath);
39224
- if (!existsSync24(absVideoPath)) {
39242
+ if (!existsSync25(absVideoPath)) {
39225
39243
  return { success: false, error: `Video not found: ${absVideoPath}` };
39226
39244
  }
39227
39245
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -39231,7 +39249,7 @@ async function executeReview(options) {
39231
39249
  let storyboardContext = "";
39232
39250
  if (storyboardPath) {
39233
39251
  const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
39234
- if (existsSync24(absStoryboardPath)) {
39252
+ if (existsSync25(absStoryboardPath)) {
39235
39253
  const content = await readFile22(absStoryboardPath, "utf-8");
39236
39254
  storyboardContext = `
39237
39255
 
@@ -39437,7 +39455,7 @@ var init_ai_review = __esm({
39437
39455
  // src/commands/ai-highlights.ts
39438
39456
  import { readFile as readFile26, writeFile as writeFile33, mkdir as mkdir15 } from "node:fs/promises";
39439
39457
  import { resolve as resolve35, dirname as dirname12, basename as basename8, extname as extname8 } from "node:path";
39440
- import { existsSync as existsSync27 } from "node:fs";
39458
+ import { existsSync as existsSync28 } from "node:fs";
39441
39459
  function filterHighlights(highlights, options) {
39442
39460
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
39443
39461
  filtered.sort((a, b) => b.confidence - a.confidence);
@@ -39477,7 +39495,7 @@ function truncate(text, maxLength) {
39477
39495
  async function executeHighlights(options) {
39478
39496
  try {
39479
39497
  const absPath = resolve35(process.cwd(), options.media);
39480
- if (!existsSync27(absPath)) {
39498
+ if (!existsSync28(absPath)) {
39481
39499
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
39482
39500
  }
39483
39501
  const ext = extname8(absPath).toLowerCase();
@@ -39593,7 +39611,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
39593
39611
  const audioBuffer = await readFile26(audioPath);
39594
39612
  const audioBlob = new Blob([audioBuffer]);
39595
39613
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
39596
- if (tempAudioPath && existsSync27(tempAudioPath)) {
39614
+ if (tempAudioPath && existsSync28(tempAudioPath)) {
39597
39615
  const { unlink: unlinkFile } = await import("node:fs/promises");
39598
39616
  await unlinkFile(tempAudioPath).catch(() => {
39599
39617
  });
@@ -39680,7 +39698,7 @@ async function executeAutoShorts(options) {
39680
39698
  return { success: false, shorts: [], error: "FFmpeg not found" };
39681
39699
  }
39682
39700
  const absPath = resolve35(process.cwd(), options.video);
39683
- if (!existsSync27(absPath)) {
39701
+ if (!existsSync28(absPath)) {
39684
39702
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
39685
39703
  }
39686
39704
  const targetDuration = options.duration ?? 60;
@@ -39808,7 +39826,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
39808
39826
  };
39809
39827
  }
39810
39828
  const outputDir = options.outputDir ? resolve35(process.cwd(), options.outputDir) : dirname12(absPath);
39811
- if (options.outputDir && !existsSync27(outputDir)) {
39829
+ if (options.outputDir && !existsSync28(outputDir)) {
39812
39830
  await mkdir15(outputDir, { recursive: true });
39813
39831
  }
39814
39832
  const result = {
@@ -39891,7 +39909,7 @@ function registerHighlightsCommands(aiCommand) {
39891
39909
  validateOutputPath(options.output);
39892
39910
  }
39893
39911
  const absPath = resolve35(process.cwd(), mediaPath);
39894
- if (!existsSync27(absPath)) {
39912
+ if (!existsSync28(absPath)) {
39895
39913
  exitWithError(notFoundError(absPath));
39896
39914
  }
39897
39915
  if (options.dryRun) {
@@ -40078,7 +40096,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40078
40096
  const transcriptResult = await whisper.transcribe(audioBlob, options.language);
40079
40097
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
40080
40098
  transcribeSpinner.fail("Transcription failed");
40081
- if (tempAudioPath && existsSync27(tempAudioPath)) {
40099
+ if (tempAudioPath && existsSync28(tempAudioPath)) {
40082
40100
  const { unlink: unlinkFile } = await import("node:fs/promises");
40083
40101
  await unlinkFile(tempAudioPath).catch(() => {
40084
40102
  });
@@ -40086,7 +40104,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40086
40104
  exitWithError(apiError(`Transcription failed: ${transcriptResult.error}`, true));
40087
40105
  }
40088
40106
  transcribeSpinner.succeed(source_default.green(`Transcribed ${transcriptResult.segments.length} segments`));
40089
- if (tempAudioPath && existsSync27(tempAudioPath)) {
40107
+ if (tempAudioPath && existsSync28(tempAudioPath)) {
40090
40108
  const { unlink: unlinkFile } = await import("node:fs/promises");
40091
40109
  await unlinkFile(tempAudioPath).catch(() => {
40092
40110
  });
@@ -40192,7 +40210,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40192
40210
  validateOutputPath(options.output);
40193
40211
  }
40194
40212
  const absPath = resolve35(process.cwd(), videoPath);
40195
- if (!existsSync27(absPath)) {
40213
+ if (!existsSync28(absPath)) {
40196
40214
  exitWithError(notFoundError(absPath));
40197
40215
  }
40198
40216
  if (options.dryRun) {
@@ -40385,7 +40403,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
40385
40403
  return;
40386
40404
  }
40387
40405
  const outputDir = options.outputDir ? resolve35(process.cwd(), options.outputDir) : dirname12(absPath);
40388
- if (options.outputDir && !existsSync27(outputDir)) {
40406
+ if (options.outputDir && !existsSync28(outputDir)) {
40389
40407
  await mkdir15(outputDir, { recursive: true });
40390
40408
  }
40391
40409
  for (let i = 0; i < selectedHighlights.length; i++) {
@@ -40402,7 +40420,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
40402
40420
  outputPath = resolve35(outputDir, `${baseName}-short-${i + 1}.mp4`);
40403
40421
  }
40404
40422
  const parentDir = dirname12(outputPath);
40405
- if (!existsSync27(parentDir)) {
40423
+ if (!existsSync28(parentDir)) {
40406
40424
  await mkdir15(parentDir, { recursive: true });
40407
40425
  }
40408
40426
  const { stdout: probeOut } = await execSafe("ffprobe", [
@@ -40477,7 +40495,7 @@ var init_ai_highlights = __esm({
40477
40495
  // src/commands/ai-animated-caption.ts
40478
40496
  import { resolve as resolve36, dirname as dirname13, basename as basename9 } from "node:path";
40479
40497
  import { writeFile as writeFile34, mkdir as mkdir16, rm as rm2 } from "node:fs/promises";
40480
- import { existsSync as existsSync28 } from "node:fs";
40498
+ import { existsSync as existsSync29 } from "node:fs";
40481
40499
  import { tmpdir as tmpdir2 } from "node:os";
40482
40500
  function groupWords(words, options = {}) {
40483
40501
  if (words.length === 0) return [];
@@ -40654,7 +40672,7 @@ async function executeAnimatedCaption(options) {
40654
40672
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
40655
40673
  const absOutputPath = resolve36(process.cwd(), outputPath);
40656
40674
  const outDir = dirname13(absOutputPath);
40657
- if (!existsSync28(outDir)) {
40675
+ if (!existsSync29(outDir)) {
40658
40676
  await mkdir16(outDir, { recursive: true });
40659
40677
  }
40660
40678
  if (tier === "ass") {
@@ -50603,7 +50621,7 @@ var init_detect = __esm({
50603
50621
  });
50604
50622
 
50605
50623
  // src/pipeline/renderers/chrome.ts
50606
- import { existsSync as existsSync31 } from "node:fs";
50624
+ import { existsSync as existsSync32 } from "node:fs";
50607
50625
  import { homedir as homedir5 } from "node:os";
50608
50626
  import { join as join11 } from "node:path";
50609
50627
  function findChrome() {
@@ -50629,7 +50647,7 @@ function findChrome() {
50629
50647
  "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe"
50630
50648
  ];
50631
50649
  for (const c of candidates) {
50632
- if (c && existsSync31(c)) return c;
50650
+ if (c && existsSync32(c)) return c;
50633
50651
  }
50634
50652
  return void 0;
50635
50653
  }
@@ -50884,7 +50902,7 @@ __export(scene_render_exports, {
50884
50902
  qualityToCrf: () => qualityToCrf
50885
50903
  });
50886
50904
  import { mkdir as mkdir18, readFile as readFile32, stat as stat4 } from "node:fs/promises";
50887
- import { existsSync as existsSync32 } from "node:fs";
50905
+ import { existsSync as existsSync33 } from "node:fs";
50888
50906
  import { resolve as resolve45, relative as relative6, dirname as dirname16, basename as basename16 } from "node:path";
50889
50907
  import {
50890
50908
  createRenderJob,
@@ -50902,7 +50920,7 @@ function defaultOutputPath(opts) {
50902
50920
  }
50903
50921
  async function readProjectName(projectDir) {
50904
50922
  const cfgPath = resolve45(projectDir, "vibe.project.yaml");
50905
- if (!existsSync32(cfgPath)) return void 0;
50923
+ if (!existsSync33(cfgPath)) return void 0;
50906
50924
  try {
50907
50925
  const raw = await (await import("node:fs/promises")).readFile(cfgPath, "utf-8");
50908
50926
  const parsed = (0, import_yaml7.parse)(raw);
@@ -51032,7 +51050,7 @@ __export(scene_build_exports, {
51032
51050
  executeSceneBuild: () => executeSceneBuild,
51033
51051
  resolveSceneBuildMode: () => resolveSceneBuildMode
51034
51052
  });
51035
- import { existsSync as existsSync33 } from "node:fs";
51053
+ import { existsSync as existsSync34 } from "node:fs";
51036
51054
  import { mkdir as mkdir19, readFile as readFile33, writeFile as writeFile38 } from "node:fs/promises";
51037
51055
  import { dirname as dirname17, join as join12, resolve as resolve46 } from "node:path";
51038
51056
  async function executeSceneBuild(opts) {
@@ -51042,7 +51060,7 @@ async function executeSceneBuild(opts) {
51042
51060
  });
51043
51061
  const mode = resolveSceneBuildMode(opts);
51044
51062
  const storyboardPath = join12(projectDir, "STORYBOARD.md");
51045
- if (!existsSync33(storyboardPath)) {
51063
+ if (!existsSync34(storyboardPath)) {
51046
51064
  return failBeforePrimitives(
51047
51065
  `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
51048
51066
  startedAt
@@ -51078,7 +51096,7 @@ async function executeSceneBuild(opts) {
51078
51096
  if (mode === "agent") {
51079
51097
  const compositionsDir = join12(projectDir, "compositions");
51080
51098
  const missingBeats = parsed.beats.filter(
51081
- (b) => !existsSync33(join12(compositionsDir, `scene-${b.id}.html`))
51099
+ (b) => !existsSync34(join12(compositionsDir, `scene-${b.id}.html`))
51082
51100
  );
51083
51101
  if (missingBeats.length > 0) {
51084
51102
  const plan = await getComposePrompts({ projectDir });
@@ -51126,7 +51144,7 @@ async function executeSceneBuild(opts) {
51126
51144
  }
51127
51145
  composeData = composeResult.data;
51128
51146
  }
51129
- if (!existsSync33(join12(projectDir, "index.html"))) {
51147
+ if (!existsSync34(join12(projectDir, "index.html"))) {
51130
51148
  await scaffoldSceneProject({
51131
51149
  dir: projectDir,
51132
51150
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -51186,7 +51204,7 @@ async function dispatchNarration(beat, ctx) {
51186
51204
  if (!text) return { status: "no-cue" };
51187
51205
  for (const ext of ["mp3", "wav"]) {
51188
51206
  const rel2 = `assets/narration-${beat.id}.${ext}`;
51189
- if (existsSync33(join12(ctx.projectDir, rel2)) && !ctx.force) {
51207
+ if (existsSync34(join12(ctx.projectDir, rel2)) && !ctx.force) {
51190
51208
  ctx.onProgress({ type: "narration-cached", beatId: beat.id, path: rel2 });
51191
51209
  return { status: "cached", path: rel2 };
51192
51210
  }
@@ -51227,7 +51245,7 @@ async function dispatchBackdrop(beat, ctx) {
51227
51245
  }
51228
51246
  const rel = `assets/backdrop-${beat.id}.png`;
51229
51247
  const abs = join12(ctx.projectDir, rel);
51230
- if (existsSync33(abs) && !ctx.force) {
51248
+ if (existsSync34(abs) && !ctx.force) {
51231
51249
  ctx.onProgress({ type: "backdrop-cached", beatId: beat.id, path: rel });
51232
51250
  return { status: "cached", path: rel };
51233
51251
  }
@@ -51265,7 +51283,7 @@ function loadSceneBuildEnv(projectDir) {
51265
51283
  (0, import_dotenv3.config)({ path: resolve46(process.cwd(), ".env"), quiet: true });
51266
51284
  let dir = process.cwd();
51267
51285
  while (dir !== dirname17(dir)) {
51268
- if (existsSync33(join12(dir, "pnpm-workspace.yaml"))) {
51286
+ if (existsSync34(join12(dir, "pnpm-workspace.yaml"))) {
51269
51287
  (0, import_dotenv3.config)({ path: join12(dir, ".env"), quiet: true });
51270
51288
  return;
51271
51289
  }
@@ -51296,7 +51314,7 @@ function resolveSceneBuildMode(opts) {
51296
51314
  }
51297
51315
  async function syncRootClipReferences(beats, projectDir, outcomes) {
51298
51316
  const rootPath = join12(projectDir, "index.html");
51299
- if (!existsSync33(rootPath)) return;
51317
+ if (!existsSync34(rootPath)) return;
51300
51318
  const html = await readFile33(rootPath, "utf-8");
51301
51319
  let cursor = 0;
51302
51320
  const clipLines = [];
@@ -51747,14 +51765,14 @@ __export(ai_audio_exports, {
51747
51765
  });
51748
51766
  import { resolve as resolve53, dirname as dirname19, basename as basename17, extname as extname12 } from "node:path";
51749
51767
  import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
51750
- import { existsSync as existsSync36 } from "node:fs";
51768
+ import { existsSync as existsSync37 } from "node:fs";
51751
51769
  async function executeTranscribe(options) {
51752
51770
  const { audioPath, language, output, format, apiKey } = options;
51753
51771
  try {
51754
51772
  const key = apiKey || process.env.OPENAI_API_KEY;
51755
51773
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
51756
51774
  const absPath = resolve53(process.cwd(), audioPath);
51757
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}` };
51775
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
51758
51776
  const whisper = new WhisperProvider();
51759
51777
  await whisper.initialize({ apiKey: key });
51760
51778
  const audioBuffer = await readFile36(absPath);
@@ -51787,7 +51805,7 @@ async function executeIsolate(options) {
51787
51805
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
51788
51806
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
51789
51807
  const absPath = resolve53(process.cwd(), audioPath);
51790
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}` };
51808
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
51791
51809
  const audioBuffer = await readFile36(absPath);
51792
51810
  const elevenlabs = new ElevenLabsProvider();
51793
51811
  await elevenlabs.initialize({ apiKey: key });
@@ -51813,7 +51831,7 @@ async function executeVoiceClone(options) {
51813
51831
  const audioBuffers = [];
51814
51832
  for (const samplePath of samplePaths) {
51815
51833
  const absPath = resolve53(process.cwd(), samplePath);
51816
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${samplePath}` };
51834
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${samplePath}` };
51817
51835
  const buffer = await readFile36(absPath);
51818
51836
  audioBuffers.push(buffer);
51819
51837
  }
@@ -51835,7 +51853,7 @@ async function executeDub(options) {
51835
51853
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
51836
51854
  try {
51837
51855
  const absPath = resolve53(process.cwd(), mediaPath);
51838
- if (!existsSync36(absPath)) return { success: false, error: `File not found: ${absPath}` };
51856
+ if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
51839
51857
  const openaiKey = process.env.OPENAI_API_KEY;
51840
51858
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
51841
51859
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -51963,8 +51981,8 @@ async function executeDuck(options) {
51963
51981
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
51964
51982
  const absMusicPath = resolve53(process.cwd(), musicPath);
51965
51983
  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}` };
51984
+ if (!existsSync37(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
51985
+ if (!existsSync37(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
51968
51986
  const defaultOutput = resolve53(dirname19(absMusicPath), `${basename17(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
51969
51987
  const outputPath = resolve53(process.cwd(), output || defaultOutput);
51970
51988
  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 +51996,7 @@ async function executeDuck(options) {
51978
51996
  "-y",
51979
51997
  outputPath
51980
51998
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
51981
- if (!existsSync36(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
51999
+ if (!existsSync37(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
51982
52000
  return { success: true, outputPath };
51983
52001
  } catch (error) {
51984
52002
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -51996,7 +52014,7 @@ var init_ai_audio = __esm({
51996
52014
  // src/pipeline/executor.ts
51997
52015
  import { resolve as resolve54 } from "node:path";
51998
52016
  import { readFile as readFile37, writeFile as writeFile41, mkdir as mkdir21 } from "node:fs/promises";
51999
- import { existsSync as existsSync37 } from "node:fs";
52017
+ import { existsSync as existsSync38 } from "node:fs";
52000
52018
  function maxCostFor(action) {
52001
52019
  const cmd = ACTION_METADATA[action]?.command;
52002
52020
  if (!cmd) return 0;
@@ -52213,7 +52231,7 @@ async function ensureActionsRegistered() {
52213
52231
  }
52214
52232
  async function loadPipeline(filePath) {
52215
52233
  const absPath = resolve54(process.cwd(), filePath);
52216
- if (!existsSync37(absPath)) {
52234
+ if (!existsSync38(absPath)) {
52217
52235
  throw new Error(`Pipeline file not found: ${absPath}`);
52218
52236
  }
52219
52237
  const content = await readFile37(absPath, "utf-8");
@@ -52238,7 +52256,7 @@ async function executePipeline(manifest2, options = {}) {
52238
52256
  const startTime = Date.now();
52239
52257
  if (options.resume) {
52240
52258
  const checkpointPath = resolve54(outputDir, CHECKPOINT_FILE);
52241
- if (existsSync37(checkpointPath)) {
52259
+ if (existsSync38(checkpointPath)) {
52242
52260
  const checkpointContent = await readFile37(checkpointPath, "utf-8");
52243
52261
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
52244
52262
  for (const cs of checkpoint.completedSteps) {
@@ -59644,7 +59662,7 @@ __export(project_builder_exports, {
59644
59662
  resolveSourceUrl: () => resolveSourceUrl
59645
59663
  });
59646
59664
  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";
59665
+ import { existsSync as existsSync39 } from "node:fs";
59648
59666
  import { tmpdir as tmpdir4 } from "node:os";
59649
59667
  import { createRequire } from "node:module";
59650
59668
  import * as path from "node:path";
@@ -59656,7 +59674,7 @@ async function buildTempProject(state, projectFileDir) {
59656
59674
  for (const source of state.sources) {
59657
59675
  if (copied.has(source.id)) continue;
59658
59676
  const resolved = resolveSourceUrl(source.url, projectFileDir);
59659
- if (!existsSync38(resolved)) {
59677
+ if (!existsSync39(resolved)) {
59660
59678
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
59661
59679
  }
59662
59680
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -60494,11 +60512,11 @@ var init_export = __esm({
60494
60512
  ).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
60513
  Examples:
60496
60514
  $ 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
60515
+ $ vibe export project.vibe.json -o output.mp4 --preset high --overwrite
60516
+ $ vibe export project.vibe.json -o output.webm --format webm
60517
+ $ vibe export project.vibe.json -o output.gif --format gif
60500
60518
  $ 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
60519
+ $ vibe export project.vibe.json -o out.mp4 --preset high --fps 60
60502
60520
 
60503
60521
  Cost: Free (no API keys needed). Requires FFmpeg.
60504
60522
  GIF format: 15fps, no audio, looping. Good for previews and sharing.
@@ -61634,28 +61652,31 @@ narration / backdrop intent.
61634
61652
  ## Quick-draft path
61635
61653
 
61636
61654
  \`\`\`bash
61637
- vibe scene init my-promo -r 16:9 -d 30
61655
+ vibe init my-promo -r 16:9 -d 30
61638
61656
  vibe scene add intro --style announcement \\
61639
61657
  --headline "Ship videos, not clicks"
61640
61658
  vibe scene lint
61641
61659
  vibe render my-promo
61642
61660
  \`\`\`
61643
61661
 
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.
61662
+ \`vibe init\` is **idempotent** \u2014 running it on an existing Hyperframes
61663
+ directory merges \`hyperframes.json\` instead of clobbering it. Safe to
61664
+ invoke on user-provided projects.
61647
61665
 
61648
61666
  ## Subcommands
61649
61667
 
61650
61668
  \`\`\`bash
61651
- vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61669
+ # Project flow (top-level \u2014 preferred entry points)
61670
+ vibe init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61671
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
61672
+ vibe render [<dir>] [--fps 30] [--quality standard] [--format mp4]
61673
+
61674
+ # Lower-level scene authoring
61652
61675
  vibe scene list-styles [<name>] # list / show vendored visual identities
61653
61676
  vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
61654
61677
  vibe scene add <name> --style <preset> [...]
61655
61678
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
61656
61679
  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
61680
  \`\`\`
61660
61681
 
61661
61682
  ## Style presets (for \`vibe scene add --style\`)
@@ -61709,7 +61730,7 @@ and surface the error to the user.
61709
61730
  | Generate narration + image, then author scene | \`vibe scene add\` |
61710
61731
  | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
61711
61732
  | 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 |
61733
+ | Render the project | \`vibe render\` (one canonical entry point) |
61713
61734
  | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
61714
61735
 
61715
61736
  The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
@@ -61837,12 +61858,12 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
61837
61858
  ],
61838
61859
  relatedCommands: [
61839
61860
  "vibe init",
61861
+ "vibe build",
61862
+ "vibe render",
61840
61863
  "vibe scene list-styles",
61841
61864
  "vibe scene install-skill",
61842
61865
  "vibe scene compose-prompts",
61843
- "vibe build",
61844
61866
  "vibe scene lint",
61845
- "vibe scene render",
61846
61867
  "vibe scene add"
61847
61868
  ]
61848
61869
  },
@@ -62166,7 +62187,7 @@ init_source();
62166
62187
  // package.json
62167
62188
  var package_default = {
62168
62189
  name: "@vibeframe/cli",
62169
- version: "0.78.0",
62190
+ version: "0.79.0",
62170
62191
  description: "VibeFrame CLI - natural language video editing from the terminal",
62171
62192
  private: false,
62172
62193
  license: "MIT",
@@ -62578,6 +62599,7 @@ init_engine();
62578
62599
  init_validate();
62579
62600
  init_output();
62580
62601
  import { readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
62602
+ import { existsSync as existsSync8 } from "node:fs";
62581
62603
  import { resolve as resolve11, basename as basename2, extname } from "node:path";
62582
62604
  var timelineCommand = new Command("timeline").description("Timeline editing commands").addHelpText("after", `
62583
62605
  Examples:
@@ -62928,6 +62950,9 @@ timelineCommand.command("list").description("List timeline contents").argument("
62928
62950
  const startedAt = Date.now();
62929
62951
  try {
62930
62952
  const filePath = resolve11(process.cwd(), projectPath);
62953
+ if (!existsSync8(filePath)) {
62954
+ exitWithError(notFoundError(projectPath));
62955
+ }
62931
62956
  const content = await readFile9(filePath, "utf-8");
62932
62957
  const data = JSON.parse(content);
62933
62958
  const project = Project.fromJSON(data);
@@ -63548,16 +63573,16 @@ init_output();
63548
63573
  init_validate();
63549
63574
  import { resolve as resolve33, dirname as dirname11, basename as basename7, extname as extname7 } from "node:path";
63550
63575
  import { readFile as readFile24, writeFile as writeFile31 } from "node:fs/promises";
63551
- import { existsSync as existsSync25 } from "node:fs";
63576
+ import { existsSync as existsSync26 } from "node:fs";
63552
63577
  var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
63553
63578
  "after",
63554
63579
  `
63555
63580
  Examples:
63556
- $ vibe audio transcribe interview.mp3 -o transcript.srt -f srt
63581
+ $ vibe audio transcribe interview.mp3 -o transcript.srt --format srt
63557
63582
  $ vibe audio transcribe video.mp4 -l ko # Specify language
63558
63583
  $ vibe audio list-voices # List available voices
63559
63584
  $ vibe audio isolate song.mp3 -o vocals.mp3
63560
- $ vibe audio clone-voice sample.mp3 -n "my-voice"
63585
+ $ vibe audio clone-voice sample.mp3 --name "my-voice"
63561
63586
  $ vibe audio dub video.mp4 -l ko -o dubbed.mp4
63562
63587
  $ vibe audio duck music.mp3 --voice narration.mp3 -o ducked.mp3
63563
63588
 
@@ -63752,7 +63777,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
63752
63777
  const audioBuffers = [];
63753
63778
  for (const samplePath of samples) {
63754
63779
  const absPath = resolve33(process.cwd(), samplePath);
63755
- if (!existsSync25(absPath)) {
63780
+ if (!existsSync26(absPath)) {
63756
63781
  spinner2.fail(`File not found: ${samplePath}`);
63757
63782
  exitWithError(notFoundError(samplePath));
63758
63783
  }
@@ -63813,7 +63838,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
63813
63838
  exitWithError(usageError("Target language is required. Use -l or --language"));
63814
63839
  }
63815
63840
  const absPath = resolve33(process.cwd(), mediaPath);
63816
- if (!existsSync25(absPath)) {
63841
+ if (!existsSync26(absPath)) {
63817
63842
  exitWithError(notFoundError(mediaPath));
63818
63843
  }
63819
63844
  const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
@@ -64056,7 +64081,7 @@ init_esm();
64056
64081
  init_source();
64057
64082
  init_ora();
64058
64083
  import { resolve as resolve37, basename as basename10 } from "node:path";
64059
- import { existsSync as existsSync29 } from "node:fs";
64084
+ import { existsSync as existsSync30 } from "node:fs";
64060
64085
 
64061
64086
  // src/commands/ai-script-pipeline-cli.ts
64062
64087
  init_source();
@@ -64067,19 +64092,19 @@ init_ai_script_pipeline();
64067
64092
  init_output();
64068
64093
  import { readFile as readFile25, writeFile as writeFile32 } from "node:fs/promises";
64069
64094
  import { resolve as resolve34 } from "node:path";
64070
- import { existsSync as existsSync26 } from "node:fs";
64095
+ import { existsSync as existsSync27 } from "node:fs";
64071
64096
  function registerScriptPipelineCommands(aiCommand) {
64072
64097
  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
64098
  const startedAt = Date.now();
64074
64099
  try {
64075
64100
  const outputDir = resolve34(process.cwd(), projectDir);
64076
64101
  const projectPath = resolve34(outputDir, "project.vibe.json");
64077
- if (!existsSync26(outputDir)) {
64102
+ if (!existsSync27(outputDir)) {
64078
64103
  exitWithError(notFoundError(outputDir));
64079
64104
  }
64080
64105
  const yamlPath = resolve34(outputDir, "storyboard.yaml");
64081
64106
  const jsonPath = resolve34(outputDir, "storyboard.json");
64082
- const storyboardPath = existsSync26(yamlPath) ? yamlPath : existsSync26(jsonPath) ? jsonPath : null;
64107
+ const storyboardPath = existsSync27(yamlPath) ? yamlPath : existsSync27(jsonPath) ? jsonPath : null;
64083
64108
  const storyboardIsYaml = storyboardPath === yamlPath;
64084
64109
  if (!storyboardPath) {
64085
64110
  exitWithError(notFoundError(`${outputDir}/storyboard.{yaml,json}`));
@@ -64177,7 +64202,7 @@ function registerScriptPipelineCommands(aiCommand) {
64177
64202
  exitWithError(apiError(result.error || "Scene regeneration failed", true));
64178
64203
  }
64179
64204
  spinner2.succeed(source_default.green(`Regenerated ${result.regeneratedScenes.length} scene(s)`));
64180
- if (existsSync26(projectPath) && result.regeneratedScenes.length > 0) {
64205
+ if (existsSync27(projectPath) && result.regeneratedScenes.length > 0) {
64181
64206
  const updateSpinner = ora("\u{1F4E6} Updating project file...").start();
64182
64207
  try {
64183
64208
  const updatedContent = await readFile25(storyboardPath, "utf-8");
@@ -64191,7 +64216,7 @@ function registerScriptPipelineCommands(aiCommand) {
64191
64216
  const imagePath = resolve34(outputDir, `scene-${sceneNum}.png`);
64192
64217
  const videoSource = projectData.state.sources.find((s) => s.name === `Scene ${sceneNum}`);
64193
64218
  if (videoSource) {
64194
- const hasVideo = existsSync26(videoPath);
64219
+ const hasVideo = existsSync27(videoPath);
64195
64220
  videoSource.url = hasVideo ? videoPath : imagePath;
64196
64221
  videoSource.type = hasVideo ? "video" : "image";
64197
64222
  videoSource.duration = segment.duration;
@@ -64255,9 +64280,9 @@ Two flows \u2014 pick by intent:
64255
64280
 
64256
64281
  Examples (REMIX):
64257
64282
  $ 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
64283
+ $ vibe remix auto-shorts long-video.mp4 -o shorts/ --count 3 --add-captions
64284
+ $ vibe remix animated-caption video.mp4 -o captioned.mp4 --style highlight
64285
+ $ vibe remix animated-caption video.mp4 -o out.mp4 --style karaoke-sweep --fast
64261
64286
 
64262
64287
  Provider API Keys:
64263
64288
  highlights: GOOGLE_API_KEY (Gemini analysis)
@@ -64282,8 +64307,8 @@ pipelineCommand.command("animated-caption").description("Add animated captions w
64282
64307
  `
64283
64308
  Examples:
64284
64309
  $ 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
64310
+ $ vibe remix animated-caption video.mp4 -o out.mp4 --style bounce
64311
+ $ vibe remix animated-caption video.mp4 -o out.mp4 --style karaoke-sweep --fast
64287
64312
 
64288
64313
  Styles:
64289
64314
  highlight (default) TikTok-style background highlight on active word (Remotion)
@@ -64299,7 +64324,7 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
64299
64324
  const startedAt = Date.now();
64300
64325
  try {
64301
64326
  const absVideoPath = resolve37(process.cwd(), videoPath);
64302
- if (!existsSync29(absVideoPath)) {
64327
+ if (!existsSync30(absVideoPath)) {
64303
64328
  exitWithError(notFoundError(absVideoPath));
64304
64329
  }
64305
64330
  if (!ANIMATED_CAPTION_STYLES.includes(options.style)) {
@@ -69400,7 +69425,7 @@ init_agent_host_detect();
69400
69425
  init_scene_project();
69401
69426
  init_visual_styles();
69402
69427
  init_install_skill();
69403
- import { existsSync as existsSync30 } from "node:fs";
69428
+ import { existsSync as existsSync31 } from "node:fs";
69404
69429
  import { mkdir as mkdir17, readFile as readFile30, writeFile as writeFile37 } from "node:fs/promises";
69405
69430
  import { basename as basename14, dirname as dirname14, resolve as resolve42 } from "node:path";
69406
69431
 
@@ -69691,7 +69716,7 @@ var initCommand = new Command("init").description("Scaffold a VibeFrame project
69691
69716
  const wantsGemini = targetHosts.includes("gemini-cli");
69692
69717
  const wantsCrossTool = targetHosts.some((h) => h !== "claude-code");
69693
69718
  const actions = [];
69694
- if (!existsSync30(projectDir)) {
69719
+ if (!existsSync31(projectDir)) {
69695
69720
  if (options.dryRun) {
69696
69721
  actions.push({ path: projectDir, status: "would-write", reason: "(would create directory)" });
69697
69722
  } else {
@@ -69937,7 +69962,7 @@ function resolveTargets(agent) {
69937
69962
  return [agent];
69938
69963
  }
69939
69964
  async function writeIfMissing(absPath, content, force, dryRun) {
69940
- const exists = existsSync30(absPath);
69965
+ const exists = existsSync31(absPath);
69941
69966
  if (exists && !force) {
69942
69967
  return { path: absPath, status: "skipped-exists", reason: "(use --force to overwrite)" };
69943
69968
  }
@@ -69950,7 +69975,7 @@ async function writeIfMissing(absPath, content, force, dryRun) {
69950
69975
  }
69951
69976
  async function mergeGitignore(absPath, additions, dryRun) {
69952
69977
  const marker = "# VibeFrame";
69953
- if (!existsSync30(absPath)) {
69978
+ if (!existsSync31(absPath)) {
69954
69979
  if (dryRun) return { path: absPath, status: "would-write" };
69955
69980
  await writeFile37(absPath, additions.trimStart(), "utf-8");
69956
69981
  return { path: absPath, status: "wrote" };
@@ -70293,7 +70318,7 @@ init_composer_resolve();
70293
70318
  init_scene_build();
70294
70319
  init_output();
70295
70320
  import { access as access6 } from "node:fs/promises";
70296
- import { existsSync as existsSync34 } from "node:fs";
70321
+ import { existsSync as existsSync35 } from "node:fs";
70297
70322
  import { resolve as resolve49 } from "node:path";
70298
70323
  var COMMAND_KEY_MAP = getCommandKeyMap();
70299
70324
  var FREE_COMMANDS = [
@@ -70407,7 +70432,7 @@ async function runDiagnostics() {
70407
70432
  installed = false;
70408
70433
  }
70409
70434
  } else if (toolName === "chrome") {
70410
- const { existsSync: existsSync39 } = await import("node:fs");
70435
+ const { existsSync: existsSync40 } = await import("node:fs");
70411
70436
  const { homedir: homedir6 } = await import("node:os");
70412
70437
  const { join: join16 } = await import("node:path");
70413
70438
  const chromePaths = [
@@ -70420,7 +70445,7 @@ async function runDiagnostics() {
70420
70445
  "/usr/bin/chromium",
70421
70446
  "/usr/bin/chromium-browser"
70422
70447
  ];
70423
- installed = chromePaths.some((p) => p && existsSync39(p));
70448
+ installed = chromePaths.some((p) => p && existsSync40(p));
70424
70449
  } else {
70425
70450
  installed = commandExists(toolName);
70426
70451
  }
@@ -70451,7 +70476,7 @@ async function runDiagnostics() {
70451
70476
  const cwd = process.cwd();
70452
70477
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
70453
70478
  path: rel,
70454
- exists: existsSync34(resolve49(cwd, rel))
70479
+ exists: existsSync35(resolve49(cwd, rel))
70455
70480
  }));
70456
70481
  const projectInitialized = projectFiles.some((f) => f.exists);
70457
70482
  const hosts = detectAgentHosts();
@@ -70466,8 +70491,8 @@ async function runDiagnostics() {
70466
70491
  } catch (err) {
70467
70492
  if (!(err instanceof ComposerResolveError)) throw err;
70468
70493
  }
70469
- const sceneProjectInCwd = existsSync34(resolve49(cwd, "STORYBOARD.md"));
70470
- const skillInstalled = existsSync34(resolve49(cwd, "SKILL.md"));
70494
+ const sceneProjectInCwd = existsSync35(resolve49(cwd, "STORYBOARD.md"));
70495
+ const skillInstalled = existsSync35(resolve49(cwd, "SKILL.md"));
70471
70496
  return {
70472
70497
  system: {
70473
70498
  node: { version: nodeVersion, ok: true },
@@ -70633,7 +70658,7 @@ init_detect();
70633
70658
  init_ai_edit();
70634
70659
  init_output();
70635
70660
  import { resolve as resolve50 } from "node:path";
70636
- import { existsSync as existsSync35 } from "node:fs";
70661
+ import { existsSync as existsSync36 } from "node:fs";
70637
70662
  import { mkdir as mkdir20, rm as rm3 } from "node:fs/promises";
70638
70663
  var DEMO_DIR = resolve50(process.cwd(), ".vibeframe-demo");
70639
70664
  async function generateTestVideo(outputPath) {
@@ -70664,7 +70689,7 @@ async function generateTestVideo(outputPath) {
70664
70689
  "-y",
70665
70690
  outputPath
70666
70691
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
70667
- return existsSync35(outputPath);
70692
+ return existsSync36(outputPath);
70668
70693
  } catch {
70669
70694
  try {
70670
70695
  await execSafe("ffmpeg", [
@@ -70690,7 +70715,7 @@ async function generateTestVideo(outputPath) {
70690
70715
  "-y",
70691
70716
  outputPath
70692
70717
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
70693
- return existsSync35(outputPath);
70718
+ return existsSync36(outputPath);
70694
70719
  } catch {
70695
70720
  return false;
70696
70721
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
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.0",
186
+ "@vibeframe/core": "0.79.0"
187
187
  },
188
188
  "engines": {
189
189
  "node": ">=20"