@vibeframe/mcp-server 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.
Files changed (3) hide show
  1. package/README.md +8 -6
  2. package/dist/index.js +238 -47
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -45,16 +45,17 @@ enabled = true
45
45
 
46
46
  Install the prebuilt Desktop Extension — no Node, npx, or JSON editing needed:
47
47
 
48
- 1. Download `vibeframe-<version>.mcpb` from the
49
- [latest GitHub Release](https://github.com/vericontext/vibeframe/releases/latest).
48
+ 1. Download the extension: [vibeframe.mcpb (always latest)](https://github.com/vericontext/vibeframe/releases/latest/download/vibeframe.mcpb)
49
+ — or pick a specific version from the [releases page](https://github.com/vericontext/vibeframe/releases).
50
50
  2. In Claude Desktop open **Settings → Extensions** and drag the `.mcpb` file in
51
51
  (or double-click the file).
52
52
  3. In the extension's settings pick your **Workspace folder** (projects are
53
53
  created there; a `.env` file in that folder is loaded for API keys) and
54
54
  optionally paste provider keys.
55
55
 
56
- Rendering still needs Google Chrome and `ffmpeg` on the machine. For free local
57
- Kokoro TTS, run `npm i kokoro-js` once inside the workspace folder. To update,
56
+ Rendering still needs Google Chrome and `ffmpeg` on the machine. Free local
57
+ Kokoro narration is bundled inside the extension — no install step; the first
58
+ use downloads a ~90 MB voice model to `~/.cache/vibeframe/models`. To update,
58
59
  download the new release's `.mcpb` and install it over the old one.
59
60
 
60
61
  To build the bundle from source: `pnpm -F @vibeframe/mcp-server build:mcpb`
@@ -131,8 +132,9 @@ The server prefers asking over silently picking defaults:
131
132
 
132
133
  - On hosts that support **MCP elicitation** (Claude Code 2.1.76+), the `build`
133
134
  tool opens a native form before the build starts for any choice the call
134
- left unspecified: narration provider (Kokoro free/local vs ElevenLabs),
135
- backdrop image generation (skip vs paid OpenAI), and a max cost cap.
135
+ left unspecified: narration provider (Kokoro free/local, OpenAI's
136
+ gpt-4o-mini-tts, or ElevenLabs), backdrop image generation (skip vs paid
137
+ OpenAI), and a max cost cap.
136
138
  Declining cancels the build before any provider spend. Set
137
139
  `VIBE_MCP_ELICIT=off` in the server env to disable the form (headless and
138
140
  automation setups).
package/dist/index.js CHANGED
@@ -192,8 +192,8 @@ var init_api_keys = __esm({
192
192
  envVar: "OPENAI_API_KEY",
193
193
  label: "OpenAI",
194
194
  showInSetup: true,
195
- setupDescription: "gpt-image-2 image gen ($, default since v0.56), Whisper transcribe, Agent",
196
- envExampleComment: "OpenAI API Key (Whisper transcription, gpt-image-2 \u2014 default text-to-image since v0.56)",
195
+ setupDescription: "gpt-image-2 image gen ($, default since v0.56), gpt-4o-mini-tts narration, Whisper transcribe, Agent",
196
+ envExampleComment: "OpenAI API Key (Whisper transcription, gpt-4o-mini-tts narration, gpt-image-2 \u2014 default text-to-image since v0.56)",
197
197
  envExampleUrl: "https://platform.openai.com/api-keys",
198
198
  keyFormat: { prefix: /^sk-/, example: "sk-..." }
199
199
  });
@@ -2300,8 +2300,8 @@ var init_openai = __esm({
2300
2300
  id: "openai",
2301
2301
  label: "OpenAI",
2302
2302
  apiKey: "openai",
2303
- kinds: ["llm", "image", "transcription"],
2304
- resolverPriority: { image: 1 },
2303
+ kinds: ["llm", "image", "transcription", "speech"],
2304
+ resolverPriority: { image: 1, speech: 2 },
2305
2305
  commandsUnlocked: [
2306
2306
  "agent -p openai",
2307
2307
  "generate image -p openai",
@@ -4723,16 +4723,19 @@ var init_elevenlabs = __esm({
4723
4723
  });
4724
4724
 
4725
4725
  // ../ai-providers/dist/kokoro/KokoroProvider.js
4726
- async function loadKokoroFactory() {
4726
+ async function loadKokoroRuntime() {
4727
4727
  if (factoryOverride)
4728
- return factoryOverride;
4728
+ return { factory: factoryOverride, device: "cpu" };
4729
4729
  try {
4730
4730
  const mod = await import("kokoro-js");
4731
- return mod.KokoroTTS;
4731
+ return { factory: mod.KokoroTTS, device: "cpu" };
4732
4732
  } catch (err) {
4733
- const fallback2 = await loadKokoroFromWorkspace();
4734
- if (fallback2)
4735
- return fallback2;
4733
+ const workspace = await loadKokoroFromWorkspace();
4734
+ if (workspace)
4735
+ return { factory: workspace, device: "cpu" };
4736
+ const bundled = await loadBundledKokoroRuntime();
4737
+ if (bundled)
4738
+ return bundled;
4736
4739
  throw mapKokoroImportError(err);
4737
4740
  }
4738
4741
  }
@@ -4752,10 +4755,51 @@ async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
4752
4755
  return null;
4753
4756
  }
4754
4757
  }
4758
+ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUNTIME) {
4759
+ const base = runtimeDir?.trim();
4760
+ if (!base)
4761
+ return null;
4762
+ try {
4763
+ const { readFile: readFile46, writeFile: writeFile53, mkdir: mkdir30 } = await import("node:fs/promises");
4764
+ const { join: join45, resolve: resolve80 } = await import("node:path");
4765
+ const { pathToFileURL } = await import("node:url");
4766
+ const { homedir: homedir8 } = await import("node:os");
4767
+ const transformersDir = resolve80(base, "node_modules", "@huggingface", "transformers");
4768
+ const ortDistDir = resolve80(base, "node_modules", "onnxruntime-web", "dist");
4769
+ const transformers = await import(pathToFileURL(join45(transformersDir, "dist", "transformers.web.js")).href);
4770
+ const cacheRoot = join45(homedir8(), ".cache", "vibeframe", "models");
4771
+ transformers.env.allowLocalModels = false;
4772
+ transformers.env.useCustomCache = true;
4773
+ transformers.env.customCache = {
4774
+ async match(key2) {
4775
+ try {
4776
+ return new Response(await readFile46(join45(cacheRoot, encodeURIComponent(String(key2)))));
4777
+ } catch {
4778
+ return void 0;
4779
+ }
4780
+ },
4781
+ async put(key2, response) {
4782
+ try {
4783
+ const buf = Buffer.from(await response.arrayBuffer());
4784
+ await mkdir30(cacheRoot, { recursive: true });
4785
+ await writeFile53(join45(cacheRoot, encodeURIComponent(String(key2))), buf);
4786
+ } catch {
4787
+ }
4788
+ }
4789
+ };
4790
+ transformers.env.backends.onnx.wasm.wasmPaths = pathToFileURL(ortDistDir + "/").href;
4791
+ const factory = await loadKokoroFromWorkspace(base);
4792
+ if (!factory)
4793
+ return null;
4794
+ return { factory, device: "cpu" };
4795
+ } catch {
4796
+ return null;
4797
+ }
4798
+ }
4755
4799
  function mapKokoroImportError(err) {
4756
4800
  const code = err instanceof Error && "code" in err ? err.code : void 0;
4757
4801
  if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
4758
- 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.");
4802
+ 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.");
4759
4803
  }
4760
4804
  return err instanceof Error ? err : new Error(String(err));
4761
4805
  }
@@ -4763,10 +4807,12 @@ function loadModel(progress) {
4763
4807
  if (modelPromise)
4764
4808
  return modelPromise;
4765
4809
  modelPromise = (async () => {
4766
- const factory = await loadKokoroFactory();
4767
- return factory.from_pretrained(KOKORO_MODEL_ID, {
4810
+ const runtime = await loadKokoroRuntime();
4811
+ const override = process.env.VIBE_ONNX_DEVICE;
4812
+ const device = override === "cpu" || override === "wasm" ? override : runtime.device;
4813
+ return runtime.factory.from_pretrained(KOKORO_MODEL_ID, {
4768
4814
  dtype: "q8",
4769
- device: "cpu",
4815
+ device,
4770
4816
  progress_callback: progress ? (raw2) => progress(normaliseEvent(raw2)) : void 0
4771
4817
  });
4772
4818
  })().catch((err) => {
@@ -4850,7 +4896,7 @@ var init_kokoro = __esm({
4850
4896
  label: "Kokoro (local)",
4851
4897
  apiKey: null,
4852
4898
  kinds: ["speech"],
4853
- resolverPriority: { speech: 2 }
4899
+ resolverPriority: { speech: 3 }
4854
4900
  });
4855
4901
  }
4856
4902
  });
@@ -5122,12 +5168,119 @@ var init_openai_image = __esm({
5122
5168
  }
5123
5169
  });
5124
5170
 
5171
+ // ../ai-providers/dist/openai-tts/OpenAiTtsProvider.js
5172
+ var OPENAI_TTS_VOICES, DEFAULT_VOICE, DEFAULT_MODEL2, TTS_429_MAX_RETRIES2, TTS_429_RETRY_DELAY_MS2, OpenAiTtsProvider, openaiTtsProvider;
5173
+ var init_OpenAiTtsProvider = __esm({
5174
+ "../ai-providers/dist/openai-tts/OpenAiTtsProvider.js"() {
5175
+ "use strict";
5176
+ OPENAI_TTS_VOICES = [
5177
+ "alloy",
5178
+ "ash",
5179
+ "ballad",
5180
+ "cedar",
5181
+ "coral",
5182
+ "echo",
5183
+ "fable",
5184
+ "marin",
5185
+ "nova",
5186
+ "onyx",
5187
+ "sage",
5188
+ "shimmer",
5189
+ "verse"
5190
+ ];
5191
+ DEFAULT_VOICE = "marin";
5192
+ DEFAULT_MODEL2 = "gpt-4o-mini-tts";
5193
+ TTS_429_MAX_RETRIES2 = 1;
5194
+ TTS_429_RETRY_DELAY_MS2 = 2e3;
5195
+ OpenAiTtsProvider = class {
5196
+ constructor() {
5197
+ this.id = "openai";
5198
+ this.name = "OpenAI TTS";
5199
+ this.description = "OpenAI cloud text-to-speech (gpt-4o-mini-tts)";
5200
+ this.capabilities = ["text-to-speech"];
5201
+ this.isAvailable = true;
5202
+ this.baseUrl = "https://api.openai.com/v1";
5203
+ }
5204
+ async initialize(config4) {
5205
+ this.apiKey = config4.apiKey;
5206
+ if (config4.baseUrl) {
5207
+ this.baseUrl = config4.baseUrl;
5208
+ }
5209
+ }
5210
+ isConfigured() {
5211
+ return !!this.apiKey;
5212
+ }
5213
+ async textToSpeech(text, options = {}) {
5214
+ if (!this.apiKey) {
5215
+ return { success: false, error: "OpenAI API key not configured" };
5216
+ }
5217
+ const voice = (options.voice ?? DEFAULT_VOICE).toLowerCase();
5218
+ if (!OPENAI_TTS_VOICES.includes(voice)) {
5219
+ return {
5220
+ success: false,
5221
+ error: `Unknown OpenAI voice "${options.voice}". Available voices: ${OPENAI_TTS_VOICES.join(", ")}.`
5222
+ };
5223
+ }
5224
+ try {
5225
+ let response;
5226
+ for (let attempt = 0; ; attempt++) {
5227
+ response = await fetch(`${this.baseUrl}/audio/speech`, {
5228
+ method: "POST",
5229
+ headers: {
5230
+ Authorization: `Bearer ${this.apiKey}`,
5231
+ "Content-Type": "application/json"
5232
+ },
5233
+ body: JSON.stringify({
5234
+ model: options.model ?? DEFAULT_MODEL2,
5235
+ input: text,
5236
+ voice,
5237
+ response_format: "mp3",
5238
+ ...options.speed !== void 0 && { speed: options.speed },
5239
+ ...options.instructions !== void 0 && { instructions: options.instructions }
5240
+ })
5241
+ });
5242
+ if (response.status === 429 && attempt < TTS_429_MAX_RETRIES2) {
5243
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, TTS_429_RETRY_DELAY_MS2 * (attempt + 1)));
5244
+ continue;
5245
+ }
5246
+ break;
5247
+ }
5248
+ if (!response.ok) {
5249
+ const error = await response.text();
5250
+ return { success: false, error: `OpenAI TTS failed: ${error}` };
5251
+ }
5252
+ const arrayBuffer = await response.arrayBuffer();
5253
+ return {
5254
+ success: true,
5255
+ audioBuffer: Buffer.from(arrayBuffer),
5256
+ characterCount: text.length
5257
+ };
5258
+ } catch (error) {
5259
+ return {
5260
+ success: false,
5261
+ error: error instanceof Error ? error.message : "Unknown error"
5262
+ };
5263
+ }
5264
+ }
5265
+ };
5266
+ openaiTtsProvider = new OpenAiTtsProvider();
5267
+ }
5268
+ });
5269
+
5270
+ // ../ai-providers/dist/openai-tts/index.js
5271
+ var init_openai_tts = __esm({
5272
+ "../ai-providers/dist/openai-tts/index.js"() {
5273
+ "use strict";
5274
+ init_OpenAiTtsProvider();
5275
+ }
5276
+ });
5277
+
5125
5278
  // ../ai-providers/dist/runway/RunwayProvider.js
5126
- var DEFAULT_MODEL2, RunwayProvider, runwayProvider;
5279
+ var DEFAULT_MODEL3, RunwayProvider, runwayProvider;
5127
5280
  var init_RunwayProvider = __esm({
5128
5281
  "../ai-providers/dist/runway/RunwayProvider.js"() {
5129
5282
  "use strict";
5130
- DEFAULT_MODEL2 = "gen4.5";
5283
+ DEFAULT_MODEL3 = "gen4.5";
5131
5284
  RunwayProvider = class _RunwayProvider {
5132
5285
  constructor() {
5133
5286
  this.id = "runway";
@@ -5170,7 +5323,7 @@ var init_RunwayProvider = __esm({
5170
5323
  "1:1": "960:960"
5171
5324
  };
5172
5325
  const apiRatio = ratioMap[options?.aspectRatio || "16:9"] || "1280:720";
5173
- const model = options?.model || DEFAULT_MODEL2;
5326
+ const model = options?.model || DEFAULT_MODEL3;
5174
5327
  if (!options?.referenceImage && model !== "gen4.5") {
5175
5328
  return {
5176
5329
  id: "",
@@ -5432,11 +5585,11 @@ var init_runway = __esm({
5432
5585
 
5433
5586
  // ../ai-providers/dist/kling/KlingProvider.js
5434
5587
  import { createHmac } from "node:crypto";
5435
- var DEFAULT_MODEL3, STD_MODE_MODELS, KlingProvider, klingProvider;
5588
+ var DEFAULT_MODEL4, STD_MODE_MODELS, KlingProvider, klingProvider;
5436
5589
  var init_KlingProvider = __esm({
5437
5590
  "../ai-providers/dist/kling/KlingProvider.js"() {
5438
5591
  "use strict";
5439
- DEFAULT_MODEL3 = "kling-v2-5-turbo";
5592
+ DEFAULT_MODEL4 = "kling-v2-5-turbo";
5440
5593
  STD_MODE_MODELS = ["kling-v2-5-turbo", "kling-v2-6", "kling-v3", "kling-v3-omni"];
5441
5594
  KlingProvider = class {
5442
5595
  constructor() {
@@ -5499,7 +5652,7 @@ var init_KlingProvider = __esm({
5499
5652
  }
5500
5653
  try {
5501
5654
  const token = this.generateToken();
5502
- const model = options?.model || DEFAULT_MODEL3;
5655
+ const model = options?.model || DEFAULT_MODEL4;
5503
5656
  let mode = options?.mode || "std";
5504
5657
  if (!STD_MODE_MODELS.includes(model) && mode === "std") {
5505
5658
  mode = "pro";
@@ -5870,11 +6023,11 @@ var init_kling = __esm({
5870
6023
  });
5871
6024
 
5872
6025
  // ../ai-providers/dist/grok/GrokProvider.js
5873
- var DEFAULT_MODEL4, GrokProvider, grokProvider;
6026
+ var DEFAULT_MODEL5, GrokProvider, grokProvider;
5874
6027
  var init_GrokProvider = __esm({
5875
6028
  "../ai-providers/dist/grok/GrokProvider.js"() {
5876
6029
  "use strict";
5877
- DEFAULT_MODEL4 = "grok-imagine-video";
6030
+ DEFAULT_MODEL5 = "grok-imagine-video";
5878
6031
  GrokProvider = class {
5879
6032
  constructor() {
5880
6033
  this.id = "grok";
@@ -6041,7 +6194,7 @@ var init_GrokProvider = __esm({
6041
6194
  try {
6042
6195
  const duration = Math.round(Math.min(15, Math.max(1, options?.duration || 5)));
6043
6196
  const body = {
6044
- model: DEFAULT_MODEL4,
6197
+ model: DEFAULT_MODEL5,
6045
6198
  prompt: prompt3,
6046
6199
  duration,
6047
6200
  aspect_ratio: options?.aspectRatio || "16:9"
@@ -11248,9 +11401,11 @@ __export(dist_exports, {
11248
11401
  KOKORO_MODEL_ID: () => KOKORO_MODEL_ID,
11249
11402
  KlingProvider: () => KlingProvider,
11250
11403
  KokoroProvider: () => KokoroProvider,
11404
+ OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
11251
11405
  OllamaProvider: () => OllamaProvider,
11252
11406
  OpenAIImageProvider: () => OpenAIImageProvider,
11253
11407
  OpenAIProvider: () => OpenAIProvider,
11408
+ OpenAiTtsProvider: () => OpenAiTtsProvider,
11254
11409
  ReplicateProvider: () => ReplicateProvider,
11255
11410
  RunwayProvider: () => RunwayProvider,
11256
11411
  WhisperProvider: () => WhisperProvider,
@@ -11274,9 +11429,12 @@ __export(dist_exports, {
11274
11429
  isGeminiTextModelAlias: () => isGeminiTextModelAlias,
11275
11430
  klingProvider: () => klingProvider,
11276
11431
  kokoroProvider: () => kokoroProvider,
11432
+ loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
11433
+ loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
11277
11434
  ollamaProvider: () => ollamaProvider,
11278
11435
  openaiImageProvider: () => openaiImageProvider,
11279
11436
  openaiProvider: () => openaiProvider,
11437
+ openaiTtsProvider: () => openaiTtsProvider,
11280
11438
  providerRegistry: () => providerRegistry,
11281
11439
  replicateProvider: () => replicateProvider,
11282
11440
  resolveGeminiTextModel: () => resolveGeminiTextModel,
@@ -11299,6 +11457,7 @@ var init_dist = __esm({
11299
11457
  init_elevenlabs();
11300
11458
  init_kokoro();
11301
11459
  init_openai_image();
11460
+ init_openai_tts();
11302
11461
  init_runway();
11303
11462
  init_kling();
11304
11463
  init_grok();
@@ -28714,10 +28873,22 @@ var init_esm = __esm({
28714
28873
 
28715
28874
  // ../cli/src/commands/_shared/tts-resolve.ts
28716
28875
  async function resolveTtsProvider(preferred = "auto") {
28717
- const choice = preferred === "auto" ? await getConfiguredApiKey("ELEVENLABS_API_KEY") ? "elevenlabs" : "kokoro" : preferred;
28876
+ let choice = preferred;
28877
+ if (choice === "auto") {
28878
+ if (await getConfiguredApiKey("ELEVENLABS_API_KEY")) {
28879
+ choice = "elevenlabs";
28880
+ } else if (await getConfiguredApiKey("OPENAI_API_KEY")) {
28881
+ choice = "openai";
28882
+ } else {
28883
+ choice = "kokoro";
28884
+ }
28885
+ }
28718
28886
  if (choice === "elevenlabs") {
28719
28887
  return buildElevenLabs();
28720
28888
  }
28889
+ if (choice === "openai") {
28890
+ return buildOpenAi();
28891
+ }
28721
28892
  return buildKokoro();
28722
28893
  }
28723
28894
  async function buildElevenLabs() {
@@ -28733,6 +28904,19 @@ async function buildElevenLabs() {
28733
28904
  });
28734
28905
  return { provider: "elevenlabs", audioExtension: "mp3", call };
28735
28906
  }
28907
+ async function buildOpenAi() {
28908
+ const key2 = await getApiKey("OPENAI_API_KEY", "OpenAI");
28909
+ if (!key2) {
28910
+ throw new TtsKeyMissingError("openai");
28911
+ }
28912
+ const provider = new OpenAiTtsProvider();
28913
+ await provider.initialize({ apiKey: key2 });
28914
+ const call = async (text, opts) => provider.textToSpeech(text, {
28915
+ voice: opts?.voice,
28916
+ speed: opts?.speed
28917
+ });
28918
+ return { provider: "openai", audioExtension: "mp3", call };
28919
+ }
28736
28920
  async function buildKokoro() {
28737
28921
  const provider = new KokoroProvider();
28738
28922
  await provider.initialize({});
@@ -28745,11 +28929,11 @@ async function buildKokoro() {
28745
28929
  }
28746
28930
  function parseTtsProviderName(value) {
28747
28931
  if (!value) return "auto";
28748
- if (value === "auto" || value === "elevenlabs" || value === "kokoro") {
28932
+ if (value === "auto" || value === "elevenlabs" || value === "openai" || value === "kokoro") {
28749
28933
  return value;
28750
28934
  }
28751
28935
  throw new Error(
28752
- `Invalid --tts: ${value}. Valid: auto, elevenlabs, kokoro.`
28936
+ `Invalid --tts: ${value}. Valid: auto, elevenlabs, openai, kokoro.`
28753
28937
  );
28754
28938
  }
28755
28939
  var TtsKeyMissingError;
@@ -28761,7 +28945,7 @@ var init_tts_resolve = __esm({
28761
28945
  TtsKeyMissingError = class extends Error {
28762
28946
  constructor(provider) {
28763
28947
  super(
28764
- 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.`
28948
+ 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.`
28765
28949
  );
28766
28950
  this.provider = provider;
28767
28951
  this.name = "TtsKeyMissingError";
@@ -472016,13 +472200,13 @@ async function createBuildPlan(opts) {
472016
472200
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
472017
472201
  const videoCue = videoPrompt ?? videoReference?.raw;
472018
472202
  const musicCue = musicPrompt ?? musicReference?.raw;
472019
- const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : 0;
472203
+ const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
472020
472204
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
472021
472205
  beatId: beat.id,
472022
472206
  cue: narrationText,
472023
472207
  provider: resolved.narration.resolved,
472024
472208
  voice,
472025
- ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
472209
+ ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
472026
472210
  }) : null;
472027
472211
  const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
472028
472212
  beatId: beat.id,
@@ -472440,17 +472624,20 @@ function providerInput(opts) {
472440
472624
  async function resolveNarrationProvider(input3, projectDir) {
472441
472625
  const requested = input3.value.toLowerCase();
472442
472626
  const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
472443
- const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : "kokoro";
472627
+ const hasOpenAi = Boolean(await getApiKeyFromConfig("openai", { cwd: projectDir }));
472628
+ const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "openai" ? "openai" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : hasOpenAi ? "openai" : "kokoro";
472629
+ const requiresKey = resolved !== "kokoro";
472630
+ const hasKey = resolved === "elevenlabs" ? hasElevenLabs : resolved === "openai" ? hasOpenAi : true;
472444
472631
  return {
472445
472632
  kind: "narration",
472446
472633
  requested: input3.requested,
472447
472634
  resolved,
472448
472635
  source: input3.source,
472449
- requiresKey: resolved === "elevenlabs",
472450
- configured: resolved !== "elevenlabs" || hasElevenLabs,
472451
- ...resolved === "elevenlabs" ? { configKey: "elevenlabs" } : {},
472452
- ...resolved === "elevenlabs" && !hasElevenLabs ? { missingKey: providerEnvVar("elevenlabs") } : {},
472453
- retryWith: resolved === "elevenlabs" && !hasElevenLabs ? ["vibe setup --full"] : []
472636
+ requiresKey,
472637
+ configured: hasKey,
472638
+ ...requiresKey ? { configKey: resolved } : {},
472639
+ ...requiresKey && !hasKey ? { missingKey: providerEnvVar(resolved) } : {},
472640
+ retryWith: requiresKey && !hasKey ? ["vibe setup --full"] : []
472454
472641
  };
472455
472642
  }
472456
472643
  async function keyedProviderResolution(opts, projectDir) {
@@ -472546,7 +472733,7 @@ function loadPlanEnv(projectDir) {
472546
472733
  dir = dirname17(dir);
472547
472734
  }
472548
472735
  }
472549
- var import_dotenv2, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
472736
+ var import_dotenv2, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, OPENAI_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
472550
472737
  var init_build_plan = __esm({
472551
472738
  "../cli/src/commands/_shared/build-plan.ts"() {
472552
472739
  "use strict";
@@ -472564,6 +472751,7 @@ var init_build_plan = __esm({
472564
472751
  VIDEO_COST_USD = 5;
472565
472752
  MUSIC_COST_USD = 0.5;
472566
472753
  ELEVENLABS_NARRATION_COST_USD = 0.05;
472754
+ OPENAI_NARRATION_COST_USD = 0.01;
472567
472755
  COMPOSE_COST_USD = 0.06;
472568
472756
  SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
472569
472757
  }
@@ -486565,7 +486753,7 @@ function pickOutputPath(data) {
486565
486753
  }
486566
486754
 
486567
486755
  // ../cli/src/tools/_shared/elicit.ts
486568
- var NARRATION_CHOICES = ["kokoro", "elevenlabs"];
486756
+ var NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
486569
486757
  var BACKDROP_CHOICES = ["skip", "openai"];
486570
486758
  function planBuildElicitation(args) {
486571
486759
  const assetsWillRun = args.stage === void 0 || args.stage === "all" || args.stage === "assets";
@@ -486580,8 +486768,9 @@ function planBuildElicitation(args) {
486580
486768
  description: "Which TTS engine narrates the video?",
486581
486769
  enum: [...NARRATION_CHOICES],
486582
486770
  enumNames: [
486583
- "Kokoro \u2014 free, runs locally on this machine",
486584
- "ElevenLabs \u2014 cloud TTS, uses your ElevenLabs credits"
486771
+ "Kokoro \u2014 free, runs on this machine (first use downloads a ~90 MB voice model; slower)",
486772
+ "OpenAI \u2014 fast cloud voice, uses your OpenAI API key (~$0.02 per video)",
486773
+ "ElevenLabs \u2014 premium cloud voices, uses your ElevenLabs credits"
486585
486774
  ]
486586
486775
  };
486587
486776
  required.push("narration");
@@ -487786,9 +487975,9 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487786
487975
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
487787
487976
  ).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(
487788
487977
  "--tts <provider>",
487789
- "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)",
487978
+ "TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
487790
487979
  "auto"
487791
- ).option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option(
487980
+ ).option("--voice <id>", "Voice id (ElevenLabs name/id, OpenAI voice like marin, or Kokoro id like af_heart)").option(
487792
487981
  "--no-audio",
487793
487982
  "Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
487794
487983
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
@@ -488049,7 +488238,7 @@ async function executeSceneAdd(opts) {
488049
488238
  throw error;
488050
488239
  }
488051
488240
  opts.onProgress?.(
488052
- resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads ~330MB)..." : "Generating narration with ElevenLabs..."
488241
+ 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..."
488053
488242
  );
488054
488243
  const tts = await resolution.call(narrationText, {
488055
488244
  voice: opts.voice,
@@ -488809,7 +488998,9 @@ var sceneBuildSchema = z2.object({
488809
488998
  skipVideo: z2.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
488810
488999
  skipMusic: z2.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
488811
489000
  skipRender: z2.boolean().optional().describe("Stop after compose \u2014 produces compositions/*.html but no final MP4."),
488812
- ttsProvider: z2.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override. Default 'auto'."),
489001
+ ttsProvider: z2.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe(
489002
+ "TTS provider override. Default 'auto' (elevenlabs key > openai key > local kokoro)."
489003
+ ),
488813
489004
  voice: z2.string().optional().describe("TTS voice id (provider-specific)."),
488814
489005
  imageProvider: z2.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
488815
489006
  videoProvider: z2.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
@@ -492652,7 +492843,7 @@ var planTool = defineTool({
492652
492843
  skipBackdrop: z7.boolean().optional().describe("Don't include backdrop image generation in the plan."),
492653
492844
  skipVideo: z7.boolean().optional().describe("Don't include video generation in the plan."),
492654
492845
  skipMusic: z7.boolean().optional().describe("Don't include music generation in the plan."),
492655
- ttsProvider: z7.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override."),
492846
+ ttsProvider: z7.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe("TTS provider override."),
492656
492847
  voice: z7.string().optional().describe("Voice id."),
492657
492848
  imageProvider: z7.string().optional().describe("Image provider override."),
492658
492849
  imageQuality: z7.enum(["standard", "hd"]).optional().describe("Image quality override."),
@@ -498289,7 +498480,7 @@ function buildServerInstructions(cwd = resolveServerWorkspaceRoot()) {
498289
498480
  "When creating projects with init, use a project name or workspace-relative path under the workspace root.",
498290
498481
  "Do not create projects in /tmp, /home/claude, /workspace, or other synthetic roots unless the user explicitly asks.",
498291
498482
  "Before high or very-high cost provider work, run a dry-run or plan when available and ask the user to confirm provider spend.",
498292
- "When the user's request leaves them unspecified, ask the user before building instead of silently picking defaults: narration provider (kokoro = free local, elevenlabs = cloud credits), backdrop image generation (paid; skipping is free), and visual style (list options via scene_list_styles). Present the choices and wait for the answer.",
498483
+ "When the user's request leaves them unspecified, ask the user before building instead of silently picking defaults: narration provider (kokoro = free, runs locally, first use downloads a ~90 MB voice model and synthesis is slower; openai = fast cloud voice using the OpenAI API key, roughly $0.02 per video; elevenlabs = premium cloud voices using ElevenLabs credits), backdrop image generation (paid; skipping is free), and visual style (list options via scene_list_styles). Present the choices and wait for the answer.",
498293
498484
  "Narration and backdrop choices are independent \u2014 kokoro narration with OpenAI backdrops is a common combination. Pass each answer explicitly to build (ttsProvider plus skipBackdrop/imageProvider); never let one choice imply the other."
498294
498485
  ].join("\n");
498295
498486
  }
@@ -498316,7 +498507,7 @@ var server2 = new Server(
498316
498507
  {
498317
498508
  name: "vibeframe",
498318
498509
  // Stamped with the package version by build.js at bundle time.
498319
- version: "0.112.1"
498510
+ version: "0.113.0"
498320
498511
  },
498321
498512
  {
498322
498513
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.112.1",
3
+ "version": "0.113.0",
4
4
  "description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -60,8 +60,8 @@
60
60
  "tsx": "^4.21.0",
61
61
  "typescript": "^5.3.3",
62
62
  "vitest": "^1.2.2",
63
- "@vibeframe/cli": "0.112.1",
64
- "@vibeframe/core": "0.112.1"
63
+ "@vibeframe/cli": "0.113.0",
64
+ "@vibeframe/core": "0.113.0"
65
65
  },
66
66
  "engines": {
67
67
  "node": ">=20"