@vibeframe/cli 0.112.0 → 0.113.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.
@@ -191,8 +191,8 @@ var init_api_keys = __esm({
191
191
  envVar: "OPENAI_API_KEY",
192
192
  label: "OpenAI",
193
193
  showInSetup: true,
194
- setupDescription: "gpt-image-2 image gen ($, default since v0.56), Whisper transcribe, Agent",
195
- envExampleComment: "OpenAI API Key (Whisper transcription, gpt-image-2 \u2014 default text-to-image since v0.56)",
194
+ setupDescription: "gpt-image-2 image gen ($, default since v0.56), gpt-4o-mini-tts narration, Whisper transcribe, Agent",
195
+ envExampleComment: "OpenAI API Key (Whisper transcription, gpt-4o-mini-tts narration, gpt-image-2 \u2014 default text-to-image since v0.56)",
196
196
  envExampleUrl: "https://platform.openai.com/api-keys",
197
197
  keyFormat: { prefix: /^sk-/, example: "sk-..." }
198
198
  });
@@ -2299,8 +2299,8 @@ var init_openai = __esm({
2299
2299
  id: "openai",
2300
2300
  label: "OpenAI",
2301
2301
  apiKey: "openai",
2302
- kinds: ["llm", "image", "transcription"],
2303
- resolverPriority: { image: 1 },
2302
+ kinds: ["llm", "image", "transcription", "speech"],
2303
+ resolverPriority: { image: 1, speech: 2 },
2304
2304
  commandsUnlocked: [
2305
2305
  "agent -p openai",
2306
2306
  "generate image -p openai",
@@ -4722,16 +4722,19 @@ var init_elevenlabs = __esm({
4722
4722
  });
4723
4723
 
4724
4724
  // ../ai-providers/dist/kokoro/KokoroProvider.js
4725
- async function loadKokoroFactory() {
4725
+ async function loadKokoroRuntime() {
4726
4726
  if (factoryOverride)
4727
- return factoryOverride;
4727
+ return { factory: factoryOverride, device: "cpu" };
4728
4728
  try {
4729
4729
  const mod = await import("kokoro-js");
4730
- return mod.KokoroTTS;
4730
+ return { factory: mod.KokoroTTS, device: "cpu" };
4731
4731
  } catch (err) {
4732
- const fallback2 = await loadKokoroFromWorkspace();
4733
- if (fallback2)
4734
- return fallback2;
4732
+ const workspace = await loadKokoroFromWorkspace();
4733
+ if (workspace)
4734
+ return { factory: workspace, device: "cpu" };
4735
+ const bundled = await loadBundledKokoroRuntime();
4736
+ if (bundled)
4737
+ return bundled;
4735
4738
  throw mapKokoroImportError(err);
4736
4739
  }
4737
4740
  }
@@ -4751,10 +4754,51 @@ async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
4751
4754
  return null;
4752
4755
  }
4753
4756
  }
4757
+ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUNTIME) {
4758
+ const base = runtimeDir?.trim();
4759
+ if (!base)
4760
+ return null;
4761
+ try {
4762
+ const { readFile: readFile45, writeFile: writeFile51, mkdir: mkdir26 } = await import("node:fs/promises");
4763
+ const { join: join27, resolve: resolve66 } = await import("node:path");
4764
+ const { pathToFileURL } = await import("node:url");
4765
+ const { homedir: homedir6 } = await import("node:os");
4766
+ const transformersDir = resolve66(base, "node_modules", "@huggingface", "transformers");
4767
+ const ortDistDir = resolve66(base, "node_modules", "onnxruntime-web", "dist");
4768
+ const transformers = await import(pathToFileURL(join27(transformersDir, "dist", "transformers.web.js")).href);
4769
+ const cacheRoot = join27(homedir6(), ".cache", "vibeframe", "models");
4770
+ transformers.env.allowLocalModels = false;
4771
+ transformers.env.useCustomCache = true;
4772
+ transformers.env.customCache = {
4773
+ async match(key) {
4774
+ try {
4775
+ return new Response(await readFile45(join27(cacheRoot, encodeURIComponent(String(key)))));
4776
+ } catch {
4777
+ return void 0;
4778
+ }
4779
+ },
4780
+ async put(key, response) {
4781
+ try {
4782
+ const buf = Buffer.from(await response.arrayBuffer());
4783
+ await mkdir26(cacheRoot, { recursive: true });
4784
+ await writeFile51(join27(cacheRoot, encodeURIComponent(String(key))), buf);
4785
+ } catch {
4786
+ }
4787
+ }
4788
+ };
4789
+ transformers.env.backends.onnx.wasm.wasmPaths = pathToFileURL(ortDistDir + "/").href;
4790
+ const factory = await loadKokoroFromWorkspace(base);
4791
+ if (!factory)
4792
+ return null;
4793
+ return { factory, device: "cpu" };
4794
+ } catch {
4795
+ return null;
4796
+ }
4797
+ }
4754
4798
  function mapKokoroImportError(err) {
4755
4799
  const code = err instanceof Error && "code" in err ? err.code : void 0;
4756
4800
  if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
4757
- return new Error("Local Kokoro TTS is unavailable: optional dependency kokoro-js is not installed (its native dependencies may have failed to build on this platform). Install it with `npm i kokoro-js` \u2014 run it inside your workspace folder when using the Claude Desktop extension \u2014 or set ELEVENLABS_API_KEY to use the elevenlabs TTS provider instead.");
4801
+ return new Error("Local Kokoro TTS is unavailable: kokoro-js could not be loaded from the npm install, the workspace node_modules, or the bundled extension runtime. Set OPENAI_API_KEY or ELEVENLABS_API_KEY to use a cloud voice instead (ttsProvider: openai / elevenlabs), or install the engine locally with `npm i kokoro-js` in your workspace folder.");
4758
4802
  }
4759
4803
  return err instanceof Error ? err : new Error(String(err));
4760
4804
  }
