@vibeframe/mcp-server 0.112.0 → 0.113.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -6
  2. package/dist/index.js +248 -52
  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";
@@ -471912,6 +472096,9 @@ var init_storyboard_edit = __esm({
471912
472096
  import { existsSync as existsSync33 } from "node:fs";
471913
472097
  import { readFile as readFile17 } from "node:fs/promises";
471914
472098
  import { dirname as dirname17, join as join31, resolve as resolve29 } from "node:path";
472099
+ function backdropCostUsd(quality) {
472100
+ return quality === "hd" ? 0.2 : 0.05;
472101
+ }
471915
472102
  async function createBuildPlan(opts) {
471916
472103
  const projectDir = resolve29(opts.projectDir);
471917
472104
  loadPlanEnv(projectDir);
@@ -472013,13 +472200,13 @@ async function createBuildPlan(opts) {
472013
472200
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
472014
472201
  const videoCue = videoPrompt ?? videoReference?.raw;
472015
472202
  const musicCue = musicPrompt ?? musicReference?.raw;
472016
- 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;
472017
472204
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
472018
472205
  beatId: beat.id,
472019
472206
  cue: narrationText,
472020
472207
  provider: resolved.narration.resolved,
472021
472208
  voice,
472022
- ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
472209
+ ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
472023
472210
  }) : null;
472024
472211
  const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
472025
472212
  beatId: beat.id,
@@ -472067,7 +472254,7 @@ async function createBuildPlan(opts) {
472067
472254
  reference: backdropReference,
472068
472255
  projectDir,
472069
472256
  force: opts.force,
472070
- cost: BACKDROP_COST_USD,
472257
+ cost: backdropCostUsd(imageQuality),
472071
472258
  active: includeAssets
472072
472259
  }) : null;
472073
472260
  const video = videoCue && !opts.skipVideo ? assetPlan({
@@ -472437,17 +472624,20 @@ function providerInput(opts) {
472437
472624
  async function resolveNarrationProvider(input3, projectDir) {
472438
472625
  const requested = input3.value.toLowerCase();
472439
472626
  const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
472440
- 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;
472441
472631
  return {
472442
472632
  kind: "narration",
472443
472633
  requested: input3.requested,
472444
472634
  resolved,
472445
472635
  source: input3.source,
472446
- requiresKey: resolved === "elevenlabs",
472447
- configured: resolved !== "elevenlabs" || hasElevenLabs,
472448
- ...resolved === "elevenlabs" ? { configKey: "elevenlabs" } : {},
472449
- ...resolved === "elevenlabs" && !hasElevenLabs ? { missingKey: providerEnvVar("elevenlabs") } : {},
472450
- 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"] : []
472451
472641
  };
472452
472642
  }
472453
472643
  async function keyedProviderResolution(opts, projectDir) {
@@ -472543,7 +472733,7 @@ function loadPlanEnv(projectDir) {
472543
472733
  dir = dirname17(dir);
472544
472734
  }
472545
472735
  }
472546
- var import_dotenv2, BACKDROP_COST_USD, 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;
472547
472737
  var init_build_plan = __esm({
472548
472738
  "../cli/src/commands/_shared/build-plan.ts"() {
472549
472739
  "use strict";
@@ -472558,10 +472748,10 @@ var init_build_plan = __esm({
472558
472748
  init_storyboard_parse();
472559
472749
  init_project_config();
472560
472750
  init_storyboard_edit();
472561
- BACKDROP_COST_USD = 3;
472562
472751
  VIDEO_COST_USD = 5;
472563
472752
  MUSIC_COST_USD = 0.5;
472564
472753
  ELEVENLABS_NARRATION_COST_USD = 0.05;
472754
+ OPENAI_NARRATION_COST_USD = 0.01;
472565
472755
  COMPOSE_COST_USD = 0.06;
472566
472756
  SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
472567
472757
  }
@@ -473079,7 +473269,10 @@ async function executeSceneBuild(opts) {
473079
473269
  (beat) => beat.narrationStatus === "failed" || beat.backdropStatus === "failed" || beat.videoStatus === "failed" || beat.musicStatus === "failed"
473080
473270
  );
473081
473271
  stageReports.assets.status = assetFailed ? "failed" : pendingJobs.length > 0 ? "pending-jobs" : "done";
473082
- stageReports.assets.costUsd = estimateActualAssetCost(beatOutcomes);
473272
+ stageReports.assets.costUsd = estimateActualAssetCost(
473273
+ beatOutcomes,
473274
+ opts.imageQuality ?? "hd"
473275
+ );
473083
473276
  stageReports.assets.retryWith = pendingJobs.map(
473084
473277
  (job) => `vibe status job ${job.id} --project ${projectDir} --json`
473085
473278
  );
@@ -474262,11 +474455,11 @@ function shouldRunStage(selected, stage) {
474262
474455
  if (selected === "all") return true;
474263
474456
  return selected === stage;
474264
474457
  }
474265
- function estimateActualAssetCost(outcomes) {
474458
+ function estimateActualAssetCost(outcomes, imageQuality) {
474266
474459
  let cost = 0;
474267
474460
  for (const outcome of outcomes) {
474268
474461
  if (outcome.narrationStatus === "generated") cost += 0.05;
474269
- if (outcome.backdropStatus === "generated") cost += 3;
474462
+ if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
474270
474463
  if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") cost += 5;
474271
474464
  if (outcome.musicStatus === "generated" || outcome.musicStatus === "pending") cost += 0.5;
474272
474465
  }
@@ -486560,7 +486753,7 @@ function pickOutputPath(data) {
486560
486753
  }
486561
486754
 
486562
486755
  // ../cli/src/tools/_shared/elicit.ts
486563
- var NARRATION_CHOICES = ["kokoro", "elevenlabs"];
486756
+ var NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
486564
486757
  var BACKDROP_CHOICES = ["skip", "openai"];
486565
486758
  function planBuildElicitation(args) {
486566
486759
  const assetsWillRun = args.stage === void 0 || args.stage === "all" || args.stage === "assets";
@@ -486575,8 +486768,9 @@ function planBuildElicitation(args) {
486575
486768
  description: "Which TTS engine narrates the video?",
486576
486769
  enum: [...NARRATION_CHOICES],
486577
486770
  enumNames: [
486578
- "Kokoro \u2014 free, runs locally on this machine",
486579
- "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"
486580
486774
  ]
486581
486775
  };
486582
486776
  required.push("narration");
@@ -487781,9 +487975,9 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487781
487975
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
487782
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(
487783
487977
  "--tts <provider>",
487784
- "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)",
487785
487979
  "auto"
487786
- ).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(
487787
487981
  "--no-audio",
487788
487982
  "Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
487789
487983
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
@@ -488044,7 +488238,7 @@ async function executeSceneAdd(opts) {
488044
488238
  throw error;
488045
488239
  }
488046
488240
  opts.onProgress?.(
488047
- 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..."
488048
488242
  );
488049
488243
  const tts = await resolution.call(narrationText, {
488050
488244
  voice: opts.voice,
@@ -488804,7 +488998,9 @@ var sceneBuildSchema = z2.object({
488804
488998
  skipVideo: z2.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
488805
488999
  skipMusic: z2.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
488806
489000
  skipRender: z2.boolean().optional().describe("Stop after compose \u2014 produces compositions/*.html but no final MP4."),
488807
- 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
+ ),
488808
489004
  voice: z2.string().optional().describe("TTS voice id (provider-specific)."),
488809
489005
  imageProvider: z2.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
488810
489006
  videoProvider: z2.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
@@ -492647,7 +492843,7 @@ var planTool = defineTool({
492647
492843
  skipBackdrop: z7.boolean().optional().describe("Don't include backdrop image generation in the plan."),
492648
492844
  skipVideo: z7.boolean().optional().describe("Don't include video generation in the plan."),
492649
492845
  skipMusic: z7.boolean().optional().describe("Don't include music generation in the plan."),
492650
- ttsProvider: z7.enum(["auto", "elevenlabs", "kokoro"]).optional().describe("TTS provider override."),
492846
+ ttsProvider: z7.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe("TTS provider override."),
492651
492847
  voice: z7.string().optional().describe("Voice id."),
492652
492848
  imageProvider: z7.string().optional().describe("Image provider override."),
492653
492849
  imageQuality: z7.enum(["standard", "hd"]).optional().describe("Image quality override."),
@@ -498284,7 +498480,7 @@ function buildServerInstructions(cwd = resolveServerWorkspaceRoot()) {
498284
498480
  "When creating projects with init, use a project name or workspace-relative path under the workspace root.",
498285
498481
  "Do not create projects in /tmp, /home/claude, /workspace, or other synthetic roots unless the user explicitly asks.",
498286
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.",
498287
- "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.",
498288
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."
498289
498485
  ].join("\n");
498290
498486
  }
@@ -498311,7 +498507,7 @@ var server2 = new Server(
498311
498507
  {
498312
498508
  name: "vibeframe",
498313
498509
  // Stamped with the package version by build.js at bundle time.
498314
- version: "0.112.0"
498510
+ version: "0.113.0"
498315
498511
  },
498316
498512
  {
498317
498513
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.112.0",
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.0",
64
- "@vibeframe/core": "0.112.0"
63
+ "@vibeframe/cli": "0.113.0",
64
+ "@vibeframe/core": "0.113.0"
65
65
  },
66
66
  "engines": {
67
67
  "node": ">=20"