@vibeframe/cli 0.115.2 → 0.115.4

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 (33) hide show
  1. package/dist/agent/adapters/gemini.d.ts +2 -0
  2. package/dist/agent/adapters/provider-content.test.d.ts +6 -0
  3. package/dist/agent/memory/index.d.ts +1 -1
  4. package/dist/agent/types.d.ts +8 -0
  5. package/dist/commands/_shared/build-video-providers.d.ts +12 -0
  6. package/dist/commands/_shared/build-video-providers.test.d.ts +1 -0
  7. package/dist/commands/_shared/edit/translate-srt.test.d.ts +1 -0
  8. package/dist/commands/_shared/execute-fill-gaps.d.ts +0 -2
  9. package/dist/commands/_shared/image-cost.d.ts +15 -0
  10. package/dist/commands/_shared/image-cost.test.d.ts +1 -0
  11. package/dist/commands/_shared/openai-image.d.ts +9 -8
  12. package/dist/commands/_shared/project-config.d.ts +2 -1
  13. package/dist/commands/_shared/scene-build.d.ts +3 -2
  14. package/dist/commands/_shared/scene-build.network.test.d.ts +1 -0
  15. package/dist/commands/_shared/video-utils.d.ts +3 -1
  16. package/dist/commands/agent.d.ts +6 -0
  17. package/dist/commands/ai-video.d.ts +2 -0
  18. package/dist/commands/generate/image.d.ts +1 -1
  19. package/dist/commands/generate/video.d.ts +1 -10
  20. package/dist/config/schema.d.ts +1 -1
  21. package/dist/index.js +1198 -817
  22. package/dist/testing/fake-provider-network.d.ts +45 -0
  23. package/dist/tools/define-tool.js +3 -2
  24. package/dist/tools/manifest/edit.d.ts +5 -5
  25. package/dist/tools/manifest/generate.d.ts +12 -6
  26. package/dist/tools/manifest/index.js +1132 -757
  27. package/dist/tools/manifest/scene.d.ts +3 -3
  28. package/dist/tools/manifest/storyboard.d.ts +3 -3
  29. package/dist/utils/image-file.d.ts +19 -0
  30. package/dist/utils/image-file.test.d.ts +1 -0
  31. package/dist/utils/remotion-precheck.test.d.ts +1 -0
  32. package/dist/utils/remotion.d.ts +7 -1
  33. package/package.json +4 -3
@@ -191,8 +191,8 @@ var init_api_keys = __esm({
191
191
  envVar: "OPENAI_API_KEY",
192
192
  label: "OpenAI",
193
193
  showInSetup: true,
194
- setupDescription: "gpt-image-2 image gen ($, default since v0.56), gpt-4o-mini-tts narration, Whisper transcribe, Agent",
195
- envExampleComment: "OpenAI API Key (Whisper transcription, gpt-4o-mini-tts narration, gpt-image-2 \u2014 default text-to-image since v0.56)",
194
+ setupDescription: "gpt-image-2.5 image gen ($), gpt-4o-mini-tts narration, Whisper transcribe, Agent",
195
+ envExampleComment: "OpenAI API Key (Whisper transcription, gpt-4o-mini-tts narration, gpt-image-2.5 text-to-image)",
196
196
  envExampleUrl: "https://platform.openai.com/api-keys",
197
197
  keyFormat: { prefix: /^sk-/, example: "sk-..." }
198
198
  });
@@ -267,8 +267,7 @@ var init_api_keys = __esm({
267
267
  setupDescription: "v2.5/v3 video \u2014 std ($$) and pro ($$$) modes",
268
268
  envExampleComment: "Kling API Key (Kling video generation)",
269
269
  envExampleUrl: "https://platform.klingai.com/",
270
- envExampleExtraLines: ["Format: ACCESS_KEY:SECRET_KEY"],
271
- keyFormat: { prefix: /:/, example: "ACCESS_KEY:SECRET_KEY" }
270
+ envExampleExtraLines: ["API key from the Kling console (legacy ACCESS_KEY:SECRET_KEY also works)"]
272
271
  });
273
272
  defineApiKey({
274
273
  configKey: "replicate",
@@ -440,6 +439,82 @@ var init_whisper = __esm({
440
439
  }
441
440
  });
442
441
 
442
+ // ../ai-providers/dist/catalog/catalog.js
443
+ function listModels(filter = {}) {
444
+ return MODEL_CATALOG.filter((m) => (!filter.provider || m.provider === filter.provider) && (!filter.kind || m.kind === filter.kind));
445
+ }
446
+ function defaultModel(provider, kind) {
447
+ const found = MODEL_CATALOG.find((m) => m.provider === provider && m.kind === kind && m.default);
448
+ if (!found)
449
+ throw new Error(`Model catalog has no default ${kind} model for ${provider}`);
450
+ return found;
451
+ }
452
+ function findModel(provider, kind, aliasOrId) {
453
+ const key = aliasOrId?.trim();
454
+ if (!key)
455
+ return defaultModel(provider, kind);
456
+ const lower = key.toLowerCase();
457
+ return listModels({ provider, kind }).find((m) => m.id === key || m.aliases?.includes(lower));
458
+ }
459
+ function modelAliases(provider, kind) {
460
+ return listModels({ provider, kind }).flatMap((m) => m.aliases ?? []);
461
+ }
462
+ var MODEL_CATALOG;
463
+ var init_catalog = __esm({
464
+ "../ai-providers/dist/catalog/catalog.js"() {
465
+ "use strict";
466
+ MODEL_CATALOG = [
467
+ // ── LLMs ──────────────────────────────────────────────────────────────
468
+ { id: "claude-sonnet-5-5", provider: "claude", kind: "llm", label: "Claude Sonnet 5.5", aliases: ["sonnet"], default: true, status: "ga", price: { usd: 2, per: "1M input tokens" }, note: "Agent, storyboard, translate-srt, motion default" },
469
+ { id: "claude-opus-5-5", provider: "claude", kind: "llm", label: "Claude Opus 5.5", aliases: ["opus"], status: "ga", price: { usd: 4, per: "1M input tokens" } },
470
+ { id: "claude-opus-4-6", provider: "claude", kind: "llm", label: "Claude Opus 4.6", aliases: ["opus-4-6"], status: "legacy", price: { usd: 5, per: "1M input tokens" } },
471
+ { id: "gpt-5.4-mini", provider: "openai", kind: "llm", label: "GPT-5.4 mini", default: true, status: "ga", price: { usd: 0.75, per: "1M input tokens" }, note: "Agent and storyboard default (tools on Chat Completions)" },
472
+ { id: "gemini-3.8-flash", provider: "gemini", kind: "llm", label: "Gemini 3.8 Flash", aliases: ["flash", "latest", "flash-3.8", "gemini"], default: true, status: "ga", price: { usd: 0.75, per: "1M input tokens", basis: "until 2026-12-31, then $1.50" } },
473
+ { id: "gemini-3.5-flash", provider: "gemini", kind: "llm", label: "Gemini 3.5 Flash", aliases: ["flash-3.5"], status: "ga" },
474
+ { id: "gemini-3-flash-preview", provider: "gemini", kind: "llm", label: "Gemini 3 Flash (preview)", aliases: ["flash-3"], status: "preview" },
475
+ { id: "gemini-3.1-pro-preview", provider: "gemini", kind: "llm", label: "Gemini 3.1 Pro (preview)", aliases: ["pro", "pro-3.1", "3.1-pro", "gemini-3.1-pro"], status: "preview" },
476
+ { id: "gemini-2.5-flash", provider: "gemini", kind: "llm", label: "Gemini 2.5 Flash", aliases: ["flash-2.5"], status: "legacy", note: "New projects cannot use 2.5 models since 2026-09-18" },
477
+ { id: "gemini-2.5-pro", provider: "gemini", kind: "llm", label: "Gemini 2.5 Pro", aliases: ["pro-2.5", "2.5-pro"], status: "legacy" },
478
+ { id: "grok-4.3", provider: "grok", kind: "llm", label: "Grok 4.3", default: true, status: "ga", price: { usd: 1.25, per: "1M input tokens" } },
479
+ { id: "grok-4.7", provider: "grok", kind: "llm", label: "Grok 4.7", status: "ga", price: { usd: 2, per: "1M input tokens" } },
480
+ // ── Images ────────────────────────────────────────────────────────────
481
+ { id: "gpt-image-2.5-sunburst", provider: "openai", kind: "image", label: "GPT Image 2.5 Sunburst", aliases: ["2.5", "sunburst"], default: true, status: "ga", price: { usd: 0.211, per: "image", basis: "high, 1024x1024" } },
482
+ { id: "gpt-image-2.5-flare", provider: "openai", kind: "image", label: "GPT Image 2.5 Flare", aliases: ["flare", "2.5-flare"], status: "ga", price: { usd: 0.211, per: "image", basis: "high, 1024x1024" } },
483
+ { id: "gpt-image-2", provider: "openai", kind: "image", label: "GPT Image 2", aliases: ["2"], status: "ga", price: { usd: 0.211, per: "image", basis: "high, 1024x1024" } },
484
+ { id: "gpt-image-1.5", provider: "openai", kind: "image", label: "GPT Image 1.5", aliases: ["1.5"], status: "deprecated", shutdown: "2026-12-01", price: { usd: 0.133, per: "image", basis: "high, 1024x1024" } },
485
+ { id: "gemini-3.1-flash-image", provider: "gemini", kind: "image", label: "Nano Banana 2", aliases: ["flash", "3.1-flash", "latest"], default: true, status: "ga", price: { usd: 0.067, per: "image", basis: "1K" } },
486
+ { id: "gemini-3.1-flash-lite-image", provider: "gemini", kind: "image", label: "Nano Banana 2 Lite", aliases: ["lite"], status: "ga", price: { usd: 0.034, per: "image", basis: "1K" } },
487
+ { id: "gemini-3-pro-image", provider: "gemini", kind: "image", label: "Nano Banana Pro", aliases: ["pro"], status: "ga", price: { usd: 0.134, per: "image", basis: "1K-2K" } },
488
+ { id: "gemini-2.5-flash-image", provider: "gemini", kind: "image", label: "Nano Banana (2.5 Flash Image)", status: "deprecated", shutdown: "2026-10-02" },
489
+ { id: "gemini-3.1-flash-image-preview", provider: "gemini", kind: "image", label: "Nano Banana 2 (preview)", status: "deprecated", shutdown: "2026-06-25" },
490
+ { id: "gemini-3-pro-image-preview", provider: "gemini", kind: "image", label: "Nano Banana Pro (preview)", status: "deprecated", shutdown: "2026-06-25" },
491
+ { id: "grok-imagine-image", provider: "grok", kind: "image", label: "Grok Imagine Image", default: true, status: "ga", price: { usd: 0.02, per: "image" } },
492
+ { id: "grok-imagine-image-2.0", provider: "grok", kind: "image", label: "Grok Imagine Image 2.0", aliases: ["pro", "2.0", "quality"], status: "ga", price: { usd: 0.08, per: "image", basis: "medium, 2K" } },
493
+ // ── Video ─────────────────────────────────────────────────────────────
494
+ { id: "seedance-2.0", provider: "seedance", kind: "video", label: "Seedance 2.0", aliases: ["quality", "2.0"], default: true, status: "ga", price: { usd: 0.3024, per: "second", basis: "720p 16:9" }, note: "4-15 s, native audio" },
495
+ { id: "seedance-2.0-fast", provider: "seedance", kind: "video", label: "Seedance 2.0 Fast", aliases: ["fast"], status: "ga", price: { usd: 0.2419, per: "second", basis: "720p 16:9" }, note: "4-15 s, up to 720p, native audio" },
496
+ { id: "seedance-2.5", provider: "seedance", kind: "video", label: "Seedance 2.5", aliases: ["2.5"], status: "ga", price: { usd: 0.473, per: "second", basis: "720p 16:9" }, note: "Opt-in; 4-30 s, native audio" },
497
+ { id: "gemini-omni-1.1-flash", provider: "omni", kind: "video", label: "Gemini Omni 1.1 Flash", default: true, status: "ga", price: { usd: 0.1, per: "second", basis: "720p" }, note: "Google video default; the model picks 3-10 s; native audio" },
498
+ { id: "veo-3.1-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1", aliases: ["3.1"], status: "deprecated", shutdown: "2026-10-22", price: { usd: 0.4, per: "second", basis: "720p/1080p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
499
+ { id: "veo-3.1-fast-generate-preview", provider: "veo", kind: "video", label: "Veo 3.1 Fast", aliases: ["3.1-fast"], default: true, status: "deprecated", shutdown: "2026-10-22", price: { usd: 0.1, per: "second", basis: "720p" }, note: "4-8 s, native audio; explicit `-p veo` only" },
500
+ { id: "grok-imagine-video-1.5", provider: "grok", kind: "video", label: "Grok Imagine Video 1.5", aliases: ["1.5"], default: true, status: "ga", price: { usd: 0.14, per: "second", basis: "720p" }, note: "1-15 s, native audio; 720p unless `--resolution` says otherwise" },
501
+ { id: "grok-imagine-video-1.5-lite", provider: "grok", kind: "video", label: "Grok Imagine Video 1.5 Lite", aliases: ["lite", "1.5-lite"], status: "ga", price: { usd: 0.03, per: "second", basis: "720p" }, note: "1-15 s, native audio" },
502
+ { id: "grok-imagine-video", provider: "grok", kind: "video", label: "Grok Imagine Video", aliases: ["classic"], status: "legacy", price: { usd: 0.05, per: "second", basis: "480p" }, note: "Previous generation" },
503
+ { id: "kling-v3", provider: "kling", kind: "video", label: "Kling v3", aliases: ["v3"], default: true, status: "ga", price: { usd: 0.084, per: "second", basis: "std, silent" }, note: "3-15 s, multi-shot; `std` 720p, `pro` 1080p" },
504
+ { id: "kling-v2-6", provider: "kling", kind: "video", label: "Kling v2.6", aliases: ["v2.6"], status: "ga", price: { usd: 0.042, per: "second", basis: "std 720p" }, note: "5 or 10 s" },
505
+ { id: "kling-v2-5-turbo", provider: "kling", kind: "video", label: "Kling v2.5 Turbo", aliases: ["v2.5-turbo", "v2.5"], status: "ga", price: { usd: 0.042, per: "second", basis: "std" }, note: "5 or 10 s, no audio" },
506
+ { id: "gen4.5", provider: "runway", kind: "video", label: "Runway Gen-4.5", default: true, status: "ga", price: { usd: 0.12, per: "second" }, note: "2-10 s, no audio" },
507
+ { id: "gen4_turbo", provider: "runway", kind: "video", label: "Runway Gen-4 Turbo", status: "ga", price: { usd: 0.05, per: "second" }, note: "Image-to-video only, no audio" },
508
+ // ── Audio ─────────────────────────────────────────────────────────────
509
+ { id: "eleven_v3", provider: "elevenlabs", kind: "speech", label: "ElevenLabs v3", default: true, status: "ga", price: { usd: 0.08, per: "1K characters" } },
510
+ { id: "gpt-4o-mini-tts", provider: "openai", kind: "speech", label: "GPT-4o mini TTS", default: true, status: "deprecated", shutdown: "2027-01-06" },
511
+ { id: "music_v2_5", provider: "elevenlabs", kind: "music", label: "ElevenLabs Music v2.5", default: true, status: "ga", price: { usd: 0.15, per: "minute" } },
512
+ { id: "eleven_text_to_sound_v2", provider: "elevenlabs", kind: "sound-effect", label: "ElevenLabs Sound Effects v2", default: true, status: "ga", price: { usd: 0.12, per: "minute" }, note: "0.5-30 s" },
513
+ { id: "whisper-1", provider: "openai", kind: "transcription", label: "Whisper", default: true, status: "deprecated", shutdown: "2027-02-26", note: "Only OpenAI model with word timestamps" }
514
+ ];
515
+ }
516
+ });
517
+
443
518
  // ../ai-providers/dist/gemini/gemini-models.js
444
519
  function isGeminiTextModelAlias(model) {
445
520
  return Object.prototype.hasOwnProperty.call(GEMINI_TEXT_MODEL_ALIASES, model);
@@ -458,18 +533,11 @@ var GEMINI_DEFAULT_TEXT_MODEL, GEMINI_AGENT_DEFAULT_TEXT_MODEL, GEMINI_TEXT_MODE
458
533
  var init_gemini_models = __esm({
459
534
  "../ai-providers/dist/gemini/gemini-models.js"() {
460
535
  "use strict";
461
- GEMINI_DEFAULT_TEXT_MODEL = "gemini-3.5-flash";
462
- GEMINI_AGENT_DEFAULT_TEXT_MODEL = "gemini-2.5-flash";
463
- GEMINI_TEXT_MODEL_ALIASES = {
464
- flash: GEMINI_DEFAULT_TEXT_MODEL,
465
- latest: GEMINI_DEFAULT_TEXT_MODEL,
466
- "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
467
- "flash-3": "gemini-3-flash-preview",
468
- "flash-2.5": "gemini-2.5-flash",
469
- pro: "gemini-2.5-pro",
470
- "pro-3.1": "gemini-3.1-pro-preview"
471
- };
472
- GEMINI_TEXT_MODEL_HELP = "flash/latest (Gemini 3.5 Flash), flash-3.5, flash-3, flash-2.5, pro, pro-3.1, or a full gemini-* model ID";
536
+ init_catalog();
537
+ GEMINI_DEFAULT_TEXT_MODEL = defaultModel("gemini", "llm").id;
538
+ GEMINI_AGENT_DEFAULT_TEXT_MODEL = GEMINI_DEFAULT_TEXT_MODEL;
539
+ GEMINI_TEXT_MODEL_ALIASES = Object.fromEntries(listModels({ provider: "gemini", kind: "llm" }).flatMap((m) => (m.aliases ?? []).map((a) => [a, m.id])));
540
+ GEMINI_TEXT_MODEL_HELP = "flash/latest (Gemini 3.8 Flash), flash-3.8, flash-3.5, flash-3, flash-2.5, pro (Gemini 3.1 Pro), pro-3.1, pro-2.5, or a full gemini-* model ID";
473
541
  }
474
542
  });
475
543
 
@@ -656,7 +724,6 @@ UNIVERSAL RULES:
656
724
  system_instruction: { parts: [{ text: systemPrompt }] },
657
725
  contents: [{ role: "user", parts: [{ text: userPrompt }] }],
658
726
  generationConfig: {
659
- temperature: 0.8,
660
727
  maxOutputTokens: 16384
661
728
  }
662
729
  })
@@ -757,7 +824,7 @@ Return the complete modified component as JSON.`
757
824
  ]
758
825
  }
759
826
  ],
760
- generationConfig: { temperature: 0.5, maxOutputTokens: 16384 }
827
+ generationConfig: { maxOutputTokens: 16384 }
761
828
  })
762
829
  });
763
830
  if (!response.ok) {
@@ -803,18 +870,7 @@ var init_gemini_motion = __esm({
803
870
  "../ai-providers/dist/gemini/gemini-motion.js"() {
804
871
  "use strict";
805
872
  init_gemini_models();
806
- GEMINI_MOTION_MODELS = {
807
- gemini: GEMINI_DEFAULT_TEXT_MODEL,
808
- flash: GEMINI_DEFAULT_TEXT_MODEL,
809
- latest: GEMINI_DEFAULT_TEXT_MODEL,
810
- "flash-3.5": GEMINI_DEFAULT_TEXT_MODEL,
811
- "flash-3": "gemini-3-flash-preview",
812
- "flash-2.5": "gemini-2.5-flash",
813
- pro: "gemini-2.5-pro",
814
- "2.5-pro": "gemini-2.5-pro",
815
- "3.1-pro": "gemini-3.1-pro-preview",
816
- "pro-3.1": "gemini-3.1-pro-preview"
817
- };
873
+ GEMINI_MOTION_MODELS = GEMINI_TEXT_MODEL_ALIASES;
818
874
  STYLE_USER_INSTRUCTIONS = {
819
875
  minimal: "CENTER-ALIGNED pure typography. Thin lines + large title + subtitle. NO glass panels, NO bokeh, NO dark bars. Breathing pulse during hold.",
820
876
  corporate: "Data-rich info bar at bottom + top-right badge. Animated number counter. Brand color stripe. Progress bar.",
@@ -922,7 +978,6 @@ var init_storyboard_prompt = __esm({
922
978
  async function analyzeContent(api, content, targetDuration, options) {
923
979
  const creativity = options?.creativity || "low";
924
980
  const systemPrompt = buildStoryboardSystemPrompt(targetDuration, creativity);
925
- const temperature = creativity === "high" ? 1 : 0.7;
926
981
  try {
927
982
  const payload = {
928
983
  system_instruction: {
@@ -932,7 +987,6 @@ async function analyzeContent(api, content, targetDuration, options) {
932
987
  parts: [{ text: buildStoryboardUserMessage(content) }]
933
988
  }],
934
989
  generationConfig: {
935
- temperature,
936
990
  maxOutputTokens: 4096
937
991
  }
938
992
  };
@@ -1003,7 +1057,15 @@ var init_http = __esm({
1003
1057
  });
1004
1058
 
1005
1059
  // ../ai-providers/dist/gemini/GeminiProvider.js
1006
- var MODEL_MAP, GeminiProvider, geminiProvider;
1060
+ function resolveVeoModel(alias) {
1061
+ if (Object.hasOwn(VEO_MODEL_ALIASES, alias)) {
1062
+ return VEO_MODEL_ALIASES[alias];
1063
+ }
1064
+ const valid = Object.keys(VEO_MODEL_ALIASES).join(", ");
1065
+ const hint = alias === "3.0" ? " Veo 3.0 was shut down by Google on 2025-11-12." : "";
1066
+ throw new Error(`Unknown Veo model "${alias}". Valid: ${valid}.${hint}`);
1067
+ }
1068
+ var VEO_MODEL_ALIASES, GeminiProvider, geminiProvider;
1007
1069
  var init_GeminiProvider = __esm({
1008
1070
  "../ai-providers/dist/gemini/GeminiProvider.js"() {
1009
1071
  "use strict";
@@ -1011,15 +1073,8 @@ var init_GeminiProvider = __esm({
1011
1073
  init_gemini_models();
1012
1074
  init_gemini_storyboard();
1013
1075
  init_http();
1014
- MODEL_MAP = {
1015
- "flash": "gemini-2.5-flash-image",
1016
- "3.1-flash": "gemini-3.1-flash-image-preview",
1017
- "latest": "gemini-3.1-flash-image-preview",
1018
- "pro": "gemini-3-pro-image-preview",
1019
- "gemini-2.5-flash-image": "gemini-2.5-flash-image",
1020
- "gemini-3.1-flash-image-preview": "gemini-3.1-flash-image-preview",
1021
- "gemini-3-pro-image-preview": "gemini-3-pro-image-preview"
1022
- };
1076
+ init_catalog();
1077
+ VEO_MODEL_ALIASES = Object.fromEntries(listModels({ provider: "veo", kind: "video" }).flatMap((m) => (m.aliases ?? []).map((a) => [a, m.id])));
1023
1078
  GeminiProvider = class {
1024
1079
  constructor() {
1025
1080
  this.id = "gemini";
@@ -1278,9 +1333,7 @@ var init_GeminiProvider = __esm({
1278
1333
  * Resolve model alias to full model ID
1279
1334
  */
1280
1335
  resolveModel(model) {
1281
- if (!model)
1282
- return MODEL_MAP["flash"];
1283
- return MODEL_MAP[model] || MODEL_MAP["flash"];
1336
+ return (findModel("gemini", "image", model) ?? defaultModel("gemini", "image")).id;
1284
1337
  }
1285
1338
  /**
1286
1339
  * Check if model is Pro
@@ -1324,7 +1377,7 @@ var init_GeminiProvider = __esm({
1324
1377
  contents: [{ parts: [{ text: prompt3 }] }],
1325
1378
  generationConfig
1326
1379
  };
1327
- const is31Flash = modelId === "gemini-3.1-flash-image-preview";
1380
+ const is31Flash = modelId.startsWith("gemini-3.1-flash-image");
1328
1381
  if (options.imageSearchGrounding && is31Flash) {
1329
1382
  payload.tools = [{ googleSearch: { searchTypes: { webSearch: {}, imageSearch: {} } } }];
1330
1383
  } else if (options.grounding && isPro) {
@@ -1552,7 +1605,6 @@ var init_GeminiProvider = __esm({
1552
1605
  videoPart.video_metadata = videoMetadata;
1553
1606
  }
1554
1607
  const generationConfig = {
1555
- temperature: 0.4,
1556
1608
  maxOutputTokens: 8192
1557
1609
  };
1558
1610
  if (options.lowResolution) {
@@ -1634,7 +1686,6 @@ var init_GeminiProvider = __esm({
1634
1686
  }
1635
1687
  }));
1636
1688
  const generationConfig = {
1637
- temperature: 0.4,
1638
1689
  maxOutputTokens: 8192
1639
1690
  };
1640
1691
  if (options.lowResolution) {
@@ -1734,7 +1785,6 @@ Respond with ONLY the JSON array, no other text.`;
1734
1785
  body: JSON.stringify({
1735
1786
  contents: [{ parts: [{ text: prompt3 }] }],
1736
1787
  generationConfig: {
1737
- temperature: 0.3,
1738
1788
  maxOutputTokens: 1024
1739
1789
  }
1740
1790
  })
@@ -1828,7 +1878,7 @@ Respond with ONLY the JSON array, no other text.`;
1828
1878
  return refineMotion({ apiKey: this.apiKey, baseUrl: this.baseUrl }, existingCode, instructions, options);
1829
1879
  }
1830
1880
  /**
1831
- * Generate a storyboard from script content using Gemini 2.5 Flash.
1881
+ * Generate a storyboard from script content using the default Gemini Flash model.
1832
1882
  * Alternative to ClaudeProvider.analyzeContent for when Claude is unavailable.
1833
1883
  */
1834
1884
  async analyzeContent(content, targetDuration, options) {
@@ -1843,43 +1893,48 @@ Respond with ONLY the JSON array, no other text.`;
1843
1893
  });
1844
1894
 
1845
1895
  // ../ai-providers/dist/gemini/gemini-omni.js