@@ -4762,10 +4806,12 @@ function loadModel(progress) {
4762
4806
  if (modelPromise)
4763
4807
  return modelPromise;
4764
4808
  modelPromise = (async () => {
4765
- const factory = await loadKokoroFactory();
4766
- return factory.from_pretrained(KOKORO_MODEL_ID, {
4809
+ const runtime = await loadKokoroRuntime();
4810
+ const override = process.env.VIBE_ONNX_DEVICE;
4811
+ const device = override === "cpu" || override === "wasm" ? override : runtime.device;
4812
+ return runtime.factory.from_pretrained(KOKORO_MODEL_ID, {
4767
4813
  dtype: "q8",
4768
- device: "cpu",
4814
+ device,
4769
4815
  progress_callback: progress ? (raw) => progress(normaliseEvent(raw)) : void 0
4770
4816
  });
4771
4817
  })().catch((err) => {
@@ -4849,7 +4895,7 @@ var init_kokoro = __esm({
4849
4895
  label: "Kokoro (local)",
4850
4896
  apiKey: null,
4851
4897
  kinds: ["speech"],
4852
- resolverPriority: { speech: 2 }
4898
+ resolverPriority: { speech: 3 }
4853
4899
  });
4854
4900
  }
4855
4901
  });
@@ -5121,12 +5167,119 @@ var init_openai_image = __esm({
5121
5167
  }
5122
5168
  });
5123
5169
 
5170
+ // ../ai-providers/dist/openai-tts/OpenAiTtsProvider.js
5171
+ var OPENAI_TTS_VOICES, DEFAULT_VOICE, DEFAULT_MODEL2, TTS_429_MAX_RETRIES2, TTS_429_RETRY_DELAY_MS2, OpenAiTtsProvider, openaiTtsProvider;
5172
+ var init_OpenAiTtsProvider = __esm({
5173
+ "../ai-providers/dist/openai-tts/OpenAiTtsProvider.js"() {
5174
+ "use strict";
5175
+ OPENAI_TTS_VOICES = [
5176
+ "alloy",
5177
+ "ash",
5178
+ "ballad",
5179
+ "cedar",
5180
+ "coral",
5181
+ "echo",
5182
+ "fable",
5183
+ "marin",
5184
+ "nova",
5185
+ "onyx",
5186
+ "sage",
5187
+ "shimmer",
5188
+ "verse"
5189
+ ];
5190
+ DEFAULT_VOICE = "marin";
5191
+ DEFAULT_MODEL2 = "gpt-4o-mini-tts";
5192
+ TTS_429_MAX_RETRIES2 = 1;
5193
+ TTS_429_RETRY_DELAY_MS2 = 2e3;
5194
+ OpenAiTtsProvider = class {
5195
+ constructor() {
5196
+ this.id = "openai";
5197
+ this.name = "OpenAI TTS";
5198
+ this.description = "OpenAI cloud text-to-speech (gpt-4o-mini-tts)";
5199
+ this.capabilities = ["text-to-speech"];
5200
+ this.isAvailable = true;
5201
+ this.baseUrl = "https://api.openai.com/v1";
5202
+ }
5203
+ async initialize(config2) {
5204
+ this.apiKey = config2.apiKey;
5205
+ if (config2.baseUrl) {
5206
+ this.baseUrl = config2.baseUrl;
5207
+ }
5208
+ }
5209
+ isConfigured() {
5210
+ return !!this.apiKey;
5211
+ }
5212
+ async textToSpeech(text, options = {}) {
5213
+ if (!this.apiKey) {
5214
+ return { success: false, error: "OpenAI API key not configured" };
5215
+ }
5216
+ const voice = (options.voice ?? DEFAULT_VOICE).toLowerCase();
5217
+ if (!OPENAI_TTS_VOICES.includes(voice)) {
5218
+ return {
5219
+ success: false,
5220
+ error: `Unknown OpenAI voice "${options.voice}". Available voices: ${OPENAI_TTS_VOICES.join(", ")}.`
5221
+ };
5222
+ }
5223
+ try {
5224
+ let response;
5225
+ for (let attempt = 0; ; attempt++) {
5226
+ response = await fetch(`${this.baseUrl}/audio/speech`, {
5227
+ method: "POST",
5228
+ headers: {
5229
+ Authorization: `Bearer ${this.apiKey}`,
5230
+ "Content-Type": "application/json"
5231
+ },
5232
+ body: JSON.stringify({
5233
+ model: options.model ?? DEFAULT_MODEL2,
5234
+ input: text,
5235
+ voice,
5236
+ response_format: "mp3",
5237
+ ...options.speed !== void 0 && { speed: options.speed },
5238
+ ...options.instructions !== void 0 && { instructions: options.instructions }
5239
+ })
5240
+ });
5241
+ if (response.status === 429 && attempt < TTS_429_MAX_RETRIES2) {
5242
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, TTS_429_RETRY_DELAY_MS2 * (attempt + 1)));
5243
+ continue;
5244
+ }
5245
+ break;
5246
+ }
5247
+ if (!response.ok) {
5248
+ const error = await response.text();
5249
+ return { success: false, error: `OpenAI TTS failed: ${error}` };
5250
+ }
5251
+ const arrayBuffer = await response.arrayBuffer();
5252
+ return {
5253
+ success: true,
5254
+ audioBuffer: Buffer.from(arrayBuffer),
5255
+ characterCount: text.length
5256
+ };
5257
+ } catch (error) {
5258
+ return {
5259
+ success: false,
5260
+ error: error instanceof Error ? error.message : "Unknown error"
5261
+ };
5262
+ }
5263
+ }
5264
+ };
5265
+ openaiTtsProvider = new OpenAiTtsProvider();
5266
+ }
5267
+ });
5268
+
5269
+ // ../ai-providers/dist/openai-tts/index.js
5270
+ var init_openai_tts = __esm({
5271
+ "../ai-providers/dist/openai-tts/index.js"() {
5272
+ "use strict";
5273
+ init_OpenAiTtsProvider();
5274
+ }
5275
+ });
5276
+
5124
5277
  // ../ai-providers/dist/runway/RunwayProvider.js
