@vibeframe/cli 0.76.0 → 0.78.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 +152 -97
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -18722,8 +18722,8 @@ var init_elevenlabs = __esm({
18722
18722
  "generate speech",
18723
18723
  "generate sound-effect",
18724
18724
  "generate music",
18725
- "audio voices",
18726
- "audio voice-clone",
18725
+ "audio list-voices",
18726
+ "audio clone-voice",
18727
18727
  "audio dub"
18728
18728
  ]
18729
18729
  });
@@ -25999,7 +25999,7 @@ function buildEmptyRootHtml(opts) {
25999
25999
  }
26000
26000
  function buildDesignMd(opts) {
26001
26001
  const { name, style } = opts;
26002
- const intro = style ? `Visual identity for **${name}**, scaffolded from the **${style.name}** style (after ${style.designer}). Customise freely \u2014 this file is the single source of truth for every scene's palette, typography, and motion.` : `Visual identity for **${name}**. Fill the sections below before authoring any scene HTML or generating any backdrop. Pick a named style with \`vibe scene styles\` if you want a credible starting point.`;
26002
+ const intro = style ? `Visual identity for **${name}**, scaffolded from the **${style.name}** style (after ${style.designer}). Customise freely \u2014 this file is the single source of truth for every scene's palette, typography, and motion.` : `Visual identity for **${name}**. Fill the sections below before authoring any scene HTML or generating any backdrop. Pick a named style with \`vibe scene list-styles\` if you want a credible starting point.`;
26003
26003
  const moodLine = style ? `**Mood:** ${style.mood} \xB7 **Best for:** ${style.bestFor}` : `**Mood:** _(one line \u2014 what should the viewer FEEL?)_`;
26004
26004
  const palette = style ? `${style.palette.map((c) => `- \`${c}\``).join("\n")}
26005
26005
 
@@ -26057,7 +26057,7 @@ ${avoid}
26057
26057
 
26058
26058
  ---
26059
26059
 
26060
- _Browse other named styles: \`vibe scene styles\`_
26060
+ _Browse other named styles: \`vibe scene list-styles\`_
26061
26061
  ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
26062
26062
  `;
26063
26063
  }
@@ -26123,7 +26123,7 @@ typography, motion, and transition rules. Both the agent-driven path and
26123
26123
  the fallback emit reference it; scenes that contradict DESIGN.md are
26124
26124
  rejected by the Hyperframes \`hyperframes\` skill.
26125
26125
 
26126
- Browse named styles: \`vibe scene styles\`. Re-seed from one with
26126
+ Browse named styles: \`vibe scene list-styles\`. Re-seed from one with
26127
26127
  \`vibe scene init . --visual-style "Swiss Pulse"\` (idempotent).
26128
26128
 
26129
26129
  ## Skills \u2014 USE THESE FIRST
@@ -29396,7 +29396,7 @@ var init_scene = __esm({
29396
29396
  init_agent_host_detect();
29397
29397
  init_install_skill();
29398
29398
  init_compose_prompts();
29399
- sceneCommand = new Command("scene").description("Lower-level scene authoring (add, lint, styles). For project flow use `vibe init` / `vibe build` / `vibe render`.").addHelpText("after", `
29399
+ sceneCommand = new Command("scene").description("Lower-level scene authoring (add, lint, list-styles). For project flow use `vibe init` / `vibe build` / `vibe render`.").addHelpText("after", `
29400
29400
  Examples:
29401
29401
  $ vibe scene add intro --style announcement \\
29402
29402
  --headline "Welcome to VibeFrame" # Headline-only scene
@@ -29405,7 +29405,7 @@ Examples:
29405
29405
  $ vibe scene lint # Validate every scene against composition rules
29406
29406
  $ vibe scene lint --fix # Auto-fix mechanical issues (e.g. missing class="clip")
29407
29407
  $ vibe scene lint --json # Structured output for agent loops
29408
- $ vibe scene styles # Browse seed visual styles for DESIGN.md
29408
+ $ vibe scene list-styles # Browse seed visual styles for DESIGN.md
29409
29409
 
29410
29410
  For the project flow (init / build / render), use the top-level commands.
29411
29411
  The \`scene init\`, \`scene build\`, and \`scene render\` legacy aliases
@@ -29520,13 +29520,13 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
29520
29520
  console.log();
29521
29521
  console.log(source_default.dim("Re-run with --json to get the full per-beat userPrompt + cues for direct consumption."));
29522
29522
  });
29523
- sceneCommand.command("styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
29523
+ sceneCommand.command("list-styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
29524
29524
  const startedAt = Date.now();
29525
29525
  if (!name) {
29526
29526
  const all = listVisualStyles();
29527
29527
  if (isJsonMode()) {
29528
29528
  outputSuccess({
29529
- command: "scene styles",
29529
+ command: "scene list-styles",
29530
29530
  startedAt,
29531
29531
  data: {
29532
29532
  count: all.length,
@@ -29550,7 +29550,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
29550
29550
  );
29551
29551
  }
29552
29552
  console.log();
29553
- console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene styles "<name>"'));
29553
+ console.log(source_default.dim("Show details: "), source_default.cyan('vibe scene list-styles "<name>"'));
29554
29554
  console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan('vibe scene init <dir> --visual-style "<name>"'));
29555
29555
  return;
29556
29556
  }
@@ -29566,7 +29566,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
29566
29566
  }
29567
29567
  if (isJsonMode()) {
29568
29568
  outputSuccess({
29569
- command: "scene styles",
29569
+ command: "scene list-styles",
29570
29570
  startedAt,
29571
29571
  data: { style }
29572
29572
  });
@@ -30703,7 +30703,7 @@ Use this image analysis to inform the color palette, typography placement, and o
30703
30703
  return { success: true, codePath, componentName: component.name, renderedPath: renderResult.outputPath };
30704
30704
  }
30705
30705
  function registerMotionCommand(aiCommand) {
30706
- aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("-w, --width <px>", "Width in pixels", "1920").option("-h, --height <px>", "Height in pixels", "1080").option("--fps <fps>", "Frame rate", "30").option("-s, --style <style>", "Style preset: minimal, corporate, playful, cinematic").option("--render", "Render the generated code with Remotion (output .webm)").option("--video <path>", "Base video to composite the motion graphic onto").option("--image <path>", "Image to analyze with Gemini \u2014 color/mood fed into Claude prompt").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
30706
+ aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("--width <px>", "Width in pixels", "1920").option("--height <px>", "Height in pixels", "1080").option("--fps <fps>", "Frame rate", "30").option("--style <style>", "Style preset: minimal, corporate, playful, cinematic").option("--render", "Render the generated code with Remotion (output .webm)").option("--video <path>", "Base video to composite the motion graphic onto").option("--image <path>", "Image to analyze with Gemini \u2014 color/mood fed into Claude prompt").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
30707
30707
  const startedAt = Date.now();
30708
30708
  try {
30709
30709
  if (options.output) {
@@ -31307,7 +31307,7 @@ async function executeStoryboard(options) {
31307
31307
  }
31308
31308
  }
31309
31309
  function registerStoryboardCommand(parent) {
31310
- parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("-f, --file", "Treat content argument as file path").option("-c, --creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low").option("--dry-run", "Preview parameters without executing").action(async (content, options) => {
31310
+ parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("--file", "Treat content argument as file path").option("--creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low").option("--dry-run", "Preview parameters without executing").action(async (content, options) => {
31311
31311
  const startedAt = Date.now();
31312
31312
  try {
31313
31313
  rejectControlChars(content);
@@ -31625,7 +31625,7 @@ async function executeSpeech(options) {
31625
31625
  }
31626
31626
  }
31627
31627
  function registerSpeechCommand(parent) {
31628
- parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("-v, --voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM").option("--list-voices", "List available voices").option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat).option("--dry-run", "Preview parameters without executing").action(async (text, options) => {
31628
+ parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("--voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM").option("--list-voices", "List available voices").option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat).option("--dry-run", "Preview parameters without executing").action(async (text, options) => {
31629
31629
  const startedAt = Date.now();
31630
31630
  try {
31631
31631
  if (!text) {
@@ -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("-m, --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("--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);
@@ -32265,7 +32265,7 @@ import { resolve as resolve19, dirname as dirname6, basename as basename3, extna
32265
32265
  import { existsSync as existsSync12 } from "node:fs";
32266
32266
  import { writeFile as writeFile18, mkdir as mkdir9 } from "node:fs/promises";
32267
32267
  function registerThumbnailCommand(parent) {
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("-s, --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) => {
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) => {
32269
32269
  const startedAt = Date.now();
32270
32270
  try {
32271
32271
  if (description) rejectControlChars(description);
@@ -32431,7 +32431,7 @@ function getStatusColor(status) {
32431
32431
  }
32432
32432
  }
32433
32433
  function registerVideoStatusCommand(parent) {
32434
- parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("-t, --type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("-w, --wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
32434
+ parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("--type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("--wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
32435
32435
  const startedAt = Date.now();
32436
32436
  try {
32437
32437
  const provider = (options.provider || "grok").toLowerCase();
@@ -32664,7 +32664,7 @@ var init_video_status = __esm({
32664
32664
  import { resolve as resolve21 } from "node:path";
32665
32665
  import { writeFile as writeFile20 } from "node:fs/promises";
32666
32666
  function registerVideoExtendCommand(parent) {
32667
- parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("-n, --negative <prompt>", "Negative prompt (what to avoid, Kling only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1").option("--no-wait", "Start extension and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (id, options) => {
32667
+ parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("--negative <prompt>", "Negative prompt (what to avoid, Kling only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1").option("--no-wait", "Start extension and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (id, options) => {
32668
32668
  const startedAt = Date.now();
32669
32669
  try {
32670
32670
  const provider = (options.provider || "kling").toLowerCase();
@@ -32947,7 +32947,7 @@ import { resolve as resolve22, dirname as dirname7 } from "node:path";
32947
32947
  import { fileURLToPath } from "node:url";
32948
32948
  import { writeFile as writeFile21, mkdir as mkdir10 } from "node:fs/promises";
32949
32949
  function registerImageCommand(parent) {
32950
- parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option("-p, --provider <provider>", "Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway").option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("-s, --size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024").option("-r, --ratio <ratio>", "Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)", "1:1").option("--quality <quality>", "Quality: standard, hd (openai only)", "standard").option("--style <style>", "Style: vivid, natural (openai only)", "vivid").option("-n, --count <n>", "Number of images to generate", "1").option("-m, --model <model>", "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
32950
+ parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option("-p, --provider <provider>", "Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway").option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("--size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024").option("-r, --ratio <ratio>", "Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)", "1:1").option("--quality <quality>", "Quality: standard, hd (openai only)", "standard").option("--style <style>", "Style: vivid, natural (openai only)", "vivid").option("--count <n>", "Number of images to generate", "1").option("-m, --model <model>", "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
32951
32951
  Examples:
32952
32952
  $ vibe generate image "a sunset over the ocean" -o sunset.png
32953
32953
  $ vibe gen img "logo design" -o logo.png -p openai
@@ -35090,7 +35090,7 @@ function registerVideoCommand(parent) {
35090
35090
  "-d, --duration <sec>",
35091
35091
  "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
35092
35092
  "5"
35093
- ).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("-s, --seed <number>", "Random seed for reproducibility (Runway only)").option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("-n, --negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
35093
+ ).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
35094
35094
  Examples:
35095
35095
  $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
35096
35096
  $ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
@@ -36925,7 +36925,7 @@ var init_ai_edit = __esm({
36925
36925
  import { resolve as resolve27, extname as extname5, basename as basename6 } from "node:path";
36926
36926
  import { existsSync as existsSync21 } from "node:fs";
36927
36927
  function registerEditCommands(aiCommand) {
36928
- 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("-n, --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", `
36928
+ 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
36929
  Examples:
36930
36930
  $ vibe edit silence-cut interview.mp4 -o clean.mp4
36931
36931
  $ vibe ed sc podcast.mp4 -o trimmed.mp4 -n -25 -d 1.0
@@ -37026,7 +37026,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
37026
37026
  exitWithError(apiError(`Silence cut failed: ${error instanceof Error ? error.message : String(error)}`, true));
37027
37027
  }
