@vibeframe/cli 0.91.8 → 0.92.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1576 -964
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -14443,8 +14443,8 @@ var init_api_keys = __esm({
14443
14443
  configKey: "imgbb",
14444
14444
  envVar: "IMGBB_API_KEY",
14445
14445
  label: "ImgBB",
14446
- showInSetup: false,
14447
- // not prompted in setup wizard — internal upload host
14446
+ showInSetup: true,
14447
+ setupDescription: "image hosting for Seedance/Kling image-to-video uploads",
14448
14448
  envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
14449
14449
  envExampleUrl: "https://api.imgbb.com/",
14450
14450
  // ImgBB issues 32-character lowercase hex keys. Soft-warn if a pasted
@@ -14453,9 +14453,7 @@ var init_api_keys = __esm({
14453
14453
  keyFormat: { prefix: /^[a-f0-9]{32}$/, example: "32-char hex" },
14454
14454
  // ImgBB has no provider class (envvar-only); doctor still shows what it
14455
14455
  // unlocks at the apiKey level.
14456
- commandsUnlocked: [
14457
- "generate video -p kling/seedance (image-to-video upload host)"
14458
- ]
14456
+ commandsUnlocked: ["generate video -p kling/seedance (image-to-video upload host)"]
14459
14457
  });
14460
14458
  defineProvider({
14461
14459
  id: "openrouter",
@@ -25677,6 +25675,13 @@ function createDefaultConfig() {
25677
25675
  aspectRatio: "16:9",
25678
25676
  exportQuality: "standard"
25679
25677
  },
25678
+ upload: {
25679
+ provider: "imgbb",
25680
+ ttlSeconds: 3600,
25681
+ s3: {
25682
+ prefix: "vibeframe/tmp"
25683
+ }
25684
+ },
25680
25685
  repl: {
25681
25686
  autoSave: true
25682
25687
  }
@@ -25700,6 +25705,26 @@ var init_schema = __esm({
25700
25705
  });
25701
25706
 
25702
25707
  // src/config/index.ts
25708
+ var config_exports = {};
25709
+ __export(config_exports, {
25710
+ CONFIG_DIR: () => CONFIG_DIR,
25711
+ CONFIG_PATH: () => CONFIG_PATH,
25712
+ PROVIDER_ENV_VARS: () => PROVIDER_ENV_VARS,
25713
+ PROVIDER_NAMES: () => PROVIDER_NAMES,
25714
+ USER_CONFIG_DIR: () => USER_CONFIG_DIR,
25715
+ USER_CONFIG_PATH: () => USER_CONFIG_PATH,
25716
+ createDefaultConfig: () => createDefaultConfig,
25717
+ getActiveScope: () => getActiveScope,
25718
+ getApiKeyFromConfig: () => getApiKeyFromConfig,
25719
+ getConfigDir: () => getConfigDir,
25720
+ getConfigPath: () => getConfigPath,
25721
+ getProjectConfigDir: () => getProjectConfigDir,
25722
+ getProjectConfigPath: () => getProjectConfigPath,
25723
+ isConfigured: () => isConfigured,
25724
+ loadConfig: () => loadConfig,
25725
+ saveConfig: () => saveConfig,
25726
+ updateProviderKey: () => updateProviderKey
25727
+ });
25703
25728
  import { resolve as resolve4 } from "node:path";
25704
25729
  import { homedir } from "node:os";
25705
25730
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "node:fs/promises";
@@ -25734,6 +25759,11 @@ function applyDefaults(parsed) {
25734
25759
  llm: { ...defaults.llm, ...parsed.llm },
25735
25760
  providers: { ...defaults.providers, ...parsed.providers },
25736
25761
  defaults: { ...defaults.defaults, ...parsed.defaults },
25762
+ upload: {
25763
+ ...defaults.upload,
25764
+ ...parsed.upload,
25765
+ s3: { ...defaults.upload.s3, ...parsed.upload?.s3 }
25766
+ },
25737
25767
  repl: { ...defaults.repl, ...parsed.repl }
25738
25768
  };
25739
25769
  }
@@ -25761,6 +25791,11 @@ async function loadConfig(options = {}) {
25761
25791
  llm: { ...user.llm, ...project2.llm },
25762
25792
  providers: { ...user.providers, ...project2.providers },
25763
25793
  defaults: { ...user.defaults, ...project2.defaults },
25794
+ upload: {
25795
+ ...user.upload,
25796
+ ...project2.upload,
25797
+ s3: { ...user.upload.s3, ...project2.upload.s3 }
25798
+ },
25764
25799
  repl: { ...user.repl, ...project2.repl }
25765
25800
  };
25766
25801
  }
@@ -25802,7 +25837,14 @@ async function getApiKeyFromConfig(providerKey) {
25802
25837
  if (envVar) return process.env[envVar];
25803
25838
  return void 0;
25804
25839
  }
25805
- var import_yaml, USER_CONFIG_DIR, USER_CONFIG_PATH, CONFIG_PATH;
25840
+ async function updateProviderKey(providerKey, apiKey, options = {}) {
25841
+ const scope = options.scope ?? await getActiveScope(options.cwd);
25842
+ let config2 = await loadConfig({ scope, cwd: options.cwd });
25843
+ if (!config2) config2 = createDefaultConfig();
25844
+ config2.providers[providerKey] = apiKey;
25845
+ await saveConfig(config2, { scope, cwd: options.cwd });
25846
+ }
25847
+ var import_yaml, USER_CONFIG_DIR, USER_CONFIG_PATH, CONFIG_DIR, CONFIG_PATH;
25806
25848
  var init_config = __esm({
25807
25849
  "src/config/index.ts"() {
25808
25850
  "use strict";
@@ -25811,6 +25853,7 @@ var init_config = __esm({
25811
25853
  init_schema();
25812
25854
  USER_CONFIG_DIR = resolve4(homedir(), ".vibeframe");
25813
25855
  USER_CONFIG_PATH = resolve4(USER_CONFIG_DIR, "config.yaml");
25856
+ CONFIG_DIR = USER_CONFIG_DIR;
25814
25857
  CONFIG_PATH = USER_CONFIG_PATH;
25815
25858
  }
25816
25859
  });
@@ -25910,7 +25953,9 @@ async function getApiKey(envVar, providerName, optionValue) {
25910
25953
  }
25911
25954
  console.log();
25912
25955
  console.log(source_default.yellow(`${providerName} API key not found.`));
25913
- console.log(source_default.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`));
25956
+ console.log(
25957
+ source_default.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`)
25958
+ );
25914
25959
  console.log();
25915
25960
  const apiKey = await prompt(source_default.cyan(`Enter ${providerName} API key: `), true);
25916
25961
  if (!apiKey || apiKey.trim() === "") {
@@ -25969,7 +26014,8 @@ var init_api_key = __esm({
25969
26014
  ELEVENLABS_API_KEY: "https://elevenlabs.io/app/settings/api-keys",
25970
26015
  RUNWAY_API_SECRET: "https://app.runwayml.com/settings/api-keys",
25971
26016
  KLING_API_KEY: "https://klingai.com/dev",
25972
- REPLICATE_API_TOKEN: "https://replicate.com/account/api-tokens"
26017
+ REPLICATE_API_TOKEN: "https://replicate.com/account/api-tokens",
26018
+ IMGBB_API_KEY: "https://api.imgbb.com/"
25973
26019
  };
25974
26020
  ApiKeyError = class extends Error {
25975
26021
  envVar;
@@ -31792,7 +31838,7 @@ async function promptHidden(question, opts = {}) {
31792
31838
  }
31793
31839
  });
31794
31840
  }
31795
- async function promptSelect(_question, options, defaultIndex = 0) {
31841
+ async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
31796
31842
  const input = getTTYInputStream();
31797
31843
  if (typeof input.setRawMode === "function") {
31798
31844
  return new Promise((resolve64) => {
@@ -31827,6 +31873,12 @@ async function promptSelect(_question, options, defaultIndex = 0) {
31827
31873
  input.setRawMode(false);
31828
31874
  process.stdout.write("\n");
31829
31875
  process.exit(1);
31876
+ } else if (opts.backIndex !== void 0 && (char === "\x1B" || char === "\x1B[D")) {
31877
+ finished = true;
31878
+ input.setRawMode(false);
31879
+ input.removeListener("data", onData);
31880
+ process.stdout.write("\n");
31881
+ resolve64(opts.backIndex);
31830
31882
  } else if (char === "\x1B[A" || char === "k") {
31831
31883
  selected = (selected - 1 + options.length) % options.length;
31832
31884
  render();
@@ -31982,6 +32034,11 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
31982
32034
  input.setRawMode(false);
31983
32035
  process.stdout.write("\n");
31984
32036
  process.exit(1);
32037
+ } else if (opts.backIndex !== void 0 && (char === "\x1B" || char === "\x1B[D")) {
32038
+ for (let i = 0; i < selected.length; i++) selected[i] = false;
32039
+ selected[opts.backIndex] = true;
32040
+ finished = true;
32041
+ finish();
31985
32042
  } else if (char === " ") {
31986
32043
  selected[cursor] = !selected[cursor];
31987
32044
  userToggled = true;
@@ -33342,29 +33399,61 @@ var init_video_extend = __esm({
33342
33399
  });
33343
33400
 
33344
33401
  // src/utils/provider-resolver.ts
33402
+ function loadProviderDefaultsFromConfig(config2) {
33403
+ configDefaults = {};
33404
+ if (config2?.defaults.imageProvider) configDefaults.image = config2.defaults.imageProvider;
33405
+ if (config2?.defaults.videoProvider) configDefaults.video = config2.defaults.videoProvider;
33406
+ configProviderKeys = new Set(
33407
+ Object.entries(config2?.providers ?? {}).filter(([, value]) => Boolean(value)).map(([key]) => key)
33408
+ );
33409
+ }
33410
+ async function loadProviderDefaults() {
33411
+ try {
33412
+ const { loadConfig: loadConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
33413
+ const config2 = await loadConfig2();
33414
+ loadProviderDefaultsFromConfig(config2);
33415
+ } catch {
33416
+ configDefaults = null;
33417
+ configProviderKeys = null;
33418
+ }
33419
+ }
33420
+ function providerKeyForEnvVar(envVar) {
33421
+ const envVars = getProviderEnvVars();
33422
+ for (const [providerKey, candidateEnvVar] of Object.entries(envVars)) {
33423
+ if (candidateEnvVar === envVar) return providerKey;
33424
+ }
33425
+ return null;
33426
+ }
33427
+ function hasCandidateKey(candidate) {
33428
+ if (candidate.envVar === null) return true;
33429
+ if (hasApiKey(candidate.envVar)) return true;
33430
+ const providerKey = providerKeyForEnvVar(candidate.envVar);
33431
+ return providerKey ? Boolean(configProviderKeys?.has(providerKey)) : false;
33432
+ }
33345
33433
  function resolveProvider(category) {
33346
33434
  const candidates = getProvidersFor(category);
33347
33435
  if (candidates.length === 0) return null;
33348
33436
  if (configDefaults?.[category]) {
33349
33437
  const preferred = candidates.find((c) => c.name === configDefaults[category]);
33350
- if (preferred && (preferred.envVar === null || hasApiKey(preferred.envVar))) {
33438
+ if (preferred && hasCandidateKey(preferred)) {
33351
33439
  return { name: preferred.name, label: preferred.label };
33352
33440
  }
33353
33441
  }
33354
33442
  for (const candidate of candidates) {
33355
- if (candidate.envVar === null || hasApiKey(candidate.envVar)) {
33443
+ if (hasCandidateKey(candidate)) {
33356
33444
  return { name: candidate.name, label: candidate.label };
33357
33445
  }
33358
33446
  }
33359
33447
  return null;
33360
33448
  }
33361
- var configDefaults;
33449
+ var configDefaults, configProviderKeys;
33362
33450
  var init_provider_resolver = __esm({
33363
33451
  "src/utils/provider-resolver.ts"() {
33364
33452
  "use strict";
33365
33453
  init_dist2();
33366
33454
  init_api_key();
33367
33455
  configDefaults = null;
33456
+ configProviderKeys = null;
33368
33457
  }
33369
33458
  });
33370
33459
 
@@ -33401,13 +33490,26 @@ import { resolve as resolve23, dirname as dirname7 } from "node:path";
33401
33490
  import { fileURLToPath } from "node:url";
33402
33491
  import { writeFile as writeFile21, mkdir as mkdir11 } from "node:fs/promises";
33403
33492
  function registerImageCommand(parent) {
33404
- parent.command("image").alias("img").description("Generate image using AI (Gemini, OpenAI gpt-image, Grok, or Runway)").argument("[prompt]", "Image description prompt (interactive if omitted)").option("-p, --provider <provider>", "Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway").option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("--size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024").option("-r, --ratio <ratio>", "Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)", "1:1").option("--quality <quality>", "Quality: standard, hd (openai only)", "standard").option("--style <style>", "Style: vivid, natural (openai only)", "vivid").option("--count <n>", "Number of images to generate", "1").option("-m, --model <model>", "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
33493
+ 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(
33494
+ "-p, --provider <provider>",
33495
+ "Provider: openai (default when OPENAI_API_KEY set), gemini, grok, runway"
33496
+ ).option("-k, --api-key <key>", "API key (or set env: OPENAI_API_KEY, GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path (downloads image)").option("--size <size>", "Image size (openai: 1024x1024, 1536x1024, 1024x1536)", "1024x1024").option(
33497
+ "-r, --ratio <ratio>",
33498
+ "Aspect ratio (gemini: 1:1, 1:4, 1:8, 4:1, 8:1, 16:9, 9:16, 3:4, 4:3, etc.)",
33499
+ "1:1"
33500
+ ).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(
33501
+ "-m, --model <model>",
33502
+ "Model. Gemini: flash, 3.1-flash, latest, pro. OpenAI: 1.5 (default), 2 (gpt-image-2)"
33503
+ ).option("--dry-run", "Preview parameters without executing").addHelpText(
33504
+ "after",
33505
+ `
33405
33506
  Examples:
33406
33507
  $ vibe generate image "a sunset over the ocean" -o sunset.png
33407
33508
  $ vibe gen img "logo design" -o logo.png -p openai
33408
33509
  $ vibe gen img "landscape photo" -o wide.png -r 16:9
33409
33510
  $ vibe gen img "portrait" -o portrait.png -p gemini -m pro
33410
- $ vibe gen img "product shot" --dry-run --json`).action(async (prompt3, options) => {
33511
+ $ vibe gen img "product shot" --dry-run --json`
33512
+ ).action(async (prompt3, options) => {
33411
33513
  const startedAt = Date.now();
33412
33514
  try {
33413
33515
  if (!prompt3) {
@@ -33418,10 +33520,7 @@ Examples:
33418
33520
  }
33419
33521
  } else {
33420
33522
  exitWithError(
33421
- usageError(
33422
- "Prompt argument is required.",
33423
- "Usage: vibe generate image <prompt>"
33424
- )
33523
+ usageError("Prompt argument is required.", "Usage: vibe generate image <prompt>")
33425
33524
  );
33426
33525
  }
33427
33526
  }
@@ -33429,13 +33528,16 @@ Examples:
33429
33528
  if (options.output) {
33430
33529
  validateOutputPath(options.output);
33431
33530
  }
33531
+ await loadProviderDefaults();
33432
33532
  if (options.count !== void 0) {
33433
33533
  const n = parseInt(options.count, 10);
33434
33534
  if (!Number.isFinite(n) || n < 1 || n > 10) {
33435
- exitWithError(usageError(
33436
- `Invalid --count: ${options.count}`,
33437
- "Must be an integer between 1 and 10."
33438
- ));
33535
+ exitWithError(
33536
+ usageError(
33537
+ `Invalid --count: ${options.count}`,
33538
+ "Must be an integer between 1 and 10."
33539
+ )
33540
+ );
33439
33541
  }
33440
33542
  }
33441
33543
  const imageRegistry = getProvidersFor("image");
@@ -33500,11 +33602,9 @@ Examples:
33500
33602
  const apiKey = await requireApiKey(envKey, providerName, options.apiKey);
33501
33603
  const spinner2 = ora(`Generating image with ${providerName}...`).start();
33502
33604
  if (provider === "openai") {
33503
- const { result, modelLabel } = await executeOpenAIImageGenerate(
33504
- prompt3,
33505
- options,
33506
- { apiKey }
33507
- );
33605
+ const { result, modelLabel } = await executeOpenAIImageGenerate(prompt3, options, {
33606
+ apiKey
33607
+ });
33508
33608
  if (!result.success || !result.images) {
33509
33609
  spinner2.fail(result.error || "Image generation failed");
33510
33610
  exitWithError(apiError(result.error || "Image generation failed", true));
@@ -33726,9 +33826,7 @@ Examples:
33726
33826
  spinner2.fail(result.error || "Image generation failed");
33727
33827
  exitWithError(apiError(result.error || "Image generation failed", true));
33728
33828
  }
33729
- spinner2.succeed(
33730
- source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`)
33731
- );
33829
+ spinner2.succeed(source_default.green(`Generated ${result.images.length} image(s) with xAI Grok`));
33732
33830
  if (isJsonMode()) {
33733
33831
  const outputPath = options.output ? resolve23(process.cwd(), options.output) : void 0;
33734
33832
  if (outputPath && result.images.length > 0) {
@@ -34961,606 +35059,206 @@ var init_video_utils = __esm({
34961
35059
  }
34962
35060
  });
34963
35061
 
34964
- // src/commands/_shared/video-providers.ts
34965
- async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
34966
- const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
34967
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
34968
- try {
34969
- const result = await grok.generateVideo(prompt3, {
34970
- prompt: prompt3,
34971
- duration: options.duration,
34972
- aspectRatio: options.aspectRatio,
34973
- referenceImage: options.referenceImage
34974
- });
34975
- if (result.status !== "failed" && result.id) {
34976
- return { requestId: result.id };
34977
- }
34978
- const providerErr = result.error || "Grok returned failed status";
34979
- if (attempt < maxRetries) {
34980
- onProgress?.(
34981
- `\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`
34982
- );
34983
- await sleep(RETRY_DELAY_MS);
34984
- } else {
34985
- console.error(source_default.dim(`
34986
- [Grok error: ${providerErr}]`));
34987
- }
34988
- } catch (err) {
34989
- const errMsg = err instanceof Error ? err.message : String(err);
34990
- if (attempt < maxRetries) {
34991
- onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
34992
- await sleep(RETRY_DELAY_MS);
34993
- } else {
34994
- console.error(source_default.dim(`
34995
- [Grok error: ${errMsg}]`));
34996
- }
34997
- }
34998
- }
34999
- return null;
35062
+ // src/utils/upload-host.ts
35063
+ import { createHash as createHash4, createHmac as createHmac2, randomUUID } from "node:crypto";
35064
+ import { extname as extname3 } from "node:path";
35065
+ function envNumber(name) {
35066
+ const value = process.env[name];
35067
+ if (!value) return void 0;
35068
+ const n = Number.parseInt(value, 10);
35069
+ return Number.isFinite(n) && n > 0 ? n : void 0;
35000
35070
  }
35001
- async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
35002
- const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
35003
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
35004
- try {
35005
- const result = await kling.generateVideo(prompt3, {
35006
- prompt: prompt3,
35007
- // Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
35008
- referenceImage: options.referenceImage,
35009
- duration: options.duration,
35010
- aspectRatio: options.aspectRatio,
35011
- mode: "std"
35012
- // std mode for faster generation
35013
- });
35014
- if (result.status !== "failed" && result.id) {
35015
- return {
35016
- taskId: result.id,
35017
- type: options.referenceImage ? "image2video" : "text2video"
35018
- };
35019
- }
35020
- const providerErr = result.error || "Kling returned failed status";
35021
- if (attempt < maxRetries) {
35022
- onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
35023
- await sleep(RETRY_DELAY_MS);
35024
- } else {
35025
- console.error(source_default.dim(`
35026
- [Kling error: ${providerErr}]`));
35027
- }
35028
- } catch (err) {
35029
- const errMsg = err instanceof Error ? err.message : String(err);
35030
- if (attempt < maxRetries) {
35031
- onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
35032
- await sleep(RETRY_DELAY_MS);
35033
- } else {
35034
- console.error(source_default.dim(`
35035
- [Kling error: ${errMsg}]`));
35036
- }
35037
- }
35038
- }
35039
- return null;
35071
+ function safePrefix(prefix) {
35072
+ return (prefix ?? "vibeframe/tmp").replace(/^\/+|\/+$/g, "");
35040
35073
  }
35041
- async function generateVideoWithRetryRunway(runway, segment, referenceImage, options, maxRetries, onProgress) {
35042
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
35043
- try {
35044
- const result = await runway.generateVideo(segment.visuals, {
35045
- prompt: segment.visuals,
35046
- referenceImage,
35047
- duration: options.duration,
35048
- aspectRatio: options.aspectRatio
35049
- });
35050
- if (result.status !== "failed" && result.id) {
35051
- return { taskId: result.id };
35052
- }
35053
- const providerErr = result.error || "Runway returned failed status";
35054
- if (attempt < maxRetries) {
35055
- onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
35056
- await sleep(RETRY_DELAY_MS);
35057
- } else {
35058
- console.error(source_default.dim(`
35059
- [Runway error: ${providerErr}]`));
35060
- }
35061
- } catch (err) {
35062
- const errMsg = err instanceof Error ? err.message : String(err);
35063
- if (attempt < maxRetries) {
35064
- onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
35065
- await sleep(RETRY_DELAY_MS);
35066
- } else {
35067
- console.error(source_default.dim(`
35068
- [Runway error: ${errMsg}]`));
35069
- }
35070
- }
35071
- }
35072
- return null;
35074
+ function extensionFor(opts) {
35075
+ const fromName = opts?.filename ? extname3(opts.filename).replace(/^\./, "") : "";
35076
+ if (fromName) return fromName.toLowerCase();
35077
+ const mime = opts?.mimeType ?? "image/png";
35078
+ if (mime === "image/jpeg") return "jpg";
35079
+ if (mime === "image/webp") return "webp";
35080
+ if (mime === "image/gif") return "gif";
35081
+ return "png";
35073
35082
  }
35074
- async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, onProgress) {
35075
- const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
35076
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
35077
- try {
35078
- const result = await gemini.generateVideo(prompt3, {
35079
- prompt: prompt3,
35080
- referenceImage: options.referenceImage,
35081
- duration: options.duration,
35082
- aspectRatio: options.aspectRatio,
35083
- model: "veo-3.1-fast-generate-preview"
35084
- });
35085
- if (result.status !== "failed" && result.id) {
35086
- return { operationName: result.id };
35087
- }
35088
- const providerErr = result.error || "Veo returned failed status";
35089
- if (attempt < maxRetries) {
35090
- onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
35091
- await sleep(RETRY_DELAY_MS);
35092
- } else {
35093
- console.error(source_default.dim(`
35094
- [Veo error: ${providerErr}]`));
35095
- }
35096
- } catch (err) {
35097
- const errMsg = err instanceof Error ? err.message : String(err);
35098
- if (attempt < maxRetries) {
35099
- onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
35100
- await sleep(RETRY_DELAY_MS);
35101
- } else {
35102
- console.error(source_default.dim(`
35103
- [Veo error: ${errMsg}]`));
35104
- }
35105
- }
35106
- }
35107
- return null;
35083
+ function hmac(key, value) {
35084
+ return createHmac2("sha256", key).update(value).digest();
35108
35085
  }
35109
- var init_video_providers = __esm({
35110
- "src/commands/_shared/video-providers.ts"() {
35111
- "use strict";
35112
- init_source();
35113
- init_video_utils();
35114
- }
35115
- });
35116
-
35117
- // src/commands/ai-script-pipeline.ts
35118
- import { readFile as readFile13, writeFile as writeFile23, unlink as unlink2, rename as rename2 } from "node:fs/promises";
35119
- import { resolve as resolve25, extname as extname3 } from "node:path";
35120
- import { existsSync as existsSync14 } from "node:fs";
35121
- async function executeRegenerateScene(options) {
35122
- const result = {
35123
- success: false,
35124
- regeneratedScenes: [],
35125
- failedScenes: []
35086
+ function sha256Hex(value) {
35087
+ return createHash4("sha256").update(value).digest("hex");
35088
+ }
35089
+ function awsEncode(value) {
35090
+ return encodeURIComponent(value).replace(
35091
+ /[!'()*]/g,
35092
+ (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`
35093
+ );
35094
+ }
35095
+ function presignS3PutUrl(params) {
35096
+ const now = /* @__PURE__ */ new Date();
35097
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
35098
+ const dateStamp = amzDate.slice(0, 8);
35099
+ const host = `${params.bucket}.s3.${params.region}.amazonaws.com`;
35100
+ const credentialScope = `${dateStamp}/${params.region}/s3/aws4_request`;
35101
+ const canonicalUri = `/${params.key.split("/").map(awsEncode).join("/")}`;
35102
+ const query = {
35103
+ "X-Amz-Algorithm": "AWS4-HMAC-SHA256",
35104
+ "X-Amz-Credential": `${params.accessKeyId}/${credentialScope}`,
35105
+ "X-Amz-Date": amzDate,
35106
+ "X-Amz-Expires": String(params.ttlSeconds),
35107
+ "X-Amz-SignedHeaders": "host"
35126
35108
  };
35127
- try {
35128
- const outputDir = resolve25(process.cwd(), options.projectDir);
35129
- if (!existsSync14(outputDir)) {
35130
- return { ...result, error: `Project directory not found: ${outputDir}` };
35131
- }
35132
- const yamlPath = resolve25(outputDir, "storyboard.yaml");
35133
- const jsonPath = resolve25(outputDir, "storyboard.json");
35134
- const storyboardPath = existsSync14(yamlPath) ? yamlPath : existsSync14(jsonPath) ? jsonPath : null;
35135
- if (!storyboardPath) {
35136
- return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
35137
- }
35138
- const storyboardContent = await readFile13(storyboardPath, "utf-8");
35139
- const segments = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
35140
- for (const sceneNum of options.scenes) {
35141
- if (sceneNum < 1 || sceneNum > segments.length) {
35142
- return { ...result, error: `Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.` };
35143
- }
35144
- }
35145
- const regenerateVideo = options.videoOnly || !options.narrationOnly && !options.imageOnly;
35146
- const regenerateNarration = options.narrationOnly || !options.videoOnly && !options.imageOnly;
35147
- const regenerateImage = options.imageOnly || !options.videoOnly && !options.narrationOnly;
35148
- let videoApiKey;
35149
- if (regenerateVideo) {
35150
- const generatorKeyMap = {
35151
- grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
35152
- kling: { envVar: "KLING_API_KEY", name: "Kling" },
35153
- runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
35154
- veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" }
35155
- };
35156
- const generator = options.generator || "grok";
35157
- const genInfo = generatorKeyMap[generator];
35158
- if (!genInfo) {
35159
- return { ...result, error: `Invalid generator: ${generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
35160
- }
35161
- videoApiKey = await getApiKey(genInfo.envVar, genInfo.name) ?? void 0;
35162
- if (!videoApiKey) {
35163
- return { ...result, error: `${genInfo.name} API key required. Run 'vibe setup' or set ${genInfo.envVar} in .env` };
35164
- }
35165
- }
35166
- let imageApiKey;
35167
- if (regenerateImage) {
35168
- const imageProvider = options.imageProvider || "openai";
35169
- const imageKeyMap = {
35170
- openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
35171
- gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
35172
- grok: { envVar: "XAI_API_KEY", name: "xAI" }
35173
- };
35174
- const info = imageKeyMap[imageProvider];
35175
- if (!info) {
35176
- return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
35177
- }
35178
- imageApiKey = await getApiKey(info.envVar, info.name) ?? void 0;
35179
- if (!imageApiKey) {
35180
- return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
35181
- }
35109
+ if (params.sessionToken) {
35110
+ query["X-Amz-Security-Token"] = params.sessionToken;
35111
+ }
35112
+ const canonicalQuery = Object.entries(query).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${awsEncode(key)}=${awsEncode(value)}`).join("&");
35113
+ const canonicalRequest = [
35114
+ "PUT",
35115
+ canonicalUri,
35116
+ canonicalQuery,
35117
+ `host:${host}`,
35118
+ "",
35119
+ "host",
35120
+ "UNSIGNED-PAYLOAD"
35121
+ ].join("\n");
35122
+ const stringToSign = [
35123
+ "AWS4-HMAC-SHA256",
35124
+ amzDate,
35125
+ credentialScope,
35126
+ sha256Hex(canonicalRequest)
35127
+ ].join("\n");
35128
+ const dateKey = hmac(`AWS4${params.secretAccessKey}`, dateStamp);
35129
+ const regionKey = hmac(dateKey, params.region);
35130
+ const serviceKey = hmac(regionKey, "s3");
35131
+ const signingKey = hmac(serviceKey, "aws4_request");
35132
+ const signature = createHmac2("sha256", signingKey).update(stringToSign).digest("hex");
35133
+ return `https://${host}${canonicalUri}?${canonicalQuery}&X-Amz-Signature=${signature}`;
35134
+ }
35135
+ function publicS3Url(params) {
35136
+ if (params.publicBaseUrl) {
35137
+ return `${params.publicBaseUrl.replace(/\/+$/g, "")}/${params.key.split("/").map(awsEncode).join("/")}`;
35138
+ }
35139
+ return `https://${params.bucket}.s3.${params.region}.amazonaws.com/${params.key.split("/").map(awsEncode).join("/")}`;
35140
+ }
35141
+ async function resolveUploadSettings() {
35142
+ const config2 = await loadConfig();
35143
+ const provider = process.env.VIBE_UPLOAD_PROVIDER ?? config2?.upload.provider ?? "imgbb";
35144
+ return {
35145
+ provider,
35146
+ ttlSeconds: envNumber("VIBE_UPLOAD_TTL_SECONDS") ?? config2?.upload.ttlSeconds ?? 3600,
35147
+ s3: {
35148
+ bucket: process.env.VIBE_UPLOAD_S3_BUCKET ?? config2?.upload.s3?.bucket,
35149
+ region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? config2?.upload.s3?.region,
35150
+ prefix: process.env.VIBE_UPLOAD_S3_PREFIX ?? config2?.upload.s3?.prefix,
35151
+ publicBaseUrl: process.env.VIBE_UPLOAD_PUBLIC_BASE_URL ?? config2?.upload.s3?.publicBaseUrl
35182
35152
  }
35183
- let elevenlabsApiKey;
35184
- if (regenerateNarration) {
35185
- elevenlabsApiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs") ?? void 0;
35186
- if (!elevenlabsApiKey) {
35187
- return { ...result, error: "ElevenLabs API key required. Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
35188
- }
35153
+ };
35154
+ }
35155
+ async function resolveUploadHost() {
35156
+ const settings = await resolveUploadSettings();
35157
+ if (settings.provider === "s3") {
35158
+ const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
35159
+ const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
35160
+ const sessionToken = process.env.AWS_SESSION_TOKEN;
35161
+ const { bucket, region } = settings.s3;
35162
+ if (!accessKeyId || !secretAccessKey || !bucket || !region) {
35163
+ throw new Error(
35164
+ "S3 upload host requires AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, and VIBE_UPLOAD_S3_BUCKET."
35165
+ );
35189
35166
  }
35190
- let storyboardMutated = false;
35191
- for (const sceneNum of options.scenes) {
35192
- const segment = segments[sceneNum - 1];
35193
- const narrationPath = resolve25(outputDir, `narration-${sceneNum}.mp3`);
35194
- const imagePath = resolve25(outputDir, `scene-${sceneNum}.png`);
35195
- const videoPath = resolve25(outputDir, `scene-${sceneNum}.mp4`);
35196
- let sceneFailed = false;
35197
- if (regenerateNarration && elevenlabsApiKey) {
35198
- options.onProgress?.(`Scene ${sceneNum}: regenerating narration...`);
35199
- const elevenlabs = new ElevenLabsProvider();
35200
- await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
35201
- const narrationText = segment.narration || segment.description;
35202
- const ttsResult = await elevenlabs.textToSpeech(narrationText, {
35203
- voiceId: options.voice
35167
+ return {
35168
+ provider: "s3",
35169
+ async uploadImage(imageBuffer, opts) {
35170
+ const ext = extensionFor(opts);
35171
+ const prefix = safePrefix(settings.s3.prefix);
35172
+ const key = `${prefix}/${Date.now()}-${randomUUID()}.${ext}`;
35173
+ const presignedUrl = presignS3PutUrl({
35174
+ accessKeyId,
35175
+ secretAccessKey,
35176
+ sessionToken,
35177
+ region,
35178
+ bucket,
35179
+ key,
35180
+ ttlSeconds: settings.ttlSeconds
35204
35181
  });
35205
- if (ttsResult.success && ttsResult.audioBuffer) {
35206
- await writeFile23(narrationPath, ttsResult.audioBuffer);
35207
- segment.duration = await getAudioDuration(narrationPath);
35208
- storyboardMutated = true;
35209
- } else {
35210
- sceneFailed = true;
35211
- }
35212
- }
35213
- if (!sceneFailed && regenerateImage && imageApiKey) {
35214
- options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
35215
- const imageProvider = options.imageProvider || "openai";
35216
- const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
35217
- let imagePrompt = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
35218
- if (characterDesc) {
35219
- imagePrompt = `${imagePrompt}
35220
-
35221
- IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
35222
- }
35223
- let referenceImageBuffer;
35224
- const refSceneNum = options.referenceScene;
35225
- if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
35226
- const refImagePath = resolve25(outputDir, `scene-${refSceneNum}.png`);
35227
- if (existsSync14(refImagePath)) {
35228
- referenceImageBuffer = await readFile13(refImagePath);
35229
- }
35230
- } else if (!refSceneNum) {
35231
- for (let i = 1; i <= segments.length; i++) {
35232
- if (i !== sceneNum) {
35233
- const otherImagePath = resolve25(outputDir, `scene-${i}.png`);
35234
- if (existsSync14(otherImagePath)) {
35235
- referenceImageBuffer = await readFile13(otherImagePath);
35236
- break;
35237
- }
35238
- }
35239
- }
35182
+ const response = await fetch(presignedUrl, {
35183
+ method: "PUT",
35184
+ headers: {
35185
+ "content-type": opts?.mimeType ?? "application/octet-stream"
35186
+ },
35187
+ body: new Uint8Array(imageBuffer)
35188
+ });
35189
+ if (!response.ok) {
35190
+ throw new Error(`S3 upload failed (${response.status}): ${response.statusText}`);
35240
35191
  }
35241
- const dalleImageSizes = {
35242
- "16:9": "1536x1024",
35243
- "9:16": "1024x1536",
35244
- "1:1": "1024x1024"
35192
+ return {
35193
+ provider: "s3",
35194
+ url: publicS3Url({ region, bucket, key, publicBaseUrl: settings.s3.publicBaseUrl }),
35195
+ expiresAt: new Date(Date.now() + settings.ttlSeconds * 1e3).toISOString()
35245
35196
  };
35246
- let imageBuffer;
35247
- let imageUrl;
35248
- if (imageProvider === "openai") {
35249
- const openaiImage = new OpenAIImageProvider();
35250
- await openaiImage.initialize({ apiKey: imageApiKey });
35251
- const imageResult = await openaiImage.generateImage(imagePrompt, {
35252
- size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
35253
- quality: "standard"
35254
- });
35255
- if (imageResult.success && imageResult.images?.[0]) {
35256
- const img = imageResult.images[0];
35257
- if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
35258
- else if (img.url) imageUrl = img.url;
35259
- }
35260
- } else if (imageProvider === "gemini") {
35261
- const gemini = new GeminiProvider();
35262
- await gemini.initialize({ apiKey: imageApiKey });
35263
- if (referenceImageBuffer) {
35264
- const simplifiedVisuals = segment.visuals.split(/[,.]/).find(
35265
- (part) => part.includes("standing") || part.includes("sitting") || part.includes("walking") || part.includes("lying") || part.includes("reaching") || part.includes("looking") || part.includes("working") || part.includes("coding") || part.includes("typing")
35266
- ) || segment.visuals.split(".")[0];
35267
- const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
35268
-
35269
- REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
35270
-
35271
- NEW SCENE: ${simplifiedVisuals}
35272
-
35273
- CRITICAL RULES:
35274
- 1. Show ONLY ONE person - the exact same individual from the reference image
35275
- 2. The person must have the IDENTICAL face, hair style, and body type
35276
- 3. Do NOT show multiple people or duplicate the character
35277
- 4. Create a single moment in time, one pose, one action
35278
- 5. Match the art style and quality of the reference image
35279
-
35280
- Generate the single-person scene image now.`;
35281
- const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
35282
- aspectRatio: options.aspectRatio || "16:9"
35283
- });
35284
- if (imageResult.success && imageResult.images?.[0]?.base64) {
35285
- imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
35286
- }
35287
- } else {
35288
- const imageResult = await gemini.generateImage(imagePrompt, {
35289
- aspectRatio: options.aspectRatio || "16:9"
35290
- });
35291
- if (imageResult.success && imageResult.images?.[0]?.base64) {
35292
- imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
35293
- }
35294
- }
35295
- } else if (imageProvider === "grok") {
35296
- const grok = new GrokProvider();
35297
- await grok.initialize({ apiKey: imageApiKey });
35298
- const imageResult = await grok.generateImage(imagePrompt, {
35299
- aspectRatio: options.aspectRatio || "16:9"
35300
- });
35301
- if (imageResult.success && imageResult.images?.[0]) {
35302
- const img = imageResult.images[0];
35303
- if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
35304
- else if (img.url) imageUrl = img.url;
35305
- }
35306
- }
35307
- if (imageBuffer) {
35308
- await writeFile23(imagePath, imageBuffer);
35309
- } else if (imageUrl) {
35310
- const response = await fetch(imageUrl);
35311
- const buffer = Buffer.from(await response.arrayBuffer());
35312
- await writeFile23(imagePath, buffer);
35313
- } else {
35314
- sceneFailed = true;
35315
- }
35316
- }
35317
- if (sceneFailed) {
35318
- result.failedScenes.push(sceneNum);
35319
- continue;
35320
35197
  }
35321
- if (regenerateVideo && videoApiKey) {
35322
- options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
35323
- if (!existsSync14(imagePath)) {
35324
- result.failedScenes.push(sceneNum);
35325
- continue;
35326
- }
35327
- const imageBuffer = await readFile13(imagePath);
35328
- const videoDuration = segment.duration > 5 ? 10 : 5;
35329
- const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
35330
- if (options.generator === "grok") {
35331
- const grok = new GrokProvider();
35332
- await grok.initialize({ apiKey: videoApiKey });
35333
- const ext = extname3(imagePath).toLowerCase().slice(1);
35334
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
35335
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
35336
- const grokDuration = Math.min(15, Math.max(1, segment.duration));
35337
- const taskResult = await generateVideoWithRetryGrok(
35338
- grok,
35339
- segment,
35340
- {
35341
- duration: grokDuration,
35342
- aspectRatio: options.aspectRatio || "16:9",
35343
- referenceImage
35344
- },
35345
- maxRetries
35346
- );
35347
- if (taskResult) {
35348
- try {
35349
- const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
35350
- if (waitResult.status === "completed" && waitResult.videoUrl) {
35351
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
35352
- await writeFile23(videoPath, buffer);
35353
- const targetDuration = segment.duration;
35354
- const actualVideoDuration = await getVideoDuration(videoPath);
35355
- if (actualVideoDuration < targetDuration - 0.1) {
35356
- const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
35357
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
35358
- await unlink2(videoPath);
35359
- await rename2(extendedPath, videoPath);
35360
- }
35361
- result.regeneratedScenes.push(sceneNum);
35362
- } else {
35363
- logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
35364
- result.failedScenes.push(sceneNum);
35365
- }
35366
- } catch (err) {
35367
- logSceneFailure("Grok", `scene ${sceneNum}`, err);
35368
- result.failedScenes.push(sceneNum);
35369
- }
35370
- } else {
35371
- result.failedScenes.push(sceneNum);
35372
- }
35373
- } else if (options.generator === "veo") {
35374
- const veo = new GeminiProvider();
35375
- await veo.initialize({ apiKey: videoApiKey });
35376
- const ext = extname3(imagePath).toLowerCase().slice(1);
35377
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
35378
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
35379
- const veoDuration = segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4;
35380
- const taskResult = await generateVideoWithRetryVeo(
35381
- veo,
35382
- segment,
35383
- {
35384
- duration: veoDuration,
35385
- aspectRatio: options.aspectRatio || "16:9",
35386
- referenceImage
35387
- },
35388
- maxRetries
35389
- );
35390
- if (taskResult) {
35391
- try {
35392
- const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
35393
- if (waitResult.status === "completed" && waitResult.videoUrl) {
35394
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
35395
- await writeFile23(videoPath, buffer);
35396
- const targetDuration = segment.duration;
35397
- const actualVideoDuration = await getVideoDuration(videoPath);
35398
- if (actualVideoDuration < targetDuration - 0.1) {
35399
- const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
35400
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
35401
- await unlink2(videoPath);
35402
- await rename2(extendedPath, videoPath);
35403
- }
35404
- result.regeneratedScenes.push(sceneNum);
35405
- } else {
35406
- logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
35407
- result.failedScenes.push(sceneNum);
35408
- }
35409
- } catch (err) {
35410
- logSceneFailure("Veo", `scene ${sceneNum}`, err);
35411
- result.failedScenes.push(sceneNum);
35412
- }
35413
- } else {
35414
- result.failedScenes.push(sceneNum);
35415
- }
35416
- } else if (options.generator === "kling" || !options.generator) {
35417
- const kling = new KlingProvider();
35418
- await kling.initialize({ apiKey: videoApiKey });
35419
- if (!kling.isConfigured()) {
35420
- result.failedScenes.push(sceneNum);
35421
- continue;
35422
- }
35423
- const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
35424
- let imageUrl;
35425
- if (imgbbApiKey) {
35426
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
35427
- if (uploadResult.success && uploadResult.url) {
35428
- imageUrl = uploadResult.url;
35429
- }
35430
- }
35431
- const taskResult = await generateVideoWithRetryKling(
35432
- kling,
35433
- segment,
35434
- {
35435
- duration: videoDuration,
35436
- aspectRatio: options.aspectRatio || "16:9",
35437
- referenceImage: imageUrl
35438
- },
35439
- maxRetries
35440
- );
35441
- if (taskResult) {
35442
- try {
35443
- const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
35444
- if (waitResult.status === "completed" && waitResult.videoUrl) {
35445
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
35446
- await writeFile23(videoPath, buffer);
35447
- await extendVideoToTarget(
35448
- videoPath,
35449
- segment.duration,
35450
- outputDir,
35451
- `Scene ${sceneNum}`,
35452
- {
35453
- kling,
35454
- videoId: waitResult.videoId,
35455
- onProgress: options.onProgress
35456
- }
35457
- );
35458
- result.regeneratedScenes.push(sceneNum);
35459
- } else {
35460
- logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
35461
- result.failedScenes.push(sceneNum);
35462
- }
35463
- } catch (err) {
35464
- logSceneFailure("Kling", `scene ${sceneNum}`, err);
35465
- result.failedScenes.push(sceneNum);
35466
- }
35467
- } else {
35468
- result.failedScenes.push(sceneNum);
35469
- }
35470
- } else {
35471
- const runway = new RunwayProvider();
35472
- await runway.initialize({ apiKey: videoApiKey });
35473
- const ext = extname3(imagePath).toLowerCase().slice(1);
35474
- const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
35475
- const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
35476
- const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : options.aspectRatio || "16:9";
35477
- const taskResult = await generateVideoWithRetryRunway(
35478
- runway,
35479
- segment,
35480
- referenceImage,
35481
- { duration: videoDuration, aspectRatio },
35482
- maxRetries
35483
- );
35484
- if (taskResult) {
35485
- try {
35486
- const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
35487
- if (waitResult.status === "completed" && waitResult.videoUrl) {
35488
- const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
35489
- await writeFile23(videoPath, buffer);
35490
- const targetDuration = segment.duration;
35491
- const actualVideoDuration = await getVideoDuration(videoPath);
35492
- if (actualVideoDuration < targetDuration - 0.1) {
35493
- const extendedPath = resolve25(outputDir, `scene-${sceneNum}-extended.mp4`);
35494
- await extendVideoNaturally(videoPath, targetDuration, extendedPath);
35495
- await unlink2(videoPath);
35496
- await rename2(extendedPath, videoPath);
35497
- }
35498
- result.regeneratedScenes.push(sceneNum);
35499
- } else {
35500
- logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
35501
- result.failedScenes.push(sceneNum);
35502
- }
35503
- } catch (err) {
35504
- logSceneFailure("Runway", `scene ${sceneNum}`, err);
35505
- result.failedScenes.push(sceneNum);
35506
- }
35507
- } else {
35508
- result.failedScenes.push(sceneNum);
35509
- }
35510
- }
35511
- } else if (!sceneFailed) {
35512
- result.regeneratedScenes.push(sceneNum);
35198
+ };
35199
+ }
35200
+ return {
35201
+ provider: "imgbb",
35202
+ async uploadImage(imageBuffer) {
35203
+ const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
35204
+ if (!imgbbKey) {
35205
+ throw new Error("IMGBB_API_KEY required for image-to-video uploads.");
35513
35206
  }
35514
- }
35515
- if (storyboardMutated) {
35516
- let currentTime = 0;
35517
- for (const segment of segments) {
35518
- segment.startTime = currentTime;
35519
- currentTime += segment.duration;
35207
+ const result = await uploadToImgbb(imageBuffer, imgbbKey);
35208
+ if (!result.success || !result.url) {
35209
+ throw new Error(`ImgBB upload failed: ${result.error ?? "unknown error"}`);
35520
35210
  }
35521
- const serialized = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.stringify)({ scenes: segments }, { indent: 2 }) : JSON.stringify(segments, null, 2);
35522
- await writeFile23(storyboardPath, serialized, "utf-8");
35211
+ return { provider: "imgbb", url: result.url };
35523
35212
  }
35524
- result.success = result.failedScenes.length === 0;
35525
- return result;
35526
- } catch (error) {
35527
- return {
35528
- ...result,
35529
- error: error instanceof Error ? error.message : String(error)
35530
- };
35531
- }
35213
+ };
35532
35214
  }
35533
- var import_yaml5;
35534
- var init_ai_script_pipeline = __esm({
35535
- "src/commands/ai-script-pipeline.ts"() {
35215
+ var init_upload_host = __esm({
35216
+ "src/utils/upload-host.ts"() {
35536
35217
  "use strict";
35537
- import_yaml5 = __toESM(require_dist(), 1);
35538
- init_dist2();
35539
- init_api_key();
35540
35218
  init_config();
35541
- init_audio();
35542
- init_ai_helpers();
35543
35219
  init_video_utils();
35544
- init_video_providers();
35545
35220
  }
35546
35221
  });
35547
35222
 
35548
35223
  // src/commands/generate/video.ts
35549
- import { resolve as resolve26 } from "node:path";
35550
- import { readFile as readFile14, writeFile as writeFile24 } from "node:fs/promises";
35224
+ import { resolve as resolve25 } from "node:path";
35225
+ import { readFile as readFile13, writeFile as writeFile23 } from "node:fs/promises";
35551
35226
  function registerVideoCommand(parent) {
35552
- parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a deprecated v0.x alias for seedance and will be removed in 1.0.").option("-k, --api-key <key>", "API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
35227
+ parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option(
35228
+ "-p, --provider <provider>",
35229
+ "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a deprecated v0.x alias for seedance and will be removed in 1.0."
35230
+ ).option(
35231
+ "-k, --api-key <key>",
35232
+ "API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)"
35233
+ ).option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
35553
35234
  "-d, --duration <sec>",
35554
35235
  "Duration in seconds. Seedance accepts 4-15; Kling accepts 5 or 10; Veo maps to 6 or 8.",
35555
35236
  "5"
35556
- ).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
35237
+ ).option(
35238
+ "-r, --ratio <ratio>",
35239
+ "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)"
35240
+ ).option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option(
35241
+ "--seedance-model <model>",
35242
+ "Seedance variant: quality or fast (fal.ai only)",
35243
+ "quality"
35244
+ ).option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option(
35245
+ "--ref-images <paths...>",
35246
+ "Reference images for character consistency (Veo 3.1 only, max 3)"
35247
+ ).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(
35248
+ "--runway-model <model>",
35249
+ "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)",
35250
+ "gen4.5"
35251
+ ).option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText(
35252
+ "after",
35253
+ `
35557
35254
  Examples:
35558
35255
  $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
35559
35256
  $ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
35560
35257
  $ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
35561
35258
  $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
35562
35259
  $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
35563
- $ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`).action(async (prompt3, options) => {
35260
+ $ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`
35261
+ ).action(async (prompt3, options) => {
35564
35262
  const startedAt = Date.now();
35565
35263
  try {
35566
35264
  if (!prompt3) {
@@ -35571,10 +35269,7 @@ Examples:
35571
35269
  }
35572
35270
  } else {
35573
35271
  exitWithError(
35574
- usageError(
35575
- "Prompt argument is required.",
35576
- "Usage: vibe generate video <prompt>"
35577
- )
35272
+ usageError("Prompt argument is required.", "Usage: vibe generate video <prompt>")
35578
35273
  );
35579
35274
  }
35580
35275
  }
@@ -35582,13 +35277,16 @@ Examples:
35582
35277
  if (options.output) {
35583
35278
  validateOutputPath(options.output);
35584
35279
  }
35280
+ await loadProviderDefaults();
35585
35281
  if (options.duration !== void 0) {
35586
35282
  const d = parseFloat(options.duration);
35587
35283
  if (!Number.isFinite(d) || d <= 0 || d > 60) {
35588
- exitWithError(usageError(
35589
- `Invalid --duration: ${options.duration}`,
35590
- "Must be a positive number \u2264 60 seconds."
35591
- ));
35284
+ exitWithError(
35285
+ usageError(
35286
+ `Invalid --duration: ${options.duration}`,
35287
+ "Must be a positive number \u2264 60 seconds."
35288
+ )
35289
+ );
35592
35290
  }
35593
35291
  }
35594
35292
  const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
@@ -35630,10 +35328,12 @@ Examples:
35630
35328
  provider = resolved?.name ?? "grok";
35631
35329
  }
35632
35330
  let referenceImage;
35331
+ let referenceImageBuffer;
35332
+ let referenceImageMimeType;
35633
35333
  let isImageToVideo = false;
35634
35334
  if (options.image) {
35635
- const imagePath = resolve26(process.cwd(), options.image);
35636
- const imageBuffer = await readFile14(imagePath);
35335
+ const imagePath = resolve25(process.cwd(), options.image);
35336
+ const imageBuffer = await readFile13(imagePath);
35637
35337
  const ext = options.image.toLowerCase().split(".").pop();
35638
35338
  const mimeTypes2 = {
35639
35339
  jpg: "image/jpeg",
@@ -35643,6 +35343,8 @@ Examples:
35643
35343
  webp: "image/webp"
35644
35344
  };
35645
35345
  const mimeType = mimeTypes2[ext || "png"] || "image/png";
35346
+ referenceImageBuffer = imageBuffer;
35347
+ referenceImageMimeType = mimeType;
35646
35348
  referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
35647
35349
  isImageToVideo = true;
35648
35350
  if (!options.ratio) {
@@ -35767,20 +35469,22 @@ Examples:
35767
35469
  }
35768
35470
  let klingImage = referenceImage;
35769
35471
  if (klingImage && klingImage.startsWith("data:")) {
35770
- spinner2.text = "Uploading image to ImgBB for Kling...";
35771
- const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
35772
- if (!imgbbKey) {
35773
- spinner2.fail("ImgBB API key required");
35774
- exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
35775
- }
35776
- const base64Data = klingImage.split(",")[1];
35777
- const imageBuffer = Buffer.from(base64Data, "base64");
35778
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbKey);
35779
- if (!uploadResult.success || !uploadResult.url) {
35780
- spinner2.fail("ImgBB upload failed");
35781
- exitWithError(apiError(`ImgBB upload failed: ${uploadResult.error}`, true));
35782
- }
35783
- klingImage = uploadResult.url;
35472
+ try {
35473
+ const uploadHost = await resolveUploadHost();
35474
+ spinner2.text = `Uploading image via ${uploadHost.provider} for Kling...`;
35475
+ const upload = await uploadHost.uploadImage(referenceImageBuffer, {
35476
+ filename: options.image,
35477
+ mimeType: referenceImageMimeType
35478
+ });
35479
+ klingImage = upload.url;
35480
+ } catch (err) {
35481
+ spinner2.fail("Image upload failed");
35482
+ const message = err instanceof Error ? err.message : String(err);
35483
+ if (message.includes("IMGBB_API_KEY")) {
35484
+ exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
35485
+ }
35486
+ exitWithError(apiError(message, true));
35487
+ }
35784
35488
  spinner2.text = "Starting video generation...";
35785
35489
  }
35786
35490
  result = await kling.generateVideo(prompt3, {
@@ -35835,8 +35539,8 @@ Examples:
35835
35539
  const veoDuration = parseInt(options.duration) <= 6 ? 6 : 8;
35836
35540
  let lastFrame;
35837
35541
  if (options.lastFrame) {
35838
- const lastFramePath = resolve26(process.cwd(), options.lastFrame);
35839
- const lastFrameBuffer = await readFile14(lastFramePath);
35542
+ const lastFramePath = resolve25(process.cwd(), options.lastFrame);
35543
+ const lastFrameBuffer = await readFile13(lastFramePath);
35840
35544
  const ext = options.lastFrame.toLowerCase().split(".").pop();
35841
35545
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
35842
35546
  lastFrame = `data:${mimeType};base64,${lastFrameBuffer.toString("base64")}`;
@@ -35845,8 +35549,8 @@ Examples:
35845
35549
  if (options.refImages && options.refImages.length > 0) {
35846
35550
  refImages = [];
35847
35551
  for (const refPath of options.refImages.slice(0, 3)) {
35848
- const absRefPath = resolve26(process.cwd(), refPath);
35849
- const refBuffer = await readFile14(absRefPath);
35552
+ const absRefPath = resolve25(process.cwd(), refPath);
35553
+ const refBuffer = await readFile13(absRefPath);
35850
35554
  const ext = refPath.toLowerCase().split(".").pop();
35851
35555
  const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : `image/${ext || "png"}`;
35852
35556
  refImages.push({ base64: refBuffer.toString("base64"), mimeType });
@@ -35927,20 +35631,22 @@ Examples:
35927
35631
  await fal.initialize({ apiKey });
35928
35632
  let falImage = referenceImage;
35929
35633
  if (falImage && falImage.startsWith("data:")) {
35930
- spinner2.text = "Uploading image to ImgBB for Seedance...";
35931
- const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
35932
- if (!imgbbKey) {
35933
- spinner2.fail("ImgBB API key required for Seedance image-to-video");
35934
- exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
35935
- }
35936
- const base64Data = falImage.split(",")[1];
35937
- const imageBuffer = Buffer.from(base64Data, "base64");
35938
- const uploadResult = await uploadToImgbb(imageBuffer, imgbbKey);
35939
- if (!uploadResult.success || !uploadResult.url) {
35940
- spinner2.fail("ImgBB upload failed");
35941
- exitWithError(apiError(`ImgBB upload failed: ${uploadResult.error}`, true));
35634
+ try {
35635
+ const uploadHost = await resolveUploadHost();
35636
+ spinner2.text = `Uploading image via ${uploadHost.provider} for Seedance...`;
35637
+ const upload = await uploadHost.uploadImage(referenceImageBuffer, {
35638
+ filename: options.image,
35639
+ mimeType: referenceImageMimeType
35640
+ });
35641
+ falImage = upload.url;
35642
+ } catch (err) {
35643
+ spinner2.fail("Image upload failed");
35644
+ const message = err instanceof Error ? err.message : String(err);
35645
+ if (message.includes("IMGBB_API_KEY")) {
35646
+ exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
35647
+ }
35648
+ exitWithError(apiError(message, true));
35942
35649
  }
35943
- falImage = uploadResult.url;
35944
35650
  }
35945
35651
  spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
35946
35652
  const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
@@ -35964,8 +35670,8 @@ Examples:
35964
35670
  let outputPath;
35965
35671
  if (options.output && finalResult.videoUrl) {
35966
35672
  const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
35967
- outputPath = resolve26(process.cwd(), options.output);
35968
- await writeFile24(outputPath, buffer);
35673
+ outputPath = resolve25(process.cwd(), options.output);
35674
+ await writeFile23(outputPath, buffer);
35969
35675
  }
35970
35676
  outputSuccess({
35971
35677
  command: "generate video",
@@ -35992,14 +35698,12 @@ Examples:
35992
35698
  const downloadSpinner = ora("Downloading video...").start();
35993
35699
  try {
35994
35700
  const buffer = await downloadVideo(finalResult.videoUrl, apiKey);
35995
- const outputPath = resolve26(process.cwd(), options.output);
35996
- await writeFile24(outputPath, buffer);
35701
+ const outputPath = resolve25(process.cwd(), options.output);
35702
+ await writeFile23(outputPath, buffer);
35997
35703
  downloadSpinner.succeed(source_default.green(`Saved to: ${outputPath}`));
35998
35704
  } catch (err) {
35999
35705
  downloadSpinner.fail(
36000
- source_default.red(
36001
- `Failed to download video: ${err instanceof Error ? err.message : err}`
36002
- )
35706
+ source_default.red(`Failed to download video: ${err instanceof Error ? err.message : err}`)
36003
35707
  );
36004
35708
  }
36005
35709
  }
@@ -36017,11 +35721,10 @@ var init_video = __esm({
36017
35721
  init_dist2();
36018
35722
  init_api_key();
36019
35723
  init_tty();
36020
- init_config();
36021
35724
  init_output();
36022
35725
  init_validate();
36023
35726
  init_provider_resolver();
36024
- init_ai_script_pipeline();
35727
+ init_upload_host();
36025
35728
  init_ai_helpers();
36026
35729
  }
36027
35730
  });
@@ -36126,8 +35829,8 @@ Run 'vibe schema generate.<command>' for structured parameter info.
36126
35829
  });
36127
35830
 
36128
35831
  // src/commands/_shared/edit/silence-cut.ts
36129
- import { existsSync as existsSync15 } from "node:fs";
36130
- import { readFile as readFile15 } from "node:fs/promises";
35832
+ import { existsSync as existsSync14 } from "node:fs";
35833
+ import { readFile as readFile14 } from "node:fs/promises";
36131
35834
  async function detectSilencePeriods(videoPath, noiseThreshold, minDuration) {
36132
35835
  const totalDuration = await getVideoDuration(videoPath);
36133
35836
  const { stdout, stderr } = await execSafe(
@@ -36177,7 +35880,7 @@ async function detectSilencePeriodsWithGemini(videoPath, minDuration, options) {
36177
35880
  }
36178
35881
  const gemini = new GeminiProvider();
36179
35882
  await gemini.initialize({ apiKey: geminiApiKey });
36180
- const videoBuffer = await readFile15(videoPath);
35883
+ const videoBuffer = await readFile14(videoPath);
36181
35884
  const modelMap = {
36182
35885
  flash: "gemini-3-flash-preview",
36183
35886
  "flash-2.5": "gemini-2.5-flash",
@@ -36258,7 +35961,7 @@ async function executeSilenceCut(options) {
36258
35961
  analyzeOnly = false,
36259
35962
  useGemini = false
36260
35963
  } = options;
36261
- if (!existsSync15(videoPath)) {
35964
+ if (!existsSync14(videoPath)) {
36262
35965
  return { success: false, error: `Video not found: ${videoPath}` };
36263
35966
  }
36264
35967
  if (!commandExists("ffmpeg")) {
@@ -36366,11 +36069,11 @@ var init_silence_cut = __esm({
36366
36069
  });
36367
36070
 
36368
36071
  // src/commands/_shared/edit/jump-cut.ts
36369
- import { existsSync as existsSync16 } from "node:fs";
36370
- import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir12 } from "node:fs/promises";
36072
+ import { existsSync as existsSync15 } from "node:fs";
36073
+ import { readFile as readFile15, writeFile as writeFile24, mkdir as mkdir12 } from "node:fs/promises";
36371
36074
  import { join as join8 } from "node:path";
36372
36075
  async function transcribeWithWords(audioPath, apiKey, language) {
36373
- const audioBuffer = await readFile16(audioPath);
36076
+ const audioBuffer = await readFile15(audioPath);
36374
36077
  const audioBlob = new Blob([audioBuffer]);
36375
36078
  const formData = new FormData();
36376
36079
  formData.append("file", audioBlob, "audio.wav");
@@ -36429,7 +36132,7 @@ async function executeJumpCut(options) {
36429
36132
  analyzeOnly = false,
36430
36133
  apiKey
36431
36134
  } = options;
36432
- if (!existsSync16(videoPath)) {
36135
+ if (!existsSync15(videoPath)) {
36433
36136
  return { success: false, error: `Video not found: ${videoPath}` };
36434
36137
  }
36435
36138
  if (!commandExists("ffmpeg")) {
@@ -36526,7 +36229,7 @@ async function executeJumpCut(options) {
36526
36229
  }
36527
36230
  const concatList = segmentPaths.map((p) => `file '${p}'`).join("\n");
36528
36231
  const listPath = join8(tmpDir, "concat.txt");
36529
- await writeFile25(listPath, concatList);
36232
+ await writeFile24(listPath, concatList);
36530
36233
  await execSafe(
36531
36234
  "ffmpeg",
36532
36235
  [
@@ -36715,8 +36418,8 @@ var init_helpers = __esm({
36715
36418
  });
36716
36419
 
36717
36420
  // src/commands/_shared/edit/caption.ts
36718
- import { existsSync as existsSync17 } from "node:fs";
36719
- import { readFile as readFile17, writeFile as writeFile26, mkdir as mkdir13 } from "node:fs/promises";
36421
+ import { existsSync as existsSync16 } from "node:fs";
36422
+ import { readFile as readFile16, writeFile as writeFile25, mkdir as mkdir13 } from "node:fs/promises";
36720
36423
  import { dirname as dirname8, basename as basename6, extname as extname4, join as join9 } from "node:path";
36721
36424
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
36722
36425
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
@@ -36745,7 +36448,7 @@ async function executeCaption(options) {
36745
36448
  position = "bottom",
36746
36449
  apiKey
36747
36450
  } = options;
36748
- if (!existsSync17(videoPath)) {
36451
+ if (!existsSync16(videoPath)) {
36749
36452
  return { success: false, error: `Video not found: ${videoPath}` };
36750
36453
  }
36751
36454
  if (!commandExists("ffmpeg")) {
@@ -36786,7 +36489,7 @@ async function executeCaption(options) {
36786
36489
  );
36787
36490
  const whisper = new WhisperProvider();
36788
36491
  await whisper.initialize({ apiKey: openaiKey });
36789
- const audioBuffer = await readFile17(audioPath);
36492
+ const audioBuffer = await readFile16(audioPath);
36790
36493
  const audioBlob = new Blob([audioBuffer]);
36791
36494
  const transcriptResult = await whisper.transcribe(audioBlob, language);
36792
36495
  if (transcriptResult.status === "failed" || !transcriptResult.segments || transcriptResult.segments.length === 0) {
@@ -36796,7 +36499,7 @@ async function executeCaption(options) {
36796
36499
  };
36797
36500
  }
36798
36501
  const srtContent = formatSRT(transcriptResult.segments);
36799
- await writeFile26(srtPath, srtContent);
36502
+ await writeFile25(srtPath, srtContent);
36800
36503
  const { width, height } = await getVideoResolution(videoPath);
36801
36504
  const fontSize = customFontSize || Math.round(height / 18);
36802
36505
  let hasSubtitles = false;
@@ -36838,7 +36541,7 @@ async function executeCaption(options) {
36838
36541
  outputDir2,
36839
36542
  basename6(outputPath, extname4(outputPath)) + ".srt"
36840
36543
  );
36841
- await writeFile26(outputSrtPath2, srtContent);
36544
+ await writeFile25(outputSrtPath2, srtContent);
36842
36545
  return { success: false, error: `${remotionErr}
36843
36546
  SRT saved to: ${outputSrtPath2}` };
36844
36547
  }
@@ -36877,7 +36580,7 @@ SRT saved to: ${outputSrtPath2}` };
36877
36580
  outputDir2,
36878
36581
  basename6(outputPath, extname4(outputPath)) + ".srt"
36879
36582
  );
36880
- await writeFile26(outputSrtPath2, srtContent);
36583
+ await writeFile25(outputSrtPath2, srtContent);
36881
36584
  return {
36882
36585
  success: false,
36883
36586
  error: `${renderResult.error}
@@ -36890,7 +36593,7 @@ SRT saved to: ${outputSrtPath2}`
36890
36593
  outputDir,
36891
36594
  basename6(outputPath, extname4(outputPath)) + ".srt"
36892
36595
  );
36893
- await writeFile26(outputSrtPath, srtContent);
36596
+ await writeFile25(outputSrtPath, srtContent);
36894
36597
  return {
36895
36598
  success: true,
36896
36599
  outputPath,
@@ -36923,10 +36626,10 @@ var init_caption = __esm({
36923
36626
  });
36924
36627
 
36925
36628
  // src/commands/_shared/edit/noise-reduce.ts
36926
- import { existsSync as existsSync18 } from "node:fs";
36629
+ import { existsSync as existsSync17 } from "node:fs";
36927
36630
  async function executeNoiseReduce(options) {
36928
36631
  const { inputPath, outputPath, strength = "medium", noiseFloor } = options;
36929
- if (!existsSync18(inputPath)) {
36632
+ if (!existsSync17(inputPath)) {
36930
36633
  return { success: false, error: `File not found: ${inputPath}` };
36931
36634
  }
36932
36635
  if (!commandExists("ffmpeg")) {
@@ -36983,7 +36686,7 @@ var init_noise_reduce = __esm({
36983
36686
  });
36984
36687
 
36985
36688
  // src/commands/_shared/edit/fade.ts
36986
- import { existsSync as existsSync19 } from "node:fs";
36689
+ import { existsSync as existsSync18 } from "node:fs";
36987
36690
  async function executeFade(options) {
36988
36691
  const {
36989
36692
  videoPath,
@@ -36993,7 +36696,7 @@ async function executeFade(options) {
36993
36696
  audioOnly = false,
36994
36697
  videoOnly = false
36995
36698
  } = options;
36996
- if (!existsSync19(videoPath)) {
36699
+ if (!existsSync18(videoPath)) {
36997
36700
  return { success: false, error: `Video not found: ${videoPath}` };
36998
36701
  }
36999
36702
  if (!commandExists("ffmpeg")) {
@@ -37060,8 +36763,8 @@ var init_fade = __esm({
37060
36763
  });
37061
36764
 
37062
36765
  // src/commands/_shared/edit/translate-srt.ts
37063
- import { existsSync as existsSync20 } from "node:fs";
37064
- import { readFile as readFile18, writeFile as writeFile27 } from "node:fs/promises";
36766
+ import { existsSync as existsSync19 } from "node:fs";
36767
+ import { readFile as readFile17, writeFile as writeFile26 } from "node:fs/promises";
37065
36768
  async function executeTranslateSrt(options) {
37066
36769
  const {
37067
36770
  srtPath,
@@ -37071,11 +36774,11 @@ async function executeTranslateSrt(options) {
37071
36774
  sourceLanguage,
37072
36775
  apiKey
37073
36776
  } = options;
37074
- if (!existsSync20(srtPath)) {
36777
+ if (!existsSync19(srtPath)) {
37075
36778
  return { success: false, error: `SRT file not found: ${srtPath}` };
37076
36779
  }
37077
36780
  try {
37078
- const srtContent = await readFile18(srtPath, "utf-8");
36781
+ const srtContent = await readFile17(srtPath, "utf-8");
37079
36782
  const segments = parseSRT(srtContent);
37080
36783
  if (segments.length === 0) {
37081
36784
  return { success: false, error: "No subtitle segments found in SRT file" };
@@ -37165,7 +36868,7 @@ ${textsToTranslate}`;
37165
36868
  }
37166
36869
  }
37167
36870
  const translatedSrt = formatSRT(translatedSegments);
37168
- await writeFile27(outputPath, translatedSrt);
36871
+ await writeFile26(outputPath, translatedSrt);
37169
36872
  return {
37170
36873
  success: true,
37171
36874
  outputPath,
@@ -37188,8 +36891,8 @@ var init_translate_srt = __esm({
37188
36891
  });
37189
36892
 
37190
36893
  // src/commands/_shared/edit/text-overlay.ts
37191
- import { existsSync as existsSync21 } from "node:fs";
37192
- import { resolve as resolve27 } from "node:path";
36894
+ import { existsSync as existsSync20 } from "node:fs";
36895
+ import { resolve as resolve26 } from "node:path";
37193
36896
  function detectSystemFont() {
37194
36897
  const platform3 = process.platform;
37195
36898
  if (platform3 === "darwin") {
@@ -37199,7 +36902,7 @@ function detectSystemFont() {
37199
36902
  "/Library/Fonts/Arial.ttf"
37200
36903
  ];
37201
36904
  for (const f of candidates) {
37202
- if (existsSync21(f)) return f;
36905
+ if (existsSync20(f)) return f;
37203
36906
  }
37204
36907
  } else if (platform3 === "linux") {
37205
36908
  const candidates = [
@@ -37208,7 +36911,7 @@ function detectSystemFont() {
37208
36911
  "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf"
37209
36912
  ];
37210
36913
  for (const f of candidates) {
37211
- if (existsSync21(f)) return f;
36914
+ if (existsSync20(f)) return f;
37212
36915
  }
37213
36916
  } else if (platform3 === "win32") {
37214
36917
  const candidates = [
@@ -37216,7 +36919,7 @@ function detectSystemFont() {
37216
36919
  "C:\\Windows\\Fonts\\segoeui.ttf"
37217
36920
  ];
37218
36921
  for (const f of candidates) {
37219
- if (existsSync21(f)) return f;
36922
+ if (existsSync20(f)) return f;
37220
36923
  }
37221
36924
  }
37222
36925
  return null;
@@ -37235,9 +36938,9 @@ async function applyTextOverlays(options) {
37235
36938
  if (!texts || texts.length === 0) {
37236
36939
  return { success: false, error: "No texts provided" };
37237
36940
  }
37238
- const absVideoPath = resolve27(process.cwd(), videoPath);
37239
- const absOutputPath = resolve27(process.cwd(), outputPath);
37240
- if (!existsSync21(absVideoPath)) {
36941
+ const absVideoPath = resolve26(process.cwd(), videoPath);
36942
+ const absOutputPath = resolve26(process.cwd(), outputPath);
36943
+ if (!existsSync20(absVideoPath)) {
37241
36944
  return { success: false, error: `Video not found: ${absVideoPath}` };
37242
36945
  }
37243
36946
  if (!commandExists("ffmpeg")) {
@@ -37417,8 +37120,8 @@ var init_ai_edit = __esm({
37417
37120
  });
37418
37121
 
37419
37122
  // src/commands/ai-edit-cli.ts
37420
- import { resolve as resolve28, extname as extname5, basename as basename7 } from "node:path";
37421
- import { existsSync as existsSync22 } from "node:fs";
37123
+ import { resolve as resolve27, extname as extname5, basename as basename7 } from "node:path";
37124
+ import { existsSync as existsSync21 } from "node:fs";
37422
37125
  function registerEditCommands(aiCommand) {
37423
37126
  aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "(deprecated \u2014 use `vibe detect silence`) Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
37424
37127
  Examples:
@@ -37434,8 +37137,8 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
37434
37137
  if (options.output) {
37435
37138
  validateOutputPath(options.output);
37436
37139
  }
37437
- const absVideoPath = resolve28(process.cwd(), videoPath);
37438
- if (!existsSync22(absVideoPath)) {
37140
+ const absVideoPath = resolve27(process.cwd(), videoPath);
37141
+ if (!existsSync21(absVideoPath)) {
37439
37142
  exitWithError(notFoundError(absVideoPath));
37440
37143
  }
37441
37144
  if (!commandExists("ffmpeg")) {
@@ -37474,7 +37177,7 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
37474
37177
  const spinner2 = ora(spinnerText).start();
37475
37178
  const result = await executeSilenceCut({
37476
37179
  videoPath: absVideoPath,
37477
- outputPath: resolve28(process.cwd(), outputPath),
37180
+ outputPath: resolve27(process.cwd(), outputPath),
37478
37181
  noiseThreshold: parseFloat(options.noise),
37479
37182
  minDuration: parseFloat(options.minDuration),
37480
37183
  padding: parseFloat(options.padding),
@@ -37542,8 +37245,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37542
37245
  if (options.output) {
37543
37246
  validateOutputPath(options.output);
37544
37247
  }
37545
- const absVideoPath = resolve28(process.cwd(), videoPath);
37546
- if (!existsSync22(absVideoPath)) {
37248
+ const absVideoPath = resolve27(process.cwd(), videoPath);
37249
+ if (!existsSync21(absVideoPath)) {
37547
37250
  exitWithError(notFoundError(absVideoPath));
37548
37251
  }
37549
37252
  if (!commandExists("ffmpeg")) {
@@ -37577,7 +37280,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37577
37280
  const spinner2 = ora("Starting caption process...").start();
37578
37281
  const result = await executeCaption({
37579
37282
  videoPath: absVideoPath,
37580
- outputPath: resolve28(process.cwd(), outputPath),
37283
+ outputPath: resolve27(process.cwd(), outputPath),
37581
37284
  style: options.style,
37582
37285
  fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
37583
37286
  fontColor: options.color,
@@ -37623,8 +37326,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37623
37326
  if (options.output) {
37624
37327
  validateOutputPath(options.output);
37625
37328
  }
37626
- const absInputPath = resolve28(process.cwd(), inputPath);
37627
- if (!existsSync22(absInputPath)) {
37329
+ const absInputPath = resolve27(process.cwd(), inputPath);
37330
+ if (!existsSync21(absInputPath)) {
37628
37331
  exitWithError(notFoundError(absInputPath));
37629
37332
  }
37630
37333
  if (!commandExists("ffmpeg")) {
@@ -37651,7 +37354,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37651
37354
  const spinner2 = ora("Applying noise reduction...").start();
37652
37355
  const result = await executeNoiseReduce({
37653
37356
  inputPath: absInputPath,
37654
- outputPath: resolve28(process.cwd(), outputPath),
37357
+ outputPath: resolve27(process.cwd(), outputPath),
37655
37358
  strength: options.strength,
37656
37359
  noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
37657
37360
  });
@@ -37689,8 +37392,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37689
37392
  if (options.output) {
37690
37393
  validateOutputPath(options.output);
37691
37394
  }
37692
- const absVideoPath = resolve28(process.cwd(), videoPath);
37693
- if (!existsSync22(absVideoPath)) {
37395
+ const absVideoPath = resolve27(process.cwd(), videoPath);
37396
+ if (!existsSync21(absVideoPath)) {
37694
37397
  exitWithError(notFoundError(absVideoPath));
37695
37398
  }
37696
37399
  if (!commandExists("ffmpeg")) {
@@ -37719,7 +37422,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37719
37422
  const spinner2 = ora("Applying fade effects...").start();
37720
37423
  const result = await executeFade({
37721
37424
  videoPath: absVideoPath,
37722
- outputPath: resolve28(process.cwd(), outputPath),
37425
+ outputPath: resolve27(process.cwd(), outputPath),
37723
37426
  fadeIn: parseFloat(options.fadeIn),
37724
37427
  fadeOut: parseFloat(options.fadeOut),
37725
37428
  audioOnly: options.audioOnly || false,
@@ -37764,8 +37467,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37764
37467
  if (!options.target) {
37765
37468
  exitWithError(usageError("Target language required. Use -t or --target"));
37766
37469
  }
37767
- const absSrtPath = resolve28(process.cwd(), srtPath);
37768
- if (!existsSync22(absSrtPath)) {
37470
+ const absSrtPath = resolve27(process.cwd(), srtPath);
37471
+ if (!existsSync21(absSrtPath)) {
37769
37472
  exitWithError(notFoundError(absSrtPath));
37770
37473
  }
37771
37474
  if (options.dryRun) {
@@ -37797,7 +37500,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37797
37500
  const spinner2 = ora(`Translating to ${options.target}...`).start();
37798
37501
  const result = await executeTranslateSrt({
37799
37502
  srtPath: absSrtPath,
37800
- outputPath: resolve28(process.cwd(), outputPath),
37503
+ outputPath: resolve27(process.cwd(), outputPath),
37801
37504
  targetLanguage: options.target,
37802
37505
  provider,
37803
37506
  sourceLanguage: options.source,
@@ -37840,8 +37543,8 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37840
37543
  validateOutputPath(options.output);
37841
37544
  }
37842
37545
  if (options.fillers) rejectControlChars(options.fillers);
37843
- const absVideoPath = resolve28(process.cwd(), videoPath);
37844
- if (!existsSync22(absVideoPath)) {
37546
+ const absVideoPath = resolve27(process.cwd(), videoPath);
37547
+ if (!existsSync21(absVideoPath)) {
37845
37548
  exitWithError(notFoundError(absVideoPath));
37846
37549
  }
37847
37550
  if (!commandExists("ffmpeg")) {
@@ -37876,7 +37579,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
37876
37579
  const spinner2 = ora("Transcribing with word-level timestamps...").start();
37877
37580
  const result = await executeJumpCut({
37878
37581
  videoPath: absVideoPath,
37879
- outputPath: resolve28(process.cwd(), outputPath),
37582
+ outputPath: resolve27(process.cwd(), outputPath),
37880
37583
  fillers,
37881
37584
  padding: parseFloat(options.padding),
37882
37585
  language: options.language,
@@ -37941,9 +37644,9 @@ var init_ai_edit_cli = __esm({
37941
37644
  });
37942
37645
 
37943
37646
  // src/commands/_shared/execute-fill-gaps.ts
37944
- import { existsSync as existsSync23 } from "node:fs";
37945
- import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir14, rename as renameFs } from "node:fs/promises";
37946
- import { resolve as resolve29, dirname as dirname9 } from "node:path";
37647
+ import { existsSync as existsSync22 } from "node:fs";
37648
+ import { readFile as readFile18, writeFile as writeFile27, mkdir as mkdir14, rename as renameFs } from "node:fs/promises";
37649
+ import { resolve as resolve28, dirname as dirname9 } from "node:path";
37947
37650
  function detectVideoGaps(videoClips, totalDuration) {
37948
37651
  const gaps = [];
37949
37652
  const sortedClips = [...videoClips].sort((a, b) => a.startTime - b.startTime);
@@ -37999,7 +37702,7 @@ function analyzeGapFillability(gaps, videoClips, sources) {
37999
37702
  }
38000
37703
  async function uploadFrameToImgbb(framePath, imgbbApiKey) {
38001
37704
  try {
38002
- const frameBuffer = await readFile19(framePath);
37705
+ const frameBuffer = await readFile18(framePath);
38003
37706
  const frameBase64 = frameBuffer.toString("base64");
38004
37707
  const formData = new FormData();
38005
37708
  formData.append("key", imgbbApiKey);
@@ -38024,14 +37727,14 @@ async function executeFillGaps(options) {
38024
37727
  try {
38025
37728
  onProgress("Loading timeline...");
38026
37729
  const filePath = await resolveTimelineFile(options.projectPath);
38027
- if (!existsSync23(filePath)) {
37730
+ if (!existsSync22(filePath)) {
38028
37731
  return {
38029
37732
  success: false,
38030
37733
  error: `Timeline file not found: ${filePath}`,
38031
37734
  humanLines
38032
37735
  };
38033
37736
  }
38034
- const content = await readFile19(filePath, "utf-8");
37737
+ const content = await readFile18(filePath, "utf-8");
38035
37738
  const data = JSON.parse(content);
38036
37739
  const project = Project.fromJSON(data);
38037
37740
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -38158,8 +37861,8 @@ async function executeFillGaps(options) {
38158
37861
  };
38159
37862
  }
38160
37863
  const projectDir = dirname9(filePath);
38161
- const footageDir = options.dir ? resolve29(process.cwd(), options.dir) : resolve29(projectDir, "footage");
38162
- if (!existsSync23(footageDir)) {
37864
+ const footageDir = options.dir ? resolve28(process.cwd(), options.dir) : resolve28(projectDir, "footage");
37865
+ if (!existsSync22(footageDir)) {
38163
37866
  await mkdir14(footageDir, { recursive: true });
38164
37867
  }
38165
37868
  humanLines.push("Generating AI Videos");
@@ -38190,7 +37893,7 @@ async function executeFillGaps(options) {
38190
37893
  }
38191
37894
  onProgress("Extracting frame from preceding clip...");
38192
37895
  const frameOffset = clipBefore.sourceStartOffset + clipBefore.duration - 0.1;
38193
- const framePath = resolve29(footageDir, `frame-${gap.start.toFixed(2)}.png`);
37896
+ const framePath = resolve28(footageDir, `frame-${gap.start.toFixed(2)}.png`);
38194
37897
  try {
38195
37898
  await execSafe("ffmpeg", [
38196
37899
  "-i",
@@ -38251,10 +37954,10 @@ async function executeFillGaps(options) {
38251
37954
  continue;
38252
37955
  }
38253
37956
  const videoFileName = `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}.mp4`;
38254
- const videoPath = resolve29(footageDir, videoFileName);
37957
+ const videoPath = resolve28(footageDir, videoFileName);
38255
37958
  onProgress("Downloading generated video...");
38256
37959
  const videoBuffer = await downloadVideo(finalResult.videoUrl);
38257
- await writeFile28(videoPath, videoBuffer);
37960
+ await writeFile27(videoPath, videoBuffer);
38258
37961
  generatedDuration = finalResult.duration || parseInt(klingDuration);
38259
37962
  generatedVideos.push(videoPath);
38260
37963
  humanLines.push(` Generated: ${videoFileName} (${generatedDuration}s)`);
@@ -38263,7 +37966,7 @@ async function executeFillGaps(options) {
38263
37966
  const remainingNeeded = targetDuration - generatedDuration;
38264
37967
  const segmentDuration = remainingNeeded > 5 ? "10" : "5";
38265
37968
  onProgress(`Generating additional ${segmentDuration}s segment...`);
38266
- const lastFramePath = resolve29(
37969
+ const lastFramePath = resolve28(
38267
37970
  footageDir,
38268
37971
  `frame-extend-${gap.start.toFixed(2)}-${segmentIndex}.png`
38269
37972
  );
@@ -38319,17 +38022,17 @@ async function executeFillGaps(options) {
38319
38022
  );
38320
38023
  break;
38321
38024
  }
38322
- const segVideoPath = resolve29(
38025
+ const segVideoPath = resolve28(
38323
38026
  footageDir,
38324
38027
  `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-seg${segmentIndex}.mp4`
38325
38028
  );
38326
38029
  const segVideoBuffer = await downloadVideo(segFinalResult.videoUrl);
38327
- await writeFile28(segVideoPath, segVideoBuffer);
38328
- const concatListPath = resolve29(footageDir, `concat-${gap.start.toFixed(2)}.txt`);
38030
+ await writeFile27(segVideoPath, segVideoBuffer);
38031
+ const concatListPath = resolve28(footageDir, `concat-${gap.start.toFixed(2)}.txt`);
38329
38032
  const concatList = generatedVideos.map((v) => `file '${v}'`).join("\n") + `
38330
38033
  file '${segVideoPath}'`;
38331
- await writeFile28(concatListPath, concatList);
38332
- const concatOutputPath = resolve29(
38034
+ await writeFile27(concatListPath, concatList);
38035
+ const concatOutputPath = resolve28(
38333
38036
  footageDir,
38334
38037
  `gap-fill-${gap.start.toFixed(2)}-${gap.end.toFixed(2)}-merged.mp4`
38335
38038
  );
@@ -38385,8 +38088,8 @@ file '${segVideoPath}'`;
38385
38088
  humanLines.push("");
38386
38089
  let outputPath = filePath;
38387
38090
  if (generatedCount > 0) {
38388
- outputPath = options.output ? resolve29(process.cwd(), options.output) : filePath;
38389
- await writeFile28(outputPath, JSON.stringify(project.toJSON(), null, 2));
38091
+ outputPath = options.output ? resolve28(process.cwd(), options.output) : filePath;
38092
+ await writeFile27(outputPath, JSON.stringify(project.toJSON(), null, 2));
38390
38093
  humanLines.push(`\u2714 Filled ${generatedCount} gap(s) with AI-generated video`);
38391
38094
  humanLines.push(`Project saved: ${outputPath}`);
38392
38095
  } else {
@@ -38482,14 +38185,14 @@ __export(edit_cmd_exports, {
38482
38185
  executeSpeedRamp: () => executeSpeedRamp,
38483
38186
  executeUpscale: () => executeUpscale
38484
38187
  });
38485
- import { resolve as resolve30, dirname as dirname10 } from "node:path";
38486
- import { readFile as readFile20, writeFile as writeFile29, mkdir as mkdir15 } from "node:fs/promises";
38188
+ import { resolve as resolve29, dirname as dirname10 } from "node:path";
38189
+ import { readFile as readFile19, writeFile as writeFile28, mkdir as mkdir15 } from "node:fs/promises";
38487
38190
  async function executeGrade(options) {
38488
38191
  const { videoPath, style, preset, output, analyzeOnly, apiKey } = options;
38489
38192
  try {
38490
38193
  if (!style && !preset) return { success: false, error: "Either style or preset is required" };
38491
38194
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
38492
- const absPath = resolve30(process.cwd(), videoPath);
38195
+ const absPath = resolve29(process.cwd(), videoPath);
38493
38196
  let gradeResult;
38494
38197
  if (preset) {
38495
38198
  const claude = new ClaudeProvider();
@@ -38504,7 +38207,7 @@ async function executeGrade(options) {
38504
38207
  if (analyzeOnly) {
38505
38208
  return { success: true, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
38506
38209
  }
38507
- const outputPath = output ? resolve30(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
38210
+ const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
38508
38211
  await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
38509
38212
  return { success: true, outputPath, style: preset || style, description: gradeResult.description, ffmpegFilter: gradeResult.ffmpegFilter };
38510
38213
  } catch (error) {
@@ -38519,12 +38222,12 @@ async function executeSpeedRamp(options) {
38519
38222
  const claudeKey = apiKey || process.env.ANTHROPIC_API_KEY;
38520
38223
  if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
38521
38224
  if (!claudeKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude analysis" };
38522
- const absPath = resolve30(process.cwd(), videoPath);
38225
+ const absPath = resolve29(process.cwd(), videoPath);
38523
38226
  const tempAudio = absPath.replace(/(\.[^.]+)$/, "-temp-audio.mp3");
38524
38227
  await execSafe("ffmpeg", ["-i", absPath, "-vn", "-acodec", "libmp3lame", "-q:a", "2", tempAudio, "-y"]);
38525
38228
  const whisper = new WhisperProvider();
38526
38229
  await whisper.initialize({ apiKey: openaiKey });
38527
- const audioBuffer = await readFile20(tempAudio);
38230
+ const audioBuffer = await readFile19(tempAudio);
38528
38231
  const transcript = await whisper.transcribe(new Blob([audioBuffer]), language);
38529
38232
  try {
38530
38233
  const { unlink: unlink5 } = await import("node:fs/promises");
@@ -38544,7 +38247,7 @@ async function executeSpeedRamp(options) {
38544
38247
  if (speedResult.keyframes.length < 2) {
38545
38248
  return { success: false, error: "Not enough keyframes for speed ramping" };
38546
38249
  }
38547
- const outputPath = output ? resolve30(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
38250
+ const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
38548
38251
  const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
38549
38252
  const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
38550
38253
  if (atempo) {
@@ -38561,7 +38264,7 @@ async function executeReframe(options) {
38561
38264
  const { videoPath, aspect = "9:16", output, analyzeOnly } = options;
38562
38265
  try {
38563
38266
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
38564
- const absPath = resolve30(process.cwd(), videoPath);
38267
+ const absPath = resolve29(process.cwd(), videoPath);
38565
38268
  const { stdout: probeOut } = await execSafe("ffprobe", [
38566
38269
  "-v",
38567
38270
  "error",
@@ -38596,7 +38299,7 @@ async function executeReframe(options) {
38596
38299
  keyframeCount: 1
38597
38300
  };
38598
38301
  }
38599
- const outputPath = output ? resolve30(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${aspect.replace(":", "x")}$1`);
38302
+ const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${aspect.replace(":", "x")}$1`);
38600
38303
  await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${cropX}:${cropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
38601
38304
  return { success: true, outputPath, sourceAspect: `${sourceWidth}:${sourceHeight}`, targetAspect: aspect };
38602
38305
  } catch (error) {
@@ -38608,7 +38311,7 @@ async function executeInterpolate(options) {
38608
38311
  try {
38609
38312
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
38610
38313
  if (![2, 4, 8].includes(factor)) return { success: false, error: "Factor must be 2, 4, or 8" };
38611
- const absPath = resolve30(process.cwd(), videoPath);
38314
+ const absPath = resolve29(process.cwd(), videoPath);
38612
38315
  const { stdout: fpsOut } = await execSafe("ffprobe", [
38613
38316
  "-v",
38614
38317
  "error",
@@ -38624,7 +38327,7 @@ async function executeInterpolate(options) {
38624
38327
  const originalFps = num / (den || 1);
38625
38328
  const targetFps = options.fps || originalFps * factor;
38626
38329
  const mi = quality === "fast" ? "mi_mode=mci" : "mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1";
38627
- const outputPath = output ? resolve30(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
38330
+ const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
38628
38331
  await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
38629
38332
  return { success: true, outputPath, originalFps, targetFps, factor };
38630
38333
  } catch (error) {
@@ -38635,7 +38338,7 @@ async function executeUpscale(options) {
38635
38338
  const { videoPath, output, scale = 2, quality = "quality" } = options;
38636
38339
  try {
38637
38340
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
38638
- const absPath = resolve30(process.cwd(), videoPath);
38341
+ const absPath = resolve29(process.cwd(), videoPath);
38639
38342
  const { stdout: probeOut } = await execSafe("ffprobe", [
38640
38343
  "-v",
38641
38344
  "error",
@@ -38651,7 +38354,7 @@ async function executeUpscale(options) {
38651
38354
  const targetW = w * scale;
38652
38355
  const targetH = h * scale;
38653
38356
  const scaleFilter = quality === "quality" ? `scale=${targetW}:${targetH}:flags=lanczos` : `scale=${targetW}:${targetH}`;
38654
- const outputPath = output ? resolve30(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${scale}x$1`);
38357
+ const outputPath = output ? resolve29(process.cwd(), output) : absPath.replace(/(\.[^.]+)$/, `-${scale}x$1`);
38655
38358
  await execSafe("ffmpeg", ["-i", absPath, "-vf", scaleFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
38656
38359
  return { success: true, outputPath, originalRes: `${w}x${h}`, targetRes: `${targetW}x${targetH}` };
38657
38360
  } catch (error) {
@@ -38724,7 +38427,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38724
38427
  dryRun: true,
38725
38428
  data: {
38726
38429
  params: {
38727
- videoPath: resolve30(process.cwd(), videoPath),
38430
+ videoPath: resolve29(process.cwd(), videoPath),
38728
38431
  style: options.style || options.preset,
38729
38432
  analyzeOnly: options.analyzeOnly || false
38730
38433
  }
@@ -38751,8 +38454,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38751
38454
  }
38752
38455
  spinner2.succeed(source_default.green("Color grade analyzed"));
38753
38456
  if (isJsonMode()) {
38754
- const absPath2 = resolve30(process.cwd(), videoPath);
38755
- const gradeOutputPath = options.output ? resolve30(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
38457
+ const absPath2 = resolve29(process.cwd(), videoPath);
38458
+ const gradeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
38756
38459
  outputSuccess({
38757
38460
  command: "edit grade",
38758
38461
  startedAt,
@@ -38778,8 +38481,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38778
38481
  console.log(source_default.dim("Use without --analyze-only to apply the grade."));
38779
38482
  return;
38780
38483
  }
38781
- const absPath = resolve30(process.cwd(), videoPath);
38782
- const outputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
38484
+ const absPath = resolve29(process.cwd(), videoPath);
38485
+ const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-graded$1");
38783
38486
  spinner2.start("Applying color grade...");
38784
38487
  await execSafe("ffmpeg", ["-i", absPath, "-vf", gradeResult.ffmpegFilter, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
38785
38488
  spinner2.succeed(source_default.green("Color grade applied"));
@@ -38809,7 +38512,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38809
38512
  dryRun: true,
38810
38513
  data: {
38811
38514
  params: {
38812
- videoPath: resolve30(process.cwd(), videoPath),
38515
+ videoPath: resolve29(process.cwd(), videoPath),
38813
38516
  texts: options.text,
38814
38517
  style: options.style,
38815
38518
  fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
@@ -38822,8 +38525,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38822
38525
  });
38823
38526
  return;
38824
38527
  }
38825
- const absPath = resolve30(process.cwd(), videoPath);
38826
- const outputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-overlay$1");
38528
+ const absPath = resolve29(process.cwd(), videoPath);
38529
+ const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-overlay$1");
38827
38530
  const spinner2 = ora("Applying text overlays...").start();
38828
38531
  const result = await applyTextOverlays({
38829
38532
  videoPath: absPath,
@@ -38881,7 +38584,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38881
38584
  dryRun: true,
38882
38585
  data: {
38883
38586
  params: {
38884
- videoPath: resolve30(process.cwd(), videoPath),
38587
+ videoPath: resolve29(process.cwd(), videoPath),
38885
38588
  style: options.style,
38886
38589
  minSpeed: parseFloat(options.minSpeed),
38887
38590
  maxSpeed: parseFloat(options.maxSpeed),
@@ -38893,7 +38596,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38893
38596
  }
38894
38597
  const openaiApiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
38895
38598
  const claudeApiKey = await requireApiKey("ANTHROPIC_API_KEY", "Anthropic", options.apiKey);
38896
- const absPath = resolve30(process.cwd(), videoPath);
38599
+ const absPath = resolve29(process.cwd(), videoPath);
38897
38600
  const spinner2 = ora("Extracting audio...").start();
38898
38601
  const { stdout: speedRampProbe } = await execSafe("ffprobe", [
38899
38602
  "-v",
@@ -38915,7 +38618,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38915
38618
  spinner2.text = "Transcribing audio...";
38916
38619
  const whisper = new WhisperProvider();
38917
38620
  await whisper.initialize({ apiKey: openaiApiKey });
38918
- const audioBuffer = await readFile20(tempAudio);
38621
+ const audioBuffer = await readFile19(tempAudio);
38919
38622
  const audioBlob = new Blob([audioBuffer]);
38920
38623
  const transcript = await whisper.transcribe(audioBlob, options.language);
38921
38624
  if (!transcript.segments || transcript.segments.length === 0) {
@@ -38938,7 +38641,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38938
38641
  spinner2.succeed(source_default.green(`Found ${speedResult.keyframes.length} speed keyframes`));
38939
38642
  if (isJsonMode()) {
38940
38643
  const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
38941
- const speedRampOutputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
38644
+ const speedRampOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
38942
38645
  outputSuccess({
38943
38646
  command: "edit speed-ramp",
38944
38647
  startedAt,
@@ -38967,7 +38670,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
38967
38670
  return;
38968
38671
  }
38969
38672
  spinner2.start("Applying speed ramps...");
38970
- const outputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
38673
+ const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
38971
38674
  const avgSpeed = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
38972
38675
  const setpts = `setpts=${(1 / avgSpeed).toFixed(3)}*PTS`;
38973
38676
  const atempo = avgSpeed >= 0.5 && avgSpeed <= 2 ? `atempo=${avgSpeed.toFixed(3)}` : "";
@@ -39001,7 +38704,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39001
38704
  dryRun: true,
39002
38705
  data: {
39003
38706
  params: {
39004
- videoPath: resolve30(process.cwd(), videoPath),
38707
+ videoPath: resolve29(process.cwd(), videoPath),
39005
38708
  aspect: options.aspect,
39006
38709
  focus: options.focus,
39007
38710
  analyzeOnly: options.analyzeOnly || false
@@ -39010,7 +38713,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39010
38713
  });
39011
38714
  return;
39012
38715
  }
39013
- const absPath = resolve30(process.cwd(), videoPath);
38716
+ const absPath = resolve29(process.cwd(), videoPath);
39014
38717
  const spinner2 = ora("Analyzing video...").start();
39015
38718
  const { stdout: probeOut } = await execSafe("ffprobe", [
39016
38719
  "-v",
@@ -39048,7 +38751,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39048
38751
  for (let i = 1; i <= numKeyframes && i <= 30; i++) {
39049
38752
  const framePath = `${tempDir}/frame-${i.toString().padStart(4, "0")}.jpg`;
39050
38753
  try {
39051
- const frameBuffer = await readFile20(framePath);
38754
+ const frameBuffer = await readFile19(framePath);
39052
38755
  const frameBase64 = frameBuffer.toString("base64");
39053
38756
  const result = await claude.analyzeFrameForReframe(frameBase64, options.aspect, {
39054
38757
  focusMode: options.focus,
@@ -39072,7 +38775,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39072
38775
  }
39073
38776
  spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
39074
38777
  if (isJsonMode()) {
39075
- const reframeOutputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
38778
+ const reframeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
39076
38779
  outputSuccess({
39077
38780
  command: "edit reframe",
39078
38781
  startedAt,
@@ -39107,8 +38810,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39107
38810
  }
39108
38811
  console.log();
39109
38812
  if (options.keyframes) {
39110
- const keyframesPath = resolve30(process.cwd(), options.keyframes);
39111
- await writeFile29(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
38813
+ const keyframesPath = resolve29(process.cwd(), options.keyframes);
38814
+ await writeFile28(keyframesPath, JSON.stringify(cropKeyframes, null, 2));
39112
38815
  console.log(source_default.green(`Keyframes saved to: ${keyframesPath}`));
39113
38816
  }
39114
38817
  if (options.analyzeOnly) {
@@ -39119,7 +38822,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39119
38822
  const avgCropY = Math.round(cropKeyframes.reduce((sum, kf) => sum + kf.cropY, 0) / cropKeyframes.length);
39120
38823
  const cropWidth = cropKeyframes[0]?.cropWidth || sourceWidth;
39121
38824
  const cropHeight = cropKeyframes[0]?.cropHeight || sourceHeight;
39122
- const outputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
38825
+ const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
39123
38826
  spinner2.start("Applying reframe...");
39124
38827
  await execSafe("ffmpeg", ["-i", absPath, "-vf", `crop=${cropWidth}:${cropHeight}:${avgCropX}:${avgCropY}`, "-c:a", "copy", outputPath, "-y"], { timeout: 6e5 });
39125
38828
  spinner2.succeed(source_default.green("Reframe applied"));
@@ -39155,7 +38858,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39155
38858
  dryRun: true,
39156
38859
  data: {
39157
38860
  params: {
39158
- imagePaths: imagePaths.map((p) => resolve30(process.cwd(), p)),
38861
+ imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
39159
38862
  prompt: prompt3,
39160
38863
  provider,
39161
38864
  model: options.model,
@@ -39176,8 +38879,8 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39176
38879
  const spinner2 = ora(`Reading ${imagePaths.length} image(s)...`).start();
39177
38880
  const imageBuffers = [];
39178
38881
  for (const imagePath of imagePaths) {
39179
- const absPath = resolve30(process.cwd(), imagePath);
39180
- const buffer = await readFile20(absPath);
38882
+ const absPath = resolve29(process.cwd(), imagePath);
38883
+ const buffer = await readFile19(absPath);
39181
38884
  imageBuffers.push(buffer);
39182
38885
  }
39183
38886
  let result;
@@ -39225,16 +38928,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39225
38928
  }
39226
38929
  spinner2.succeed(source_default.green("Image edited"));
39227
38930
  const img = result.images[0];
39228
- const outputPath = resolve30(process.cwd(), options.output);
38931
+ const outputPath = resolve29(process.cwd(), options.output);
39229
38932
  const saveImage = async () => {
39230
38933
  await mkdir15(dirname10(outputPath), { recursive: true });
39231
38934
  if (img.base64) {
39232
38935
  const buffer = Buffer.from(img.base64, "base64");
39233
- await writeFile29(outputPath, buffer);
38936
+ await writeFile28(outputPath, buffer);
39234
38937
  } else if (img.url) {
39235
38938
  const resp = await fetch(img.url);
39236
38939
  const arrayBuf = await resp.arrayBuffer();
39237
- await writeFile29(outputPath, Buffer.from(arrayBuf));
38940
+ await writeFile28(outputPath, Buffer.from(arrayBuf));
39238
38941
  }
39239
38942
  };
39240
38943
  const resultModel = result.model;
@@ -39267,7 +38970,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39267
38970
  if (options.output) {
39268
38971
  validateOutputPath(options.output);
39269
38972
  }
39270
- const absPath = resolve30(process.cwd(), videoPath);
38973
+ const absPath = resolve29(process.cwd(), videoPath);
39271
38974
  const factor = parseInt(options.factor);
39272
38975
  if (![2, 4, 8].includes(factor)) {
39273
38976
  exitWithError(usageError("Factor must be 2, 4, or 8"));
@@ -39288,7 +38991,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39288
38991
  });
39289
38992
  return;
39290
38993
  }
39291
- const outputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
38994
+ const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-slow${factor}x$1`);
39292
38995
  const spinner2 = ora(`Creating ${factor}x slow motion...`).start();
39293
38996
  try {
39294
38997
  const { stdout: fpsOut } = await execSafe("ffprobe", [
@@ -39349,7 +39052,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39349
39052
  if (options.output) {
39350
39053
  validateOutputPath(options.output);
39351
39054
  }
39352
- const absPath = resolve30(process.cwd(), videoPath);
39055
+ const absPath = resolve29(process.cwd(), videoPath);
39353
39056
  const scale = parseInt(options.scale);
39354
39057
  if (scale !== 2 && scale !== 4) {
39355
39058
  exitWithError(usageError("Scale must be 2 or 4"));
@@ -39371,7 +39074,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39371
39074
  return;
39372
39075
  }
39373
39076
  if (options.ffmpeg) {
39374
- const outputPath = options.output ? resolve30(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-upscaled-${scale}x$1`);
39077
+ const outputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-upscaled-${scale}x$1`);
39375
39078
  const spinner3 = ora(`Upscaling video with FFmpeg (${scale}x)...`).start();
39376
39079
  try {
39377
39080
  const { stdout: probeOut } = await execSafe("ffprobe", [
@@ -39452,9 +39155,9 @@ __export(ai_analyze_exports, {
39452
39155
  executeGeminiVideo: () => executeGeminiVideo,
39453
39156
  registerAnalyzeCommands: () => registerAnalyzeCommands
39454
39157
  });
39455
- import { readFile as readFile21 } from "node:fs/promises";
39456
- import { extname as extname6, resolve as resolve31 } from "node:path";
39457
- import { existsSync as existsSync24 } from "node:fs";
39158
+ import { readFile as readFile20 } from "node:fs/promises";
39159
+ import { extname as extname6, resolve as resolve30 } from "node:path";
39160
+ import { existsSync as existsSync23 } from "node:fs";
39458
39161
  async function executeGeminiVideo(options) {
39459
39162
  try {
39460
39163
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -39472,11 +39175,11 @@ async function executeGeminiVideo(options) {
39472
39175
  if (isYouTube) {
39473
39176
  videoData = options.source;
39474
39177
  } else {
39475
- const absPath = resolve31(process.cwd(), options.source);
39476
- if (!existsSync24(absPath)) {
39178
+ const absPath = resolve30(process.cwd(), options.source);
39179
+ if (!existsSync23(absPath)) {
39477
39180
  return { success: false, error: `File not found: ${absPath}` };
39478
39181
  }
39479
- videoData = await readFile21(absPath);
39182
+ videoData = await readFile20(absPath);
39480
39183
  }
39481
39184
  const gemini = new GeminiProvider();
39482
39185
  await gemini.initialize({ apiKey });
@@ -39545,11 +39248,11 @@ async function executeAnalyze(options) {
39545
39248
  }
39546
39249
  imageBuffer = Buffer.from(await response.arrayBuffer());
39547
39250
  } else {
39548
- const absPath = resolve31(process.cwd(), source);
39549
- if (!existsSync24(absPath)) {
39251
+ const absPath = resolve30(process.cwd(), source);
39252
+ if (!existsSync23(absPath)) {
39550
39253
  return { success: false, error: `File not found: ${absPath}` };
39551
39254
  }
39552
- imageBuffer = await readFile21(absPath);
39255
+ imageBuffer = await readFile20(absPath);
39553
39256
  }
39554
39257
  const result2 = await gemini.analyzeImage(imageBuffer, options.prompt, {
39555
39258
  model: modelId,
@@ -39580,11 +39283,11 @@ async function executeAnalyze(options) {
39580
39283
  }
39581
39284
  videoData = Buffer.from(await response.arrayBuffer());
39582
39285
  } else {
39583
- const absPath = resolve31(process.cwd(), source);
39584
- if (!existsSync24(absPath)) {
39286
+ const absPath = resolve30(process.cwd(), source);
39287
+ if (!existsSync23(absPath)) {
39585
39288
  return { success: false, error: `File not found: ${absPath}` };
39586
39289
  }
39587
- videoData = await readFile21(absPath);
39290
+ videoData = await readFile20(absPath);
39588
39291
  }
39589
39292
  const result = await gemini.analyzeVideo(videoData, options.prompt, {
39590
39293
  model: modelId,
@@ -39718,9 +39421,9 @@ __export(ai_review_exports, {
39718
39421
  executeReview: () => executeReview,
39719
39422
  registerReviewCommand: () => registerReviewCommand
39720
39423
  });
39721
- import { readFile as readFile22, rename as rename3 } from "node:fs/promises";
39722
- import { resolve as resolve32 } from "node:path";
39723
- import { existsSync as existsSync25 } from "node:fs";
39424
+ import { readFile as readFile21, rename as rename2 } from "node:fs/promises";
39425
+ import { resolve as resolve31 } from "node:path";
39426
+ import { existsSync as existsSync24 } from "node:fs";
39724
39427
  function parseReviewFeedback(response) {
39725
39428
  let cleaned = response.trim();
39726
39429
  if (cleaned.startsWith("```json")) {
@@ -39744,8 +39447,8 @@ function parseReviewFeedback(response) {
39744
39447
  }
39745
39448
  async function executeReview(options) {
39746
39449
  const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
39747
- const absVideoPath = resolve32(process.cwd(), videoPath);
39748
- if (!existsSync25(absVideoPath)) {
39450
+ const absVideoPath = resolve31(process.cwd(), videoPath);
39451
+ if (!existsSync24(absVideoPath)) {
39749
39452
  return { success: false, error: `Video not found: ${absVideoPath}` };
39750
39453
  }
39751
39454
  const apiKey = process.env.GOOGLE_API_KEY || await getApiKey("GOOGLE_API_KEY", "Google");
@@ -39754,9 +39457,9 @@ async function executeReview(options) {
39754
39457
  }
39755
39458
  let storyboardContext = "";
39756
39459
  if (storyboardPath) {
39757
- const absStoryboardPath = resolve32(process.cwd(), storyboardPath);
39758
- if (existsSync25(absStoryboardPath)) {
39759
- const content = await readFile22(absStoryboardPath, "utf-8");
39460
+ const absStoryboardPath = resolve31(process.cwd(), storyboardPath);
39461
+ if (existsSync24(absStoryboardPath)) {
39462
+ const content = await readFile21(absStoryboardPath, "utf-8");
39760
39463
  storyboardContext = `
39761
39464
 
39762
39465
  Original storyboard for reference:
@@ -39789,7 +39492,7 @@ ${content}`;
39789
39492
  Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFixable. Be specific and practical.${storyboardContext}`;
39790
39493
  const gemini = new GeminiProvider();
39791
39494
  await gemini.initialize({ apiKey });
39792
- const videoData = await readFile22(absVideoPath);
39495
+ const videoData = await readFile21(absVideoPath);
39793
39496
  const analysisResult = await gemini.analyzeVideo(videoData, reviewPrompt, {
39794
39497
  model: modelId
39795
39498
  });
@@ -39810,7 +39513,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
39810
39513
  };
39811
39514
  if (autoApply && feedback.autoFixable.length > 0) {
39812
39515
  let currentInput = absVideoPath;
39813
- const outputBase = options.outputPath ? resolve32(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
39516
+ const outputBase = options.outputPath ? resolve31(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
39814
39517
  for (const fix of feedback.autoFixable) {
39815
39518
  if (fix.type === "color_grade" && fix.ffmpegFilter) {
39816
39519
  try {
@@ -39827,7 +39530,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
39827
39530
  if (currentInput !== absVideoPath) {
39828
39531
  const finalOutput = outputBase;
39829
39532
  try {
39830
- await rename3(currentInput, finalOutput);
39533
+ await rename2(currentInput, finalOutput);
39831
39534
  result.outputPath = finalOutput;
39832
39535
  } catch {
39833
39536
  result.outputPath = currentInput;
@@ -39835,7 +39538,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
39835
39538
  }
39836
39539
  }
39837
39540
  if (verify && result.outputPath) {
39838
- const verifyVideoData = await readFile22(result.outputPath);
39541
+ const verifyVideoData = await readFile21(result.outputPath);
39839
39542
  const verifyResult = await gemini.analyzeVideo(
39840
39543
  verifyVideoData,
39841
39544
  'Rate this video overall quality on a scale of 1-10. Return ONLY a JSON object: {"score": <number>}',
@@ -39894,73 +39597,657 @@ function registerReviewCommand(aiCommand) {
39894
39597
  spinner2.fail(result.error || "Video review failed");
39895
39598
  exitWithError(apiError(result.error || "Video review failed", true));
39896
39599
  }
39897
- spinner2.succeed(source_default.green("Video review complete"));
39898
- console.log();
39899
- const fb = result.feedback;
39900
- console.log(source_default.bold.cyan("Video Review"));
39901
- console.log(source_default.dim("\u2500".repeat(60)));
39902
- console.log(`Overall Score: ${source_default.bold(fb.overallScore >= 7 ? source_default.green(String(fb.overallScore)) : fb.overallScore >= 5 ? source_default.yellow(String(fb.overallScore)) : source_default.red(String(fb.overallScore)))}/10`);
39903
- console.log();
39904
- const categories = [
39905
- ["Pacing", fb.categories.pacing],
39906
- ["Color", fb.categories.color],
39907
- ["Text Readability", fb.categories.textReadability],
39908
- ["Audio-Visual Sync", fb.categories.audioVisualSync],
39909
- ["Composition", fb.categories.composition]
39910
- ];
39911
- for (const [name, cat] of categories) {
39912
- const scoreColor = cat.score >= 7 ? source_default.green : cat.score >= 5 ? source_default.yellow : source_default.red;
39913
- const fixable = cat.fixable ? source_default.dim(" [fixable]") : "";
39914
- console.log(` ${name.padEnd(20)} ${scoreColor(String(cat.score).padStart(2))}/10${fixable}`);
39915
- if (cat.issues.length > 0) {
39916
- for (const issue of cat.issues) {
39917
- console.log(source_default.dim(` - ${issue}`));
39600
+ spinner2.succeed(source_default.green("Video review complete"));
39601
+ console.log();
39602
+ const fb = result.feedback;
39603
+ console.log(source_default.bold.cyan("Video Review"));
39604
+ console.log(source_default.dim("\u2500".repeat(60)));
39605
+ console.log(`Overall Score: ${source_default.bold(fb.overallScore >= 7 ? source_default.green(String(fb.overallScore)) : fb.overallScore >= 5 ? source_default.yellow(String(fb.overallScore)) : source_default.red(String(fb.overallScore)))}/10`);
39606
+ console.log();
39607
+ const categories = [
39608
+ ["Pacing", fb.categories.pacing],
39609
+ ["Color", fb.categories.color],
39610
+ ["Text Readability", fb.categories.textReadability],
39611
+ ["Audio-Visual Sync", fb.categories.audioVisualSync],
39612
+ ["Composition", fb.categories.composition]
39613
+ ];
39614
+ for (const [name, cat] of categories) {
39615
+ const scoreColor = cat.score >= 7 ? source_default.green : cat.score >= 5 ? source_default.yellow : source_default.red;
39616
+ const fixable = cat.fixable ? source_default.dim(" [fixable]") : "";
39617
+ console.log(` ${name.padEnd(20)} ${scoreColor(String(cat.score).padStart(2))}/10${fixable}`);
39618
+ if (cat.issues.length > 0) {
39619
+ for (const issue of cat.issues) {
39620
+ console.log(source_default.dim(` - ${issue}`));
39621
+ }
39622
+ }
39623
+ }
39624
+ if (result.appliedFixes && result.appliedFixes.length > 0) {
39625
+ console.log();
39626
+ console.log(source_default.bold.green("Applied Fixes:"));
39627
+ for (const fix of result.appliedFixes) {
39628
+ console.log(source_default.green(` + ${fix}`));
39629
+ }
39630
+ if (result.outputPath) {
39631
+ console.log(source_default.green(` Output: ${result.outputPath}`));
39632
+ }
39633
+ }
39634
+ if (result.verificationScore !== void 0) {
39635
+ console.log();
39636
+ console.log(source_default.bold(`Verification Score: ${result.verificationScore}/10`));
39637
+ }
39638
+ if (fb.recommendations.length > 0) {
39639
+ console.log();
39640
+ console.log(source_default.bold("Recommendations:"));
39641
+ for (const rec of fb.recommendations) {
39642
+ console.log(source_default.dim(` * ${rec}`));
39643
+ }
39644
+ }
39645
+ console.log();
39646
+ } catch (error) {
39647
+ exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
39648
+ }
39649
+ });
39650
+ }
39651
+ var init_ai_review = __esm({
39652
+ "src/commands/ai-review.ts"() {
39653
+ "use strict";
39654
+ init_source();
39655
+ init_ora();
39656
+ init_dist2();
39657
+ init_api_key();
39658
+ init_exec_safe();
39659
+ init_output();
39660
+ init_validate();
39661
+ }
39662
+ });
39663
+
39664
+ // src/commands/_shared/video-providers.ts
39665
+ async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
39666
+ const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
39667
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
39668
+ try {
39669
+ const result = await grok.generateVideo(prompt3, {
39670
+ prompt: prompt3,
39671
+ duration: options.duration,
39672
+ aspectRatio: options.aspectRatio,
39673
+ referenceImage: options.referenceImage
39674
+ });
39675
+ if (result.status !== "failed" && result.id) {
39676
+ return { requestId: result.id };
39677
+ }
39678
+ const providerErr = result.error || "Grok returned failed status";
39679
+ if (attempt < maxRetries) {
39680
+ onProgress?.(
39681
+ `\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`
39682
+ );
39683
+ await sleep(RETRY_DELAY_MS);
39684
+ } else {
39685
+ console.error(source_default.dim(`
39686
+ [Grok error: ${providerErr}]`));
39687
+ }
39688
+ } catch (err) {
39689
+ const errMsg = err instanceof Error ? err.message : String(err);
39690
+ if (attempt < maxRetries) {
39691
+ onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39692
+ await sleep(RETRY_DELAY_MS);
39693
+ } else {
39694
+ console.error(source_default.dim(`
39695
+ [Grok error: ${errMsg}]`));
39696
+ }
39697
+ }
39698
+ }
39699
+ return null;
39700
+ }
39701
+ async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
39702
+ const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
39703
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
39704
+ try {
39705
+ const result = await kling.generateVideo(prompt3, {
39706
+ prompt: prompt3,
39707
+ // Pass reference image (base64 or URL) - KlingProvider handles v1.5 fallback for base64
39708
+ referenceImage: options.referenceImage,
39709
+ duration: options.duration,
39710
+ aspectRatio: options.aspectRatio,
39711
+ mode: "std"
39712
+ // std mode for faster generation
39713
+ });
39714
+ if (result.status !== "failed" && result.id) {
39715
+ return {
39716
+ taskId: result.id,
39717
+ type: options.referenceImage ? "image2video" : "text2video"
39718
+ };
39719
+ }
39720
+ const providerErr = result.error || "Kling returned failed status";
39721
+ if (attempt < maxRetries) {
39722
+ onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39723
+ await sleep(RETRY_DELAY_MS);
39724
+ } else {
39725
+ console.error(source_default.dim(`
39726
+ [Kling error: ${providerErr}]`));
39727
+ }
39728
+ } catch (err) {
39729
+ const errMsg = err instanceof Error ? err.message : String(err);
39730
+ if (attempt < maxRetries) {
39731
+ onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39732
+ await sleep(RETRY_DELAY_MS);
39733
+ } else {
39734
+ console.error(source_default.dim(`
39735
+ [Kling error: ${errMsg}]`));
39736
+ }
39737
+ }
39738
+ }
39739
+ return null;
39740
+ }
39741
+ async function generateVideoWithRetryRunway(runway, segment, referenceImage, options, maxRetries, onProgress) {
39742
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
39743
+ try {
39744
+ const result = await runway.generateVideo(segment.visuals, {
39745
+ prompt: segment.visuals,
39746
+ referenceImage,
39747
+ duration: options.duration,
39748
+ aspectRatio: options.aspectRatio
39749
+ });
39750
+ if (result.status !== "failed" && result.id) {
39751
+ return { taskId: result.id };
39752
+ }
39753
+ const providerErr = result.error || "Runway returned failed status";
39754
+ if (attempt < maxRetries) {
39755
+ onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39756
+ await sleep(RETRY_DELAY_MS);
39757
+ } else {
39758
+ console.error(source_default.dim(`
39759
+ [Runway error: ${providerErr}]`));
39760
+ }
39761
+ } catch (err) {
39762
+ const errMsg = err instanceof Error ? err.message : String(err);
39763
+ if (attempt < maxRetries) {
39764
+ onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39765
+ await sleep(RETRY_DELAY_MS);
39766
+ } else {
39767
+ console.error(source_default.dim(`
39768
+ [Runway error: ${errMsg}]`));
39769
+ }
39770
+ }
39771
+ }
39772
+ return null;
39773
+ }
39774
+ async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, onProgress) {
39775
+ const prompt3 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
39776
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
39777
+ try {
39778
+ const result = await gemini.generateVideo(prompt3, {
39779
+ prompt: prompt3,
39780
+ referenceImage: options.referenceImage,
39781
+ duration: options.duration,
39782
+ aspectRatio: options.aspectRatio,
39783
+ model: "veo-3.1-fast-generate-preview"
39784
+ });
39785
+ if (result.status !== "failed" && result.id) {
39786
+ return { operationName: result.id };
39787
+ }
39788
+ const providerErr = result.error || "Veo returned failed status";
39789
+ if (attempt < maxRetries) {
39790
+ onProgress?.(`\u26A0 ${providerErr.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39791
+ await sleep(RETRY_DELAY_MS);
39792
+ } else {
39793
+ console.error(source_default.dim(`
39794
+ [Veo error: ${providerErr}]`));
39795
+ }
39796
+ } catch (err) {
39797
+ const errMsg = err instanceof Error ? err.message : String(err);
39798
+ if (attempt < maxRetries) {
39799
+ onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
39800
+ await sleep(RETRY_DELAY_MS);
39801
+ } else {
39802
+ console.error(source_default.dim(`
39803
+ [Veo error: ${errMsg}]`));
39804
+ }
39805
+ }
39806
+ }
39807
+ return null;
39808
+ }
39809
+ var init_video_providers = __esm({
39810
+ "src/commands/_shared/video-providers.ts"() {
39811
+ "use strict";
39812
+ init_source();
39813
+ init_video_utils();
39814
+ }
39815
+ });
39816
+
39817
+ // src/commands/ai-script-pipeline.ts
39818
+ import { readFile as readFile24, writeFile as writeFile31, unlink as unlink2, rename as rename3 } from "node:fs/promises";
39819
+ import { resolve as resolve34, extname as extname8 } from "node:path";
39820
+ import { existsSync as existsSync26 } from "node:fs";
39821
+ async function executeRegenerateScene(options) {
39822
+ const result = {
39823
+ success: false,
39824
+ regeneratedScenes: [],
39825
+ failedScenes: []
39826
+ };
39827
+ try {
39828
+ const outputDir = resolve34(process.cwd(), options.projectDir);
39829
+ if (!existsSync26(outputDir)) {
39830
+ return { ...result, error: `Project directory not found: ${outputDir}` };
39831
+ }
39832
+ const yamlPath = resolve34(outputDir, "storyboard.yaml");
39833
+ const jsonPath = resolve34(outputDir, "storyboard.json");
39834
+ const storyboardPath = existsSync26(yamlPath) ? yamlPath : existsSync26(jsonPath) ? jsonPath : null;
39835
+ if (!storyboardPath) {
39836
+ return { ...result, error: `Storyboard not found in: ${outputDir} (expected storyboard.yaml or storyboard.json)` };
39837
+ }
39838
+ const storyboardContent = await readFile24(storyboardPath, "utf-8");
39839
+ const segments = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.parse)(storyboardContent).scenes : JSON.parse(storyboardContent);
39840
+ for (const sceneNum of options.scenes) {
39841
+ if (sceneNum < 1 || sceneNum > segments.length) {
39842
+ return { ...result, error: `Scene ${sceneNum} does not exist. Storyboard has ${segments.length} scenes.` };
39843
+ }
39844
+ }
39845
+ const regenerateVideo = options.videoOnly || !options.narrationOnly && !options.imageOnly;
39846
+ const regenerateNarration = options.narrationOnly || !options.videoOnly && !options.imageOnly;
39847
+ const regenerateImage = options.imageOnly || !options.videoOnly && !options.narrationOnly;
39848
+ let videoApiKey;
39849
+ if (regenerateVideo) {
39850
+ const generatorKeyMap = {
39851
+ grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
39852
+ kling: { envVar: "KLING_API_KEY", name: "Kling" },
39853
+ runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
39854
+ veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" }
39855
+ };
39856
+ const generator = options.generator || "grok";
39857
+ const genInfo = generatorKeyMap[generator];
39858
+ if (!genInfo) {
39859
+ return { ...result, error: `Invalid generator: ${generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
39860
+ }
39861
+ videoApiKey = await getApiKey(genInfo.envVar, genInfo.name) ?? void 0;
39862
+ if (!videoApiKey) {
39863
+ return { ...result, error: `${genInfo.name} API key required. Run 'vibe setup' or set ${genInfo.envVar} in .env` };
39864
+ }
39865
+ }
39866
+ let imageApiKey;
39867
+ if (regenerateImage) {
39868
+ const imageProvider = options.imageProvider || "openai";
39869
+ const imageKeyMap = {
39870
+ openai: { envVar: "OPENAI_API_KEY", name: "OpenAI" },
39871
+ gemini: { envVar: "GOOGLE_API_KEY", name: "Google" },
39872
+ grok: { envVar: "XAI_API_KEY", name: "xAI" }
39873
+ };
39874
+ const info = imageKeyMap[imageProvider];
39875
+ if (!info) {
39876
+ return { ...result, error: `Invalid imageProvider: ${imageProvider}` };
39877
+ }
39878
+ imageApiKey = await getApiKey(info.envVar, info.name) ?? void 0;
39879
+ if (!imageApiKey) {
39880
+ return { ...result, error: `${info.name} API key required. Run 'vibe setup' or set ${info.envVar} in .env` };
39881
+ }
39882
+ }
39883
+ let elevenlabsApiKey;
39884
+ if (regenerateNarration) {
39885
+ elevenlabsApiKey = await getApiKey("ELEVENLABS_API_KEY", "ElevenLabs") ?? void 0;
39886
+ if (!elevenlabsApiKey) {
39887
+ return { ...result, error: "ElevenLabs API key required. Run 'vibe setup' or set ELEVENLABS_API_KEY in .env" };
39888
+ }
39889
+ }
39890
+ let storyboardMutated = false;
39891
+ for (const sceneNum of options.scenes) {
39892
+ const segment = segments[sceneNum - 1];
39893
+ const narrationPath = resolve34(outputDir, `narration-${sceneNum}.mp3`);
39894
+ const imagePath = resolve34(outputDir, `scene-${sceneNum}.png`);
39895
+ const videoPath = resolve34(outputDir, `scene-${sceneNum}.mp4`);
39896
+ let sceneFailed = false;
39897
+ if (regenerateNarration && elevenlabsApiKey) {
39898
+ options.onProgress?.(`Scene ${sceneNum}: regenerating narration...`);
39899
+ const elevenlabs = new ElevenLabsProvider();
39900
+ await elevenlabs.initialize({ apiKey: elevenlabsApiKey });
39901
+ const narrationText = segment.narration || segment.description;
39902
+ const ttsResult = await elevenlabs.textToSpeech(narrationText, {
39903
+ voiceId: options.voice
39904
+ });
39905
+ if (ttsResult.success && ttsResult.audioBuffer) {
39906
+ await writeFile31(narrationPath, ttsResult.audioBuffer);
39907
+ segment.duration = await getAudioDuration(narrationPath);
39908
+ storyboardMutated = true;
39909
+ } else {
39910
+ sceneFailed = true;
39911
+ }
39912
+ }
39913
+ if (!sceneFailed && regenerateImage && imageApiKey) {
39914
+ options.onProgress?.(`Scene ${sceneNum}: regenerating image...`);
39915
+ const imageProvider = options.imageProvider || "openai";
39916
+ const characterDesc = segment.characterDescription || segments[0]?.characterDescription;
39917
+ let imagePrompt = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
39918
+ if (characterDesc) {
39919
+ imagePrompt = `${imagePrompt}
39920
+
39921
+ IMPORTANT - Character appearance must match exactly: ${characterDesc}`;
39922
+ }
39923
+ let referenceImageBuffer;
39924
+ const refSceneNum = options.referenceScene;
39925
+ if (refSceneNum && refSceneNum >= 1 && refSceneNum <= segments.length && refSceneNum !== sceneNum) {
39926
+ const refImagePath = resolve34(outputDir, `scene-${refSceneNum}.png`);
39927
+ if (existsSync26(refImagePath)) {
39928
+ referenceImageBuffer = await readFile24(refImagePath);
39929
+ }
39930
+ } else if (!refSceneNum) {
39931
+ for (let i = 1; i <= segments.length; i++) {
39932
+ if (i !== sceneNum) {
39933
+ const otherImagePath = resolve34(outputDir, `scene-${i}.png`);
39934
+ if (existsSync26(otherImagePath)) {
39935
+ referenceImageBuffer = await readFile24(otherImagePath);
39936
+ break;
39937
+ }
39938
+ }
39918
39939
  }
39919
39940
  }
39920
- }
39921
- if (result.appliedFixes && result.appliedFixes.length > 0) {
39922
- console.log();
39923
- console.log(source_default.bold.green("Applied Fixes:"));
39924
- for (const fix of result.appliedFixes) {
39925
- console.log(source_default.green(` + ${fix}`));
39941
+ const dalleImageSizes = {
39942
+ "16:9": "1536x1024",
39943
+ "9:16": "1024x1536",
39944
+ "1:1": "1024x1024"
39945
+ };
39946
+ let imageBuffer;
39947
+ let imageUrl;
39948
+ if (imageProvider === "openai") {
39949
+ const openaiImage = new OpenAIImageProvider();
39950
+ await openaiImage.initialize({ apiKey: imageApiKey });
39951
+ const imageResult = await openaiImage.generateImage(imagePrompt, {
39952
+ size: dalleImageSizes[options.aspectRatio || "16:9"] || "1536x1024",
39953
+ quality: "standard"
39954
+ });
39955
+ if (imageResult.success && imageResult.images?.[0]) {
39956
+ const img = imageResult.images[0];
39957
+ if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
39958
+ else if (img.url) imageUrl = img.url;
39959
+ }
39960
+ } else if (imageProvider === "gemini") {
39961
+ const gemini = new GeminiProvider();
39962
+ await gemini.initialize({ apiKey: imageApiKey });
39963
+ if (referenceImageBuffer) {
39964
+ const simplifiedVisuals = segment.visuals.split(/[,.]/).find(
39965
+ (part) => part.includes("standing") || part.includes("sitting") || part.includes("walking") || part.includes("lying") || part.includes("reaching") || part.includes("looking") || part.includes("working") || part.includes("coding") || part.includes("typing")
39966
+ ) || segment.visuals.split(".")[0];
39967
+ const editPrompt = `Generate a new image showing the SAME SINGLE person from the reference image in a new scene.
39968
+
39969
+ REFERENCE: Look at the person in the reference image - their face, hair, build, and overall appearance.
39970
+
39971
+ NEW SCENE: ${simplifiedVisuals}
39972
+
39973
+ CRITICAL RULES:
39974
+ 1. Show ONLY ONE person - the exact same individual from the reference image
39975
+ 2. The person must have the IDENTICAL face, hair style, and body type
39976
+ 3. Do NOT show multiple people or duplicate the character
39977
+ 4. Create a single moment in time, one pose, one action
39978
+ 5. Match the art style and quality of the reference image
39979
+
39980
+ Generate the single-person scene image now.`;
39981
+ const imageResult = await gemini.editImage([referenceImageBuffer], editPrompt, {
39982
+ aspectRatio: options.aspectRatio || "16:9"
39983
+ });
39984
+ if (imageResult.success && imageResult.images?.[0]?.base64) {
39985
+ imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
39986
+ }
39987
+ } else {
39988
+ const imageResult = await gemini.generateImage(imagePrompt, {
39989
+ aspectRatio: options.aspectRatio || "16:9"
39990
+ });
39991
+ if (imageResult.success && imageResult.images?.[0]?.base64) {
39992
+ imageBuffer = Buffer.from(imageResult.images[0].base64, "base64");
39993
+ }
39994
+ }
39995
+ } else if (imageProvider === "grok") {
39996
+ const grok = new GrokProvider();
39997
+ await grok.initialize({ apiKey: imageApiKey });
39998
+ const imageResult = await grok.generateImage(imagePrompt, {
39999
+ aspectRatio: options.aspectRatio || "16:9"
40000
+ });
40001
+ if (imageResult.success && imageResult.images?.[0]) {
40002
+ const img = imageResult.images[0];
40003
+ if (img.base64) imageBuffer = Buffer.from(img.base64, "base64");
40004
+ else if (img.url) imageUrl = img.url;
40005
+ }
39926
40006
  }
39927
- if (result.outputPath) {
39928
- console.log(source_default.green(` Output: ${result.outputPath}`));
40007
+ if (imageBuffer) {
40008
+ await writeFile31(imagePath, imageBuffer);
40009
+ } else if (imageUrl) {
40010
+ const response = await fetch(imageUrl);
40011
+ const buffer = Buffer.from(await response.arrayBuffer());
40012
+ await writeFile31(imagePath, buffer);
40013
+ } else {
40014
+ sceneFailed = true;
39929
40015
  }
39930
40016
  }
39931
- if (result.verificationScore !== void 0) {
39932
- console.log();
39933
- console.log(source_default.bold(`Verification Score: ${result.verificationScore}/10`));
40017
+ if (sceneFailed) {
40018
+ result.failedScenes.push(sceneNum);
40019
+ continue;
39934
40020
  }
39935
- if (fb.recommendations.length > 0) {
39936
- console.log();
39937
- console.log(source_default.bold("Recommendations:"));
39938
- for (const rec of fb.recommendations) {
39939
- console.log(source_default.dim(` * ${rec}`));
40021
+ if (regenerateVideo && videoApiKey) {
40022
+ options.onProgress?.(`Scene ${sceneNum}: regenerating video (${options.generator || "grok"})...`);
40023
+ if (!existsSync26(imagePath)) {
40024
+ result.failedScenes.push(sceneNum);
40025
+ continue;
40026
+ }
40027
+ const imageBuffer = await readFile24(imagePath);
40028
+ const videoDuration = segment.duration > 5 ? 10 : 5;
40029
+ const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
40030
+ if (options.generator === "grok") {
40031
+ const grok = new GrokProvider();
40032
+ await grok.initialize({ apiKey: videoApiKey });
40033
+ const ext = extname8(imagePath).toLowerCase().slice(1);
40034
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
40035
+ const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
40036
+ const grokDuration = Math.min(15, Math.max(1, segment.duration));
40037
+ const taskResult = await generateVideoWithRetryGrok(
40038
+ grok,
40039
+ segment,
40040
+ {
40041
+ duration: grokDuration,
40042
+ aspectRatio: options.aspectRatio || "16:9",
40043
+ referenceImage
40044
+ },
40045
+ maxRetries
40046
+ );
40047
+ if (taskResult) {
40048
+ try {
40049
+ const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
40050
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
40051
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
40052
+ await writeFile31(videoPath, buffer);
40053
+ const targetDuration = segment.duration;
40054
+ const actualVideoDuration = await getVideoDuration(videoPath);
40055
+ if (actualVideoDuration < targetDuration - 0.1) {
40056
+ const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
40057
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
40058
+ await unlink2(videoPath);
40059
+ await rename3(extendedPath, videoPath);
40060
+ }
40061
+ result.regeneratedScenes.push(sceneNum);
40062
+ } else {
40063
+ logSceneFailure("Grok", `scene ${sceneNum}`, waitResult);
40064
+ result.failedScenes.push(sceneNum);
40065
+ }
40066
+ } catch (err) {
40067
+ logSceneFailure("Grok", `scene ${sceneNum}`, err);
40068
+ result.failedScenes.push(sceneNum);
40069
+ }
40070
+ } else {
40071
+ result.failedScenes.push(sceneNum);
40072
+ }
40073
+ } else if (options.generator === "veo") {
40074
+ const veo = new GeminiProvider();
40075
+ await veo.initialize({ apiKey: videoApiKey });
40076
+ const ext = extname8(imagePath).toLowerCase().slice(1);
40077
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
40078
+ const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
40079
+ const veoDuration = segment.duration > 6 ? 8 : segment.duration > 4 ? 6 : 4;
40080
+ const taskResult = await generateVideoWithRetryVeo(
40081
+ veo,
40082
+ segment,
40083
+ {
40084
+ duration: veoDuration,
40085
+ aspectRatio: options.aspectRatio || "16:9",
40086
+ referenceImage
40087
+ },
40088
+ maxRetries
40089
+ );
40090
+ if (taskResult) {
40091
+ try {
40092
+ const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
40093
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
40094
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
40095
+ await writeFile31(videoPath, buffer);
40096
+ const targetDuration = segment.duration;
40097
+ const actualVideoDuration = await getVideoDuration(videoPath);
40098
+ if (actualVideoDuration < targetDuration - 0.1) {
40099
+ const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
40100
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
40101
+ await unlink2(videoPath);
40102
+ await rename3(extendedPath, videoPath);
40103
+ }
40104
+ result.regeneratedScenes.push(sceneNum);
40105
+ } else {
40106
+ logSceneFailure("Veo", `scene ${sceneNum}`, waitResult);
40107
+ result.failedScenes.push(sceneNum);
40108
+ }
40109
+ } catch (err) {
40110
+ logSceneFailure("Veo", `scene ${sceneNum}`, err);
40111
+ result.failedScenes.push(sceneNum);
40112
+ }
40113
+ } else {
40114
+ result.failedScenes.push(sceneNum);
40115
+ }
40116
+ } else if (options.generator === "kling" || !options.generator) {
40117
+ const kling = new KlingProvider();
40118
+ await kling.initialize({ apiKey: videoApiKey });
40119
+ if (!kling.isConfigured()) {
40120
+ result.failedScenes.push(sceneNum);
40121
+ continue;
40122
+ }
40123
+ const imgbbApiKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
40124
+ let imageUrl;
40125
+ if (imgbbApiKey) {
40126
+ const uploadResult = await uploadToImgbb(imageBuffer, imgbbApiKey);
40127
+ if (uploadResult.success && uploadResult.url) {
40128
+ imageUrl = uploadResult.url;
40129
+ }
40130
+ }
40131
+ const taskResult = await generateVideoWithRetryKling(
40132
+ kling,
40133
+ segment,
40134
+ {
40135
+ duration: videoDuration,
40136
+ aspectRatio: options.aspectRatio || "16:9",
40137
+ referenceImage: imageUrl
40138
+ },
40139
+ maxRetries
40140
+ );
40141
+ if (taskResult) {
40142
+ try {
40143
+ const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
40144
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
40145
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
40146
+ await writeFile31(videoPath, buffer);
40147
+ await extendVideoToTarget(
40148
+ videoPath,
40149
+ segment.duration,
40150
+ outputDir,
40151
+ `Scene ${sceneNum}`,
40152
+ {
40153
+ kling,
40154
+ videoId: waitResult.videoId,
40155
+ onProgress: options.onProgress
40156
+ }
40157
+ );
40158
+ result.regeneratedScenes.push(sceneNum);
40159
+ } else {
40160
+ logSceneFailure("Kling", `scene ${sceneNum}`, waitResult);
40161
+ result.failedScenes.push(sceneNum);
40162
+ }
40163
+ } catch (err) {
40164
+ logSceneFailure("Kling", `scene ${sceneNum}`, err);
40165
+ result.failedScenes.push(sceneNum);
40166
+ }
40167
+ } else {
40168
+ result.failedScenes.push(sceneNum);
40169
+ }
40170
+ } else {
40171
+ const runway = new RunwayProvider();
40172
+ await runway.initialize({ apiKey: videoApiKey });
40173
+ const ext = extname8(imagePath).toLowerCase().slice(1);
40174
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
40175
+ const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
40176
+ const aspectRatio = options.aspectRatio === "1:1" ? "16:9" : options.aspectRatio || "16:9";
40177
+ const taskResult = await generateVideoWithRetryRunway(
40178
+ runway,
40179
+ segment,
40180
+ referenceImage,
40181
+ { duration: videoDuration, aspectRatio },
40182
+ maxRetries
40183
+ );
40184
+ if (taskResult) {
40185
+ try {
40186
+ const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
40187
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
40188
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
40189
+ await writeFile31(videoPath, buffer);
40190
+ const targetDuration = segment.duration;
40191
+ const actualVideoDuration = await getVideoDuration(videoPath);
40192
+ if (actualVideoDuration < targetDuration - 0.1) {
40193
+ const extendedPath = resolve34(outputDir, `scene-${sceneNum}-extended.mp4`);
40194
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
40195
+ await unlink2(videoPath);
40196
+ await rename3(extendedPath, videoPath);
40197
+ }
40198
+ result.regeneratedScenes.push(sceneNum);
40199
+ } else {
40200
+ logSceneFailure("Runway", `scene ${sceneNum}`, waitResult);
40201
+ result.failedScenes.push(sceneNum);
40202
+ }
40203
+ } catch (err) {
40204
+ logSceneFailure("Runway", `scene ${sceneNum}`, err);
40205
+ result.failedScenes.push(sceneNum);
40206
+ }
40207
+ } else {
40208
+ result.failedScenes.push(sceneNum);
40209
+ }
39940
40210
  }
40211
+ } else if (!sceneFailed) {
40212
+ result.regeneratedScenes.push(sceneNum);
39941
40213
  }
39942
- console.log();
39943
- } catch (error) {
39944
- exitWithError(generalError(error instanceof Error ? error.message : "Video review failed"));
39945
40214
  }
39946
- });
40215
+ if (storyboardMutated) {
40216
+ let currentTime = 0;
40217
+ for (const segment of segments) {
40218
+ segment.startTime = currentTime;
40219
+ currentTime += segment.duration;
40220
+ }
40221
+ const serialized = storyboardPath.endsWith(".yaml") ? (0, import_yaml5.stringify)({ scenes: segments }, { indent: 2 }) : JSON.stringify(segments, null, 2);
40222
+ await writeFile31(storyboardPath, serialized, "utf-8");
40223
+ }
40224
+ result.success = result.failedScenes.length === 0;
40225
+ return result;
40226
+ } catch (error) {
40227
+ return {
40228
+ ...result,
40229
+ error: error instanceof Error ? error.message : String(error)
40230
+ };
40231
+ }
39947
40232
  }
39948
- var init_ai_review = __esm({
39949
- "src/commands/ai-review.ts"() {
40233
+ var import_yaml5;
40234
+ var init_ai_script_pipeline = __esm({
40235
+ "src/commands/ai-script-pipeline.ts"() {
39950
40236
  "use strict";
39951
- init_source();
39952
- init_ora();
40237
+ import_yaml5 = __toESM(require_dist(), 1);
39953
40238
  init_dist2();
39954
40239
  init_api_key();
39955
- init_exec_safe();
39956
- init_output();
39957
- init_validate();
40240
+ init_config();
40241
+ init_audio();
40242
+ init_ai_helpers();
40243
+ init_video_utils();
40244
+ init_video_providers();
39958
40245
  }
39959
40246
  });
39960
40247
 
39961
40248
  // src/commands/ai-highlights.ts
39962
40249
  import { readFile as readFile26, writeFile as writeFile33, mkdir as mkdir16 } from "node:fs/promises";
39963
- import { resolve as resolve36, dirname as dirname12, basename as basename9, extname as extname8 } from "node:path";
40250
+ import { resolve as resolve36, dirname as dirname12, basename as basename9, extname as extname9 } from "node:path";
39964
40251
  import { existsSync as existsSync28 } from "node:fs";
39965
40252
  function filterHighlights(highlights, options) {
39966
40253
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
@@ -40004,7 +40291,7 @@ async function executeHighlights(options) {
40004
40291
  if (!existsSync28(absPath)) {
40005
40292
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
40006
40293
  }
40007
- const ext = extname8(absPath).toLowerCase();
40294
+ const ext = extname9(absPath).toLowerCase();
40008
40295
  const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
40009
40296
  const isVideo = videoExtensions.includes(ext);
40010
40297
  const targetDuration = options.duration;
@@ -40341,7 +40628,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
40341
40628
  };
40342
40629
  for (let i = 0; i < selectedHighlights.length; i++) {
40343
40630
  const h = selectedHighlights[i];
40344
- const baseName = basename9(absPath, extname8(absPath));
40631
+ const baseName = basename9(absPath, extname9(absPath));
40345
40632
  const outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
40346
40633
  const { stdout: probeOut } = await execSafe("ffprobe", [
40347
40634
  "-v",
@@ -40440,7 +40727,7 @@ function registerHighlightsCommands(aiCommand) {
40440
40727
  });
40441
40728
  return;
40442
40729
  }
40443
- const ext = extname8(absPath).toLowerCase();
40730
+ const ext = extname9(absPath).toLowerCase();
40444
40731
  const videoExtensions = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
40445
40732
  const isVideo = videoExtensions.includes(ext);
40446
40733
  console.log();
@@ -40918,11 +41205,11 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
40918
41205
  let outputPath;
40919
41206
  if (shortCount === 1 && options.output) {
40920
41207
  outputPath = resolve36(process.cwd(), options.output);
40921
- if (!extname8(outputPath)) {
41208
+ if (!extname9(outputPath)) {
40922
41209
  outputPath += ".mp4";
40923
41210
  }
40924
41211
  } else {
40925
- const baseName = basename9(absPath, extname8(absPath));
41212
+ const baseName = basename9(absPath, extname9(absPath));
40926
41213
  outputPath = resolve36(outputDir, `${baseName}-short-${i + 1}.mp4`);
40927
41214
  }
40928
41215
  const parentDir = dirname12(outputPath);
@@ -51808,7 +52095,7 @@ var init_scene_audio_scan = __esm({
51808
52095
 
51809
52096
  // src/commands/_shared/scene-audio-mux.ts
51810
52097
  import { rename as rename4, unlink as unlink3 } from "node:fs/promises";
51811
- import { resolve as resolve45, dirname as dirname15, extname as extname11, basename as basename16 } from "node:path";
52098
+ import { resolve as resolve45, dirname as dirname15, extname as extname12, basename as basename16 } from "node:path";
51812
52099
  function buildAudioMuxFilter(audios) {
51813
52100
  if (audios.length === 0) return null;
51814
52101
  const labels = [];
@@ -51867,7 +52154,7 @@ async function muxAudioIntoVideo(opts) {
51867
52154
  if (!filter) {
51868
52155
  return { success: true, outputPath: opts.videoPath, audioCount: 0 };
51869
52156
  }
51870
- const ext = extname11(opts.videoPath) || `.${opts.format}`;
52157
+ const ext = extname12(opts.videoPath) || `.${opts.format}`;
51871
52158
  const tmpPath = resolve45(
51872
52159
  dirname15(opts.videoPath),
51873
52160
  `.${basename16(opts.videoPath, ext)}.muxing${ext}`
@@ -52525,12 +52812,22 @@ async function executeVideoGenerate(options) {
52525
52812
  const key = apiKey || process.env[envKeyMap[provider] || ""];
52526
52813
  if (!key) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
52527
52814
  let referenceImage;
52815
+ let referenceImageBuffer;
52816
+ let referenceImageMimeType;
52528
52817
  if (image) {
52529
52818
  const imagePath = resolve53(process.cwd(), image);
52530
52819
  const imageBuffer = await readFile35(imagePath);
52531
52820
  const ext = image.toLowerCase().split(".").pop();
52532
- const mimeTypes2 = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
52821
+ const mimeTypes2 = {
52822
+ jpg: "image/jpeg",
52823
+ jpeg: "image/jpeg",
52824
+ png: "image/png",
52825
+ gif: "image/gif",
52826
+ webp: "image/webp"
52827
+ };
52533
52828
  const mimeType = mimeTypes2[ext || "png"] || "image/png";
52829
+ referenceImageBuffer = imageBuffer;
52830
+ referenceImageMimeType = mimeType;
52534
52831
  referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
52535
52832
  }
52536
52833
  if (provider === "seedance" || provider === "fal") {
@@ -52538,12 +52835,12 @@ async function executeVideoGenerate(options) {
52538
52835
  await fal.initialize({ apiKey: key });
52539
52836
  let falImage = referenceImage;
52540
52837
  if (falImage && falImage.startsWith("data:")) {
52541
- const imgbbKey = process.env.IMGBB_API_KEY;
52542
- if (!imgbbKey) return { success: false, error: "IMGBB_API_KEY required for Seedance image-to-video" };
52543
- const base64Data = falImage.split(",")[1];
52544
- const uploadResult = await uploadToImgbb(Buffer.from(base64Data, "base64"), imgbbKey);
52545
- if (!uploadResult.success || !uploadResult.url) return { success: false, error: `ImgBB upload failed: ${uploadResult.error}` };
52546
- falImage = uploadResult.url;
52838
+ const uploadHost = await resolveUploadHost();
52839
+ const upload = await uploadHost.uploadImage(referenceImageBuffer, {
52840
+ filename: image,
52841
+ mimeType: referenceImageMimeType
52842
+ });
52843
+ falImage = upload.url;
52547
52844
  }
52548
52845
  const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
52549
52846
  const result = await fal.generateVideo(prompt3, {
@@ -52554,14 +52851,22 @@ async function executeVideoGenerate(options) {
52554
52851
  negativePrompt: negative,
52555
52852
  model
52556
52853
  });
52557
- if (result.status === "failed") return { success: false, error: result.error || "Seedance generation failed" };
52854
+ if (result.status === "failed")
52855
+ return { success: false, error: result.error || "Seedance generation failed" };
52558
52856
  let outputPath;
52559
52857
  if (output && result.videoUrl) {
52560
52858
  const buffer = await downloadVideo(result.videoUrl, key);
52561
52859
  outputPath = resolve53(process.cwd(), output);
52562
52860
  await writeFile39(outputPath, buffer);
52563
52861
  }
52564
- return { success: true, taskId: result.id, status: "completed", videoUrl: result.videoUrl, outputPath, provider: "seedance" };
52862
+ return {
52863
+ success: true,
52864
+ taskId: result.id,
52865
+ status: "completed",
52866
+ videoUrl: result.videoUrl,
52867
+ outputPath,
52868
+ provider: "seedance"
52869
+ };
52565
52870
  } else if (provider === "runway") {
52566
52871
  const runway = new RunwayProvider();
52567
52872
  await runway.initialize({ apiKey: key });
@@ -52572,30 +52877,41 @@ async function executeVideoGenerate(options) {
52572
52877
  aspectRatio: ratio,
52573
52878
  seed
52574
52879
  });
52575
- if (result.status === "failed") return { success: false, error: result.error || "Runway generation failed" };
52576
- if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "runway" };
52880
+ if (result.status === "failed")
52881
+ return { success: false, error: result.error || "Runway generation failed" };
52882
+ if (!wait)
52883
+ return { success: true, taskId: result.id, status: "processing", provider: "runway" };
52577
52884
  const finalResult = await runway.waitForCompletion(result.id, () => {
52578
52885
  }, 3e5);
52579
- if (finalResult.status !== "completed") return { success: false, error: finalResult.error || "Runway generation failed" };
52886
+ if (finalResult.status !== "completed")
52887
+ return { success: false, error: finalResult.error || "Runway generation failed" };
52580
52888
  let outputPath;
52581
52889
  if (output && finalResult.videoUrl) {
52582
52890
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52583
52891
  outputPath = resolve53(process.cwd(), output);
52584
52892
  await writeFile39(outputPath, buffer);
52585
52893
  }
52586
- return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "runway" };
52894
+ return {
52895
+ success: true,
52896
+ taskId: result.id,
52897
+ status: "completed",
52898
+ videoUrl: finalResult.videoUrl,
52899
+ duration: finalResult.duration,
52900
+ outputPath,
52901
+ provider: "runway"
52902
+ };
52587
52903
  } else if (provider === "kling") {
52588
52904
  const kling = new KlingProvider();
52589
52905
  await kling.initialize({ apiKey: key });
52590
52906
  if (!kling.isConfigured()) return { success: false, error: "Invalid Kling API key format" };
52591
52907
  let klingImage = referenceImage;
52592
52908
  if (klingImage && klingImage.startsWith("data:")) {
52593
- const imgbbKey = process.env.IMGBB_API_KEY;
52594
- if (!imgbbKey) return { success: false, error: "IMGBB_API_KEY required for Kling image-to-video" };
52595
- const base64Data = klingImage.split(",")[1];
52596
- const uploadResult = await uploadToImgbb(Buffer.from(base64Data, "base64"), imgbbKey);
52597
- if (!uploadResult.success || !uploadResult.url) return { success: false, error: `ImgBB upload failed: ${uploadResult.error}` };
52598
- klingImage = uploadResult.url;
52909
+ const uploadHost = await resolveUploadHost();
52910
+ const upload = await uploadHost.uploadImage(referenceImageBuffer, {
52911
+ filename: image,
52912
+ mimeType: referenceImageMimeType
52913
+ });
52914
+ klingImage = upload.url;
52599
52915
  }
52600
52916
  const result = await kling.generateVideo(prompt3, {
52601
52917
  prompt: prompt3,
@@ -52605,23 +52921,38 @@ async function executeVideoGenerate(options) {
52605
52921
  negativePrompt: negative,
52606
52922
  mode
52607
52923
  });
52608
- if (result.status === "failed") return { success: false, error: result.error || "Kling generation failed" };
52924
+ if (result.status === "failed")
52925
+ return { success: false, error: result.error || "Kling generation failed" };
52609
52926
  const taskType = referenceImage ? "image2video" : "text2video";
52610
- if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "kling" };
52927
+ if (!wait)
52928
+ return { success: true, taskId: result.id, status: "processing", provider: "kling" };
52611
52929
  const finalResult = await kling.waitForCompletion(result.id, taskType, () => {
52612
52930
  }, 6e5);
52613
- if (finalResult.status !== "completed") return { success: false, error: finalResult.error || "Kling generation failed" };
52931
+ if (finalResult.status !== "completed")
52932
+ return { success: false, error: finalResult.error || "Kling generation failed" };
52614
52933
  let outputPath;
52615
52934
  if (output && finalResult.videoUrl) {
52616
52935
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52617
52936
  outputPath = resolve53(process.cwd(), output);
52618
52937
  await writeFile39(outputPath, buffer);
52619
52938
  }
52620
- return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "kling" };
52939
+ return {
52940
+ success: true,
52941
+ taskId: result.id,
52942
+ status: "completed",
52943
+ videoUrl: finalResult.videoUrl,
52944
+ duration: finalResult.duration,
52945
+ outputPath,
52946
+ provider: "kling"
52947
+ };
52621
52948
  } else if (provider === "veo") {
52622
52949
  const gemini = new GeminiProvider();
52623
52950
  await gemini.initialize({ apiKey: key });
52624
- const veoModelMap = { "3.0": "veo-3.0-generate-preview", "3.1": "veo-3.1-generate-preview", "3.1-fast": "veo-3.1-fast-generate-preview" };
52951
+ const veoModelMap = {
52952
+ "3.0": "veo-3.0-generate-preview",
52953
+ "3.1": "veo-3.1-generate-preview",
52954
+ "3.1-fast": "veo-3.1-fast-generate-preview"
52955
+ };
52625
52956
  const model = veoModelMap[veoModel] || "veo-3.1-fast-generate-preview";
52626
52957
  const veoDuration = duration <= 6 ? 6 : 8;
52627
52958
  const result = await gemini.generateVideo(prompt3, {
@@ -52633,18 +52964,27 @@ async function executeVideoGenerate(options) {
52633
52964
  negativePrompt: negative,
52634
52965
  resolution
52635
52966
  });
52636
- if (result.status === "failed") return { success: false, error: result.error || "Veo generation failed" };
52967
+ if (result.status === "failed")
52968
+ return { success: false, error: result.error || "Veo generation failed" };
52637
52969
  if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "veo" };
52638
52970
  const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
52639
52971
  }, 3e5);
52640
- if (finalResult.status !== "completed") return { success: false, error: finalResult.error || "Veo generation failed" };
52972
+ if (finalResult.status !== "completed")
52973
+ return { success: false, error: finalResult.error || "Veo generation failed" };
52641
52974
  let outputPath;
52642
52975
  if (output && finalResult.videoUrl) {
52643
52976
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52644
52977
  outputPath = resolve53(process.cwd(), output);
52645
52978
  await writeFile39(outputPath, buffer);
52646
52979
  }
52647
- return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, outputPath, provider: "veo" };
52980
+ return {
52981
+ success: true,
52982
+ taskId: result.id,
52983
+ status: "completed",
52984
+ videoUrl: finalResult.videoUrl,
52985
+ outputPath,
52986
+ provider: "veo"
52987
+ };
52648
52988
  } else if (provider === "grok") {
52649
52989
  const grok = new GrokProvider();
52650
52990
  await grok.initialize({ apiKey: key });
@@ -52654,28 +52994,52 @@ async function executeVideoGenerate(options) {
52654
52994
  duration,
52655
52995
  aspectRatio: ratio
52656
52996
  });
52657
- if (result.status === "failed") return { success: false, error: result.error || "Grok generation failed" };
52658
- if (!wait) return { success: true, taskId: result.id, status: "processing", provider: "grok" };
52997
+ if (result.status === "failed")
52998
+ return { success: false, error: result.error || "Grok generation failed" };
52999
+ if (!wait)
53000
+ return { success: true, taskId: result.id, status: "processing", provider: "grok" };
52659
53001
  const finalResult = await grok.waitForCompletion(result.id, () => {
52660
53002
  }, 3e5);
52661
- if (finalResult.status !== "completed") return { success: false, error: finalResult.error || "Grok generation failed" };
53003
+ if (finalResult.status !== "completed")
53004
+ return { success: false, error: finalResult.error || "Grok generation failed" };
52662
53005
  let outputPath;
52663
53006
  if (output && finalResult.videoUrl) {
52664
53007
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52665
53008
  outputPath = resolve53(process.cwd(), output);
52666
53009
  await writeFile39(outputPath, buffer);
52667
53010
  }
52668
- return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "grok" };
53011
+ return {
53012
+ success: true,
53013
+ taskId: result.id,
53014
+ status: "completed",
53015
+ videoUrl: finalResult.videoUrl,
53016
+ duration: finalResult.duration,
53017
+ outputPath,
53018
+ provider: "grok"
53019
+ };
52669
53020
  }
52670
53021
  return { success: false, error: `Unsupported provider: ${provider}` };
52671
53022
  } catch (error) {
52672
- return { success: false, error: `Video generation failed: ${error instanceof Error ? error.message : String(error)}` };
53023
+ return {
53024
+ success: false,
53025
+ error: `Video generation failed: ${error instanceof Error ? error.message : String(error)}`
53026
+ };
52673
53027
  }
52674
53028
  }
52675
53029
  async function executeVideoStatus(options) {
52676
- const { taskId, provider = "runway", taskType = "text2video", wait = false, output, apiKey } = options;
53030
+ const {
53031
+ taskId,
53032
+ provider = "runway",
53033
+ taskType = "text2video",
53034
+ wait = false,
53035
+ output,
53036
+ apiKey
53037
+ } = options;
52677
53038
  try {
52678
- const envKeyMap = { runway: "RUNWAY_API_SECRET", kling: "KLING_API_KEY" };
53039
+ const envKeyMap = {
53040
+ runway: "RUNWAY_API_SECRET",
53041
+ kling: "KLING_API_KEY"
53042
+ };
52679
53043
  const key = apiKey || process.env[envKeyMap[provider] || ""];
52680
53044
  if (!key) return { success: false, error: `${envKeyMap[provider]} required` };
52681
53045
  if (provider === "runway") {
@@ -52692,7 +53056,14 @@ async function executeVideoStatus(options) {
52692
53056
  outputPath = resolve53(process.cwd(), output);
52693
53057
  await writeFile39(outputPath, buffer);
52694
53058
  }
52695
- return { success: true, taskId, status: result.status, progress: result.progress, videoUrl: result.videoUrl, outputPath };
53059
+ return {
53060
+ success: true,
53061
+ taskId,
53062
+ status: result.status,
53063
+ progress: result.progress,
53064
+ videoUrl: result.videoUrl,
53065
+ outputPath
53066
+ };
52696
53067
  } else if (provider === "kling") {
52697
53068
  const kling = new KlingProvider();
52698
53069
  await kling.initialize({ apiKey: key });
@@ -52707,11 +53078,21 @@ async function executeVideoStatus(options) {
52707
53078
  outputPath = resolve53(process.cwd(), output);
52708
53079
  await writeFile39(outputPath, buffer);
52709
53080
  }
52710
- return { success: true, taskId, status: result.status, videoUrl: result.videoUrl, duration: result.duration, outputPath };
53081
+ return {
53082
+ success: true,
53083
+ taskId,
53084
+ status: result.status,
53085
+ videoUrl: result.videoUrl,
53086
+ duration: result.duration,
53087
+ outputPath
53088
+ };
52711
53089
  }
52712
53090
  return { success: false, error: `Unsupported provider: ${provider}` };
52713
53091
  } catch (error) {
52714
- return { success: false, error: `Status check failed: ${error instanceof Error ? error.message : String(error)}` };
53092
+ return {
53093
+ success: false,
53094
+ error: `Status check failed: ${error instanceof Error ? error.message : String(error)}`
53095
+ };
52715
53096
  }
52716
53097
  }
52717
53098
  async function executeVideoCancel(options) {
@@ -52724,11 +53105,24 @@ async function executeVideoCancel(options) {
52724
53105
  const success = await runway.cancelGeneration(taskId);
52725
53106
  return { success };
52726
53107
  } catch (error) {
52727
- return { success: false, error: `Cancel failed: ${error instanceof Error ? error.message : String(error)}` };
53108
+ return {
53109
+ success: false,
53110
+ error: `Cancel failed: ${error instanceof Error ? error.message : String(error)}`
53111
+ };
52728
53112
  }
52729
53113
  }
52730
53114
  async function executeVideoExtend(options) {
52731
- const { videoId, provider = "kling", prompt: prompt3, duration = 5, negative, veoModel = "3.1", output, wait = true, apiKey } = options;
53115
+ const {
53116
+ videoId,
53117
+ provider = "kling",
53118
+ prompt: prompt3,
53119
+ duration = 5,
53120
+ negative,
53121
+ veoModel = "3.1",
53122
+ output,
53123
+ wait = true,
53124
+ apiKey
53125
+ } = options;
52732
53126
  try {
52733
53127
  if (provider === "kling") {
52734
53128
  const key = apiKey || process.env.KLING_API_KEY;
@@ -52741,52 +53135,76 @@ async function executeVideoExtend(options) {
52741
53135
  negativePrompt: negative,
52742
53136
  duration: String(duration)
52743
53137
  });
52744
- if (result.status === "failed") return { success: false, error: result.error || "Kling extension failed" };
53138
+ if (result.status === "failed")
53139
+ return { success: false, error: result.error || "Kling extension failed" };
52745
53140
  if (!wait) return { success: true, taskId: result.id, status: "processing" };
52746
53141
  const finalResult = await kling.waitForExtendCompletion(result.id, () => {
52747
53142
  }, 6e5);
52748
- if (finalResult.status !== "completed") return { success: false, error: finalResult.error || "Kling extension failed" };
53143
+ if (finalResult.status !== "completed")
53144
+ return { success: false, error: finalResult.error || "Kling extension failed" };
52749
53145
  let outputPath;
52750
53146
  if (output && finalResult.videoUrl) {
52751
53147
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52752
53148
  outputPath = resolve53(process.cwd(), output);
52753
53149
  await writeFile39(outputPath, buffer);
52754
53150
  }
52755
- return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath };
53151
+ return {
53152
+ success: true,
53153
+ taskId: result.id,
53154
+ status: "completed",
53155
+ videoUrl: finalResult.videoUrl,
53156
+ duration: finalResult.duration,
53157
+ outputPath
53158
+ };
52756
53159
  } else if (provider === "veo") {
52757
53160
  const key = apiKey || process.env.GOOGLE_API_KEY;
52758
53161
  if (!key) return { success: false, error: "GOOGLE_API_KEY required" };
52759
53162
  const gemini = new GeminiProvider();
52760
53163
  await gemini.initialize({ apiKey: key });
52761
- const veoModelMap = { "3.0": "veo-3.0-generate-preview", "3.1": "veo-3.1-generate-preview", "3.1-fast": "veo-3.1-fast-generate-preview" };
53164
+ const veoModelMap = {
53165
+ "3.0": "veo-3.0-generate-preview",
53166
+ "3.1": "veo-3.1-generate-preview",
53167
+ "3.1-fast": "veo-3.1-fast-generate-preview"
53168
+ };
52762
53169
  const model = veoModelMap[veoModel] || "veo-3.1-generate-preview";
52763
53170
  const result = await gemini.extendVideo(videoId, prompt3, {
52764
53171
  duration,
52765
53172
  model
52766
53173
  });
52767
- if (result.status === "failed") return { success: false, error: result.error || "Veo extension failed" };
53174
+ if (result.status === "failed")
53175
+ return { success: false, error: result.error || "Veo extension failed" };
52768
53176
  if (!wait) return { success: true, taskId: result.id, status: "processing" };
52769
53177
  const finalResult = await gemini.waitForVideoCompletion(result.id, () => {
52770
53178
  }, 3e5);
52771
- if (finalResult.status !== "completed") return { success: false, error: finalResult.error || "Veo extension failed" };
53179
+ if (finalResult.status !== "completed")
53180
+ return { success: false, error: finalResult.error || "Veo extension failed" };
52772
53181
  let outputPath;
52773
53182
  if (output && finalResult.videoUrl) {
52774
53183
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52775
53184
  outputPath = resolve53(process.cwd(), output);
52776
53185
  await writeFile39(outputPath, buffer);
52777
53186
  }
52778
- return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, outputPath };
53187
+ return {
53188
+ success: true,
53189
+ taskId: result.id,
53190
+ status: "completed",
53191
+ videoUrl: finalResult.videoUrl,
53192
+ outputPath
53193
+ };
52779
53194
  }
52780
53195
  return { success: false, error: `Unsupported provider: ${provider}` };
52781
53196
  } catch (error) {
52782
- return { success: false, error: `Video extension failed: ${error instanceof Error ? error.message : String(error)}` };
53197
+ return {
53198
+ success: false,
53199
+ error: `Video extension failed: ${error instanceof Error ? error.message : String(error)}`
53200
+ };
52783
53201
  }
52784
53202
  }
52785
53203
  var init_ai_video = __esm({
52786
53204
  "src/commands/ai-video.ts"() {
52787
53205
  "use strict";
52788
53206
  init_dist2();
52789
- init_ai_script_pipeline();
53207
+ init_upload_host();
52790
53208
  init_ai_helpers();
52791
53209
  }
52792
53210
  });
@@ -52800,7 +53218,7 @@ __export(ai_audio_exports, {
52800
53218
  executeTranscribe: () => executeTranscribe,
52801
53219
  executeVoiceClone: () => executeVoiceClone
52802
53220
  });
52803
- import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as extname12 } from "node:path";
53221
+ import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as extname13 } from "node:path";
52804
53222
  import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
52805
53223
  import { existsSync as existsSync38 } from "node:fs";
52806
53224
  async function executeTranscribe(options) {
@@ -52897,7 +53315,7 @@ async function executeDub(options) {
52897
53315
  if (!openaiKey) return { success: false, error: "OPENAI_API_KEY required for Whisper transcription" };
52898
53316
  if (!anthropicKey) return { success: false, error: "ANTHROPIC_API_KEY required for Claude translation" };
52899
53317
  if (!analyzeOnly && !elevenlabsKey) return { success: false, error: "ELEVENLABS_API_KEY required for TTS" };
52900
- const ext = extname12(absPath).toLowerCase();
53318
+ const ext = extname13(absPath).toLowerCase();
52901
53319
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
52902
53320
  let audioPath = absPath;
52903
53321
  if (isVideo) {
@@ -52982,8 +53400,8 @@ ${segmentTexts}`,
52982
53400
  }
52983
53401
  }
52984
53402
  const combinedBuffer = Buffer.concat(dubbedBuffers);
52985
- const outputExt = isVideo ? ".mp3" : extname12(absPath);
52986
- const defaultOutputPath2 = resolve54(dirname19(absPath), `${basename18(absPath, extname12(absPath))}-${language}${outputExt}`);
53403
+ const outputExt = isVideo ? ".mp3" : extname13(absPath);
53404
+ const defaultOutputPath2 = resolve54(dirname19(absPath), `${basename18(absPath, extname13(absPath))}-${language}${outputExt}`);
52987
53405
  const finalOutputPath = resolve54(process.cwd(), output || defaultOutputPath2);
52988
53406
  await writeFile40(finalOutputPath, combinedBuffer);
52989
53407
  if (isVideo && audioPath !== absPath) {
@@ -53020,7 +53438,7 @@ async function executeDuck(options) {
53020
53438
  const absVoicePath = resolve54(process.cwd(), voicePath);
53021
53439
  if (!existsSync38(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
53022
53440
  if (!existsSync38(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
53023
- const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
53441
+ const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname13(absMusicPath))}-ducked${extname13(absMusicPath)}`);
53024
53442
  const outputPath = resolve54(process.cwd(), output || defaultOutput);
53025
53443
  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`;
53026
53444
  await execSafe("ffmpeg", [
@@ -59597,11 +60015,17 @@ var init_generate2 = __esm({
59597
60015
  }),
59598
60016
  async execute(args) {
59599
60017
  const result = await executeMotion(args);
59600
- if (!result.success) return { success: false, error: result.error ?? "Motion generation failed" };
60018
+ if (!result.success)
60019
+ return { success: false, error: result.error ?? "Motion generation failed" };
59601
60020
  const out = result.compositedPath ?? result.renderedPath ?? result.codePath;
59602
60021
  return {
59603
60022
  success: true,
59604
- data: { codePath: result.codePath, renderedPath: result.renderedPath, compositedPath: result.compositedPath, componentName: result.componentName },
60023
+ data: {
60024
+ codePath: result.codePath,
60025
+ renderedPath: result.renderedPath,
60026
+ compositedPath: result.compositedPath,
60027
+ componentName: result.componentName
60028
+ },
59605
60029
  humanLines: [`\u2705 Motion generated \u2192 ${out}`]
59606
60030
  };
59607
60031
  }
@@ -59665,7 +60089,9 @@ var init_generate2 = __esm({
59665
60089
  return {
59666
60090
  success: true,
59667
60091
  data: { outputPath: result.outputPath, provider: result.provider, duration: result.duration },
59668
- humanLines: [`\u2705 Music${result.provider ? ` (${result.provider})` : ""} \u2192 ${result.outputPath ?? "(async)"}`]
60092
+ humanLines: [
60093
+ `\u2705 Music${result.provider ? ` (${result.provider})` : ""} \u2192 ${result.outputPath ?? "(async)"}`
60094
+ ]
59669
60095
  };
59670
60096
  }
59671
60097
  });
@@ -59682,7 +60108,12 @@ var init_generate2 = __esm({
59682
60108
  if (!result.success) return { success: false, error: result.error ?? "Music status failed" };
59683
60109
  return {
59684
60110
  success: true,
59685
- data: { taskId: result.taskId, status: result.status, audioUrl: result.audioUrl, error: result.error },
60111
+ data: {
60112
+ taskId: result.taskId,
60113
+ status: result.status,
60114
+ audioUrl: result.audioUrl,
60115
+ error: result.error
60116
+ },
59686
60117
  humanLines: [`Music task ${result.taskId}: ${result.status}`]
59687
60118
  };
59688
60119
  }
@@ -59694,7 +60125,9 @@ var init_generate2 = __esm({
59694
60125
  description: "Generate an image using AI. Supports Gemini (free), OpenAI GPT Image, or Grok Imagine. Requires GOOGLE_API_KEY (Gemini), OPENAI_API_KEY (OpenAI), or XAI_API_KEY (Grok).",
59695
60126
  schema: external_exports.object({
59696
60127
  prompt: external_exports.string().describe("Image description prompt"),
59697
- provider: external_exports.enum(["gemini", "openai", "grok"]).optional().describe("Image provider (default: gemini)"),
60128
+ provider: external_exports.enum(["gemini", "openai", "grok"]).optional().describe(
60129
+ "Image provider (default: openai when OPENAI_API_KEY is configured, otherwise first configured provider)"
60130
+ ),
59698
60131
  output: external_exports.string().optional().describe("Output file path"),
59699
60132
  size: external_exports.string().optional().describe("Image size for OpenAI (1024x1024, 1536x1024, 1024x1536)"),
59700
60133
  ratio: external_exports.string().optional().describe("Aspect ratio for Gemini (1:1, 16:9, 9:16, 4:3, 3:4, etc.)"),
@@ -59704,10 +60137,16 @@ var init_generate2 = __esm({
59704
60137
  }),
59705
60138
  async execute(args) {
59706
60139
  const result = await executeImageGenerate(args);
59707
- if (!result.success) return { success: false, error: result.error ?? "Image generation failed" };
60140
+ if (!result.success)
60141
+ return { success: false, error: result.error ?? "Image generation failed" };
59708
60142
  return {
59709
60143
  success: true,
59710
- data: { outputPath: result.outputPath, provider: result.provider, model: result.model, imageCount: result.images?.length },
60144
+ data: {
60145
+ outputPath: result.outputPath,
60146
+ provider: result.provider,
60147
+ model: result.model,
60148
+ imageCount: result.images?.length
60149
+ },
59711
60150
  humanLines: [`\u2705 Image (${result.provider}) \u2192 ${result.outputPath}`]
59712
60151
  };
59713
60152
  }
@@ -59729,7 +60168,9 @@ var init_generate2 = __esm({
59729
60168
  return {
59730
60169
  success: true,
59731
60170
  data: { segmentCount: result.segmentCount, outputPath: result.outputPath },
59732
- humanLines: [`\u2705 Storyboard: ${result.segmentCount} segments${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`]
60171
+ humanLines: [
60172
+ `\u2705 Storyboard: ${result.segmentCount} segments${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
60173
+ ]
59733
60174
  };
59734
60175
  }
59735
60176
  });
@@ -59748,7 +60189,11 @@ var init_generate2 = __esm({
59748
60189
  if (!result.success) return { success: false, error: result.error ?? "Background failed" };
59749
60190
  return {
59750
60191
  success: true,
59751
- data: { imageUrl: result.imageUrl, outputPath: result.outputPath, revisedPrompt: result.revisedPrompt },
60192
+ data: {
60193
+ imageUrl: result.imageUrl,
60194
+ outputPath: result.outputPath,
60195
+ revisedPrompt: result.revisedPrompt
60196
+ },
59752
60197
  humanLines: [`\u2705 Background \u2192 ${result.outputPath ?? result.imageUrl}`]
59753
60198
  };
59754
60199
  }
@@ -59806,7 +60251,9 @@ var init_generate2 = __esm({
59806
60251
  outputPath: result.outputPath,
59807
60252
  provider: result.provider
59808
60253
  },
59809
- humanLines: [`\u2705 Video (${result.provider}, ${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`]
60254
+ humanLines: [
60255
+ `\u2705 Video (${result.provider}, ${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
60256
+ ]
59810
60257
  };
59811
60258
  }
59812
60259
  });
@@ -59827,8 +60274,16 @@ var init_generate2 = __esm({
59827
60274
  if (!result.success) return { success: false, error: result.error ?? "Status check failed" };
59828
60275
  return {
59829
60276
  success: true,
59830
- data: { taskId: result.taskId, status: result.status, progress: result.progress, videoUrl: result.videoUrl, outputPath: result.outputPath },
59831
- humanLines: [`Task ${result.taskId}: ${result.status}${result.progress !== void 0 ? ` (${result.progress}%)` : ""}`]
60277
+ data: {
60278
+ taskId: result.taskId,
60279
+ status: result.status,
60280
+ progress: result.progress,
60281
+ videoUrl: result.videoUrl,
60282
+ outputPath: result.outputPath
60283
+ },
60284
+ humanLines: [
60285
+ `Task ${result.taskId}: ${result.status}${result.progress !== void 0 ? ` (${result.progress}%)` : ""}`
60286
+ ]
59832
60287
  };
59833
60288
  }
59834
60289
  });
@@ -59870,8 +60325,16 @@ var init_generate2 = __esm({
59870
60325
  if (!result.success) return { success: false, error: result.error ?? "Extend failed" };
59871
60326
  return {
59872
60327
  success: true,
59873
- data: { taskId: result.taskId, status: result.status, videoUrl: result.videoUrl, duration: result.duration, outputPath: result.outputPath },
59874
- humanLines: [`\u2705 Video extended (${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`]
60328
+ data: {
60329
+ taskId: result.taskId,
60330
+ status: result.status,
60331
+ videoUrl: result.videoUrl,
60332
+ duration: result.duration,
60333
+ outputPath: result.outputPath
60334
+ },
60335
+ humanLines: [
60336
+ `\u2705 Video extended (${result.status})${result.outputPath ? ` \u2192 ${result.outputPath}` : ""}`
60337
+ ]
59875
60338
  };
59876
60339
  }
59877
60340
  });
@@ -61802,7 +62265,7 @@ var init_export2 = __esm({
61802
62265
 
61803
62266
  // src/tools/manifest/agent-only.ts
61804
62267
  import { readFile as readFile41, writeFile as writeFile48, readdir as readdir4, stat as stat6, access as access9, unlink as unlink4 } from "node:fs/promises";
61805
- import { resolve as resolve63, join as join16, basename as basename22, extname as extname13 } from "node:path";
62268
+ import { resolve as resolve63, join as join16, basename as basename22, extname as extname14 } from "node:path";
61806
62269
  function matchPattern(filename, pattern) {
61807
62270
  const regex2 = new RegExp(
61808
62271
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -61821,7 +62284,7 @@ function formatSize(bytes) {
61821
62284
  return `${size.toFixed(1)}${units2[unit]}`;
61822
62285
  }
61823
62286
  function detectMediaType4(filePath) {
61824
- const ext = extname13(filePath).toLowerCase();
62287
+ const ext = extname14(filePath).toLowerCase();
61825
62288
  if ([".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"].includes(ext)) return "video";
61826
62289
  if ([".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"].includes(ext)) return "audio";
61827
62290
  if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"].includes(ext)) return "image";
@@ -61848,7 +62311,7 @@ function isMediaFile2(filePath) {
61848
62311
  ".webp",
61849
62312
  ".bmp"
61850
62313
  ];
61851
- return mediaExts.includes(extname13(filePath).toLowerCase());
62314
+ return mediaExts.includes(extname14(filePath).toLowerCase());
61852
62315
  }
61853
62316
  async function getMediaDuration2(filePath) {
61854
62317
  try {
@@ -61994,7 +62457,7 @@ var init_agent_only = __esm({
61994
62457
  if (entry.isDirectory() && recursive) {
61995
62458
  await scanDir(entryPath);
61996
62459
  } else if (entry.isFile()) {
61997
- const ext = extname13(entry.name).toLowerCase();
62460
+ const ext = extname14(entry.name).toLowerCase();
61998
62461
  const matchesFilter = !filterExts || filterExts.includes(ext);
61999
62462
  if (matchesFilter && isMediaFile2(entryPath)) mediaFiles.push(entryPath);
62000
62463
  }
@@ -62962,7 +63425,7 @@ init_source();
62962
63425
  // package.json
62963
63426
  var package_default = {
62964
63427
  name: "@vibeframe/cli",
62965
- version: "0.91.8",
63428
+ version: "0.92.0",
62966
63429
  description: "VibeFrame CLI - natural language video editing from the terminal",
62967
63430
  private: false,
62968
63431
  license: "MIT",
@@ -64148,8 +64611,8 @@ init_output();
64148
64611
  init_sanitize();
64149
64612
  init_validate();
64150
64613
  init_cost_tier();
64151
- import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
64152
- import { resolve as resolve33 } from "node:path";
64614
+ import { readFile as readFile22, writeFile as writeFile29 } from "node:fs/promises";
64615
+ import { resolve as resolve32 } from "node:path";
64153
64616
  var inspectCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
64154
64617
  "after",
64155
64618
  `
@@ -64346,8 +64809,8 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
64346
64809
  }
64347
64810
  const apiKey = await requireApiKey("GOOGLE_API_KEY", "Google", options.apiKey);
64348
64811
  const spinner2 = ora("Initializing Gemini...").start();
64349
- const filePath = resolve33(process.cwd(), projectPath);
64350
- const content = await readFile23(filePath, "utf-8");
64812
+ const filePath = resolve32(process.cwd(), projectPath);
64813
+ const content = await readFile22(filePath, "utf-8");
64351
64814
  const data = JSON.parse(content);
64352
64815
  const project = Project.fromJSON(data);
64353
64816
  const gemini = new GeminiProvider();
@@ -64384,7 +64847,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
64384
64847
  const sug = suggestions[0];
64385
64848
  const applied = applySuggestion(project, sug);
64386
64849
  if (applied) {
64387
- await writeFile30(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
64850
+ await writeFile29(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
64388
64851
  spinner2.succeed(source_default.green("Suggestion applied"));
64389
64852
  } else {
64390
64853
  spinner2.warn(source_default.yellow("Could not apply suggestion automatically"));
@@ -64409,9 +64872,9 @@ init_cost_tier();
64409
64872
  init_ai_helpers();
64410
64873
  init_output();
64411
64874
  init_validate();
64412
- import { resolve as resolve34, dirname as dirname11, basename as basename8, extname as extname7 } from "node:path";
64413
- import { readFile as readFile24, writeFile as writeFile31 } from "node:fs/promises";
64414
- import { existsSync as existsSync26 } from "node:fs";
64875
+ import { resolve as resolve33, dirname as dirname11, basename as basename8, extname as extname7 } from "node:path";
64876
+ import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
64877
+ import { existsSync as existsSync25 } from "node:fs";
64415
64878
  var audioCommand = new Command("audio").alias("au").description("Audio operations (transcribe, TTS, voice clone, ducking)").addHelpText(
64416
64879
  "after",
64417
64880
  `
@@ -64444,8 +64907,8 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
64444
64907
  const whisper = new WhisperProvider();
64445
64908
  await whisper.initialize({ apiKey });
64446
64909
  spinner2.text = "Reading audio file...";
64447
- const absPath = resolve34(process.cwd(), audioPath);
64448
- const audioBuffer = await readFile24(absPath);
64910
+ const absPath = resolve33(process.cwd(), audioPath);
64911
+ const audioBuffer = await readFile23(absPath);
64449
64912
  const audioBlob = new Blob([audioBuffer]);
64450
64913
  spinner2.text = "Transcribing...";
64451
64914
  const result = await whisper.transcribe(audioBlob, options.language);
@@ -64462,7 +64925,7 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
64462
64925
  fullText: result.fullText,
64463
64926
  segments: result.segments,
64464
64927
  language: result.detectedLanguage,
64465
- outputPath: options.output ? resolve34(process.cwd(), options.output) : void 0
64928
+ outputPath: options.output ? resolve33(process.cwd(), options.output) : void 0
64466
64929
  }
64467
64930
  });
64468
64931
  return;
@@ -64482,10 +64945,10 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
64482
64945
  console.log();
64483
64946
  }
64484
64947
  if (options.output) {
64485
- const outputPath = resolve34(process.cwd(), options.output);
64948
+ const outputPath = resolve33(process.cwd(), options.output);
64486
64949
  const format = detectFormat(options.output, options.format);
64487
64950
  const content = formatTranscript(result, format);
64488
- await writeFile31(outputPath, content, "utf-8");
64951
+ await writeFile30(outputPath, content, "utf-8");
64489
64952
  console.log(source_default.green(`Saved ${format.toUpperCase()} to: ${outputPath}`));
64490
64953
  }
64491
64954
  } catch (error) {
@@ -64543,8 +65006,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
64543
65006
  }
64544
65007
  const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
64545
65008
  const spinner2 = ora("Reading audio file...").start();
64546
- const absPath = resolve34(process.cwd(), audioPath);
64547
- const audioBuffer = await readFile24(absPath);
65009
+ const absPath = resolve33(process.cwd(), audioPath);
65010
+ const audioBuffer = await readFile23(absPath);
64548
65011
  spinner2.text = "Isolating vocals...";
64549
65012
  const elevenlabs = new ElevenLabsProvider();
64550
65013
  await elevenlabs.initialize({ apiKey });
@@ -64553,8 +65016,8 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
64553
65016
  spinner2.fail(result.error || "Audio isolation failed");
64554
65017
  exitWithError(apiError(result.error || "Audio isolation failed", true));
64555
65018
  }
64556
- const outputPath = resolve34(process.cwd(), options.output);
64557
- await writeFile31(outputPath, result.audioBuffer);
65019
+ const outputPath = resolve33(process.cwd(), options.output);
65020
+ await writeFile30(outputPath, result.audioBuffer);
64558
65021
  spinner2.succeed(source_default.green("Vocals isolated"));
64559
65022
  if (isJsonMode()) {
64560
65023
  outputSuccess({
@@ -64614,12 +65077,12 @@ audioCommand.command("clone-voice").description("Clone a voice from audio sample
64614
65077
  const spinner2 = ora("Reading audio samples...").start();
64615
65078
  const audioBuffers = [];
64616
65079
  for (const samplePath of samples) {
64617
- const absPath = resolve34(process.cwd(), samplePath);
64618
- if (!existsSync26(absPath)) {
65080
+ const absPath = resolve33(process.cwd(), samplePath);
65081
+ if (!existsSync25(absPath)) {
64619
65082
  spinner2.fail(`File not found: ${samplePath}`);
64620
65083
  exitWithError(notFoundError(samplePath));
64621
65084
  }
64622
- const buffer = await readFile24(absPath);
65085
+ const buffer = await readFile23(absPath);
64623
65086
  audioBuffers.push(buffer);
64624
65087
  }
64625
65088
  spinner2.text = `Cloning voice from ${audioBuffers.length} sample(s)...`;
@@ -64675,8 +65138,8 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
64675
65138
  if (!options.language) {
64676
65139
  exitWithError(usageError("Target language is required. Use -l or --language"));
64677
65140
  }
64678
- const absPath = resolve34(process.cwd(), mediaPath);
64679
- if (!existsSync26(absPath)) {
65141
+ const absPath = resolve33(process.cwd(), mediaPath);
65142
+ if (!existsSync25(absPath)) {
64680
65143
  exitWithError(notFoundError(mediaPath));
64681
65144
  }
64682
65145
  const openaiKey = await requireApiKey("OPENAI_API_KEY", "OpenAI");
@@ -64687,7 +65150,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
64687
65150
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
64688
65151
  let audioPath = absPath;
64689
65152
  if (isVideo) {
64690
- const tempAudioPath = resolve34(dirname11(absPath), `temp-audio-${Date.now()}.mp3`);
65153
+ const tempAudioPath = resolve33(dirname11(absPath), `temp-audio-${Date.now()}.mp3`);
64691
65154
  try {
64692
65155
  execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
64693
65156
  audioPath = tempAudioPath;
@@ -64699,7 +65162,7 @@ audioCommand.command("dub").description("Dub audio/video to another language (tr
64699
65162
  spinner2.text = "Transcribing audio...";
64700
65163
  const whisper = new WhisperProvider();
64701
65164
  await whisper.initialize({ apiKey: openaiKey });
64702
- const audioBuffer = await readFile24(audioPath);
65165
+ const audioBuffer = await readFile23(audioPath);
64703
65166
  const audioBlob = new Blob([audioBuffer]);
64704
65167
  const transcriptResult = await whisper.transcribe(audioBlob, options.source);
64705
65168
  if (transcriptResult.status === "failed" || !transcriptResult.segments) {
@@ -64780,7 +65243,7 @@ ${segmentTexts}`,
64780
65243
  console.log();
64781
65244
  console.log(source_default.dim("Use without --analyze-only to generate dubbed audio"));
64782
65245
  if (options.output) {
64783
- const timingPath = resolve34(process.cwd(), options.output);
65246
+ const timingPath = resolve33(process.cwd(), options.output);
64784
65247
  const timingData = {
64785
65248
  sourcePath: absPath,
64786
65249
  sourceLanguage: transcriptResult.detectedLanguage || options.source || "auto",
@@ -64793,7 +65256,7 @@ ${segmentTexts}`,
64793
65256
  translated: translatedSegments[i]?.text || s.text
64794
65257
  }))
64795
65258
  };
64796
- await writeFile31(timingPath, JSON.stringify(timingData, null, 2));
65259
+ await writeFile30(timingPath, JSON.stringify(timingData, null, 2));
64797
65260
  console.log(`Timing saved to: ${source_default.bold(timingPath)}`);
64798
65261
  }
64799
65262
  return;
@@ -64818,12 +65281,12 @@ ${segmentTexts}`,
64818
65281
  spinner2.text = "Combining audio...";
64819
65282
  const combinedBuffer = Buffer.concat(dubbedAudioBuffers.map((a) => a.buffer));
64820
65283
  const outputExt = isVideo ? ".mp3" : extname7(absPath);
64821
- const defaultOutputPath2 = resolve34(
65284
+ const defaultOutputPath2 = resolve33(
64822
65285
  dirname11(absPath),
64823
65286
  `${basename8(absPath, extname7(absPath))}-${options.language}${outputExt}`
64824
65287
  );
64825
- const finalOutputPath = resolve34(process.cwd(), options.output || defaultOutputPath2);
64826
- await writeFile31(finalOutputPath, combinedBuffer);
65288
+ const finalOutputPath = resolve33(process.cwd(), options.output || defaultOutputPath2);
65289
+ await writeFile30(finalOutputPath, combinedBuffer);
64827
65290
  spinner2.succeed(source_default.green("Dubbing complete"));
64828
65291
  if (isJsonMode()) {
64829
65292
  outputSuccess({
@@ -64879,9 +65342,9 @@ audioCommand.command("duck").description("Auto-duck background music when voice
64879
65342
  exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
64880
65343
  }
64881
65344
  const spinner2 = ora("Processing audio ducking...").start();
64882
- const absMusic = resolve34(process.cwd(), musicPath);
64883
- const absVoice = resolve34(process.cwd(), options.voice);
64884
- const outputPath = options.output ? resolve34(process.cwd(), options.output) : absMusic.replace(/(\.[^.]+)$/, "-ducked$1");
65345
+ const absMusic = resolve33(process.cwd(), musicPath);
65346
+ const absVoice = resolve33(process.cwd(), options.voice);
65347
+ const outputPath = options.output ? resolve33(process.cwd(), options.output) : absMusic.replace(/(\.[^.]+)$/, "-ducked$1");
64885
65348
  const thresholdDb = parseFloat(options.threshold);
64886
65349
  const thresholdLinear = Math.pow(10, thresholdDb / 20);
64887
65350
  const ratio = parseFloat(options.ratio);
@@ -65489,7 +65952,7 @@ init_esm();
65489
65952
  init_source();
65490
65953
  init_ora();
65491
65954
  import { stat as stat4 } from "node:fs/promises";
65492
- import { resolve as resolve39, extname as extname9, basename as basename12 } from "node:path";
65955
+ import { resolve as resolve39, extname as extname10, basename as basename12 } from "node:path";
65493
65956
 
65494
65957
  // ../../node_modules/.pnpm/music-metadata@11.11.1/node_modules/music-metadata/lib/index.js
65495
65958
  init_lib();
@@ -69366,7 +69829,7 @@ mediaCommand.command("info").description("Get media file information").argument(
69366
69829
  const spinner2 = ora("Analyzing media...").start();
69367
69830
  try {
69368
69831
  const filePath = resolve39(process.cwd(), file);
69369
- const ext = extname9(filePath).toLowerCase();
69832
+ const ext = extname10(filePath).toLowerCase();
69370
69833
  const fileName = basename12(filePath);
69371
69834
  const fileStat = await stat4(filePath);
69372
69835
  const mediaType = detectMediaType2(ext);
@@ -69508,10 +69971,10 @@ init_engine();
69508
69971
  init_output();
69509
69972
  init_cost_tier();
69510
69973
  import { readFile as readFile27, writeFile as writeFile35, readdir as readdir2 } from "node:fs/promises";
69511
- import { resolve as resolve40, basename as basename13, extname as extname10, join as join10 } from "node:path";
69974
+ import { resolve as resolve40, basename as basename13, extname as extname11, join as join10 } from "node:path";
69512
69975
  var batchCommand = new Command("batch").description("Batch operations for processing multiple items");
69513
69976
  function detectMediaType3(filePath) {
69514
- const ext = extname10(filePath).toLowerCase();
69977
+ const ext = extname11(filePath).toLowerCase();
69515
69978
  const videoExts = [".mp4", ".mov", ".avi", ".mkv", ".webm", ".m4v"];
69516
69979
  const audioExts = [".mp3", ".wav", ".aac", ".flac", ".ogg", ".m4a"];
69517
69980
  const imageExts = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"];
@@ -69521,7 +69984,7 @@ function detectMediaType3(filePath) {
69521
69984
  return "video";
69522
69985
  }
69523
69986
  function isMediaFile(filePath) {
69524
- const ext = extname10(filePath).toLowerCase();
69987
+ const ext = extname11(filePath).toLowerCase();
69525
69988
  const mediaExts = [
69526
69989
  ".mp4",
69527
69990
  ".mov",
@@ -69579,7 +70042,7 @@ batchCommand.command("import").description("Import multiple media files from a d
69579
70042
  if (entry.isDirectory() && options.recursive) {
69580
70043
  await scanDir(entryPath);
69581
70044
  } else if (entry.isFile()) {
69582
- const ext = extname10(entry.name).toLowerCase();
70045
+ const ext = extname11(entry.name).toLowerCase();
69583
70046
  const matchesFilter = !filterExts || filterExts.includes(ext);
69584
70047
  if (matchesFilter && isMediaFile(entryPath)) {
69585
70048
  mediaFiles.push(entryPath);
@@ -70047,6 +70510,7 @@ var AI_FEATURES = [
70047
70510
  desc: "generate + edit",
70048
70511
  defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
70049
70512
  alsoAvailable: "Gemini Nano Banana, Grok Imagine",
70513
+ keyHint: "1 key",
70050
70514
  keys: [
70051
70515
  {
70052
70516
  configKey: "openai",
@@ -70101,6 +70565,7 @@ var AI_FEATURES = [
70101
70565
  desc: "generate + extend",
70102
70566
  defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
70103
70567
  alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
70568
+ keyHint: "1-2 keys",
70104
70569
  keys: [
70105
70570
  {
70106
70571
  configKey: "fal",
@@ -70121,6 +70586,15 @@ var AI_FEATURES = [
70121
70586
  url: "https://fal.ai/dashboard/keys",
70122
70587
  what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
70123
70588
  },
70589
+ extraKeys: [
70590
+ {
70591
+ configKey: "imgbb",
70592
+ envVar: "IMGBB_API_KEY",
70593
+ name: "ImgBB",
70594
+ url: "https://api.imgbb.com/",
70595
+ what: "Image hosting for Seedance image-to-video uploads"
70596
+ }
70597
+ ],
70124
70598
  defaultFor: { kind: "video", value: "seedance" }
70125
70599
  },
70126
70600
  {
@@ -70145,6 +70619,15 @@ var AI_FEATURES = [
70145
70619
  url: "https://platform.klingai.com/",
70146
70620
  what: "Kling video generation"
70147
70621
  },
70622
+ extraKeys: [
70623
+ {
70624
+ configKey: "imgbb",
70625
+ envVar: "IMGBB_API_KEY",
70626
+ name: "ImgBB",
70627
+ url: "https://api.imgbb.com/",
70628
+ what: "Image hosting for Kling image-to-video uploads"
70629
+ }
70630
+ ],
70148
70631
  defaultFor: { kind: "video", value: "kling" }
70149
70632
  },
70150
70633
  {
@@ -70179,6 +70662,7 @@ var AI_FEATURES = [
70179
70662
  desc: "TTS, SFX, music, voice clone",
70180
70663
  defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
70181
70664
  alsoAvailable: "Replicate MusicGen (music only)",
70665
+ keyHint: "0-1 key",
70182
70666
  keys: [
70183
70667
  {
70184
70668
  configKey: "elevenlabs",
@@ -70224,6 +70708,7 @@ var AI_FEATURES = [
70224
70708
  desc: "captions, grade, reframe, motion graphics",
70225
70709
  defaultProvider: "Whisper (transcription) + Claude (reasoning)",
70226
70710
  alsoAvailable: "",
70711
+ keyHint: "2 keys",
70227
70712
  keys: [
70228
70713
  {
70229
70714
  configKey: "openai",
@@ -70257,8 +70742,11 @@ function applyProviderDefault(config2, choice) {
70257
70742
  }
70258
70743
  }
70259
70744
  function isProviderChoiceConfigured(config2, choice) {
70260
- if (choice.key) {
70261
- const value = config2.providers[choice.key.configKey];
70745
+ const keys = [choice.key, ...getChoiceExtraKeys(config2, choice)].filter(
70746
+ (keyDef) => Boolean(keyDef)
70747
+ );
70748
+ for (const keyDef of keys) {
70749
+ const value = config2.providers[keyDef.configKey];
70262
70750
  if (value) return true;
70263
70751
  }
70264
70752
  if (choice.defaultFor?.kind === "image") {
@@ -70269,6 +70757,96 @@ function isProviderChoiceConfigured(config2, choice) {
70269
70757
  }
70270
70758
  return false;
70271
70759
  }
70760
+ function getConfiguredUploadProvider(config2) {
70761
+ return process.env.VIBE_UPLOAD_PROVIDER?.toLowerCase() === "s3" || config2.upload.provider === "s3" ? "s3" : "imgbb";
70762
+ }
70763
+ function hasVideoFeature(features) {
70764
+ return features.some((feature) => feature.label === "Videos");
70765
+ }
70766
+ async function promptOptionalValue(label, currentValue) {
70767
+ const suffix = currentValue ? source_default.dim(` [${currentValue}]`) : "";
70768
+ const value = await prompt2(source_default.cyan(` ${label}${suffix}: `));
70769
+ const trimmed = value.trim();
70770
+ return trimmed || currentValue;
70771
+ }
70772
+ async function configureUploadHost(config2) {
70773
+ console.log(source_default.dim("2b. Upload host"));
70774
+ console.log(source_default.bold("Where should image-to-video upload reference images?"));
70775
+ console.log(
70776
+ source_default.dim(
70777
+ " Seedance and Kling need a temporary HTTPS image URL when you start from a local image."
70778
+ )
70779
+ );
70780
+ console.log();
70781
+ const currentProvider = getConfiguredUploadProvider(config2);
70782
+ const selected = await promptSelect(
70783
+ source_default.cyan(" Upload host [1-3]: "),
70784
+ [
70785
+ `ImgBB ${source_default.dim("- easiest setup, third-party image hosting (default)")}`,
70786
+ `S3 ${source_default.dim("- your own bucket, better control/privacy")}`,
70787
+ `Back ${source_default.dim("(choose different features)")}`
70788
+ ],
70789
+ currentProvider === "s3" ? 1 : 0,
70790
+ { backIndex: 2 }
70791
+ );
70792
+ console.log();
70793
+ if (selected === 2) {
70794
+ return "back";
70795
+ }
70796
+ if (selected === 0) {
70797
+ config2.upload.provider = "imgbb";
70798
+ process.env.VIBE_UPLOAD_PROVIDER = "imgbb";
70799
+ console.log(source_default.dim(" ImgBB selected. Seedance/Kling setup will ask for IMGBB_API_KEY."));
70800
+ console.log();
70801
+ return "continue";
70802
+ }
70803
+ config2.upload.provider = "s3";
70804
+ process.env.VIBE_UPLOAD_PROVIDER = "s3";
70805
+ config2.upload.s3 = config2.upload.s3 ?? {};
70806
+ const envBucket = process.env.VIBE_UPLOAD_S3_BUCKET;
70807
+ const envRegion = process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION;
70808
+ const envPrefix = process.env.VIBE_UPLOAD_S3_PREFIX;
70809
+ const envTtl = process.env.VIBE_UPLOAD_TTL_SECONDS;
70810
+ console.log(source_default.dim(" S3 selected. AWS credentials stay in your shell/.env/AWS profile."));
70811
+ console.log(source_default.dim(" Required at runtime: AWS credentials, region, and bucket."));
70812
+ console.log();
70813
+ const bucket = await promptOptionalValue("S3 bucket", envBucket ?? config2.upload.s3.bucket);
70814
+ const region = await promptOptionalValue("AWS region", envRegion ?? config2.upload.s3.region);
70815
+ const prefix = await promptOptionalValue(
70816
+ "S3 prefix",
70817
+ envPrefix ?? config2.upload.s3.prefix ?? "vibeframe/tmp"
70818
+ );
70819
+ const ttlValue = await promptOptionalValue(
70820
+ "Upload URL TTL seconds",
70821
+ envTtl ?? String(config2.upload.ttlSeconds ?? 3600)
70822
+ );
70823
+ if (bucket) config2.upload.s3.bucket = bucket;
70824
+ if (region) config2.upload.s3.region = region;
70825
+ if (prefix) config2.upload.s3.prefix = prefix;
70826
+ const ttlSeconds = ttlValue ? Number.parseInt(ttlValue, 10) : NaN;
70827
+ if (Number.isFinite(ttlSeconds) && ttlSeconds > 0) {
70828
+ config2.upload.ttlSeconds = ttlSeconds;
70829
+ }
70830
+ if (!config2.upload.s3.bucket || !config2.upload.s3.region) {
70831
+ console.log(
70832
+ source_default.yellow(
70833
+ " \u26A0 S3 selected, but bucket/region is incomplete. Set VIBE_UPLOAD_S3_BUCKET and AWS_REGION before image-to-video."
70834
+ )
70835
+ );
70836
+ } else {
70837
+ console.log(
70838
+ ` ${source_default.green("\u2713")} S3 upload host configured ${source_default.dim(`(${config2.upload.s3.bucket}/${config2.upload.s3.prefix ?? "vibeframe/tmp"})`)}`
70839
+ );
70840
+ }
70841
+ console.log();
70842
+ return "continue";
70843
+ }
70844
+ function getChoiceExtraKeys(config2, choice) {
70845
+ if (config2.upload.provider === "s3") {
70846
+ return [];
70847
+ }
70848
+ return choice.extraKeys ?? [];
70849
+ }
70272
70850
  function isFeatureConfigured(config2, feature) {
70273
70851
  if (feature.providerChoices?.some((choice) => isProviderChoiceConfigured(config2, choice))) {
70274
70852
  return true;
@@ -70378,6 +70956,7 @@ async function runNonInteractiveSetup(opts) {
70378
70956
  }
70379
70957
  async function runSetupWizard(fullSetup = false, scope = "user") {
70380
70958
  console.log();
70959
+ loadEnv();
70381
70960
  const cfgPath = getConfigPath(scope).replace(homedir5(), "~");
70382
70961
  const scopeNote = scope === "project" ? "applies to this project only \xB7 add .vibeframe/ to .gitignore" : "applies to every project for this user";
70383
70962
  console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.bold(` \u2014 ${scope} scope`));
@@ -70520,13 +71099,16 @@ async function runAIFeaturesSetup(config2, _scope) {
70520
71099
  )
70521
71100
  );
70522
71101
  console.log(source_default.dim(" Shortcut: enter alone picks only the highlighted feature."));
70523
- console.log(source_default.dim(` Select ${source_default.bold("Back")} to return to the setup checkpoint.`));
71102
+ console.log(
71103
+ source_default.dim(
71104
+ ` Select ${source_default.bold("Back")} or press ${source_default.bold("Esc/\u2190")} to return to the setup checkpoint.`
71105
+ )
71106
+ );
70524
71107
  console.log();
70525
71108
  const backIndex = AI_FEATURES.length;
70526
71109
  const featureLabels = [
70527
71110
  ...AI_FEATURES.map((f) => {
70528
- const keyCount = f.keys.length;
70529
- const tag = source_default.dim(`${keyCount} key${keyCount > 1 ? "s" : ""}`);
71111
+ const tag = source_default.dim(f.keyHint ?? `${f.keys.length} key${f.keys.length > 1 ? "s" : ""}`);
70530
71112
  return `${f.label} ${source_default.dim(`(${f.desc})`)} ${tag}`;
70531
71113
  }),
70532
71114
  `Back ${source_default.dim("(return to setup checkpoint)")}`
@@ -70536,7 +71118,7 @@ async function runAIFeaturesSetup(config2, _scope) {
70536
71118
  source_default.cyan(" Pick (e.g. 1,3 or 'all'): "),
70537
71119
  featureLabels,
70538
71120
  [...configuredFeatures, false],
70539
- { pickFocusedOnEnter: true, preserveDefaultSelectionOnEnter: true }
71121
+ { pickFocusedOnEnter: true, backIndex }
70540
71122
  );
70541
71123
  if (picked.includes(backIndex)) {
70542
71124
  console.log();
@@ -70549,12 +71131,19 @@ async function runAIFeaturesSetup(config2, _scope) {
70549
71131
  console.log();
70550
71132
  return { tryCommand: "vibe --help", selectedFeatures: [] };
70551
71133
  }
71134
+ if (hasVideoFeature(selectedFeatures)) {
71135
+ const uploadResult = await configureUploadHost(config2);
71136
+ if (uploadResult === "back") {
71137
+ console.log();
71138
+ continue featureSelection;
71139
+ }
71140
+ }
70552
71141
  const plannedKeys = /* @__PURE__ */ new Map();
70553
71142
  console.log(source_default.dim("3. Providers"));
70554
71143
  console.log(source_default.bold("Choose providers for each selected feature"));
70555
71144
  console.log(
70556
71145
  source_default.dim(
70557
- ` Recommended default is highlighted. Press ${source_default.bold("enter")} to pick the highlighted provider, or ${source_default.bold("space")} to choose multiple.`
71146
+ ` Recommended default is highlighted. Press ${source_default.bold("enter")} to pick the highlighted provider, ${source_default.bold("space")} to choose multiple, or ${source_default.bold("Esc/\u2190")} to go back.`
70558
71147
  )
70559
71148
  );
70560
71149
  console.log();
@@ -70571,7 +71160,12 @@ async function runAIFeaturesSetup(config2, _scope) {
70571
71160
  (choice) => isProviderChoiceConfigured(config2, choice)
70572
71161
  );
70573
71162
  const providerLabels = feature.providerChoices.map((choice) => {
70574
- const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(choice.key.envVar) : source_default.dim("no key");
71163
+ const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(
71164
+ [
71165
+ choice.key.envVar,
71166
+ ...getChoiceExtraKeys(config2, choice).map((keyDef) => keyDef.envVar)
71167
+ ].filter(Boolean).join(" + ")
71168
+ ) : source_default.dim("no key");
70575
71169
  const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
70576
71170
  return [choice.label, defaultHint, source_default.dim(`- ${choice.desc}`), keyHint].filter(Boolean).join(" ");
70577
71171
  });
@@ -70580,7 +71174,11 @@ async function runAIFeaturesSetup(config2, _scope) {
70580
71174
  source_default.cyan(" Providers (arrow + enter picks one, space adds more): "),
70581
71175
  [...providerLabels, `Back ${source_default.dim("(choose different features)")}`],
70582
71176
  [...defaultSelected, false],
70583
- { pickFocusedOnEnter: true, preserveDefaultSelectionOnEnter: true }
71177
+ {
71178
+ pickFocusedOnEnter: true,
71179
+ preserveDefaultSelectionOnEnter: true,
71180
+ backIndex: backProviderIndex
71181
+ }
70584
71182
  );
70585
71183
  if (pickedProviders.includes(backProviderIndex)) {
70586
71184
  console.log();
@@ -70593,6 +71191,9 @@ async function runAIFeaturesSetup(config2, _scope) {
70593
71191
  }
70594
71192
  for (const choice of selectedChoices) {
70595
71193
  if (choice.key) addUniqueKey(keysForFeature, choice.key);
71194
+ for (const keyDef of getChoiceExtraKeys(config2, choice)) {
71195
+ addUniqueKey(keysForFeature, keyDef);
71196
+ }
70596
71197
  }
70597
71198
  if (selectedChoices.some((choice) => choice.keyless)) {
70598
71199
  console.log(source_default.dim(" No API key needed for selected local provider(s)."));
@@ -71247,10 +71848,21 @@ OPENROUTER_API_KEY= # multiplexes any provider above
71247
71848
  # \u2500\u2500 Media providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
71248
71849
  ELEVENLABS_API_KEY= # paid TTS / SFX / music (Kokoro is the free fallback)
71249
71850
  FAL_KEY= # Seedance 2.0 \u2014 default video provider since v0.57
71851
+ IMGBB_API_KEY= # image hosting for Seedance/Kling image-to-video
71250
71852
  RUNWAY_API_SECRET= # Runway Gen-4.5 video
71251
71853
  KLING_API_KEY= # Kling video
71252
71854
  REPLICATE_API_TOKEN= # MusicGen, real-esrgan, etc.
71253
- IMGBB_API_KEY= # public image hosting (rare \u2014 some Replicate flows need it)
71855
+
71856
+ # \u2500\u2500 Optional private upload host for image-to-video \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
71857
+ # Default is ImgBB. Set these only if you want temporary URLs from your own S3 bucket.
71858
+ # VIBE_UPLOAD_PROVIDER=s3 # imgbb | s3
71859
+ # VIBE_UPLOAD_TTL_SECONDS=3600
71860
+ # VIBE_UPLOAD_S3_BUCKET=
71861
+ # VIBE_UPLOAD_S3_PREFIX=vibeframe/tmp
71862
+ # AWS_REGION=
71863
+ # AWS_ACCESS_KEY_ID=
71864
+ # AWS_SECRET_ACCESS_KEY=
71865
+ # AWS_SESSION_TOKEN=
71254
71866
  `;
71255
71867
  }
71256
71868
  var GITIGNORE_ADDITIONS = `