5125
- var DEFAULT_MODEL2, RunwayProvider, runwayProvider;
5278
+ var DEFAULT_MODEL3, RunwayProvider, runwayProvider;
5126
5279
  var init_RunwayProvider = __esm({
5127
5280
  "../ai-providers/dist/runway/RunwayProvider.js"() {
5128
5281
  "use strict";
5129
- DEFAULT_MODEL2 = "gen4.5";
5282
+ DEFAULT_MODEL3 = "gen4.5";
5130
5283
  RunwayProvider = class _RunwayProvider {
5131
5284
  constructor() {
5132
5285
  this.id = "runway";
@@ -5169,7 +5322,7 @@ var init_RunwayProvider = __esm({
5169
5322
  "1:1": "960:960"
5170
5323
  };
5171
5324
  const apiRatio = ratioMap[options?.aspectRatio || "16:9"] || "1280:720";
5172
- const model = options?.model || DEFAULT_MODEL2;
5325
+ const model = options?.model || DEFAULT_MODEL3;
5173
5326
  if (!options?.referenceImage && model !== "gen4.5") {
5174
5327
  return {
5175
5328
  id: "",
@@ -5431,11 +5584,11 @@ var init_runway = __esm({
5431
5584
 
5432
5585
  // ../ai-providers/dist/kling/KlingProvider.js
5433
5586
  import { createHmac } from "node:crypto";
5434
- var DEFAULT_MODEL3, STD_MODE_MODELS, KlingProvider, klingProvider;
5587
+ var DEFAULT_MODEL4, STD_MODE_MODELS, KlingProvider, klingProvider;
5435
5588
  var init_KlingProvider = __esm({
5436
5589
  "../ai-providers/dist/kling/KlingProvider.js"() {
5437
5590
  "use strict";
5438
- DEFAULT_MODEL3 = "kling-v2-5-turbo";
5591
+ DEFAULT_MODEL4 = "kling-v2-5-turbo";
5439
5592
  STD_MODE_MODELS = ["kling-v2-5-turbo", "kling-v2-6", "kling-v3", "kling-v3-omni"];
5440
5593
  KlingProvider = class {
5441
5594
  constructor() {
@@ -5498,7 +5651,7 @@ var init_KlingProvider = __esm({
5498
5651
  }
5499
5652
  try {
5500
5653
  const token = this.generateToken();
5501
- const model = options?.model || DEFAULT_MODEL3;
5654
+ const model = options?.model || DEFAULT_MODEL4;
5502
5655
  let mode = options?.mode || "std";
5503
5656
  if (!STD_MODE_MODELS.includes(model) && mode === "std") {
5504
5657
  mode = "pro";
@@ -5869,11 +6022,11 @@ var init_kling = __esm({
5869
6022
  });
5870
6023
 
5871
6024
  // ../ai-providers/dist/grok/GrokProvider.js
5872
- var DEFAULT_MODEL4, GrokProvider, grokProvider;
6025
+ var DEFAULT_MODEL5, GrokProvider, grokProvider;
5873
6026
  var init_GrokProvider = __esm({
5874
6027
  "../ai-providers/dist/grok/GrokProvider.js"() {
5875
6028
  "use strict";
5876
- DEFAULT_MODEL4 = "grok-imagine-video";
6029
+ DEFAULT_MODEL5 = "grok-imagine-video";
5877
6030
  GrokProvider = class {
5878
6031
  constructor() {
5879
6032
  this.id = "grok";
@@ -6040,7 +6193,7 @@ var init_GrokProvider = __esm({
6040
6193
  try {
6041
6194
  const duration = Math.round(Math.min(15, Math.max(1, options?.duration || 5)));
6042
6195
  const body = {
6043
- model: DEFAULT_MODEL4,
6196
+ model: DEFAULT_MODEL5,
6044
6197
  prompt: prompt3,
6045
6198
  duration,
6046
6199
  aspect_ratio: options?.aspectRatio || "16:9"
@@ -11247,9 +11400,11 @@ __export(dist_exports2, {
11247
11400
  KOKORO_MODEL_ID: () => KOKORO_MODEL_ID,
11248
11401
  KlingProvider: () => KlingProvider,
11249
11402
  KokoroProvider: () => KokoroProvider,
11403
+ OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
11250
11404
  OllamaProvider: () => OllamaProvider,
11251
11405
  OpenAIImageProvider: () => OpenAIImageProvider,
11252
11406
  OpenAIProvider: () => OpenAIProvider,
11407
+ OpenAiTtsProvider: () => OpenAiTtsProvider,
11253
11408
  ReplicateProvider: () => ReplicateProvider,
11254
11409
  RunwayProvider: () => RunwayProvider,
11255
11410
  WhisperProvider: () => WhisperProvider,
@@ -11273,9 +11428,12 @@ __export(dist_exports2, {
11273
11428
  isGeminiTextModelAlias: () => isGeminiTextModelAlias,
11274
11429
  klingProvider: () => klingProvider,
11275
11430
  kokoroProvider: () => kokoroProvider,
11431
+ loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
11432
+ loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
11276
11433
  ollamaProvider: () => ollamaProvider,
11277
11434
  openaiImageProvider: () => openaiImageProvider,
11278
11435
  openaiProvider: () => openaiProvider,
11436
+ openaiTtsProvider: () => openaiTtsProvider,
11279
11437
  providerRegistry: () => providerRegistry,
11280
11438
  replicateProvider: () => replicateProvider,
11281
11439
  resolveGeminiTextModel: () => resolveGeminiTextModel,
@@ -11298,6 +11456,7 @@ var init_dist2 = __esm({
11298
11456
  init_elevenlabs();
11299
11457
  init_kokoro();
11300
11458
  init_openai_image();
11459
+ init_openai_tts();
11301
11460
  init_runway();
11302
11461
  init_kling();
11303
11462
  init_grok();
@@ -28713,10 +28872,22 @@ var init_esm = __esm({
28713
28872
 
28714
28873
  // src/commands/_shared/tts-resolve.ts
28715
28874
  async function resolveTtsProvider(preferred = "auto") {
28716
- const choice = preferred === "auto" ? await getConfiguredApiKey("ELEVENLABS_API_KEY") ? "elevenlabs" : "kokoro" : preferred;
28875
+ let choice = preferred;
28876
+ if (choice === "auto") {
28877
+ if (await getConfiguredApiKey("ELEVENLABS_API_KEY")) {
28878
+ choice = "elevenlabs";
28879
+ } else if (await getConfiguredApiKey("OPENAI_API_KEY")) {
28880
+ choice = "openai";
28881
+ } else {
28882
+ choice = "kokoro";
28883
+ }
28884
+ }
28717
28885
  if (choice === "elevenlabs") {
28718
28886
  return buildElevenLabs();
28719
28887
  }
28888
+ if (choice === "openai") {
28889
+ return buildOpenAi();
28890
+ }
28720
28891
  return buildKokoro();
28721
28892
  }
28722
28893
  async function buildElevenLabs() {
@@ -28732,6 +28903,19 @@ async function buildElevenLabs() {
28732
28903
  });
28733
28904
  return { provider: "elevenlabs", audioExtension: "mp3", call };
28734
28905
  }
28906
+ async function buildOpenAi() {
28907
+ const key = await getApiKey("OPENAI_API_KEY", "OpenAI");
28908
+ if (!key) {
28909
+ throw new TtsKeyMissingError("openai");
28910
+ }
28911
+ const provider = new OpenAiTtsProvider();
28912
+ await provider.initialize({ apiKey: key });
28913
+ const call = async (text, opts) => provider.textToSpeech(text, {
28914
+ voice: opts?.voice,
28915
+ speed: opts?.speed
28916
+ });
28917
+ return { provider: "openai", audioExtension: "mp3", call };
28918
+ }
28735
28919
  async function buildKokoro() {
28736
28920
  const provider = new KokoroProvider();
28737
28921
  await provider.initialize({});
@@ -28744,11 +28928,11 @@ async function buildKokoro() {
28744
28928
  }
28745
28929
  function parseTtsProviderName(value) {
28746
28930
  if (!value) return "auto";
28747
- if (value === "auto" || value === "elevenlabs" || value === "kokoro") {
28931
+ if (value === "auto" || value === "elevenlabs" || value === "openai" || value === "kokoro") {
28748
28932
  return value;
28749
28933
  }
28750
28934
  throw new Error(
28751
- `Invalid --tts: ${value}. Valid: auto, elevenlabs, kokoro.`
28935
+ `Invalid --tts: ${value}. Valid: auto, elevenlabs, openai, kokoro.`
28752
28936
  );
28753
28937
  }
28754
28938
  var TtsKeyMissingError;
@@ -28760,7 +28944,7 @@ var init_tts_resolve = __esm({
28760
28944
  TtsKeyMissingError = class extends Error {
28761
28945
  constructor(provider) {
28762
28946
  super(
28763
- provider === "elevenlabs" ? "ElevenLabs API key required (ELEVENLABS_API_KEY). Run 'vibe setup', set ELEVENLABS_API_KEY in .env, or pass --tts kokoro for local synthesis." : `Provider ${provider} is unavailable.`
28947
+ provider === "elevenlabs" ? "ElevenLabs API key required (ELEVENLABS_API_KEY). Run 'vibe setup', set ELEVENLABS_API_KEY in .env, or pass --tts kokoro for local synthesis." : provider === "openai" ? "OpenAI API key required (OPENAI_API_KEY). Run 'vibe setup', set OPENAI_API_KEY in .env, or pass --tts kokoro for local synthesis." : `Provider ${provider} is unavailable.`
28764
28948
  );
28765
28949
  this.provider = provider;
28766
28950
  this.name = "TtsKeyMissingError";
@@ -32697,6 +32881,9 @@ var init_storyboard_edit = __esm({
32697
32881
  import { existsSync as existsSync17 } from "node:fs";
32698
32882
  import { readFile as readFile17 } from "node:fs/promises";
32699
32883
  import { dirname as dirname9, join as join15, resolve as resolve22 } from "node:path";
32884
+ function backdropCostUsd(quality) {
32885
+ return quality === "hd" ? 0.2 : 0.05;
32886
+ }
32700
32887
  async function createBuildPlan(opts) {
32701
32888
  const projectDir = resolve22(opts.projectDir);
32702
32889
  loadPlanEnv(projectDir);
@@ -32798,13 +32985,13 @@ async function createBuildPlan(opts) {
32798
32985
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
32799
32986
  const videoCue = videoPrompt ?? videoReference?.raw;
32800
32987
  const musicCue = musicPrompt ?? musicReference?.raw;
32801
- const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : 0;
32988
+ const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
32802
32989
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
32803
32990
  beatId: beat.id,
32804
32991
  cue: narrationText,
32805
32992
  provider: resolved.narration.resolved,
32806
32993
  voice,
32807
- ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
32994
+ ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
32808
32995
  }) : null;
32809
32996
  const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
32810
32997
  beatId: beat.id,
@@ -32852,7 +33039,7 @@ async function createBuildPlan(opts) {
32852
33039
  reference: backdropReference,
32853
33040
  projectDir,
32854
33041
  force: opts.force,
32855
- cost: BACKDROP_COST_USD,
33042
+ cost: backdropCostUsd(imageQuality),
32856
33043
  active: includeAssets
32857
33044
  }) : null;
32858
33045
  const video = videoCue && !opts.skipVideo ? assetPlan({
@@ -33222,17 +33409,20 @@ function providerInput(opts) {
33222
33409
  async function resolveNarrationProvider(input, projectDir) {
33223
33410
  const requested = input.value.toLowerCase();
33224
33411
  const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
33225
- const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : "kokoro";
33412
+ const hasOpenAi = Boolean(await getApiKeyFromConfig("openai", { cwd: projectDir }));
33413
+ const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "openai" ? "openai" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : hasOpenAi ? "openai" : "kokoro";
33414
+ const requiresKey = resolved !== "kokoro";
33415
+ const hasKey = resolved === "elevenlabs" ? hasElevenLabs : resolved === "openai" ? hasOpenAi : true;
33226
33416
  return {
33227
33417
  kind: "narration",
33228
33418
  requested: input.requested,
33229
33419
  resolved,
33230
33420
  source: input.source,
33231
- requiresKey: resolved === "elevenlabs",
33232
- configured: resolved !== "elevenlabs" || hasElevenLabs,
33233
- ...resolved === "elevenlabs" ? { configKey: "elevenlabs" } : {},
33234
- ...resolved === "elevenlabs" && !hasElevenLabs ? { missingKey: providerEnvVar("elevenlabs") } : {},
33235
- retryWith: resolved === "elevenlabs" && !hasElevenLabs ? ["vibe setup --full"] : []
33421
+ requiresKey,
33422
+ configured: hasKey,
33423
+ ...requiresKey ? { configKey: resolved } : {},
33424
+ ...requiresKey && !hasKey ? { missingKey: providerEnvVar(resolved) } : {},
33425
+ retryWith: requiresKey && !hasKey ? ["vibe setup --full"] : []
33236
33426
  };
33237
33427
  }
33238
33428
  async function keyedProviderResolution(opts, projectDir) {
@@ -33328,7 +33518,7 @@ function loadPlanEnv(projectDir) {
33328
33518
  dir = dirname9(dir);
33329
33519
  }
33330
33520
  }
33331
- var import_dotenv2, BACKDROP_COST_USD, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
33521
+ var import_dotenv2, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, OPENAI_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
33332
33522
  var init_build_plan = __esm({
33333
33523
  "src/commands/_shared/build-plan.ts"() {
33334
33524
  "use strict";
@@ -33343,10 +33533,10 @@ var init_build_plan = __esm({
33343
33533
  init_storyboard_parse();
33344
33534
  init_project_config();
33345
33535
  init_storyboard_edit();
33346
- BACKDROP_COST_USD = 3;
33347
33536
  VIDEO_COST_USD = 5;
33348
33537
  MUSIC_COST_USD = 0.5;
33349
33538
  ELEVENLABS_NARRATION_COST_USD = 0.05;
33539
+ OPENAI_NARRATION_COST_USD = 0.01;
33350
33540
  COMPOSE_COST_USD = 0.06;
33351
33541
  SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
33352
33542
  }
@@ -33864,7 +34054,10 @@ async function executeSceneBuild(opts) {
33864
34054
  (beat) => beat.narrationStatus === "failed" || beat.backdropStatus === "failed" || beat.videoStatus === "failed" || beat.musicStatus === "failed"
33865
34055
  );
33866
34056
  stageReports.assets.status = assetFailed ? "failed" : pendingJobs.length > 0 ? "pending-jobs" : "done";
33867
- stageReports.assets.costUsd = estimateActualAssetCost(beatOutcomes);
34057
+ stageReports.assets.costUsd = estimateActualAssetCost(
34058
+ beatOutcomes,
34059
+ opts.imageQuality ?? "hd"
34060
+ );
33868
34061
  stageReports.assets.retryWith = pendingJobs.map(
33869
34062
  (job) => `vibe status job ${job.id} --project ${projectDir} --json`
33870
34063
  );
@@ -35047,11 +35240,11 @@ function shouldRunStage(selected, stage) {
35047
35240
  if (selected === "all") return true;
35048
35241
  return selected === stage;
35049
35242
  }
35050
- function estimateActualAssetCost(outcomes) {
35243
+ function estimateActualAssetCost(outcomes, imageQuality) {
35051
35244
  let cost = 0;
35052
35245
  for (const outcome of outcomes) {
35053
35246
  if (outcome.narrationStatus === "generated") cost += 0.05;
35054
- if (outcome.backdropStatus === "generated") cost += 3;
35247
+ if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
35055
35248
  if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") cost += 5;
35056
35249
  if (outcome.musicStatus === "generated" || outcome.musicStatus === "pending") cost += 0.5;
35057
35250
  }
@@ -51374,7 +51567,7 @@ function pickOutputPath(data) {
51374
51567
  }
51375
51568
 
51376
51569
  // src/tools/_shared/elicit.ts
51377
- var NARRATION_CHOICES = ["kokoro", "elevenlabs"];
51570
+ var NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
51378
51571
  var BACKDROP_CHOICES = ["skip", "openai"];
51379
51572
  function planBuildElicitation(args) {
51380
51573
  const assetsWillRun = args.stage === void 0 || args.stage === "all" || args.stage === "assets";
@@ -51389,8 +51582,9 @@ function planBuildElicitation(args) {
51389
51582
  description: "Which TTS engine narrates the video?",
51390
51583
  enum: [...NARRATION_CHOICES],
51391
51584
  enumNames: [
51392
- "Kokoro \u2014 free, runs locally on this machine",
51393
- "ElevenLabs \u2014 cloud TTS, uses your ElevenLabs credits"
51585
+ "Kokoro \u2014 free, runs on this machine (first use downloads a ~90 MB voice model; slower)",
51586
+ "OpenAI \u2014 fast cloud voice, uses your OpenAI API key (~$0.02 per video)",
51587
+ "ElevenLabs \u2014 premium cloud voices, uses your ElevenLabs credits"
51394
51588
  ]
51395
51589
  };
51396
51590
  required.push("narration");
@@ -52595,9 +52789,9 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
52595
52789
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
52596
52790
  ).option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option(
52597
52791
  "--tts <provider>",
52598
- "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)",
52792
+ "TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
52599
52793
  "auto"
52600
- ).option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option(
52794
+ ).option("--voice <id>", "Voice id (ElevenLabs name/id, OpenAI voice like marin, or Kokoro id like af_heart)").option(
52601
52795
  "--no-audio",
52602
52796
  "Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
52603
52797
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
@@ -52858,7 +53052,7 @@ async function executeSceneAdd(opts) {
52858
53052
  throw error;
52859
53053
  }
52860
53054
  opts.onProgress?.(
52861
- resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads ~330MB)..." : "Generating narration with ElevenLabs..."
53055
+ resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads a ~90MB model)..." : resolution.provider === "openai" ? "Generating narration with OpenAI (gpt-4o-mini-tts)..." : "Generating narration with ElevenLabs..."
52862
53056
  );
52863
53057
  const tts = await resolution.call(narrationText, {
52864
53058
  voice: opts.voice,
@@ -53618,7 +53812,9 @@ var sceneBuildSchema = external_exports.object({
53618
53812
  skipVideo: external_exports.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
53619
53813
  skipMusic: external_exports.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
53620
53814
  skipRender: external_exports.boolean().optional().describe("Stop after compose \u2014 produces compositions/*.html but no final MP4."),
53621
- ttsProvider: external_exports.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override. Default 'auto'."),
53815
+ ttsProvider: external_exports.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe(
53816
+ "TTS provider override. Default 'auto' (elevenlabs key > openai key > local kokoro)."
53817
+ ),
53622
53818
  voice: external_exports.string().optional().describe("TTS voice id (provider-specific)."),
53623
53819
  imageProvider: external_exports.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
53624
53820
  videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
@@ -57456,7 +57652,7 @@ var planTool = defineTool({
57456
57652
  skipBackdrop: external_exports.boolean().optional().describe("Don't include backdrop image generation in the plan."),
57457
57653
  skipVideo: external_exports.boolean().optional().describe("Don't include video generation in the plan."),
57458
57654
  skipMusic: external_exports.boolean().optional().describe("Don't include music generation in the plan."),
57459
- ttsProvider: external_exports.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override."),
57655
+ ttsProvider: external_exports.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe("TTS provider override."),
57460
57656
  voice: external_exports.string().optional().describe("Voice id."),
57461
57657
  imageProvider: external_exports.string().optional().describe("Image provider override."),
57462
57658
  imageQuality: external_exports.enum(["standard", "hd"]).optional().describe("Image quality override."),
@@ -145,7 +145,7 @@ export declare const sceneBuildTool: import("../define-tool.js").ToolDefinition<
145
145
  skipVideo: z.ZodOptional<z.ZodBoolean>;
146
146
  skipMusic: z.ZodOptional<z.ZodBoolean>;
147
147
  skipRender: z.ZodOptional<z.ZodBoolean>;
148
- ttsProvider: z.ZodOptional<z.ZodEnum<["auto", "elevenlabs", "kokoro"]>>;
148
+ ttsProvider: z.ZodOptional<z.ZodEnum<["auto", "elevenlabs", "openai", "kokoro"]>>;
149
149
  voice: z.ZodOptional<z.ZodString>;
150
150
  imageProvider: z.ZodOptional<z.ZodEnum<["openai"]>>;
151
151
  videoProvider: z.ZodOptional<z.ZodEnum<["seedance", "grok", "kling", "runway", "veo"]>>;
@@ -167,7 +167,7 @@ export declare const sceneBuildTool: import("../define-tool.js").ToolDefinition<
167
167
  skipBackdrop?: boolean | undefined;
168
168
  skipRender?: boolean | undefined;
169
169
  imageProvider?: "openai" | undefined;
170
- ttsProvider?: "elevenlabs" | "auto" | "kokoro" | undefined;
170
+ ttsProvider?: "openai" | "elevenlabs" | "auto" | "kokoro" | undefined;
171
171
  skipVideo?: boolean | undefined;
172
172
  skipMusic?: boolean | undefined;
173
173
  videoProvider?: "grok" | "seedance" | "kling" | "runway" | "veo" | undefined;
@@ -188,7 +188,7 @@ export declare const sceneBuildTool: import("../define-tool.js").ToolDefinition<
188
188
  skipBackdrop?: boolean | undefined;
189
189
  skipRender?: boolean | undefined;
190
190
  imageProvider?: "openai" | undefined;
191
- ttsProvider?: "elevenlabs" | "auto" | "kokoro" | undefined;
191
+ ttsProvider?: "openai" | "elevenlabs" | "auto" | "kokoro" | undefined;
192
192
  skipVideo?: boolean | undefined;
193
193
  skipMusic?: boolean | undefined;
194
194
  videoProvider?: "grok" | "seedance" | "kling" | "runway" | "veo" | undefined;
@@ -87,7 +87,7 @@ export declare const planTool: import("../define-tool.js").ToolDefinition<z.ZodO
87
87
  skipBackdrop: z.ZodOptional<z.ZodBoolean>;
88
88
  skipVideo: z.ZodOptional<z.ZodBoolean>;
89
89
  skipMusic: z.ZodOptional<z.ZodBoolean>;
90
- ttsProvider: z.ZodOptional<z.ZodEnum<["auto", "elevenlabs", "kokoro"]>>;
90
+ ttsProvider: z.ZodOptional<z.ZodEnum<["auto", "elevenlabs", "openai", "kokoro"]>>;
91
91
  voice: z.ZodOptional<z.ZodString>;
92
92
  imageProvider: z.ZodOptional<z.ZodString>;
93
93
  imageQuality: z.ZodOptional<z.ZodEnum<["standard", "hd"]>>;
@@ -107,7 +107,7 @@ export declare const planTool: import("../define-tool.js").ToolDefinition<z.ZodO
107
107
  skipNarration?: boolean | undefined;
108
108
  skipBackdrop?: boolean | undefined;
109
109
  imageProvider?: string | undefined;
110
- ttsProvider?: "elevenlabs" | "auto" | "kokoro" | undefined;
110
+ ttsProvider?: "openai" | "elevenlabs" | "auto" | "kokoro" | undefined;
111
111
  skipVideo?: boolean | undefined;
112
112
  skipMusic?: boolean | undefined;
113
113
  videoProvider?: "grok" | "seedance" | "kling" | "runway" | "veo" | undefined;
@@ -125,7 +125,7 @@ export declare const planTool: import("../define-tool.js").ToolDefinition<z.ZodO
125
125
  skipNarration?: boolean | undefined;
126
126
  skipBackdrop?: boolean | undefined;
127
127
  imageProvider?: string | undefined;
128
- ttsProvider?: "elevenlabs" | "auto" | "kokoro" | undefined;
128
+ ttsProvider?: "openai" | "elevenlabs" | "auto" | "kokoro" | undefined;
129
129
  skipVideo?: boolean | undefined;
130
130
  skipMusic?: boolean | undefined;
131
131
  videoProvider?: "grok" | "seedance" | "kling" | "runway" | "veo" | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.112.0",
3
+ "version": "0.113.0",
4
4
  "description": "VibeFrame CLI - agentic video workflow runtime",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -83,8 +83,8 @@
83
83
  "tsx": "^4.21.0",
84
84
  "typescript": "^5.3.3",
85
85
  "vitest": "^1.2.2",
86
- "@vibeframe/ai-providers": "0.112.0",
87
- "@vibeframe/core": "0.112.0"
86
+ "@vibeframe/ai-providers": "0.113.0",
87
+ "@vibeframe/core": "0.113.0"
88
88
  },
89
89
  "engines": {
90
90
  "node": ">=20"