37028
37028
  });
37029
- 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("-s, --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", `
37029
+ 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
37030
  Examples:
37031
37031
  $ vibe edit caption video.mp4 -o captioned.mp4
37032
37032
  $ vibe ed cap video.mp4 -o out.mp4 -s bold --position top
@@ -37115,7 +37115,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37115
37115
  exitWithError(apiError(`Caption failed: ${error instanceof Error ? error.message : String(error)}`, true));
37116
37116
  }
37117
37117
  });
37118
- aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("-s, --strength <level>", "Noise reduction strength: low, medium, high (default: medium)", "medium").option("-n, --noise-floor <dB>", "Custom noise floor in dB (overrides strength preset)").option("--dry-run", "Preview parameters without executing").action(async (inputPath, options) => {
37118
+ aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("--strength <level>", "Noise reduction strength: low, medium, high (default: medium)", "medium").option("--noise-floor <dB>", "Custom noise floor in dB (overrides strength preset)").option("--dry-run", "Preview parameters without executing").action(async (inputPath, options) => {
37119
37119
  const startedAt = Date.now();
37120
37120
  try {
37121
37121
  if (options.output) {
@@ -37253,7 +37253,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37253
37253
  exitWithError(generalError(`Fade failed: ${error instanceof Error ? error.message : String(error)}`));
37254
37254
  }
37255
37255
  });
37256
- aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude/OpenAI)").argument("<srt>", "SRT file path").option("-t, --target <language>", "Target language (e.g., ko, es, fr, ja, zh)").option("-o, --output <path>", "Output file path (default: <name>-<target>.srt)").option("-p, --provider <provider>", "Translation provider: claude, openai (default: claude)", "claude").option("--source <language>", "Source language (auto-detected if omitted)").option("-k, --api-key <key>", "API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (srtPath, options) => {
37256
+ aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude or OpenAI)").argument("<srt>", "SRT file path").option("--target <language>", "Target language (e.g., ko, es, fr, ja, zh)").option("-o, --output <path>", "Output file path (default: <name>-<target>.srt)").option("-p, --provider <provider>", "Translation provider: claude, openai (default: claude)", "claude").option("--source <language>", "Source language (auto-detected if omitted)").option("-k, --api-key <key>", "API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (srtPath, options) => {
37257
37257
  const startedAt = Date.now();
37258
37258
  try {
37259
37259
  if (options.output) {
@@ -37919,7 +37919,7 @@ var init_execute_fill_gaps = __esm({
37919
37919
 
37920
37920
  // src/commands/ai-fill-gaps.ts
37921
37921
  function registerFillGapsCommand(aiCommand) {
37922
- aiCommand.command("fill-gaps").description("Fill timeline gaps with AI-generated video (Kling image-to-video)").argument("<project>", "Project file path").option("-p, --provider <provider>", "AI provider (kling)", "kling").option("-o, --output <path>", "Output project path (default: overwrite)").option("-d, --dir <path>", "Directory to save generated videos").option("--prompt <text>", "Custom prompt for video generation").option("--dry-run", "Show gaps without generating").option("-m, --mode <mode>", "Generation mode: std or pro (Kling)", "std").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1", "16:9").action(async (projectPath, options) => {
37922
+ aiCommand.command("fill-gaps").description("Fill timeline gaps with AI-generated video (Kling image-to-video)").argument("<project>", "Project file path").option("-p, --provider <provider>", "AI provider (kling)", "kling").option("-o, --output <path>", "Output project path (default: overwrite)").option("-d, --dir <path>", "Directory to save generated videos").option("--prompt <text>", "Custom prompt for video generation").option("--dry-run", "Show gaps without generating").option("--mode <mode>", "Generation mode: std or pro (Kling)", "std").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1", "16:9").action(async (projectPath, options) => {
37923
37923
  try {
37924
37924
  if (options.output) {
37925
37925
  validateOutputPath(options.output);
@@ -38197,7 +38197,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38197
38197
  );
38198
38198
  registerEditCommands(editCommand);
38199
38199
  registerFillGapsCommand(editCommand);
38200
- editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("-s, --style <prompt>", "Style description (e.g., 'cinematic warm')").option("--preset <name>", "Built-in preset: film-noir, vintage, cinematic-warm, cool-tones, high-contrast, pastel, cyberpunk, horror").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show filter without applying").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38200
+ editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("--style <prompt>", "Style description (e.g., 'cinematic warm')").option("--preset <name>", "Built-in preset: film-noir, vintage, cinematic-warm, cool-tones, high-contrast, pastel, cyberpunk, horror").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show filter without applying").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38201
38201
  const startedAt = Date.now();
38202
38202
  try {
38203
38203
  if (options.style) rejectControlChars(options.style);
@@ -38285,7 +38285,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38285
38285
  exitWithError(apiError(`Color grading failed: ${error.message}`));
38286
38286
  }
38287
38287
  });
38288
- editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("-t, --text <texts...>", "Text lines to overlay (repeat for multiple)").option("-s, --style <style>", "Overlay style: lower-third, center-bold, subtitle, minimal", "lower-third").option("--font-size <size>", "Font size in pixels (auto-calculated if omitted)").option("--font-color <color>", "Font color (default: white)", "white").option("--fade <seconds>", "Fade in/out duration in seconds", "0.3").option("--start <seconds>", "Start time in seconds", "0").option("--end <seconds>", "End time in seconds (default: video duration)").option("-o, --output <path>", "Output video file path").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38288
+ editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("--text <texts...>", "Text lines to overlay (repeat for multiple)").option("--style <style>", "Overlay style: lower-third, center-bold, subtitle, minimal", "lower-third").option("--font-size <size>", "Font size in pixels (auto-calculated if omitted)").option("--font-color <color>", "Font color (default: white)", "white").option("--fade <seconds>", "Fade in/out duration in seconds", "0.3").option("--start <seconds>", "Start time in seconds", "0").option("--end <seconds>", "End time in seconds (default: video duration)").option("-o, --output <path>", "Output video file path").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38289
38289
  const startedAt = Date.now();
38290
38290
  try {
38291
38291
  if (!options.text || options.text.length === 0) {
@@ -38361,7 +38361,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38361
38361
  exitWithError(generalError(`Text overlay failed: ${msg}`));
38362
38362
  }
38363
38363
  });
38364
- editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("-s, --style <style>", "Style: dramatic, smooth, action", "dramatic").option("--min-speed <factor>", "Minimum speed factor", "0.25").option("--max-speed <factor>", "Maximum speed factor", "4.0").option("--analyze-only", "Show keyframes without applying").option("-l, --language <lang>", "Language code for transcription").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38364
+ editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("--style <style>", "Style: dramatic, smooth, action", "dramatic").option("--min-speed <factor>", "Minimum speed factor", "0.25").option("--max-speed <factor>", "Maximum speed factor", "4.0").option("--analyze-only", "Show keyframes without applying").option("-l, --language <lang>", "Language code for transcription").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38365
38365
  const startedAt = Date.now();
38366
38366
  try {
38367
38367
  if (options.output) {
@@ -38481,7 +38481,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38481
38481
  exitWithError(generalError(`Speed ramping failed: ${msg}`));
38482
38482
  }
38483
38483
  });
38484
- editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("-f, --focus <mode>", "Focus mode: auto, face, center, action", "auto").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show crop regions without applying").option("--keyframes <path>", "Export keyframes to JSON file").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38484
+ editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("--focus <mode>", "Focus mode: auto, face, center, action", "auto").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show crop regions without applying").option("--keyframes <path>", "Export keyframes to JSON file").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38485
38485
  const startedAt = Date.now();
38486
38486
  try {
38487
38487
  if (options.output) {
@@ -38628,7 +38628,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38628
38628
  exitWithError(generalError(`Reframe failed: ${msg}`));
38629
38629
  }
38630
38630
  });
38631
- editCommand.command("image").description("Edit image(s) using AI (Gemini/OpenAI/Grok)").argument("<images...>", "Input image file(s) followed by edit prompt").option("-p, --provider <provider>", "Provider: gemini (default), openai, grok", "gemini").option("-k, --api-key <key>", "API key (or set env variable)").option("-o, --output <path>", "Output file path", "edited.png").option("-m, --model <model>", "Model: flash/3.1-flash/latest/pro (Gemini only)", "flash").option("-r, --ratio <ratio>", "Output aspect ratio").option("-s, --size <resolution>", "Resolution: 1K, 2K, 4K (Gemini Pro only)").option("--dry-run", "Preview parameters without executing").action(async (args, options) => {
38631
+ editCommand.command("image").description("Edit image(s) using AI (Gemini, OpenAI, or Grok)").argument("<images...>", "Input image file(s) followed by edit prompt").option("-p, --provider <provider>", "Provider: gemini (default), openai, grok", "gemini").option("-k, --api-key <key>", "API key (or set env variable)").option("-o, --output <path>", "Output file path", "edited.png").option("-m, --model <model>", "Model: flash/3.1-flash/latest/pro (Gemini only)", "flash").option("-r, --ratio <ratio>", "Output aspect ratio").option("--size <resolution>", "Resolution: 1K, 2K, 4K (Gemini Pro only)").option("--dry-run", "Preview parameters without executing").action(async (args, options) => {
38632
38632
  const startedAt = Date.now();
38633
38633
  try {
38634
38634
  if (args.length < 2) {
@@ -38757,7 +38757,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38757
38757
  exitWithError(apiError(`Image editing failed: ${msg}`, true));
38758
38758
  }
38759
38759
  });
38760
- editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-f, --factor <number>", "Slow motion factor: 2, 4, or 8", "2").option("--fps <number>", "Target output FPS").option("--quality <mode>", "Quality: fast or quality", "quality").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38760
+ editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("--factor <number>", "Slow motion factor: 2, 4, or 8", "2").option("--fps <number>", "Target output FPS").option("--mode <mode>", "Speed/quality tradeoff: fast or quality", "quality").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38761
38761
  const startedAt = Date.now();
38762
38762
  try {
38763
38763
  if (options.output) {
@@ -38778,7 +38778,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38778
38778
  videoPath: absPath,
38779
38779
  factor,
38780
38780
  fps: options.fps ? parseInt(options.fps) : void 0,
38781
- quality: options.quality
38781
+ mode: options.mode
38782
38782
  }
38783
38783
  }
38784
38784
  });
@@ -38801,7 +38801,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38801
38801
  const [num, den] = fpsOut.trim().split("/").map(Number);
38802
38802
  const originalFps = num / (den || 1);
38803
38803
  const targetFps = options.fps ? parseInt(options.fps) : originalFps * factor;
38804
- const mi = options.quality === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
38804
+ const mi = options.mode === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
38805
38805
  spinner2.text = `Interpolating frames (${originalFps.toFixed(1)} \u2192 ${targetFps}fps)...`;
38806
38806
  await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
38807
38807
  spinner2.succeed(source_default.green(`Created ${factor}x slow motion`));
@@ -38839,7 +38839,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38839
38839
  exitWithError(generalError(`Frame interpolation failed: ${msg}`));
38840
38840
  }
38841
38841
  });
38842
- editCommand.command("upscale-video").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-s, --scale <factor>", "Scale factor: 2 or 4", "2").option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan").option("--ffmpeg", "Use FFmpeg lanczos (free, no API)").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").option("--no-wait", "Start processing and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38842
+ editCommand.command("upscale").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("--scale <factor>", "Scale factor: 2 or 4", "2").option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan").option("--ffmpeg", "Use FFmpeg lanczos (free, no API)").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").option("--no-wait", "Start processing and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
38843
38843
  const startedAt = Date.now();
38844
38844
  try {
38845
38845
  if (options.output) {
@@ -38852,7 +38852,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38852
38852
  }
38853
38853
  if (options.dryRun) {
38854
38854
  outputSuccess({
38855
- command: "edit upscale-video",
38855
+ command: "edit upscale",
38856
38856
  startedAt,
38857
38857
  dryRun: true,
38858
38858
  data: {
@@ -38888,7 +38888,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38888
38888
  spinner3.succeed(source_default.green(`Upscaled to ${newWidth}x${newHeight}`));
38889
38889
  if (isJsonMode()) {
38890
38890
  outputSuccess({
38891
- command: "edit upscale-video",
38891
+ command: "edit upscale",
38892
38892
  startedAt,
38893
38893
  data: {
38894
38894
  dimensions: `${newWidth}x${newHeight}`,
@@ -39332,7 +39332,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
39332
39332
  return result;
39333
39333
  }
39334
39334
  function registerReviewCommand(aiCommand) {
39335
- aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<video>", "Video file path").option("-s, --storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
39335
+ aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<source>", "Video file path").option("--storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
39336
39336
  const startedAt = Date.now();
39337
39337
  try {
39338
39338
  if (options.output) {
@@ -39340,7 +39340,7 @@ function registerReviewCommand(aiCommand) {
39340
39340
  }
39341
39341
  if (options.dryRun) {
39342
39342
  outputSuccess({
39343
- command: "ai review",
39343
+ command: "inspect review",
39344
39344
  startedAt,
39345
39345
  dryRun: true,
39346
39346
  data: {
@@ -39884,7 +39884,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
39884
39884
  }
39885
39885
  }
39886
39886
  function registerHighlightsCommands(aiCommand) {
39887
- aiCommand.command("highlights").description("Extract highlights from long-form video/audio content").argument("<media>", "Video or audio file path").option("-o, --output <path>", "Output JSON file with highlights").option("--project <path>", "Create project with highlight clips").option("-d, --duration <seconds>", "Target highlight reel duration", "60").option("-n, --count <number>", "Maximum number of highlights").option("-t, --threshold <value>", "Confidence threshold (0-1)", "0.7").option("--criteria <type>", "Selection criteria: emotional | informative | funny | all", "all").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
39887
+ aiCommand.command("highlights").description("Extract highlights from long-form video/audio content").argument("<media>", "Video or audio file path").option("-o, --output <path>", "Output JSON file with highlights").option("--project <path>", "Create project with highlight clips").option("-d, --duration <seconds>", "Target highlight reel duration", "60").option("--count <number>", "Maximum number of highlights").option("--threshold <value>", "Confidence threshold (0-1)", "0.7").option("--criteria <type>", "Selection criteria: emotional | informative | funny | all", "all").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
39888
39888
  const startedAt = Date.now();
39889
39889
  try {
39890
39890
  if (options.output) {
@@ -40185,7 +40185,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
40185
40185
  exitWithError(apiError(`Highlight extraction failed: ${error instanceof Error ? error.message : String(error)}`, true));
40186
40186
  }
40187
40187
  });
40188
- aiCommand.command("auto-shorts").alias("shorts").description("Auto-generate shorts from long-form video").argument("<video>", "Video file path").option("-o, --output <path>", "Output file (single) or directory (multiple)").option("-d, --duration <seconds>", "Target duration in seconds (15-60)", "60").option("-n, --count <number>", "Number of shorts to generate", "1").option("-a, --aspect <ratio>", "Aspect ratio: 9:16, 1:1", "9:16").option("--output-dir <dir>", "Output directory for multiple shorts").option("--add-captions", "Add auto-generated captions").option("--caption-style <style>", "Caption style: minimal, bold, animated", "bold").option("--analyze-only", "Show segments without generating").option("-l, --language <lang>", "Language code for transcription").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
40188
+ aiCommand.command("auto-shorts").alias("shorts").description("Auto-generate shorts from long-form video").argument("<video>", "Video file path").option("-o, --output <path>", "Output file (single) or directory (multiple)").option("-d, --duration <seconds>", "Target duration in seconds (15-60)", "60").option("--count <number>", "Number of shorts to generate", "1").option("-a, --aspect <ratio>", "Aspect ratio: 9:16, 1:1", "9:16").option("--output-dir <dir>", "Output directory for multiple shorts").option("--add-captions", "Add auto-generated captions").option("--caption-style <style>", "Caption style: minimal, bold, animated", "bold").option("--analyze-only", "Show segments without generating").option("-l, --language <lang>", "Language code for transcription").option("--use-gemini", "Use Gemini Video Understanding for enhanced visual+audio analysis").option("--low-res", "Use low resolution mode for longer videos (Gemini only)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
40189
40189
  const startedAt = Date.now();
40190
40190
  try {
40191
40191
  if (options.output) {
@@ -50304,15 +50304,14 @@ var init_detect = __esm({
50304
50304
  "use strict";
50305
50305
  init_esm();
50306
50306
  init_source();
50307
- init_ora();
50308
50307
  init_engine();
50309
50308
  init_exec_safe();
50310
50309
  init_output();
50311
50310
  init_validate();
50312
50311
  detectCommand = new Command("detect").description("Auto-detect scenes, beats, and silences in media");
50313
- detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("-t, --threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("-p, --project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
50312
+ detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("--threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("--project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
50314
50313
  const startedAt = Date.now();
50315
- const spinner2 = ora("Detecting scenes...").start();
50314
+ const spinner2 = spinner("Detecting scenes...").start();
50316
50315
  try {
50317
50316
  if (options.output) {
50318
50317
  validateOutputPath(options.output);
@@ -50429,9 +50428,9 @@ var init_detect = __esm({
50429
50428
  exitWithError(generalError(`Scene detection failed: ${msg}`));
50430
50429
  }
50431
50430
  });
50432
- detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("-n, --noise <dB>", "Noise threshold in dB", "-30").option("-d, --duration <sec>", "Minimum silence duration", "0.5").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
50431
+ detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("--noise <dB>", "Noise threshold in dB", "-30").option("-d, --duration <sec>", "Minimum silence duration", "0.5").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
50433
50432
  const startedAt = Date.now();
50434
- const spinner2 = ora("Detecting silence...").start();
50433
+ const spinner2 = spinner("Detecting silence...").start();
50435
50434
  try {
50436
50435
  if (options.output) {
50437
50436
  validateOutputPath(options.output);
@@ -50517,7 +50516,7 @@ var init_detect = __esm({
50517
50516
  });
50518
50517
  detectCommand.command("beats").description("Detect beats in audio (for music sync)").argument("<audio>", "Audio file path").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (audioPath, options) => {
50519
50518
  const startedAt = Date.now();
50520
- const spinner2 = ora("Detecting beats...").start();
50519
+ const spinner2 = spinner("Detecting beats...").start();
50521
50520
  try {
50522
50521
  if (options.output) {
50523
50522
  validateOutputPath(options.output);
@@ -57372,7 +57371,7 @@ var init_scene2 = __esm({
57372
57371
  )
57373
57372
  });
57374
57373
  sceneStylesTool = defineTool({
57375
- name: "scene_styles",
57374
+ name: "scene_list_styles",
57376
57375
  category: "scene",
57377
57376
  cost: "free",
57378
57377
  description: "List the 8 vendored visual identities available for `init --visual-style` (Swiss Pulse, Data Drift, \u2026) or, when `name` is provided, return the full DESIGN.md hard-gate body for one style. The DESIGN.md content is what the LLM uses as a non-negotiable visual rulebook during compose-scenes-with-skills.",
@@ -57383,7 +57382,7 @@ var init_scene2 = __esm({
57383
57382
  if (!style) {
57384
57383
  return {
57385
57384
  success: false,
57386
- error: `Unknown visual style "${args.name}". Run scene_styles with no name to list all 8.`
57385
+ error: `Unknown visual style "${args.name}". Run scene_list_styles with no name to list all 8.`
57387
57386
  };
57388
57387
  }
57389
57388
  return {
@@ -57423,7 +57422,7 @@ var init_scene2 = __esm({
57423
57422
  (s) => ` \u2022 ${s.name} (${s.slug}) \u2014 ${s.mood}; best for ${s.bestFor}`
57424
57423
  ),
57425
57424
  ``,
57426
- `Run scene_styles { name: "<slug>" } to fetch the full DESIGN.md hard-gate body for one style.`
57425
+ `Run scene_list_styles { name: "<slug>" } to fetch the full DESIGN.md hard-gate body for one style.`
57427
57426
  ]
57428
57427
  };
57429
57428
  }
@@ -60488,11 +60487,11 @@ var init_export = __esm({
60488
60487
  BITRATE_PATTERN = /^\d+(?:\.\d+)?[kKmMgG]?$/;
60489
60488
  RESOLUTION_PATTERN = /^\d+x\d+$/;
60490
60489
  VALID_CODECS = ["h264", "h265", "vp9"];
60491
- exportCommand = new Command("export").description("Export project to video file").argument("<project>", "Project file path").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Output format (mp4, webm, mov, gif)", "mp4").option(
60492
- "-p, --preset <preset>",
60490
+ exportCommand = new Command("export").description("Export project to video file").argument("<project>", "Project file path").option("-o, --output <path>", "Output file path").option("--format <format>", "Output format (mp4, webm, mov, gif)", "mp4").option(
60491
+ "--preset <preset>",
60493
60492
  "Quality preset (draft, standard, high, ultra)",
60494
60493
  "standard"
60495
- ).option("-y, --overwrite", "Overwrite output file if exists", false).option("-g, --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", `
60494
+ ).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", `
60496
60495
  Examples:
