@vibeframe/cli 0.112.1 → 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.
@@ -4,7 +4,7 @@ export declare const LEGACY_VIBE_PROJECT_FILENAME = "vibe.project.yaml";
4
4
  export type CompositionEngine = "hyperframes";
5
5
  export type ProjectImageProvider = "openai" | "gemini" | "grok" | null;
6
6
  export type ProjectVideoProvider = "seedance" | "grok" | "kling" | "runway" | "veo" | null;
7
- export type ProjectNarrationProvider = "elevenlabs" | "kokoro" | null;
7
+ export type ProjectNarrationProvider = "elevenlabs" | "openai" | "kokoro" | null;
8
8
  export type ProjectComposerProvider = "claude" | "openai" | "gemini" | null;
9
9
  export type ProjectQuality = "draft" | "standard" | "high";
10
10
  export interface VibeProjectConfigV1 {
@@ -12,7 +12,7 @@
12
12
  * Idempotent: assets that already exist on disk are reused unless `force`.
13
13
  *
14
14
  * Scope held tight for v0.60:
15
- * - TTS via `resolveTtsProvider` (ElevenLabs / Kokoro auto-fallback)
15
+ * - TTS via `resolveTtsProvider` (ElevenLabs / OpenAI / Kokoro auto-fallback)
16
16
  * - T2I via OpenAI, Gemini, or Grok
17
17
  * - No Whisper transcribe step (compose handles its own)
18
18
  * - No root `index.html` synthesis — driver expects the project to
@@ -45,7 +45,7 @@ export interface VibeProjectConfig {
45
45
  /** Default providers per capability. `null` means "auto-resolve from env". */
46
46
  providers: {
47
47
  image: "openai" | "gemini" | "grok" | null;
48
- tts: "elevenlabs" | "kokoro" | null;
48
+ tts: "elevenlabs" | "openai" | "kokoro" | null;
49
49
  transcribe: "whisper" | null;
50
50
  };
51
51
  /** Cost ceiling for `vibe remix` runs in this project. 0 disables. */
@@ -8,18 +8,21 @@
8
8
  * Routes between:
9
9
  * - **ElevenLabs** — cloud, API-key gated, paid (~$0.02/scene). Default
10
10
  * when `ELEVENLABS_API_KEY` is set.
11
- * - **Kokoro** — local Apache 2.0 model, free, ~330MB first-call download.
11
+ * - **OpenAI** — cloud gpt-4o-mini-tts, cheap (~$0.015/min of audio).
12
+ * Default when only `OPENAI_API_KEY` is set — the common Claude Desktop
13
+ * case where one key already covers backdrop images.
14
+ * - **Kokoro** — local Apache 2.0 model, free, ~90MB first-call download.
12
15
  * The fallback for key-less users.
13
16
  *
14
17
  * The router exposes a uniform `TtsCallable` shape so call sites don't have
15
- * to branch on provider id. Both providers already return the same
18
+ * to branch on provider id. All providers already return the same
16
19
  * `{ success, audioBuffer, error?, characterCount? }` result.
17
20
  */
18
21
  import type { KokoroLoadEvent } from "@vibeframe/ai-providers";
19
22
  /** TTS providers VibeFrame can route to. `"auto"` picks based on key availability. */
20
- export type TtsProviderName = "auto" | "elevenlabs" | "kokoro";
23
+ export type TtsProviderName = "auto" | "elevenlabs" | "openai" | "kokoro";
21
24
  /** Concrete provider id surfaced in result metadata. Never `"auto"`. */
22
- export type ResolvedTtsProvider = "elevenlabs" | "kokoro";
25
+ export type ResolvedTtsProvider = "elevenlabs" | "openai" | "kokoro";
23
26
  /** Options accepted by every {@link TtsCallable}. Subset of provider-specific options. */
24
27
  export interface TtsCallOptions {
25
28
  /**
@@ -56,10 +59,11 @@ export interface TtsResolution {
56
59
  * Resolution policy:
57
60
  * - `"elevenlabs"`: requires `ELEVENLABS_API_KEY`. Throws an `ApiKeyError`-style
58
61
  * error via {@link getApiKey} (consumed by `requireApiKey`/`exitWithError`).
62
+ * - `"openai"`: requires `OPENAI_API_KEY`.
59
63
  * - `"kokoro"`: always available (local). No key check.
60
- * - `"auto"` (or `undefined`): if an ElevenLabs key is configured in
61
- * project/user config.yaml, .env, or process env, picks ElevenLabs;
62
- * otherwise falls back to Kokoro.
64
+ * - `"auto"` (or `undefined`): ElevenLabs key configured → ElevenLabs
65
+ * (preserves pre-0.113 behavior — a dedicated TTS key signals intent);
66
+ * else OpenAI key configured → OpenAI; else Kokoro.
63
67
  */
64
68
  export declare function resolveTtsProvider(preferred?: TtsProviderName): Promise<TtsResolution>;
65
69
  /**
package/dist/index.js CHANGED
@@ -7066,8 +7066,8 @@ var init_api_keys = __esm({
7066
7066
  envVar: "OPENAI_API_KEY",
7067
7067
  label: "OpenAI",
7068
7068
  showInSetup: true,
7069
- setupDescription: "gpt-image-2 image gen ($, default since v0.56), Whisper transcribe, Agent",
7070
- envExampleComment: "OpenAI API Key (Whisper transcription, gpt-image-2 \u2014 default text-to-image since v0.56)",
7069
+ setupDescription: "gpt-image-2 image gen ($, default since v0.56), gpt-4o-mini-tts narration, Whisper transcribe, Agent",
7070
+ envExampleComment: "OpenAI API Key (Whisper transcription, gpt-4o-mini-tts narration, gpt-image-2 \u2014 default text-to-image since v0.56)",
7071
7071
  envExampleUrl: "https://platform.openai.com/api-keys",
7072
7072
  keyFormat: { prefix: /^sk-/, example: "sk-..." }
7073
7073
  });
@@ -9174,8 +9174,8 @@ var init_openai = __esm({
9174
9174
  id: "openai",
9175
9175
  label: "OpenAI",
9176
9176
  apiKey: "openai",
9177
- kinds: ["llm", "image", "transcription"],
9178
- resolverPriority: { image: 1 },
9177
+ kinds: ["llm", "image", "transcription", "speech"],
9178
+ resolverPriority: { image: 1, speech: 2 },
9179
9179
  commandsUnlocked: [
9180
9180
  "agent -p openai",
9181
9181
  "generate image -p openai",
@@ -11597,16 +11597,19 @@ var init_elevenlabs = __esm({
11597
11597
  });
11598
11598
 
11599
11599
  // ../ai-providers/dist/kokoro/KokoroProvider.js
11600
- async function loadKokoroFactory() {
11600
+ async function loadKokoroRuntime() {
11601
11601
  if (factoryOverride)
11602
- return factoryOverride;
11602
+ return { factory: factoryOverride, device: "cpu" };
11603
11603
  try {
11604
11604
  const mod = await import("kokoro-js");
11605
- return mod.KokoroTTS;
11605
+ return { factory: mod.KokoroTTS, device: "cpu" };
11606
11606
  } catch (err) {
11607
- const fallback2 = await loadKokoroFromWorkspace();
11608
- if (fallback2)
11609
- return fallback2;
11607
+ const workspace = await loadKokoroFromWorkspace();
11608
+ if (workspace)
11609
+ return { factory: workspace, device: "cpu" };
11610
+ const bundled = await loadBundledKokoroRuntime();
11611
+ if (bundled)
11612
+ return bundled;
11610
11613
  throw mapKokoroImportError(err);
11611
11614
  }
11612
11615
  }
@@ -11626,10 +11629,51 @@ async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
11626
11629
  return null;
11627
11630
  }
11628
11631
  }
11632
+ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUNTIME) {
11633
+ const base = runtimeDir?.trim();
11634
+ if (!base)
11635
+ return null;
11636
+ try {
11637
+ const { readFile: readFile56, writeFile: writeFile61, mkdir: mkdir31 } = await import("node:fs/promises");
11638
+ const { join: join30, resolve: resolve86 } = await import("node:path");
11639
+ const { pathToFileURL } = await import("node:url");
11640
+ const { homedir: homedir8 } = await import("node:os");
11641
+ const transformersDir = resolve86(base, "node_modules", "@huggingface", "transformers");
11642
+ const ortDistDir = resolve86(base, "node_modules", "onnxruntime-web", "dist");
11643
+ const transformers = await import(pathToFileURL(join30(transformersDir, "dist", "transformers.web.js")).href);
11644
+ const cacheRoot = join30(homedir8(), ".cache", "vibeframe", "models");
11645
+ transformers.env.allowLocalModels = false;
11646
+ transformers.env.useCustomCache = true;
11647
+ transformers.env.customCache = {
11648
+ async match(key) {
11649
+ try {
11650
+ return new Response(await readFile56(join30(cacheRoot, encodeURIComponent(String(key)))));
11651
+ } catch {
11652
+ return void 0;
11653
+ }
11654
+ },
11655
+ async put(key, response) {
11656
+ try {
11657
+ const buf = Buffer.from(await response.arrayBuffer());
11658
+ await mkdir31(cacheRoot, { recursive: true });
11659
+ await writeFile61(join30(cacheRoot, encodeURIComponent(String(key))), buf);
11660
+ } catch {
11661
+ }
11662
+ }
11663
+ };
11664
+ transformers.env.backends.onnx.wasm.wasmPaths = pathToFileURL(ortDistDir + "/").href;
11665
+ const factory = await loadKokoroFromWorkspace(base);
11666
+ if (!factory)
11667
+ return null;
11668
+ return { factory, device: "cpu" };
11669
+ } catch {
11670
+ return null;
11671
+ }
11672
+ }
11629
11673
  function mapKokoroImportError(err) {
11630
11674
  const code = err instanceof Error && "code" in err ? err.code : void 0;
11631
11675
  if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
11632
- 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.");
11676
+ 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.");
11633
11677
  }
11634
11678
  return err instanceof Error ? err : new Error(String(err));
11635
11679
  }
@@ -11637,10 +11681,12 @@ function loadModel(progress) {
11637
11681
  if (modelPromise)
11638
11682
  return modelPromise;
11639
11683
  modelPromise = (async () => {
11640
- const factory = await loadKokoroFactory();
11641
- return factory.from_pretrained(KOKORO_MODEL_ID, {
11684
+ const runtime = await loadKokoroRuntime();
11685
+ const override = process.env.VIBE_ONNX_DEVICE;
11686
+ const device = override === "cpu" || override === "wasm" ? override : runtime.device;
11687
+ return runtime.factory.from_pretrained(KOKORO_MODEL_ID, {
11642
11688
  dtype: "q8",
11643
- device: "cpu",
11689
+ device,
11644
11690
  progress_callback: progress ? (raw) => progress(normaliseEvent(raw)) : void 0
11645
11691
  });
11646
11692
  })().catch((err) => {
@@ -11724,7 +11770,7 @@ var init_kokoro = __esm({
11724
11770
  label: "Kokoro (local)",
11725
11771
  apiKey: null,
11726
11772
  kinds: ["speech"],
11727
- resolverPriority: { speech: 2 }
11773
+ resolverPriority: { speech: 3 }
11728
11774
  });
11729
11775
  }
11730
11776
  });
@@ -11996,12 +12042,119 @@ var init_openai_image = __esm({
11996
12042
  }
11997
12043
  });
11998
12044
 
12045
+ // ../ai-providers/dist/openai-tts/OpenAiTtsProvider.js
12046
+ var OPENAI_TTS_VOICES, DEFAULT_VOICE, DEFAULT_MODEL2, TTS_429_MAX_RETRIES2, TTS_429_RETRY_DELAY_MS2, OpenAiTtsProvider, openaiTtsProvider;
12047
+ var init_OpenAiTtsProvider = __esm({
12048
+ "../ai-providers/dist/openai-tts/OpenAiTtsProvider.js"() {
12049
+ "use strict";
12050
+ OPENAI_TTS_VOICES = [
12051
+ "alloy",
12052
+ "ash",
12053
+ "ballad",
12054
+ "cedar",
12055
+ "coral",
12056
+ "echo",
12057
+ "fable",
12058
+ "marin",
12059
+ "nova",
12060
+ "onyx",
12061
+ "sage",
12062
+ "shimmer",
12063
+ "verse"
12064
+ ];
12065
+ DEFAULT_VOICE = "marin";
12066
+ DEFAULT_MODEL2 = "gpt-4o-mini-tts";
12067
+ TTS_429_MAX_RETRIES2 = 1;
12068
+ TTS_429_RETRY_DELAY_MS2 = 2e3;
12069
+ OpenAiTtsProvider = class {
12070
+ constructor() {
12071
+ this.id = "openai";
12072
+ this.name = "OpenAI TTS";
12073
+ this.description = "OpenAI cloud text-to-speech (gpt-4o-mini-tts)";
12074
+ this.capabilities = ["text-to-speech"];
12075
+ this.isAvailable = true;
12076
+ this.baseUrl = "https://api.openai.com/v1";
12077
+ }
12078
+ async initialize(config2) {
12079
+ this.apiKey = config2.apiKey;
12080
+ if (config2.baseUrl) {
12081
+ this.baseUrl = config2.baseUrl;
12082
+ }
12083
+ }
12084
+ isConfigured() {
12085
+ return !!this.apiKey;
12086
+ }
12087
+ async textToSpeech(text, options = {}) {
12088
+ if (!this.apiKey) {
12089
+ return { success: false, error: "OpenAI API key not configured" };
12090
+ }
12091
+ const voice = (options.voice ?? DEFAULT_VOICE).toLowerCase();
12092
+ if (!OPENAI_TTS_VOICES.includes(voice)) {
12093
+ return {
12094
+ success: false,
12095
+ error: `Unknown OpenAI voice "${options.voice}". Available voices: ${OPENAI_TTS_VOICES.join(", ")}.`
12096
+ };
12097
+ }
12098
+ try {
12099
+ let response;
12100
+ for (let attempt = 0; ; attempt++) {
12101
+ response = await fetch(`${this.baseUrl}/audio/speech`, {
12102
+ method: "POST",
12103
+ headers: {
12104
+ Authorization: `Bearer ${this.apiKey}`,
12105
+ "Content-Type": "application/json"
12106
+ },
12107
+ body: JSON.stringify({
12108
+ model: options.model ?? DEFAULT_MODEL2,
12109
+ input: text,
12110
+ voice,
12111
+ response_format: "mp3",
12112
+ ...options.speed !== void 0 && { speed: options.speed },
12113
+ ...options.instructions !== void 0 && { instructions: options.instructions }
12114
+ })
12115
+ });
12116
+ if (response.status === 429 && attempt < TTS_429_MAX_RETRIES2) {
12117
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, TTS_429_RETRY_DELAY_MS2 * (attempt + 1)));
12118
+ continue;
12119
+ }
12120
+ break;
12121
+ }
12122
+ if (!response.ok) {
12123
+ const error = await response.text();
12124
+ return { success: false, error: `OpenAI TTS failed: ${error}` };
12125
+ }
12126
+ const arrayBuffer = await response.arrayBuffer();
12127
+ return {
12128
+ success: true,
12129
+ audioBuffer: Buffer.from(arrayBuffer),
12130
+ characterCount: text.length
12131
+ };
12132
+ } catch (error) {
12133
+ return {
12134
+ success: false,
12135
+ error: error instanceof Error ? error.message : "Unknown error"
12136
+ };
12137
+ }
12138
+ }
12139
+ };
12140
+ openaiTtsProvider = new OpenAiTtsProvider();
12141
+ }
12142
+ });
12143
+
12144
+ // ../ai-providers/dist/openai-tts/index.js
12145
+ var init_openai_tts = __esm({
12146
+ "../ai-providers/dist/openai-tts/index.js"() {
12147
+ "use strict";
12148
+ init_OpenAiTtsProvider();
12149
+ }
12150
+ });
12151
+
11999
12152
  // ../ai-providers/dist/runway/RunwayProvider.js
12000
- var DEFAULT_MODEL2, RunwayProvider, runwayProvider;
12153
+ var DEFAULT_MODEL3, RunwayProvider, runwayProvider;
12001
12154
  var init_RunwayProvider = __esm({
12002
12155
  "../ai-providers/dist/runway/RunwayProvider.js"() {
12003
12156
  "use strict";
12004
- DEFAULT_MODEL2 = "gen4.5";
12157
+ DEFAULT_MODEL3 = "gen4.5";
12005
12158
  RunwayProvider = class _RunwayProvider {
12006
12159
  constructor() {
12007
12160
  this.id = "runway";
@@ -12044,7 +12197,7 @@ var init_RunwayProvider = __esm({
12044
12197
  "1:1": "960:960"
12045
12198
  };
12046
12199
  const apiRatio = ratioMap[options?.aspectRatio || "16:9"] || "1280:720";
12047
- const model = options?.model || DEFAULT_MODEL2;
12200
+ const model = options?.model || DEFAULT_MODEL3;
12048
12201
  if (!options?.referenceImage && model !== "gen4.5") {
12049
12202
  return {
12050
12203
  id: "",
@@ -12306,11 +12459,11 @@ var init_runway = __esm({
12306
12459
 
12307
12460
  // ../ai-providers/dist/kling/KlingProvider.js
12308
12461
  import { createHmac } from "node:crypto";
12309
- var DEFAULT_MODEL3, STD_MODE_MODELS, KlingProvider, klingProvider;
12462
+ var DEFAULT_MODEL4, STD_MODE_MODELS, KlingProvider, klingProvider;
12310
12463
  var init_KlingProvider = __esm({
12311
12464
  "../ai-providers/dist/kling/KlingProvider.js"() {
12312
12465
  "use strict";
12313
- DEFAULT_MODEL3 = "kling-v2-5-turbo";
12466
+ DEFAULT_MODEL4 = "kling-v2-5-turbo";
12314
12467
  STD_MODE_MODELS = ["kling-v2-5-turbo", "kling-v2-6", "kling-v3", "kling-v3-omni"];
12315
12468
  KlingProvider = class {
12316
12469
  constructor() {
@@ -12373,7 +12526,7 @@ var init_KlingProvider = __esm({
12373
12526
  }
12374
12527
  try {
12375
12528
  const token = this.generateToken();
12376
- const model = options?.model || DEFAULT_MODEL3;
12529
+ const model = options?.model || DEFAULT_MODEL4;
12377
12530
  let mode = options?.mode || "std";
12378
12531
  if (!STD_MODE_MODELS.includes(model) && mode === "std") {
12379
12532
  mode = "pro";
@@ -12744,11 +12897,11 @@ var init_kling = __esm({
12744
12897
  });
12745
12898
 
12746
12899
  // ../ai-providers/dist/grok/GrokProvider.js
12747
- var DEFAULT_MODEL4, GrokProvider, grokProvider;
12900
+ var DEFAULT_MODEL5, GrokProvider, grokProvider;
12748
12901
  var init_GrokProvider = __esm({
12749
12902
  "../ai-providers/dist/grok/GrokProvider.js"() {
12750
12903
  "use strict";
12751
- DEFAULT_MODEL4 = "grok-imagine-video";
12904
+ DEFAULT_MODEL5 = "grok-imagine-video";
12752
12905
  GrokProvider = class {
12753
12906
  constructor() {
12754
12907
  this.id = "grok";
@@ -12915,7 +13068,7 @@ var init_GrokProvider = __esm({
12915
13068
  try {
12916
13069
  const duration = Math.round(Math.min(15, Math.max(1, options?.duration || 5)));
12917
13070
  const body = {
12918
- model: DEFAULT_MODEL4,
13071
+ model: DEFAULT_MODEL5,
12919
13072
  prompt: prompt3,
12920
13073
  duration,
12921
13074
  aspect_ratio: options?.aspectRatio || "16:9"
@@ -18122,9 +18275,11 @@ __export(dist_exports2, {
18122
18275
  KOKORO_MODEL_ID: () => KOKORO_MODEL_ID,
18123
18276
  KlingProvider: () => KlingProvider,
18124
18277
  KokoroProvider: () => KokoroProvider,
18278
+ OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
18125
18279
  OllamaProvider: () => OllamaProvider,
18126
18280
  OpenAIImageProvider: () => OpenAIImageProvider,
18127
18281
  OpenAIProvider: () => OpenAIProvider,
18282
+ OpenAiTtsProvider: () => OpenAiTtsProvider,
18128
18283
  ReplicateProvider: () => ReplicateProvider,
18129
18284
  RunwayProvider: () => RunwayProvider,
18130
18285
  WhisperProvider: () => WhisperProvider,
@@ -18148,9 +18303,12 @@ __export(dist_exports2, {
18148
18303
  isGeminiTextModelAlias: () => isGeminiTextModelAlias,
18149
18304
  klingProvider: () => klingProvider,
18150
18305
  kokoroProvider: () => kokoroProvider,
18306
+ loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
18307
+ loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
18151
18308
  ollamaProvider: () => ollamaProvider,
18152
18309
  openaiImageProvider: () => openaiImageProvider,
18153
18310
  openaiProvider: () => openaiProvider,
18311
+ openaiTtsProvider: () => openaiTtsProvider,
18154
18312
  providerRegistry: () => providerRegistry,
18155
18313
  replicateProvider: () => replicateProvider,
18156
18314
  resolveGeminiTextModel: () => resolveGeminiTextModel,
@@ -18173,6 +18331,7 @@ var init_dist2 = __esm({
18173
18331
  init_elevenlabs();
18174
18332
  init_kokoro();
18175
18333
  init_openai_image();
18334
+ init_openai_tts();
18176
18335
  init_runway();
18177
18336
  init_kling();
18178
18337
  init_grok();
@@ -26347,10 +26506,22 @@ var init_api_key = __esm({
26347
26506
 
26348
26507
  // src/commands/_shared/tts-resolve.ts
26349
26508
  async function resolveTtsProvider(preferred = "auto") {
26350
- const choice = preferred === "auto" ? await getConfiguredApiKey("ELEVENLABS_API_KEY") ? "elevenlabs" : "kokoro" : preferred;
26509
+ let choice = preferred;
26510
+ if (choice === "auto") {
26511
+ if (await getConfiguredApiKey("ELEVENLABS_API_KEY")) {
26512
+ choice = "elevenlabs";
26513
+ } else if (await getConfiguredApiKey("OPENAI_API_KEY")) {
26514
+ choice = "openai";
26515
+ } else {
26516
+ choice = "kokoro";
26517
+ }
26518
+ }
26351
26519
  if (choice === "elevenlabs") {
26352
26520
  return buildElevenLabs();
26353
26521
  }
26522
+ if (choice === "openai") {
26523
+ return buildOpenAi();
26524
+ }
26354
26525
  return buildKokoro();
26355
26526
  }
26356
26527
  async function buildElevenLabs() {
@@ -26366,6 +26537,19 @@ async function buildElevenLabs() {
26366
26537
  });
26367
26538
  return { provider: "elevenlabs", audioExtension: "mp3", call };
26368
26539
  }
26540
+ async function buildOpenAi() {
26541
+ const key = await getApiKey("OPENAI_API_KEY", "OpenAI");
26542
+ if (!key) {
26543
+ throw new TtsKeyMissingError("openai");
26544
+ }
26545
+ const provider = new OpenAiTtsProvider();
26546
+ await provider.initialize({ apiKey: key });
26547
+ const call = async (text, opts) => provider.textToSpeech(text, {
26548
+ voice: opts?.voice,
26549
+ speed: opts?.speed
26550
+ });
26551
+ return { provider: "openai", audioExtension: "mp3", call };
26552
+ }
26369
26553
  async function buildKokoro() {
26370
26554
  const provider = new KokoroProvider();
26371
26555
  await provider.initialize({});
@@ -26378,11 +26562,11 @@ async function buildKokoro() {
26378
26562
  }
26379
26563
  function parseTtsProviderName(value) {
26380
26564
  if (!value) return "auto";
26381
- if (value === "auto" || value === "elevenlabs" || value === "kokoro") {
26565
+ if (value === "auto" || value === "elevenlabs" || value === "openai" || value === "kokoro") {
26382
26566
  return value;
26383
26567
  }
26384
26568
  throw new Error(
26385
- `Invalid --tts: ${value}. Valid: auto, elevenlabs, kokoro.`
26569
+ `Invalid --tts: ${value}. Valid: auto, elevenlabs, openai, kokoro.`
26386
26570
  );
26387
26571
  }
26388
26572
  var TtsKeyMissingError;
@@ -26394,7 +26578,7 @@ var init_tts_resolve = __esm({
26394
26578
  TtsKeyMissingError = class extends Error {
26395
26579
  constructor(provider) {
26396
26580
  super(
26397
- 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.`
26581
+ 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.`
26398
26582
  );
26399
26583
  this.provider = provider;
26400
26584
  this.name = "TtsKeyMissingError";
@@ -31013,7 +31197,7 @@ async function executeSceneAdd(opts) {
31013
31197
  throw error;
31014
31198
  }
31015
31199
  opts.onProgress?.(
31016
- resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads ~330MB)..." : "Generating narration with ElevenLabs..."
31200
+ 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..."
31017
31201
  );
31018
31202
  const tts = await resolution.call(narrationText, {
31019
31203
  voice: opts.voice,
@@ -31536,9 +31720,9 @@ Run 'vibe schema scene.<command>' for structured parameter info.`
31536
31720
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
31537
31721
  ).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(
31538
31722
  "--tts <provider>",
31539
- "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)",
31723
+ "TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
31540
31724
  "auto"
31541
- ).option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option(
31725
+ ).option("--voice <id>", "Voice id (ElevenLabs name/id, OpenAI voice like marin, or Kokoro id like af_heart)").option(
31542
31726
  "--no-audio",
31543
31727
  "Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
31544
31728
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
@@ -59619,13 +59803,13 @@ async function createBuildPlan(opts) {
59619
59803
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
59620
59804
  const videoCue = videoPrompt ?? videoReference?.raw;
59621
59805
  const musicCue = musicPrompt ?? musicReference?.raw;
59622
- const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : 0;
59806
+ const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
59623
59807
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
59624
59808
  beatId: beat.id,
59625
59809
  cue: narrationText,
59626
59810
  provider: resolved.narration.resolved,
59627
59811
  voice,
59628
- ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
59812
+ ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
59629
59813
  }) : null;
59630
59814
  const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
59631
59815
  beatId: beat.id,
@@ -60043,17 +60227,20 @@ function providerInput(opts) {
60043
60227
  async function resolveNarrationProvider(input, projectDir) {
60044
60228
  const requested = input.value.toLowerCase();
60045
60229
  const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
60046
- const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : "kokoro";
60230
+ const hasOpenAi = Boolean(await getApiKeyFromConfig("openai", { cwd: projectDir }));
60231
+ const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "openai" ? "openai" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : hasOpenAi ? "openai" : "kokoro";
60232
+ const requiresKey = resolved !== "kokoro";
60233
+ const hasKey = resolved === "elevenlabs" ? hasElevenLabs : resolved === "openai" ? hasOpenAi : true;
60047
60234
  return {
60048
60235
  kind: "narration",
60049
60236
  requested: input.requested,
60050
60237
  resolved,
60051
60238
  source: input.source,
60052
- requiresKey: resolved === "elevenlabs",
60053
- configured: resolved !== "elevenlabs" || hasElevenLabs,
60054
- ...resolved === "elevenlabs" ? { configKey: "elevenlabs" } : {},
60055
- ...resolved === "elevenlabs" && !hasElevenLabs ? { missingKey: providerEnvVar("elevenlabs") } : {},
60056
- retryWith: resolved === "elevenlabs" && !hasElevenLabs ? ["vibe setup --full"] : []
60239
+ requiresKey,
60240
+ configured: hasKey,
60241
+ ...requiresKey ? { configKey: resolved } : {},
60242
+ ...requiresKey && !hasKey ? { missingKey: providerEnvVar(resolved) } : {},
60243
+ retryWith: requiresKey && !hasKey ? ["vibe setup --full"] : []
60057
60244
  };
60058
60245
  }
60059
60246
  async function keyedProviderResolution(opts, projectDir) {
@@ -60149,7 +60336,7 @@ function loadPlanEnv(projectDir) {
60149
60336
  dir = dirname21(dir);
60150
60337
  }
60151
60338
  }
60152
- var import_dotenv3, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
60339
+ var import_dotenv3, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, OPENAI_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
60153
60340
  var init_build_plan = __esm({
60154
60341
  "src/commands/_shared/build-plan.ts"() {
60155
60342
  "use strict";
@@ -60167,6 +60354,7 @@ var init_build_plan = __esm({
60167
60354
  VIDEO_COST_USD = 5;
60168
60355
  MUSIC_COST_USD = 0.5;
60169
60356
  ELEVENLABS_NARRATION_COST_USD = 0.05;
60357
+ OPENAI_NARRATION_COST_USD = 0.01;
60170
60358
  COMPOSE_COST_USD = 0.06;
60171
60359
  SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
60172
60360
  }
@@ -67930,8 +68118,9 @@ function planBuildElicitation(args) {
67930
68118
  description: "Which TTS engine narrates the video?",
67931
68119
  enum: [...NARRATION_CHOICES],
67932
68120
  enumNames: [
67933
- "Kokoro \u2014 free, runs locally on this machine",
67934
- "ElevenLabs \u2014 cloud TTS, uses your ElevenLabs credits"
68121
+ "Kokoro \u2014 free, runs on this machine (first use downloads a ~90 MB voice model; slower)",
68122
+ "OpenAI \u2014 fast cloud voice, uses your OpenAI API key (~$0.02 per video)",
68123
+ "ElevenLabs \u2014 premium cloud voices, uses your ElevenLabs credits"
67935
68124
  ]
67936
68125
  };
67937
68126
  required.push("narration");
@@ -67987,7 +68176,7 @@ var NARRATION_CHOICES, BACKDROP_CHOICES;
67987
68176
  var init_elicit = __esm({
67988
68177
  "src/tools/_shared/elicit.ts"() {
67989
68178
  "use strict";
67990
- NARRATION_CHOICES = ["kokoro", "elevenlabs"];
68179
+ NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
67991
68180
  BACKDROP_CHOICES = ["skip", "openai"];
67992
68181
  }
67993
68182
  });
@@ -68465,7 +68654,9 @@ var init_scene2 = __esm({
68465
68654
  skipVideo: external_exports.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
68466
68655
  skipMusic: external_exports.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
68467
68656
  skipRender: external_exports.boolean().optional().describe("Stop after compose \u2014 produces compositions/*.html but no final MP4."),
68468
- ttsProvider: external_exports.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override. Default 'auto'."),
68657
+ ttsProvider: external_exports.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe(
68658
+ "TTS provider override. Default 'auto' (elevenlabs key > openai key > local kokoro)."
68659
+ ),
68469
68660
  voice: external_exports.string().optional().describe("TTS voice id (provider-specific)."),
68470
68661
  imageProvider: external_exports.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
68471
68662
  videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
@@ -70320,7 +70511,7 @@ var init_storyboard2 = __esm({
70320
70511
  skipBackdrop: external_exports.boolean().optional().describe("Don't include backdrop image generation in the plan."),
70321
70512
  skipVideo: external_exports.boolean().optional().describe("Don't include video generation in the plan."),
70322
70513
  skipMusic: external_exports.boolean().optional().describe("Don't include music generation in the plan."),
70323
- ttsProvider: external_exports.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override."),
70514
+ ttsProvider: external_exports.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe("TTS provider override."),
70324
70515
  voice: external_exports.string().optional().describe("Voice id."),
70325
70516
  imageProvider: external_exports.string().optional().describe("Image provider override."),
70326
70517
  imageQuality: external_exports.enum(["standard", "hd"]).optional().describe("Image quality override."),
@@ -73436,7 +73627,7 @@ init_source();
73436
73627
  // package.json
73437
73628
  var package_default = {
73438
73629
  name: "@vibeframe/cli",
73439
- version: "0.112.1",
73630
+ version: "0.113.0",
73440
73631
  description: "VibeFrame CLI - agentic video workflow runtime",
73441
73632
  private: false,
73442
73633
  license: "MIT",
@@ -83439,13 +83630,13 @@ init_output();
83439
83630
  import { resolve as resolve61 } from "node:path";
83440
83631
  var VALID_STAGES = ["assets", "compose", "sync", "render", "all"];
83441
83632
  var VALID_MODES = ["agent", "batch", "auto"];
83442
- var VALID_TTS_PROVIDERS = ["auto", "elevenlabs", "kokoro"];
83633
+ var VALID_TTS_PROVIDERS = ["auto", "elevenlabs", "openai", "kokoro"];
83443
83634
  var VALID_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
83444
83635
  var VALID_VIDEO_PROVIDERS = ["seedance", "grok", "kling", "runway", "veo"];
83445
83636
  var VALID_MUSIC_PROVIDERS = ["elevenlabs", "replicate"];
83446
83637
  var VALID_COMPOSERS = ["claude", "openai", "gemini"];
83447
83638
  var VALID_IMAGE_QUALITIES = ["standard", "hd"];
83448
- var planCommand = new Command("plan").description("Read STORYBOARD.md and show build plan, costs, missing cues, and provider needs").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Stage to plan: ${VALID_STAGES.join("|")}`, "all").option("--beat <id>", "Restrict the plan to one beat").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--skip-narration", "Don't include narration generation in the plan").option("--skip-backdrop", "Don't include backdrop image generation in the plan").option("--skip-video", "Don't include video generation in the plan").option("--skip-music", "Don't include music generation in the plan").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS.join("|")}`).option("--quality <q>", "Image quality: standard|hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--force", "Plan regeneration even when outputs already exist").option("--max-cost <usd>", "Fail if estimated cost exceeds this USD cap").action(async (projectDirArg, options) => {
83639
+ var planCommand = new Command("plan").description("Read STORYBOARD.md and show build plan, costs, missing cues, and provider needs").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Stage to plan: ${VALID_STAGES.join("|")}`, "all").option("--beat <id>", "Restrict the plan to one beat").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--skip-narration", "Don't include narration generation in the plan").option("--skip-backdrop", "Don't include backdrop image generation in the plan").option("--skip-video", "Don't include video generation in the plan").option("--skip-music", "Don't include music generation in the plan").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS.join("|")}`).option("--quality <q>", "Image quality: standard|hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--force", "Plan regeneration even when outputs already exist").option("--max-cost <usd>", "Fail if estimated cost exceeds this USD cap").action(async (projectDirArg, options) => {
83449
83640
  const startedAt = Date.now();
83450
83641
  const stage = String(options.stage ?? "all");
83451
83642
  if (!VALID_STAGES.includes(stage)) {
@@ -83636,7 +83827,7 @@ var VALID_STAGES2 = ["assets", "compose", "sync", "render", "all"];
83636
83827
  var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
83637
83828
  var VALID_VIDEO_PROVIDERS2 = ["seedance", "grok", "kling", "runway", "veo"];
83638
83829
  var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
83639
- var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-video", "Don't dispatch video generation even when beats declare video cues").option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
83830
+ var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-video", "Don't dispatch video generation even when beats declare video cues").option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
83640
83831
  "after",
83641
83832
  `
83642
83833
  Examples:
@@ -11,7 +11,7 @@ import type { ElicitForm } from "../adapters/mcp.js";
11
11
  /** The subset of build args that drive the question set. */
12
12
  export interface BuildChoiceArgs {
13
13
  stage?: "assets" | "compose" | "sync" | "render" | "all";
14
- ttsProvider?: "auto" | "elevenlabs" | "kokoro";
14
+ ttsProvider?: "auto" | "elevenlabs" | "openai" | "kokoro";
15
15
  skipNarration?: boolean;
16
16
  skipBackdrop?: boolean;
17
17
  imageProvider?: string;
@@ -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";
@@ -32801,13 +32985,13 @@ async function createBuildPlan(opts) {
32801
32985
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
32802
32986
  const videoCue = videoPrompt ?? videoReference?.raw;
32803
32987
  const musicCue = musicPrompt ?? musicReference?.raw;
32804
- 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;
32805
32989
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
32806
32990
  beatId: beat.id,
32807
32991
  cue: narrationText,
32808
32992
  provider: resolved.narration.resolved,
32809
32993
  voice,
32810
- ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
32994
+ ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
32811
32995
  }) : null;
32812
32996
  const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
32813
32997
  beatId: beat.id,
@@ -33225,17 +33409,20 @@ function providerInput(opts) {
33225
33409
  async function resolveNarrationProvider(input, projectDir) {
33226
33410
  const requested = input.value.toLowerCase();
33227
33411
  const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
33228
- 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;
33229
33416
  return {
33230
33417
  kind: "narration",
33231
33418
  requested: input.requested,
33232
33419
  resolved,
33233
33420
  source: input.source,
33234
- requiresKey: resolved === "elevenlabs",
33235
- configured: resolved !== "elevenlabs" || hasElevenLabs,
33236
- ...resolved === "elevenlabs" ? { configKey: "elevenlabs" } : {},
33237
- ...resolved === "elevenlabs" && !hasElevenLabs ? { missingKey: providerEnvVar("elevenlabs") } : {},
33238
- 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"] : []
33239
33426
  };
33240
33427
  }
33241
33428
  async function keyedProviderResolution(opts, projectDir) {
@@ -33331,7 +33518,7 @@ function loadPlanEnv(projectDir) {
33331
33518
  dir = dirname9(dir);
33332
33519
  }
33333
33520
  }
33334
- var import_dotenv2, 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;
33335
33522
  var init_build_plan = __esm({
33336
33523
  "src/commands/_shared/build-plan.ts"() {
33337
33524
  "use strict";
@@ -33349,6 +33536,7 @@ var init_build_plan = __esm({
33349
33536
  VIDEO_COST_USD = 5;
33350
33537
  MUSIC_COST_USD = 0.5;
33351
33538
  ELEVENLABS_NARRATION_COST_USD = 0.05;
33539
+ OPENAI_NARRATION_COST_USD = 0.01;
33352
33540
  COMPOSE_COST_USD = 0.06;
33353
33541
  SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
33354
33542
  }
@@ -51379,7 +51567,7 @@ function pickOutputPath(data) {
51379
51567
  }
51380
51568
 
51381
51569
  // src/tools/_shared/elicit.ts
51382
- var NARRATION_CHOICES = ["kokoro", "elevenlabs"];
51570
+ var NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
51383
51571
  var BACKDROP_CHOICES = ["skip", "openai"];
51384
51572
  function planBuildElicitation(args) {
51385
51573
  const assetsWillRun = args.stage === void 0 || args.stage === "all" || args.stage === "assets";
@@ -51394,8 +51582,9 @@ function planBuildElicitation(args) {
51394
51582
  description: "Which TTS engine narrates the video?",
51395
51583
  enum: [...NARRATION_CHOICES],
51396
51584
  enumNames: [
51397
- "Kokoro \u2014 free, runs locally on this machine",
51398
- "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"
51399
51588
  ]
51400
51589
  };
51401
51590
  required.push("narration");
@@ -52600,9 +52789,9 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
52600
52789
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
52601
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(
52602
52791
  "--tts <provider>",
52603
- "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)",
52604
52793
  "auto"
52605
- ).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(
52606
52795
  "--no-audio",
52607
52796
  "Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
52608
52797
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
@@ -52863,7 +53052,7 @@ async function executeSceneAdd(opts) {
52863
53052
  throw error;
52864
53053
  }
52865
53054
  opts.onProgress?.(
52866
- 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..."
52867
53056
  );
52868
53057
  const tts = await resolution.call(narrationText, {
52869
53058
  voice: opts.voice,
@@ -53623,7 +53812,9 @@ var sceneBuildSchema = external_exports.object({
53623
53812
  skipVideo: external_exports.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
53624
53813
  skipMusic: external_exports.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
53625
53814
  skipRender: external_exports.boolean().optional().describe("Stop after compose \u2014 produces compositions/*.html but no final MP4."),
53626
- 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
+ ),
53627
53818
  voice: external_exports.string().optional().describe("TTS voice id (provider-specific)."),
53628
53819
  imageProvider: external_exports.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
53629
53820
  videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
@@ -57461,7 +57652,7 @@ var planTool = defineTool({
57461
57652
  skipBackdrop: external_exports.boolean().optional().describe("Don't include backdrop image generation in the plan."),
57462
57653
  skipVideo: external_exports.boolean().optional().describe("Don't include video generation in the plan."),
57463
57654
  skipMusic: external_exports.boolean().optional().describe("Don't include music generation in the plan."),
57464
- 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."),
57465
57656
  voice: external_exports.string().optional().describe("Voice id."),
57466
57657
  imageProvider: external_exports.string().optional().describe("Image provider override."),
57467
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.1",
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.1",
87
- "@vibeframe/core": "0.112.1"
86
+ "@vibeframe/ai-providers": "0.113.0",
87
+ "@vibeframe/core": "0.113.0"
88
88
  },
89
89
  "engines": {
90
90
  "node": ">=20"