1846
- function firstReferenceImage(options) {
1847
- const refs = options.referenceImages;
1848
- if (refs && refs.length > 0 && refs[0].base64)
1849
- return refs[0];
1850
- const ref = options.referenceImage;
1851
- if (typeof ref === "string" && ref) {
1896
+ function frameImages(options) {
1897
+ const frames = [];
1898
+ const toInline = (ref) => {
1852
1899
  const m = ref.match(/^data:(.+?);base64,(.*)$/);
1853
- if (m)
1854
- return { mimeType: m[1], base64: m[2] };
1855
- return { mimeType: "image/png", base64: ref };
1900
+ return m ? { mimeType: m[1], base64: m[2] } : { mimeType: "image/png", base64: ref };
1901
+ };
1902
+ if (options.referenceImages?.[0]?.base64)
1903
+ frames.push(options.referenceImages[0]);
1904
+ else if (typeof options.referenceImage === "string" && options.referenceImage) {
1905
+ frames.push(toInline(options.referenceImage));
1906
+ }
1907
+ if (frames.length > 0 && options.lastFrame)
1908
+ frames.push(toInline(options.lastFrame));
1909
+ return frames;
1910
+ }
1911
+ function findOmniVideo(response) {
1912
+ for (const step of response.steps ?? []) {
1913
+ if (step.type !== "model_output")
1914
+ continue;
1915
+ const video = step.content?.find((c) => c.type === "video");
1916
+ if (video)
1917
+ return video;
1856
1918
  }
1857
- return void 0;
1919
+ return response.output_video;
1858
1920
  }
1859
- function findVideoUrl(obj) {
1860
- if (!obj || typeof obj !== "object")
1861
- return void 0;
1862
- for (const [k, v] of Object.entries(obj)) {
1863
- if (typeof v === "string" && /^https?:\/\//.test(v) && /(uri|url|video)/i.test(k))
1864
- return v;
1865
- if (typeof v === "object") {
1866
- const nested = findVideoUrl(v);
1867
- if (nested)
1868
- return nested;
1869
- }
1870
- }
1871
- return void 0;
1921
+ function fileIdFromUri(uri) {
1922
+ return uri.match(/files\/([^/?:]+)/)?.[1];
1872
1923
  }
1873
- var OMNI_MODEL, INTERACTIONS_URL, OmniProvider, omniProvider;
1924
+ var OMNI_MODEL, API_ROOT, FILE_POLL_INTERVAL_MS, FILE_POLL_TIMEOUT_MS, OmniProvider, omniProvider;
1874
1925
  var init_gemini_omni = __esm({
1875
1926
  "../ai-providers/dist/gemini/gemini-omni.js"() {
1876
1927
  "use strict";
1877
- OMNI_MODEL = "gemini-omni-flash-preview";
1878
- INTERACTIONS_URL = "https://generativelanguage.googleapis.com/v1beta/interactions";
1928
+ init_http();
1929
+ init_catalog();
1930
+ OMNI_MODEL = defaultModel("omni", "video").id;
1931
+ API_ROOT = "https://generativelanguage.googleapis.com";
1932
+ FILE_POLL_INTERVAL_MS = 3e3;
1933
+ FILE_POLL_TIMEOUT_MS = 5 * 60 * 1e3;
1879
1934
  OmniProvider = class {
1880
1935
  constructor() {
1881
1936
  this.id = "omni";
1882
- this.label = "Gemini Omni (experimental)";
1937
+ this.label = "Gemini Omni 1.1 Flash";
1883
1938
  }
1884
1939
  async initialize(config2) {
1885
1940
  this.apiKey = config2.apiKey;
@@ -1892,22 +1947,23 @@ var init_gemini_omni = __esm({
1892
1947
  return { id: "", status: "failed", error: "GOOGLE_API_KEY not configured for Gemini Omni" };
1893
1948
  }
1894
1949
  const opts = options ?? { prompt: prompt3 };
1895
- const image = firstReferenceImage(opts);
1896
- const task = image ? "image_to_video" : "text_to_video";
1897
- const aspectRatio2 = opts.aspectRatio === "9:16" ? "9:16" : "16:9";
1898
- const inputs = [{ text: prompt3 }];
1899
- if (image) {
1900
- inputs.push({ inline_data: { mime_type: image.mimeType, data: image.base64 } });
1901
- }
1950
+ const frames = frameImages(opts);
1951
+ const input = frames.length === 0 ? prompt3 : [
1952
+ ...frames.map((f) => ({ type: "image", data: f.base64, mime_type: f.mimeType })),
1953
+ { type: "text", text: prompt3 }
1954
+ ];
1902
1955
  const body = {
1903
1956
  model: OMNI_MODEL,
1904
- inputs,
1905
- generation_config: {
1906
- video_config: { task, aspect_ratio: aspectRatio2 }
1957
+ input,
1958
+ response_format: {
1959
+ type: "video",
1960
+ aspect_ratio: opts.aspectRatio === "9:16" ? "9:16" : "16:9",
1961
+ resolution: opts.resolution ?? "720p",
1962
+ delivery: "uri"
1907
1963
  }
1908
1964
  };
1909
1965
  try {
1910
- const res = await fetch(INTERACTIONS_URL, {
1966
+ const res = await fetch(`${API_ROOT}/v1beta/interactions`, {
1911
1967
  method: "POST",
1912
1968
  headers: { "Content-Type": "application/json", "x-goog-api-key": this.apiKey },
1913
1969
  body: JSON.stringify(body)
@@ -1917,33 +1973,52 @@ var init_gemini_omni = __esm({
1917
1973
  return {
1918
1974
  id: "",
1919
1975
  status: "failed",
1920
- error: `Gemini Omni (experimental) request failed: HTTP ${res.status} \u2014 ${text.slice(0, 300)}`
1976
+ error: `Gemini Omni request failed: HTTP ${res.status} - ${text.slice(0, 300)}`
1921
1977
  };
1922
1978
  }
1923
- let parsed;
1924
- try {
1925
- parsed = JSON.parse(text);
1926
- } catch {
1927
- return { id: "", status: "failed", error: "Gemini Omni returned a non-JSON response" };
1979
+ const parsed = JSON.parse(text);
1980
+ const id = parsed.id ?? "";
1981
+ const video = findOmniVideo(parsed);
1982
+ if (!video?.uri) {
1983
+ const reason = parsed.error?.message ?? `status ${parsed.status ?? "unknown"}`;
1984
+ return { id, status: "failed", error: `Gemini Omni returned no video (${reason})` };
1928
1985
  }
1929
- const videoUrl = findVideoUrl(parsed);
1930
- const id = parsed?.interaction_id ?? parsed?.name ?? "";
1931
- if (!videoUrl) {
1932
- return {
1933
- id,
1934
- status: "failed",
1935
- error: "Gemini Omni response contained no video URL (the preview interactions schema may have changed)."
1936
- };
1986
+ const fileId = fileIdFromUri(video.uri);
1987
+ if (!fileId) {
1988
+ return { id, status: "failed", error: `Gemini Omni returned an unexpected video URI: ${video.uri}` };
1989
+ }
1990
+ const ready = await this.waitForFile(fileId);
1991
+ if (ready !== "ACTIVE") {
1992
+ return { id, status: "failed", error: `Gemini Omni video file ended in state ${ready}` };
1937
1993
  }
1938
- return { id, status: "completed", videoUrl };
1994
+ return {
1995
+ id,
1996
+ status: "completed",
1997
+ videoUrl: `${API_ROOT}/download/v1beta/files/${fileId}:download?alt=media`
1998
+ };
1939
1999
  } catch (err) {
1940
2000
  return {
1941
2001
  id: "",
1942
2002
  status: "failed",
1943
- error: `Gemini Omni (experimental) error: ${err instanceof Error ? err.message : String(err)}`
2003
+ error: `Gemini Omni error: ${err instanceof Error ? err.message : String(err)}`
1944
2004
  };
1945
2005
  }
1946
2006
  }
2007
+ /** Poll the Files API until the generated video is downloadable. */
2008
+ async waitForFile(fileId) {
2009
+ const deadline = Date.now() + FILE_POLL_TIMEOUT_MS;
2010
+ for (; ; ) {
2011
+ const res = await fetch(`${API_ROOT}/v1beta/files/${fileId}`, {
2012
+ headers: { "x-goog-api-key": this.apiKey }
2013
+ });
2014
+ const state = res.ok ? (await res.json()).state : `HTTP ${res.status}`;
2015
+ if (state !== "PROCESSING")
2016
+ return state ?? "UNKNOWN";
2017
+ if (Date.now() > deadline)
2018
+ return "TIMEOUT";
2019
+ await sleep(FILE_POLL_INTERVAL_MS);
2020
+ }
2021
+ }
1947
2022
  };
1948
2023
  omniProvider = new OmniProvider();
1949
2024
  }
@@ -1977,24 +2052,33 @@ var init_gemini = __esm({
1977
2052
  label: "Veo",
1978
2053
  apiKey: "google",
1979
2054
  kinds: ["video"],
1980
- resolverPriority: { video: 3 },
2055
+ // No resolverPriority: the Veo 3.1 previews shut down on 2026-10-22, so
2056
+ // Veo is explicit-only (`-p veo`) and Omni is the Google default.
1981
2057
  commandsUnlocked: ["generate video -p veo"]
1982
2058
  });
1983
2059
  defineProvider({
1984
2060
  id: "omni",
1985
- label: "Gemini Omni (experimental)",
2061
+ label: "Gemini Omni",
1986
2062
  apiKey: "google",
1987
2063
  kinds: ["video"],
2064
+ resolverPriority: { video: 3 },
1988
2065
  commandsUnlocked: ["generate video -p omni"]
1989
2066
  });
1990
2067
  }
1991
2068
  });
1992
2069
 
2070
+ // ../ai-providers/dist/catalog/index.js
2071
+ var init_catalog2 = __esm({
2072
+ "../ai-providers/dist/catalog/index.js"() {
2073
+ "use strict";
2074
+ init_catalog();
2075
+ }
2076
+ });
2077
+
1993
2078
  // ../ai-providers/dist/openai/openai-storyboard.js
1994
2079
  async function analyzeContent2(apiKey, content, targetDuration, options) {
1995
2080
  const creativity = options?.creativity || "low";
1996
- const systemPrompt = buildStoryboardSystemPrompt(targetDuration, creativity);
1997
- const temperature = creativity === "high" ? 1 : 0.7;
2081
+ const systemPrompt = buildStoryboardSystemPrompt(targetDuration, creativity) + '\n\nWrap the array in a JSON object: {"segments": [ ...segment objects... ]}.';
1998
2082
  try {
1999
2083
  const response = await fetch("https://api.openai.com/v1/chat/completions", {
2000
2084
  method: "POST",
@@ -2003,13 +2087,13 @@ async function analyzeContent2(apiKey, content, targetDuration, options) {
2003
2087
  Authorization: `Bearer ${apiKey}`
2004
2088
  },
2005
2089
  body: JSON.stringify({
2006
- model: "gpt-5-mini",
2090
+ model: defaultModel("openai", "llm").id,
2007
2091
  messages: [
2008
2092
  { role: "system", content: systemPrompt },
2009
2093
  { role: "user", content: buildStoryboardUserMessage(content) }
2010
2094
  ],
2011
- temperature,
2012
- max_tokens: 4096,
2095
+ // Reasoning tokens count against this cap.
2096
+ max_completion_tokens: 16384,
2013
2097
  response_format: { type: "json_object" }
2014
2098
  })
2015
2099
  });
@@ -2048,6 +2132,7 @@ var init_openai_storyboard = __esm({
2048
2132
  "../ai-providers/dist/openai/openai-storyboard.js"() {
2049
2133
  "use strict";
2050
2134
  init_storyboard_prompt();
2135
+ init_catalog();
2051
2136
  }
2052
2137
  });
2053
2138
 
@@ -2057,6 +2142,7 @@ var init_OpenAIProvider = __esm({
2057
2142
  "../ai-providers/dist/openai/OpenAIProvider.js"() {
2058
2143
  "use strict";
2059
2144
  init_openai_storyboard();
2145
+ init_catalog();
2060
2146
  OpenAIProvider = class {
2061
2147
  constructor() {
2062
2148
  this.id = "openai-gpt";
@@ -2066,7 +2152,7 @@ var init_OpenAIProvider = __esm({
2066
2152
  this.iconUrl = "/icons/openai.svg";
2067
2153
  this.isAvailable = true;
2068
2154
  this.baseUrl = "https://api.openai.com/v1";
2069
- this.model = "gpt-5-mini";
2155
+ this.model = defaultModel("openai", "llm").id;
2070
2156
  }
2071
2157
  async initialize(config2) {
2072
2158
  this.apiKey = config2.apiKey;
@@ -2162,7 +2248,6 @@ Or if clarification needed:
2162
2248
  { role: "system", content: systemPrompt },
2163
2249
  { role: "user", content: instruction }
2164
2250
  ],
2165
- temperature: 0.2,
2166
2251
  response_format: { type: "json_object" }
2167
2252
  })
2168
2253
  });
@@ -2373,8 +2458,9 @@ Each segment should be 3-10 seconds long.`;
2373
2458
  Authorization: `Bearer ${this.apiKey}`
2374
2459
  },
2375
2460
  body: JSON.stringify({
2376
- model: "gpt-5-mini",
2377
- max_tokens: 4096,
2461
+ model: defaultModel("openai", "llm").id,
2462
+ // Reasoning tokens count against this cap.
2463
+ max_completion_tokens: 16384,
2378
2464
  response_format: { type: "json_object" },
2379
2465
  messages: [
2380
2466
  { role: "system", content: systemPrompt },
@@ -2407,7 +2493,7 @@ ${videoAnalysis}` }
2407
2493
  }
2408
2494
  }
2409
2495
  /**
2410
- * Generate a storyboard from script content using GPT-5-mini.
2496
+ * Generate a storyboard from script content using GPT-5.4 mini.
2411
2497
  * Alternative to ClaudeProvider.analyzeContent for when Claude is unavailable.
2412
2498
  */
2413
2499
  async analyzeContent(content, targetDuration, options) {
@@ -2455,10 +2541,12 @@ async function callClaude(params, opts) {
2455
2541
  },
2456
2542
  body: JSON.stringify({
2457
2543
  model: params.model,
2458
- max_tokens: opts.maxTokens,
2459
- ...opts.temperature !== void 0 ? { temperature: opts.temperature } : {},
2544
+ // Claude 5.x thinks by default and thinking tokens count against
2545
+ // max_tokens, so a small cap can end before any text is written.
2546
+ max_tokens: Math.max(opts.maxTokens, CLAUDE_MIN_MAX_TOKENS),
2460
2547
  messages: opts.messages,
2461
- system: opts.system
2548
+ system: opts.system,
2549
+ ...opts.jsonSchema ? { output_config: { format: { type: "json_schema", schema: opts.jsonSchema } } } : {}
2462
2550
  })
2463
2551
  });
2464
2552
  if (!response.ok) {
@@ -2480,9 +2568,11 @@ function extractJsonArray(text) {
2480
2568
  const match = text.match(/\[[\s\S]*\]/);
2481
2569
  return match ? match[0] : null;
2482
2570
  }
2571
+ var CLAUDE_MIN_MAX_TOKENS;
2483
2572
  var init_claude_api = __esm({
2484
2573
  "../ai-providers/dist/claude/claude-api.js"() {
2485
2574
  "use strict";
2575
+ CLAUDE_MIN_MAX_TOKENS = 16e3;
2486
2576
  }
2487
2577
  });
2488
2578
 
@@ -2518,7 +2608,8 @@ MANDATORY ADAPTATION RULES:
2518
2608
  const text = await callClaude(api, {
2519
2609
  system: systemPrompt,
2520
2610
  messages: [{ role: "user", content: userContent }],
2521
- maxTokens: 16e3
2611
+ maxTokens: 16e3,
2612
+ jsonSchema: MOTION_COMPONENT_SCHEMA
2522
2613
  });
2523
2614
  const jsonStr = extractJsonObject(text);
2524
2615
  if (!jsonStr) {
@@ -2593,7 +2684,8 @@ Modification instructions: ${instructions}
2593
2684
 
2594
2685
  Return the complete modified component as JSON.`
2595
2686
  }],
2596
- maxTokens: 16e3
2687
+ maxTokens: 16e3,
2688
+ jsonSchema: MOTION_COMPONENT_SCHEMA
2597
2689
  });
2598
2690
  const jsonStr = extractJsonObject(text);
2599
2691
  if (!jsonStr)
@@ -2622,15 +2714,13 @@ async function analyzeContent3(api, content, targetDuration, options) {
2622
2714
  const creativity = options?.creativity || "low";
2623
2715
  const systemPrompt = buildStoryboardSystemPrompt(targetDuration, creativity);
2624
2716
  try {
2625
- const temperature = creativity === "high" ? 1 : 0.7;
2626
2717
  const text = await callClaude(api, {
2627
2718
  system: systemPrompt,
2628
2719
  messages: [{
2629
2720
  role: "user",
2630
2721
  content: buildStoryboardUserMessage(content)
2631
2722
  }],
2632
- maxTokens: 4096,
2633
- temperature
2723
+ maxTokens: 4096
2634
2724
  });
2635
2725
  const jsonStr = extractJsonArray(text);
2636
2726
  if (!jsonStr) {
@@ -2987,11 +3077,22 @@ UNIVERSAL RULES:
2987
3077
  - Both entrance AND exit animations
2988
3078
  - whiteSpace: "nowrap" on all text`;
2989
3079
  }
3080
+ var MOTION_COMPONENT_SCHEMA;
2990
3081
  var init_claude_motion = __esm({
2991
3082
  "../ai-providers/dist/claude/claude-motion.js"() {
2992
3083
  "use strict";
2993
3084
  init_claude_api();
2994
3085
  init_storyboard_prompt();
3086
+ MOTION_COMPONENT_SCHEMA = {
3087
+ type: "object",
3088
+ properties: {
3089
+ name: { type: "string" },
3090
+ code: { type: "string" },
3091
+ description: { type: "string" }
3092
+ },
3093
+ required: ["name", "code", "description"],
3094
+ additionalProperties: false
3095
+ };
2995
3096
  }
2996
3097
  });
2997
3098
 
@@ -3753,10 +3854,12 @@ var ClaudeProvider, claudeProvider;
3753
3854
  var init_ClaudeProvider = __esm({
3754
3855
  "../ai-providers/dist/claude/ClaudeProvider.js"() {
3755
3856
  "use strict";
3857
+ init_claude_api();
3756
3858
  init_claude_motion();
3757
3859
  init_claude_analysis();
3758
3860
  init_claude_viral();
3759
3861
  init_claude_visual_fx();
3862
+ init_catalog();
3760
3863
  ClaudeProvider = class {
3761
3864
  constructor() {
3762
3865
  this.id = "claude";
@@ -3766,7 +3869,7 @@ var init_ClaudeProvider = __esm({
3766
3869
  this.iconUrl = "/icons/claude.svg";
3767
3870
  this.isAvailable = true;
3768
3871
  this.baseUrl = "https://api.anthropic.com/v1";
3769
- this.model = "claude-sonnet-4-6";
3872
+ this.model = defaultModel("claude", "llm").id;
3770
3873
  }
3771
3874
  async initialize(config2) {
3772
3875
  this.apiKey = config2.apiKey;
@@ -3870,7 +3973,7 @@ Or if clarification needed:
3870
3973
  },
3871
3974
  body: JSON.stringify({
3872
3975
  model: this.model,
3873
- max_tokens: 2048,
3976
+ max_tokens: CLAUDE_MIN_MAX_TOKENS,
3874
3977
  messages: [{ role: "user", content: instruction }],
3875
3978
  system: systemPrompt
3876
3979
  })
@@ -3940,7 +4043,7 @@ Respond with JSON array:
3940
4043
  },
3941
4044
  body: JSON.stringify({
3942
4045
  model: this.model,
3943
- max_tokens: 2048,
4046
+ max_tokens: CLAUDE_MIN_MAX_TOKENS,
3944
4047
  messages: [{
3945
4048
  role: "user",
3946
4049
  content: `Clips: ${JSON.stringify(clipsInfo)}
@@ -4068,11 +4171,7 @@ Instruction: ${instruction}`
4068
4171
  return generateNarrationScript(this.api, videoAnalysis, duration, style, language);
4069
4172
  }
4070
4173
  };
4071
- ClaudeProvider.MOTION_MODELS = {
4072
- sonnet: "claude-sonnet-4-6",
4073
- opus: "claude-opus-4-7",
4074
- "opus-4-6": "claude-opus-4-6"
4075
- };
4174
+ ClaudeProvider.MOTION_MODELS = Object.fromEntries(listModels({ provider: "claude", kind: "llm" }).flatMap((m) => (m.aliases ?? []).map((a) => [a, m.id])));
4076
4175
  claudeProvider = new ClaudeProvider();
4077
4176
  }
4078
4177
  });
@@ -4408,10 +4507,11 @@ function resolveVoiceId(input) {
4408
4507
  const availableVoices = Object.keys(KNOWN_VOICES).join(", ");
4409
4508
  throw new Error(`Unknown voice "${input}". Available voices: ${availableVoices}. You can also use a custom voice ID from your ElevenLabs account.`);
4410
4509
  }
4411
- var KNOWN_VOICES, TTS_429_MAX_RETRIES, TTS_429_RETRY_DELAY_MS, ElevenLabsProvider, elevenLabsProvider;
4510
+ var KNOWN_VOICES, TTS_429_MAX_RETRIES, TTS_429_RETRY_DELAY_MS, ELEVENLABS_MUSIC_MODEL, ElevenLabsProvider, elevenLabsProvider;
4412
4511
  var init_ElevenLabsProvider = __esm({
4413
4512
  "../ai-providers/dist/elevenlabs/ElevenLabsProvider.js"() {
4414
4513
  "use strict";
4514
+ init_catalog();
4415
4515
  KNOWN_VOICES = {
4416
4516
  // Default/popular voices
4417
4517
  rachel: "21m00Tcm4TlvDq8ikWAM",
@@ -4445,6 +4545,7 @@ var init_ElevenLabsProvider = __esm({
4445
4545
  };
4446
4546
  TTS_429_MAX_RETRIES = 1;
4447
4547
  TTS_429_RETRY_DELAY_MS = 2e3;
4548
+ ELEVENLABS_MUSIC_MODEL = defaultModel("elevenlabs", "music").id;
4448
4549
  ElevenLabsProvider = class {
4449
4550
  constructor() {
4450
4551
  this.id = "elevenlabs";
@@ -4525,9 +4626,10 @@ var init_ElevenLabsProvider = __esm({
4525
4626
  stability: options.stability ?? 0.5,
4526
4627
  similarity_boost: options.similarityBoost ?? 0.75,
4527
4628
  style: options.style ?? 0,
4528
- use_speaker_boost: true
4529
- },
4530
- ...options.speed !== void 0 && { speed: options.speed }
4629
+ use_speaker_boost: true,
4630
+ // The API reads speed only inside voice_settings.
4631
+ ...options.speed !== void 0 && { speed: options.speed }
4632
+ }
4531
4633
  })
4532
4634
  });
4533
4635
  if (response.status === 429 && attempt < TTS_429_MAX_RETRIES) {
@@ -4597,7 +4699,7 @@ var init_ElevenLabsProvider = __esm({
4597
4699
  prompt_influence: options.promptInfluence ?? 0.3
4598
4700
  };
4599
4701
  if (options.duration !== void 0) {
4600
- const duration = Math.max(0.5, Math.min(22, options.duration));
4702
+ const duration = Math.max(0.5, Math.min(30, options.duration));
4601
4703
  body.duration_seconds = duration;
4602
4704
  }
4603
4705
  const response = await fetch(`${this.baseUrl}/sound-generation`, {
@@ -4642,7 +4744,7 @@ var init_ElevenLabsProvider = __esm({
4642
4744
  try {
4643
4745
  const body = {
4644
4746
  prompt: prompt3,
4645
- model_id: "music_v1"
4747
+ model_id: ELEVENLABS_MUSIC_MODEL
4646
4748
  };
4647
4749
  if (options.duration !== void 0) {
4648
4750
  const duration = Math.max(3, Math.min(600, options.duration));
@@ -4853,6 +4955,55 @@ var init_elevenlabs = __esm({
4853
4955
  });
4854
4956
 
4855
4957
  // ../ai-providers/dist/kokoro/KokoroProvider.js
4958
+ function splitForKokoro(text, maxChars = KOKORO_MAX_CHUNK_CHARS) {
4959
+ const pieces = (part, pattern) => part.length <= maxChars ? [part] : part.split(pattern).filter((p) => p.trim().length > 0);
4960
+ const units2 = text.trim().split(/(?<=[.!?…])\s+/).flatMap((sentence) => pieces(sentence, /(?<=[,;:])\s+/)).flatMap((clause) => {
4961
+ if (clause.length <= maxChars)
4962
+ return [clause];
4963
+ const words = [];
4964
+ let line = "";
4965
+ for (const word of clause.split(/\s+/)) {
4966
+ if (line && line.length + word.length + 1 > maxChars) {
4967
+ words.push(line);
4968
+ line = word;
4969
+ } else {
4970
+ line = line ? `${line} ${word}` : word;
4971
+ }
4972
+ }
4973
+ if (line)
4974
+ words.push(line);
4975
+ return words;
4976
+ });
4977
+ const chunks = [];
4978
+ for (const unit of units2) {
4979
+ const last = chunks.at(-1);
4980
+ if (last !== void 0 && last.length + unit.length + 1 <= maxChars) {
4981
+ chunks[chunks.length - 1] = `${last} ${unit}`;
4982
+ } else {
4983
+ chunks.push(unit);
4984
+ }
4985
+ }
4986
+ return chunks;
4987
+ }
4988
+ function encodeFloatWav(samples, sampleRate) {
4989
+ const dataBytes = samples.length * 4;
4990
+ const buffer = Buffer.alloc(44 + dataBytes);
4991
+ buffer.write("RIFF", 0, "ascii");
4992
+ buffer.writeUInt32LE(36 + dataBytes, 4);
4993
+ buffer.write("WAVE", 8, "ascii");
4994
+ buffer.write("fmt ", 12, "ascii");
4995
+ buffer.writeUInt32LE(16, 16);
4996
+ buffer.writeUInt16LE(3, 20);
4997
+ buffer.writeUInt16LE(1, 22);
4998
+ buffer.writeUInt32LE(sampleRate, 24);
4999
+ buffer.writeUInt32LE(sampleRate * 4, 28);
5000
+ buffer.writeUInt16LE(4, 32);
5001
+ buffer.writeUInt16LE(32, 34);
5002
+ buffer.write("data", 36, "ascii");
5003
+ buffer.writeUInt32LE(dataBytes, 40);
5004
+ Buffer.from(samples.buffer, samples.byteOffset, dataBytes).copy(buffer, 44);
5005
+ return buffer;
5006
+ }
4856
5007
  async function loadKokoroRuntime() {
4857
5008
  if (factoryOverride)
4858
5009
  return { factory: factoryOverride, device: "cpu" };
@@ -4890,7 +5041,7 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
4890
5041
  if (!base)
4891
5042
  return null;
4892
5043
  try {
4893
- const { readFile: readFile44, writeFile: writeFile48, mkdir: mkdir26 } = await import("node:fs/promises");
5044
+ const { readFile: readFile44, writeFile: writeFile47, mkdir: mkdir27 } = await import("node:fs/promises");
4894
5045
  const { join: join29, resolve: resolve65 } = await import("node:path");
4895
5046
  const { pathToFileURL } = await import("node:url");
4896
5047
  const { homedir: homedir5 } = await import("node:os");
@@ -4911,8 +5062,8 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
4911
5062
  async put(key, response) {
4912
5063
  try {
4913
5064
  const buf = Buffer.from(await response.arrayBuffer());
4914
- await mkdir26(cacheRoot, { recursive: true });
4915
- await writeFile48(join29(cacheRoot, encodeURIComponent(String(key))), buf);
5065
+ await mkdir27(cacheRoot, { recursive: true });
5066
+ await writeFile47(join29(cacheRoot, encodeURIComponent(String(key))), buf);
4916
5067
  } catch {
4917
5068
  }
4918
5069
  }
@@ -4961,12 +5112,13 @@ function normaliseEvent(raw) {
4961
5112
  total: typeof r.total === "number" ? r.total : void 0
4962
5113
  };
4963
5114
  }
4964
- var KOKORO_DEFAULT_VOICE, KOKORO_MODEL_ID, modelPromise, factoryOverride, KokoroProvider, kokoroProvider;
5115
+ var KOKORO_DEFAULT_VOICE, KOKORO_MODEL_ID, KOKORO_MAX_CHUNK_CHARS, modelPromise, factoryOverride, KokoroProvider, kokoroProvider;
4965
5116
  var init_KokoroProvider = __esm({
4966
5117
  "../ai-providers/dist/kokoro/KokoroProvider.js"() {
4967
5118
  "use strict";
4968
5119
  KOKORO_DEFAULT_VOICE = "af_heart";
4969
5120
  KOKORO_MODEL_ID = "onnx-community/Kokoro-82M-v1.0-ONNX";
5121
+ KOKORO_MAX_CHUNK_CHARS = 300;
4970
5122
  modelPromise = null;
4971
5123
  factoryOverride = null;
4972
5124
  KokoroProvider = class {
@@ -4993,11 +5145,32 @@ var init_KokoroProvider = __esm({
4993
5145
  }
4994
5146
  try {
4995
5147
  const model = await loadModel(options.onProgress);
4996
- const audio = await model.generate(text, {
5148
+ const voiceOptions = {
4997
5149
  voice: options.voice ?? KOKORO_DEFAULT_VOICE,
4998
5150
  speed: options.speed ?? 1
4999
- });
5000
- const buffer = Buffer.from(audio.toWav());
5151
+ };
5152
+ const chunks = splitForKokoro(text);
5153
+ let buffer;
5154
+ if (chunks.length === 1) {
5155
+ buffer = Buffer.from((await model.generate(chunks[0], voiceOptions)).toWav());
5156
+ } else {
5157
+ const parts = [];
5158
+ let sampleRate = 24e3;
5159
+ for (const chunk of chunks) {
5160
+ const audio = await model.generate(chunk, voiceOptions);
5161
+ if (!audio.audio)
5162
+ throw new Error("Kokoro returned no raw samples to join");
5163
+ parts.push(audio.audio);
5164
+ sampleRate = audio.sampling_rate ?? sampleRate;
5165
+ }
5166
+ const samples = new Float32Array(parts.reduce((n, part) => n + part.length, 0));
5167
+ let offset = 0;
5168
+ for (const part of parts) {
5169
+ samples.set(part, offset);
5170
+ offset += part.length;
5171
+ }
5172
+ buffer = encodeFloatWav(samples, sampleRate);
5173
+ }
5001
5174
  return {
5002
5175
  success: true,
5003
5176
  audioBuffer: buffer,
@@ -5032,11 +5205,13 @@ var init_kokoro = __esm({
5032
5205
  });
5033
5206
 
5034
5207
  // ../ai-providers/dist/openai-image/OpenAIImageProvider.js
5035
- var DEFAULT_MODEL, OpenAIImageProvider, openaiImageProvider;
5208
+ var OPENAI_IMAGE_DEFAULT_MODEL, DEFAULT_MODEL, OpenAIImageProvider, openaiImageProvider;
5036
5209
  var init_OpenAIImageProvider = __esm({
5037
5210
  "../ai-providers/dist/openai-image/OpenAIImageProvider.js"() {
5038
5211
  "use strict";
5039
- DEFAULT_MODEL = "gpt-image-2";
5212
+ init_catalog();
5213
+ OPENAI_IMAGE_DEFAULT_MODEL = defaultModel("openai", "image").id;
5214
+ DEFAULT_MODEL = OPENAI_IMAGE_DEFAULT_MODEL;
5040
5215
  OpenAIImageProvider = class {
5041
5216
  constructor() {
5042
5217
  this.id = "openai-image";
@@ -5058,7 +5233,7 @@ var init_OpenAIImageProvider = __esm({
5058
5233
  }
5059
5234
  /**
5060
5235
  * Generate images from text prompt
5061
- * Uses GPT Image 1.5 by default (fastest, best quality)
5236
+ * Uses GPT Image 2.5 Sunburst by default
5062
5237
  */
5063
5238
  async generateImage(prompt3, options = {}) {
5064
5239
  if (!this.apiKey) {
@@ -5068,28 +5243,20 @@ var init_OpenAIImageProvider = __esm({
5068
5243
  };
5069
5244
  }
5070
5245
  const model = options.model || DEFAULT_MODEL;
5071
- const isGPTImage = model === "gpt-image-1.5" || model === "gpt-image-2";
5072
5246
  try {
5073
5247
  const body = {
5074
5248
  model,
5075
5249
  prompt: prompt3,
5076
5250
  n: options.n || 1
5077
5251
  };
5078
- if (isGPTImage) {
5079
- const qualityMap = {
5080
- standard: "medium",
5081
- hd: "high"
5082
- };
5083
- const quality = options.quality || "medium";
5084
- body.quality = qualityMap[quality] || quality;
5085
- if (options.size && options.size !== "auto") {
5086
- body.size = options.size;
5087
- }
5088
- } else {
5089
- body.response_format = "url";
5090
- body.size = options.size || "1024x1024";
5091
- body.quality = options.quality === "high" ? "hd" : options.quality || "standard";
5092
- body.style = options.style || "vivid";
5252
+ const qualityMap = {
5253
+ standard: "medium",
5254
+ hd: "high"
5255
+ };
5256
+ const quality = options.quality || "medium";
5257
+ body.quality = qualityMap[quality] || quality;
5258
+ if (options.size && options.size !== "auto") {
5259
+ body.size = options.size;
5093
5260
  }
5094
5261
  const response = await fetch(`${this.baseUrl}/images/generations`, {
5095
5262
  method: "POST",
@@ -5242,49 +5409,6 @@ var init_OpenAIImageProvider = __esm({
5242
5409
  };
5243
5410
  }
5244
5411
  }
5245
- /**
5246
- * Create image variations (uses DALL-E 2)
5247
- */
5248
- async createVariation(imageBuffer, options = {}) {
5249
- if (!this.apiKey) {
5250
- return {
5251
- success: false,
5252
- error: "OpenAI API key not configured"
5253
- };
5254
- }
5255
- try {
5256
- const formData = new FormData();
5257
- const uint8Array = new Uint8Array(imageBuffer);
5258
- formData.append("image", new Blob([uint8Array], { type: "image/png" }), "image.png");
5259
- formData.append("model", "dall-e-2");
5260
- formData.append("n", String(options.n || 1));
5261
- formData.append("size", options.size || "1024x1024");
5262
- const response = await fetch(`${this.baseUrl}/images/variations`, {
5263
- method: "POST",
5264
- headers: {
5265
- Authorization: `Bearer ${this.apiKey}`
5266
- },
5267
- body: formData
5268
- });
5269
- if (!response.ok) {
5270
- const error = await response.text();
5271
- return {
5272
- success: false,
5273
- error: `API error: ${response.status} - ${error}`
5274
- };
5275
- }
5276
- const data = await response.json();
5277
- return {
5278
- success: true,
5279
- images: data.data.map((img) => ({ url: img.url }))
5280
- };
5281
- } catch (error) {
5282
- return {
5283
- success: false,
5284
- error: error instanceof Error ? error.message : "Unknown error"
5285
- };
5286
- }
5287
- }
5288
5412
  };
5289
5413
  openaiImageProvider = new OpenAIImageProvider();
5290
5414
  }
@@ -5410,7 +5534,8 @@ var DEFAULT_MODEL3, RunwayProvider, runwayProvider;
5410
5534
  var init_RunwayProvider = __esm({
5411
5535
  "../ai-providers/dist/runway/RunwayProvider.js"() {
5412
5536
  "use strict";
5413
- DEFAULT_MODEL3 = "gen4.5";
5537
+ init_catalog();
5538
+ DEFAULT_MODEL3 = defaultModel("runway", "video").id;
5414
5539
  RunwayProvider = class _RunwayProvider {
5415
5540
  constructor() {
5416
5541
  this.id = "runway";
@@ -5463,6 +5588,13 @@ var init_RunwayProvider = __esm({
5463
5588
  }
5464
5589
  const hasImage = !!options?.referenceImage;
5465
5590
  const endpoint = hasImage ? "image_to_video" : "text_to_video";
5591
+ if (!hasImage && apiRatio === "960:960") {
5592
+ return {
5593
+ id: "",
5594
+ status: "failed",
5595
+ error: "Runway text-to-video supports 16:9 and 9:16 only. Use 16:9 or 9:16, or pass an image (-i) for square output."
5596
+ };
5597
+ }
5466
5598
  const body = {
5467
5599
  model,
5468
5600
  promptText: prompt3,
@@ -5563,7 +5695,9 @@ var init_RunwayProvider = __esm({
5563
5695
  SUCCEEDED: "completed",
5564
5696
  FAILED: "failed",
5565
5697
  CANCELLED: "cancelled",
5566
- THROTTLED: "failed"
5698
+ // THROTTLED means queued behind the account's concurrency limit; the
5699
+ // task starts on its own, so keep polling.
5700
+ THROTTLED: "pending"
5567
5701
  };
5568
5702
  const result = {
5569
5703
  id: data.id,
@@ -5573,8 +5707,9 @@ var init_RunwayProvider = __esm({
5573
5707
  if (data.status === "SUCCEEDED" && data.output && data.output.length > 0) {
5574
5708
  result.videoUrl = data.output[0];
5575
5709
  }
5576
- if (data.status === "FAILED" || data.status === "THROTTLED") {
5577
- result.error = data.failure || data.failureCode || "Generation failed";
5710
+ if (data.status === "FAILED") {
5711
+ const reason = data.failure || "Generation failed";
5712
+ result.error = data.failureCode ? `${reason} (${data.failureCode})` : reason;
5578
5713
  }
5579
5714
  return result;
5580
5715
  } catch (error) {
@@ -5586,27 +5721,14 @@ var init_RunwayProvider = __esm({
5586
5721
  }
5587
5722
  }
5588
5723
  /**
5589
- * Cancel ongoing generation
5724
+ * Cancel a pending or running task. Runway documents cancellation as
5725
+ * `DELETE /v1/tasks/{id}` (there is no `/cancel` route).
5590
5726
  */
5591
5727
  async cancelGeneration(id) {
5592
- if (!this.apiKey) {
5593
- return false;
5594
- }
5595
- try {
5596
- const response = await fetch(`${this.baseUrl}/tasks/${id}/cancel`, {
5597
- method: "POST",
5598
- headers: {
5599
- Authorization: `Bearer ${this.apiKey}`,
5600
- "X-Runway-Version": _RunwayProvider.API_VERSION
5601
- }
5602
- });
5603
- return response.ok;
5604
- } catch {
5605
- return false;
5606
- }
5728
+ return this.deleteTask(id);
5607
5729
  }
5608
5730
  /**
5609
- * Delete a completed task
5731
+ * Cancel a running task, or delete a finished one (`DELETE /v1/tasks/{id}`).
5610
5732
  */
5611
5733
  async deleteTask(id) {
5612
5734
  if (!this.apiKey) {
@@ -5715,12 +5837,25 @@ var init_runway = __esm({
5715
5837
 
5716
5838
  // ../ai-providers/dist/kling/KlingProvider.js
5717
5839
  import { createHmac } from "node:crypto";
5840
+ function resolveKlingModel(alias) {
5841
+ const model = findModel("kling", "video", alias);
5842
+ if (model)
5843
+ return model.id;
5844
+ throw new Error(`Unknown Kling model "${alias}". Valid: ${modelAliases("kling", "video").join(", ")}.`);
5845
+ }
5846
+ function klingDuration(model, seconds) {
5847
+ const requested = typeof seconds === "number" && Number.isFinite(seconds) ? Math.round(seconds) : 5;
5848
+ if (model === "kling-v3")
5849
+ return String(Math.max(3, Math.min(15, requested)));
5850
+ return requested >= 10 ? "10" : "5";
5851
+ }
5718
5852
  var DEFAULT_MODEL4, STD_MODE_MODELS, KlingProvider, klingProvider;
5719
5853
  var init_KlingProvider = __esm({
5720
5854
  "../ai-providers/dist/kling/KlingProvider.js"() {
5721
5855
  "use strict";
5722
- DEFAULT_MODEL4 = "kling-v2-5-turbo";
5723
- STD_MODE_MODELS = ["kling-v2-5-turbo", "kling-v2-6", "kling-v3", "kling-v3-omni"];
5856
+ init_catalog();
5857
+ DEFAULT_MODEL4 = defaultModel("kling", "video").id;
5858
+ STD_MODE_MODELS = ["kling-v2-5-turbo", "kling-v2-6", "kling-v3"];
5724
5859
  KlingProvider = class {
5725
5860
  constructor() {
5726
5861
  this.id = "kling";
@@ -5735,11 +5870,11 @@ var init_KlingProvider = __esm({
5735
5870
  async initialize(config2) {
5736
5871
  if (config2.apiKey) {
5737
5872
  const parts = config2.apiKey.split(":");
5738
- if (parts.length === 2) {
5873
+ if (parts.length === 2 && parts[0] && parts[1]) {
5739
5874
  this.accessKey = parts[0];
5740
5875
  this.secretKey = parts[1];
5741
5876
  } else {
5742
- this.accessKey = config2.apiKey;
5877
+ this.apiKey = config2.apiKey;
5743
5878
  }
5744
5879
  }
5745
5880
  if (config2.baseUrl) {
@@ -5747,7 +5882,11 @@ var init_KlingProvider = __esm({
5747
5882
  }
5748
5883
  }
5749
5884
  isConfigured() {
5750
- return !!(this.accessKey && this.secretKey);
5885
+ return !!(this.apiKey || this.accessKey && this.secretKey);
5886
+ }
5887
+ /** Bearer value: the API key itself, or a JWT signed from the legacy pair. */
5888
+ bearerToken() {
5889
+ return this.apiKey ?? this.generateToken();
5751
5890
  }
5752
5891
  /**
5753
5892
  * Generate JWT token for Kling API authentication
@@ -5777,11 +5916,11 @@ var init_KlingProvider = __esm({
5777
5916
  return {
5778
5917
  id: "",
5779
5918
  status: "failed",
5780
- error: "Kling API credentials not configured. Use format: KLING_ACCESS_KEY:KLING_SECRET_KEY"
5919
+ error: "Kling API credentials not configured. Set KLING_API_KEY to your Kling API key (or the legacy ACCESS_KEY:SECRET_KEY pair)."
5781
5920
  };
5782
5921
  }
5783
5922
  try {
5784
- const token = this.generateToken();
5923
+ const token = this.bearerToken();
5785
5924
  const model = options?.model || DEFAULT_MODEL4;
5786
5925
  let mode = options?.mode || "std";
5787
5926
  if (!STD_MODE_MODELS.includes(model) && mode === "std") {
@@ -5799,7 +5938,7 @@ var init_KlingProvider = __esm({
5799
5938
  model_name: model,
5800
5939
  mode,
5801
5940
  aspect_ratio: aspectRatioMap[options?.aspectRatio || "16:9"] || "16:9",
5802
- duration: options?.duration === 10 ? "10" : "5"
5941
+ duration: klingDuration(model, options?.duration)
5803
5942
  };
5804
5943
  if (options?.negativePrompt) {
5805
5944
  body.negative_prompt = options.negativePrompt;
@@ -5911,7 +6050,7 @@ var init_KlingProvider = __esm({
5911
6050
  };
5912
6051
  }
5913
6052
  try {
5914
- const token = this.generateToken();
6053
+ const token = this.bearerToken();
5915
6054
  const response = await fetch(`${this.baseUrl}/videos/${type}/${id}`, {
5916
6055
  headers: {
5917
6056
  "Content-Type": "application/json",
@@ -5993,11 +6132,11 @@ var init_KlingProvider = __esm({
5993
6132
  return {
5994
6133
  id: "",
5995
6134
  status: "failed",
5996
- error: "Kling API credentials not configured. Use format: KLING_ACCESS_KEY:KLING_SECRET_KEY"
6135
+ error: "Kling API credentials not configured. Set KLING_API_KEY to your Kling API key (or the legacy ACCESS_KEY:SECRET_KEY pair)."
5997
6136
  };
5998
6137
  }
5999
6138
  try {
6000
- const token = this.generateToken();
6139
+ const token = this.bearerToken();
6001
6140
  const body = {
6002
6141
  video_id: videoId,
6003
6142
  duration: options?.duration || "5"
@@ -6037,7 +6176,7 @@ var init_KlingProvider = __esm({
6037
6176
  };
6038
6177
  }
6039
6178
  try {
6040
- const token = this.generateToken();
6179
+ const token = this.bearerToken();
6041
6180
  const response = await fetch(`${this.baseUrl}/videos/video-extend/${id}`, {
6042
6181
  headers: {
6043
6182
  "Content-Type": "application/json",
@@ -6153,11 +6292,24 @@ var init_kling = __esm({
6153
6292
  });
6154
6293
 
6155
6294
  // ../ai-providers/dist/grok/GrokProvider.js
6156
- var DEFAULT_MODEL5, GrokProvider, grokProvider;
6295
+ function resolveGrokVideoModel(alias) {
6296
+ const model = findModel("grok", "video", alias);
6297
+ if (model)
6298
+ return model.id;
6299
+ throw new Error(`Unknown Grok video model "${alias}". Valid: ${modelAliases("grok", "video").join(", ")}.`);
6300
+ }
6301
+ function resolveGrokImageModel(alias) {
6302
+ const model = (findModel("grok", "image", alias) ?? defaultModel("grok", "image")).id;
6303
+ return model === "grok-imagine-image-2.0" ? { model, quality: "medium" } : { model };
6304
+ }
6305
+ var DEFAULT_MODEL5, DEFAULT_VIDEO_RESOLUTION, GROK_VIDEO_RESOLUTIONS, GrokProvider, grokProvider;
6157
6306
  var init_GrokProvider = __esm({
6158
6307
  "../ai-providers/dist/grok/GrokProvider.js"() {
6159
6308
  "use strict";
6160
- DEFAULT_MODEL5 = "grok-imagine-video";
6309
+ init_catalog();
6310
+ DEFAULT_MODEL5 = defaultModel("grok", "video").id;
6311
+ DEFAULT_VIDEO_RESOLUTION = "720p";
6312
+ GROK_VIDEO_RESOLUTIONS = ["480p", "720p", "1080p"];
6161
6313
  GrokProvider = class {
6162
6314
  constructor() {
6163
6315
  this.id = "grok";
@@ -6191,6 +6343,7 @@ var init_GrokProvider = __esm({
6191
6343
  try {
6192
6344
  const body = {
6193
6345
  model: options.model || "grok-imagine-image",
6346
+ ...options.quality ? { quality: options.quality } : {},
6194
6347
  prompt: prompt3,
6195
6348
  n: options.n || 1,
6196
6349
  response_format: options.responseFormat || "url"
@@ -6258,6 +6411,7 @@ var init_GrokProvider = __esm({
6258
6411
  const dataUri = `data:image/png;base64,${base64}`;
6259
6412
  const body = {
6260
6413
  model: options.model || "grok-imagine-image",
6414
+ ...options.quality ? { quality: options.quality } : {},
6261
6415
  prompt: prompt3,
6262
6416
  image: {
6263
6417
  url: dataUri,
@@ -6324,11 +6478,15 @@ var init_GrokProvider = __esm({
6324
6478
  try {
6325
6479
  const duration = Math.round(Math.min(15, Math.max(1, options?.duration || 5)));
6326
6480
  const body = {
6327
- model: DEFAULT_MODEL5,
6481
+ model: options?.model || DEFAULT_MODEL5,
6328
6482
  prompt: prompt3,
6329
6483
  duration,
6330
- aspect_ratio: options?.aspectRatio || "16:9"
6484
+ aspect_ratio: options?.aspectRatio || "16:9",
6485
+ resolution: GROK_VIDEO_RESOLUTIONS.includes(options?.resolution ?? "") ? options.resolution : DEFAULT_VIDEO_RESOLUTION
6331
6486
  };
6487
+ if (typeof options?.generateAudio === "boolean") {
6488
+ body.generate_audio = options.generateAudio;
6489
+ }
6332
6490
  if (options?.referenceImage) {
6333
6491
  body.image = { url: options.referenceImage };
6334
6492
  }
@@ -6390,13 +6548,14 @@ var init_GrokProvider = __esm({
6390
6548
  const statusMap = {
6391
6549
  pending: "pending",
6392
6550
  done: "completed",
6393
- expired: "failed"
6551
+ expired: "failed",
6552
+ failed: "failed"
6394
6553
  };
6395
6554
  return {
6396
6555
  id,
6397
6556
  status: statusMap[data.status] || "pending",
6398
6557
  videoUrl: data.video?.url,
6399
- error: data.status === "expired" ? "Generation expired" : void 0
6558
+ error: data.status === "expired" ? "Generation expired" : data.status === "failed" ? `Generation failed: ${data.error?.message ?? data.error?.code ?? "unknown error"}` : void 0
6400
6559
  };
6401
6560
  } catch (error) {
6402
6561
  return {
@@ -10590,14 +10749,21 @@ var require_src = __commonJS({
10590
10749
  });
10591
10750
 
10592
10751
  // ../ai-providers/dist/fal/FalProvider.js
10752
+ function resolveSeedanceVariant(alias) {
10753
+ const model = findModel("seedance", "video", alias);
10754
+ if (model)
10755
+ return model.id;
10756
+ throw new Error(`Unknown Seedance model "${alias}". Valid: ${modelAliases("seedance", "video").join(", ")}.`);
10757
+ }
10593
10758
  function estimateSeedanceVideoCostUsd(opts) {
10594
10759
  const shortSide = SEEDANCE_SHORT_SIDE[opts.resolution ?? "720p"] ?? 720;
10595
10760
  const [a, b] = (opts.aspectRatio ?? "16:9").split(":").map(Number);
10596
10761
  const ratioFactor = a > 0 && b > 0 ? Math.max(a, b) / Math.min(a, b) : 16 / 9;
10597
10762
  const longSide = Math.round(shortSide * ratioFactor);
10598
10763
  const tokensPerSecond = shortSide * longSide * 24 / 1024;
10599
- let usdPerSecond = tokensPerSecond / 1e3 * SEEDANCE_USD_PER_1K_TOKENS;
10600
- if (opts.fast && shortSide <= 720)
10764
+ const rate = opts.v25 ? shortSide >= 1080 ? SEEDANCE_25_1080P_USD_PER_1K_TOKENS : SEEDANCE_25_USD_PER_1K_TOKENS : SEEDANCE_USD_PER_1K_TOKENS;
10765
+ let usdPerSecond = tokensPerSecond / 1e3 * rate;
10766
+ if (opts.fast && !opts.v25 && shortSide <= 720)
10601
10767
  usdPerSecond *= SEEDANCE_FAST_FACTOR;
10602
10768
  if (opts.hasVideoReference)
10603
10769
  usdPerSecond *= SEEDANCE_VIDEO_REF_FACTOR;
@@ -10705,34 +10871,43 @@ function normaliseResolution(value) {
10705
10871
  return "1080p";
10706
10872
  return "720p";
10707
10873
  }
10708
- function normaliseDuration(value) {
10709
- if (typeof value !== "number")
10874
+ function normaliseDuration(value, maxSec) {
10875
+ if (typeof value !== "number" || !Number.isFinite(value))
10710
10876
  return "auto";
10711
- if (!Number.isFinite(value))
10712
- return "auto";
10713
- return Math.max(4, Math.min(15, Math.round(value)));
10877
+ return String(Math.max(4, Math.min(maxSec, Math.round(value))));
10714
10878
  }
10715
- var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, SEEDANCE_USD_PER_1K_TOKENS, SEEDANCE_FAST_FACTOR, SEEDANCE_VIDEO_REF_FACTOR, SEEDANCE_SHORT_SIDE, FalProvider, falProvider;
10879
+ var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, MAX_DURATION_SEC, VALID_RESOLUTIONS, VALID_ASPECTS, SEEDANCE_USD_PER_1K_TOKENS, SEEDANCE_25_USD_PER_1K_TOKENS, SEEDANCE_25_1080P_USD_PER_1K_TOKENS, SEEDANCE_FAST_FACTOR, SEEDANCE_VIDEO_REF_FACTOR, SEEDANCE_SHORT_SIDE, FalProvider, falProvider;
10716
10880
  var init_FalProvider = __esm({
10717
10881
  "../ai-providers/dist/fal/FalProvider.js"() {
10718
10882
  "use strict";
10719
10883
  import_client = __toESM(require_src(), 1);
10884
+ init_catalog();
10720
10885
  ENDPOINT_TEXT_TO_VIDEO = {
10721
10886
  "seedance-2.0": "bytedance/seedance-2.0/text-to-video",
10722
- "seedance-2.0-fast": "bytedance/seedance-2.0/fast/text-to-video"
10887
+ "seedance-2.0-fast": "bytedance/seedance-2.0/fast/text-to-video",
10888
+ "seedance-2.5": "bytedance/seedance-2.5/text-to-video"
10723
10889
  };
10724
10890
  ENDPOINT_IMAGE_TO_VIDEO = {
10725
10891
  "seedance-2.0": "bytedance/seedance-2.0/image-to-video",
10726
- "seedance-2.0-fast": "bytedance/seedance-2.0/fast/image-to-video"
10892
+ "seedance-2.0-fast": "bytedance/seedance-2.0/fast/image-to-video",
10893
+ "seedance-2.5": "bytedance/seedance-2.5/image-to-video"
10727
10894
  };
10728
10895
  ENDPOINT_REFERENCE_TO_VIDEO = {
10729
10896
  "seedance-2.0": "bytedance/seedance-2.0/reference-to-video",
10730
- "seedance-2.0-fast": "bytedance/seedance-2.0/fast/reference-to-video"
10897
+ "seedance-2.0-fast": "bytedance/seedance-2.0/fast/reference-to-video",
10898
+ "seedance-2.5": "bytedance/seedance-2.5/reference-to-video"
10731
10899
  };
10732
10900
  DEFAULT_VARIANT = "seedance-2.0";
10901
+ MAX_DURATION_SEC = {
10902
+ "seedance-2.0": 15,
10903
+ "seedance-2.0-fast": 15,
10904
+ "seedance-2.5": 30
10905
+ };
10733
10906
  VALID_RESOLUTIONS = ["480p", "720p", "1080p"];
10734
10907
  VALID_ASPECTS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"];
10735
10908
  SEEDANCE_USD_PER_1K_TOKENS = 0.014;
10909
+ SEEDANCE_25_USD_PER_1K_TOKENS = 0.0214;
10910
+ SEEDANCE_25_1080P_USD_PER_1K_TOKENS = 0.0234;
10736
10911
  SEEDANCE_FAST_FACTOR = 0.8;
10737
10912
  SEEDANCE_VIDEO_REF_FACTOR = 0.6;
10738
10913
  SEEDANCE_SHORT_SIDE = { "480p": 480, "720p": 720, "1080p": 1080 };
@@ -10787,7 +10962,7 @@ var init_FalProvider = __esm({
10787
10962
  const endpointId = hasReferences ? ENDPOINT_REFERENCE_TO_VIDEO[variant] : isImageToVideo ? ENDPOINT_IMAGE_TO_VIDEO[variant] : ENDPOINT_TEXT_TO_VIDEO[variant];
10788
10963
  const aspect = normaliseAspect(options?.aspectRatio);
10789
10964
  const resolution = normaliseResolution(options?.resolution);
10790
- const duration = normaliseDuration(options?.duration);
10965
+ const duration = normaliseDuration(options?.duration, MAX_DURATION_SEC[variant]);
10791
10966
  const input = {
10792
10967
  prompt: prompt3,
10793
10968
  aspect_ratio: aspect,
@@ -10805,10 +10980,12 @@ var init_FalProvider = __esm({
10805
10980
  } else if (referenceImage) {
10806
10981
  input.image_url = referenceImage;
10807
10982
  }
10808
- if (options?.negativePrompt)
10809
- input.negative_prompt = options.negativePrompt;
10810
- if (typeof options?.seed === "number")
10811
- input.seed = options.seed;
10983
+ if (variant !== "seedance-2.5") {
10984
+ if (options?.negativePrompt)
10985
+ input.negative_prompt = options.negativePrompt;
10986
+ if (typeof options?.seed === "number")
10987
+ input.seed = options.seed;
10988
+ }
10812
10989
  if (typeof options?.generateAudio === "boolean")
10813
10990
  input.generate_audio = options.generateAudio;
10814
10991
  if (options?.endUserId)
@@ -11535,6 +11712,7 @@ var init_replicate = __esm({
11535
11712
  var dist_exports2 = {};
11536
11713
  __export(dist_exports2, {
11537
11714
  ClaudeProvider: () => ClaudeProvider,
11715
+ ELEVENLABS_MUSIC_MODEL: () => ELEVENLABS_MUSIC_MODEL,
11538
11716
  ElevenLabsProvider: () => ElevenLabsProvider,
11539
11717
  FalProvider: () => FalProvider,
11540
11718
  GEMINI_AGENT_DEFAULT_TEXT_MODEL: () => GEMINI_AGENT_DEFAULT_TEXT_MODEL,
@@ -11548,6 +11726,8 @@ __export(dist_exports2, {
11548
11726
  KOKORO_MODEL_ID: () => KOKORO_MODEL_ID,
11549
11727
  KlingProvider: () => KlingProvider,
11550
11728
  KokoroProvider: () => KokoroProvider,
11729
+ MODEL_CATALOG: () => MODEL_CATALOG,
11730
+ OPENAI_IMAGE_DEFAULT_MODEL: () => OPENAI_IMAGE_DEFAULT_MODEL,
11551
11731
  OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
11552
11732
  OllamaProvider: () => OllamaProvider,
11553
11733
  OmniProvider: () => OmniProvider,
@@ -11556,13 +11736,16 @@ __export(dist_exports2, {
11556
11736
  OpenAiTtsProvider: () => OpenAiTtsProvider,
11557
11737
  ReplicateProvider: () => ReplicateProvider,
11558
11738
  RunwayProvider: () => RunwayProvider,
11739
+ VEO_MODEL_ALIASES: () => VEO_MODEL_ALIASES,
11559
11740
  WhisperProvider: () => WhisperProvider,
11560
11741
  claudeProvider: () => claudeProvider,
11742
+ defaultModel: () => defaultModel,
11561
11743
  defineApiKey: () => defineApiKey,
11562
11744
  defineProvider: () => defineProvider,
11563
11745
  elevenLabsProvider: () => elevenLabsProvider,
11564
11746
  estimateSeedanceVideoCostUsd: () => estimateSeedanceVideoCostUsd,
11565
11747
  falProvider: () => falProvider,
11748
+ findModel: () => findModel,
11566
11749
  geminiProvider: () => geminiProvider,
11567
11750
  getAllApiKeys: () => getAllApiKeys,
11568
11751
  getAllProviders: () => getAllProviders,
@@ -11576,10 +11759,13 @@ __export(dist_exports2, {
11576
11759
  getSetupProviders: () => getSetupProviders,
11577
11760
  grokProvider: () => grokProvider,
11578
11761
  isGeminiTextModelAlias: () => isGeminiTextModelAlias,
11762
+ klingDuration: () => klingDuration,
11579
11763
  klingProvider: () => klingProvider,
11580
11764
  kokoroProvider: () => kokoroProvider,
11765
+ listModels: () => listModels,
11581
11766
  loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
11582
11767
  loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
11768
+ modelAliases: () => modelAliases,
11583
11769
  ollamaProvider: () => ollamaProvider,
11584
11770
  omniProvider: () => omniProvider,
11585
11771
  openaiImageProvider: () => openaiImageProvider,
@@ -11588,6 +11774,11 @@ __export(dist_exports2, {
11588
11774
  providerRegistry: () => providerRegistry,
11589
11775
  replicateProvider: () => replicateProvider,
11590
11776
  resolveGeminiTextModel: () => resolveGeminiTextModel,
11777
+ resolveGrokImageModel: () => resolveGrokImageModel,
11778
+ resolveGrokVideoModel: () => resolveGrokVideoModel,
11779
+ resolveKlingModel: () => resolveKlingModel,
11780
+ resolveSeedanceVariant: () => resolveSeedanceVariant,
11781
+ resolveVeoModel: () => resolveVeoModel,
11591
11782
  resolveVoiceId: () => resolveVoiceId,
11592
11783
  runwayProvider: () => runwayProvider,
11593
11784
  whisperProvider: () => whisperProvider
@@ -11601,6 +11792,7 @@ var init_dist2 = __esm({
11601
11792
  init_define_provider();
11602
11793
  init_whisper();
11603
11794
  init_gemini();
11795
+ init_catalog2();
11604
11796
  init_openai();
11605
11797
  init_claude();
11606
11798
  init_ollama();
@@ -19843,12 +20035,15 @@ var init_ai_helpers = __esm({
19843
20035
  });
19844
20036
 
19845
20037
  // src/commands/_shared/video-utils.ts
19846
- async function uploadToImgbb(imageBuffer, apiKey) {
20038
+ async function uploadToImgbb(imageBuffer, apiKey, expirationSeconds) {
19847
20039
  try {
19848
20040
  const base64Image = imageBuffer.toString("base64");
19849
20041
  const formData = new URLSearchParams();
19850
20042
  formData.append("key", apiKey);
19851
20043
  formData.append("image", base64Image);
20044
+ if (expirationSeconds !== void 0) {
20045
+ formData.append("expiration", String(expirationSeconds));
20046
+ }
19852
20047
  const response = await fetch("https://api.imgbb.com/1/upload", {
19853
20048
  method: "POST",
19854
20049
  body: formData
@@ -19911,7 +20106,10 @@ function awsEncode(value) {
19911
20106
  (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
19912
20107
  );
19913
20108
  }
19914
- function presignS3PutUrl(params) {
20109
+ function clamp(value, min, max) {
20110
+ return Math.min(max, Math.max(min, Math.round(value)));
20111
+ }
20112
+ function presignS3Url(params) {
19915
20113
  const now = /* @__PURE__ */ new Date();
19916
20114
  const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
19917
20115
  const dateStamp = amzDate.slice(0, 8);
@@ -19922,7 +20120,7 @@ function presignS3PutUrl(params) {
19922
20120
  "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
19923
20121
  "X-Amz-Credential": `${params.accessKeyId}/${credentialScope}`,
19924
20122
  "X-Amz-Date": amzDate,
19925
- "X-Amz-Expires": String(params.ttlSeconds),
20123
+ "X-Amz-Expires": String(clamp(params.ttlSeconds, 1, S3_MAX_PRESIGN_SECONDS)),
19926
20124
  "X-Amz-SignedHeaders": "host"
19927
20125
  };
19928
20126
  if (params.sessionToken) {
@@ -19930,7 +20128,7 @@ function presignS3PutUrl(params) {
19930
20128
  }
19931
20129
  const canonicalQuery = Object.entries(query).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${awsEncode(key)}=${awsEncode(value)}`).join("&");
19932
20130
  const canonicalRequest = [
19933
- "PUT",
20131
+ params.method,
19934
20132
  canonicalUri,
19935
20133
  canonicalQuery,
19936
20134
  `host:${host}`,
@@ -19989,7 +20187,8 @@ async function resolveUploadHost() {
19989
20187
  const ext = extensionFor(opts);
19990
20188
  const prefix = safePrefix(settings.s3.prefix);
19991
20189
  const key = `${prefix}/${Date.now()}-${randomUUID()}.${ext}`;
19992
- const presignedUrl = presignS3PutUrl({
20190
+ const presignedUrl = presignS3Url({
20191
+ method: "PUT",
19993
20192
  accessKeyId,
19994
20193
  secretAccessKey,
19995
20194
  sessionToken,
@@ -20008,10 +20207,26 @@ async function resolveUploadHost() {
20008
20207
  if (!response.ok) {
20009
20208
  throw new Error(`S3 upload failed (${response.status}): ${response.statusText}`);
20010
20209
  }
20210
+ if (settings.s3.publicBaseUrl) {
20211
+ return {
20212
+ provider: "s3",
20213
+ url: publicS3Url({ region, bucket, key, publicBaseUrl: settings.s3.publicBaseUrl })
20214
+ };
20215
+ }
20216
+ const ttlSeconds = clamp(settings.ttlSeconds, 1, S3_MAX_PRESIGN_SECONDS);
20011
20217
  return {
20012
20218
  provider: "s3",
20013
- url: publicS3Url({ region, bucket, key, publicBaseUrl: settings.s3.publicBaseUrl }),
20014
- expiresAt: new Date(Date.now() + settings.ttlSeconds * 1e3).toISOString()
20219
+ url: presignS3Url({
20220
+ method: "GET",
20221
+ accessKeyId,
20222
+ secretAccessKey,
20223
+ sessionToken,
20224
+ region,
20225
+ bucket,
20226
+ key,
20227
+ ttlSeconds
20228
+ }),
20229
+ expiresAt: new Date(Date.now() + ttlSeconds * 1e3).toISOString()
20015
20230
  };
20016
20231
  }
20017
20232
  };
@@ -20023,19 +20238,28 @@ async function resolveUploadHost() {
20023
20238
  if (!imgbbKey) {
20024
20239
  throw new Error("IMGBB_API_KEY required for image-to-video uploads.");
20025
20240
  }
20026
- const result = await uploadToImgbb(imageBuffer, imgbbKey);
20241
+ const expiration = clamp(settings.ttlSeconds, IMGBB_MIN_EXPIRATION_SECONDS, IMGBB_MAX_EXPIRATION_SECONDS);
20242
+ const result = await uploadToImgbb(imageBuffer, imgbbKey, expiration);
20027
20243
  if (!result.success || !result.url) {
20028
20244
  throw new Error(`ImgBB upload failed: ${result.error ?? "unknown error"}`);
20029
20245
  }
20030
- return { provider: "imgbb", url: result.url };
20246
+ return {
20247
+ provider: "imgbb",
20248
+ url: result.url,
20249
+ expiresAt: new Date(Date.now() + expiration * 1e3).toISOString()
20250
+ };
20031
20251
  }
20032
20252
  };
20033
20253
  }
20254
+ var S3_MAX_PRESIGN_SECONDS, IMGBB_MIN_EXPIRATION_SECONDS, IMGBB_MAX_EXPIRATION_SECONDS;
20034
20255
  var init_upload_host = __esm({
20035
20256
  "src/utils/upload-host.ts"() {
20036
20257
  "use strict";
20037
20258
  init_config();
20038
20259
  init_video_utils();
20260
+ S3_MAX_PRESIGN_SECONDS = 7 * 24 * 60 * 60;
20261
+ IMGBB_MIN_EXPIRATION_SECONDS = 60;
20262
+ IMGBB_MAX_EXPIRATION_SECONDS = 15552e3;
20039
20263
  }
20040
20264
  });
20041
20265
 
@@ -20653,8 +20877,14 @@ __export(ai_video_exports, {
20653
20877
  executeVideoGenerate: () => executeVideoGenerate,
20654
20878
  executeVideoStatus: () => executeVideoStatus
20655
20879
  });
20656
- import { readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
20657
- import { resolve as resolve3 } from "node:path";
20880
+ import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
20881
+ import { dirname as dirname2, resolve as resolve3 } from "node:path";
20882
+ async function saveOutput(output, buffer) {
20883
+ const outputPath = resolve3(process.cwd(), output);
20884
+ await mkdir2(dirname2(outputPath), { recursive: true });
20885
+ await writeFile3(outputPath, buffer);
20886
+ return outputPath;
20887
+ }
20658
20888
  async function executeVideoGenerate(options) {
20659
20889
  const {
20660
20890
  prompt: prompt3,
@@ -20670,7 +20900,10 @@ async function executeVideoGenerate(options) {
20670
20900
  negative,
20671
20901
  resolution,
20672
20902
  veoModel = "3.1-fast",
20903
+ runwayModel,
20673
20904
  seedanceModel = "quality",
20905
+ grokModel,
20906
+ klingModel,
20674
20907
  generateAudio,
20675
20908
  output,
20676
20909
  wait = true,
@@ -20725,7 +20958,7 @@ async function executeVideoGenerate(options) {
20725
20958
  });
20726
20959
  falImage = upload.url;
20727
20960
  }
20728
- const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
20961
+ const model = resolveSeedanceVariant(seedanceModel);
20729
20962
  const result = await fal.generateVideo(prompt3, {
20730
20963
  prompt: prompt3,
20731
20964
  referenceImage: references.length > 0 ? void 0 : falImage,
@@ -20742,8 +20975,7 @@ async function executeVideoGenerate(options) {
20742
20975
  let outputPath;
20743
20976
  if (output && result.videoUrl) {
20744
20977
  const buffer = await downloadVideo(result.videoUrl, key);
20745
- outputPath = resolve3(process.cwd(), output);
20746
- await writeFile3(outputPath, buffer);
20978
+ outputPath = await saveOutput(output, buffer);
20747
20979
  }
20748
20980
  return {
20749
20981
  success: true,
@@ -20758,6 +20990,7 @@ async function executeVideoGenerate(options) {
20758
20990
  await runway.initialize({ apiKey: key });
20759
20991
  const result = await runway.generateVideo(prompt3, {
20760
20992
  prompt: prompt3,
20993
+ model: runwayModel,
20761
20994
  referenceImage,
20762
20995
  duration,
20763
20996
  aspectRatio: ratio,
@@ -20774,8 +21007,7 @@ async function executeVideoGenerate(options) {
20774
21007
  let outputPath;
20775
21008
  if (output && finalResult.videoUrl) {
20776
21009
  const buffer = await downloadVideo(finalResult.videoUrl, key);
20777
- outputPath = resolve3(process.cwd(), output);
20778
- await writeFile3(outputPath, buffer);
21010
+ outputPath = await saveOutput(output, buffer);
20779
21011
  }
20780
21012
  return {
20781
21013
  success: true,
@@ -20801,6 +21033,7 @@ async function executeVideoGenerate(options) {
20801
21033
  }
20802
21034
  const result = await kling.generateVideo(prompt3, {
20803
21035
  prompt: prompt3,
21036
+ model: resolveKlingModel(klingModel),
20804
21037
  referenceImage: klingImage,
20805
21038
  duration,
20806
21039
  aspectRatio: ratio,
@@ -20819,8 +21052,7 @@ async function executeVideoGenerate(options) {
20819
21052
  let outputPath;
20820
21053
  if (output && finalResult.videoUrl) {
20821
21054
  const buffer = await downloadVideo(finalResult.videoUrl, key);
20822
- outputPath = resolve3(process.cwd(), output);
20823
- await writeFile3(outputPath, buffer);
21055
+ outputPath = await saveOutput(output, buffer);
20824
21056
  }
20825
21057
  return {
20826
21058
  success: true,
@@ -20834,12 +21066,7 @@ async function executeVideoGenerate(options) {
20834
21066
  } else if (provider === "veo") {
20835
21067
  const gemini = new GeminiProvider();
20836
21068
  await gemini.initialize({ apiKey: key });
20837
- const veoModelMap = {
20838
- "3.0": "veo-3.0-generate-preview",
20839
- "3.1": "veo-3.1-generate-preview",
20840
- "3.1-fast": "veo-3.1-fast-generate-preview"
20841
- };
20842
- const model = veoModelMap[veoModel] || "veo-3.1-fast-generate-preview";
21069
+ const model = resolveVeoModel(veoModel);
20843
21070
  const veoDuration = duration <= 6 ? 6 : 8;
20844
21071
  const result = await gemini.generateVideo(prompt3, {
20845
21072
  prompt: prompt3,
@@ -20860,8 +21087,7 @@ async function executeVideoGenerate(options) {
20860
21087
  let outputPath;
20861
21088
  if (output && finalResult.videoUrl) {
20862
21089
  const buffer = await downloadVideo(finalResult.videoUrl, key);
20863
- outputPath = resolve3(process.cwd(), output);
20864
- await writeFile3(outputPath, buffer);
21090
+ outputPath = await saveOutput(output, buffer);
20865
21091
  }
20866
21092
  return {
20867
21093
  success: true,
@@ -20876,9 +21102,12 @@ async function executeVideoGenerate(options) {
20876
21102
  await grok.initialize({ apiKey: key });
20877
21103
  const result = await grok.generateVideo(prompt3, {
20878
21104
  prompt: prompt3,
21105
+ model: resolveGrokVideoModel(grokModel),
20879
21106
  referenceImage,
20880
21107
  duration,
20881
- aspectRatio: ratio
21108
+ aspectRatio: ratio,
21109
+ resolution,
21110
+ generateAudio
20882
21111
  });
20883
21112
  if (result.status === "failed")
20884
21113
  return { success: false, error: result.error || "Grok generation failed" };
@@ -20891,8 +21120,7 @@ async function executeVideoGenerate(options) {
20891
21120
  let outputPath;
20892
21121
  if (output && finalResult.videoUrl) {
20893
21122
  const buffer = await downloadVideo(finalResult.videoUrl, key);
20894
- outputPath = resolve3(process.cwd(), output);
20895
- await writeFile3(outputPath, buffer);
21123
+ outputPath = await saveOutput(output, buffer);
20896
21124
  }
20897
21125
  return {
20898
21126
  success: true,
@@ -20909,7 +21137,8 @@ async function executeVideoGenerate(options) {
20909
21137
  const result = await omni.generateVideo(prompt3, {
20910
21138
  prompt: prompt3,
20911
21139
  referenceImage,
20912
- aspectRatio: ratio
21140
+ aspectRatio: ratio,
21141
+ resolution
20913
21142
  });
20914
21143
  if (result.status !== "completed" || !result.videoUrl) {
20915
21144
  return { success: false, error: result.error || "Gemini Omni generation failed" };
@@ -20917,8 +21146,7 @@ async function executeVideoGenerate(options) {
20917
21146
  let outputPath;
20918
21147
  if (output) {
20919
21148
  const buffer = await downloadVideo(result.videoUrl, key);
20920
- outputPath = resolve3(process.cwd(), output);
20921
- await writeFile3(outputPath, buffer);
21149
+ outputPath = await saveOutput(output, buffer);
20922
21150
  }
20923
21151
  return {
20924
21152
  success: true,
@@ -21014,8 +21242,7 @@ async function executeVideoStatus(options) {
21014
21242
  let outputPath;
21015
21243
  if (output && result.videoUrl) {
21016
21244
  const buffer = await downloadVideo(result.videoUrl);
21017
- outputPath = resolve3(process.cwd(), output);
21018
- await writeFile3(outputPath, buffer);
21245
+ outputPath = await saveOutput(output, buffer);
21019
21246
  }
21020
21247
  return {
21021
21248
  success: true,
@@ -21037,8 +21264,7 @@ async function executeVideoStatus(options) {
21037
21264
  let outputPath;
21038
21265
  if (output && result.videoUrl) {
21039
21266
  const buffer = await downloadVideo(result.videoUrl, key);
21040
- outputPath = resolve3(process.cwd(), output);
21041
- await writeFile3(outputPath, buffer);
21267
+ outputPath = await saveOutput(output, buffer);
21042
21268
  }
21043
21269
  return {
21044
21270
  success: true,
@@ -21059,8 +21285,7 @@ async function executeVideoStatus(options) {
21059
21285
  let outputPath;
21060
21286
  if (output && result.videoUrl) {
21061
21287
  const buffer = await downloadVideo(result.videoUrl, key);
21062
- outputPath = resolve3(process.cwd(), output);
21063
- await writeFile3(outputPath, buffer);
21288
+ outputPath = await saveOutput(output, buffer);
21064
21289
  }
21065
21290
  return {
21066
21291
  success: true,
@@ -21082,8 +21307,7 @@ async function executeVideoStatus(options) {
21082
21307
  let outputPath;
21083
21308
  if (output && result.videoUrl) {
21084
21309
  const buffer = await downloadVideo(result.videoUrl, key);
21085
- outputPath = resolve3(process.cwd(), output);
21086
- await writeFile3(outputPath, buffer);
21310
+ outputPath = await saveOutput(output, buffer);
21087
21311
  }
21088
21312
  return {
21089
21313
  success: true,
@@ -21153,8 +21377,7 @@ async function executeVideoExtend(options) {
21153
21377
  let outputPath;
21154
21378
  if (output && finalResult.videoUrl) {
21155
21379
  const buffer = await downloadVideo(finalResult.videoUrl, key);
21156
- outputPath = resolve3(process.cwd(), output);
21157
- await writeFile3(outputPath, buffer);
21380
+ outputPath = await saveOutput(output, buffer);
21158
21381
  }
21159
21382
  return {
21160
21383
  success: true,
@@ -21169,12 +21392,7 @@ async function executeVideoExtend(options) {
21169
21392
  if (!key) return { success: false, error: "GOOGLE_API_KEY required" };
21170
21393
  const gemini = new GeminiProvider();
21171
21394
  await gemini.initialize({ apiKey: key });
21172
- const veoModelMap = {
21173
- "3.0": "veo-3.0-generate-preview",
21174
- "3.1": "veo-3.1-generate-preview",
21175
- "3.1-fast": "veo-3.1-fast-generate-preview"
21176
- };
21177
- const model = veoModelMap[veoModel] || "veo-3.1-generate-preview";
21395
+ const model = resolveVeoModel(veoModel);
21178
21396
  const result = await gemini.extendVideo(videoId, prompt3, {
21179
21397
  duration,
21180
21398
  model
@@ -21189,8 +21407,7 @@ async function executeVideoExtend(options) {
21189
21407
  let outputPath;
21190
21408
  if (output && finalResult.videoUrl) {
21191
21409
  const buffer = await downloadVideo(finalResult.videoUrl, key);
21192
- outputPath = resolve3(process.cwd(), output);
21193
- await writeFile3(outputPath, buffer);
21410
+ outputPath = await saveOutput(output, buffer);
21194
21411
  }
21195
21412
  return {
21196
21413
  success: true,
@@ -21252,8 +21469,8 @@ var init_music_status = __esm({
21252
21469
 
21253
21470
  // src/commands/_shared/build-asset-metadata.ts
21254
21471
  import { existsSync, readFileSync } from "node:fs";
21255
- import { mkdir as mkdir2, writeFile as writeFile4 } from "node:fs/promises";
21256
- import { dirname as dirname2, join } from "node:path";
21472
+ import { mkdir as mkdir3, writeFile as writeFile4 } from "node:fs/promises";
21473
+ import { dirname as dirname3, join } from "node:path";
21257
21474
  function assetMetadataPath(kind, beatId) {
21258
21475
  return `.vibeframe/assets/${kind}-${beatId}.json`;
21259
21476
  }
@@ -21301,7 +21518,7 @@ async function writeAssetMetadata(opts) {
21301
21518
  cachePath: opts.cachePath,
21302
21519
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
21303
21520
  };
21304
- await mkdir2(dirname2(abs), { recursive: true });
21521
+ await mkdir3(dirname3(abs), { recursive: true });
21305
21522
  await writeFile4(abs, JSON.stringify(metadata, null, 2) + "\n", "utf-8");
21306
21523
  }
21307
21524
  var init_build_asset_metadata = __esm({
@@ -22260,7 +22477,7 @@ var init_storyboard_edit = __esm({
22260
22477
 
22261
22478
  // src/commands/_shared/storyboard-source.ts
22262
22479
  import { existsSync as existsSync2 } from "node:fs";
22263
- import { mkdir as mkdir3, readdir, readFile as readFile4, rename, rm, writeFile as writeFile6 } from "node:fs/promises";
22480
+ import { mkdir as mkdir4, readdir, readFile as readFile4, rename, rm, writeFile as writeFile6 } from "node:fs/promises";
22264
22481
  import { basename, join as join3 } from "node:path";
22265
22482
  function sceneFileFrom(dir, filename) {
22266
22483
  const stem = filename.replace(/\.mdx?$/i, "");
@@ -22373,7 +22590,7 @@ ${(0, import_yaml5.stringify)(data, { lineWidth: 0 }).trimEnd()}
22373
22590
  };
22374
22591
  }
22375
22592
  async function writeMigration(projectDir, plan) {
22376
- await mkdir3(join3(projectDir, SCENES_DIRNAME), { recursive: true });
22593
+ await mkdir4(join3(projectDir, SCENES_DIRNAME), { recursive: true });
22377
22594
  for (const file of plan.scenes) {
22378
22595
  await writeFile6(join3(projectDir, file.path), file.contents, "utf-8");
22379
22596
  }
@@ -22395,7 +22612,7 @@ function strayBeatIssues(strayBeatIds) {
22395
22612
  async function upsertScene(projectDir, opts) {
22396
22613
  const beatId = deriveBeatId(opts.beatId);
22397
22614
  const dir = join3(projectDir, SCENES_DIRNAME);
22398
- await mkdir3(dir, { recursive: true });
22615
+ await mkdir4(dir, { recursive: true });
22399
22616
  const cues = {};
22400
22617
  if (opts.duration !== void 0) cues.duration = opts.duration;
22401
22618
  if (opts.narration?.trim()) cues.narration = opts.narration.trim();
@@ -22467,22 +22684,22 @@ var init_storyboard_source = __esm({
22467
22684
  // src/commands/_shared/status-jobs.ts
22468
22685
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
22469
22686
  import { existsSync as existsSync3, statSync } from "node:fs";
22470
- import { copyFile, mkdir as mkdir4, readFile as readFile5, readdir as readdir2, rename as rename2, stat, writeFile as writeFile7 } from "node:fs/promises";
22471
- import { dirname as dirname3, join as join4, parse as parse2, relative, resolve as resolve5 } from "node:path";
22687
+ import { copyFile, mkdir as mkdir5, readFile as readFile5, readdir as readdir2, rename as rename2, stat, writeFile as writeFile7 } from "node:fs/promises";
22688
+ import { dirname as dirname4, join as join4, parse as parse2, relative, resolve as resolve5 } from "node:path";
22472
22689
  function isLocalJob(record) {
22473
22690
  return record.jobType === "build" || record.jobType === "render";
22474
22691
  }
22475
22692
  function findProjectRoot(start = process.cwd()) {
22476
22693
  let dir = resolve5(start);
22477
22694
  try {
22478
- if (existsSync3(dir) && statSync(dir).isFile()) dir = dirname3(dir);
22695
+ if (existsSync3(dir) && statSync(dir).isFile()) dir = dirname4(dir);
22479
22696
  } catch {
22480
22697
  }
22481
22698
  for (; ; ) {
22482
22699
  if (existsSync3(join4(dir, "vibe.config.json")) || existsSync3(join4(dir, "STORYBOARD.md"))) {
22483
22700
  return dir;
22484
22701
  }
22485
- const parent = dirname3(dir);
22702
+ const parent = dirname4(dir);
22486
22703
  if (parent === dir) return resolve5(start);
22487
22704
  dir = parent;
22488
22705
  }
@@ -22549,7 +22766,7 @@ async function createAndWriteJobRecord(opts) {
22549
22766
  }
22550
22767
  async function writeJobRecord(record) {
22551
22768
  const dir = jobsDir(record.projectDir);
22552
- await mkdir4(dir, { recursive: true });
22769
+ await mkdir5(dir, { recursive: true });
22553
22770
  const path2 = jobRecordPath(record.id, record.projectDir);
22554
22771
  const tmp = `${path2}.${randomUUID2()}.tmp`;
22555
22772
  await writeFile7(tmp, JSON.stringify(stripUndefined2(record), null, 2) + "\n", "utf-8");
@@ -22597,7 +22814,7 @@ async function refreshJobRecord(record, opts = {}) {
22597
22814
  try {
22598
22815
  if (record.jobType === "generate-video") {
22599
22816
  const output = resolveRefreshOutput(record, opts);
22600
- if (output) await mkdir4(dirname3(output), { recursive: true });
22817
+ if (output) await mkdir5(dirname4(output), { recursive: true });
22601
22818
  const result = await executeVideoStatus({
22602
22819
  taskId: record.providerTaskId,
22603
22820
  provider: record.provider,
@@ -22880,14 +23097,14 @@ async function downloadUrl(url, outputPath) {
22880
23097
  const response = await fetch(url);
22881
23098
  if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`);
22882
23099
  const buffer = Buffer.from(await response.arrayBuffer());
22883
- await mkdir4(dirname3(outputPath), { recursive: true });
23100
+ await mkdir5(dirname4(outputPath), { recursive: true });
22884
23101
  await writeFile7(outputPath, buffer);
22885
23102
  return outputPath;
22886
23103
  }
22887
23104
  async function maybeCacheOutput(record) {
22888
23105
  if (record.status !== "completed" || !record.outputPath || !record.cachePath) return;
22889
23106
  try {
22890
- await mkdir4(dirname3(record.cachePath), { recursive: true });
23107
+ await mkdir5(dirname4(record.cachePath), { recursive: true });
22891
23108
  await copyFile(record.outputPath, record.cachePath);
22892
23109
  } catch {
22893
23110
  }
@@ -23471,7 +23688,7 @@ var init_project_config = __esm({
23471
23688
  });
23472
23689
 
23473
23690
  // src/commands/_shared/scene-project.ts
23474
- import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile8, access as access3 } from "node:fs/promises";
23691
+ import { mkdir as mkdir6, readFile as readFile7, writeFile as writeFile8, access as access3 } from "node:fs/promises";
23475
23692
  import { resolve as resolve7, basename as basename2 } from "node:path";
23476
23693
  function isSceneKind(value) {
23477
23694
  return VALID_SCENE_KINDS.includes(value);
@@ -24083,11 +24300,11 @@ async function scaffoldSceneProject(opts) {
24083
24300
  const now = opts.now ?? /* @__PURE__ */ new Date();
24084
24301
  const profile = opts.profile ?? "full";
24085
24302
  const kind = opts.kind ?? DEFAULT_SCENE_KIND;
24086
- await mkdir5(dir, { recursive: true });
24303
+ await mkdir6(dir, { recursive: true });
24087
24304
  if (profile === "full") {
24088
- await mkdir5(resolve7(dir, "compositions"), { recursive: true });
24089
- await mkdir5(resolve7(dir, "assets"), { recursive: true });
24090
- await mkdir5(resolve7(dir, "renders"), { recursive: true });
24305
+ await mkdir6(resolve7(dir, "compositions"), { recursive: true });
24306
+ await mkdir6(resolve7(dir, "assets"), { recursive: true });
24307
+ await mkdir6(resolve7(dir, "renders"), { recursive: true });
24091
24308
  }
24092
24309
  const created = [];
24093
24310
  const skipped2 = [];
@@ -31642,7 +31859,7 @@ var init_scene_audio_scan = __esm({
31642
31859
 
31643
31860
  // src/commands/_shared/scene-audio-mux.ts
31644
31861
  import { rename as rename3, unlink } from "node:fs/promises";
31645
- import { resolve as resolve17, dirname as dirname5, extname as extname2, basename as basename3 } from "node:path";
31862
+ import { resolve as resolve17, dirname as dirname6, extname as extname2, basename as basename3 } from "node:path";
31646
31863
  function buildAudioMuxFilter(audios) {
31647
31864
  if (audios.length === 0) return null;
31648
31865
  const labels = [];
@@ -31703,7 +31920,7 @@ async function muxAudioIntoVideo(opts) {
31703
31920
  }
31704
31921
  const ext = extname2(opts.videoPath) || `.${opts.format}`;
31705
31922
  const tmpPath = resolve17(
31706
- dirname5(opts.videoPath),
31923
+ dirname6(opts.videoPath),
31707
31924
  `.${basename3(opts.videoPath, ext)}.muxing${ext}`
31708
31925
  );
31709
31926
  const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
@@ -31851,8 +32068,8 @@ __export(scene_render_exports, {
31851
32068
  });
31852
32069
  import { execFile as execFile2 } from "node:child_process";
31853
32070
  import { existsSync as existsSync15 } from "node:fs";
31854
- import { mkdir as mkdir8, readFile as readFile16, stat as stat3, writeFile as writeFile14 } from "node:fs/promises";
31855
- import { resolve as resolve19, relative as relative6, dirname as dirname6, basename as basename4, join as join13, isAbsolute as isAbsolute2 } from "node:path";
32071
+ import { mkdir as mkdir9, readFile as readFile16, stat as stat3, writeFile as writeFile14 } from "node:fs/promises";
32072
+ import { resolve as resolve19, relative as relative6, dirname as dirname7, basename as basename4, join as join13, isAbsolute as isAbsolute2 } from "node:path";
31856
32073
  import { promisify as promisify2 } from "node:util";
31857
32074
  import {
31858
32075
  createRenderJob,
@@ -31905,7 +32122,7 @@ function buildMediaOpenCommand(action, filePath, platform = process.platform) {
31905
32122
  display: ["cmd", ...args].map(quoteCliArg).join(" ")
31906
32123
  };
31907
32124
  }
31908
- const target = action === "reveal" ? dirname6(filePath) : filePath;
32125
+ const target = action === "reveal" ? dirname7(filePath) : filePath;
31909
32126
  return {
31910
32127
  command: "xdg-open",
31911
32128
  args: [target],
@@ -32001,7 +32218,7 @@ async function executeSceneRender(opts = {}) {
32001
32218
  }
32002
32219
  const projectName = projectConfig.config.name;
32003
32220
  const outputPath = opts.output ? resolve19(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
32004
- await mkdir8(dirname6(outputPath), { recursive: true });
32221
+ await mkdir9(dirname7(outputPath), { recursive: true });
32005
32222
  const config2 = buildRenderConfig({
32006
32223
  fps: opts.fps,
32007
32224
  quality: opts.quality,
@@ -32197,7 +32414,7 @@ async function prepareBeatRenderRoot(opts) {
32197
32414
  durationSec,
32198
32415
  narrationPath
32199
32416
  });
32200
- await mkdir8(dirname6(join13(opts.projectDir, root)), { recursive: true });
32417
+ await mkdir9(dirname7(join13(opts.projectDir, root)), { recursive: true });
32201
32418
  await writeFile14(join13(opts.projectDir, root), html, "utf-8");
32202
32419
  return {
32203
32420
  success: true,
@@ -32481,7 +32698,7 @@ var init_design_parse = __esm({
32481
32698
 
32482
32699
  // src/commands/_shared/compose-aivideo.ts
32483
32700
  import { existsSync as existsSync16 } from "node:fs";
32484
- import { mkdir as mkdir9, readFile as readFile17, writeFile as writeFile15 } from "node:fs/promises";
32701
+ import { mkdir as mkdir10, readFile as readFile17, writeFile as writeFile15 } from "node:fs/promises";
32485
32702
  import { join as join14, resolve as resolve20 } from "node:path";
32486
32703
  function headingDescriptor(heading) {
32487
32704
  const stripped = heading.replace(/^\s*beat\s+/i, "");
@@ -32644,7 +32861,7 @@ async function composeAiVideo(opts) {
32644
32861
  }
32645
32862
  clipRels.push(rel);
32646
32863
  }
32647
- await mkdir9(join14(projectDir, "assets"), { recursive: true });
32864
+ await mkdir10(join14(projectDir, "assets"), { recursive: true });
32648
32865
  const bgRel = "assets/bg-full.mp4";
32649
32866
  const concatArgs = buildBgConcatArgs(
32650
32867
  clipRels.map((r) => join14(projectDir, r)),
@@ -32652,7 +32869,7 @@ async function composeAiVideo(opts) {
32652
32869
  );
32653
32870
  await execSafe("ffmpeg", concatArgs, { timeout: 6e5 });
32654
32871
  const totalDurationSec = await probeDurationSec(join14(projectDir, bgRel));
32655
- await mkdir9(join14(projectDir, "compositions"), { recursive: true });
32872
+ await mkdir10(join14(projectDir, "compositions"), { recursive: true });
32656
32873
  const sceneFiles = [];
32657
32874
  for (let i = 0; i < beats.length; i++) {
32658
32875
  const beat = beats[i];
@@ -32801,8 +33018,8 @@ var init_compose_aivideo = __esm({
32801
33018
 
32802
33019
  // src/commands/_shared/footage-assemble.ts
32803
33020
  import { existsSync as existsSync17 } from "node:fs";
32804
- import { mkdir as mkdir10, writeFile as writeFile16 } from "node:fs/promises";
32805
- import { dirname as dirname7, isAbsolute as isAbsolute3, join as join15, resolve as resolve21 } from "node:path";
33021
+ import { mkdir as mkdir11, writeFile as writeFile16 } from "node:fs/promises";
33022
+ import { dirname as dirname8, isAbsolute as isAbsolute3, join as join15, resolve as resolve21 } from "node:path";
32806
33023
  function planFootageTimeline(inputs, opts = {}) {
32807
33024
  if (inputs.length === 0) {
32808
33025
  throw new Error("No clips to assemble.");
@@ -33131,7 +33348,7 @@ async function executeFootageAssemble(opts) {
33131
33348
  if (opts.dryRun) {
33132
33349
  return { success: true, dryRun: true, outputPath, report };
33133
33350
  }
33134
- await mkdir10(dirname7(outputPath), { recursive: true });
33351
+ await mkdir11(dirname8(outputPath), { recursive: true });
33135
33352
  const args = buildFootageAssembleArgs({
33136
33353
  sources: sourceRels.map((s) => ({ path: join15(projectDir, s.rel), kind: s.kind })),
33137
33354
  plan,
@@ -33492,9 +33709,40 @@ var init_composer_resolve = __esm({
33492
33709
  }
33493
33710
  });
33494
33711
 
33712
+ // src/commands/_shared/build-video-providers.ts
33713
+ function isBuildVideoProvider(value) {
33714
+ return BUILD_VIDEO_PROVIDERS.includes(value);
33715
+ }
33716
+ function resolveBuildVideoProvider(value) {
33717
+ const provider = String(value ?? "seedance").toLowerCase();
33718
+ if (provider === "fal") return "seedance";
33719
+ return isBuildVideoProvider(provider) ? provider : "seedance";
33720
+ }
33721
+ function videoProviderConfigKey(provider) {
33722
+ switch (provider) {
33723
+ case "seedance":
33724
+ case "fal":
33725
+ return "fal";
33726
+ case "grok":
33727
+ return "xai";
33728
+ case "veo":
33729
+ case "omni":
33730
+ return "google";
33731
+ default:
33732
+ return provider;
33733
+ }
33734
+ }
33735
+ var BUILD_VIDEO_PROVIDERS;
33736
+ var init_build_video_providers = __esm({
33737
+ "src/commands/_shared/build-video-providers.ts"() {
33738
+ "use strict";
33739
+ BUILD_VIDEO_PROVIDERS = ["seedance", "grok", "kling", "runway", "omni", "veo"];
33740
+ }
33741
+ });
33742
+
33495
33743
  // src/commands/_shared/build-plan.ts
33496
33744
  import { existsSync as existsSync19 } from "node:fs";
33497
- import { dirname as dirname8, join as join16, resolve as resolve23 } from "node:path";
33745
+ import { dirname as dirname9, join as join16, resolve as resolve23 } from "node:path";
33498
33746
  function backdropCostUsd(quality) {
33499
33747
  return quality === "hd" ? 0.2 : 0.05;
33500
33748
  }
@@ -34166,12 +34414,7 @@ function normalizeImageProvider(value) {
34166
34414
  return provider || "openai";
34167
34415
  }
34168
34416
  function normalizeVideoProvider(value) {
34169
- const provider = String(value ?? "seedance").toLowerCase();
34170
- if (provider === "fal") return "seedance";
34171
- if (provider === "seedance" || provider === "grok" || provider === "kling" || provider === "runway" || provider === "veo") {
34172
- return provider;
34173
- }
34174
- return "seedance";
34417
+ return resolveBuildVideoProvider(value);
34175
34418
  }
34176
34419
  function normalizeMusicProvider(value) {
34177
34420
  const provider = String(value ?? "elevenlabs").toLowerCase();
@@ -34183,7 +34426,7 @@ function imageConfigKey(provider) {
34183
34426
  return "openai";
34184
34427
  }
34185
34428
  function videoConfigKey(provider) {
34186
- return provider === "seedance" ? "fal" : provider === "grok" ? "xai" : provider === "veo" ? "google" : provider;
34429
+ return videoProviderConfigKey(provider);
34187
34430
  }
34188
34431
  function composerConfigKey(provider) {
34189
34432
  return provider === "claude" ? "anthropic" : provider === "gemini" ? "google" : "openai";
@@ -34201,12 +34444,12 @@ function loadPlanEnv(projectDir) {
34201
34444
  (0, import_dotenv2.config)({ path: join16(projectDir, ".env"), quiet: true });
34202
34445
  (0, import_dotenv2.config)({ path: resolve23(process.cwd(), ".env"), quiet: true });
34203
34446
  let dir = process.cwd();
34204
- while (dir !== dirname8(dir)) {
34447
+ while (dir !== dirname9(dir)) {
34205
34448
  if (existsSync19(join16(dir, "pnpm-workspace.yaml"))) {
34206
34449
  (0, import_dotenv2.config)({ path: join16(dir, ".env"), quiet: true });
34207
34450
  return;
34208
34451
  }
34209
- dir = dirname8(dir);
34452
+ dir = dirname9(dir);
34210
34453
  }
34211
34454
  }
34212
34455
  var import_dotenv2, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, OPENAI_NARRATION_COST_USD, TRANSCRIPT_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
@@ -34227,6 +34470,7 @@ var init_build_plan = __esm({
34227
34470
  init_project_config();
34228
34471
  init_scene_project();
34229
34472
  init_storyboard_edit();
34473
+ init_build_video_providers();
34230
34474
  VIDEO_COST_USD = 5;
34231
34475
  MUSIC_COST_USD = 0.5;
34232
34476
  ELEVENLABS_NARRATION_COST_USD = 0.05;
@@ -34405,7 +34649,7 @@ function registerMusicCommand(parent) {
34405
34649
  console.log();
34406
34650
  console.log(`Saved to: ${source_default.bold(outputPath)}`);
34407
34651
  console.log(`Duration: ${duration}s`);
34408
- console.log(`Provider: ElevenLabs (music_v1)`);
34652
+ console.log(`Provider: ElevenLabs (${ELEVENLABS_MUSIC_MODEL})`);
34409
34653
  if (options.instrumental) console.log(`Mode: Instrumental`);
34410
34654
  console.log();
34411
34655
  } else {
@@ -34544,6 +34788,48 @@ var init_scene_build_mode = __esm({
34544
34788
  }
34545
34789
  });
34546
34790
 
34791
+ // src/utils/image-file.ts
34792
+ import { rm as rm2, writeFile as writeFile18 } from "node:fs/promises";
34793
+ import { extname as extname4 } from "node:path";
34794
+ function detectImageFormat(buffer) {
34795
+ if (buffer.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
34796
+ return "png";
34797
+ }
34798
+ if (buffer[0] === 255 && buffer[1] === 216 && buffer[2] === 255) return "jpeg";
34799
+ if (buffer.toString("ascii", 0, 4) === "RIFF" && buffer.toString("ascii", 8, 12) === "WEBP") {
34800
+ return "webp";
34801
+ }
34802
+ return void 0;
34803
+ }
34804
+ async function writeImageFile(path2, buffer) {
34805
+ const wanted = EXTENSION_FORMATS[extname4(path2).toLowerCase()];
34806
+ const actual = detectImageFormat(buffer);
34807
+ if (!wanted || !actual || wanted === actual) {
34808
+ await writeFile18(path2, buffer);
34809
+ return;
34810
+ }
34811
+ const source = `${path2}.source.${actual === "jpeg" ? "jpg" : actual}`;
34812
+ await writeFile18(source, buffer);
34813
+ try {
34814
+ await execSafe("ffmpeg", ["-y", "-loglevel", "error", "-i", source, "-frames:v", "1", path2]);
34815
+ } finally {
34816
+ await rm2(source, { force: true });
34817
+ }
34818
+ }
34819
+ var EXTENSION_FORMATS;
34820
+ var init_image_file = __esm({
34821
+ "src/utils/image-file.ts"() {
34822
+ "use strict";
34823
+ init_exec_safe();
34824
+ EXTENSION_FORMATS = {
34825
+ ".png": "png",
34826
+ ".jpg": "jpeg",
34827
+ ".jpeg": "jpeg",
34828
+ ".webp": "webp"
34829
+ };
34830
+ }
34831
+ });
34832
+
34547
34833
  // src/commands/_shared/scene-build.ts
34548
34834
  var scene_build_exports = {};
34549
34835
  __export(scene_build_exports, {
@@ -34552,8 +34838,8 @@ __export(scene_build_exports, {
34552
34838
  resolveSceneBuildMode: () => resolveSceneBuildMode
34553
34839
  });
34554
34840
  import { existsSync as existsSync21, readFileSync as readFileSync2 } from "node:fs";
34555
- import { copyFile as copyFile3, mkdir as mkdir11, readFile as readFile18, rm as rm2, writeFile as writeFile18 } from "node:fs/promises";
34556
- import { dirname as dirname9, join as join17, resolve as resolve26 } from "node:path";
34841
+ import { copyFile as copyFile3, mkdir as mkdir12, readFile as readFile18, rm as rm3, writeFile as writeFile19 } from "node:fs/promises";
34842
+ import { dirname as dirname10, join as join17, resolve as resolve26 } from "node:path";
34557
34843
  function readProjectKindSync(projectDir) {
34558
34844
  try {
34559
34845
  const raw = readFileSync2(join17(projectDir, "vibe.config.json"), "utf-8");
@@ -35406,7 +35692,7 @@ async function dispatchNarration(beat, ctx) {
35406
35692
  };
35407
35693
  }
35408
35694
  if (existsSync21(cacheAbs) && !ctx.force) {
35409
- await mkdir11(dirname9(abs), { recursive: true });
35695
+ await mkdir12(dirname10(abs), { recursive: true });
35410
35696
  await copyFile3(cacheAbs, abs);
35411
35697
  await writeAssetMetadata({
35412
35698
  projectDir: ctx.projectDir,
@@ -35437,14 +35723,14 @@ async function dispatchNarration(beat, ctx) {
35437
35723
  ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
35438
35724
  return { status: "failed", error };
35439
35725
  }
35440
- await mkdir11(dirname9(abs), { recursive: true });
35441
- await writeFile18(abs, result.audioBuffer);
35726
+ await mkdir12(dirname10(abs), { recursive: true });
35727
+ await writeFile19(abs, result.audioBuffer);
35442
35728
  for (const otherExt of ["mp3", "wav"]) {
35443
35729
  const sibling = join17(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
35444
- if (sibling !== abs && existsSync21(sibling)) await rm2(sibling, { force: true });
35730
+ if (sibling !== abs && existsSync21(sibling)) await rm3(sibling, { force: true });
35445
35731
  }
35446
- await mkdir11(dirname9(cacheAbs), { recursive: true });
35447
- await writeFile18(cacheAbs, result.audioBuffer);
35732
+ await mkdir12(dirname10(cacheAbs), { recursive: true });
35733
+ await writeFile19(cacheAbs, result.audioBuffer);
35448
35734
  await writeAssetMetadata({
35449
35735
  projectDir: ctx.projectDir,
35450
35736
  kind: "narration",
@@ -35500,8 +35786,8 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
35500
35786
  return "skipped";
35501
35787
  }
35502
35788
  try {
35503
- await mkdir11(dirname9(abs), { recursive: true });
35504
- await writeFile18(abs, JSON.stringify(words, null, 2), "utf-8");
35789
+ await mkdir12(dirname10(abs), { recursive: true });
35790
+ await writeFile19(abs, JSON.stringify(words, null, 2), "utf-8");
35505
35791
  } catch (err) {
35506
35792
  ctx.onProgress({
35507
35793
  type: "transcript-skipped",
@@ -35589,7 +35875,7 @@ async function buildCharacters(characters, ctx) {
35589
35875
  }
35590
35876
  const cacheAbs = join17(ctx.projectDir, cache.path);
35591
35877
  if (existsSync21(cacheAbs) && !ctx.force) {
35592
- await mkdir11(dirname9(abs), { recursive: true });
35878
+ await mkdir12(dirname10(abs), { recursive: true });
35593
35879
  await copyFile3(cacheAbs, abs);
35594
35880
  await writeAssetMetadata({
35595
35881
  projectDir: ctx.projectDir,
@@ -35622,10 +35908,10 @@ async function buildCharacters(characters, ctx) {
35622
35908
  ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
35623
35909
  continue;
35624
35910
  }
35625
- await mkdir11(dirname9(abs), { recursive: true });
35626
- await writeFile18(abs, generated.buffer);
35627
- await mkdir11(dirname9(cacheAbs), { recursive: true });
35628
- await writeFile18(cacheAbs, generated.buffer);
35911
+ await mkdir12(dirname10(abs), { recursive: true });
35912
+ await writeImageFile(abs, generated.buffer);
35913
+ await mkdir12(dirname10(cacheAbs), { recursive: true });
35914
+ await writeImageFile(cacheAbs, generated.buffer);
35629
35915
  await writeAssetMetadata({
35630
35916
  projectDir: ctx.projectDir,
35631
35917
  kind: "character",
@@ -35694,7 +35980,7 @@ async function dispatchBackdrop(beat, ctx) {
35694
35980
  }
35695
35981
  const cacheAbs = join17(ctx.projectDir, cache.path);
35696
35982
  if (existsSync21(cacheAbs) && !ctx.force) {
35697
- await mkdir11(dirname9(abs), { recursive: true });
35983
+ await mkdir12(dirname10(abs), { recursive: true });
35698
35984
  await copyFile3(cacheAbs, abs);
35699
35985
  await writeAssetMetadata({
35700
35986
  projectDir: ctx.projectDir,
@@ -35724,11 +36010,11 @@ async function dispatchBackdrop(beat, ctx) {
35724
36010
  ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
35725
36011
  return { status: "failed", error };
35726
36012
  }
35727
- await mkdir11(dirname9(abs), { recursive: true });
36013
+ await mkdir12(dirname10(abs), { recursive: true });
35728
36014
  const buffer = generated.buffer;
35729
- await writeFile18(abs, buffer);
35730
- await mkdir11(dirname9(cacheAbs), { recursive: true });
35731
- await writeFile18(cacheAbs, buffer);
36015
+ await writeImageFile(abs, buffer);
36016
+ await mkdir12(dirname10(cacheAbs), { recursive: true });
36017
+ await writeImageFile(cacheAbs, buffer);
35732
36018
  await writeAssetMetadata({
35733
36019
  projectDir: ctx.projectDir,
35734
36020
  kind: "backdrop",
@@ -35772,7 +36058,7 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
35772
36058
  const result2 = await provider2.generateImage(prompt3, {
35773
36059
  // CLI input is free-form; an alias the provider doesn't know fails the
35774
36060
  // API call and surfaces as a beat-scoped backdrop/keyframe error.
35775
- model: ctx.imageModel ?? "gpt-image-2",
36061
+ model: ctx.imageModel ?? OPENAI_IMAGE_DEFAULT_MODEL,
35776
36062
  size: ctx.imageSize,
35777
36063
  quality: ctx.imageQuality
35778
36064
  });
@@ -35857,7 +36143,7 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35857
36143
  }
35858
36144
  const cacheAbs = join17(ctx.projectDir, cache.path);
35859
36145
  if (existsSync21(cacheAbs) && !ctx.force) {
35860
- await mkdir11(dirname9(abs), { recursive: true });
36146
+ await mkdir12(dirname10(abs), { recursive: true });
35861
36147
  await copyFile3(cacheAbs, abs);
35862
36148
  await writeAssetMetadata({
35863
36149
  projectDir: ctx.projectDir,
@@ -35887,10 +36173,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
35887
36173
  const identityLock = "Keep the EXACT same person and face as the reference image(s): identical facial features (eyes, nose shape, jawline), skin tone and complexion, hairstyle, hair color, and wardrobe. Do not restyle or change the face. Change only the scene, pose, framing, and lighting. ";
35888
36174
  const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
35889
36175
  if (!generated.success) return { status: "failed", error: generated.error };
35890
- await mkdir11(dirname9(abs), { recursive: true });
35891
- await writeFile18(abs, generated.buffer);
35892
- await mkdir11(dirname9(cacheAbs), { recursive: true });
35893
- await writeFile18(cacheAbs, generated.buffer);
36176
+ await mkdir12(dirname10(abs), { recursive: true });
36177
+ await writeImageFile(abs, generated.buffer);
36178
+ await mkdir12(dirname10(cacheAbs), { recursive: true });
36179
+ await writeImageFile(cacheAbs, generated.buffer);
35894
36180
  await writeAssetMetadata({
35895
36181
  projectDir: ctx.projectDir,
35896
36182
  kind: "keyframe",
@@ -35986,7 +36272,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
35986
36272
  }
35987
36273
  const cacheAbs = join17(ctx.projectDir, cache.path);
35988
36274
  if (existsSync21(cacheAbs) && !ctx.force) {
35989
- await mkdir11(dirname9(abs), { recursive: true });
36275
+ await mkdir12(dirname10(abs), { recursive: true });
35990
36276
  await copyFile3(cacheAbs, abs);
35991
36277
  await writeAssetMetadata({
35992
36278
  projectDir: ctx.projectDir,
@@ -36063,7 +36349,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
36063
36349
  }
36064
36350
  }
36065
36351
  if (result.status === "completed" && existsSync21(abs)) {
36066
- await mkdir11(dirname9(cacheAbs), { recursive: true });
36352
+ await mkdir12(dirname10(cacheAbs), { recursive: true });
36067
36353
  await copyFile3(abs, cacheAbs);
36068
36354
  await writeAssetMetadata({
36069
36355
  projectDir: ctx.projectDir,
@@ -36165,7 +36451,7 @@ async function dispatchMusic(beat, ctx) {
36165
36451
  }
36166
36452
  const cacheAbs = join17(ctx.projectDir, cache.path);
36167
36453
  if (existsSync21(cacheAbs) && !ctx.force) {
36168
- await mkdir11(dirname9(abs), { recursive: true });
36454
+ await mkdir12(dirname10(abs), { recursive: true });
36169
36455
  await copyFile3(cacheAbs, abs);
36170
36456
  await writeAssetMetadata({
36171
36457
  projectDir: ctx.projectDir,
@@ -36243,7 +36529,7 @@ async function dispatchMusic(beat, ctx) {
36243
36529
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
36244
36530
  return { status: "failed", error };
36245
36531
  }
36246
- await mkdir11(dirname9(cacheAbs), { recursive: true });
36532
+ await mkdir12(dirname10(cacheAbs), { recursive: true });
36247
36533
  await copyFile3(abs, cacheAbs);
36248
36534
  await writeAssetMetadata({
36249
36535
  projectDir: ctx.projectDir,
@@ -36277,12 +36563,12 @@ function loadSceneBuildEnv(projectDir) {
36277
36563
  (0, import_dotenv3.config)({ path: join17(projectDir, ".env"), quiet: true });
36278
36564
  (0, import_dotenv3.config)({ path: resolve26(process.cwd(), ".env"), quiet: true });
36279
36565
  let dir = process.cwd();
36280
- while (dir !== dirname9(dir)) {
36566
+ while (dir !== dirname10(dir)) {
36281
36567
  if (existsSync21(join17(dir, "pnpm-workspace.yaml"))) {
36282
36568
  (0, import_dotenv3.config)({ path: join17(dir, ".env"), quiet: true });
36283
36569
  return;
36284
36570
  }
36285
- dir = dirname9(dir);
36571
+ dir = dirname10(dir);
36286
36572
  }
36287
36573
  }
36288
36574
  async function skipped(kind, beatId, reason, ctx) {
@@ -36507,14 +36793,6 @@ async function safeAudioDuration(absPath) {
36507
36793
  return void 0;
36508
36794
  }
36509
36795
  }
36510
- function resolveBuildVideoProvider(value) {
36511
- const provider = String(value ?? "seedance").toLowerCase();
36512
- if (provider === "fal") return "seedance";
36513
- if (provider === "seedance" || provider === "grok" || provider === "kling" || provider === "runway" || provider === "veo") {
36514
- return provider;
36515
- }
36516
- return "seedance";
36517
- }
36518
36796
  function resolveBuildMusicProvider(value) {
36519
36797
  const provider = String(value ?? "elevenlabs").toLowerCase();
36520
36798
  return provider === "replicate" ? "replicate" : "elevenlabs";
@@ -36526,8 +36804,7 @@ function stringOrUndefined4(value) {
36526
36804
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
36527
36805
  }
36528
36806
  async function apiKeyForVideoProvider(provider, projectDir) {
36529
- const providerKey = provider === "seedance" ? "fal" : provider === "grok" ? "xai" : provider === "veo" ? "google" : provider;
36530
- return getApiKeyFromConfig(providerKey, { cwd: projectDir });
36807
+ return getApiKeyFromConfig(videoProviderConfigKey(provider), { cwd: projectDir });
36531
36808
  }
36532
36809
  async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
36533
36810
  const reportPath = join17(projectDir, "build-report.json");
@@ -36550,7 +36827,7 @@ async function finalizeBuildResult(projectDir, startedAt, result, reportResult =
36550
36827
  const withMeta = decorate(result);
36551
36828
  const reportMeta = reportResult === result ? withMeta : decorate(reportResult);
36552
36829
  try {
36553
- await writeFile18(
36830
+ await writeFile19(
36554
36831
  reportPath,
36555
36832
  JSON.stringify(toBuildReport(projectDir, reportMeta), null, 2) + "\n",
36556
36833
  "utf-8"
@@ -36803,6 +37080,8 @@ var init_scene_build = __esm({
36803
37080
  init_scene_repair();
36804
37081
  init_tts_resolve();
36805
37082
  init_scene_build_mode();
37083
+ init_image_file();
37084
+ init_build_video_providers();
36806
37085
  init_scene_build_mode();
36807
37086
  BEAT_PRIMITIVES_CONCURRENCY = 4;
36808
37087
  }
@@ -36915,8 +37194,8 @@ __export(ai_audio_exports, {
36915
37194
  executeTranscribe: () => executeTranscribe,
36916
37195
  executeVoiceClone: () => executeVoiceClone
36917
37196
  });
36918
- import { resolve as resolve28, dirname as dirname10, basename as basename5, extname as extname4 } from "node:path";
36919
- import { readFile as readFile19, writeFile as writeFile19 } from "node:fs/promises";
37197
+ import { resolve as resolve28, dirname as dirname11, basename as basename5, extname as extname5 } from "node:path";
37198
+ import { readFile as readFile19, writeFile as writeFile20 } from "node:fs/promises";
36920
37199
  import { existsSync as existsSync22 } from "node:fs";
36921
37200
  async function executeTranscribe(options) {
36922
37201
  const { audioPath, language, output, format, apiKey } = options;
@@ -36938,7 +37217,7 @@ async function executeTranscribe(options) {
36938
37217
  outputPath = resolve28(process.cwd(), output);
36939
37218
  const fmt = detectFormat(output, format);
36940
37219
  const content = formatTranscript(result, fmt);
36941
- await writeFile19(outputPath, content, "utf-8");
37220
+ await writeFile20(outputPath, content, "utf-8");
36942
37221
  }
36943
37222
  return {
36944
37223
  success: true,
@@ -36966,7 +37245,7 @@ async function executeIsolate(options) {
36966
37245
  return { success: false, error: result.error || "Audio isolation failed" };
36967
37246
  }
36968
37247
  const outputPath = resolve28(process.cwd(), output);
36969
- await writeFile19(outputPath, result.audioBuffer);
37248
+ await writeFile20(outputPath, result.audioBuffer);
36970
37249
  return { success: true, outputPath };
36971
37250
  } catch (error) {
36972
37251
  return { success: false, error: `Audio isolation failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -37012,11 +37291,11 @@ async function executeDub(options) {
37012
37291
  if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
37013
37292
  if (!anthropicKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude translation" };
37014
37293
  if (!analyzeOnly && !elevenlabsKey) return { success: false, error: "ELEVENLABS_API_KEY required for TTS" };
37015
- const ext = extname4(absPath).toLowerCase();
37294
+ const ext = extname5(absPath).toLowerCase();
37016
37295
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
37017
37296
  let audioPath = absPath;
37018
37297
  if (isVideo) {
37019
- const tempAudioPath = resolve28(dirname10(absPath), `temp-audio-${Date.now()}.mp3`);
37298
+ const tempAudioPath = resolve28(dirname11(absPath), `temp-audio-${Date.now()}.mp3`);
37020
37299
  execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
37021
37300
  audioPath = tempAudioPath;
37022
37301
  }
@@ -37077,7 +37356,7 @@ ${segmentTexts}`,
37077
37356
  translated: translatedSegments[i]?.text || s.text
37078
37357
  }))
37079
37358
  };
37080
- await writeFile19(timingPath, JSON.stringify(timingData, null, 2));
37359
+ await writeFile20(timingPath, JSON.stringify(timingData, null, 2));
37081
37360
  }
37082
37361
  return {
37083
37362
  success: true,
@@ -37097,10 +37376,10 @@ ${segmentTexts}`,
37097
37376
  }
37098
37377
  }
37099
37378
  const combinedBuffer = Buffer.concat(dubbedBuffers);
37100
- const outputExt = isVideo ? ".mp3" : extname4(absPath);
37101
- const defaultOutputPath2 = resolve28(dirname10(absPath), `${basename5(absPath, extname4(absPath))}-${language}${outputExt}`);
37379
+ const outputExt = isVideo ? ".mp3" : extname5(absPath);
37380
+ const defaultOutputPath2 = resolve28(dirname11(absPath), `${basename5(absPath, extname5(absPath))}-${language}${outputExt}`);
37102
37381
  const finalOutputPath = resolve28(process.cwd(), output || defaultOutputPath2);
37103
- await writeFile19(finalOutputPath, combinedBuffer);
37382
+ await writeFile20(finalOutputPath, combinedBuffer);
37104
37383
  if (isVideo && audioPath !== absPath) {
37105
37384
  try {
37106
37385
  const { unlink: unlink3 } = await import("node:fs/promises");
@@ -37135,7 +37414,7 @@ async function executeDuck(options) {
37135
37414
  const absVoicePath = resolve28(process.cwd(), voicePath);
37136
37415
  if (!existsSync22(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
37137
37416
  if (!existsSync22(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
37138
- const defaultOutput = resolve28(dirname10(absMusicPath), `${basename5(absMusicPath, extname4(absMusicPath))}-ducked${extname4(absMusicPath)}`);
37417
+ const defaultOutput = resolve28(dirname11(absMusicPath), `${basename5(absMusicPath, extname5(absMusicPath))}-ducked${extname5(absMusicPath)}`);
37139
37418
  const outputPath = resolve28(process.cwd(), output || defaultOutput);
37140
37419
  const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
37141
37420
  await execSafe("ffmpeg", [
@@ -37400,7 +37679,7 @@ var init_silence_cut = __esm({
37400
37679
 
37401
37680
  // src/commands/_shared/edit/jump-cut.ts
37402
37681
  import { existsSync as existsSync24 } from "node:fs";
37403
- import { readFile as readFile21, writeFile as writeFile20, mkdir as mkdir12 } from "node:fs/promises";
37682
+ import { readFile as readFile21, writeFile as writeFile21, mkdir as mkdir13 } from "node:fs/promises";
37404
37683
  import { join as join18 } from "node:path";
37405
37684
  async function transcribeWithWords(audioPath, apiKey, language) {
37406
37685
  const audioBuffer = await readFile21(audioPath);
@@ -37480,7 +37759,7 @@ async function executeJumpCut(options) {
37480
37759
  }
37481
37760
  try {
37482
37761
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
37483
- await mkdir12(tmpDir, { recursive: true });
37762
+ await mkdir13(tmpDir, { recursive: true });
37484
37763
  const audioPath = join18(tmpDir, "audio.wav");
37485
37764
  try {
37486
37765
  await execSafe(
@@ -37559,7 +37838,7 @@ async function executeJumpCut(options) {
37559
37838
  }
37560
37839
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
37561
37840
  const listPath = join18(tmpDir, "concat.txt");
37562
- await writeFile20(listPath, concatList);
37841
+ await writeFile21(listPath, concatList);
37563
37842
  await execSafe(
37564
37843
  "ffmpeg",
37565
37844
  [
@@ -37586,8 +37865,8 @@ async function executeJumpCut(options) {
37586
37865
  };
37587
37866
  } finally {
37588
37867
  try {
37589
- const { rm: rm6 } = await import("node:fs/promises");
37590
- await rm6(tmpDir, { recursive: true, force: true });
37868
+ const { rm: rm7 } = await import("node:fs/promises");
37869
+ await rm7(tmpDir, { recursive: true, force: true });
37591
37870
  } catch {
37592
37871
  }
37593
37872
  }
@@ -37664,29 +37943,27 @@ __export(remotion_exports, {
37664
37943
  wrapComponentWithImage: () => wrapComponentWithImage,
37665
37944
  wrapComponentWithVideo: () => wrapComponentWithVideo
37666
37945
  });
37667
- import { writeFile as writeFile21, mkdir as mkdir13, rm as rm3, copyFile as copyFile4 } from "node:fs/promises";
37946
+ import { writeFile as writeFile22, mkdir as mkdir14, rm as rm4, copyFile as copyFile4 } from "node:fs/promises";
37668
37947
  import { existsSync as existsSync25 } from "node:fs";
37669
37948
  import { join as join19 } from "node:path";
37670
37949
  import { tmpdir } from "node:os";
37671
37950
  import { createHash as createHash4 } from "node:crypto";
37672
37951
  async function ensureRemotionInstalled() {
37673
37952
  try {
37674
- await execSafe("npx", ["--yes", "remotion", "--help"], { timeout: 6e4 });
37953
+ await execSafe("npm", ["--version"], { timeout: 3e4 });
37675
37954
  return null;
37676
37955
  } catch (error) {
37677
37956
  const detail = error instanceof Error ? error.message : String(error);
37678
- console.error(`[Remotion] ensureRemotionInstalled failed: ${detail.slice(0, 300)}`);
37679
37957
  return [
37680
- "Remotion CLI not found or failed to initialize.",
37958
+ "Remotion renders need npm to install the Remotion CLI into a temporary project.",
37681
37959
  ` Debug: ${detail.slice(0, 200)}`,
37682
- " Fix: npm install -g @remotion/cli",
37683
- " Or ensure npx is available and can download @remotion/cli on demand."
37960
+ " Fix: install Node.js 20+ (which includes npm) and make sure `npm` is on PATH."
37684
37961
  ].join("\n");
37685
37962
  }
37686
37963
  }
37687
37964
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
37688
37965
  const dir = join19(tmpdir(), `vibe_motion_${Date.now()}`);
37689
- await mkdir13(dir, { recursive: true });
37966
+ await mkdir14(dir, { recursive: true });
37690
37967
  const deps = {
37691
37968
  remotion: REMOTION_VERSION,
37692
37969
  "@remotion/cli": REMOTION_VERSION,
@@ -37704,7 +37981,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37704
37981
  dependencies: deps
37705
37982
  };
37706
37983
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
37707
- await writeFile21(join19(dir, "package.json"), packageJsonStr);
37984
+ await writeFile22(join19(dir, "package.json"), packageJsonStr);
37708
37985
  const tsconfig = {
37709
37986
  compilerOptions: {
37710
37987
  target: "ES2020",
@@ -37716,8 +37993,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
37716
37993
  skipLibCheck: true
37717
37994
  }
37718
37995
  };
37719
- await writeFile21(join19(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
37720
- await writeFile21(join19(dir, "Component.tsx"), componentCode);
37996
+ await writeFile22(join19(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
37997
+ await writeFile22(join19(dir, "Component.tsx"), componentCode);
37721
37998
  const rootCode = `import { registerRoot, Composition } from "remotion";
37722
37999
  import { ${componentName} } from "./Component";
37723
38000
 
@@ -37736,7 +38013,7 @@ const Root = () => {
37736
38013
 
37737
38014
  registerRoot(Root);
37738
38015
  `;
37739
- await writeFile21(join19(dir, "Root.tsx"), rootCode);
38016
+ await writeFile22(join19(dir, "Root.tsx"), rootCode);
37740
38017
  const depsHash = createHash4("md5").update(packageJsonStr).digest("hex").slice(0, 12);
37741
38018
  const cacheMarker = join19(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
37742
38019
  const cachedModules = join19(REMOTION_CACHE_DIR, "node_modules");
@@ -37750,15 +38027,15 @@ registerRoot(Root);
37750
38027
  } else {
37751
38028
  await this_npmInstall(dir);
37752
38029
  try {
37753
- await mkdir13(REMOTION_CACHE_DIR, { recursive: true });
37754
- await writeFile21(join19(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
38030
+ await mkdir14(REMOTION_CACHE_DIR, { recursive: true });
38031
+ await writeFile22(join19(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
37755
38032
  if (existsSync25(join19(dir, "node_modules"))) {
37756
38033
  const { rename: rename5, symlink } = await import("node:fs/promises");
37757
38034
  await rename5(join19(dir, "node_modules"), cachedModules).catch(() => {
37758
38035
  });
37759
38036
  await symlink(cachedModules, join19(dir, "node_modules"), "dir").catch(() => {
37760
38037
  });
37761
- await writeFile21(cacheMarker, depsHash);
38038
+ await writeFile22(cacheMarker, depsHash);
37762
38039
  }
37763
38040
  } catch {
37764
38041
  }
@@ -37858,7 +38135,7 @@ async function renderMotion(options) {
37858
38135
  stderr: ${stderr}` : ""}`);
37859
38136
  return { success: false, error: `Remotion render failed: ${msg}` };
37860
38137
  } finally {
37861
- await rm3(dir, { recursive: true, force: true }).catch(() => {
38138
+ await rm4(dir, { recursive: true, force: true }).catch(() => {
37862
38139
  });
37863
38140
  }
37864
38141
  }
@@ -37927,7 +38204,7 @@ async function renderWithEmbeddedImage(options) {
37927
38204
  );
37928
38205
  try {
37929
38206
  const publicDir = join19(dir, "public");
37930
- await mkdir13(publicDir, { recursive: true });
38207
+ await mkdir14(publicDir, { recursive: true });
37931
38208
  await copyFile4(options.imagePath, join19(publicDir, options.imageFileName));
37932
38209
  const entryPoint = join19(dir, "Root.tsx");
37933
38210
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
@@ -37950,7 +38227,7 @@ async function renderWithEmbeddedImage(options) {
37950
38227
  const msg = error instanceof Error ? error.message : String(error);
37951
38228
  return { success: false, error: `Remotion image embed render failed: ${msg}` };
37952
38229
  } finally {
37953
- await rm3(dir, { recursive: true, force: true }).catch(() => {
38230
+ await rm4(dir, { recursive: true, force: true }).catch(() => {
37954
38231
  });
37955
38232
  }
37956
38233
  }
@@ -37991,7 +38268,7 @@ async function renderWithEmbeddedVideo(options) {
37991
38268
  );
37992
38269
  try {
37993
38270
  const publicDir = join19(dir, "public");
37994
- await mkdir13(publicDir, { recursive: true });
38271
+ await mkdir14(publicDir, { recursive: true });
37995
38272
  await copyFile4(options.videoPath, join19(publicDir, options.videoFileName));
37996
38273
  const entryPoint = join19(dir, "Root.tsx");
37997
38274
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
@@ -38027,14 +38304,14 @@ async function renderWithEmbeddedVideo(options) {
38027
38304
  "-shortest",
38028
38305
  mp4Out
38029
38306
  ], { timeout: 12e4 });
38030
- await rm3(mp4VideoOnly, { force: true }).catch(() => {
38307
+ await rm4(mp4VideoOnly, { force: true }).catch(() => {
38031
38308
  });
38032
38309
  return { success: true, outputPath: mp4Out };
38033
38310
  } catch (error) {
38034
38311
  const msg = error instanceof Error ? error.message : String(error);
38035
38312
  return { success: false, error: `Remotion video embed render failed: ${msg}` };
38036
38313
  } finally {
38037
- await rm3(dir, { recursive: true, force: true }).catch(() => {
38314
+ await rm4(dir, { recursive: true, force: true }).catch(() => {
38038
38315
  });
38039
38316
  }
38040
38317
  }
@@ -38415,8 +38692,8 @@ var init_remotion = __esm({
38415
38692
 
38416
38693
  // src/commands/_shared/edit/caption.ts
38417
38694
  import { existsSync as existsSync26 } from "node:fs";
38418
- import { readFile as readFile22, writeFile as writeFile22, mkdir as mkdir14 } from "node:fs/promises";
38419
- import { dirname as dirname11, basename as basename6, extname as extname5, join as join20 } from "node:path";
38695
+ import { readFile as readFile22, writeFile as writeFile23, mkdir as mkdir15 } from "node:fs/promises";
38696
+ import { dirname as dirname12, basename as basename6, extname as extname6, join as join20 } from "node:path";
38420
38697
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
38421
38698
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
38422
38699
  const marginV = position === "center" ? 0 : 30;
@@ -38462,7 +38739,7 @@ async function executeCaption(options) {
38462
38739
  }
38463
38740
  try {
38464
38741
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
38465
- await mkdir14(tmpDir, { recursive: true });
38742
+ await mkdir15(tmpDir, { recursive: true });
38466
38743
  const audioPath = join20(tmpDir, "audio.wav");
38467
38744
  const srtPath = join20(tmpDir, "captions.srt");
38468
38745
  try {
@@ -38495,7 +38772,7 @@ async function executeCaption(options) {
38495
38772
  };
38496
38773
  }
38497
38774
  const srtContent = formatSRT(transcriptResult.segments);
38498
- await writeFile22(srtPath, srtContent);
38775
+ await writeFile23(srtPath, srtContent);
38499
38776
  const { width, height } = await getVideoResolution(videoPath);
38500
38777
  const fontSize = customFontSize || Math.round(height / 18);
38501
38778
  let hasSubtitles = false;
@@ -38532,12 +38809,12 @@ async function executeCaption(options) {
38532
38809
  } = await Promise.resolve().then(() => (init_remotion(), remotion_exports));
38533
38810
  const remotionErr = await ensureRemotionInstalled2();
38534
38811
  if (remotionErr) {
38535
- const outputDir2 = dirname11(outputPath);
38812
+ const outputDir2 = dirname12(outputPath);
38536
38813
  const outputSrtPath2 = join20(
38537
38814
  outputDir2,
38538
- basename6(outputPath, extname5(outputPath)) + ".srt"
38815
+ basename6(outputPath, extname6(outputPath)) + ".srt"
38539
38816
  );
38540
- await writeFile22(outputSrtPath2, srtContent);
38817
+ await writeFile23(outputSrtPath2, srtContent);
38541
38818
  return { success: false, error: `${remotionErr}
38542
38819
  SRT saved to: ${outputSrtPath2}` };
38543
38820
  }
@@ -38571,12 +38848,12 @@ SRT saved to: ${outputSrtPath2}` };
38571
38848
  outputPath
38572
38849
  });
38573
38850
  if (!renderResult.success) {
38574
- const outputDir2 = dirname11(outputPath);
38851
+ const outputDir2 = dirname12(outputPath);
38575
38852
  const outputSrtPath2 = join20(
38576
38853
  outputDir2,
38577
- basename6(outputPath, extname5(outputPath)) + ".srt"
38854
+ basename6(outputPath, extname6(outputPath)) + ".srt"
38578
38855
  );
38579
- await writeFile22(outputSrtPath2, srtContent);
38856
+ await writeFile23(outputSrtPath2, srtContent);
38580
38857
  return {
38581
38858
  success: false,
38582
38859
  error: `${renderResult.error}
@@ -38584,12 +38861,12 @@ SRT saved to: ${outputSrtPath2}`
38584
38861
  };
38585
38862
  }
38586
38863
  }
38587
- const outputDir = dirname11(outputPath);
38864
+ const outputDir = dirname12(outputPath);
38588
38865
  const outputSrtPath = join20(
38589
38866
  outputDir,
38590
- basename6(outputPath, extname5(outputPath)) + ".srt"
38867
+ basename6(outputPath, extname6(outputPath)) + ".srt"
38591
38868
  );
38592
- await writeFile22(outputSrtPath, srtContent);
38869
+ await writeFile23(outputSrtPath, srtContent);
38593
38870
  return {
38594
38871
  success: true,
38595
38872
  outputPath,
@@ -38598,8 +38875,8 @@ SRT saved to: ${outputSrtPath2}`
38598
38875
  };
38599
38876
  } finally {
38600
38877
  try {
38601
- const { rm: rm6 } = await import("node:fs/promises");
38602
- await rm6(tmpDir, { recursive: true, force: true });
38878
+ const { rm: rm7 } = await import("node:fs/promises");
38879
+ await rm7(tmpDir, { recursive: true, force: true });
38603
38880
  } catch {
38604
38881
  }
38605
38882
  }
@@ -38760,7 +39037,7 @@ var init_fade = __esm({
38760
39037
 
38761
39038
  // src/commands/_shared/edit/translate-srt.ts
38762
39039
  import { existsSync as existsSync29 } from "node:fs";
38763
- import { readFile as readFile23, writeFile as writeFile23 } from "node:fs/promises";
39040
+ import { readFile as readFile23, writeFile as writeFile24 } from "node:fs/promises";
38764
39041
  async function executeTranslateSrt(options) {
38765
39042
  const {
38766
39043
  srtPath,
@@ -38803,15 +39080,14 @@ ${textsToTranslate}`;
38803
39080
  Authorization: `Bearer ${openaiKey}`
38804
39081
  },
38805
39082
  body: JSON.stringify({
38806
- model: "gpt-5-mini",
38807
- messages: [{ role: "user", content: translatePrompt }],
38808
- temperature: 0.3
39083
+ model: defaultModel("openai", "llm").id,
39084
+ messages: [{ role: "user", content: translatePrompt }]
38809
39085
  })
38810
39086
  });
38811
39087
  if (!response.ok) {
38812
39088
  return {
38813
39089
  success: false,
38814
- error: `OpenAI API error: ${response.status} ${response.statusText}`
39090
+ error: `OpenAI API error: ${response.status} ${response.statusText}: ${await response.text()}`
38815
39091
  };
38816
39092
  }
38817
39093
  const data = await response.json();
@@ -38832,15 +39108,15 @@ ${textsToTranslate}`;
38832
39108
  "anthropic-version": "2023-06-01"
38833
39109
  },
38834
39110
  body: JSON.stringify({
38835
- model: "claude-sonnet-4-6-20250514",
38836
- max_tokens: 4096,
39111
+ model: defaultModel("claude", "llm").id,
39112
+ max_tokens: 16e3,
38837
39113
  messages: [{ role: "user", content: translatePrompt }]
38838
39114
  })
38839
39115
  });
38840
39116
  if (!response.ok) {
38841
39117
  return {
38842
39118
  success: false,
38843
- error: `Claude API error: ${response.status} ${response.statusText}`
39119
+ error: `Claude API error: ${response.status} ${response.statusText}: ${await response.text()}`
38844
39120
  };
38845
39121
  }
38846
39122
  const data = await response.json();
@@ -38864,7 +39140,7 @@ ${textsToTranslate}`;
38864
39140
  }
38865
39141
  }
38866
39142
  const translatedSrt = formatSRT(translatedSegments);
38867
- await writeFile23(outputPath, translatedSrt);
39143
+ await writeFile24(outputPath, translatedSrt);
38868
39144
  return {
38869
39145
  success: true,
38870
39146
  outputPath,
@@ -38883,6 +39159,7 @@ var init_translate_srt = __esm({
38883
39159
  "src/commands/_shared/edit/translate-srt.ts"() {
38884
39160
  "use strict";
38885
39161
  init_subtitle();
39162
+ init_dist2();
38886
39163
  }
38887
39164
  });
38888
39165
 
@@ -39116,7 +39393,7 @@ var init_ai_edit = __esm({
39116
39393
  });
39117
39394
 
39118
39395
  // src/commands/ai-edit-cli.ts
39119
- import { resolve as resolve30, extname as extname6, basename as basename7 } from "node:path";
39396
+ import { resolve as resolve30, extname as extname7, basename as basename7 } from "node:path";
39120
39397
  import { existsSync as existsSync31 } from "node:fs";
39121
39398
  function registerEditCommands(aiCommand) {
39122
39399
  aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "(deprecated - use `vibe detect silence`) Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
@@ -39140,7 +39417,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
39140
39417
  if (!commandExists("ffmpeg")) {
39141
39418
  exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
39142
39419
  }
39143
- const ext = extname6(videoPath);
39420
+ const ext = extname7(videoPath);
39144
39421
  const name = basename7(videoPath, ext);
39145
39422
  const outputPath = options.output || `${name}-cut${ext}`;
39146
39423
  const useGemini = options.useGemini || false;
@@ -39270,7 +39547,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39270
39547
  if (!apiKey) {
39271
39548
  exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
39272
39549
  }
39273
- const ext = extname6(videoPath);
39550
+ const ext = extname7(videoPath);
39274
39551
  const name = basename7(videoPath, ext);
39275
39552
  const outputPath = options.output || `${name}-captioned${ext}`;
39276
39553
  const spinner2 = ora("Starting caption process...").start();
@@ -39344,7 +39621,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39344
39621
  });
39345
39622
  return;
39346
39623
  }
39347
- const ext = extname6(inputPath);
39624
+ const ext = extname7(inputPath);
39348
39625
  const name = basename7(inputPath, ext);
39349
39626
  const outputPath = options.output || `${name}-denoised${ext}`;
39350
39627
  const spinner2 = ora("Applying noise reduction...").start();
@@ -39412,7 +39689,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39412
39689
  });
39413
39690
  return;
39414
39691
  }
39415
- const ext = extname6(videoPath);
39692
+ const ext = extname7(videoPath);
39416
39693
  const name = basename7(videoPath, ext);
39417
39694
  const outputPath = options.output || `${name}-faded${ext}`;
39418
39695
  const spinner2 = ora("Applying fade effects...").start();
@@ -39490,7 +39767,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39490
39767
  if (!apiKey) {
39491
39768
  exitWithError(authError(envKey, providerName));
39492
39769
  }
39493
- const ext = extname6(srtPath);
39770
+ const ext = extname7(srtPath);
39494
39771
  const name = basename7(srtPath, ext);
39495
39772
  const outputPath = options.output || `${name}-${options.target}${ext}`;
39496
39773
  const spinner2 = ora(`Translating to ${options.target}...`).start();
@@ -39568,7 +39845,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
39568
39845
  if (!apiKey) {
39569
39846
  exitWithError(authError("OPENAI_API_KEY", "OpenAI"));
39570
39847
  }
39571
- const ext = extname6(videoPath);
39848
+ const ext = extname7(videoPath);
39572
39849
  const name = basename7(videoPath, ext);
39573
39850
  const outputPath = options.output || `${name}-jumpcut${ext}`;
39574
39851
  const fillers = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
@@ -40029,8 +40306,8 @@ var init_project_resolver = __esm({
40029
40306
 
40030
40307
  // src/commands/_shared/execute-fill-gaps.ts
40031
40308
  import { existsSync as existsSync32 } from "node:fs";
40032
- import { readFile as readFile24, writeFile as writeFile24, mkdir as mkdir15, rename as renameFs } from "node:fs/promises";
40033
- import { resolve as resolve32, dirname as dirname12 } from "node:path";
40309
+ import { readFile as readFile24, writeFile as writeFile25, mkdir as mkdir16, rename as renameFs } from "node:fs/promises";
40310
+ import { resolve as resolve32, dirname as dirname13 } from "node:path";
40034
40311
  function detectVideoGaps(videoClips, totalDuration) {
40035
40312
  const gaps = [];
40036
40313
  const sortedClips = [...videoClips].sort((a, b) => a.startTime - b.startTime);
@@ -40084,22 +40361,13 @@ function analyzeGapFillability(gaps, videoClips, sources) {
40084
40361
  return { gap, canExtendBefore, canExtendAfter, remainingGap, gapStart };
40085
40362
  });
40086
40363
  }
40087
- async function uploadFrameToImgbb(framePath, imgbbApiKey) {
40364
+ async function uploadFrame(host, framePath) {
40088
40365
  try {
40089
- const frameBuffer = await readFile24(framePath);
40090
- const frameBase64 = frameBuffer.toString("base64");
40091
- const formData = new FormData();
40092
- formData.append("key", imgbbApiKey);
40093
- formData.append("image", frameBase64);
40094
- const response = await fetch("https://api.imgbb.com/1/upload", {
40095
- method: "POST",
40096
- body: formData
40366
+ const upload = await host.uploadImage(await readFile24(framePath), {
40367
+ filename: framePath,
40368
+ mimeType: "image/png"
40097
40369
  });
40098
- const data = await response.json();
40099
- if (!data.success || !data.data?.url) {
40100
- return { error: data.error?.message || "Upload failed" };
40101
- }
40102
- return { url: data.data.url };
40370
+ return { url: upload.url };
40103
40371
  } catch (err) {
40104
40372
  return { error: err instanceof Error ? err.message : String(err) };
40105
40373
  }
@@ -40240,21 +40508,23 @@ async function executeFillGaps(options) {
40240
40508
  if (!kling.isConfigured()) {
40241
40509
  return {
40242
40510
  success: false,
40243
- error: "Invalid KLING_API_KEY (expected ACCESS_KEY:SECRET_KEY format)",
40511
+ error: "Invalid KLING_API_KEY (set your Kling API key, or the legacy ACCESS_KEY:SECRET_KEY pair)",
40244
40512
  humanLines
40245
40513
  };
40246
40514
  }
40247
- const projectDir = dirname12(filePath);
40515
+ const projectDir = dirname13(filePath);
40248
40516
  const footageDir = options.dir ? resolve32(process.cwd(), options.dir) : resolve32(projectDir, "footage");
40249
40517
  if (!existsSync32(footageDir)) {
40250
- await mkdir15(footageDir, { recursive: true });
40518
+ await mkdir16(footageDir, { recursive: true });
40251
40519
  }
40252
40520
  humanLines.push("Generating AI Videos");
40253
- const imgbbApiKey = options.imgbbApiKey || await getApiKey("IMGBB_API_KEY", "imgbb", void 0);
40254
- if (!imgbbApiKey) {
40521
+ let uploadHost;
40522
+ try {
40523
+ uploadHost = await resolveUploadHost();
40524
+ } catch (err) {
40255
40525
  return {
40256
40526
  success: false,
40257
- error: "IMGBB_API_KEY required for image hosting. Get a free API key at https://api.imgbb.com/",
40527
+ error: err instanceof Error ? err.message : String(err),
40258
40528
  humanLines
40259
40529
  };
40260
40530
  }
@@ -40298,12 +40568,12 @@ async function executeFillGaps(options) {
40298
40568
  humanLines
40299
40569
  };
40300
40570
  }
40301
- onProgress("Uploading frame to imgbb...");
40302
- const upload = await uploadFrameToImgbb(framePath, imgbbApiKey);
40571
+ onProgress(`Uploading frame via ${uploadHost.provider}...`);
40572
+ const upload = await uploadFrame(uploadHost, framePath);
40303
40573
  if (!upload.url) {
40304
40574
  return {
40305
40575
  success: false,
40306
- error: `Failed to upload frame to imgbb: ${upload.error || "unknown"}`,
40576
+ error: `Failed to upload frame via ${uploadHost.provider}: ${upload.error || "unknown"}`,
40307
40577
  humanLines
40308
40578
  };
40309
40579
  }
@@ -40312,13 +40582,13 @@ async function executeFillGaps(options) {
40312
40582
  let generatedDuration = 0;
40313
40583
  const generatedVideos = [];
40314
40584
  const initialDuration = Math.min(10, targetDuration);
40315
- const klingDuration = initialDuration > 5 ? "10" : "5";
40316
- onProgress(`Generating ${klingDuration}s video with Kling...`);
40585
+ const klingDuration2 = initialDuration > 5 ? "10" : "5";
40586
+ onProgress(`Generating ${klingDuration2}s video with Kling...`);
40317
40587
  const prompt3 = options.prompt || "Continue the scene naturally with subtle motion";
40318
40588
  const result = await kling.generateVideo(prompt3, {
40319
40589
  prompt: prompt3,
40320
40590
  referenceImage: frameUrl,
40321
- duration: parseInt(klingDuration),
40591
+ duration: parseInt(klingDuration2),
40322
40592
  aspectRatio: options.ratio || "16:9",
40323
40593
  mode: options.mode || "std"
40324
40594
  });
@@ -40341,8 +40611,8 @@ async function executeFillGaps(options) {
40341
40611
  const videoPath = resolve32(footageDir, videoFileName);
40342
40612
  onProgress("Downloading generated video...");
40343
40613
  const videoBuffer = await downloadVideo(finalResult.videoUrl);
40344
- await writeFile24(videoPath, videoBuffer);
40345
- generatedDuration = finalResult.duration || parseInt(klingDuration);
40614
+ await writeFile25(videoPath, videoBuffer);
40615
+ generatedDuration = finalResult.duration || parseInt(klingDuration2);
40346
40616
  generatedVideos.push(videoPath);
40347
40617
  humanLines.push(` Generated: ${videoFileName} (${generatedDuration}s)`);
40348
40618
  let segmentIndex = 1;
@@ -40378,7 +40648,7 @@ async function executeFillGaps(options) {
40378
40648
  humanLines.push(" Failed to extract frame for continuation");
40379
40649
  break;
40380
40650
  }
40381
- const extUpload = await uploadFrameToImgbb(lastFramePath, imgbbApiKey);
40651
+ const extUpload = await uploadFrame(uploadHost, lastFramePath);
40382
40652
  if (!extUpload.url) {
40383
40653
  humanLines.push(" Failed to upload continuation frame");
40384
40654
  break;
@@ -40411,11 +40681,11 @@ async function executeFillGaps(options) {
40411
40681
  `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-seg${segmentIndex}.mp4`
40412
40682
  );
40413
40683
  const segVideoBuffer = await downloadVideo(segFinalResult.videoUrl);
40414
- await writeFile24(segVideoPath, segVideoBuffer);
40684
+ await writeFile25(segVideoPath, segVideoBuffer);
40415
40685
  const concatListPath = resolve32(footageDir, `concat-${gap.start.toFixed(2)}.txt`);
40416
40686
  const concatList = generatedVideos.map((v) => `file '${v}'`).join("\n") + `
40417
40687
  file '${segVideoPath}'`;
40418
- await writeFile24(concatListPath, concatList);
40688
+ await writeFile25(concatListPath, concatList);
40419
40689
  const concatOutputPath = resolve32(
40420
40690
  footageDir,
40421
40691
  `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-merged.mp4`
@@ -40473,7 +40743,7 @@ file '${segVideoPath}'`;
40473
40743
  let outputPath = filePath;
40474
40744
  if (generatedCount > 0) {
40475
40745
  outputPath = options.output ? resolve32(process.cwd(), options.output) : filePath;
40476
- await writeFile24(outputPath, JSON.stringify(project.toJSON(), null, 2));
40746
+ await writeFile25(outputPath, JSON.stringify(project.toJSON(), null, 2));
40477
40747
  humanLines.push(`\u2714 Filled ${generatedCount} gap(s) with AI-generated video`);
40478
40748
  humanLines.push(`Project saved: ${outputPath}`);
40479
40749
  } else {
@@ -40501,6 +40771,7 @@ var init_execute_fill_gaps = __esm({
40501
40771
  init_dist2();
40502
40772
  init_engine();
40503
40773
  init_api_key();
40774
+ init_upload_host();
40504
40775
  init_exec_safe();
40505
40776
  init_project_resolver();
40506
40777
  init_ai_helpers();
@@ -40567,7 +40838,14 @@ __export(ai_motion_exports, {
40567
40838
  });
40568
40839
  import { resolve as resolve33 } from "node:path";
40569
40840
  import { existsSync as existsSync33 } from "node:fs";
40570
- import { readFile as readFile25, writeFile as writeFile25 } from "node:fs/promises";
40841
+ import { readFile as readFile25, writeFile as writeFile26 } from "node:fs/promises";
40842
+ function resolveMotionModel(alias) {
40843
+ const claude = findModel("claude", "llm", alias);
40844
+ if (claude) return { provider: "claude", modelId: claude.id };
40845
+ const gemini = findModel("gemini", "llm", alias);
40846
+ if (gemini) return { provider: "gemini", modelId: gemini.id };
40847
+ return { provider: "claude", modelId: defaultModel("claude", "llm").id };
40848
+ }
40571
40849
  async function getOptionalGoogleApiKey() {
40572
40850
  const configKey = await getApiKeyFromConfig("google");
40573
40851
  if (configKey) return configKey;
@@ -40582,7 +40860,7 @@ function normalizeUnderstand(value) {
40582
40860
  }
40583
40861
  async function executeMotion(options) {
40584
40862
  const modelAlias = options.model || "sonnet";
40585
- const modelConfig = MODEL_MAP2[modelAlias] ?? MODEL_MAP2["sonnet"];
40863
+ const modelConfig = resolveMotionModel(modelAlias);
40586
40864
  const useGemini = modelConfig.provider === "gemini";
40587
40865
  const width = options.width || 1920;
40588
40866
  const height = options.height || 1080;
@@ -40737,7 +41015,7 @@ Use this video understanding to place motion graphics in safe zones, match the c
40737
41015
  const defaultOutput = options.video || options.image ? "motion-output.mp4" : options.render ? "motion.webm" : "motion.tsx";
40738
41016
  const outputPath = resolve33(process.cwd(), options.output || defaultOutput);
40739
41017
  const codePath = outputPath.replace(/\.\w+$/, ".tsx");
40740
- await writeFile25(codePath, component.code, "utf-8");
41018
+ await writeFile26(codePath, component.code, "utf-8");
40741
41019
  const shouldRender = options.render || !!options.video || !!options.image;
40742
41020
  if (!shouldRender) {
40743
41021
  return { success: true, codePath, componentName: component.name };
@@ -40961,7 +41239,6 @@ function registerMotionCommand(aiCommand) {
40961
41239
  }
40962
41240
  });
40963
41241
  }
40964
- var MODEL_MAP2;
40965
41242
  var init_ai_motion = __esm({
40966
41243
  "src/commands/ai-motion.ts"() {
40967
41244
  "use strict";
@@ -40972,14 +41249,6 @@ var init_ai_motion = __esm({
40972
41249
  init_config();
40973
41250
  init_output();
40974
41251
  init_validate();
40975
- MODEL_MAP2 = {
40976
- sonnet: { provider: "claude", modelId: "claude-sonnet-4-6" },
40977
- opus: { provider: "claude", modelId: "claude-opus-4-7" },
40978
- "opus-4-6": { provider: "claude", modelId: "claude-opus-4-6" },
40979
- gemini: { provider: "gemini", modelId: GEMINI_DEFAULT_TEXT_MODEL },
40980
- "gemini-2.5-pro": { provider: "gemini", modelId: "gemini-2.5-pro" },
40981
- "gemini-3.1-pro": { provider: "gemini", modelId: "gemini-3.1-pro-preview" }
40982
- };
40983
41252
  }
40984
41253
  });
40985
41254
 
@@ -41216,7 +41485,7 @@ __export(project_builder_exports, {
41216
41485
  buildTempProject: () => buildTempProject,
41217
41486
  resolveSourceUrl: () => resolveSourceUrl
41218
41487
  });
41219
- import { mkdtemp, mkdir as mkdir16, copyFile as copyFile5, writeFile as writeFile26, rm as rm4, readdir as readdir4 } from "node:fs/promises";
41488
+ import { mkdtemp, mkdir as mkdir17, copyFile as copyFile5, writeFile as writeFile27, rm as rm5, readdir as readdir4 } from "node:fs/promises";
41220
41489
  import { existsSync as existsSync34 } from "node:fs";
41221
41490
  import { tmpdir as tmpdir2 } from "node:os";
41222
41491
  import { createRequire } from "node:module";
@@ -41224,7 +41493,7 @@ import * as path from "node:path";
41224
41493
  async function buildTempProject(state, projectFileDir) {
41225
41494
  const dir = await mkdtemp(path.join(tmpdir2(), "vibeframe-hf-"));
41226
41495
  const assetsDir = path.join(dir, "assets");
41227
- await mkdir16(assetsDir, { recursive: true });
41496
+ await mkdir17(assetsDir, { recursive: true });
41228
41497
  const copied = /* @__PURE__ */ new Map();
41229
41498
  for (const source of state.sources) {
41230
41499
  if (copied.has(source.id)) continue;
@@ -41240,10 +41509,10 @@ async function buildTempProject(state, projectFileDir) {
41240
41509
  await copyLottieRuntime(dir);
41241
41510
  }
41242
41511
  const html = generateCompositionHtml(state);
41243
- await writeFile26(path.join(dir, "index.html"), html, "utf-8");
41512
+ await writeFile27(path.join(dir, "index.html"), html, "utf-8");
41244
41513
  return {
41245
41514
  dir,
41246
- cleanup: () => rm4(dir, { recursive: true, force: true })
41515
+ cleanup: () => rm5(dir, { recursive: true, force: true })
41247
41516
  };
41248
41517
  }
41249
41518
  async function copyLottieRuntime(tempDir) {
@@ -41254,7 +41523,7 @@ async function copyLottieRuntime(tempDir) {
41254
41523
  const wasmSrc = path.join(path.dirname(webEntry), "dotlottie-player.wasm");
41255
41524
  const vendorDir = path.join(tempDir, "vendor");
41256
41525
  const wcDest = path.join(vendorDir, "dotlottie-wc");
41257
- await mkdir16(wcDest, { recursive: true });
41526
+ await mkdir17(wcDest, { recursive: true });
41258
41527
  for (const file of await readdir4(wcDistDir)) {
41259
41528
  if (file.endsWith(".map") || file.endsWith(".d.ts")) continue;
41260
41529
  await copyFile5(path.join(wcDistDir, file), path.join(wcDest, file));
@@ -41362,9 +41631,9 @@ __export(motion_overlay_exports, {
41362
41631
  executeMotionOverlay: () => executeMotionOverlay,
41363
41632
  registerMotionOverlayCommand: () => registerMotionOverlayCommand
41364
41633
  });
41365
- import { mkdir as mkdir17 } from "node:fs/promises";
41634
+ import { mkdir as mkdir18 } from "node:fs/promises";
41366
41635
  import { existsSync as existsSync35 } from "node:fs";
41367
- import { basename as basename9, dirname as dirname14, extname as extname7, resolve as resolve35 } from "node:path";
41636
+ import { basename as basename9, dirname as dirname15, extname as extname8, resolve as resolve35 } from "node:path";
41368
41637
  function normalizeUnderstand2(value) {
41369
41638
  return value === "off" || value === "required" || value === "auto" ? value : "auto";
41370
41639
  }
@@ -41387,7 +41656,7 @@ function assertValidRequest(options) {
41387
41656
  return "Provide a motion description, or pass --asset <logo.json|logo.lottie>.";
41388
41657
  }
41389
41658
  if (hasAsset) {
41390
- const ext = extname7(options.asset).toLowerCase();
41659
+ const ext = extname8(options.asset).toLowerCase();
41391
41660
  if (ext !== ".json" && ext !== ".lottie") {
41392
41661
  return "Lottie overlay asset must be a .json or .lottie file.";
41393
41662
  }
@@ -41510,7 +41779,7 @@ async function executeMotionOverlay(options) {
41510
41779
  const scale = Math.max(0.01, Math.min(2, numberOption(options.scale, position === "full" ? 1 : 0.25)));
41511
41780
  const opacity = Math.max(0, Math.min(1, numberOption(options.opacity, 1)));
41512
41781
  const loop = options.loop ?? true;
41513
- await mkdir17(dirname14(outputPath), { recursive: true });
41782
+ await mkdir18(dirname15(outputPath), { recursive: true });
41514
41783
  const backend = createHyperframesBackend();
41515
41784
  const result2 = await backend.render({
41516
41785
  projectState: buildLottieTimelineState({
@@ -41536,7 +41805,7 @@ async function executeMotionOverlay(options) {
41536
41805
  return { success: true, outputPath, provider: "lottie" };
41537
41806
  }
41538
41807
  const duration = options.duration ?? await ffprobeDuration(absVideo).catch(() => void 0);
41539
- await mkdir17(dirname14(outputPath), { recursive: true });
41808
+ await mkdir18(dirname15(outputPath), { recursive: true });
41540
41809
  const result = await executeMotion({
41541
41810
  description: options.description.trim(),
41542
41811
  video: absVideo,
@@ -41664,8 +41933,8 @@ __export(edit_cmd_exports, {
41664
41933
  executeSpeedRamp: () => executeSpeedRamp,
41665
41934
  executeUpscale: () => executeUpscale
41666
41935
  });
41667
- import { resolve as resolve36, dirname as dirname15 } from "node:path";
41668
- import { readFile as readFile26, writeFile as writeFile27, mkdir as mkdir18 } from "node:fs/promises";
41936
+ import { resolve as resolve36, dirname as dirname16 } from "node:path";
41937
+ import { readFile as readFile26, writeFile as writeFile28, mkdir as mkdir19 } from "node:fs/promises";
41669
41938
  async function executeGrade(options) {
41670
41939
  const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
41671
41940
  try {
@@ -41858,6 +42127,7 @@ var init_edit_cmd = __esm({
41858
42127
  init_output();
41859
42128
  init_validate();
41860
42129
  init_cost_tier();
42130
+ init_image_file();
41861
42131
  editCommand = new Command("edit").alias("ed").description(
41862
42132
  "Edit and post-process media (silence-cut, caption, grade, reframe, upscale...)"
41863
42133
  ).addHelpText(
@@ -42293,7 +42563,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42293
42563
  console.log();
42294
42564
  if (options.keyframes) {
42295
42565
  const keyframesPath = resolve36(process.cwd(), options.keyframes);
42296
- await writeFile27(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
42566
+ await writeFile28(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
42297
42567
  console.log(source_default.green(`Keyframes saved to: ${keyframesPath}`));
42298
42568
  }
42299
42569
  if (options.analyzeOnly) {
@@ -42317,7 +42587,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42317
42587
  exitWithError(generalError(`Reframe failed: ${msg}`));
42318
42588
  }
42319
42589
  });
42320
- 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) => {
42590
+ 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, lite, pro (Gemini); pro (Grok)", "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) => {
42321
42591
  const startedAt = Date.now();
42322
42592
  try {
42323
42593
  if (args.length < 2) {
@@ -42376,14 +42646,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42376
42646
  const grok = new GrokProvider();
42377
42647
  await grok.initialize({ apiKey });
42378
42648
  result = await grok.editImage(imageBuffers[0], prompt3, {
42649
+ ...resolveGrokImageModel(options.model),
42379
42650
  aspectRatio: options.ratio
42380
42651
  });
42381
42652
  } else {
42382
42653
  const editModelNames = {
42383
- flash: "gemini-2.5-flash-image",
42384
- "3.1-flash": "gemini-3.1-flash-image-preview",
42385
- latest: "gemini-3.1-flash-image-preview",
42386
- pro: "gemini-3-pro-image-preview"
42654
+ flash: "gemini-3.1-flash-image",
42655
+ "3.1-flash": "gemini-3.1-flash-image",
42656
+ latest: "gemini-3.1-flash-image",
42657
+ lite: "gemini-3.1-flash-lite-image",
42658
+ pro: "gemini-3-pro-image"
42387
42659
  };
42388
42660
  const editModelName = editModelNames[options.model] || editModelNames.flash;
42389
42661
  spinner2.text = `Editing with ${editModelName}...`;
@@ -42394,15 +42666,6 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42394
42666
  aspectRatio: options.ratio,
42395
42667
  resolution: options.size
42396
42668
  });
42397
- const fallbackModels = ["latest", "3.1-flash"];
42398
- if (!result.success && fallbackModels.includes(options.model)) {
42399
- spinner2.text = `${source_default.dim(result.error || `${editModelName} failed`)} \u2014 retrying with flash...`;
42400
- result = await gemini.editImage(imageBuffers, prompt3, {
42401
- model: "flash",
42402
- aspectRatio: options.ratio,
42403
- resolution: options.size
42404
- });
42405
- }
42406
42669
  }
42407
42670
  if (!result.success || !result.images || result.images.length === 0) {
42408
42671
  spinner2.fail(result.error || "Image editing failed");
@@ -42412,14 +42675,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
42412
42675
  const img = result.images[0];
42413
42676
  const outputPath = resolve36(process.cwd(), options.output);
42414
42677
  const saveImage = async () => {
42415
- await mkdir18(dirname15(outputPath), { recursive: true });
42678
+ await mkdir19(dirname16(outputPath), { recursive: true });
42416
42679
  if (img.base64) {
42417
42680
  const buffer = Buffer.from(img.base64, "base64");
42418
- await writeFile27(outputPath, buffer);
42681
+ await writeImageFile(outputPath, buffer);
42419
42682
  } else if (img.url) {
42420
42683
  const resp = await fetch(img.url);
42421
42684
  const arrayBuf = await resp.arrayBuffer();
42422
- await writeFile27(outputPath, Buffer.from(arrayBuf));
42685
+ await writeImageFile(outputPath, Buffer.from(arrayBuf));
42423
42686
  }
42424
42687
  };
42425
42688
  const resultModel = result.model;
@@ -42638,8 +42901,8 @@ __export(ai_image_exports, {
42638
42901
  executeImageGenerate: () => executeImageGenerate,
42639
42902
  executeThumbnailBestFrame: () => executeThumbnailBestFrame
42640
42903
  });
42641
- import { resolve as resolve38, dirname as dirname17 } from "node:path";
42642
- import { readFile as readFile27, writeFile as writeFile29, mkdir as mkdir20 } from "node:fs/promises";
42904
+ import { resolve as resolve38, dirname as dirname18 } from "node:path";
42905
+ import { readFile as readFile27, mkdir as mkdir21 } from "node:fs/promises";
42643
42906
  import { existsSync as existsSync37 } from "node:fs";
42644
42907
  async function executeImageGenerate(options) {
42645
42908
  const {
@@ -42682,8 +42945,8 @@ async function executeImageGenerate(options) {
42682
42945
  return { success: false, error: "No image data available" };
42683
42946
  }
42684
42947
  outputPath = resolve38(process.cwd(), output);
42685
- await mkdir20(dirname17(outputPath), { recursive: true });
42686
- await writeFile29(outputPath, buffer);
42948
+ await mkdir21(dirname18(outputPath), { recursive: true });
42949
+ await writeImageFile(outputPath, buffer);
42687
42950
  }
42688
42951
  return {
42689
42952
  success: true,
@@ -42717,8 +42980,8 @@ async function executeImageGenerate(options) {
42717
42980
  const img = result.images[0];
42718
42981
  if (img.base64) {
42719
42982
  outputPath = resolve38(process.cwd(), output);
42720
- await mkdir20(dirname17(outputPath), { recursive: true });
42721
- await writeFile29(outputPath, Buffer.from(img.base64, "base64"));
42983
+ await mkdir21(dirname18(outputPath), { recursive: true });
42984
+ await writeImageFile(outputPath, Buffer.from(img.base64, "base64"));
42722
42985
  }
42723
42986
  }
42724
42987
  return {
@@ -42731,10 +42994,11 @@ async function executeImageGenerate(options) {
42731
42994
  } else if (provider === "grok") {
42732
42995
  const key = apiKey || process.env.XAI_API_KEY;
42733
42996
  if (!key) return { success: false, error: "XAI_API_KEY required" };
42734
- const openaiImage = new OpenAIImageProvider();
42735
- await openaiImage.initialize({ apiKey: key, baseUrl: "https://api.x.ai/v1" });
42736
- const result = await openaiImage.generateImage(prompt3, {
42737
- size,
42997
+ const grok = new GrokProvider();
42998
+ await grok.initialize({ apiKey: key });
42999
+ const result = await grok.generateImage(prompt3, {
43000
+ ...resolveGrokImageModel(model),
43001
+ aspectRatio: ratio,
42738
43002
  n: count
42739
43003
  });
42740
43004
  if (!result.success || !result.images) {
@@ -42753,8 +43017,8 @@ async function executeImageGenerate(options) {
42753
43017
  return { success: false, error: "No image data available" };
42754
43018
  }
42755
43019
  outputPath = resolve38(process.cwd(), output);
42756
- await mkdir20(dirname17(outputPath), { recursive: true });
42757
- await writeFile29(outputPath, buffer);
43020
+ await mkdir21(dirname18(outputPath), { recursive: true });
43021
+ await writeImageFile(outputPath, buffer);
42758
43022
  }
42759
43023
  return {
42760
43024
  success: true,
@@ -42812,8 +43076,8 @@ async function executeGeminiEdit(options) {
42812
43076
  let outputPath;
42813
43077
  if (img.base64) {
42814
43078
  outputPath = resolve38(process.cwd(), output);
42815
- await mkdir20(dirname17(outputPath), { recursive: true });
42816
- await writeFile29(outputPath, Buffer.from(img.base64, "base64"));
43079
+ await mkdir21(dirname18(outputPath), { recursive: true });
43080
+ await writeImageFile(outputPath, Buffer.from(img.base64, "base64"));
42817
43081
  }
42818
43082
  return {
42819
43083
  success: true,
@@ -42887,6 +43151,7 @@ var init_ai_image = __esm({
42887
43151
  "use strict";
42888
43152
  init_dist2();
42889
43153
  init_exec_safe();
43154
+ init_image_file();
42890
43155
  }
42891
43156
  });
42892
43157
 
@@ -42898,7 +43163,7 @@ __export(ai_analyze_exports, {
42898
43163
  registerAnalyzeCommands: () => registerAnalyzeCommands
42899
43164
  });
42900
43165
  import { readFile as readFile28 } from "node:fs/promises";
42901
- import { extname as extname8, resolve as resolve39 } from "node:path";
43166
+ import { extname as extname9, resolve as resolve39 } from "node:path";
42902
43167
  import { existsSync as existsSync38 } from "node:fs";
42903
43168
  async function executeGeminiVideo(options) {
42904
43169
  try {
@@ -42955,7 +43220,7 @@ async function executeAnalyze(options) {
42955
43220
  const isYouTube = source.includes("youtube.com") || source.includes("youtu.be");
42956
43221
  const isImageUrl = /^https?:\/\/.+\.(png|jpg|jpeg|webp|gif)(\?.*)?$/i.test(source);
42957
43222
  const isVideoUrl = /^https?:\/\/.+\.(mp4|mov|webm)(\?.*)?$/i.test(source);
42958
- const ext = extname8(source).toLowerCase();
43223
+ const ext = extname9(source).toLowerCase();
42959
43224
  const imageExts = [".png", ".jpg", ".jpeg", ".webp", ".gif"];
42960
43225
  const videoExts = [".mp4", ".mov", ".webm", ".avi", ".mkv"];
42961
43226
  const isLocalImage = imageExts.includes(ext);
@@ -43363,7 +43628,7 @@ async function executeSoundEffect(options) {
43363
43628
  }
43364
43629
  }
43365
43630
  function registerSoundEffectCommand(parent) {
43366
- parent.command("sound-effect").description("Generate sound effect using ElevenLabs").argument("<prompt>", "Description of the sound effect").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "sound-effect.mp3").option("-d, --duration <seconds>", "Duration in seconds (0.5-22, default: auto)").option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
43631
+ parent.command("sound-effect").description("Generate sound effect using ElevenLabs").argument("<prompt>", "Description of the sound effect").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "sound-effect.mp3").option("-d, --duration <seconds>", "Duration in seconds (0.5-30, default: auto)").option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
43367
43632
  const startedAt = Date.now();
43368
43633
  try {
43369
43634
  rejectControlChars(prompt3);
@@ -43663,9 +43928,9 @@ var init_speech = __esm({
43663
43928
  });
43664
43929
 
43665
43930
  // src/commands/generate/thumbnail.ts
43666
- import { resolve as resolve47, dirname as dirname18, basename as basename13, extname as extname9 } from "node:path";
43931
+ import { resolve as resolve47, dirname as dirname19, basename as basename13, extname as extname10 } from "node:path";
43667
43932
  import { existsSync as existsSync42 } from "node:fs";
43668
- import { writeFile as writeFile33, mkdir as mkdir21 } from "node:fs/promises";
43933
+ import { writeFile as writeFile33, mkdir as mkdir22 } from "node:fs/promises";
43669
43934
  function registerThumbnailCommand(parent) {
43670
43935
  parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("--style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", `Gemini model: ${GEMINI_TEXT_MODEL_HELP}`, "flash").action(async (description, options) => {
43671
43936
  const startedAt = Date.now();
@@ -43688,7 +43953,7 @@ function registerThumbnailCommand(parent) {
43688
43953
  );
43689
43954
  }
43690
43955
  const apiKey2 = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
43691
- const name = basename13(options.bestFrame, extname9(options.bestFrame));
43956
+ const name = basename13(options.bestFrame, extname10(options.bestFrame));
43692
43957
  const outputPath = options.output || `${name}-thumbnail.png`;
43693
43958
  const spinner3 = ora("Analyzing video for best frame...").start();
43694
43959
  const result2 = await executeThumbnailBestFrame({
@@ -43756,7 +44021,7 @@ function registerThumbnailCommand(parent) {
43756
44021
  throw new Error("No image data available");
43757
44022
  }
43758
44023
  outputPath = resolve47(process.cwd(), options.output);
43759
- await mkdir21(dirname18(outputPath), { recursive: true });
44024
+ await mkdir22(dirname19(outputPath), { recursive: true });
43760
44025
  await writeFile33(outputPath, buffer);
43761
44026
  }
43762
44027
  outputSuccess({
@@ -43787,7 +44052,7 @@ function registerThumbnailCommand(parent) {
43787
44052
  throw new Error("No image data available");
43788
44053
  }
43789
44054
  const outputPath = resolve47(process.cwd(), options.output);
43790
- await mkdir21(dirname18(outputPath), { recursive: true });
44055
+ await mkdir22(dirname19(outputPath), { recursive: true });
43791
44056
  await writeFile33(outputPath, buffer);
43792
44057
  saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
43793
44058
  } catch {
@@ -43818,7 +44083,7 @@ var init_thumbnail = __esm({
43818
44083
  import { resolve as resolve48 } from "node:path";
43819
44084
  import { writeFile as writeFile34 } from "node:fs/promises";
43820
44085
  function registerVideoExtendCommand(parent) {
43821
- 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) => {
44086
+ 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.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) => {
43822
44087
  const startedAt = Date.now();
43823
44088
  try {
43824
44089
  const provider = (options.provider || "kling").toLowerCase();
@@ -43936,12 +44201,7 @@ function registerVideoExtendCommand(parent) {
43936
44201
  const spinner2 = ora("Initializing Veo...").start();
43937
44202
  const gemini = new GeminiProvider();
43938
44203
  await gemini.initialize({ apiKey });
43939
- const veoModelMap = {
43940
- "3.0": "veo-3.0-generate-preview",
43941
- "3.1": "veo-3.1-generate-preview",
43942
- "3.1-fast": "veo-3.1-fast-generate-preview"
43943
- };
43944
- const veoModel = veoModelMap[options.veoModel] || "veo-3.1-generate-preview";
44204
+ const veoModel = resolveVeoModel(options.veoModel);
43945
44205
  spinner2.text = "Starting video extension...";
43946
44206
  const result = await gemini.extendVideo(id, options.prompt, {
43947
44207
  duration: parseInt(options.duration),
@@ -44102,11 +44362,8 @@ var init_provider_resolver = __esm({
44102
44362
 
44103
44363
  // src/commands/_shared/openai-image.ts
44104
44364
  function resolveOpenAIImageModel(modelAlias) {
44105
- const isGptImage15 = modelAlias === "1.5" || modelAlias === "gpt-image-1.5";
44106
- return {
44107
- openaiModel: isGptImage15 ? "gpt-image-1.5" : "gpt-image-2",
44108
- modelLabel: isGptImage15 ? "GPT Image 1.5" : "GPT Image 2"
44109
- };
44365
+ const model = findModel("openai", "image", modelAlias) ?? defaultModel("openai", "image");
44366
+ return { openaiModel: model.id, modelLabel: model.label };
44110
44367
  }
44111
44368
  async function executeOpenAIImageGenerate(prompt3, options, ctx) {
44112
44369
  const provider = new OpenAIImageProvider();
@@ -44128,10 +44385,44 @@ var init_openai_image2 = __esm({
44128
44385
  }
44129
44386
  });
44130
44387
 
44388
+ // src/commands/_shared/image-cost.ts
44389
+ function catalogPrice(provider, model) {
44390
+ if (provider === "openai") return findModel("openai", "image", resolveOpenAIImageModel(model).openaiModel)?.price?.usd;
44391
+ if (provider === "gemini") return findModel("gemini", "image", model ?? "flash")?.price?.usd ?? findModel("gemini", "image")?.price?.usd;
44392
+ if (provider === "grok") return findModel("grok", "image", resolveGrokImageModel(model).model)?.price?.usd;
44393
+ return void 0;
44394
+ }
44395
+ function estimateImageCostUsd(provider, model, count) {
44396
+ const images = Math.max(1, count);
44397
+ const perImage = catalogPrice(provider, model);
44398
+ if (perImage === void 0) {
44399
+ return {
44400
+ costUsd: lookupCostEstimateUpperBound("generate image") ?? 5,
44401
+ warnings: [
44402
+ `costUsd is the tier upper bound for ${provider} - actual provider billing is typically lower and is not metered here.`
44403
+ ]
44404
+ };
44405
+ }
44406
+ return {
44407
+ costUsd: Number((perImage * images).toFixed(3)),
44408
+ warnings: [
44409
+ `costUsd is an upper-bound estimate from ${provider} list prices for ${images} image(s) - not metered billing.`
44410
+ ]
44411
+ };
44412
+ }
44413
+ var init_image_cost = __esm({
44414
+ "src/commands/_shared/image-cost.ts"() {
44415
+ "use strict";
44416
+ init_dist2();
44417
+ init_output();
44418
+ init_openai_image2();
44419
+ }
44420
+ });
44421
+
44131
44422
  // src/commands/generate/image.ts
44132
- import { resolve as resolve49, dirname as dirname19 } from "node:path";
44423
+ import { resolve as resolve49, dirname as dirname20 } from "node:path";
44133
44424
  import { fileURLToPath } from "node:url";
44134
- import { writeFile as writeFile35, mkdir as mkdir22 } from "node:fs/promises";
44425
+ import { mkdir as mkdir23 } from "node:fs/promises";
44135
44426
  function registerImageCommand(parent) {
44136
44427
  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(
44137
44428
  "-p, --provider <provider>",
@@ -44142,7 +44433,7 @@ function registerImageCommand(parent) {
44142
44433
  "1:1"
44143
44434
  ).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(
44144
44435
  "-m, --model <model>",
44145
- "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 2 (default), 1.5"
44436
+ "Model. Gemini: flash (default), lite, pro. OpenAI: 2.5 (default), flare, 2, 1.5. Grok: pro"
44146
44437
  ).option("--dry-run", "Preview parameters without executing").addHelpText(
44147
44438
  "after",
44148
44439
  `
@@ -44225,6 +44516,7 @@ Examples:
44225
44516
  command: "generate image",
44226
44517
  startedAt,
44227
44518
  dryRun: true,
44519
+ ...estimateImageCostUsd(provider, options.model, parseInt(options.count) || 1),
44228
44520
  data: {
44229
44521
  params: {
44230
44522
  prompt: prompt3,
@@ -44245,7 +44537,7 @@ Examples:
44245
44537
  const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
44246
44538
  const spinner2 = ora(`Generating image with ${providerName}...`).start();
44247
44539
  if (provider === "openai") {
44248
- const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, {
44540
+ const { result, modelLabel, openaiModel } = await executeOpenAIImageGenerate(prompt3, options, {
44249
44541
  apiKey
44250
44542
  });
44251
44543
  if (!result.success || !result.images) {
@@ -44268,14 +44560,17 @@ Examples:
44268
44560
  } else {
44269
44561
  throw new Error("No image data available");
44270
44562
  }
44271
- await mkdir22(dirname19(outputPath), { recursive: true });
44272
- await writeFile35(outputPath, buffer);
44563
+ await mkdir23(dirname20(outputPath), { recursive: true });
44564
+ await writeImageFile(outputPath, buffer);
44273
44565
  }
44274
44566
  outputSuccess({
44275
44567
  command: "generate image",
44276
44568
  startedAt,
44569
+ ...estimateImageCostUsd("openai", options.model, result.images.length),
44277
44570
  data: {
44278
44571
  provider: "openai",
44572
+ model: modelLabel,
44573
+ modelId: openaiModel,
44279
44574
  images: result.images.map((img) => ({
44280
44575
  url: img.url,
44281
44576
  revisedPrompt: img.revisedPrompt
@@ -44315,8 +44610,8 @@ Examples:
44315
44610
  throw new Error("No image data available");
44316
44611
  }
44317
44612
  const outputPath = resolve49(process.cwd(), options.output);
44318
- await mkdir22(dirname19(outputPath), { recursive: true });
44319
- await writeFile35(outputPath, buffer);
44613
+ await mkdir23(dirname20(outputPath), { recursive: true });
44614
+ await writeImageFile(outputPath, buffer);
44320
44615
  saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
44321
44616
  } catch (err) {
44322
44617
  saveSpinner.fail(
@@ -44325,7 +44620,7 @@ Examples:
44325
44620
  }
44326
44621
  }
44327
44622
  } else if (provider === "gemini") {
44328
- const validGeminiModels = ["flash", "3.1-flash", "latest", "pro"];
44623
+ const validGeminiModels = ["flash", "3.1-flash", "latest", "lite", "pro"];
44329
44624
  if (options.model && !validGeminiModels.includes(options.model)) {
44330
44625
  console.warn(
44331
44626
  source_default.yellow(
@@ -44358,26 +44653,17 @@ Examples:
44358
44653
  const gemini = new GeminiProvider();
44359
44654
  await gemini.initialize({ apiKey });
44360
44655
  const geminiModelNames = {
44361
- flash: "Nano Banana",
44656
+ flash: "Nano Banana 2",
44362
44657
  "3.1-flash": "Nano Banana 2",
44363
44658
  latest: "Nano Banana 2",
44659
+ lite: "Nano Banana 2 Lite",
44364
44660
  pro: "Nano Banana Pro"
44365
44661
  };
44366
- const modelLabel = geminiModelNames[options.model] || "Nano Banana";
44367
- let result = await gemini.generateImage(prompt3, {
44662
+ const usedLabel = geminiModelNames[options.model] || "Nano Banana 2";
44663
+ const result = await gemini.generateImage(prompt3, {
44368
44664
  model: options.model,
44369
44665
  aspectRatio: options.ratio
44370
44666
  });
44371
- let usedLabel = modelLabel;
44372
- const fallbackModels = ["latest", "3.1-flash"];
44373
- if (!result.success && options.model && fallbackModels.includes(options.model)) {
44374
- spinner2.text = `${source_default.dim(result.error || "Failed")} \u2014 retrying with Nano Banana (flash)...`;
44375
- result = await gemini.generateImage(prompt3, {
44376
- model: "flash",
44377
- aspectRatio: options.ratio
44378
- });
44379
- usedLabel = "Nano Banana (fallback)";
44380
- }
44381
44667
  if (!result.success || !result.images) {
44382
44668
  spinner2.fail(result.error || "Image generation failed");
44383
44669
  exitWithError(apiError(result.error || "Image generation failed", true));
@@ -44390,16 +44676,17 @@ Examples:
44390
44676
  if (outputPath && result.images.length > 0) {
44391
44677
  const img = result.images[0];
44392
44678
  const buffer = Buffer.from(img.base64, "base64");
44393
- await mkdir22(dirname19(outputPath), { recursive: true });
44394
- await writeFile35(outputPath, buffer);
44679
+ await mkdir23(dirname20(outputPath), { recursive: true });
44680
+ await writeImageFile(outputPath, buffer);
44395
44681
  }
44396
44682
  outputSuccess({
44397
44683
  command: "generate image",
44398
44684
  startedAt,
44399
- warnings: usedLabel.includes("fallback") ? [`Model "${options.model}" failed; fell back to flash`] : [],
44685
+ ...estimateImageCostUsd("gemini", options.model, result.images.length),
44400
44686
  data: {
44401
44687
  provider: "gemini",
44402
44688
  model: usedLabel,
44689
+ modelId: result.model,
44403
44690
  images: result.images.map((img) => ({
44404
44691
  mimeType: img.mimeType
44405
44692
  })),
@@ -44423,8 +44710,8 @@ Examples:
44423
44710
  const img = result.images[0];
44424
44711
  const buffer = Buffer.from(img.base64, "base64");
44425
44712
  const outputPath = resolve49(process.cwd(), options.output);
44426
- await mkdir22(dirname19(outputPath), { recursive: true });
44427
- await writeFile35(outputPath, buffer);
44713
+ await mkdir23(dirname20(outputPath), { recursive: true });
44714
+ await writeImageFile(outputPath, buffer);
44428
44715
  saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
44429
44716
  } catch (err) {
44430
44717
  saveSpinner.fail(
@@ -44462,6 +44749,7 @@ Examples:
44462
44749
  options.ratio = "1:1";
44463
44750
  }
44464
44751
  const result = await grok.generateImage(prompt3, {
44752
+ ...resolveGrokImageModel(options.model),
44465
44753
  aspectRatio: options.ratio || "1:1",
44466
44754
  n: parseInt(options.count)
44467
44755
  });
@@ -44483,14 +44771,16 @@ Examples:
44483
44771
  } else {
44484
44772
  throw new Error("No image data available");
44485
44773
  }
44486
- await mkdir22(dirname19(outputPath), { recursive: true });
44487
- await writeFile35(outputPath, buffer);
44774
+ await mkdir23(dirname20(outputPath), { recursive: true });
44775
+ await writeImageFile(outputPath, buffer);
44488
44776
  }
44489
44777
  outputSuccess({
44490
44778
  command: "generate image",
44491
44779
  startedAt,
44780
+ ...estimateImageCostUsd("grok", options.model, result.images.length),
44492
44781
  data: {
44493
44782
  provider: "grok",
44783
+ modelId: resolveGrokImageModel(options.model).model,
44494
44784
  images: result.images.map((img) => ({ url: img.url })),
44495
44785
  outputPath
44496
44786
  }
@@ -44524,8 +44814,8 @@ Examples:
44524
44814
  throw new Error("No image data available");
44525
44815
  }
44526
44816
  const outputPath = resolve49(process.cwd(), options.output);
44527
- await mkdir22(dirname19(outputPath), { recursive: true });
44528
- await writeFile35(outputPath, buffer);
44817
+ await mkdir23(dirname20(outputPath), { recursive: true });
44818
+ await writeImageFile(outputPath, buffer);
44529
44819
  saveSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
44530
44820
  } catch (err) {
44531
44821
  saveSpinner.fail(
@@ -44536,7 +44826,7 @@ Examples:
44536
44826
  } else if (provider === "runway") {
44537
44827
  const { spawn: spawn2 } = await import("child_process");
44538
44828
  const __filename = fileURLToPath(import.meta.url);
44539
- const __dirname = dirname19(__filename);
44829
+ const __dirname = dirname20(__filename);
44540
44830
  const scriptPath = resolve49(
44541
44831
  __dirname,
44542
44832
  "../../../../.claude/skills/runway-video/scripts/image.py"
@@ -44607,6 +44897,8 @@ var init_image = __esm({
44607
44897
  init_validate();
44608
44898
  init_provider_resolver();
44609
44899
  init_openai_image2();
44900
+ init_image_cost();
44901
+ init_image_file();
44610
44902
  }
44611
44903
  });
44612
44904
 
@@ -45555,11 +45847,11 @@ var init_dist3 = __esm({
45555
45847
 
45556
45848
  // src/commands/generate/video.ts
45557
45849
  import { resolve as resolve50 } from "node:path";
45558
- import { readFile as readFile33, writeFile as writeFile36 } from "node:fs/promises";
45850
+ import { readFile as readFile33, writeFile as writeFile35 } from "node:fs/promises";
45559
45851
  function registerVideoCommand(parent) {
45560
45852
  parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, Veo, or Gemini Omni)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
45561
45853
  "-p, --provider <provider>",
45562
- "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo, omni (Gemini Omni, experimental). `fal` is a deprecated v0.x alias for seedance and will be removed in 1.0."
45854
+ "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, omni (Gemini Omni 1.1 Flash; Google default), veo (Veo 3.1 preview, shuts down 2026-10-22). `fal` is a deprecated v0.x alias for seedance and will be removed in 1.0."
45563
45855
  ).option(
45564
45856
  "-k, --api-key <key>",
45565
45857
  "API key (or set FAL_API_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
@@ -45572,12 +45864,12 @@ function registerVideoCommand(parent) {
45572
45864
  "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)"
45573
45865
  ).option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option(
45574
45866
  "--seedance-model <model>",
45575
- "Seedance variant: quality or fast (fal.ai only)",
45867
+ "Seedance variant: quality (2.0), fast, or 2.5 (fal.ai only)",
45576
45868
  "quality"
45577
- ).option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 480p, 720p, 1080p, or 4k depending on provider").option("--last-frame <path>", "Last frame image for frame interpolation (Veo) or Seedance end frame").option("--end-image <path>", "Ending frame image for Seedance image-to-video").option(
45869
+ ).option("--grok-model <model>", "Grok video model: 1.5 (default), lite, classic", "1.5").option("--kling-model <model>", "Kling model: v3 (default), v2.6, v2.5-turbo", "v3").option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 480p, 720p, 1080p, or 4k depending on provider").option("--last-frame <path>", "Last frame image for frame interpolation (Veo) or Seedance end frame").option("--end-image <path>", "Ending frame image for Seedance image-to-video").option(
45578
45870
  "--ref-images <paths...>",
45579
45871
  "Reference images for Seedance reference-to-video or Veo character consistency"
45580
- ).option("--ref-videos <paths...>", "Reference videos for Seedance reference-to-video").option("--ref-audio <paths...>", "Reference audio for Seedance reference-to-video").option("--no-generate-audio", "Disable native audio when the provider supports it").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(
45872
+ ).option("--ref-videos <paths...>", "Reference videos for Seedance reference-to-video").option("--ref-audio <paths...>", "Reference audio for Seedance reference-to-video").option("--no-generate-audio", "Disable native audio when the provider supports it").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option(
45581
45873
  "--runway-model <model>",
45582
45874
  "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)",
45583
45875
  "gen4.5"
@@ -45638,7 +45930,7 @@ Examples:
45638
45930
  exitWithError(
45639
45931
  usageError(
45640
45932
  `Invalid provider: ${provider}`,
45641
- "Available providers: seedance, grok, kling, runway, veo, omni (experimental). `fal` is a deprecated alias for seedance."
45933
+ "Available providers: seedance, grok, kling, runway, omni, veo. `fal` is a deprecated alias for seedance."
45642
45934
  )
45643
45935
  );
45644
45936
  }
@@ -45705,14 +45997,16 @@ Examples:
45705
45997
  log(`${provider} does not support 1:1 \u2014 falling back to 16:9`);
45706
45998
  options.ratio = "16:9";
45707
45999
  }
46000
+ try {
46001
+ if (provider === "seedance" || provider === "fal") resolveSeedanceVariant(options.seedanceModel);
46002
+ if (provider === "grok") resolveGrokVideoModel(options.grokModel);
46003
+ if (provider === "kling") resolveKlingModel(options.klingModel);
46004
+ if (provider === "veo") resolveVeoModel(options.veoModel);
46005
+ } catch (err) {
46006
+ exitWithError(usageError(err instanceof Error ? err.message : String(err)));
46007
+ }
45708
46008
  if (options.dryRun) {
45709
- const costUsd = provider === "seedance" || provider === "fal" ? estimateSeedanceVideoCostUsd({
45710
- durationSec: Number(options.duration) || 5,
45711
- resolution: options.resolution,
45712
- aspectRatio: options.ratio,
45713
- fast: options.seedanceModel === "fast",
45714
- hasVideoReference: Boolean(options.refVideos)
45715
- }) : void 0;
46009
+ const costUsd = provider === "seedance" || provider === "fal" ? realRunCost(provider, options).costUsd : void 0;
45716
46010
  outputSuccess({
45717
46011
  command: "generate video",
45718
46012
  startedAt,
@@ -45730,6 +46024,8 @@ Examples:
45730
46024
  resolution: options.resolution,
45731
46025
  veoModel: options.veoModel,
45732
46026
  seedanceModel: options.seedanceModel,
46027
+ grokModel: options.grokModel,
46028
+ klingModel: options.klingModel,
45733
46029
  refImages: options.refImages,
45734
46030
  refVideos: options.refVideos,
45735
46031
  refAudio: options.refAudio,
@@ -45751,7 +46047,7 @@ Examples:
45751
46047
  runway: "Runway",
45752
46048
  kling: "Kling",
45753
46049
  veo: "Veo",
45754
- omni: "Gemini Omni (experimental)",
46050
+ omni: "Gemini Omni 1.1 Flash",
45755
46051
  grok: "Grok",
45756
46052
  seedance: "Seedance 2.0 via fal.ai"
45757
46053
  };
@@ -45850,6 +46146,7 @@ Examples:
45850
46146
  }
45851
46147
  result = await kling.generateVideo(prompt3, {
45852
46148
  prompt: prompt3,
46149
+ model: resolveKlingModel(options.klingModel),
45853
46150
  referenceImage: klingImage,
45854
46151
  duration: parseInt(options.duration),
45855
46152
  aspectRatio: options.ratio,
@@ -45899,12 +46196,7 @@ Examples:
45899
46196
  } else if (provider === "veo") {
45900
46197
  const gemini = new GeminiProvider();
45901
46198
  await gemini.initialize({ apiKey });
45902
- const veoModelMap = {
45903
- "3.0": "veo-3.0-generate-preview",
45904
- "3.1": "veo-3.1-generate-preview",
45905
- "3.1-fast": "veo-3.1-fast-generate-preview"
45906
- };
45907
- const veoModel = veoModelMap[options.veoModel] || "veo-3.1-fast-generate-preview";
46199
+ const veoModel = resolveVeoModel(options.veoModel);
45908
46200
  const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
45909
46201
  let lastFrame;
45910
46202
  if (options.lastFrame) {
@@ -45978,9 +46270,12 @@ Examples:
45978
46270
  await grok.initialize({ apiKey });
45979
46271
  result = await grok.generateVideo(prompt3, {
45980
46272
  prompt: prompt3,
46273
+ model: resolveGrokVideoModel(options.grokModel),
45981
46274
  referenceImage,
45982
46275
  duration: parseInt(options.duration),
45983
- aspectRatio: options.ratio
46276
+ aspectRatio: options.ratio,
46277
+ resolution: options.resolution,
46278
+ generateAudio: options.generateAudio
45984
46279
  });
45985
46280
  if (result.status === "failed") {
45986
46281
  spinner2.fail(result.error || "Failed to start generation");
@@ -46070,9 +46365,8 @@ Examples:
46070
46365
  exitWithError(apiError(message, true));
46071
46366
  }
46072
46367
  }
46073
- spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
46074
- const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
46075
- const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
46368
+ const falModel = resolveSeedanceVariant(options.seedanceModel);
46369
+ spinner2.text = `Generating video with fal.ai ${falModel} (this may take 1-3 minutes)...`;
46076
46370
  result = await fal.generateVideo(prompt3, {
46077
46371
  prompt: prompt3,
46078
46372
  referenceImage: seedanceReferences.length > 0 ? void 0 : falImage,
@@ -46089,11 +46383,20 @@ Examples:
46089
46383
  } else if (provider === "omni") {
46090
46384
  const omni = new OmniProvider();
46091
46385
  await omni.initialize({ apiKey });
46092
- spinner2.text = "Generating video with Gemini Omni (experimental)...";
46386
+ spinner2.text = "Generating video with Gemini Omni 1.1 Flash...";
46387
+ let lastFrame;
46388
+ if (options.lastFrame) {
46389
+ const lastFramePath = resolve50(process.cwd(), options.lastFrame);
46390
+ const ext = options.lastFrame.toLowerCase().split(".").pop();
46391
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
46392
+ lastFrame = `data:${mimeType};base64,${(await readFile33(lastFramePath)).toString("base64")}`;
46393
+ }
46093
46394
  result = await omni.generateVideo(prompt3, {
46094
46395
  prompt: prompt3,
46095
46396
  referenceImage,
46096
- aspectRatio: options.ratio
46397
+ lastFrame,
46398
+ aspectRatio: options.ratio,
46399
+ resolution: options.resolution
46097
46400
  });
46098
46401
  finalResult = result;
46099
46402
  }
@@ -46107,14 +46410,14 @@ Examples:
46107
46410
  if (options.output && finalResult.videoUrl) {
46108
46411
  const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
46109
46412
  outputPath = resolve50(process.cwd(), options.output);
46110
- await writeFile36(outputPath, buffer);
46413
+ await writeFile35(outputPath, buffer);
46111
46414
  }
46112
46415
  const cost = realRunCost(provider, options);
46113
46416
  outputSuccess({
46114
46417
  command: "generate video",
46115
46418
  startedAt,
46116
46419
  costUsd: cost.costUsd,
46117
- warnings: cost.warnings,
46420
+ warnings: provider === "veo" ? [VEO_SHUTDOWN_WARNING, ...cost.warnings] : cost.warnings,
46118
46421
  data: {
46119
46422
  provider,
46120
46423
  taskId: result?.id,
@@ -46126,6 +46429,9 @@ Examples:
46126
46429
  return;
46127
46430
  }
46128
46431
  console.log();
46432
+ if (provider === "veo") {
46433
+ console.log(source_default.yellow(VEO_SHUTDOWN_WARNING));
46434
+ }
46129
46435
  if (finalResult.videoUrl) {
46130
46436
  console.log(`Video URL: ${finalResult.videoUrl}`);
46131
46437
  }
@@ -46138,7 +46444,7 @@ Examples:
46138
46444
  try {
46139
46445
  const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
46140
46446
  const outputPath = resolve50(process.cwd(), options.output);
46141
- await writeFile36(outputPath, buffer);
46447
+ await writeFile35(outputPath, buffer);
46142
46448
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
46143
46449
  } catch (err) {
46144
46450
  downloadSpinner.fail(
@@ -46153,12 +46459,14 @@ Examples:
46153
46459
  }
46154
46460
  function realRunCost(provider, options) {
46155
46461
  if (provider === "seedance" || provider === "fal") {
46462
+ const variant = resolveSeedanceVariant(options.seedanceModel);
46156
46463
  return {
46157
46464
  costUsd: estimateSeedanceVideoCostUsd({
46158
46465
  durationSec: Number(options.duration) || 5,
46159
46466
  resolution: options.resolution,
46160
46467
  aspectRatio: options.ratio,
46161
- fast: options.seedanceModel === "fast",
46468
+ fast: variant === "seedance-2.0-fast",
46469
+ v25: variant === "seedance-2.5",
46162
46470
  hasVideoReference: Boolean(options.refVideos)
46163
46471
  }),
46164
46472
  warnings: []
@@ -46248,6 +46556,7 @@ function printVideoStarted(providerLabel, taskId, taskType) {
46248
46556
  console.log(`Task ID: ${source_default.bold(taskId)}`);
46249
46557
  if (taskType) console.log(`Type: ${taskType}`);
46250
46558
  }
46559
+ var VEO_SHUTDOWN_WARNING;
46251
46560
  var init_video = __esm({
46252
46561
  "src/commands/generate/video.ts"() {
46253
46562
  "use strict";
@@ -46263,6 +46572,7 @@ var init_video = __esm({
46263
46572
  init_upload_host();
46264
46573
  init_ai_helpers();
46265
46574
  init_status_jobs();
46575
+ VEO_SHUTDOWN_WARNING = "Veo 3.1 preview models shut down on 2026-10-22. Use `-p omni` (Gemini Omni 1.1 Flash) instead.";
46266
46576
  }
46267
46577
  });
46268
46578
 
@@ -46282,7 +46592,7 @@ var init_sanitize = __esm({
46282
46592
 
46283
46593
  // src/commands/generate/storyboard.ts
46284
46594
  import { resolve as resolve51 } from "node:path";
46285
- import { writeFile as writeFile37 } from "node:fs/promises";
46595
+ import { writeFile as writeFile36 } from "node:fs/promises";
46286
46596
  async function executeStoryboard(options) {
46287
46597
  const { content, duration, creativity = "low", output, apiKey } = options;
46288
46598
  try {
@@ -46301,7 +46611,7 @@ async function executeStoryboard(options) {
46301
46611
  let outputPath;
46302
46612
  if (output) {
46303
46613
  outputPath = resolve51(process.cwd(), output);
46304
- await writeFile37(outputPath, JSON.stringify(segments, null, 2), "utf-8");
46614
+ await writeFile36(outputPath, JSON.stringify(segments, null, 2), "utf-8");
46305
46615
  }
46306
46616
  return { success: true, segments, segmentCount: segments.length, outputPath };
46307
46617
  } catch (error) {
@@ -46417,10 +46727,11 @@ var OpenAIAdapter;
46417
46727
  var init_openai2 = __esm({
46418
46728
  "src/agent/adapters/openai.ts"() {
46419
46729
  "use strict";
46730
+ init_dist2();
46420
46731
  OpenAIAdapter = class {
46421
46732
  provider = "openai";
46422
46733
  client = null;
46423
- model = "gpt-5-mini";
46734
+ model = defaultModel("openai", "llm").id;
46424
46735
  async initialize(apiKey) {
46425
46736
  this.client = new OpenAI({ apiKey });
46426
46737
  }
@@ -46513,10 +46824,11 @@ var ClaudeAdapter;
46513
46824
  var init_claude2 = __esm({
46514
46825
  "src/agent/adapters/claude.ts"() {
46515
46826
  "use strict";
46827
+ init_dist2();
46516
46828
  ClaudeAdapter = class {
46517
46829
  provider = "claude";
46518
46830
  client = null;
46519
- model = "claude-sonnet-4-6";
46831
+ model = defaultModel("claude", "llm").id;
46520
46832
  async initialize(apiKey) {
46521
46833
  this.client = new Anthropic({ apiKey });
46522
46834
  }
@@ -46541,6 +46853,13 @@ var init_claude2 = __esm({
46541
46853
  content: msg.content
46542
46854
  });
46543
46855
  } else if (msg.role === "assistant") {
46856
+ if (msg.providerContent) {
46857
+ claudeMessages.push({
46858
+ role: "assistant",
46859
+ content: msg.providerContent
46860
+ });
46861
+ continue;
46862
+ }
46544
46863
  const content = [];
46545
46864
  if (msg.content) {
46546
46865
  content.push({ type: "text", text: msg.content });
@@ -46562,16 +46881,17 @@ var init_claude2 = __esm({
46562
46881
  });
46563
46882
  }
46564
46883
  } else if (msg.role === "tool") {
46565
- claudeMessages.push({
46566
- role: "user",
46567
- content: [
46568
- {
46569
- type: "tool_result",
46570
- tool_use_id: msg.toolCallId,
46571
- content: msg.content
46572
- }
46573
- ]
46574
- });
46884
+ const result = {
46885
+ type: "tool_result",
46886
+ tool_use_id: msg.toolCallId,
46887
+ content: msg.content
46888
+ };
46889
+ const last = claudeMessages.at(-1);
46890
+ if (last?.role === "user" && Array.isArray(last.content)) {
46891
+ last.content.push(result);
46892
+ } else {
46893
+ claudeMessages.push({ role: "user", content: [result] });
46894
+ }
46575
46895
  }
46576
46896
  }
46577
46897
  const claudeTools = tools.map((tool) => ({
@@ -46585,7 +46905,8 @@ var init_claude2 = __esm({
46585
46905
  }));
46586
46906
  const response = await this.client.messages.create({
46587
46907
  model: this.model,
46588
- max_tokens: 4096,
46908
+ // Thinking tokens count against this cap on Claude 5.x.
46909
+ max_tokens: 16e3,
46589
46910
  system: systemPrompt,
46590
46911
  messages: claudeMessages,
46591
46912
  tools: claudeTools.length > 0 ? claudeTools : void 0
@@ -46612,7 +46933,8 @@ var init_claude2 = __esm({
46612
46933
  return {
46613
46934
  content: textContent,
46614
46935
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
46615
- finishReason
46936
+ finishReason,
46937
+ providerContent: response.content
46616
46938
  };
46617
46939
  }
46618
46940
  };
@@ -46622,17 +46944,51 @@ var init_claude2 = __esm({
46622
46944
  // src/agent/adapters/gemini.ts
46623
46945
  var gemini_exports = {};
46624
46946
  __export(gemini_exports, {
46625
- GeminiAdapter: () => GeminiAdapter
46947
+ GeminiAdapter: () => GeminiAdapter,
46948
+ toGeminiSchema: () => toGeminiSchema
46626
46949
  });
46627
46950
  import { GoogleGenerativeAI, SchemaType } from "@google/generative-ai";
46628
- var GeminiAdapter;
46951
+ function toGeminiSchema(schema) {
46952
+ if (Array.isArray(schema)) return schema.map(toGeminiSchema);
46953
+ if (!schema || typeof schema !== "object") return schema;
46954
+ const out = {};
46955
+ for (const [key, value] of Object.entries(schema)) {
46956
+ if (!GEMINI_SCHEMA_KEYS.has(key)) continue;
46957
+ if (key === "properties" && value && typeof value === "object") {
46958
+ out.properties = Object.fromEntries(
46959
+ Object.entries(value).map(([name, prop]) => [name, toGeminiSchema(prop)])
46960
+ );
46961
+ } else if (key === "items") {
46962
+ out.items = toGeminiSchema(value);
46963
+ } else {
46964
+ out[key] = value;
46965
+ }
46966
+ }
46967
+ return out;
46968
+ }
46969
+ var GEMINI_SCHEMA_KEYS, GeminiAdapter;
46629
46970
  var init_gemini2 = __esm({
46630
46971
  "src/agent/adapters/gemini.ts"() {
46631
46972
  "use strict";
46973
+ init_dist2();
46974
+ GEMINI_SCHEMA_KEYS = /* @__PURE__ */ new Set([
46975
+ "type",
46976
+ "format",
46977
+ "description",
46978
+ "nullable",
46979
+ "enum",
46980
+ "items",
46981
+ "properties",
46982
+ "required",
46983
+ "minItems",
46984
+ "maxItems",
46985
+ "minimum",
46986
+ "maximum"
46987
+ ]);
46632
46988
  GeminiAdapter = class {
46633
46989
  provider = "gemini";
46634
46990
  client = null;
46635
- model = "gemini-2.5-flash";
46991
+ model = GEMINI_AGENT_DEFAULT_TEXT_MODEL;
46636
46992
  async initialize(apiKey) {
46637
46993
  this.client = new GoogleGenerativeAI(apiKey);
46638
46994
  }
@@ -46654,7 +47010,12 @@ var init_gemini2 = __esm({
46654
47010
  description: tool.description,
46655
47011
  parameters: {
46656
47012
  type: SchemaType.OBJECT,
46657
- properties: tool.parameters.properties,
47013
+ properties: Object.fromEntries(
47014
+ Object.entries(tool.parameters.properties).map(([name, prop]) => [
47015
+ name,
47016
+ toGeminiSchema(prop)
47017
+ ])
47018
+ ),
46658
47019
  required: tool.parameters.required
46659
47020
  }
46660
47021
  }))
@@ -46673,6 +47034,10 @@ var init_gemini2 = __esm({
46673
47034
  parts: [{ text: msg.content }]
46674
47035
  });
46675
47036
  } else if (msg.role === "assistant") {
47037
+ if (msg.providerContent) {
47038
+ geminiContents.push({ role: "model", parts: msg.providerContent });
47039
+ continue;
47040
+ }
46676
47041
  const parts = [];
46677
47042
  if (msg.content) {
46678
47043
  parts.push({ text: msg.content });
@@ -46694,16 +47059,19 @@ var init_gemini2 = __esm({
46694
47059
  });
46695
47060
  }
46696
47061
  } else if (msg.role === "tool") {
46697
- geminiContents.push({
46698
- role: "user",
46699
- parts: [{
46700
- functionResponse: {
46701
- name: msg.toolCallId.split(":::")[0] || "unknown",
46702
- // Extract function name from id
46703
- response: { result: msg.content }
46704
- }
46705
- }]
46706
- });
47062
+ const part = {
47063
+ functionResponse: {
47064
+ name: msg.toolCallId.split(":::")[0] || "unknown",
47065
+ // Extract function name from id
47066
+ response: { result: msg.content }
47067
+ }
47068
+ };
47069
+ const last = geminiContents.at(-1);
47070
+ if (last?.role === "user" && last.parts.every((p) => "functionResponse" in p)) {
47071
+ last.parts.push(part);
47072
+ } else {
47073
+ geminiContents.push({ role: "user", parts: [part] });
47074
+ }
46707
47075
  }
46708
47076
  }
46709
47077
  const result = await model.generateContent({
@@ -46740,7 +47108,8 @@ var init_gemini2 = __esm({
46740
47108
  return {
46741
47109
  content: textContent,
46742
47110
  toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
46743
- finishReason
47111
+ finishReason,
47112
+ providerContent: candidate.content.parts
46744
47113
  };
46745
47114
  }
46746
47115
  };
@@ -46940,10 +47309,11 @@ var XAIAdapter;
46940
47309
  var init_xai = __esm({
46941
47310
  "src/agent/adapters/xai.ts"() {
46942
47311
  "use strict";
47312
+ init_dist2();
46943
47313
  XAIAdapter = class {
46944
47314
  provider = "xai";
46945
47315
  client = null;
46946
- model = "grok-4-1-fast-reasoning";
47316
+ model = defaultModel("grok", "llm").id;
46947
47317
  async initialize(apiKey) {
46948
47318
  this.client = new OpenAI2({
46949
47319
  apiKey,
@@ -47243,7 +47613,7 @@ __export(detect_exports, {
47243
47613
  executeDetectScenes: () => executeDetectScenes,
47244
47614
  executeDetectSilence: () => executeDetectSilence
47245
47615
  });
47246
- import { readFile as readFile38, writeFile as writeFile41 } from "node:fs/promises";
47616
+ import { readFile as readFile38, writeFile as writeFile40 } from "node:fs/promises";
47247
47617
  import { resolve as resolve56, basename as basename15 } from "node:path";
47248
47618
  async function executeDetectScenes(options) {
47249
47619
  try {
@@ -47282,7 +47652,7 @@ async function executeDetectScenes(options) {
47282
47652
  }));
47283
47653
  if (options.outputPath) {
47284
47654
  const outputPath = resolve56(process.cwd(), options.outputPath);
47285
- await writeFile41(outputPath, JSON.stringify({ source: absPath, totalDuration, threshold, scenes: result }, null, 2), "utf-8");
47655
+ await writeFile40(outputPath, JSON.stringify({ source: absPath, totalDuration, threshold, scenes: result }, null, 2), "utf-8");
47286
47656
  }
47287
47657
  return { success: true, scenes: result, totalDuration };
47288
47658
  } catch (error) {
@@ -47329,7 +47699,7 @@ async function executeDetectSilence(options) {
47329
47699
  }
47330
47700
  if (options.outputPath) {
47331
47701
  const outputPath = resolve56(process.cwd(), options.outputPath);
47332
- await writeFile41(outputPath, JSON.stringify({ source: absPath, silences }, null, 2), "utf-8");
47702
+ await writeFile40(outputPath, JSON.stringify({ source: absPath, silences }, null, 2), "utf-8");
47333
47703
  }
47334
47704
  return { success: true, silences };
47335
47705
  } catch (error) {
@@ -47378,7 +47748,7 @@ async function executeDetectBeats(options) {
47378
47748
  }
47379
47749
  if (options.outputPath) {
47380
47750
  const outputPath = resolve56(process.cwd(), options.outputPath);
47381
- await writeFile41(outputPath, JSON.stringify({ source: absPath, beatCount: beats.length, beats }, null, 2), "utf-8");
47751
+ await writeFile40(outputPath, JSON.stringify({ source: absPath, beatCount: beats.length, beats }, null, 2), "utf-8");
47382
47752
  }
47383
47753
  return { success: true, beats, beatCount: beats.length };
47384
47754
  } catch (error) {
@@ -47482,7 +47852,7 @@ var init_detect = __esm({
47482
47852
  duration: (i < scenes.length - 1 ? scenes[i + 1].timestamp : totalDuration) - s.timestamp
47483
47853
  }))
47484
47854
  };
47485
- await writeFile41(outputPath, JSON.stringify(result, null, 2), "utf-8");
47855
+ await writeFile40(outputPath, JSON.stringify(result, null, 2), "utf-8");
47486
47856
  console.log(source_default.green(`Saved to: ${outputPath}`));
47487
47857
  }
47488
47858
  if (options.project) {
@@ -47512,7 +47882,7 @@ var init_detect = __esm({
47512
47882
  sourceEndOffset: end
47513
47883
  });
47514
47884
  }
47515
- await writeFile41(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
47885
+ await writeFile40(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
47516
47886
  console.log(source_default.green(`Added ${scenes.length} clips to project`));
47517
47887
  }
47518
47888
  } catch (error) {
@@ -47594,7 +47964,7 @@ var init_detect = __esm({
47594
47964
  console.log();
47595
47965
  if (options.output) {
47596
47966
  const outputPath = resolve56(process.cwd(), options.output);
47597
- await writeFile41(
47967
+ await writeFile40(
47598
47968
  outputPath,
47599
47969
  JSON.stringify({ source: absPath, silences }, null, 2),
47600
47970
  "utf-8"
@@ -47679,7 +48049,7 @@ var init_detect = __esm({
47679
48049
  console.log();
47680
48050
  if (options.output) {
47681
48051
  const outputPath = resolve56(process.cwd(), options.output);
47682
- await writeFile41(
48052
+ await writeFile40(
47683
48053
  outputPath,
47684
48054
  JSON.stringify({ source: absPath, beatCount: beats.length, beats }, null, 2),
47685
48055
  "utf-8"
@@ -51762,8 +52132,9 @@ var EXPLICIT_COMMAND_METADATA = {
51762
52132
  schema: { surface: "agent", note: "Machine-readable command discovery." },
51763
52133
  completion: { surface: "advanced", note: "Shell ergonomics helper." },
51764
52134
  agent: {
51765
- surface: "advanced",
51766
- note: "Optional fallback REPL; external coding agents are the primary workflow."
52135
+ surface: "legacy",
52136
+ replacement: "a host agent (Claude Code, Codex, Cursor) or the MCP server via `vibe host setup`",
52137
+ note: "Deprecated built-in REPL, removed before 1.0."
51767
52138
  },
51768
52139
  "design.validate": { surface: "public", note: "Validate the DESIGN.md visual contract." },
51769
52140
  "generate.image": { surface: "public" },
@@ -52300,8 +52671,8 @@ init_tts_resolve();
52300
52671
  init_scene_project();
52301
52672
  init_project_config();
52302
52673
  init_cost_tier();
52303
- import { resolve as resolve15, relative as relative5, dirname as dirname4 } from "node:path";
52304
- import { mkdir as mkdir7, readFile as readFile13, writeFile as writeFile13, access as access4, copyFile as copyFile2 } from "node:fs/promises";
52674
+ import { resolve as resolve15, relative as relative5, dirname as dirname5 } from "node:path";
52675
+ import { mkdir as mkdir8, readFile as readFile13, writeFile as writeFile13, access as access4, copyFile as copyFile2 } from "node:fs/promises";
52305
52676
  import { existsSync as existsSync12 } from "node:fs";
52306
52677
 
52307
52678
  // src/commands/_shared/scene-html-emit.ts
@@ -52781,7 +53152,7 @@ init_storyboard_parse();
52781
53152
  init_storyboard_source();
52782
53153
  init_root_sync();
52783
53154
  import { existsSync as existsSync10 } from "node:fs";
52784
- import { mkdir as mkdir6, writeFile as writeFile11 } from "node:fs/promises";
53155
+ import { mkdir as mkdir7, writeFile as writeFile11 } from "node:fs/promises";
52785
53156
  import { join as join10, resolve as resolve12 } from "node:path";
52786
53157
  async function executeSceneSubmit(opts) {
52787
53158
  const projectDir = resolve12(opts.projectDir);
@@ -52875,7 +53246,7 @@ async function executeSceneSubmit(opts) {
52875
53246
  return { ...base, success: true };
52876
53247
  }
52877
53248
  const absPath = join10(projectDir, scenePath);
52878
- await mkdir6(join10(projectDir, "compositions"), { recursive: true });
53249
+ await mkdir7(join10(projectDir, "compositions"), { recursive: true });
52879
53250
  await writeFile11(absPath, html.endsWith("\n") ? html : `${html}
52880
53251
  `, "utf-8");
52881
53252
  return {
@@ -53466,7 +53837,7 @@ async function executeSceneAdd(opts) {
53466
53837
  }
53467
53838
  audioRelPath = `assets/narration-${id}.${ext}`;
53468
53839
  audioAbsPath = resolve15(projectDir, audioRelPath);
53469
- await mkdir7(dirname4(audioAbsPath), { recursive: true });
53840
+ await mkdir8(dirname5(audioAbsPath), { recursive: true });
53470
53841
  await copyFile2(sourceAbs, audioAbsPath);
53471
53842
  try {
53472
53843
  narrationDuration = await getAudioDuration(audioAbsPath);
@@ -53499,7 +53870,7 @@ async function executeSceneAdd(opts) {
53499
53870
  }
53500
53871
  audioRelPath = `assets/narration-${id}.${resolution.audioExtension}`;
53501
53872
  audioAbsPath = resolve15(projectDir, audioRelPath);
53502
- await mkdir7(dirname4(audioAbsPath), { recursive: true });
53873
+ await mkdir8(dirname5(audioAbsPath), { recursive: true });
53503
53874
  await writeFile13(audioAbsPath, tts.audioBuffer);
53504
53875
  try {
53505
53876
  narrationDuration = await getAudioDuration(audioAbsPath);
@@ -53562,7 +53933,7 @@ async function executeSceneAdd(opts) {
53562
53933
  const img = imageResult.images[0];
53563
53934
  imageRelPath = `assets/scene-${id}.png`;
53564
53935
  imageAbsPath = resolve15(projectDir, imageRelPath);
53565
- await mkdir7(dirname4(imageAbsPath), { recursive: true });
53936
+ await mkdir8(dirname5(imageAbsPath), { recursive: true });
53566
53937
  let buffer;
53567
53938
  if (img.base64) {
53568
53939
  buffer = Buffer.from(img.base64, "base64");
@@ -53589,7 +53960,7 @@ async function executeSceneAdd(opts) {
53589
53960
  }
53590
53961
  imageRelPath = `assets/scene-${id}.png`;
53591
53962
  imageAbsPath = resolve15(projectDir, imageRelPath);
53592
- await mkdir7(dirname4(imageAbsPath), { recursive: true });
53963
+ await mkdir8(dirname5(imageAbsPath), { recursive: true });
53593
53964
  const buffer = Buffer.from(imageResult.images[0].base64, "base64");
53594
53965
  await writeFile13(imageAbsPath, buffer);
53595
53966
  }
@@ -53608,7 +53979,7 @@ async function executeSceneAdd(opts) {
53608
53979
  }
53609
53980
  lottieRelPath = `assets/lottie-${id}.${ext}`;
53610
53981
  lottieAbsPath = resolve15(projectDir, lottieRelPath);
53611
- await mkdir7(dirname4(lottieAbsPath), { recursive: true });
53982
+ await mkdir8(dirname5(lottieAbsPath), { recursive: true });
53612
53983
  await copyFile2(sourceAbs, lottieAbsPath);
53613
53984
  lottieInput = {
53614
53985
  src: lottieRelPath,
@@ -53678,7 +54049,7 @@ async function executeSceneAdd(opts) {
53678
54049
  transcript: transcriptWords,
53679
54050
  lottie: lottieInput
53680
54051
  });
53681
- await mkdir7(dirname4(scenePath), { recursive: true });
54052
+ await mkdir8(dirname5(scenePath), { recursive: true });
53682
54053
  await writeFile13(scenePath, sceneHtml, "utf-8");
53683
54054
  opts.onProgress?.("Updating root composition...");
53684
54055
  let start = 0;
@@ -53895,6 +54266,7 @@ init_scene_build();
53895
54266
  init_install_skill();
53896
54267
  init_compose_prompts();
53897
54268
  init_scene_repair();
54269
+ init_build_video_providers();
53898
54270
  var SCENE_PRESETS2 = [
53899
54271
  "simple",
53900
54272
  "announcement",
@@ -54294,12 +54666,12 @@ var sceneBuildSchema = external_exports.object({
54294
54666
  "TTS provider override. Default 'auto' (elevenlabs key > openai key > local kokoro)."
54295
54667
  ),
54296
54668
  voice: external_exports.string().optional().describe("TTS voice id (provider-specific)."),
54297
- imageProvider: external_exports.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
54298
- videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
54669
+ imageProvider: external_exports.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2.5-sunburst)."),
54670
+ videoProvider: external_exports.enum(BUILD_VIDEO_PROVIDERS).optional().describe("Video provider for per-beat video cues. Default seedance."),
54299
54671
  musicProvider: external_exports.enum(["elevenlabs", "replicate"]).optional().describe("Music provider for per-beat music cues. Default elevenlabs."),
54300
54672
  imageQuality: external_exports.enum(["standard", "hd"]).optional().describe("OpenAI image quality. Default 'standard'."),
54301
54673
  imageModel: external_exports.string().optional().describe(
54302
- "Image model for keyframes/backdrops/character sheets (gemini: flash|pro, openai: gpt-image-2). Provider default when omitted."
54674
+ "Image model for keyframes/backdrops/character sheets (gemini: flash|lite|pro, openai: gpt-image-2.5-sunburst|gpt-image-2.5-flare|gpt-image-2). Provider default when omitted."
54303
54675
  ),
54304
54676
  imageSize: external_exports.enum(["1024x1024", "1536x1024", "1024x1536"]).optional().describe("OpenAI image size. Default '1536x1024' (cinematic 16:9-ish)."),
54305
54677
  maxCostUsd: external_exports.number().optional().describe("Fail before provider spend when estimated cost exceeds this USD cap."),
@@ -54729,8 +55101,8 @@ init_ai_edit();
54729
55101
  init_api_key();
54730
55102
  init_exec_safe();
54731
55103
  init_remotion();
54732
- import { resolve as resolve37, dirname as dirname16, basename as basename10 } from "node:path";
54733
- import { writeFile as writeFile28, mkdir as mkdir19, rm as rm5 } from "node:fs/promises";
55104
+ import { resolve as resolve37, dirname as dirname17, basename as basename10 } from "node:path";
55105
+ import { writeFile as writeFile29, mkdir as mkdir20, rm as rm6 } from "node:fs/promises";
54734
55106
  import { existsSync as existsSync36 } from "node:fs";
54735
55107
  import { tmpdir as tmpdir3 } from "node:os";
54736
55108
  var ASS_STYLES = ["karaoke-sweep", "typewriter"];
@@ -54882,7 +55254,7 @@ async function executeAnimatedCaption(options) {
54882
55254
  }
54883
55255
  const effectiveFontSize = fontSize ?? Math.round(height * 0.04);
54884
55256
  const tmpAudioDir = resolve37(tmpdir3(), `vf-ac-${Date.now()}`);
54885
- await mkdir19(tmpAudioDir, { recursive: true });
55257
+ await mkdir20(tmpAudioDir, { recursive: true });
54886
55258
  const audioPath = resolve37(tmpAudioDir, "audio.wav");
54887
55259
  await execSafe("ffmpeg", [
54888
55260
  "-y",
@@ -54899,21 +55271,21 @@ async function executeAnimatedCaption(options) {
54899
55271
  ], { timeout: 12e4 });
54900
55272
  const apiKey = await getApiKey("OPENAI_API_KEY", "OpenAI");
54901
55273
  if (!apiKey) {
54902
- await rm5(tmpAudioDir, { recursive: true, force: true }).catch(() => {
55274
+ await rm6(tmpAudioDir, { recursive: true, force: true }).catch(() => {
54903
55275
  });
54904
55276
  return { success: false, error: "OPENAI_API_KEY required for Whisper transcription. Run 'vibe setup' or set OPENAI_API_KEY in .env" };
54905
55277
  }
54906
55278
  const transcript = await transcribeWithWords(audioPath, apiKey, language);
54907
55279
  if (!transcript.words || transcript.words.length === 0) {
54908
- await rm5(tmpAudioDir, { recursive: true, force: true }).catch(() => {
55280
+ await rm6(tmpAudioDir, { recursive: true, force: true }).catch(() => {
54909
55281
  });
54910
55282
  return { success: false, error: "No words detected in transcription" };
54911
55283
  }
54912
55284
  const groups = groupWords(transcript.words, { wordsPerGroup, maxChars });
54913
55285
  const absOutputPath = resolve37(process.cwd(), outputPath);
54914
- const outDir = dirname16(absOutputPath);
55286
+ const outDir = dirname17(absOutputPath);
54915
55287
  if (!existsSync36(outDir)) {
54916
- await mkdir19(outDir, { recursive: true });
55288
+ await mkdir20(outDir, { recursive: true });
54917
55289
  }
54918
55290
  if (tier === "ass") {
54919
55291
  const assContent = generateASS(
@@ -54922,7 +55294,7 @@ async function executeAnimatedCaption(options) {
54922
55294
  { highlightColor, fontSize: effectiveFontSize, position, width, height }
54923
55295
  );
54924
55296
  const assPath = resolve37(tmpAudioDir, "captions.ass");
54925
- await writeFile28(assPath, assContent, "utf-8");
55297
+ await writeFile29(assPath, assContent, "utf-8");
54926
55298
  const escapedAssPath = assPath.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
54927
55299
  await execSafe("ffmpeg", [
54928
55300
  "-y",
@@ -54959,12 +55331,12 @@ async function executeAnimatedCaption(options) {
54959
55331
  outputPath: absOutputPath
54960
55332
  });
54961
55333
  if (!renderResult.success) {
54962
- await rm5(tmpAudioDir, { recursive: true, force: true }).catch(() => {
55334
+ await rm6(tmpAudioDir, { recursive: true, force: true }).catch(() => {
54963
55335
  });
54964
55336
  return { success: false, error: renderResult.error };
54965
55337
  }
54966
55338
  }
54967
- await rm5(tmpAudioDir, { recursive: true, force: true }).catch(() => {
55339
+ await rm6(tmpAudioDir, { recursive: true, force: true }).catch(() => {
54968
55340
  });
54969
55341
  return {
54970
55342
  success: true,
@@ -55373,7 +55745,7 @@ var editImageTool = defineTool({
55373
55745
  imagePaths: external_exports.array(external_exports.string()).describe("Input image file path(s)"),
55374
55746
  prompt: external_exports.string().describe("Edit instruction"),
55375
55747
  output: external_exports.string().optional().describe("Output file path (default: edited.png)"),
55376
- model: external_exports.enum(["flash", "3.1-flash", "latest", "pro"]).optional().describe("Gemini model (default: flash)"),
55748
+ model: external_exports.enum(["flash", "3.1-flash", "latest", "lite", "pro"]).optional().describe("Gemini model (default: flash = Nano Banana 2)"),
55377
55749
  ratio: external_exports.string().optional().describe("Output aspect ratio"),
55378
55750
  resolution: external_exports.string().optional().describe("Resolution: 1K, 2K, 4K (Pro only)")
55379
55751
  }),
@@ -57224,7 +57596,7 @@ var generateSoundEffectTool = defineTool({
57224
57596
  schema: external_exports.object({
57225
57597
  prompt: external_exports.string().describe("Description of the sound effect"),
57226
57598
  output: external_exports.string().optional().describe("Output audio file path (default: sound-effect.mp3)"),
57227
- duration: external_exports.number().optional().describe("Duration in seconds (0.5-22, default: auto)"),
57599
+ duration: external_exports.number().optional().describe("Duration in seconds (0.5-30, default: auto)"),
57228
57600
  promptInfluence: external_exports.number().optional().describe("Prompt influence 0-1 (default: 0.3)")
57229
57601
  }),
57230
57602
  async execute(args) {
@@ -57301,7 +57673,7 @@ var generateImageTool = defineTool({
57301
57673
  ratio: external_exports.string().optional().describe("Aspect ratio for Gemini (1:1, 16:9, 9:16, 4:3, 3:4, etc.)"),
57302
57674
  quality: external_exports.string().optional().describe("Quality for OpenAI: standard, hd"),
57303
57675
  count: external_exports.number().optional().describe("Number of images (default: 1)"),
57304
- model: external_exports.string().optional().describe("Gemini model: flash, 3.1-flash, latest, pro")
57676
+ model: external_exports.string().optional().describe("Image model. Gemini: flash (default), lite, pro. OpenAI: 2.5 (default), flare, 2, 1.5. Grok: pro")
57305
57677
  }),
57306
57678
  async execute(args) {
57307
57679
  const result = await executeImageGenerate(args);
@@ -57364,9 +57736,11 @@ var generateVideoTool = defineTool({
57364
57736
  mode: external_exports.string().optional().describe("Kling mode: std or pro"),
57365
57737
  negative: external_exports.string().optional().describe("Negative prompt (Seedance/Kling/Veo)"),
57366
57738
  resolution: external_exports.string().optional().describe("Resolution: 480p, 720p, 1080p, or 4k depending on provider"),
57367
- veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
57739
+ veoModel: external_exports.string().optional().describe("Veo model: 3.1, 3.1-fast"),
57368
57740
  runwayModel: external_exports.string().optional().describe("Runway model: gen4.5, gen4_turbo"),
57369
- seedanceModel: external_exports.string().optional().describe("Seedance variant: quality or fast (fal.ai only)"),
57741
+ seedanceModel: external_exports.string().optional().describe("Seedance variant: quality (2.0), fast, or 2.5 (fal.ai only)"),
57742
+ grokModel: external_exports.string().optional().describe("Grok video model: 1.5 (default), lite, classic"),
57743
+ klingModel: external_exports.string().optional().describe("Kling model: v3 (default), v2.6, v2.5-turbo"),
57370
57744
  generateAudio: external_exports.boolean().optional().describe("Generate native synchronized audio when supported"),
57371
57745
  output: external_exports.string().optional().describe("Output file path (downloads video)"),
57372
57746
  wait: external_exports.boolean().optional().describe("Wait for completion (default: true)")
@@ -57438,7 +57812,7 @@ var generateVideoExtendTool = defineTool({
57438
57812
  prompt: external_exports.string().optional().describe("Continuation prompt"),
57439
57813
  duration: external_exports.number().optional().describe("Duration in seconds"),
57440
57814
  negative: external_exports.string().optional().describe("Negative prompt (Kling)"),
57441
- veoModel: external_exports.string().optional().describe("Veo model: 3.0, 3.1, 3.1-fast"),
57815
+ veoModel: external_exports.string().optional().describe("Veo model: 3.1, 3.1-fast"),
57442
57816
  output: external_exports.string().optional().describe("Output file path"),
57443
57817
  wait: external_exports.boolean().optional().describe("Wait for completion (default: true)")
57444
57818
  }),
@@ -57474,14 +57848,14 @@ var generateTools = [
57474
57848
 
57475
57849
  // src/tools/manifest/storyboard.ts
57476
57850
  import { existsSync as existsSync44 } from "node:fs";
57477
- import { readFile as readFile35, writeFile as writeFile39 } from "node:fs/promises";
57851
+ import { readFile as readFile35, writeFile as writeFile38 } from "node:fs/promises";
57478
57852
  import { resolve as resolve53 } from "node:path";
57479
57853
  init_storyboard_parse();
57480
57854
  init_storyboard_edit();
57481
57855
 
57482
57856
  // src/commands/_shared/storyboard-revise.ts
57483
57857
  import { existsSync as existsSync43 } from "node:fs";
57484
- import { readFile as readFile34, writeFile as writeFile38 } from "node:fs/promises";
57858
+ import { readFile as readFile34, writeFile as writeFile37 } from "node:fs/promises";
57485
57859
  import { join as join24, resolve as resolve52 } from "node:path";
57486
57860
 
57487
57861
  // src/agent/adapters/index.ts
@@ -57650,7 +58024,7 @@ async function executeStoryboardRevision(opts) {
57650
58024
  if (loaded.layout === "bundle") {
57651
58025
  await rewriteBundleFromMarkdown(projectDir, storyboardMd);
57652
58026
  } else {
57653
- await writeFile38(storyboardPath, storyboardMd, "utf-8");
58027
+ await writeFile37(storyboardPath, storyboardMd, "utf-8");
57654
58028
  }
57655
58029
  }
57656
58030
  const changedBeats = unique5([...diffChangedBeats(currentStoryboard, storyboardMd), ...payload.changedBeats]);
@@ -57828,6 +58202,7 @@ function stripUndefined3(value) {
57828
58202
 
57829
58203
  // src/tools/manifest/storyboard.ts
57830
58204
  init_build_plan();
58205
+ init_build_video_providers();
57831
58206
  var PROJECT_DIR_DESCRIPTION3 = "Project directory. Defaults to the surface's cwd; in MCP hosts, relative paths resolve under the configured server workspace.";
57832
58207
  var projectDirSchema = external_exports.object({
57833
58208
  projectDir: external_exports.string().optional().describe(PROJECT_DIR_DESCRIPTION3)
@@ -57948,7 +58323,7 @@ var storyboardSetTool = defineTool({
57948
58323
  value,
57949
58324
  unset: args.unset
57950
58325
  });
57951
- await writeFile39(file.path, next, "utf-8");
58326
+ await writeFile38(file.path, next, "utf-8");
57952
58327
  return {
57953
58328
  success: true,
57954
58329
  data: { beat: args.beat, key: args.key },
@@ -57980,7 +58355,7 @@ var storyboardMoveTool = defineTool({
57980
58355
  beatId: args.beat,
57981
58356
  afterBeatId: args.after
57982
58357
  });
57983
- await writeFile39(file.path, next, "utf-8");
58358
+ await writeFile38(file.path, next, "utf-8");
57984
58359
  return {
57985
58360
  success: true,
57986
58361
  data: { beat: args.beat, after: args.after },
@@ -58045,7 +58420,7 @@ var planTool = defineTool({
58045
58420
  imageProvider: external_exports.string().optional().describe("Image provider override."),
58046
58421
  imageQuality: external_exports.enum(["standard", "hd"]).optional().describe("Image quality override."),
58047
58422
  imageSize: external_exports.string().optional().describe("Image size override."),
58048
- videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider override."),
58423
+ videoProvider: external_exports.enum(BUILD_VIDEO_PROVIDERS).optional().describe("Video provider override."),
58049
58424
  musicProvider: external_exports.enum(["elevenlabs", "replicate"]).optional().describe("Music provider override."),
58050
58425
  composer: external_exports.enum(["claude", "openai", "gemini"]).optional().describe("Composer provider override."),
58051
58426
  force: external_exports.boolean().optional().describe("Plan regeneration even when outputs exist.")
@@ -58133,13 +58508,13 @@ var designValidateTool = defineTool({
58133
58508
  var designTools = [designValidateTool];
58134
58509
 
58135
58510
  // src/tools/manifest/remix.ts
58136
- import { writeFile as writeFile43 } from "node:fs/promises";
58511
+ import { writeFile as writeFile42 } from "node:fs/promises";
58137
58512
  import { tmpdir as tmpdir4 } from "node:os";
58138
58513
  import { join as join26 } from "node:path";
58139
58514
 
58140
58515
  // src/commands/ai-highlights.ts
58141
- import { readFile as readFile37, writeFile as writeFile40, mkdir as mkdir23 } from "node:fs/promises";
58142
- import { resolve as resolve55, dirname as dirname20, basename as basename14, extname as extname10 } from "node:path";
58516
+ import { readFile as readFile37, writeFile as writeFile39, mkdir as mkdir24 } from "node:fs/promises";
58517
+ import { resolve as resolve55, dirname as dirname21, basename as basename14, extname as extname11 } from "node:path";
58143
58518
  import { existsSync as existsSync46 } from "node:fs";
58144
58519
  init_dist2();
58145
58520
  init_engine();
@@ -58174,7 +58549,7 @@ async function executeHighlights(options) {
58174
58549
  if (!existsSync46(absPath)) {
58175
58550
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
58176
58551
  }
58177
- const ext = extname10(absPath).toLowerCase();
58552
+ const ext = extname11(absPath).toLowerCase();
58178
58553
  const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
58179
58554
  const isVideo = videoExtensions.includes(ext);
58180
58555
  const targetDuration = options.duration;
@@ -58319,7 +58694,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
58319
58694
  };
58320
58695
  if (options.output) {
58321
58696
  const outputPath = resolve55(process.cwd(), options.output);
58322
- await writeFile40(outputPath, JSON.stringify({
58697
+ await writeFile39(outputPath, JSON.stringify({
58323
58698
  sourceFile: absPath,
58324
58699
  totalDuration: sourceDuration,
58325
58700
  criteria: options.criteria || "all",
@@ -58354,7 +58729,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
58354
58729
  }
58355
58730
  }
58356
58731
  const projectPath = resolve55(process.cwd(), options.project);
58357
- await writeFile40(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
58732
+ await writeFile39(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
58358
58733
  extractResult.projectPath = projectPath;
58359
58734
  }
58360
58735
  return extractResult;
@@ -58501,9 +58876,9 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
58501
58876
  }))
58502
58877
  };
58503
58878
  }
58504
- const outputDir = options.outputDir ? resolve55(process.cwd(), options.outputDir) : dirname20(absPath);
58879
+ const outputDir = options.outputDir ? resolve55(process.cwd(), options.outputDir) : dirname21(absPath);
58505
58880
  if (options.outputDir && !existsSync46(outputDir)) {
58506
- await mkdir23(outputDir, { recursive: true });
58881
+ await mkdir24(outputDir, { recursive: true });
58507
58882
  }
58508
58883
  const result = {
58509
58884
  success: true,
@@ -58511,7 +58886,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
58511
58886
  };
58512
58887
  for (let i = 0; i < selectedHighlights.length; i++) {
58513
58888
  const h = selectedHighlights[i];
58514
- const baseName = basename14(absPath, extname10(absPath));
58889
+ const baseName = basename14(absPath, extname11(absPath));
58515
58890
  const outputPath = resolve55(outputDir, `${baseName}-short-${i + 1}.mp4`);
58516
58891
  const { stdout: probeOut } = await execSafe("ffprobe", [
58517
58892
  "-v",
@@ -58581,7 +58956,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
58581
58956
  // src/pipeline/executor.ts
58582
58957
  var import_yaml8 = __toESM(require_dist2(), 1);
58583
58958
  import { resolve as resolve57 } from "node:path";
58584
- import { readFile as readFile39, writeFile as writeFile42, mkdir as mkdir24 } from "node:fs/promises";
58959
+ import { readFile as readFile39, writeFile as writeFile41, mkdir as mkdir25 } from "node:fs/promises";
58585
58960
  import { existsSync as existsSync47 } from "node:fs";
58586
58961
 
58587
58962
  // src/pipeline/resolver.ts
@@ -58643,6 +59018,7 @@ function findUnresolvedRefs(params, availableStepIds) {
58643
59018
  init_output();
58644
59019
  init_config();
58645
59020
  init_provider_resolver();
59021
+ init_build_video_providers();
58646
59022
  var ACTION_METADATA = {
58647
59023
  "generate-image": { id: "generate-image", title: "Generate image", category: "generate", command: "generate image", outputs: ["image"] },
58648
59024
  "generate-video": { id: "generate-video", title: "Generate video", category: "generate", command: "generate video", outputs: ["video"], requiredKeys: ["provider-dependent"] },
@@ -58716,7 +59092,7 @@ async function resolvePipelineVideoProvider(provider) {
58716
59092
  if (explicit === "fal") return "seedance";
58717
59093
  if (explicit) return explicit;
58718
59094
  await loadProviderDefaults();
58719
- return resolveProvider("video")?.name ?? "grok";
59095
+ return resolveBuildVideoProvider(resolveProvider("video")?.name ?? "grok");
58720
59096
  }
58721
59097
  function providerKeyForImage(provider) {
58722
59098
  switch (provider) {
@@ -58742,6 +59118,7 @@ function providerKeyForVideo(provider) {
58742
59118
  case "runway":
58743
59119
  return "runway";
58744
59120
  case "veo":
59121
+ case "omni":
58745
59122
  return "google";
58746
59123
  default:
58747
59124
  return void 0;
@@ -58993,7 +59370,7 @@ var CHECKPOINT_FILE = ".pipeline-state.yaml";
58993
59370
  async function executePipeline(manifest2, options = {}) {
58994
59371
  await ensureActionsRegistered();
58995
59372
  const outputDir = resolve57(process.cwd(), options.outputDir || `${manifest2.name}-output`);
58996
- await mkdir24(outputDir, { recursive: true });
59373
+ await mkdir25(outputDir, { recursive: true });
58997
59374
  const completedSteps = /* @__PURE__ */ new Map();
58998
59375
  const results = [];
58999
59376
  const startTime = Date.now();
@@ -59118,7 +59495,7 @@ async function executePipeline(manifest2, options = {}) {
59118
59495
  data: s.data
59119
59496
  }))
59120
59497
  };
59121
- await writeFile42(
59498
+ await writeFile41(
59122
59499
  resolve57(outputDir, CHECKPOINT_FILE),
59123
59500
  (0, import_yaml8.stringify)(checkpoint, { indent: 2 }),
59124
59501
  "utf-8"
@@ -59273,7 +59650,7 @@ var pipelineRunTool = defineTool({
59273
59650
  let tempPath;
59274
59651
  if (args.pipelineYaml) {
59275
59652
  tempPath = join26(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
59276
- await writeFile43(tempPath, args.pipelineYaml, "utf-8");
59653
+ await writeFile42(tempPath, args.pipelineYaml, "utf-8");
59277
59654
  resolvedPath = tempPath;
59278
59655
  }
59279
59656
  try {
@@ -59408,7 +59785,7 @@ var detectTools = [
59408
59785
  ];
59409
59786
 
59410
59787
  // src/tools/manifest/timeline.ts
59411
- import { mkdir as mkdir25, writeFile as writeFile45 } from "node:fs/promises";
59788
+ import { mkdir as mkdir26, writeFile as writeFile44 } from "node:fs/promises";
59412
59789
  import { basename as basename16, resolve as resolve58 } from "node:path";
59413
59790
  init_engine();
59414
59791
  init_project_resolver();
@@ -59416,7 +59793,7 @@ init_project_resolver();
59416
59793
  // src/tools/manifest/_project-io.ts
59417
59794
  init_engine();
59418
59795
  init_project_resolver();
59419
- import { readFile as readFile40, writeFile as writeFile44 } from "node:fs/promises";
59796
+ import { readFile as readFile40, writeFile as writeFile43 } from "node:fs/promises";
59420
59797
  async function loadProject(projectPath, cwd) {
59421
59798
  const absPath = await resolveTimelineFile(projectPath, cwd);
59422
59799
  const content = await readFile40(absPath, "utf-8");
@@ -59424,7 +59801,7 @@ async function loadProject(projectPath, cwd) {
59424
59801
  return { project: Project.fromJSON(data), absPath };
59425
59802
  }
59426
59803
  async function saveProject(absPath, project) {
59427
- await writeFile44(absPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
59804
+ await writeFile43(absPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
59428
59805
  }
59429
59806
 
59430
59807
  // src/tools/manifest/timeline.ts
@@ -59464,10 +59841,10 @@ var timelineCreateTool = defineTool({
59464
59841
  outputPath = resolve58(ctx.workingDirectory, args.outputPath);
59465
59842
  } else {
59466
59843
  const dirPath = resolve58(ctx.workingDirectory, args.name);
59467
- await mkdir25(dirPath, { recursive: true });
59844
+ await mkdir26(dirPath, { recursive: true });
59468
59845
  outputPath = resolve58(dirPath, TIMELINE_FILENAME);
59469
59846
  }
59470
- await writeFile45(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
59847
+ await writeFile44(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
59471
59848
  return {
59472
59849
  success: true,
59473
59850
  data: { name: projectName, outputPath },
@@ -59764,7 +60141,7 @@ var timelineTools = [
59764
60141
 
59765
60142
  // src/tools/manifest/project.ts
59766
60143
  import { resolve as resolve60 } from "node:path";
59767
- import { writeFile as writeFile46 } from "node:fs/promises";
60144
+ import { writeFile as writeFile45 } from "node:fs/promises";
59768
60145
  init_engine();
59769
60146
 
59770
60147
  // src/commands/_shared/project-list.ts
@@ -59922,7 +60299,7 @@ var projectCreateTool = defineTool({
59922
60299
  const outputPath = resolve60(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
59923
60300
  const project = new Project(args.name);
59924
60301
  if (args.fps) project.setFrameRate(args.fps);
59925
- await writeFile46(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
60302
+ await writeFile45(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
59926
60303
  return {
59927
60304
  success: true,
59928
60305
  data: { name: args.name, outputPath },
@@ -60642,8 +61019,8 @@ var exportVideoTool = defineTool({
60642
61019
  var exportTools = [exportVideoTool];
60643
61020
 
60644
61021
  // src/tools/manifest/agent-only.ts
60645
- import { readFile as readFile43, writeFile as writeFile47, readdir as readdir8, stat as stat8, access as access7, unlink as unlink2 } from "node:fs/promises";
60646
- import { resolve as resolve63, join as join28, basename as basename17, extname as extname11 } from "node:path";
61022
+ import { readFile as readFile43, writeFile as writeFile46, readdir as readdir8, stat as stat8, access as access7, unlink as unlink2 } from "node:fs/promises";
61023
+ import { resolve as resolve63, join as join28, basename as basename17, extname as extname12 } from "node:path";
60647
61024
  init_engine();
60648
61025
  init_exec_safe();
60649
61026
  init_project_resolver();
@@ -60665,7 +61042,7 @@ function formatSize(bytes) {
60665
61042
  return `${size.toFixed(1)}${units2[unit]}`;
60666
61043
  }
60667
61044
  function detectMediaType(filePath) {
60668
- const ext = extname11(filePath).toLowerCase();
61045
+ const ext = extname12(filePath).toLowerCase();
60669
61046
  if ([".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"].includes(ext)) return "video";
60670
61047
  if ([".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"].includes(ext)) return "audio";
60671
61048
  if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext)) return "image";
@@ -60692,7 +61069,7 @@ function isMediaFile(filePath) {
60692
61069
  ".webp",
60693
61070
  ".bmp"
60694
61071
  ];
60695
- return mediaExts.includes(extname11(filePath).toLowerCase());
61072
+ return mediaExts.includes(extname12(filePath).toLowerCase());
60696
61073
  }
60697
61074
  async function getMediaDuration2(filePath) {
60698
61075
  try {
@@ -60775,7 +61152,7 @@ var fsWriteTool = defineTool({
60775
61152
  async execute(args, ctx) {
60776
61153
  try {
60777
61154
  const absPath = resolve63(ctx.workingDirectory, args.path);
60778
- await writeFile47(absPath, args.content, "utf-8");
61155
+ await writeFile46(absPath, args.content, "utf-8");
60779
61156
  return { success: true, data: { bytes: args.content.length }, humanLines: [`File written: ${args.path} (${formatSize(args.content.length)})`] };
60780
61157
  } catch (error) {
60781
61158
  return { success: false, error: `Failed to write file: ${error instanceof Error ? error.message : String(error)}` };
@@ -60838,7 +61215,7 @@ var batchImportTool = defineTool({
60838
61215
  if (entry.isDirectory() && recursive) {
60839
61216
  await scanDir(entryPath);
60840
61217
  } else if (entry.isFile()) {
60841
- const ext = extname11(entry.name).toLowerCase();
61218
+ const ext = extname12(entry.name).toLowerCase();
60842
61219
  const matchesFilter = !filterExts || filterExts.includes(ext);
60843
61220
  if (matchesFilter && isMediaFile(entryPath)) mediaFiles.push(entryPath);
60844
61221
  }
@@ -60861,7 +61238,7 @@ var batchImportTool = defineTool({
60861
61238
  const source = project.addSource({ name: mediaName, type: mediaType, url: mediaFile, duration });
60862
61239
  addedSources.push({ id: source.id, name: mediaName, type: mediaType });
60863
61240
  }
60864
- await writeFile47(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61241
+ await writeFile46(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
60865
61242
  return {
60866
61243
  success: true,
60867
61244
  data: { count: addedSources.length, sources: addedSources },
@@ -60931,7 +61308,7 @@ var batchConcatTool = defineTool({
60931
61308
  createdClips.push({ id: clip.id, sourceName: source.name, startTime: currentTime, duration: source.duration });
60932
61309
  currentTime += source.duration + gap;
60933
61310
  }
60934
- await writeFile47(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61311
+ await writeFile46(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
60935
61312
  const totalDuration = currentTime - gap - startTime;
60936
61313
  return {
60937
61314
  success: true,
@@ -60994,7 +61371,7 @@ var batchApplyEffectTool = defineTool({
60994
61371
  });
60995
61372
  if (effect) appliedEffects.push({ clipId: clip.id, effectId: effect.id });
60996
61373
  }
60997
- await writeFile47(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61374
+ await writeFile46(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
60998
61375
  return {
60999
61376
  success: true,
61000
61377
  data: { applied: appliedEffects.length, effectType: args.effectType },
@@ -61063,7 +61440,7 @@ var timelineClearTool = defineTool({
61063
61440
  removed.sources++;
61064
61441
  }
61065
61442
  }
61066
- await writeFile47(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61443
+ await writeFile46(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61067
61444
  const parts = [];
61068
61445
  if (removed.clips > 0) parts.push(`${removed.clips} clips`);
61069
61446
  if (removed.tracks > 0) parts.push(`${removed.tracks} tracks`);
@@ -61114,7 +61491,7 @@ var projectSetTool = defineTool({
61114
61491
  project.setFrameRate(args.fps);
61115
61492
  updates.push(`Frame Rate: ${args.fps} fps`);
61116
61493
  }
61117
- await writeFile47(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61494
+ await writeFile46(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
61118
61495
  return {
61119
61496
  success: true,
61120
61497
  data: { updates },
@@ -61484,7 +61861,7 @@ var mediaConcatTool = defineTool({
61484
61861
  } else {
61485
61862
  const tempList = resolve63(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
61486
61863
  const listContent = args.inputs.map((i) => `file '${resolve63(ctx.workingDirectory, i)}'`).join("\n");
61487
- await writeFile47(tempList, listContent, "utf-8");
61864
+ await writeFile46(tempList, listContent, "utf-8");
61488
61865
  try {
61489
61866
  await execSafe(
61490
61867
  "ffmpeg",
@@ -61775,8 +62152,9 @@ they want for a given task. This guide is the operator-facing summary.
61775
62152
 
61776
62153
  If you already use Claude Code, Codex, Cursor, Aider, Gemini CLI, OpenCode,
61777
62154
  or another coding agent, let that host drive \`vibe\` directly through shell
61778
- commands plus \`AGENTS.md\` / \`CLAUDE.md\`. \`vibe agent\` is the optional
61779
- built-in fallback when you do not already have an agent host.
62155
+ commands plus \`AGENTS.md\` / \`CLAUDE.md\`. Without a coding agent, connect
62156
+ the MCP server to Claude Desktop or ChatGPT (\`vibe host setup\`).
62157
+ \`vibe agent\` is deprecated and will be removed before 1.0.
61780
62158
 
61781
62159
  ## TL;DR
61782
62160
 
@@ -61787,12 +62165,12 @@ built-in fallback when you do not already have an agent host.
61787
62165
  | **Reproducibility** | none | high (idempotent) | highest (checkpointed) |
61788
62166
  | **Budget caps** | per-session max-turns | none | \`--budget-usd\`, \`--budget-tokens\`, \`--max-errors\` |
61789
62167
  | **Resume after crash** | no | re-invoke | \`--resume\` |
61790
- | **Best for** | optional built-in exploration | finished script | repeatable workflows |
62168
+ | **Best for** | deprecated; use a host agent | finished script | repeatable workflows |
61791
62169
 
61792
62170
  ## Decision tree
61793
62171
 
61794
62172
  - "I am already in Claude Code/Codex/Cursor/etc." \u2192 regular \`vibe\` shell commands + \`vibe guide\` / \`vibe schema\`
61795
- - "I want to play and do not have an external coding agent" \u2192 \`vibe agent\`
62173
+ - "I do not use a coding agent" \u2192 the MCP server in Claude Desktop or ChatGPT (\`vibe host setup\`)
61796
62174
  - "I have a finished script + visual identity" \u2192 \`vibe build\`
61797
62175
  - "I want this to run again next month" \u2192 \`vibe run\`
61798
62176
 
@@ -61818,9 +62196,6 @@ agent calls them through a tool manifest; \`build\` calls a curated subset
61818
62196
  - \`vibe build\` is conceptually a 4-step pipeline; \`vibe run\` could express
61819
62197
  the same flow but build's STORYBOARD.md input + opinionated defaults are
61820
62198
  the value-add.
61821
- - \`vibe agent\`'s tool registry currently exposes primitives, not
61822
- orchestrators. Adding \`vibe.build\` / \`vibe.run\` as agent tools is on the
61823
- table for a future major.
61824
62199
  - \`vibe run --resume\` has no analog in build/agent; build's idempotent
61825
62200
  re-invoke covers the common case.
61826
62201
  `;
@@ -61927,17 +62302,17 @@ var META = {
61927
62302
  },
61928
62303
  architecture: {
61929
62304
  title: "external agents / vibe agent / build / run",
61930
- summary: "Choose between host-agent shell use, optional built-in agent mode, storyboard builds, and YAML pipelines",
62305
+ summary: "Choose between host-agent shell use, MCP hosts, storyboard builds, and YAML pipelines",
61931
62306
  steps: [
61932
62307
  "If you are already in Claude Code/Codex/Cursor/etc., let that host drive normal `vibe` shell commands using `AGENTS.md`, `vibe guide`, and `vibe schema`.",
61933
- "Pick `vibe agent` only for optional built-in exploration when no external agent host is driving the CLI.",
62308
+ "Without a coding agent, connect the MCP server to Claude Desktop or ChatGPT with `vibe host setup`; `vibe agent` is deprecated.",
61934
62309
  "Pick build for STORYBOARD.md \u2192 MP4 with opinionated defaults.",
61935
62310
  "Pick run for repeatable, budget-capped, checkpointed YAML pipelines.",
61936
62311
  "Run `vibe doctor --test-keys` before any high-cost orchestrator to validate keys upfront.",
61937
62312
  "Use `vibe schema --list` (cost field, v0.84) to plan the budget per step before kicking off `vibe run --budget-usd`."
61938
62313
  ],
61939
62314
  relatedCommands: [
61940
- "vibe agent",
62315
+ "vibe host setup",
61941
62316
  "vibe build",
61942
62317
  "vibe run",
61943
62318
  "vibe doctor",