60497
60496
  $ vibe export project.vibe.json -o output.mp4
60498
60497
  $ vibe export project.vibe.json -o output.mp4 -p high -y
@@ -61650,7 +61649,7 @@ Safe to invoke on user-provided projects.
61650
61649
 
61651
61650
  \`\`\`bash
61652
61651
  vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61653
- vibe scene styles [<name>] # list / show vendored visual identities
61652
+ vibe scene list-styles [<name>] # list / show vendored visual identities
61654
61653
  vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
61655
61654
  vibe scene add <name> --style <preset> [...]
61656
61655
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
@@ -61838,7 +61837,7 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
61838
61837
  ],
61839
61838
  relatedCommands: [
61840
61839
  "vibe init",
61841
- "vibe scene styles",
61840
+ "vibe scene list-styles",
61842
61841
  "vibe scene install-skill",
61843
61842
  "vibe scene compose-prompts",
61844
61843
  "vibe build",
@@ -62167,7 +62166,7 @@ init_source();
62167
62166
  // package.json
62168
62167
  var package_default = {
62169
62168
  name: "@vibeframe/cli",
62170
- version: "0.76.0",
62169
+ version: "0.78.0",
62171
62170
  description: "VibeFrame CLI - natural language video editing from the terminal",
62172
62171
  private: false,
62173
62172
  license: "MIT",
@@ -62389,7 +62388,7 @@ async function resolveProjectPath(inputPath) {
62389
62388
  }
62390
62389
  return filePath;
62391
62390
  }
62392
- var projectCommand = new Command("project").description("Project management commands").addHelpText("after", `
62391
+ var projectCommand = new Command("project").description("Manage VibeFrame .vibe.json project files (create, info, set)").addHelpText("after", `
62393
62392
  Examples:
62394
62393
  $ vibe project create "my-video" -o project.vibe.json
62395
62394
  $ vibe project create "shorts" -r 9:16 -o vertical.vibe.json
@@ -62398,7 +62397,7 @@ Examples:
62398
62397
 
62399
62398
  Cost: Free (no API keys needed). Projects are saved as .vibe.json files.
62400
62399
  Run 'vibe schema project.<command>' for structured parameter info.`);
62401
- projectCommand.command("create").description("Create a new project").argument("<name>", "Project name or path (e.g., 'my-project' or 'output/my-project')").option("-o, --output <path>", "Output file path (overrides name-based path)").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)", "16:9").option("-f, --fps <fps>", "Frame rate", "30").option("--dry-run", "Preview parameters without executing").action(async (name, options) => {
62400
+ projectCommand.command("create").description("Create a new project").argument("<name>", "Project name or path (e.g., 'my-project' or 'output/my-project')").option("-o, --output <path>", "Output file path (overrides name-based path)").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)", "16:9").option("--fps <fps>", "Frame rate", "30").option("--dry-run", "Preview parameters without executing").action(async (name, options) => {
62402
62401
  const startedAt = Date.now();
62403
62402
  const spinner2 = ora("Creating project...").start();
62404
62403
  try {
@@ -62512,7 +62511,7 @@ projectCommand.command("info").description("Show project information").argument(
62512
62511
  exitWithError(generalError(`Failed to load project: ${msg}`));
62513
62512
  }
62514
62513
  });
62515
- projectCommand.command("set").description("Update project settings").argument("<file>", "Project file path").option("-n, --name <name>", "Project name").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)").option("-f, --fps <fps>", "Frame rate").option("--dry-run", "Preview parameters without executing").action(async (file, options) => {
62514
+ projectCommand.command("set").description("Update project settings").argument("<file>", "Project file path").option("--name <name>", "Project name").option("-r, --ratio <ratio>", "Aspect ratio (16:9, 9:16, 1:1, 4:5)").option("--fps <fps>", "Frame rate").option("--dry-run", "Preview parameters without executing").action(async (file, options) => {
62516
62515
  const startedAt = Date.now();
62517
62516
  const spinner2 = ora("Updating project...").start();
62518
62517
  try {
@@ -62584,15 +62583,15 @@ var timelineCommand = new Command("timeline").description("Timeline editing comm
62584
62583
  Examples:
62585
62584
  $ vibe timeline add-source project.vibe.json video.mp4 # Returns source ID
62586
62585
  $ vibe timeline add-clip project.vibe.json <source-id> # Add clip from source
62587
- $ vibe timeline trim project.vibe.json <clip-id> --start 5 --end 30
62588
- $ vibe timeline split project.vibe.json <clip-id> --time 10
62586
+ $ vibe timeline trim-clip project.vibe.json <clip-id> --start 5 --end 30
62587
+ $ vibe timeline split-clip project.vibe.json <clip-id> --time 10
62589
62588
  $ vibe timeline list project.vibe.json --json
62590
- $ vibe timeline delete project.vibe.json <clip-id>
62589
+ $ vibe timeline delete-clip project.vibe.json <clip-id>
62591
62590
 
62592
- Typical workflow: create project \u2192 add-source \u2192 add-clip \u2192 trim/split \u2192 export
62591
+ Typical workflow: create project \u2192 add-source \u2192 add-clip \u2192 trim-clip/split-clip \u2192 export
62593
62592
  Cost: Free (no API keys needed).
62594
62593
  Run 'vibe schema timeline.<command>' for structured parameter info.`);
62595
- timelineCommand.command("add-source").description("Add a media source to the project").argument("<project>", "Project file path").argument("<media>", "Media file path").option("-n, --name <name>", "Source name (defaults to filename)").option("-t, --type <type>", "Media type (video, audio, image, lottie)").option("-d, --duration <seconds>", "Duration in seconds (required for images)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, mediaPath, options) => {
62594
+ timelineCommand.command("add-source").description("Add a media source to the project").argument("<project>", "Project file path").argument("<media>", "Media file path").option("--name <name>", "Source name (defaults to filename)").option("--type <type>", "Media type (video, audio, image, lottie)").option("-d, --duration <seconds>", "Duration in seconds (required for images)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, mediaPath, options) => {
62596
62595
  const startedAt = Date.now();
62597
62596
  const spinner2 = ora("Adding source...").start();
62598
62597
  try {
@@ -62654,7 +62653,7 @@ timelineCommand.command("add-source").description("Add a media source to the pro
62654
62653
  exitWithError(generalError(`Failed to add source: ${msg}`));
62655
62654
  }
62656
62655
  });
62657
- timelineCommand.command("add-clip").description("Add a clip to the timeline").argument("<project>", "Project file path").argument("<source-id>", "Source ID to use").option("-t, --track <id>", "Track ID (defaults to first matching track)").option("-s, --start <seconds>", "Start time in timeline", "0").option("-d, --duration <seconds>", "Clip duration (defaults to source duration)").option("--offset <seconds>", "Source start offset", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, sourceId, options) => {
62656
+ timelineCommand.command("add-clip").description("Add a clip to the timeline").argument("<project>", "Project file path").argument("<source-id>", "Source ID to use").option("--track <id>", "Track ID (defaults to first matching track)").option("--start <seconds>", "Start time in timeline", "0").option("-d, --duration <seconds>", "Clip duration (defaults to source duration)").option("--offset <seconds>", "Source start offset", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, sourceId, options) => {
62658
62657
  const startedAt = Date.now();
62659
62658
  const spinner2 = ora("Adding clip...").start();
62660
62659
  try {
@@ -62736,7 +62735,7 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
62736
62735
  exitWithError(generalError(`Failed to add clip: ${msg}`));
62737
62736
  }
62738
62737
  });
62739
- timelineCommand.command("add-track").description("Add a new track").argument("<project>", "Project file path").argument("<type>", "Track type (video, audio)").option("-n, --name <name>", "Track name").option("--dry-run", "Preview parameters without executing").action(async (projectPath, type, options) => {
62738
+ timelineCommand.command("add-track").description("Add a new track").argument("<project>", "Project file path").argument("<type>", "Track type (video, audio)").option("--name <name>", "Track name").option("--dry-run", "Preview parameters without executing").action(async (projectPath, type, options) => {
62740
62739
  const startedAt = Date.now();
62741
62740
  const spinner2 = ora("Adding track...").start();
62742
62741
  try {
@@ -62793,7 +62792,7 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
62793
62792
  exitWithError(generalError(`Failed to add track: ${msg}`));
62794
62793
  }
62795
62794
  });
62796
- timelineCommand.command("add-effect").description("Add an effect to a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, brightness, contrast, saturation, speed, volume)").option("-s, --start <seconds>", "Effect start time (relative to clip)", "0").option("-d, --duration <seconds>", "Effect duration (defaults to clip duration)").option("-p, --params <json>", "Effect parameters as JSON", "{}").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, effectType, options) => {
62795
+ timelineCommand.command("add-effect").description("Add an effect to a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, brightness, contrast, saturation, speed, volume)").option("--start <seconds>", "Effect start time (relative to clip)", "0").option("-d, --duration <seconds>", "Effect duration (defaults to clip duration)").option("--params <json>", "Effect parameters as JSON", "{}").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, effectType, options) => {
62797
62796
  const startedAt = Date.now();
62798
62797
  const spinner2 = ora("Adding effect...").start();
62799
62798
  try {
@@ -62865,14 +62864,14 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
62865
62864
  exitWithError(generalError(`Failed to add effect: ${msg}`));
62866
62865
  }
62867
62866
  });
62868
- timelineCommand.command("trim").description("Trim a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").option("--start <seconds>", "New start time").option("--duration <seconds>", "New duration").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
62867
+ timelineCommand.command("trim-clip").description("Trim a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID").option("--start <seconds>", "New start time").option("--duration <seconds>", "New duration").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
62869
62868
  const startedAt = Date.now();
62870
62869
  const spinner2 = ora("Trimming clip...").start();
62871
62870
  try {
62872
62871
  validateResourceId(clipId);
62873
62872
  if (options.dryRun) {
62874
62873
  outputSuccess({
62875
- command: "timeline trim",
62874
+ command: "timeline trim-clip",
62876
62875
  startedAt,
62877
62876
  dryRun: true,
62878
62877
  data: {
@@ -62906,7 +62905,7 @@ timelineCommand.command("trim").description("Trim a clip").argument("<project>",
62906
62905
  spinner2.succeed(source_default.green("Clip trimmed"));
62907
62906
  if (isJsonMode()) {
62908
62907
  outputSuccess({
62909
- command: "timeline trim",
62908
+ command: "timeline trim-clip",
62910
62909
  startedAt,
62911
62910
  data: {
62912
62911
  id: updatedClip.id,
@@ -63027,14 +63026,14 @@ timelineCommand.command("list").description("List timeline contents").argument("
63027
63026
  exitWithError(generalError(`Failed to list timeline: ${msg}`));
63028
63027
  }
63029
63028
  });
63030
- timelineCommand.command("split").description("Split a clip at a specific time").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to split").option("-t, --time <seconds>", "Split time relative to clip start", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63029
+ timelineCommand.command("split-clip").description("Split a clip at a specific time").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to split").option("--time <seconds>", "Split time relative to clip start", "0").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63031
63030
  const startedAt = Date.now();
63032
63031
  const spinner2 = ora("Splitting clip...").start();
63033
63032
  try {
63034
63033
  validateResourceId(clipId);
63035
63034
  if (options.dryRun) {
63036
63035
  outputSuccess({
63037
- command: "timeline split",
63036
+ command: "timeline split-clip",
63038
63037
  startedAt,
63039
63038
  dryRun: true,
63040
63039
  data: {
@@ -63071,7 +63070,7 @@ timelineCommand.command("split").description("Split a clip at a specific time").
63071
63070
  spinner2.succeed(source_default.green("Clip split successfully"));
63072
63071
  if (isJsonMode()) {
63073
63072
  outputSuccess({
63074
- command: "timeline split",
63073
+ command: "timeline split-clip",
63075
63074
  startedAt,
63076
63075
  data: {
63077
63076
  first: { id: first.id, duration: first.duration },
@@ -63089,14 +63088,14 @@ timelineCommand.command("split").description("Split a clip at a specific time").
63089
63088
  exitWithError(generalError(`Failed to split clip: ${msg}`));
63090
63089
  }
63091
63090
  });
63092
- timelineCommand.command("duplicate").description("Duplicate a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to duplicate").option("-t, --time <seconds>", "Start time for duplicate (default: after original)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63091
+ timelineCommand.command("duplicate-clip").description("Duplicate a clip").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to duplicate").option("--time <seconds>", "Start time for duplicate (default: after original)").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63093
63092
  const startedAt = Date.now();
63094
63093
  const spinner2 = ora("Duplicating clip...").start();
63095
63094
  try {
63096
63095
  validateResourceId(clipId);
63097
63096
  if (options.dryRun) {
63098
63097
  outputSuccess({
63099
- command: "timeline duplicate",
63098
+ command: "timeline duplicate-clip",
63100
63099
  startedAt,
63101
63100
  dryRun: true,
63102
63101
  data: {
@@ -63128,7 +63127,7 @@ timelineCommand.command("duplicate").description("Duplicate a clip").argument("<
63128
63127
  spinner2.succeed(source_default.green(`Clip duplicated: ${duplicated.id}`));
63129
63128
  if (isJsonMode()) {
63130
63129
  outputSuccess({
63131
- command: "timeline duplicate",
63130
+ command: "timeline duplicate-clip",
63132
63131
  startedAt,
63133
63132
  data: {
63134
63133
  id: duplicated.id,
@@ -63148,14 +63147,14 @@ timelineCommand.command("duplicate").description("Duplicate a clip").argument("<
63148
63147
  exitWithError(generalError(`Failed to duplicate clip: ${msg}`));
63149
63148
  }
63150
63149
  });
63151
- timelineCommand.command("delete").description("Delete a clip from the timeline").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to delete").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63150
+ timelineCommand.command("delete-clip").description("Delete a clip from the timeline").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to delete").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63152
63151
  const startedAt = Date.now();
63153
63152
  const spinner2 = ora("Deleting clip...").start();
63154
63153
  try {
63155
63154
  validateResourceId(clipId);
63156
63155
  if (options.dryRun) {
63157
63156
  outputSuccess({
63158
- command: "timeline delete",
63157
+ command: "timeline delete-clip",
63159
63158
  startedAt,
63160
63159
  dryRun: true,
63161
63160
  data: {
@@ -63185,7 +63184,7 @@ timelineCommand.command("delete").description("Delete a clip from the timeline")
63185
63184
  spinner2.succeed(source_default.green("Clip deleted"));
63186
63185
  if (isJsonMode()) {
63187
63186
  outputSuccess({
63188
- command: "timeline delete",
63187
+ command: "timeline delete-clip",
63189
63188
  startedAt,
63190
63189
  data: {
63191
63190
  id: clipId,
@@ -63200,7 +63199,7 @@ timelineCommand.command("delete").description("Delete a clip from the timeline")
63200
63199
  exitWithError(generalError(`Failed to delete clip: ${msg}`));
63201
63200
  }
63202
63201
  });
63203
- timelineCommand.command("move").description("Move a clip to a new position").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to move").option("-t, --time <seconds>", "New start time").option("--track <track-id>", "Move to different track").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63202
+ timelineCommand.command("move-clip").description("Move a clip to a new position").argument("<project>", "Project file path").argument("<clip-id>", "Clip ID to move").option("--time <seconds>", "New start time").option("--track <track-id>", "Move to different track").option("--dry-run", "Preview parameters without executing").action(async (projectPath, clipId, options) => {
63204
63203
  const startedAt = Date.now();
63205
63204
  const spinner2 = ora("Moving clip...").start();
63206
63205
  try {
@@ -63208,7 +63207,7 @@ timelineCommand.command("move").description("Move a clip to a new position").arg
63208
63207
  if (options.track) validateResourceId(options.track);
63209
63208
  if (options.dryRun) {
63210
63209
  outputSuccess({
63211
- command: "timeline move",
63210
+ command: "timeline move-clip",
63212
63211
  startedAt,
63213
63212
  dryRun: true,
63214
63213
  data: {
@@ -63243,7 +63242,7 @@ timelineCommand.command("move").description("Move a clip to a new position").arg
63243
63242
  spinner2.succeed(source_default.green("Clip moved"));
63244
63243
  if (isJsonMode()) {
63245
63244
  outputSuccess({
63246
- command: "timeline move",
63245
+ command: "timeline move-clip",
63247
63246
  startedAt,
63248
63247
  data: {
63249
63248
  id: updated.id,
@@ -63311,10 +63310,29 @@ Use '--fields response,model' to limit output size.
63311
63310
  Run 'vibe schema inspect.<command>' for structured parameter info.
63312
63311
  `
63313
63312
  );
63314
- analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("-v, --verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").action(async (source, prompt3, options) => {
63313
+ analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63315
63314
  const startedAt = Date.now();
63316
63315
  try {
63317
63316
  rejectControlChars(prompt3);
63317
+ if (options.dryRun) {
63318
+ outputSuccess({
63319
+ command: "inspect media",
63320
+ startedAt,
63321
+ dryRun: true,
63322
+ data: {
63323
+ params: {
63324
+ source,
63325
+ prompt: prompt3,
63326
+ model: options.model,
63327
+ fps: options.fps,
63328
+ start: options.start,
63329
+ end: options.end,
63330
+ lowRes: options.lowRes ?? false
63331
+ }
63332
+ }
63333
+ });
63334
+ return;
63335
+ }
63318
63336
  if (options.apiKey) {
63319
63337
  process.env.GOOGLE_API_KEY = options.apiKey;
63320
63338
  } else {
@@ -63344,7 +63362,7 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
63344
63362
  data.totalTokens = result.totalTokens;
63345
63363
  }
63346
63364
  outputSuccess({
63347
- command: "analyze media",
63365
+ command: "inspect media",
63348
63366
  startedAt,
63349
63367
  data
63350
63368
  });
@@ -63369,10 +63387,29 @@ analyzeCommand.command("media").description("Analyze any media: images, videos,
63369
63387
  exitWithError(apiError(`Analysis failed: ${error.message}`));
63370
63388
  }
63371
63389
  });
63372
- analyzeCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("-v, --verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").action(async (source, prompt3, options) => {
63390
+ analyzeCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63373
63391
  const startedAt = Date.now();
63374
63392
  try {
63375
63393
  rejectControlChars(prompt3);
63394
+ if (options.dryRun) {
63395
+ outputSuccess({
63396
+ command: "inspect video",
63397
+ startedAt,
63398
+ dryRun: true,
63399
+ data: {
63400
+ params: {
63401
+ source,
63402
+ prompt: prompt3,
63403
+ model: options.model,
63404
+ fps: options.fps,
63405
+ start: options.start,
63406
+ end: options.end,
63407
+ lowRes: options.lowRes ?? false
63408
+ }
63409
+ }
63410
+ });
63411
+ return;
63412
+ }
63376
63413
  if (options.apiKey) {
63377
63414
  process.env.GOOGLE_API_KEY = options.apiKey;
63378
63415
  } else {
@@ -63402,7 +63439,7 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
63402
63439
  data.totalTokens = result.totalTokens;
63403
63440
  }
63404
63441
  outputSuccess({
63405
- command: "analyze video",
63442
+ command: "inspect video",
63406
63443
  startedAt,
63407
63444
  data
63408
63445
  });
@@ -63427,10 +63464,25 @@ analyzeCommand.command("video").description("Analyze video using Gemini (summari
63427
63464
  }
63428
63465
  });
63429
63466
  registerReviewCommand(analyzeCommand);
63430
- analyzeCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Project file path").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").action(async (projectPath, instruction, options) => {
63467
+ analyzeCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Project file path").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").option("--dry-run", "Preview parameters without executing").action(async (projectPath, instruction, options) => {
63431
63468
  const startedAt = Date.now();
63432
63469
  try {
63433
63470
  rejectControlChars(instruction);
63471
+ if (options.dryRun) {
63472
+ outputSuccess({
63473
+ command: "inspect suggest",
63474
+ startedAt,
63475
+ dryRun: true,
63476
+ data: {
63477
+ params: {
63478
+ project: projectPath,
63479
+ instruction,
63480
+ apply: options.apply ?? false
63481
+ }
63482
+ }
63483
+ });
63484
+ return;
63485
+ }
63434
63486
  const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
63435
63487
  const spinner2 = ora("Initializing Gemini...").start();
63436
63488
  const filePath = resolve32(process.cwd(), projectPath);
@@ -63445,7 +63497,7 @@ analyzeCommand.command("suggest").description("Get AI edit suggestions using Gem
63445
63497
  spinner2.succeed(source_default.green(`Found ${suggestions.length} suggestion(s)`));
63446
63498
  if (isJsonMode()) {
63447
63499
  outputSuccess({
63448
- command: "analyze suggest",
63500
+ command: "inspect suggest",
63449
63501
  startedAt,
63450
63502
  data: {
63451
63503
  suggestions: suggestions.map((s) => ({ type: s.type, description: s.description, confidence: s.confidence, clipIds: s.clipIds, params: s.params }))
@@ -63518,7 +63570,7 @@ API Keys:
63518
63570
  Run 'vibe schema audio.<command>' for structured parameter info.
63519
63571
  `
63520
63572
  );
63521
- audioCommand.command("transcribe").description("Transcribe audio using Whisper").argument("<audio>", "Audio file path").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-l, --language <lang>", "Language code (e.g., en, ko)").option("-o, --output <path>", "Output file path").option("-f, --format <format>", "Output format: json, srt, vtt (auto-detected from extension)").action(async (audioPath, options) => {
63573
+ audioCommand.command("transcribe").description("Transcribe audio using Whisper").argument("<audio>", "Audio file path").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-l, --language <lang>", "Language code (e.g., en, ko)").option("-o, --output <path>", "Output file path").option("--format <format>", "Output format: json, srt, vtt (auto-detected from extension)").action(async (audioPath, options) => {
63522
63574
  const startedAt = Date.now();
63523
63575
  try {
63524
63576
  if (options.output) {
@@ -63656,7 +63708,7 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
63656
63708
  exitWithError(apiError(`Audio isolation failed: ${msg}`, true));
63657
63709
  }
63658
63710
  });
63659
- audioCommand.command("clone-voice").description("Clone a voice from audio samples using ElevenLabs").argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-n, --name <name>", "Voice name (required)").option("-d, --description <desc>", "Voice description").option("--labels <json>", `Labels as JSON (e.g., '{"accent": "american"}')`).option("--remove-noise", "Remove background noise from samples").option("--list", "List all available voices").option("--dry-run", "Preview parameters without executing").action(async (samples, options) => {
63711
+ audioCommand.command("clone-voice").description("Clone a voice from audio samples using ElevenLabs").argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("--name <name>", "Voice name (required)").option("-d, --description <desc>", "Voice description").option("--labels <json>", `Labels as JSON (e.g., '{"accent": "american"}')`).option("--remove-noise", "Remove background noise from samples").option("--list", "List all available voices").option("--dry-run", "Preview parameters without executing").action(async (samples, options) => {
63660
63712
  const startedAt = Date.now();
63661
63713
  try {
63662
63714
  if (options.dryRun) {
@@ -63742,7 +63794,7 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
63742
63794
  exitWithError(apiError(`Voice cloning failed: ${msg}`, true));
63743
63795
  }
63744
63796
  });
63745
- audioCommand.command("dub").description("Dub audio/video to another language (transcribe, translate, TTS)").argument("<media>", "Input media file (video or audio)").option("-l, --language <lang>", "Target language code (e.g., es, ko, ja) (required)").option("--source <lang>", "Source language code (default: auto-detect)").option("-v, --voice <id>", "ElevenLabs voice ID for output").option("--analyze-only", "Only analyze and show timing, don't generate audio").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
63797
+ audioCommand.command("dub").description("Dub audio/video to another language (transcribe, translate, TTS)").argument("<media>", "Input media file (video or audio)").option("-l, --language <lang>", "Target language code (e.g., es, ko, ja) (required)").option("--source <lang>", "Source language code (default: auto-detect)").option("--voice <id>", "ElevenLabs voice ID for output").option("--analyze-only", "Only analyze and show timing, don't generate audio").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
63746
63798
  const startedAt = Date.now();
63747
63799
  try {
63748
63800
  if (options.dryRun) {
@@ -63938,7 +63990,7 @@ ${segmentTexts}`,
63938
63990
  exitWithError(apiError(`Dubbing failed: ${msg}`, true));
63939
63991
  }
63940
63992
  });
63941
- audioCommand.command("duck").description("Auto-duck background music when voice is present (FFmpeg)").argument("<music>", "Background music file path").option("-v, --voice <path>", "Voice/narration track (required)").option("-o, --output <path>", "Output audio file path").option("-t, --threshold <dB>", "Sidechain threshold in dB", "-30").option("-r, --ratio <ratio>", "Compression ratio", "3").option("-a, --attack <ms>", "Attack time in ms", "20").option("-l, --release <ms>", "Release time in ms", "200").option("--dry-run", "Preview parameters without executing").action(async (musicPath, options) => {
63993
+ audioCommand.command("duck").description("Auto-duck background music when voice is present (FFmpeg)").argument("<music>", "Background music file path").option("--voice <path>", "Voice/narration track (required)").option("-o, --output <path>", "Output audio file path").option("--threshold <dB>", "Sidechain threshold in dB", "-30").option("-r, --ratio <ratio>", "Compression ratio", "3").option("-a, --attack <ms>", "Attack time in ms", "20").option("-l, --release <ms>", "Release time in ms", "200").option("--dry-run", "Preview parameters without executing").action(async (musicPath, options) => {
63942
63994
  const startedAt = Date.now();
63943
63995
  try {
63944
63996
  if (options.dryRun) {
@@ -64017,7 +64069,7 @@ import { readFile as readFile25, writeFile as writeFile32 } from "node:fs/promis
64017
64069
  import { resolve as resolve34 } from "node:path";
64018
64070
  import { existsSync as existsSync26 } from "node:fs";
64019
64071
  function registerScriptPipelineCommands(aiCommand) {
64020
- 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("-g, --generator <engine>", "Video generator: grok | kling | runway | veo", "grok").option("-i, --image-provider <provider>", "Image provider: gemini | openai | grok", "gemini").option("-v, --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) => {
64072
+ 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) => {
64021
64073
  const startedAt = Date.now();
64022
64074
  try {
64023
64075
  const outputDir = resolve34(process.cwd(), projectDir);
@@ -64189,7 +64241,7 @@ init_ai_highlights();
64189
64241
  init_ai_animated_caption();
64190
64242
  init_output();
64191
64243
  var pipelineCommand = new Command("remix").description(
64192
- "AI media transformations on existing video / audio (highlights, auto-shorts, animated captions). For BUILD-from-text flows see `vibe build`."
64244
+ "Transform existing media (highlights, auto-shorts, animated captions, regenerate-scene)"
64193
64245
  ).addHelpText(
64194
64246
  "after",
64195
64247
  `
@@ -64225,7 +64277,7 @@ Run 'vibe schema remix.<command>' for structured parameter info.
64225
64277
  registerScriptPipelineCommands(pipelineCommand);
64226
64278
  registerHighlightsCommands(pipelineCommand);
64227
64279
  var ANIMATED_CAPTION_STYLES = ["highlight", "bounce", "pop-in", "neon", "karaoke-sweep", "typewriter"];
64228
- pipelineCommand.command("animated-caption").description("Add animated captions with word-by-word effects (Whisper + Remotion/ASS)").argument("<video>", "Video file path").option("-s, --style <preset>", "Style preset (default: highlight)", "highlight").option("--highlight-color <color>", "Active word highlight color", "#FFFF00").option("--font-size <px>", "Font size (default: auto based on resolution)").option("--position <pos>", "Caption position: top, center, bottom", "bottom").option("--words-per-group <n>", "Words shown at once (default: auto 3-5)").option("--max-chars <n>", "Max characters per group").option("-l, --language <lang>", "Whisper language hint").option("--fast", "Use ASS/FFmpeg only (no Remotion, forces ASS tier styles)").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").addHelpText(
64280
+ pipelineCommand.command("animated-caption").description("Add animated captions with word-by-word effects (Whisper + Remotion/ASS)").argument("<video>", "Video file path").option("--style <preset>", "Style preset (default: highlight)", "highlight").option("--highlight-color <color>", "Active word highlight color", "#FFFF00").option("--font-size <px>", "Font size (default: auto based on resolution)").option("--position <pos>", "Caption position: top, center, bottom", "bottom").option("--words-per-group <n>", "Words shown at once (default: auto 3-5)").option("--max-chars <n>", "Max characters per group").option("-l, --language <lang>", "Whisper language hint").option("--fast", "Use ASS/FFmpeg only (no Remotion, forces ASS tier styles)").option("-o, --output <path>", "Output file path").option("--dry-run", "Preview parameters without executing").addHelpText(
64229
64281
  "after",
64230
64282
  `
64231
64283
  Examples:
@@ -68747,7 +68799,7 @@ batchCommand.command("concat").description("Concatenate multiple sources into se
68747
68799
  exitWithError(generalError(`Concat failed: ${msg}`));
68748
68800
  }
68749
68801
  });
68750
- batchCommand.command("apply-effect").description("Apply an effect to multiple clips").argument("<project>", "Project file path").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, etc.)").argument("[clip-ids...]", "Clip IDs to apply effect to (or --all)").option("--all", "Apply to all clips", false).option("-d, --duration <seconds>", "Effect duration", "1").option("-s, --start <seconds>", "Effect start time (relative to clip)", "0").option("--intensity <value>", "Effect intensity (0-1)", "1").option("--dry-run", "Preview parameters without executing").action(
68802
+ batchCommand.command("apply-effect").description("Apply an effect to multiple clips").argument("<project>", "Project file path").argument("<effect-type>", "Effect type (fadeIn, fadeOut, blur, etc.)").argument("[clip-ids...]", "Clip IDs to apply effect to (or --all)").option("--all", "Apply to all clips", false).option("-d, --duration <seconds>", "Effect duration", "1").option("--start <seconds>", "Effect start time (relative to clip)", "0").option("--intensity <value>", "Effect intensity (0-1)", "1").option("--dry-run", "Preview parameters without executing").action(
68751
68803
  async (projectPath, effectType, clipIds, options) => {
68752
68804
  const startedAt = Date.now();
68753
68805
  const spinner2 = ora("Applying effects...").start();
@@ -69745,7 +69797,7 @@ async function runSceneInit(projectDirArg, options, startedAt) {
69745
69797
  }
69746
69798
  const visualStyle = options.visualStyle ? getVisualStyle(String(options.visualStyle)) : void 0;
69747
69799
  if (options.visualStyle && !visualStyle) {
69748
- exitWithError(usageError(`Unknown visual style: ${String(options.visualStyle)}`, `Valid: ${visualStyleNames()}. Browse with \`vibe scene styles\`.`));
69800
+ exitWithError(usageError(`Unknown visual style: ${String(options.visualStyle)}`, `Valid: ${visualStyleNames()}. Browse with \`vibe scene list-styles\`.`));
69749
69801
  }
69750
69802
  const projectDir = resolve42(projectDirArg);
69751
69803
  const projectName = basename14(projectDir);
@@ -71923,10 +71975,13 @@ program2.name("vibe").showSuggestionAfterError(true).description("VibeFrame CLI
71923
71975
  "after",
71924
71976
  `
71925
71977
  Global flags (work with any command):
71978
+ -V, --version Print version and exit
71979
+ -h, --help Print help for the command and exit
71926
71980
  --json Output JSON (auto-enabled when piped)
71927
71981
  --fields Limit output fields (e.g., --fields "path,duration")
71928
- --quiet Output only the result value (path, URL, or ID)
71982
+ -q, --quiet Output only the result value (path, URL, or ID)
71929
71983
  --stdin Read options from stdin as JSON (for agent/script use)
71984
+ --describe Print the command's JSON schema and exit (no execution)
71930
71985
  --dry-run Preview without executing (most commands)
71931
71986
 
71932
71987
  Get started (90% of users start here):
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.76.0",
3
+ "version": "0.78.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.76.0",
186
- "@vibeframe/ai-providers": "0.76.0"
185
+ "@vibeframe/core": "0.78.0",
186
+ "@vibeframe/ai-providers": "0.78.0"
187
187
  },
188
188
  "engines": {
189
189
  "node": ">=20"