@vibeframe/mcp-server 0.112.1 → 0.113.2

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 +139 -126
  2. package/dist/index.js +1919 -693
  3. package/package.json +3 -3
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,39 +4723,83 @@ 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
  }
4739
4742
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
4740
4743
  try {
4741
4744
  const { readFile: readFile46 } = await import("node:fs/promises");
4742
- const { join: join45, resolve: resolve80 } = await import("node:path");
4745
+ const { join: join44, resolve: resolve80 } = await import("node:path");
4743
4746
  const { pathToFileURL } = await import("node:url");
4744
4747
  const pkgDir = resolve80(baseDir, "node_modules", "kokoro-js");
4745
- const pkg = JSON.parse(await readFile46(join45(pkgDir, "package.json"), "utf-8"));
4748
+ const pkg = JSON.parse(await readFile46(join44(pkgDir, "package.json"), "utf-8"));
4746
4749
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
4747
4750
  if (!entry)
4748
4751
  return null;
4749
- const mod = await import(pathToFileURL(join45(pkgDir, entry)).href);
4752
+ const mod = await import(pathToFileURL(join44(pkgDir, entry)).href);
4750
4753
  return mod.KokoroTTS ?? null;
4751
4754
  } catch {
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: join44, 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(join44(transformersDir, "dist", "transformers.web.js")).href);
4770
+ const cacheRoot = join44(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(join44(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(join44(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"
@@ -10307,6 +10460,19 @@ var require_src = __commonJS({
10307
10460
  });
10308
10461
 
10309
10462
  // ../ai-providers/dist/fal/FalProvider.js
10463
+ function estimateSeedanceVideoCostUsd(opts) {
10464
+ const shortSide = SEEDANCE_SHORT_SIDE[opts.resolution ?? "720p"] ?? 720;
10465
+ const [a2, b2] = (opts.aspectRatio ?? "16:9").split(":").map(Number);
10466
+ const ratioFactor = a2 > 0 && b2 > 0 ? Math.max(a2, b2) / Math.min(a2, b2) : 16 / 9;
10467
+ const longSide = Math.round(shortSide * ratioFactor);
10468
+ const tokensPerSecond = shortSide * longSide * 24 / 1024;
10469
+ let usdPerSecond = tokensPerSecond / 1e3 * SEEDANCE_USD_PER_1K_TOKENS;
10470
+ if (opts.fast && shortSide <= 720)
10471
+ usdPerSecond *= SEEDANCE_FAST_FACTOR;
10472
+ if (opts.hasVideoReference)
10473
+ usdPerSecond *= SEEDANCE_VIDEO_REF_FACTOR;
10474
+ return Number((usdPerSecond * Math.max(0, opts.durationSec)).toFixed(2));
10475
+ }
10310
10476
  function formatFalError(error) {
10311
10477
  if (!(error instanceof Error))
10312
10478
  return String(error);
@@ -10416,7 +10582,7 @@ function normaliseDuration(value) {
10416
10582
  return "auto";
10417
10583
  return Math.max(4, Math.min(15, Math.round(value)));
10418
10584
  }
10419
- var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, FalProvider, falProvider;
10585
+ var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, SEEDANCE_USD_PER_1K_TOKENS, SEEDANCE_FAST_FACTOR, SEEDANCE_VIDEO_REF_FACTOR, SEEDANCE_SHORT_SIDE, FalProvider, falProvider;
10420
10586
  var init_FalProvider = __esm({
10421
10587
  "../ai-providers/dist/fal/FalProvider.js"() {
10422
10588
  "use strict";
@@ -10436,6 +10602,10 @@ var init_FalProvider = __esm({
10436
10602
  DEFAULT_VARIANT = "seedance-2.0";
10437
10603
  VALID_RESOLUTIONS = ["480p", "720p", "1080p"];
10438
10604
  VALID_ASPECTS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"];
10605
+ SEEDANCE_USD_PER_1K_TOKENS = 0.014;
10606
+ SEEDANCE_FAST_FACTOR = 0.8;
10607
+ SEEDANCE_VIDEO_REF_FACTOR = 0.6;
10608
+ SEEDANCE_SHORT_SIDE = { "480p": 480, "720p": 720, "1080p": 1080 };
10439
10609
  FalProvider = class {
10440
10610
  constructor() {
10441
10611
  this.id = "seedance";
@@ -11248,9 +11418,11 @@ __export(dist_exports, {
11248
11418
  KOKORO_MODEL_ID: () => KOKORO_MODEL_ID,
11249
11419
  KlingProvider: () => KlingProvider,
11250
11420
  KokoroProvider: () => KokoroProvider,
11421
+ OPENAI_TTS_VOICES: () => OPENAI_TTS_VOICES,
11251
11422
  OllamaProvider: () => OllamaProvider,
11252
11423
  OpenAIImageProvider: () => OpenAIImageProvider,
11253
11424
  OpenAIProvider: () => OpenAIProvider,
11425
+ OpenAiTtsProvider: () => OpenAiTtsProvider,
11254
11426
  ReplicateProvider: () => ReplicateProvider,
11255
11427
  RunwayProvider: () => RunwayProvider,
11256
11428
  WhisperProvider: () => WhisperProvider,
@@ -11258,6 +11430,7 @@ __export(dist_exports, {
11258
11430
  defineApiKey: () => defineApiKey,
11259
11431
  defineProvider: () => defineProvider,
11260
11432
  elevenLabsProvider: () => elevenLabsProvider,
11433
+ estimateSeedanceVideoCostUsd: () => estimateSeedanceVideoCostUsd,
11261
11434
  falProvider: () => falProvider,
11262
11435
  geminiProvider: () => geminiProvider,
11263
11436
  getAllApiKeys: () => getAllApiKeys,
@@ -11274,9 +11447,12 @@ __export(dist_exports, {
11274
11447
  isGeminiTextModelAlias: () => isGeminiTextModelAlias,
11275
11448
  klingProvider: () => klingProvider,
11276
11449
  kokoroProvider: () => kokoroProvider,
11450
+ loadBundledKokoroRuntime: () => loadBundledKokoroRuntime,
11451
+ loadKokoroFromWorkspace: () => loadKokoroFromWorkspace,
11277
11452
  ollamaProvider: () => ollamaProvider,
11278
11453
  openaiImageProvider: () => openaiImageProvider,
11279
11454
  openaiProvider: () => openaiProvider,
11455
+ openaiTtsProvider: () => openaiTtsProvider,
11280
11456
  providerRegistry: () => providerRegistry,
11281
11457
  replicateProvider: () => replicateProvider,
11282
11458
  resolveGeminiTextModel: () => resolveGeminiTextModel,
@@ -11299,6 +11475,7 @@ var init_dist = __esm({
11299
11475
  init_elevenlabs();
11300
11476
  init_kokoro();
11301
11477
  init_openai_image();
11478
+ init_openai_tts();
11302
11479
  init_runway();
11303
11480
  init_kling();
11304
11481
  init_grok();
@@ -24060,7 +24237,534 @@ var init_build_asset_metadata = __esm({
24060
24237
  }
24061
24238
  });
24062
24239
 
24240
+ // ../cli/src/commands/_shared/review-report.ts
24241
+ import { writeFile as writeFile6 } from "node:fs/promises";
24242
+ import { join as join2, resolve as resolve5 } from "node:path";
24243
+ function statusFromIssues(issues) {
24244
+ if (issues.some((issue) => issue.severity === "error")) return "fail";
24245
+ if (issues.some((issue) => issue.severity === "warning")) return "warn";
24246
+ return "pass";
24247
+ }
24248
+ function scoreIssues(issues) {
24249
+ let score = 100;
24250
+ for (const issue of issues) {
24251
+ if (issue.severity === "error") score -= 25;
24252
+ else if (issue.severity === "warning") score -= 8;
24253
+ else score -= 2;
24254
+ }
24255
+ return Math.max(0, Math.min(100, score));
24256
+ }
24257
+ function uniqueRetryWith(items) {
24258
+ return [
24259
+ ...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
24260
+ ];
24261
+ }
24262
+ function commandReviewAction(command3, opts) {
24263
+ const classified = classifyReviewCommand(command3);
24264
+ return stripUndefined({
24265
+ id: actionId("command", command3),
24266
+ kind: "command",
24267
+ label: opts.label ?? command3,
24268
+ command: command3,
24269
+ fixOwner: opts.fixOwner ?? classified.fixOwner,
24270
+ costTier: opts.costTier ?? classified.costTier,
24271
+ safeToAutoRun: opts.safeToAutoRun ?? classified.safeToAutoRun,
24272
+ requiresConfirmation: opts.requiresConfirmation ?? classified.requiresConfirmation,
24273
+ reason: opts.reason,
24274
+ sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
24275
+ });
24276
+ }
24277
+ function agentReviewAction(opts) {
24278
+ return stripUndefined({
24279
+ id: actionId("agent", opts.agentPrompt),
24280
+ kind: "agent",
24281
+ label: opts.label,
24282
+ agentPrompt: opts.agentPrompt,
24283
+ fixOwner: "host-agent",
24284
+ costTier: "unknown",
24285
+ safeToAutoRun: false,
24286
+ requiresConfirmation: false,
24287
+ reason: opts.reason,
24288
+ sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
24289
+ });
24290
+ }
24291
+ function manualReviewAction(opts) {
24292
+ return stripUndefined({
24293
+ id: actionId("manual", opts.label),
24294
+ kind: "manual",
24295
+ label: opts.label,
24296
+ fixOwner: opts.fixOwner ?? "host-agent",
24297
+ costTier: "unknown",
24298
+ safeToAutoRun: false,
24299
+ requiresConfirmation: false,
24300
+ reason: opts.reason,
24301
+ sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
24302
+ });
24303
+ }
24304
+ function reviewActionsFromRetryWith(retryWith, sourceIssueCodes = []) {
24305
+ return normalizeReviewActions(
24306
+ retryWith.map((item) => {
24307
+ if (/^codex\s+/i.test(item)) {
24308
+ return agentReviewAction({
24309
+ label: "Ask the host agent to fix review issues",
24310
+ agentPrompt: item,
24311
+ reason: "The review report contains host-agent-owned issues.",
24312
+ sourceIssueCodes
24313
+ });
24314
+ }
24315
+ return commandReviewAction(item, {
24316
+ reason: "Backward-compatible retryWith command.",
24317
+ sourceIssueCodes
24318
+ });
24319
+ })
24320
+ );
24321
+ }
24322
+ function normalizeReviewActions(actions) {
24323
+ const merged = /* @__PURE__ */ new Map();
24324
+ for (const action of actions) {
24325
+ const key2 = [action.kind, action.command ?? action.agentPrompt ?? action.label].join("\0");
24326
+ const existing = merged.get(key2);
24327
+ if (!existing) {
24328
+ merged.set(key2, {
24329
+ ...action,
24330
+ sourceIssueCodes: uniqueStrings(action.sourceIssueCodes ?? [])
24331
+ });
24332
+ continue;
24333
+ }
24334
+ merged.set(key2, {
24335
+ ...existing,
24336
+ safeToAutoRun: existing.safeToAutoRun && action.safeToAutoRun,
24337
+ requiresConfirmation: existing.requiresConfirmation || action.requiresConfirmation,
24338
+ sourceIssueCodes: uniqueStrings([
24339
+ ...existing.sourceIssueCodes ?? [],
24340
+ ...action.sourceIssueCodes ?? []
24341
+ ])
24342
+ });
24343
+ }
24344
+ return [...merged.values()].map(
24345
+ (action) => stripUndefined({
24346
+ ...action,
24347
+ sourceIssueCodes: action.sourceIssueCodes && action.sourceIssueCodes.length > 0 ? action.sourceIssueCodes : void 0
24348
+ })
24349
+ );
24350
+ }
24351
+ function deriveNextReviewActions(opts) {
24352
+ return normalizeReviewActions([
24353
+ ...opts.issues.flatMap((issue) => issue.actions ?? []),
24354
+ ...reviewActionsFromRetryWith(opts.retryWith)
24355
+ ]);
24356
+ }
24357
+ function fixOwnerForIssue(issue) {
24358
+ if (issue.fixOwner) return issue.fixOwner;
24359
+ if (issue.code.startsWith("AI_REVIEW_")) return "host-agent";
24360
+ if (issue.code.startsWith("STORYBOARD_")) return "host-agent";
24361
+ if (issue.code === "PROJECT_NOT_FOUND" || issue.code === "MISSING_STORYBOARD" || issue.code === "BEAT_NOT_FOUND" || issue.code === "MISSING_DESIGN") {
24362
+ return "host-agent";
24363
+ }
24364
+ return "vibe";
24365
+ }
24366
+ function normalizeReviewIssues(issues, opts) {
24367
+ const fallbackOwner = typeof opts === "string" ? opts : opts?.fallbackOwner;
24368
+ const projectDir = typeof opts === "string" ? void 0 : opts?.projectDir;
24369
+ return issues.map((issue) => {
24370
+ const normalized = {
24371
+ ...issue,
24372
+ fixOwner: issue.fixOwner ?? fallbackOwner ?? fixOwnerForIssue(issue)
24373
+ };
24374
+ const actions = normalizeReviewActions([
24375
+ ...issue.actions ?? [],
24376
+ ...projectDir ? defaultActionsForIssue(normalized, projectDir) : []
24377
+ ]);
24378
+ return stripUndefined({
24379
+ ...normalized,
24380
+ actions: actions.length > 0 ? actions : void 0
24381
+ });
24382
+ });
24383
+ }
24384
+ function summarizeReviewIssues(issues) {
24385
+ const normalized = normalizeReviewIssues(issues);
24386
+ return {
24387
+ issueCount: normalized.length,
24388
+ errorCount: normalized.filter((issue) => issue.severity === "error").length,
24389
+ warningCount: normalized.filter((issue) => issue.severity === "warning").length,
24390
+ infoCount: normalized.filter((issue) => issue.severity === "info").length,
24391
+ fixOwners: {
24392
+ vibe: normalized.filter((issue) => issue.fixOwner === "vibe").length,
24393
+ hostAgent: normalized.filter((issue) => issue.fixOwner === "host-agent").length
24394
+ }
24395
+ };
24396
+ }
24397
+ function buildReviewReport(opts) {
24398
+ const retryWith = uniqueRetryWith(opts.retryWith);
24399
+ const issues = normalizeReviewIssues(opts.issues, {
24400
+ projectDir: opts.project,
24401
+ retryWith
24402
+ });
24403
+ return stripUndefined({
24404
+ schemaVersion: "1",
24405
+ kind: "review",
24406
+ project: resolve5(opts.project),
24407
+ mode: opts.mode,
24408
+ beat: opts.beat,
24409
+ status: opts.status,
24410
+ score: opts.score,
24411
+ issues,
24412
+ summary: summarizeReviewIssues(issues),
24413
+ nextActions: deriveNextReviewActions({ issues, retryWith, projectDir: opts.project }),
24414
+ retryWith,
24415
+ sourceReports: opts.sourceReports ?? [],
24416
+ reportPath: opts.reportPath
24417
+ });
24418
+ }
24419
+ function defaultReviewReportPath(projectDir) {
24420
+ return join2(resolve5(projectDir), "review-report.json");
24421
+ }
24422
+ async function writeReviewReport(path14, report) {
24423
+ await writeFile6(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
24424
+ }
24425
+ function stripUndefined(value) {
24426
+ return Object.fromEntries(Object.entries(value).filter(([, v2]) => v2 !== void 0));
24427
+ }
24428
+ function defaultActionsForIssue(issue, projectDir) {
24429
+ const code = issue.code;
24430
+ const beat = issue.beatId ?? issue.scene;
24431
+ const beatFlag = beat ? ` --beat ${beat}` : "";
24432
+ const sourceIssueCodes = [code];
24433
+ if (code === "PROJECT_NOT_FOUND" || code === "MISSING_STORYBOARD") {
24434
+ return [
24435
+ commandReviewAction(`vibe init ${projectDir} --from "<brief>" --json`, {
24436
+ label: "Initialize the project from a brief",
24437
+ fixOwner: "host-agent",
24438
+ reason: "The project cannot be inspected until the core project files exist.",
24439
+ sourceIssueCodes
24440
+ })
24441
+ ];
24442
+ }
24443
+ if (code === "MISSING_DESIGN" || code === "DESIGN_PLACEHOLDER_FIELD") {
24444
+ return [
24445
+ manualReviewAction({
24446
+ label: "Create or complete DESIGN.md",
24447
+ reason: "Visual direction needs host-agent or human input before VibeFrame can repair it.",
24448
+ sourceIssueCodes
24449
+ })
24450
+ ];
24451
+ }
24452
+ if (code === "BEAT_NOT_FOUND" || code.startsWith("STORYBOARD_")) {
24453
+ const actions = [
24454
+ commandReviewAction(`vibe storyboard validate ${projectDir} --json`, {
24455
+ label: "Validate the storyboard",
24456
+ fixOwner: "host-agent",
24457
+ reason: "The storyboard issue needs structured validation before editing.",
24458
+ sourceIssueCodes
24459
+ })
24460
+ ];
24461
+ if (code === "STORYBOARD_PLACEHOLDER_CUE") {
24462
+ actions.push(
24463
+ commandReviewAction(
24464
+ `vibe storyboard revise ${projectDir} --from "<make cues concrete>" --dry-run --json`,
24465
+ {
24466
+ label: "Preview a storyboard revision",
24467
+ fixOwner: "host-agent",
24468
+ reason: "Placeholder cues should be revised into concrete video intent.",
24469
+ sourceIssueCodes
24470
+ }
24471
+ )
24472
+ );
24473
+ }
24474
+ return actions;
24475
+ }
24476
+ if (code === "MISSING_ROOT_COMPOSITION" || code === "ROOT_SYNC_CHECK_FAILED") {
24477
+ return [
24478
+ commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
24479
+ label: "Sync the project root composition",
24480
+ reason: "The root composition is missing or could not be verified.",
24481
+ sourceIssueCodes
24482
+ })
24483
+ ];
24484
+ }
24485
+ if (code.startsWith("ROOT_")) {
24486
+ return issue.fixOwner === "host-agent" ? [
24487
+ agentReviewAction({
24488
+ label: "Repair the root composition shell",
24489
+ agentPrompt: "Fix host-agent-owned root composition issues from review-report.json",
24490
+ reason: "The root composition cannot be repaired deterministically.",
24491
+ sourceIssueCodes
24492
+ })
24493
+ ] : [
24494
+ commandReviewAction(`vibe scene repair ${projectDir} --json`, {
24495
+ label: "Repair deterministic scene/root issues",
24496
+ reason: "The root composition has deterministic sync issues.",
24497
+ sourceIssueCodes
24498
+ }),
24499
+ commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
24500
+ label: "Resync root composition after repair",
24501
+ reason: "The root composition should be synced after deterministic repair.",
24502
+ sourceIssueCodes
24503
+ })
24504
+ ];
24505
+ }
24506
+ if (code === "MISSING_COMPOSITION") {
24507
+ return [
24508
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage compose --json`, {
24509
+ label: beat ? `Compose beat ${beat}` : "Compose missing scenes",
24510
+ reason: "Missing scene compositions must be generated or authored before render.",
24511
+ sourceIssueCodes
24512
+ })
24513
+ ];
24514
+ }
24515
+ if (code === "MISSING_BUILD_REPORT") {
24516
+ return [
24517
+ commandReviewAction(`vibe build ${projectDir} --dry-run --json`, {
24518
+ label: "Preview the build plan",
24519
+ reason: "A dry-run build report is needed before choosing a paid or mutating build step.",
24520
+ sourceIssueCodes
24521
+ })
24522
+ ];
24523
+ }
24524
+ if (code === "BUILD_REPORT_BEAT_MISSING") {
24525
+ return [
24526
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
24527
+ label: beat ? `Sync build report for beat ${beat}` : "Sync the build report",
24528
+ reason: "The build report does not include the inspected beat.",
24529
+ sourceIssueCodes
24530
+ })
24531
+ ];
24532
+ }
24533
+ if (code === "MALFORMED_BUILD_REPORT") {
24534
+ return [
24535
+ commandReviewAction(`vibe build ${projectDir} --json`, {
24536
+ label: "Regenerate the build report",
24537
+ reason: "The existing build report cannot be parsed.",
24538
+ sourceIssueCodes
24539
+ })
24540
+ ];
24541
+ }
24542
+ if (code === "MISSING_REPORTED_ASSET" || code === "STALE_ASSET" || code === "UNKNOWN_ASSET_FRESHNESS") {
24543
+ return [
24544
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage assets --force --json`, {
24545
+ label: beat ? `Regenerate assets for beat ${beat}` : "Regenerate stale or missing assets",
24546
+ reason: "Asset generation may call providers, so the host should confirm first.",
24547
+ sourceIssueCodes
24548
+ })
24549
+ ];
24550
+ }
24551
+ if (code === "MUSIC_CUE_NOT_READY") {
24552
+ return [
24553
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage assets --json`, {
24554
+ label: beat ? `Generate music for beat ${beat}` : "Generate missing music assets",
24555
+ reason: "Music generation may call providers, so the host should confirm first.",
24556
+ sourceIssueCodes
24557
+ }),
24558
+ commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
24559
+ label: "Sync music into the root composition",
24560
+ reason: "Generated music needs root composition wiring.",
24561
+ sourceIssueCodes
24562
+ })
24563
+ ];
24564
+ }
24565
+ if (code.startsWith("SCENE_LINT_")) {
24566
+ return [
24567
+ commandReviewAction(`vibe scene repair ${projectDir} --json`, {
24568
+ label: "Repair deterministic scene lint issues",
24569
+ reason: "Scene lint reported an issue VibeFrame may be able to repair mechanically.",
24570
+ sourceIssueCodes
24571
+ })
24572
+ ];
24573
+ }
24574
+ if (code === "RENDER_NOT_FOUND" || code === "EMPTY_RENDER") {
24575
+ return [
24576
+ commandReviewAction(
24577
+ beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
24578
+ {
24579
+ label: beat ? `Render beat ${beat}` : "Render the project",
24580
+ reason: "A render file is required before render inspection can pass.",
24581
+ sourceIssueCodes
24582
+ }
24583
+ )
24584
+ ];
24585
+ }
24586
+ if (code === "NO_AUDIO_STREAM" || code === "DURATION_DRIFT") {
24587
+ return [
24588
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
24589
+ label: beat ? `Sync timing/audio for beat ${beat}` : "Sync timing and audio wiring",
24590
+ reason: "The render output no longer matches synced project state.",
24591
+ sourceIssueCodes
24592
+ }),
24593
+ commandReviewAction(
24594
+ beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
24595
+ {
24596
+ label: beat ? `Rerender beat ${beat}` : "Rerender the project",
24597
+ reason: "The render should be regenerated after sync repair.",
24598
+ sourceIssueCodes
24599
+ }
24600
+ )
24601
+ ];
24602
+ }
24603
+ if (code === "BLACK_FRAME_SEGMENT") {
24604
+ return issue.fixOwner === "host-agent" ? [
24605
+ agentReviewAction({
24606
+ label: beat ? `Fix black frames in beat ${beat}` : "Fix black frame segment",
24607
+ agentPrompt: "Fix black-frame issues from review-report.json, then rerender.",
24608
+ reason: "The issue likely needs scene, storyboard, or visual cue changes.",
24609
+ sourceIssueCodes
24610
+ })
24611
+ ] : [
24612
+ commandReviewAction(`vibe scene repair ${projectDir} --json`, {
24613
+ label: "Repair deterministic scene timing",
24614
+ reason: "Black frames outside a specific beat may be recoverable with scene repair.",
24615
+ sourceIssueCodes
24616
+ })
24617
+ ];
24618
+ }
24619
+ if (code === "STATIC_FRAME_SEGMENT") {
24620
+ return [
24621
+ agentReviewAction({
24622
+ label: beat ? `Add motion to beat ${beat}` : "Fix static visual hold",
24623
+ agentPrompt: "Fix static-frame issues from review-report.json, then rerender.",
24624
+ reason: "Static segments usually need creative scene, storyboard, or asset changes.",
24625
+ sourceIssueCodes
24626
+ })
24627
+ ];
24628
+ }
24629
+ if (code === "LONG_SILENCE") {
24630
+ return issue.fixOwner === "host-agent" ? [
24631
+ agentReviewAction({
24632
+ label: beat ? `Fix long silence in beat ${beat}` : "Fix long silence",
24633
+ agentPrompt: "Fix long-silence issues from review-report.json, then rerender.",
24634
+ reason: "The beat duration, narration, or music cue needs host-agent judgment.",
24635
+ sourceIssueCodes
24636
+ })
24637
+ ] : [
24638
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
24639
+ label: beat ? `Sync audio for beat ${beat}` : "Sync audio wiring",
24640
+ reason: "The silence may be caused by stale narration or music wiring.",
24641
+ sourceIssueCodes
24642
+ })
24643
+ ];
24644
+ }
24645
+ if (code.startsWith("AI_REVIEW_")) {
24646
+ return [
24647
+ agentReviewAction({
24648
+ label: "Fix AI review findings",
24649
+ agentPrompt: "Fix host-agent-owned AI review issues from review-report.json, then rerender.",
24650
+ reason: "AI review findings require storyboard, design, or composition judgment.",
24651
+ sourceIssueCodes
24652
+ })
24653
+ ];
24654
+ }
24655
+ if (code === "FFPROBE_UNAVAILABLE" || code === "FFMPEG_UNAVAILABLE") {
24656
+ return [
24657
+ manualReviewAction({
24658
+ label: "Install FFmpeg and rerun inspection",
24659
+ reason: "Local media inspection depends on ffmpeg/ffprobe being available.",
24660
+ fixOwner: "host-agent",
24661
+ sourceIssueCodes
24662
+ })
24663
+ ];
24664
+ }
24665
+ if (code === "FFPROBE_FAILED" || code === "NO_VIDEO_STREAM") {
24666
+ return [
24667
+ commandReviewAction(
24668
+ beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
24669
+ {
24670
+ label: beat ? `Rerender beat ${beat}` : "Rerender the project",
24671
+ reason: "The rendered media could not be probed as a valid video.",
24672
+ sourceIssueCodes
24673
+ }
24674
+ )
24675
+ ];
24676
+ }
24677
+ if (code === "ASPECT_MISMATCH") {
24678
+ return [
24679
+ manualReviewAction({
24680
+ label: "Check project aspect and render settings",
24681
+ reason: "Aspect mismatches can come from either config intent or render settings.",
24682
+ fixOwner: issue.fixOwner,
24683
+ sourceIssueCodes
24684
+ })
24685
+ ];
24686
+ }
24687
+ return [];
24688
+ }
24689
+ function classifyReviewCommand(command3) {
24690
+ const lower = command3.trim().toLowerCase();
24691
+ if (lower.startsWith("vibe status ")) return safe("free");
24692
+ if (lower.startsWith("vibe scene repair")) return safe("free");
24693
+ if (lower.startsWith("vibe storyboard validate") || lower.startsWith("vibe storyboard get") || lower.startsWith("vibe storyboard list")) {
24694
+ return safe("free", "host-agent");
24695
+ }
24696
+ if (lower.startsWith("vibe inspect project")) return safe("free");
24697
+ if (lower.startsWith("vibe inspect render")) {
24698
+ if (lower.includes("--ai")) return unsafe("low");
24699
+ return safe("free");
24700
+ }
24701
+ if (lower.startsWith("vibe render ")) return safe("free");
24702
+ if (lower.startsWith("vibe build ")) {
24703
+ if (lower.includes("--dry-run")) return safe("free");
24704
+ if (lower.includes("--stage sync") || lower.includes("--stage render")) return safe("free");
24705
+ return unsafe("unknown");
24706
+ }
24707
+ if (lower.startsWith("vibe generate video") || lower.startsWith("vibe edit fill-gaps") || lower.startsWith("vibe remix ")) {
24708
+ return unsafe("very-high");
24709
+ }
24710
+ if (lower.startsWith("vibe storyboard revise")) return unsafe("unknown", "host-agent");
24711
+ return unsafe("unknown");
24712
+ }
24713
+ function safe(costTier, fixOwner = "vibe") {
24714
+ return { fixOwner, costTier, safeToAutoRun: true, requiresConfirmation: false };
24715
+ }
24716
+ function unsafe(costTier, fixOwner = "vibe") {
24717
+ return { fixOwner, costTier, safeToAutoRun: false, requiresConfirmation: true };
24718
+ }
24719
+ function actionId(kind3, value) {
24720
+ const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
24721
+ return `${kind3}:${slug || "action"}`;
24722
+ }
24723
+ function uniqueStrings(items) {
24724
+ return [
24725
+ ...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
24726
+ ];
24727
+ }
24728
+ var init_review_report = __esm({
24729
+ "../cli/src/commands/_shared/review-report.ts"() {
24730
+ "use strict";
24731
+ }
24732
+ });
24733
+
24063
24734
  // ../cli/src/commands/_shared/storyboard-parse.ts
24735
+ function resolveCharacters(frontmatter) {
24736
+ const pool = frontmatter?.characters;
24737
+ if (!pool || typeof pool !== "object") return [];
24738
+ const resolved = [];
24739
+ for (const [name, spec] of Object.entries(pool)) {
24740
+ if (typeof spec === "string") {
24741
+ const prompt3 = spec.trim();
24742
+ if (prompt3) resolved.push({ name, prompt: prompt3 });
24743
+ continue;
24744
+ }
24745
+ if (spec && typeof spec === "object") {
24746
+ const image = typeof spec.image === "string" ? spec.image.trim() : "";
24747
+ if (image) {
24748
+ resolved.push({ name, imagePath: image });
24749
+ continue;
24750
+ }
24751
+ const description = typeof spec.description === "string" ? spec.description.trim() : "";
24752
+ if (description) resolved.push({ name, prompt: description });
24753
+ }
24754
+ }
24755
+ return resolved;
24756
+ }
24757
+ function beatCharacterNames(cues) {
24758
+ const raw2 = cues?.characters;
24759
+ if (typeof raw2 === "string") {
24760
+ const name = raw2.trim();
24761
+ return name ? [name] : [];
24762
+ }
24763
+ if (Array.isArray(raw2)) {
24764
+ return raw2.filter((v2) => typeof v2 === "string" && v2.trim().length > 0).map((v2) => v2.trim());
24765
+ }
24766
+ return [];
24767
+ }
24064
24768
  function parseStoryboard(md) {
24065
24769
  const normalized = md.replace(/\r\n/g, "\n");
24066
24770
  const { frontmatter, remaining } = extractProjectFrontmatter(normalized);
@@ -24173,28 +24877,28 @@ var init_storyboard_parse = __esm({
24173
24877
  // ../cli/src/commands/_shared/status-jobs.ts
24174
24878
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
24175
24879
  import { existsSync as existsSync2, statSync } from "node:fs";
24176
- import { copyFile, mkdir as mkdir3, readFile as readFile4, readdir, stat, writeFile as writeFile6 } from "node:fs/promises";
24177
- import { dirname as dirname3, join as join2, parse as parse2, relative, resolve as resolve5 } from "node:path";
24880
+ import { copyFile, mkdir as mkdir3, readFile as readFile4, readdir, stat, writeFile as writeFile7 } from "node:fs/promises";
24881
+ import { dirname as dirname3, join as join3, parse as parse2, relative, resolve as resolve6 } from "node:path";
24178
24882
  function isLocalJob(record) {
24179
24883
  return record.jobType === "build" || record.jobType === "render";
24180
24884
  }
24181
24885
  function findProjectRoot(start = process.cwd()) {
24182
- let dir = resolve5(start);
24886
+ let dir = resolve6(start);
24183
24887
  try {
24184
24888
  if (existsSync2(dir) && statSync(dir).isFile()) dir = dirname3(dir);
24185
24889
  } catch {
24186
24890
  }
24187
24891
  for (; ; ) {
24188
- if (existsSync2(join2(dir, "vibe.config.json")) || existsSync2(join2(dir, "STORYBOARD.md"))) {
24892
+ if (existsSync2(join3(dir, "vibe.config.json")) || existsSync2(join3(dir, "STORYBOARD.md"))) {
24189
24893
  return dir;
24190
24894
  }
24191
24895
  const parent = dirname3(dir);
24192
- if (parent === dir) return resolve5(start);
24896
+ if (parent === dir) return resolve6(start);
24193
24897
  dir = parent;
24194
24898
  }
24195
24899
  }
24196
24900
  function jobsDir(projectDir) {
24197
- return join2(resolve5(projectDir), ".vibeframe", "jobs");
24901
+ return join3(resolve6(projectDir), ".vibeframe", "jobs");
24198
24902
  }
24199
24903
  function normalizeJobStatus(status) {
24200
24904
  const value = String(status ?? "").toLowerCase();
@@ -24208,10 +24912,10 @@ function normalizeJobStatus(status) {
24208
24912
  }
24209
24913
  function createJobRecord(opts) {
24210
24914
  const now = opts.now ?? /* @__PURE__ */ new Date();
24211
- const projectDir = resolve5(
24915
+ const projectDir = resolve6(
24212
24916
  opts.projectDir ?? findProjectRoot(opts.workingDirectory ?? process.cwd())
24213
24917
  );
24214
- const workingDirectory = resolve5(opts.workingDirectory ?? process.cwd());
24918
+ const workingDirectory = resolve6(opts.workingDirectory ?? process.cwd());
24215
24919
  const id = opts.id ?? makeJobId(opts.providerTaskId, now);
24216
24920
  const base = {
24217
24921
  schemaVersion: "1",
@@ -24246,7 +24950,7 @@ function createJobRecord(opts) {
24246
24950
  message: opts.message
24247
24951
  };
24248
24952
  base.retryWith = retryWithForJob(base);
24249
- return stripUndefined(base);
24953
+ return stripUndefined2(base);
24250
24954
  }
24251
24955
  async function createAndWriteJobRecord(opts) {
24252
24956
  const record = createJobRecord(opts);
@@ -24256,15 +24960,15 @@ async function createAndWriteJobRecord(opts) {
24256
24960
  async function writeJobRecord(record) {
24257
24961
  const dir = jobsDir(record.projectDir);
24258
24962
  await mkdir3(dir, { recursive: true });
24259
- await writeFile6(
24963
+ await writeFile7(
24260
24964
  jobRecordPath(record.id, record.projectDir),
24261
- JSON.stringify(stripUndefined(record), null, 2) + "\n",
24965
+ JSON.stringify(stripUndefined2(record), null, 2) + "\n",
24262
24966
  "utf-8"
24263
24967
  );
24264
24968
  }
24265
24969
  async function readJobRecord(jobId, projectDir) {
24266
24970
  const id = normalizeJobId(jobId);
24267
- const project = resolve5(projectDir ?? findProjectRoot());
24971
+ const project = resolve6(projectDir ?? findProjectRoot());
24268
24972
  const path14 = jobRecordPath(id, project);
24269
24973
  if (!existsSync2(path14)) return null;
24270
24974
  return parseJobRecord(await readFile4(path14, "utf-8"));
@@ -24277,7 +24981,7 @@ async function listJobRecords(projectDir) {
24277
24981
  for (const entry of entries2) {
24278
24982
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
24279
24983
  try {
24280
- const parsed = parseJobRecord(await readFile4(join2(dir, entry.name), "utf-8"));
24984
+ const parsed = parseJobRecord(await readFile4(join3(dir, entry.name), "utf-8"));
24281
24985
  if (parsed) records.push(parsed);
24282
24986
  } catch {
24283
24987
  }
@@ -24371,9 +25075,9 @@ async function refreshLocalJobRecord(record, opts) {
24371
25075
  const heartbeatAge = record.heartbeatAt ? Date.now() - new Date(record.heartbeatAt).getTime() : Number.POSITIVE_INFINITY;
24372
25076
  const orphaned = !isPidAlive(record.pid) || heartbeatAge > LOCAL_JOB_ORPHAN_AFTER_MS;
24373
25077
  if (orphaned) {
24374
- const hint = `The process that ran this ${record.jobType} job is gone. Check ${join2(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
25078
+ const hint = `The process that ran this ${record.jobType} job is gone. Check ${join3(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
24375
25079
  warnings.push(hint);
24376
- updated = stripUndefined({
25080
+ updated = stripUndefined2({
24377
25081
  ...record,
24378
25082
  status: "unknown",
24379
25083
  error: record.error ?? hint,
@@ -24390,7 +25094,7 @@ async function refreshLocalJobRecord(record, opts) {
24390
25094
  });
24391
25095
  }
24392
25096
  async function inspectProjectStatus(projectDirArg, opts = {}) {
24393
- const project = resolve5(projectDirArg);
25097
+ const project = resolve6(projectDirArg);
24394
25098
  const warnings = [];
24395
25099
  let records = await listJobRecords(project);
24396
25100
  if (opts.refresh) {
@@ -24420,6 +25124,10 @@ async function inspectProjectStatus(projectDirArg, opts = {}) {
24420
25124
  ...review?.retryWith ?? [],
24421
25125
  ...workflow.retryWith
24422
25126
  ];
25127
+ const nextActions = normalizeReviewActions([
25128
+ ...review?.nextActions ?? [],
25129
+ ...reviewActionsFromRetryWith([...build?.retryWith ?? [], ...workflow.retryWith])
25130
+ ]);
24423
25131
  return {
24424
25132
  schemaVersion: "1",
24425
25133
  kind: "project",
@@ -24431,11 +25139,12 @@ async function inspectProjectStatus(projectDirArg, opts = {}) {
24431
25139
  review,
24432
25140
  jobs,
24433
25141
  warnings,
25142
+ nextActions,
24434
25143
  retryWith: unique(retryWith)
24435
25144
  };
24436
25145
  }
24437
25146
  function summarizeJob(record) {
24438
- return stripUndefined({
25147
+ return stripUndefined2({
24439
25148
  id: record.id,
24440
25149
  jobType: record.jobType,
24441
25150
  status: record.status,
@@ -24459,7 +25168,7 @@ function retryWithForJob(record) {
24459
25168
  ]);
24460
25169
  }
24461
25170
  function makeJobStatusResult(record, opts) {
24462
- return stripUndefined({
25171
+ return stripUndefined2({
24463
25172
  schemaVersion: "1",
24464
25173
  kind: "job",
24465
25174
  id: record.id,
@@ -24483,7 +25192,7 @@ function jobProgress(record) {
24483
25192
  if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") {
24484
25193
  if (record.progress === void 0 && !record.providerStatus) return void 0;
24485
25194
  }
24486
- return stripUndefined({
25195
+ return stripUndefined2({
24487
25196
  percent: record.progress,
24488
25197
  phase: record.stage ?? record.providerStatus ?? statusPhase(record.status),
24489
25198
  providerStatus: record.providerStatus ?? record.message
@@ -24493,7 +25202,7 @@ function jobResult(record) {
24493
25202
  if (!record.resultUrl && !record.outputPath && !record.cachePath && !record.error && !record.resultPayload) {
24494
25203
  return null;
24495
25204
  }
24496
- return stripUndefined({
25205
+ return stripUndefined2({
24497
25206
  url: record.resultUrl,
24498
25207
  outputPath: record.outputPath,
24499
25208
  cachePath: record.cachePath,
@@ -24529,7 +25238,7 @@ function normalizeJobId(jobId) {
24529
25238
  return id;
24530
25239
  }
24531
25240
  function jobRecordPath(jobId, projectDir) {
24532
- return join2(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
25241
+ return join3(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
24533
25242
  }
24534
25243
  function parseJobRecord(raw2) {
24535
25244
  const parsed = JSON.parse(raw2);
@@ -24554,11 +25263,11 @@ function updateRecordFromProvider(record, result) {
24554
25263
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24555
25264
  };
24556
25265
  next.retryWith = retryWithForJob(next);
24557
- return stripUndefined(next);
25266
+ return stripUndefined2(next);
24558
25267
  }
24559
25268
  function resolveRefreshOutput(record, opts) {
24560
- if (opts.output) return resolve5(opts.workingDirectory ?? process.cwd(), opts.output);
24561
- return record.outputPath ? resolve5(record.outputPath) : void 0;
25269
+ if (opts.output) return resolve6(opts.workingDirectory ?? process.cwd(), opts.output);
25270
+ return record.outputPath ? resolve6(record.outputPath) : void 0;
24562
25271
  }
24563
25272
  function liveSupport(record) {
24564
25273
  if (record.jobType === "generate-video" && (record.provider === "grok" || record.provider === "runway" || record.provider === "kling" || record.provider === "veo")) {
@@ -24583,7 +25292,7 @@ async function downloadUrl(url, outputPath) {
24583
25292
  if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`);
24584
25293
  const buffer = Buffer.from(await response.arrayBuffer());
24585
25294
  await mkdir3(dirname3(outputPath), { recursive: true });
24586
- await writeFile6(outputPath, buffer);
25295
+ await writeFile7(outputPath, buffer);
24587
25296
  return outputPath;
24588
25297
  }
24589
25298
  async function maybeCacheOutput(record) {
@@ -24615,7 +25324,7 @@ async function maybeWriteCompletedAssetMetadata(record) {
24615
25324
  }
24616
25325
  }
24617
25326
  async function refreshBuildReportFromJobs(projectDir, records) {
24618
- const reportPath = join2(projectDir, "build-report.json");
25327
+ const reportPath = join3(projectDir, "build-report.json");
24619
25328
  const report = await readJson(reportPath);
24620
25329
  if (!report || !Array.isArray(report.beats)) return;
24621
25330
  let changed = false;
@@ -24643,7 +25352,7 @@ async function refreshBuildReportFromJobs(projectDir, records) {
24643
25352
  if (kind3 === "music" && record.assetOptions && typeof record.assetOptions.duration === "number") {
24644
25353
  nested.durationSec = record.assetOptions.duration;
24645
25354
  }
24646
- beatRecord[kind3] = stripUndefined(nested);
25355
+ beatRecord[kind3] = stripUndefined2(nested);
24647
25356
  beatRecord[`${kind3}Path`] = outputPath;
24648
25357
  beatRecord[`${kind3}Status`] = "generated";
24649
25358
  beatRecord[`${kind3}JobId`] = record.id;
@@ -24663,17 +25372,17 @@ async function refreshBuildReportFromJobs(projectDir, records) {
24663
25372
  }
24664
25373
  if (!changed) return;
24665
25374
  try {
24666
- await writeFile6(reportPath, JSON.stringify(stripUndefined(report), null, 2) + "\n", "utf-8");
25375
+ await writeFile7(reportPath, JSON.stringify(stripUndefined2(report), null, 2) + "\n", "utf-8");
24667
25376
  } catch {
24668
25377
  }
24669
25378
  }
24670
25379
  async function readBuildSummary(projectDir) {
24671
- const reportPath = join2(projectDir, "build-report.json");
25380
+ const reportPath = join3(projectDir, "build-report.json");
24672
25381
  const report = await readJson(reportPath);
24673
25382
  if (!report) return null;
24674
25383
  const info = await statOrNull(reportPath);
24675
25384
  const beatSummary = projectBeatReadinessFromUnknown(report.beatSummary);
24676
- return stripUndefined({
25385
+ return stripUndefined2({
24677
25386
  reportPath,
24678
25387
  kind: typeof report.kind === "string" ? report.kind : void 0,
24679
25388
  success: typeof report.success === "boolean" ? report.success : void 0,
@@ -24691,7 +25400,7 @@ async function readBuildSummary(projectDir) {
24691
25400
  });
24692
25401
  }
24693
25402
  async function readReviewSummary(projectDir) {
24694
- const reportPath = join2(projectDir, "review-report.json");
25403
+ const reportPath = join3(projectDir, "review-report.json");
24695
25404
  const report = await readJson(reportPath);
24696
25405
  if (!report) return null;
24697
25406
  const info = await statOrNull(reportPath);
@@ -24699,7 +25408,7 @@ async function readReviewSummary(projectDir) {
24699
25408
  const summary = report.summary && typeof report.summary === "object" ? report.summary : {};
24700
25409
  const fixOwners = summary.fixOwners && typeof summary.fixOwners === "object" ? summary.fixOwners : {};
24701
25410
  const computedFixOwners = countIssueFixOwners(issues);
24702
- return stripUndefined({
25411
+ return stripUndefined2({
24703
25412
  reportPath,
24704
25413
  kind: typeof report.kind === "string" ? report.kind : void 0,
24705
25414
  mode: typeof report.mode === "string" ? report.mode : void 0,
@@ -24724,9 +25433,44 @@ async function readReviewSummary(projectDir) {
24724
25433
  },
24725
25434
  sourceReports: stringArray(report.sourceReports),
24726
25435
  updatedAt: info?.mtime.toISOString(),
25436
+ nextActions: reviewActionsFromUnknown(report.nextActions),
24727
25437
  retryWith: stringArray(report.retryWith)
24728
25438
  });
24729
25439
  }
25440
+ function reviewActionsFromUnknown(value) {
25441
+ if (!Array.isArray(value)) return [];
25442
+ return value.flatMap((item) => {
25443
+ if (!item || typeof item !== "object") return [];
25444
+ const record = item;
25445
+ const kind3 = stringEnum(record.kind, ["command", "agent", "manual"]);
25446
+ const fixOwner = stringEnum(record.fixOwner, ["vibe", "host-agent"]);
25447
+ const costTier = stringEnum(record.costTier, [
25448
+ "free",
25449
+ "low",
25450
+ "high",
25451
+ "very-high",
25452
+ "unknown"
25453
+ ]);
25454
+ if (!kind3 || !fixOwner || !costTier || typeof record.id !== "string" || typeof record.label !== "string" || typeof record.safeToAutoRun !== "boolean" || typeof record.requiresConfirmation !== "boolean" || typeof record.reason !== "string") {
25455
+ return [];
25456
+ }
25457
+ return [
25458
+ stripUndefined2({
25459
+ id: record.id,
25460
+ kind: kind3,
25461
+ label: record.label,
25462
+ command: typeof record.command === "string" ? record.command : void 0,
25463
+ agentPrompt: typeof record.agentPrompt === "string" ? record.agentPrompt : void 0,
25464
+ fixOwner,
25465
+ costTier,
25466
+ safeToAutoRun: record.safeToAutoRun,
25467
+ requiresConfirmation: record.requiresConfirmation,
25468
+ reason: record.reason,
25469
+ sourceIssueCodes: stringArray(record.sourceIssueCodes)
25470
+ })
25471
+ ];
25472
+ });
25473
+ }
24730
25474
  function summarizeJobs(records) {
24731
25475
  return {
24732
25476
  total: records.length,
@@ -24745,7 +25489,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
24745
25489
  if (fromBeats) return fromBeats;
24746
25490
  if (build.beats) return augmentBeatReadinessFromJobs(build.beats, records, report?.jobs);
24747
25491
  }
24748
- const storyboardPath = join2(projectDir, "STORYBOARD.md");
25492
+ const storyboardPath = join3(projectDir, "STORYBOARD.md");
24749
25493
  if (!existsSync2(storyboardPath)) {
24750
25494
  return { total: 0, assetsReady: 0, compositionsReady: 0, needsAuthor: [] };
24751
25495
  }
@@ -24754,7 +25498,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
24754
25498
  const needsAuthor = [];
24755
25499
  let compositionsReady = 0;
24756
25500
  for (const beat of parsed.beats) {
24757
- if (existsSync2(join2(projectDir, "compositions", `scene-${beat.id}.html`))) {
25501
+ if (existsSync2(join3(projectDir, "compositions", `scene-${beat.id}.html`))) {
24758
25502
  compositionsReady += 1;
24759
25503
  } else {
24760
25504
  needsAuthor.push(beat.id);
@@ -24772,7 +25516,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
24772
25516
  }
24773
25517
  function deriveProjectWorkflow(opts) {
24774
25518
  const retryWith = [];
24775
- const hasStoryboard = existsSync2(join2(opts.project, "STORYBOARD.md"));
25519
+ const hasStoryboard = existsSync2(join3(opts.project, "STORYBOARD.md"));
24776
25520
  if (!hasStoryboard && !opts.build) {
24777
25521
  retryWith.push(`vibe init ${opts.project} --from "<brief>" --json`);
24778
25522
  return { status: "empty", currentStage: "init", retryWith };
@@ -24868,7 +25612,7 @@ function beatReadinessFromBuildBeats(projectDir, beats, records = [], reportJobs
24868
25612
  if (buildBeatAssetsReady(record) || (id ? buildBeatJobsReady(id, records, reportJobs) : false))
24869
25613
  assetsReady += 1;
24870
25614
  const compositionPath = typeof record.compositionPath === "string" ? record.compositionPath : id ? `compositions/scene-${id}.html` : void 0;
24871
- if (compositionPath && existsSync2(resolve5(projectDir, compositionPath))) {
25615
+ if (compositionPath && existsSync2(resolve6(projectDir, compositionPath))) {
24872
25616
  compositionsReady += 1;
24873
25617
  } else if (id) {
24874
25618
  needsAuthor.push(id);
@@ -24938,6 +25682,9 @@ function numberOr(fallback2, value) {
24938
25682
  function stringArray(value) {
24939
25683
  return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
24940
25684
  }
25685
+ function stringEnum(value, allowed) {
25686
+ return typeof value === "string" && allowed.includes(value) ? value : void 0;
25687
+ }
24941
25688
  function isActiveStatus(status) {
24942
25689
  return status === "queued" || status === "running";
24943
25690
  }
@@ -24967,7 +25714,7 @@ function assetKindForJob(record) {
24967
25714
  return void 0;
24968
25715
  }
24969
25716
  function projectRelativePath(projectDir, path14) {
24970
- const rel = relative(resolve5(projectDir), resolve5(path14));
25717
+ const rel = relative(resolve6(projectDir), resolve6(path14));
24971
25718
  if (!rel || rel.startsWith("..")) return path14;
24972
25719
  return rel.replace(/\\/g, "/");
24973
25720
  }
@@ -24985,7 +25732,7 @@ async function statOrNull(path14) {
24985
25732
  return null;
24986
25733
  }
24987
25734
  }
24988
- function stripUndefined(value) {
25735
+ function stripUndefined2(value) {
24989
25736
  return Object.fromEntries(Object.entries(value).filter(([, v2]) => v2 !== void 0));
24990
25737
  }
24991
25738
  function unique(items) {
@@ -25003,6 +25750,7 @@ var init_status_jobs = __esm({
25003
25750
  init_ai_video();
25004
25751
  init_music_status();
25005
25752
  init_build_asset_metadata();
25753
+ init_review_report();
25006
25754
  init_storyboard_parse();
25007
25755
  LOCAL_JOB_ORPHAN_AFTER_MS = 9e4;
25008
25756
  }
@@ -25011,7 +25759,7 @@ var init_status_jobs = __esm({
25011
25759
  // ../cli/src/commands/_shared/project-config.ts
25012
25760
  import { existsSync as existsSync3 } from "node:fs";
25013
25761
  import { readFile as readFile5 } from "node:fs/promises";
25014
- import { resolve as resolve6 } from "node:path";
25762
+ import { resolve as resolve7 } from "node:path";
25015
25763
  function defaultProjectConfig(opts) {
25016
25764
  return {
25017
25765
  schemaVersion: "1",
@@ -25062,9 +25810,9 @@ function projectConfigJson(opts) {
25062
25810
  return JSON.stringify(defaultProjectConfig(opts), null, 2) + "\n";
25063
25811
  }
25064
25812
  async function readProjectConfig(projectDir) {
25065
- const dir = resolve6(projectDir);
25066
- const canonicalPath = resolve6(dir, VIBE_CONFIG_FILENAME);
25067
- const legacyPath = resolve6(dir, LEGACY_VIBE_PROJECT_FILENAME);
25813
+ const dir = resolve7(projectDir);
25814
+ const canonicalPath = resolve7(dir, VIBE_CONFIG_FILENAME);
25815
+ const legacyPath = resolve7(dir, LEGACY_VIBE_PROJECT_FILENAME);
25068
25816
  if (existsSync3(canonicalPath)) {
25069
25817
  try {
25070
25818
  const raw2 = await readFile5(canonicalPath, "utf-8");
@@ -25127,8 +25875,8 @@ var init_project_config = __esm({
25127
25875
  });
25128
25876
 
25129
25877
  // ../cli/src/commands/_shared/scene-project.ts
25130
- import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile7, access as access3 } from "node:fs/promises";
25131
- import { resolve as resolve7, basename as basename2 } from "node:path";
25878
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile8, access as access3 } from "node:fs/promises";
25879
+ import { resolve as resolve8, basename as basename2 } from "node:path";
25132
25880
  function aspectToDims(aspect) {
25133
25881
  return ASPECT_DIMS[aspect];
25134
25882
  }
@@ -25426,6 +26174,43 @@ vibe host setup all --write # write project/app config
25426
26174
  vibe host doctor all --json # verify readiness
25427
26175
  \`\`\`
25428
26176
 
26177
+ ## Native host goal loop
26178
+
26179
+ For long-running work, use Codex Goal mode, Claude Code \`/goal\`, or the
26180
+ host's equivalent as the outer loop. VibeFrame should not run a competing
26181
+ project-level goal loop; it provides \`--json\` commands, dry runs, cost caps,
26182
+ \`build-report.json\`, \`review-report.json\`, \`nextActions\`, \`safeToAutoRun\`,
26183
+ \`requiresConfirmation\`, \`fixOwner\`, deterministic repair, and render
26184
+ inspection for the host agent to reason over.
26185
+
26186
+ Copy-paste examples:
26187
+
26188
+ \`\`\`text
26189
+ /goal Build this VibeFrame project into renders/final.mp4. Use --json for every
26190
+ vibe command, run --dry-run before paid operations, use --max-cost 5 for builds
26191
+ unless the user sets another budget, read build-report.json and
26192
+ review-report.json before deciding the next action, prefer nextActions before
26193
+ guessing, run only safeToAutoRun:true actions automatically, and ask before
26194
+ requiresConfirmation:true actions. Treat retryWith as the compatibility
26195
+ fallback, fixOwner:"vibe" as CLI repair work, and fixOwner:"host-agent" as
26196
+ STORYBOARD.md, DESIGN.md, or composition edits.
26197
+ Stop only when renders/final.mp4 exists, duration and aspect ratio match the
26198
+ brief, inspect render --cheap has no errors, any AI review score is >= 90 when AI review is requested, and every
26199
+ remaining host-agent issue is fixed, accepted with rationale, or reported as
26200
+ blocked.
26201
+ \`\`\`
26202
+
26203
+ \`\`\`text
26204
+ /goal Finish this VibeFrame render using Claude Code goal mode as the outer
26205
+ loop. Use vibe context/schema when unsure, --json everywhere, dry-run before
26206
+ paid operations, budget cap via --max-cost 5, nextActions before guessing,
26207
+ build-report.json/review-report.json as loop state, and safeToAutoRun,
26208
+ requiresConfirmation, and fixOwner to decide between vibe scene repair and
26209
+ host-agent edits. Stop only after final MP4,
26210
+ target duration/aspect ratio, clean render inspection, any AI review score >= 90 when AI review is requested, and
26211
+ no unresolved unacknowledged host-agent issues.
26212
+ \`\`\`
26213
+
25429
26214
  ## Skills \u2014 USE THESE FIRST
25430
26215
 
25431
26216
  @SKILL.md
@@ -25529,35 +26314,35 @@ tmp/
25529
26314
  `;
25530
26315
  }
25531
26316
  function describeSceneScaffold(opts) {
25532
- const dir = resolve7(opts.dir);
26317
+ const dir = resolve8(opts.dir);
25533
26318
  const profile = opts.profile ?? "full";
25534
26319
  const groups = {
25535
26320
  authoring: [
25536
- resolve7(dir, "STORYBOARD.md"),
25537
- resolve7(dir, "DESIGN.md"),
25538
- resolve7(dir, VIBE_CONFIG_FILENAME),
25539
- resolve7(dir, "vibe.project.yaml"),
25540
- resolve7(dir, ".gitignore")
26321
+ resolve8(dir, "STORYBOARD.md"),
26322
+ resolve8(dir, "DESIGN.md"),
26323
+ resolve8(dir, VIBE_CONFIG_FILENAME),
26324
+ resolve8(dir, "vibe.project.yaml"),
26325
+ resolve8(dir, ".gitignore")
25541
26326
  ],
25542
26327
  render: [],
25543
26328
  agent: []
25544
26329
  };
25545
26330
  if (profile === "full") {
25546
26331
  groups.render = [
25547
- resolve7(dir, "index.html"),
25548
- resolve7(dir, "compositions"),
25549
- resolve7(dir, "assets"),
25550
- resolve7(dir, "renders"),
25551
- resolve7(dir, "hyperframes.json"),
25552
- resolve7(dir, "meta.json")
26332
+ resolve8(dir, "index.html"),
26333
+ resolve8(dir, "compositions"),
26334
+ resolve8(dir, "assets"),
26335
+ resolve8(dir, "renders"),
26336
+ resolve8(dir, "hyperframes.json"),
26337
+ resolve8(dir, "meta.json")
25553
26338
  ];
25554
26339
  }
25555
26340
  if (profile === "agent" || profile === "full") {
25556
26341
  groups.agent = [
25557
- resolve7(dir, "AGENTS.md"),
25558
- resolve7(dir, "SKILL.md"),
25559
- resolve7(dir, "references"),
25560
- resolve7(dir, "CLAUDE.md")
26342
+ resolve8(dir, "AGENTS.md"),
26343
+ resolve8(dir, "SKILL.md"),
26344
+ resolve8(dir, "references"),
26345
+ resolve8(dir, "CLAUDE.md")
25561
26346
  ];
25562
26347
  }
25563
26348
  return groups;
@@ -25571,7 +26356,7 @@ async function pathExists(p2) {
25571
26356
  }
25572
26357
  }
25573
26358
  async function scaffoldSceneProject(opts) {
25574
- const dir = resolve7(opts.dir);
26359
+ const dir = resolve8(opts.dir);
25575
26360
  const name = opts.name ?? basename2(dir);
25576
26361
  const aspect = opts.aspect ?? "16:9";
25577
26362
  const duration = opts.duration ?? 10;
@@ -25579,103 +26364,103 @@ async function scaffoldSceneProject(opts) {
25579
26364
  const profile = opts.profile ?? "full";
25580
26365
  await mkdir4(dir, { recursive: true });
25581
26366
  if (profile === "full") {
25582
- await mkdir4(resolve7(dir, "compositions"), { recursive: true });
25583
- await mkdir4(resolve7(dir, "assets"), { recursive: true });
25584
- await mkdir4(resolve7(dir, "renders"), { recursive: true });
26367
+ await mkdir4(resolve8(dir, "compositions"), { recursive: true });
26368
+ await mkdir4(resolve8(dir, "assets"), { recursive: true });
26369
+ await mkdir4(resolve8(dir, "renders"), { recursive: true });
25585
26370
  }
25586
26371
  const created = [];
25587
26372
  const skipped2 = [];
25588
26373
  const merged = [];
25589
26374
  if (profile === "full") {
25590
- const hfPath = resolve7(dir, "hyperframes.json");
26375
+ const hfPath = resolve8(dir, "hyperframes.json");
25591
26376
  const hfDefaults = buildHyperframesConfig();
25592
26377
  if (await pathExists(hfPath)) {
25593
26378
  const existingRaw = await readFile6(hfPath, "utf-8");
25594
26379
  const existing = JSON.parse(existingRaw);
25595
26380
  const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
25596
- await writeFile7(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
26381
+ await writeFile8(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
25597
26382
  merged.push(hfPath);
25598
26383
  } else {
25599
- await writeFile7(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
26384
+ await writeFile8(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
25600
26385
  created.push(hfPath);
25601
26386
  }
25602
- const metaPath = resolve7(dir, "meta.json");
26387
+ const metaPath = resolve8(dir, "meta.json");
25603
26388
  if (await pathExists(metaPath)) {
25604
26389
  skipped2.push(metaPath);
25605
26390
  } else {
25606
- await writeFile7(
26391
+ await writeFile8(
25607
26392
  metaPath,
25608
26393
  JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n",
25609
26394
  "utf-8"
25610
26395
  );
25611
26396
  created.push(metaPath);
25612
26397
  }
25613
- const rootPath = resolve7(dir, "index.html");
26398
+ const rootPath = resolve8(dir, "index.html");
25614
26399
  if (await pathExists(rootPath)) {
25615
26400
  skipped2.push(rootPath);
25616
26401
  } else {
25617
- await writeFile7(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
26402
+ await writeFile8(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
25618
26403
  created.push(rootPath);
25619
26404
  }
25620
26405
  }
25621
- const vibeConfigJsonPath = resolve7(dir, VIBE_CONFIG_FILENAME);
26406
+ const vibeConfigJsonPath = resolve8(dir, VIBE_CONFIG_FILENAME);
25622
26407
  if (await pathExists(vibeConfigJsonPath)) {
25623
26408
  skipped2.push(vibeConfigJsonPath);
25624
26409
  } else {
25625
- await writeFile7(vibeConfigJsonPath, projectConfigJson({ name, aspect }), "utf-8");
26410
+ await writeFile8(vibeConfigJsonPath, projectConfigJson({ name, aspect }), "utf-8");
25626
26411
  created.push(vibeConfigJsonPath);
25627
26412
  }
25628
- const vibePath = resolve7(dir, "vibe.project.yaml");
26413
+ const vibePath = resolve8(dir, "vibe.project.yaml");
25629
26414
  if (await pathExists(vibePath)) {
25630
26415
  skipped2.push(vibePath);
25631
26416
  } else {
25632
26417
  const cfg = { ...defaultVibeProjectConfig(name), aspect };
25633
- await writeFile7(vibePath, (0, import_yaml4.stringify)(cfg), "utf-8");
26418
+ await writeFile8(vibePath, (0, import_yaml4.stringify)(cfg), "utf-8");
25634
26419
  created.push(vibePath);
25635
26420
  }
25636
26421
  if (profile === "agent" || profile === "full") {
25637
- const agentsPath = resolve7(dir, "AGENTS.md");
26422
+ const agentsPath = resolve8(dir, "AGENTS.md");
25638
26423
  if (await pathExists(agentsPath)) {
25639
26424
  skipped2.push(agentsPath);
25640
26425
  } else {
25641
- await writeFile7(agentsPath, buildProjectAgentsMd(name), "utf-8");
26426
+ await writeFile8(agentsPath, buildProjectAgentsMd(name), "utf-8");
25642
26427
  created.push(agentsPath);
25643
26428
  }
25644
- const claudePath = resolve7(dir, "CLAUDE.md");
26429
+ const claudePath = resolve8(dir, "CLAUDE.md");
25645
26430
  if (await pathExists(claudePath)) {
25646
26431
  const existing = await readFile6(claudePath, "utf-8");
25647
26432
  if (existing.includes("@AGENTS.md")) {
25648
26433
  skipped2.push(claudePath);
25649
26434
  } else {
25650
- await writeFile7(claudePath, `@AGENTS.md
26435
+ await writeFile8(claudePath, `@AGENTS.md
25651
26436
 
25652
26437
  ${existing}`, "utf-8");
25653
26438
  merged.push(claudePath);
25654
26439
  }
25655
26440
  } else {
25656
- await writeFile7(claudePath, buildProjectClaudeMd(name), "utf-8");
26441
+ await writeFile8(claudePath, buildProjectClaudeMd(name), "utf-8");
25657
26442
  created.push(claudePath);
25658
26443
  }
25659
26444
  }
25660
- const designPath = resolve7(dir, "DESIGN.md");
26445
+ const designPath = resolve8(dir, "DESIGN.md");
25661
26446
  if (await pathExists(designPath)) {
25662
26447
  skipped2.push(designPath);
25663
26448
  } else {
25664
- await writeFile7(designPath, buildDesignMd({ name, style: opts.visualStyle }), "utf-8");
26449
+ await writeFile8(designPath, buildDesignMd({ name, style: opts.visualStyle }), "utf-8");
25665
26450
  created.push(designPath);
25666
26451
  }
25667
- const storyboardPath = resolve7(dir, "STORYBOARD.md");
26452
+ const storyboardPath = resolve8(dir, "STORYBOARD.md");
25668
26453
  if (await pathExists(storyboardPath)) {
25669
26454
  skipped2.push(storyboardPath);
25670
26455
  } else {
25671
- await writeFile7(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
26456
+ await writeFile8(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
25672
26457
  created.push(storyboardPath);
25673
26458
  }
25674
- const gitignorePath = resolve7(dir, ".gitignore");
26459
+ const gitignorePath = resolve8(dir, ".gitignore");
25675
26460
  if (await pathExists(gitignorePath)) {
25676
26461
  skipped2.push(gitignorePath);
25677
26462
  } else {
25678
- await writeFile7(gitignorePath, buildSceneGitignore(), "utf-8");
26463
+ await writeFile8(gitignorePath, buildSceneGitignore(), "utf-8");
25679
26464
  created.push(gitignorePath);
25680
26465
  }
25681
26466
  return { created, skipped: skipped2, merged, groups: describeSceneScaffold({ dir, profile }) };
@@ -28714,10 +29499,22 @@ var init_esm = __esm({
28714
29499
 
28715
29500
  // ../cli/src/commands/_shared/tts-resolve.ts
28716
29501
  async function resolveTtsProvider(preferred = "auto") {
28717
- const choice = preferred === "auto" ? await getConfiguredApiKey("ELEVENLABS_API_KEY") ? "elevenlabs" : "kokoro" : preferred;
29502
+ let choice = preferred;
29503
+ if (choice === "auto") {
29504
+ if (await getConfiguredApiKey("ELEVENLABS_API_KEY")) {
29505
+ choice = "elevenlabs";
29506
+ } else if (await getConfiguredApiKey("OPENAI_API_KEY")) {
29507
+ choice = "openai";
29508
+ } else {
29509
+ choice = "kokoro";
29510
+ }
29511
+ }
28718
29512
  if (choice === "elevenlabs") {
28719
29513
  return buildElevenLabs();
28720
29514
  }
29515
+ if (choice === "openai") {
29516
+ return buildOpenAi();
29517
+ }
28721
29518
  return buildKokoro();
28722
29519
  }
28723
29520
  async function buildElevenLabs() {
@@ -28733,6 +29530,19 @@ async function buildElevenLabs() {
28733
29530
  });
28734
29531
  return { provider: "elevenlabs", audioExtension: "mp3", call };
28735
29532
  }
29533
+ async function buildOpenAi() {
29534
+ const key2 = await getApiKey("OPENAI_API_KEY", "OpenAI");
29535
+ if (!key2) {
29536
+ throw new TtsKeyMissingError("openai");
29537
+ }
29538
+ const provider = new OpenAiTtsProvider();
29539
+ await provider.initialize({ apiKey: key2 });
29540
+ const call = async (text, opts) => provider.textToSpeech(text, {
29541
+ voice: opts?.voice,
29542
+ speed: opts?.speed
29543
+ });
29544
+ return { provider: "openai", audioExtension: "mp3", call };
29545
+ }
28736
29546
  async function buildKokoro() {
28737
29547
  const provider = new KokoroProvider();
28738
29548
  await provider.initialize({});
@@ -28745,11 +29555,11 @@ async function buildKokoro() {
28745
29555
  }
28746
29556
  function parseTtsProviderName(value) {
28747
29557
  if (!value) return "auto";
28748
- if (value === "auto" || value === "elevenlabs" || value === "kokoro") {
29558
+ if (value === "auto" || value === "elevenlabs" || value === "openai" || value === "kokoro") {
28749
29559
  return value;
28750
29560
  }
28751
29561
  throw new Error(
28752
- `Invalid --tts: ${value}. Valid: auto, elevenlabs, kokoro.`
29562
+ `Invalid --tts: ${value}. Valid: auto, elevenlabs, openai, kokoro.`
28753
29563
  );
28754
29564
  }
28755
29565
  var TtsKeyMissingError;
@@ -28761,7 +29571,7 @@ var init_tts_resolve = __esm({
28761
29571
  TtsKeyMissingError = class extends Error {
28762
29572
  constructor(provider) {
28763
29573
  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.`
29574
+ 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
29575
  );
28766
29576
  this.provider = provider;
28767
29577
  this.name = "TtsKeyMissingError";
@@ -32900,7 +33710,7 @@ var require_util = __commonJS({
32900
33710
  return path14;
32901
33711
  });
32902
33712
  exports.normalize = normalize3;
32903
- function join45(aRoot, aPath) {
33713
+ function join44(aRoot, aPath) {
32904
33714
  if (aRoot === "") {
32905
33715
  aRoot = ".";
32906
33716
  }
@@ -32932,7 +33742,7 @@ var require_util = __commonJS({
32932
33742
  }
32933
33743
  return joined;
32934
33744
  }
32935
- exports.join = join45;
33745
+ exports.join = join44;
32936
33746
  exports.isAbsolute = function(aPath) {
32937
33747
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
32938
33748
  };
@@ -33146,7 +33956,7 @@ var require_util = __commonJS({
33146
33956
  parsed.path = parsed.path.substring(0, index + 1);
33147
33957
  }
33148
33958
  }
33149
- sourceURL = join45(urlGenerate(parsed), sourceURL);
33959
+ sourceURL = join44(urlGenerate(parsed), sourceURL);
33150
33960
  }
33151
33961
  return normalize3(sourceURL);
33152
33962
  }
@@ -34586,8 +35396,8 @@ var require_source_map = __commonJS({
34586
35396
  var require_previous_map = __commonJS({
34587
35397
  "../../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/previous-map.js"(exports, module) {
34588
35398
  "use strict";
34589
- var { existsSync: existsSync67, readFileSync: readFileSync12 } = __require("fs");
34590
- var { dirname: dirname33, join: join45 } = __require("path");
35399
+ var { existsSync: existsSync67, readFileSync: readFileSync13 } = __require("fs");
35400
+ var { dirname: dirname33, join: join44 } = __require("path");
34591
35401
  var { SourceMapConsumer, SourceMapGenerator } = require_source_map();
34592
35402
  function fromBase64(str2) {
34593
35403
  if (Buffer) {
@@ -34651,7 +35461,7 @@ var require_previous_map = __commonJS({
34651
35461
  this.root = dirname33(path14);
34652
35462
  if (existsSync67(path14)) {
34653
35463
  this.mapFile = path14;
34654
- return readFileSync12(path14, "utf-8").toString().trim();
35464
+ return readFileSync13(path14, "utf-8").toString().trim();
34655
35465
  }
34656
35466
  }
34657
35467
  loadMap(file, prev) {
@@ -34685,7 +35495,7 @@ var require_previous_map = __commonJS({
34685
35495
  return this.decodeInline(this.annotation);
34686
35496
  } else if (this.annotation) {
34687
35497
  let map3 = this.annotation;
34688
- if (file) map3 = join45(dirname33(file), map3);
35498
+ if (file) map3 = join44(dirname33(file), map3);
34689
35499
  return this.loadFile(map3);
34690
35500
  }
34691
35501
  }
@@ -82140,14 +82950,14 @@ var require_re = __commonJS({
82140
82950
  return value;
82141
82951
  };
82142
82952
  var createToken = (name, value, isGlobal) => {
82143
- const safe = makeSafeRegex(value);
82953
+ const safe2 = makeSafeRegex(value);
82144
82954
  const index = R2++;
82145
82955
  debug9(name, index, value);
82146
82956
  t2[name] = index;
82147
82957
  src[index] = value;
82148
- safeSrc[index] = safe;
82958
+ safeSrc[index] = safe2;
82149
82959
  re2[index] = new RegExp(value, isGlobal ? "g" : void 0);
82150
- safeRe[index] = new RegExp(safe, isGlobal ? "g" : void 0);
82960
+ safeRe[index] = new RegExp(safe2, isGlobal ? "g" : void 0);
82151
82961
  };
82152
82962
  createToken("NUMERICIDENTIFIER", "0|[1-9]\\d*");
82153
82963
  createToken("NUMERICIDENTIFIERLOOSE", "\\d+");
@@ -93314,7 +94124,7 @@ var require_util3 = __commonJS({
93314
94124
  return path14;
93315
94125
  }
93316
94126
  exports.normalize = normalize3;
93317
- function join45(aRoot, aPath) {
94127
+ function join44(aRoot, aPath) {
93318
94128
  if (aRoot === "") {
93319
94129
  aRoot = ".";
93320
94130
  }
@@ -93346,7 +94156,7 @@ var require_util3 = __commonJS({
93346
94156
  }
93347
94157
  return joined;
93348
94158
  }
93349
- exports.join = join45;
94159
+ exports.join = join44;
93350
94160
  exports.isAbsolute = function(aPath) {
93351
94161
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
93352
94162
  };
@@ -93519,7 +94329,7 @@ var require_util3 = __commonJS({
93519
94329
  parsed.path = parsed.path.substring(0, index + 1);
93520
94330
  }
93521
94331
  }
93522
- sourceURL = join45(urlGenerate(parsed), sourceURL);
94332
+ sourceURL = join44(urlGenerate(parsed), sourceURL);
93523
94333
  }
93524
94334
  return normalize3(sourceURL);
93525
94335
  }
@@ -95321,7 +96131,7 @@ var require_escodegen = __commonJS({
95321
96131
  function noEmptySpace() {
95322
96132
  return space ? space : " ";
95323
96133
  }
95324
- function join45(left3, right3) {
96134
+ function join44(left3, right3) {
95325
96135
  var leftSource, rightSource, leftCharCode, rightCharCode;
95326
96136
  leftSource = toSourceNodeWhenNeeded(left3).toString();
95327
96137
  if (leftSource.length === 0) {
@@ -95652,8 +96462,8 @@ var require_escodegen = __commonJS({
95652
96462
  } else {
95653
96463
  result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT));
95654
96464
  }
95655
- result = join45(result, operator);
95656
- result = [join45(
96465
+ result = join44(result, operator);
96466
+ result = [join44(
95657
96467
  result,
95658
96468
  that.generateExpression(stmt.right, Precedence.Assignment, E_TTT)
95659
96469
  ), ")"];
@@ -95796,11 +96606,11 @@ var require_escodegen = __commonJS({
95796
96606
  var result, fragment;
95797
96607
  result = ["class"];
95798
96608
  if (stmt.id) {
95799
- result = join45(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
96609
+ result = join44(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
95800
96610
  }
95801
96611
  if (stmt.superClass) {
95802
- fragment = join45("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
95803
- result = join45(result, fragment);
96612
+ fragment = join44("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
96613
+ result = join44(result, fragment);
95804
96614
  }
95805
96615
  result.push(space);
95806
96616
  result.push(this.generateStatement(stmt.body, S_TFFT));
@@ -95813,9 +96623,9 @@ var require_escodegen = __commonJS({
95813
96623
  return escapeDirective(stmt.directive) + this.semicolon(flags);
95814
96624
  },
95815
96625
  DoWhileStatement: function(stmt, flags) {
95816
- var result = join45("do", this.maybeBlock(stmt.body, S_TFFF));
96626
+ var result = join44("do", this.maybeBlock(stmt.body, S_TFFF));
95817
96627
  result = this.maybeBlockSuffix(stmt.body, result);
95818
- return join45(result, [
96628
+ return join44(result, [
95819
96629
  "while" + space + "(",
95820
96630
  this.generateExpression(stmt.test, Precedence.Sequence, E_TTT),
95821
96631
  ")" + this.semicolon(flags)
@@ -95851,11 +96661,11 @@ var require_escodegen = __commonJS({
95851
96661
  ExportDefaultDeclaration: function(stmt, flags) {
95852
96662
  var result = ["export"], bodyFlags;
95853
96663
  bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF;
95854
- result = join45(result, "default");
96664
+ result = join44(result, "default");
95855
96665
  if (isStatement(stmt.declaration)) {
95856
- result = join45(result, this.generateStatement(stmt.declaration, bodyFlags));
96666
+ result = join44(result, this.generateStatement(stmt.declaration, bodyFlags));
95857
96667
  } else {
95858
- result = join45(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
96668
+ result = join44(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
95859
96669
  }
95860
96670
  return result;
95861
96671
  },
@@ -95863,15 +96673,15 @@ var require_escodegen = __commonJS({
95863
96673
  var result = ["export"], bodyFlags, that = this;
95864
96674
  bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF;
95865
96675
  if (stmt.declaration) {
95866
- return join45(result, this.generateStatement(stmt.declaration, bodyFlags));
96676
+ return join44(result, this.generateStatement(stmt.declaration, bodyFlags));
95867
96677
  }
95868
96678
  if (stmt.specifiers) {
95869
96679
  if (stmt.specifiers.length === 0) {
95870
- result = join45(result, "{" + space + "}");
96680
+ result = join44(result, "{" + space + "}");
95871
96681
  } else if (stmt.specifiers[0].type === Syntax.ExportBatchSpecifier) {
95872
- result = join45(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
96682
+ result = join44(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
95873
96683
  } else {
95874
- result = join45(result, "{");
96684
+ result = join44(result, "{");
95875
96685
  withIndent(function(indent2) {
95876
96686
  var i2, iz;
95877
96687
  result.push(newline);
@@ -95889,7 +96699,7 @@ var require_escodegen = __commonJS({
95889
96699
  result.push(base + "}");
95890
96700
  }
95891
96701
  if (stmt.source) {
95892
- result = join45(result, [
96702
+ result = join44(result, [
95893
96703
  "from" + space,
95894
96704
  // ModuleSpecifier
95895
96705
  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
@@ -95977,7 +96787,7 @@ var require_escodegen = __commonJS({
95977
96787
  ];
95978
96788
  cursor = 0;
95979
96789
  if (stmt.specifiers[cursor].type === Syntax.ImportDefaultSpecifier) {
95980
- result = join45(result, [
96790
+ result = join44(result, [
95981
96791
  this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
95982
96792
  ]);
95983
96793
  ++cursor;
@@ -95987,7 +96797,7 @@ var require_escodegen = __commonJS({
95987
96797
  result.push(",");
95988
96798
  }
95989
96799
  if (stmt.specifiers[cursor].type === Syntax.ImportNamespaceSpecifier) {
95990
- result = join45(result, [
96800
+ result = join44(result, [
95991
96801
  space,
95992
96802
  this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
95993
96803
  ]);
@@ -96016,7 +96826,7 @@ var require_escodegen = __commonJS({
96016
96826
  }
96017
96827
  }
96018
96828
  }
96019
- result = join45(result, [
96829
+ result = join44(result, [
96020
96830
  "from" + space,
96021
96831
  // ModuleSpecifier
96022
96832
  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
@@ -96070,7 +96880,7 @@ var require_escodegen = __commonJS({
96070
96880
  return result;
96071
96881
  },
96072
96882
  ThrowStatement: function(stmt, flags) {
96073
- return [join45(
96883
+ return [join44(
96074
96884
  "throw",
96075
96885
  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
96076
96886
  ), this.semicolon(flags)];
@@ -96081,7 +96891,7 @@ var require_escodegen = __commonJS({
96081
96891
  result = this.maybeBlockSuffix(stmt.block, result);
96082
96892
  if (stmt.handlers) {
96083
96893
  for (i2 = 0, iz = stmt.handlers.length; i2 < iz; ++i2) {
96084
- result = join45(result, this.generateStatement(stmt.handlers[i2], S_TFFF));
96894
+ result = join44(result, this.generateStatement(stmt.handlers[i2], S_TFFF));
96085
96895
  if (stmt.finalizer || i2 + 1 !== iz) {
96086
96896
  result = this.maybeBlockSuffix(stmt.handlers[i2].body, result);
96087
96897
  }
@@ -96089,7 +96899,7 @@ var require_escodegen = __commonJS({
96089
96899
  } else {
96090
96900
  guardedHandlers = stmt.guardedHandlers || [];
96091
96901
  for (i2 = 0, iz = guardedHandlers.length; i2 < iz; ++i2) {
96092
- result = join45(result, this.generateStatement(guardedHandlers[i2], S_TFFF));
96902
+ result = join44(result, this.generateStatement(guardedHandlers[i2], S_TFFF));
96093
96903
  if (stmt.finalizer || i2 + 1 !== iz) {
96094
96904
  result = this.maybeBlockSuffix(guardedHandlers[i2].body, result);
96095
96905
  }
@@ -96097,13 +96907,13 @@ var require_escodegen = __commonJS({
96097
96907
  if (stmt.handler) {
96098
96908
  if (Array.isArray(stmt.handler)) {
96099
96909
  for (i2 = 0, iz = stmt.handler.length; i2 < iz; ++i2) {
96100
- result = join45(result, this.generateStatement(stmt.handler[i2], S_TFFF));
96910
+ result = join44(result, this.generateStatement(stmt.handler[i2], S_TFFF));
96101
96911
  if (stmt.finalizer || i2 + 1 !== iz) {
96102
96912
  result = this.maybeBlockSuffix(stmt.handler[i2].body, result);
96103
96913
  }
96104
96914
  }
96105
96915
  } else {
96106
- result = join45(result, this.generateStatement(stmt.handler, S_TFFF));
96916
+ result = join44(result, this.generateStatement(stmt.handler, S_TFFF));
96107
96917
  if (stmt.finalizer) {
96108
96918
  result = this.maybeBlockSuffix(stmt.handler.body, result);
96109
96919
  }
@@ -96111,7 +96921,7 @@ var require_escodegen = __commonJS({
96111
96921
  }
96112
96922
  }
96113
96923
  if (stmt.finalizer) {
96114
- result = join45(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]);
96924
+ result = join44(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]);
96115
96925
  }
96116
96926
  return result;
96117
96927
  },
@@ -96145,7 +96955,7 @@ var require_escodegen = __commonJS({
96145
96955
  withIndent(function() {
96146
96956
  if (stmt.test) {
96147
96957
  result = [
96148
- join45("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
96958
+ join44("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
96149
96959
  ":"
96150
96960
  ];
96151
96961
  } else {
@@ -96193,9 +97003,9 @@ var require_escodegen = __commonJS({
96193
97003
  result.push(this.maybeBlock(stmt.consequent, S_TFFF));
96194
97004
  result = this.maybeBlockSuffix(stmt.consequent, result);
96195
97005
  if (stmt.alternate.type === Syntax.IfStatement) {
96196
- result = join45(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]);
97006
+ result = join44(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]);
96197
97007
  } else {
96198
- result = join45(result, join45("else", this.maybeBlock(stmt.alternate, bodyFlags)));
97008
+ result = join44(result, join44("else", this.maybeBlock(stmt.alternate, bodyFlags)));
96199
97009
  }
96200
97010
  } else {
96201
97011
  result.push(this.maybeBlock(stmt.consequent, bodyFlags));
@@ -96296,7 +97106,7 @@ var require_escodegen = __commonJS({
96296
97106
  },
96297
97107
  ReturnStatement: function(stmt, flags) {
96298
97108
  if (stmt.argument) {
96299
- return [join45(
97109
+ return [join44(
96300
97110
  "return",
96301
97111
  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
96302
97112
  ), this.semicolon(flags)];
@@ -96385,14 +97195,14 @@ var require_escodegen = __commonJS({
96385
97195
  if (leftSource.charCodeAt(leftSource.length - 1) === 47 && esutils.code.isIdentifierPartES5(expr.operator.charCodeAt(0))) {
96386
97196
  result = [fragment, noEmptySpace(), expr.operator];
96387
97197
  } else {
96388
- result = join45(fragment, expr.operator);
97198
+ result = join44(fragment, expr.operator);
96389
97199
  }
96390
97200
  fragment = this.generateExpression(expr.right, rightPrecedence, flags);
96391
97201
  if (expr.operator === "/" && fragment.toString().charAt(0) === "/" || expr.operator.slice(-1) === "<" && fragment.toString().slice(0, 3) === "!--") {
96392
97202
  result.push(noEmptySpace());
96393
97203
  result.push(fragment);
96394
97204
  } else {
96395
- result = join45(result, fragment);
97205
+ result = join44(result, fragment);
96396
97206
  }
96397
97207
  if (expr.operator === "in" && !(flags & F_ALLOW_IN)) {
96398
97208
  return ["(", result, ")"];
@@ -96432,7 +97242,7 @@ var require_escodegen = __commonJS({
96432
97242
  var result, length, i2, iz, itemFlags;
96433
97243
  length = expr["arguments"].length;
96434
97244
  itemFlags = flags & F_ALLOW_UNPARATH_NEW && !parentheses && length === 0 ? E_TFT : E_TFF;
96435
- result = join45(
97245
+ result = join44(
96436
97246
  "new",
96437
97247
  this.generateExpression(expr.callee, Precedence.New, itemFlags)
96438
97248
  );
@@ -96482,11 +97292,11 @@ var require_escodegen = __commonJS({
96482
97292
  var result, fragment, rightCharCode, leftSource, leftCharCode;
96483
97293
  fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT);
96484
97294
  if (space === "") {
96485
- result = join45(expr.operator, fragment);
97295
+ result = join44(expr.operator, fragment);
96486
97296
  } else {
96487
97297
  result = [expr.operator];
96488
97298
  if (expr.operator.length > 2) {
96489
- result = join45(result, fragment);
97299
+ result = join44(result, fragment);
96490
97300
  } else {
96491
97301
  leftSource = toSourceNodeWhenNeeded(result).toString();
96492
97302
  leftCharCode = leftSource.charCodeAt(leftSource.length - 1);
@@ -96509,7 +97319,7 @@ var require_escodegen = __commonJS({
96509
97319
  result = "yield";
96510
97320
  }
96511
97321
  if (expr.argument) {
96512
- result = join45(
97322
+ result = join44(
96513
97323
  result,
96514
97324
  this.generateExpression(expr.argument, Precedence.Yield, E_TTT)
96515
97325
  );
@@ -96517,7 +97327,7 @@ var require_escodegen = __commonJS({
96517
97327
  return parenthesize(result, Precedence.Yield, precedence);
96518
97328
  },
96519
97329
  AwaitExpression: function(expr, precedence, flags) {
96520
- var result = join45(
97330
+ var result = join44(
96521
97331
  expr.all ? "await*" : "await",
96522
97332
  this.generateExpression(expr.argument, Precedence.Await, E_TTT)
96523
97333
  );
@@ -96600,11 +97410,11 @@ var require_escodegen = __commonJS({
96600
97410
  var result, fragment;
96601
97411
  result = ["class"];
96602
97412
  if (expr.id) {
96603
- result = join45(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
97413
+ result = join44(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
96604
97414
  }
96605
97415
  if (expr.superClass) {
96606
- fragment = join45("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
96607
- result = join45(result, fragment);
97416
+ fragment = join44("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
97417
+ result = join44(result, fragment);
96608
97418
  }
96609
97419
  result.push(space);
96610
97420
  result.push(this.generateStatement(expr.body, S_TFFT));
@@ -96619,7 +97429,7 @@ var require_escodegen = __commonJS({
96619
97429
  }
96620
97430
  if (expr.kind === "get" || expr.kind === "set") {
96621
97431
  fragment = [
96622
- join45(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
97432
+ join44(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
96623
97433
  this.generateFunctionBody(expr.value)
96624
97434
  ];
96625
97435
  } else {
@@ -96629,7 +97439,7 @@ var require_escodegen = __commonJS({
96629
97439
  this.generateFunctionBody(expr.value)
96630
97440
  ];
96631
97441
  }
96632
- return join45(result, fragment);
97442
+ return join44(result, fragment);
96633
97443
  },
96634
97444
  Property: function(expr, precedence, flags) {
96635
97445
  if (expr.kind === "get" || expr.kind === "set") {
@@ -96824,7 +97634,7 @@ var require_escodegen = __commonJS({
96824
97634
  for (i2 = 0, iz = expr.blocks.length; i2 < iz; ++i2) {
96825
97635
  fragment = that.generateExpression(expr.blocks[i2], Precedence.Sequence, E_TTT);
96826
97636
  if (i2 > 0 || extra.moz.comprehensionExpressionStartsWithAssignment) {
96827
- result = join45(result, fragment);
97637
+ result = join44(result, fragment);
96828
97638
  } else {
96829
97639
  result.push(fragment);
96830
97640
  }
@@ -96832,13 +97642,13 @@ var require_escodegen = __commonJS({
96832
97642
  });
96833
97643
  }
96834
97644
  if (expr.filter) {
96835
- result = join45(result, "if" + space);
97645
+ result = join44(result, "if" + space);
96836
97646
  fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT);
96837
- result = join45(result, ["(", fragment, ")"]);
97647
+ result = join44(result, ["(", fragment, ")"]);
96838
97648
  }
96839
97649
  if (!extra.moz.comprehensionExpressionStartsWithAssignment) {
96840
97650
  fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT);
96841
- result = join45(result, fragment);
97651
+ result = join44(result, fragment);
96842
97652
  }
96843
97653
  result.push(expr.type === Syntax.GeneratorExpression ? ")" : "]");
96844
97654
  return result;
@@ -96854,8 +97664,8 @@ var require_escodegen = __commonJS({
96854
97664
  } else {
96855
97665
  fragment = this.generateExpression(expr.left, Precedence.Call, E_TTT);
96856
97666
  }
96857
- fragment = join45(fragment, expr.of ? "of" : "in");
96858
- fragment = join45(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
97667
+ fragment = join44(fragment, expr.of ? "of" : "in");
97668
+ fragment = join44(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
96859
97669
  return ["for" + space + "(", fragment, ")"];
96860
97670
  },
96861
97671
  SpreadElement: function(expr, precedence, flags) {
@@ -118146,17 +118956,17 @@ var init_cliui = __esm({
118146
118956
  });
118147
118957
 
118148
118958
  // ../../node_modules/.pnpm/escalade@3.2.0/node_modules/escalade/sync/index.mjs
118149
- import { dirname as dirname4, resolve as resolve9 } from "path";
118959
+ import { dirname as dirname4, resolve as resolve10 } from "path";
118150
118960
  import { readdirSync, statSync as statSync2 } from "fs";
118151
118961
  function sync_default(start, callback) {
118152
- let dir = resolve9(".", start);
118962
+ let dir = resolve10(".", start);
118153
118963
  let tmp, stats = statSync2(dir);
118154
118964
  if (!stats.isDirectory()) {
118155
118965
  dir = dirname4(dir);
118156
118966
  }
118157
118967
  while (true) {
118158
118968
  tmp = callback(dir, readdirSync(dir));
118159
- if (tmp) return resolve9(dir, tmp);
118969
+ if (tmp) return resolve10(dir, tmp);
118160
118970
  dir = dirname4(tmp = dir);
118161
118971
  if (tmp === dir) break;
118162
118972
  }
@@ -119122,7 +119932,7 @@ var init_yargs_parser = __esm({
119122
119932
 
119123
119933
  // ../../node_modules/.pnpm/yargs-parser@21.1.1/node_modules/yargs-parser/build/lib/index.js
119124
119934
  import { format } from "util";
119125
- import { normalize, resolve as resolve10 } from "path";
119935
+ import { normalize, resolve as resolve11 } from "path";
119126
119936
  import { readFileSync as readFileSync3 } from "fs";
119127
119937
  var _a3, _b, _c, minNodeVersion, nodeVersion, env2, parser, yargsParser, lib_default;
119128
119938
  var init_lib2 = __esm({
@@ -119145,7 +119955,7 @@ var init_lib2 = __esm({
119145
119955
  },
119146
119956
  format,
119147
119957
  normalize,
119148
- resolve: resolve10,
119958
+ resolve: resolve11,
119149
119959
  // TODO: figure out a way to combine ESM and CJS coverage, such that
119150
119960
  // we can exercise all the lines below:
119151
119961
  require: (path14) => {
@@ -119212,19 +120022,19 @@ var init_yerror = __esm({
119212
120022
  });
119213
120023
 
119214
120024
  // ../../node_modules/.pnpm/y18n@5.0.8/node_modules/y18n/build/lib/platform-shims/node.js
119215
- import { readFileSync as readFileSync4, statSync as statSync3, writeFile as writeFile8 } from "fs";
120025
+ import { readFileSync as readFileSync4, statSync as statSync3, writeFile as writeFile9 } from "fs";
119216
120026
  import { format as format2 } from "util";
119217
- import { resolve as resolve11 } from "path";
120027
+ import { resolve as resolve12 } from "path";
119218
120028
  var node_default;
119219
120029
  var init_node = __esm({
119220
120030
  "../../node_modules/.pnpm/y18n@5.0.8/node_modules/y18n/build/lib/platform-shims/node.js"() {
119221
120031
  node_default = {
119222
120032
  fs: {
119223
120033
  readFileSync: readFileSync4,
119224
- writeFile: writeFile8
120034
+ writeFile: writeFile9
119225
120035
  },
119226
120036
  format: format2,
119227
- resolve: resolve11,
120037
+ resolve: resolve12,
119228
120038
  exists: (file) => {
119229
120039
  try {
119230
120040
  return statSync3(file).isFile();
@@ -119419,7 +120229,7 @@ import { notStrictEqual, strictEqual } from "assert";
119419
120229
  import { inspect } from "util";
119420
120230
  import { readFileSync as readFileSync5 } from "fs";
119421
120231
  import { fileURLToPath } from "url";
119422
- import { basename as basename3, dirname as dirname5, extname as extname2, relative as relative2, resolve as resolve12 } from "path";
120232
+ import { basename as basename3, dirname as dirname5, extname as extname2, relative as relative2, resolve as resolve13 } from "path";
119423
120233
  var REQUIRE_ERROR, REQUIRE_DIRECTORY_ERROR, __dirname2, mainFilename, esm_default;
119424
120234
  var init_esm2 = __esm({
119425
120235
  "../../node_modules/.pnpm/yargs@17.7.2/node_modules/yargs/lib/platform-shims/esm.mjs"() {
@@ -119460,7 +120270,7 @@ var init_esm2 = __esm({
119460
120270
  dirname: dirname5,
119461
120271
  extname: extname2,
119462
120272
  relative: relative2,
119463
- resolve: resolve12
120273
+ resolve: resolve13
119464
120274
  },
119465
120275
  process: {
119466
120276
  argv: () => process.argv,
@@ -119482,7 +120292,7 @@ var init_esm2 = __esm({
119482
120292
  return [...str2].length;
119483
120293
  },
119484
120294
  y18n: y18n_default({
119485
- directory: resolve12(__dirname2, "../../../locales"),
120295
+ directory: resolve13(__dirname2, "../../../locales"),
119486
120296
  updateFiles: false
119487
120297
  })
119488
120298
  };
@@ -123257,9 +124067,9 @@ async function getConnectionTransport(options) {
123257
124067
  throw new Error("Could not detect required browser platform");
123258
124068
  }
123259
124069
  const { convertPuppeteerChannelToBrowsersChannel: convertPuppeteerChannelToBrowsersChannel3 } = await Promise.resolve().then(() => (init_LaunchOptions(), LaunchOptions_exports));
123260
- const { join: join45 } = await import("node:path");
124070
+ const { join: join44 } = await import("node:path");
123261
124071
  const userDataDir = resolveDefaultUserDataDir4(Browser5.CHROME, platform, convertPuppeteerChannelToBrowsersChannel3(options.channel));
123262
- const portPath = join45(userDataDir, "DevToolsActivePort");
124072
+ const portPath = join44(userDataDir, "DevToolsActivePort");
123263
124073
  try {
123264
124074
  const fileContent = await environment.value.fs.promises.readFile(portPath, "ascii");
123265
124075
  const [rawPort, rawPath] = fileContent.split("\n").map((line) => {
@@ -123582,7 +124392,7 @@ var init_PipeTransport = __esm({
123582
124392
  // ../../node_modules/.pnpm/puppeteer-core@24.41.0/node_modules/puppeteer-core/lib/esm/puppeteer/node/BrowserLauncher.js
123583
124393
  import { existsSync as existsSync5 } from "node:fs";
123584
124394
  import { tmpdir } from "node:os";
123585
- import { join as join4 } from "node:path";
124395
+ import { join as join5 } from "node:path";
123586
124396
  var BrowserLauncher;
123587
124397
  var init_BrowserLauncher = __esm({
123588
124398
  "../../node_modules/.pnpm/puppeteer-core@24.41.0/node_modules/puppeteer-core/lib/esm/puppeteer/node/BrowserLauncher.js"() {
@@ -123702,7 +124512,7 @@ var init_BrowserLauncher = __esm({
123702
124512
  if (logs.includes("Failed to create a ProcessSingleton for your profile directory") || // On Windows we will not get logs due to the singleton process
123703
124513
  // handover. See
123704
124514
  // https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/process_singleton_win.cc;l=46;drc=fc7952f0422b5073515a205a04ec9c3a1ae81658
123705
- process.platform === "win32" && existsSync5(join4(launchArgs.userDataDir, "lockfile"))) {
124515
+ process.platform === "win32" && existsSync5(join5(launchArgs.userDataDir, "lockfile"))) {
123706
124516
  throw new Error(`The browser is already running for ${launchArgs.userDataDir}. Use a different \`userDataDir\` or stop the running browser first.`);
123707
124517
  }
123708
124518
  if (logs.includes("Missing X server") && options.headless === false) {
@@ -123833,7 +124643,7 @@ var init_BrowserLauncher = __esm({
123833
124643
  * @internal
123834
124644
  */
123835
124645
  getProfilePath() {
123836
- return join4(this.puppeteer.configuration.temporaryDirectory ?? tmpdir(), `puppeteer_dev_${this.browser}_profile-`);
124646
+ return join5(this.puppeteer.configuration.temporaryDirectory ?? tmpdir(), `puppeteer_dev_${this.browser}_profile-`);
123837
124647
  }
123838
124648
  /**
123839
124649
  * @internal
@@ -292488,7 +293298,7 @@ ${newComment.split("\n").map((c2) => ` * ${c2}`).join("\n")}
292488
293298
  }
292489
293299
  }
292490
293300
  return result;
292491
- function escapeRegExp3(str2) {
293301
+ function escapeRegExp5(str2) {
292492
293302
  return str2.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
292493
293303
  }
292494
293304
  function getTodoCommentsRegExp() {
@@ -292496,7 +293306,7 @@ ${newComment.split("\n").map((c2) => ` * ${c2}`).join("\n")}
292496
293306
  const multiLineCommentStart = /(?:\/\*+\s*)/.source;
292497
293307
  const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
292498
293308
  const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
292499
- const literals = "(?:" + map3(descriptors, (d2) => "(" + escapeRegExp3(d2.text) + ")").join("|") + ")";
293309
+ const literals = "(?:" + map3(descriptors, (d2) => "(" + escapeRegExp5(d2.text) + ")").join("|") + ")";
292500
293310
  const endOfLineOrEndOfComment = /(?:$|\*\/)/.source;
292501
293311
  const messageRemainder = /(?:.*?)/.source;
292502
293312
  const messagePortion = "(" + literals + messageRemainder + ")";
@@ -340329,7 +341139,7 @@ var require_dist15 = __commonJS({
340329
341139
 
340330
341140
  // ../../node_modules/.pnpm/puppeteer@24.41.0_typescript@5.9.3/node_modules/puppeteer/lib/esm/puppeteer/getConfiguration.js
340331
341141
  import { homedir as homedir3 } from "node:os";
340332
- import { join as join5 } from "node:path";
341142
+ import { join as join6 } from "node:path";
340333
341143
  function getBooleanEnvVar(name) {
340334
341144
  const env4 = process.env[name];
340335
341145
  if (env4 === void 0) {
@@ -340409,7 +341219,7 @@ var init_getConfiguration = __esm({
340409
341219
  configuration2.firefox = getBrowserSetting("firefox", configuration2, {
340410
341220
  skipDownload: true
340411
341221
  });
340412
- configuration2.cacheDirectory = process.env["PUPPETEER_CACHE_DIR"] ?? configuration2.cacheDirectory ?? join5(homedir3(), ".cache", "puppeteer");
341222
+ configuration2.cacheDirectory = process.env["PUPPETEER_CACHE_DIR"] ?? configuration2.cacheDirectory ?? join6(homedir3(), ".cache", "puppeteer");
340413
341223
  configuration2.temporaryDirectory = process.env["PUPPETEER_TMP_DIR"] ?? configuration2.temporaryDirectory;
340414
341224
  configuration2.experiments ??= {};
340415
341225
  return configuration2;
@@ -340737,7 +341547,7 @@ import { readdirSync as readdirSync2, statSync as statSync4 } from "fs";
340737
341547
  import { format as format3 } from "util";
340738
341548
  import { normalize as normalize2, resolve as resolve32 } from "path";
340739
341549
  import { readFileSync as readFileSync22 } from "fs";
340740
- import { readFileSync as readFileSync32, statSync as statSync22, writeFile as writeFile9 } from "fs";
341550
+ import { readFileSync as readFileSync32, statSync as statSync22, writeFile as writeFile10 } from "fs";
340741
341551
  import { format as format22 } from "util";
340742
341552
  import { resolve as resolve42 } from "path";
340743
341553
  import { notStrictEqual as notStrictEqual2, strictEqual as strictEqual2 } from "assert";
@@ -340795,7 +341605,7 @@ import { join as join8 } from "path";
340795
341605
  import { spawn as spawn7 } from "child_process";
340796
341606
  import { createWriteStream as createWriteStream22, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
340797
341607
  import { createHash as createHash3 } from "crypto";
340798
- import { join as join7, extname as extname22 } from "path";
341608
+ import { join as join72, extname as extname22 } from "path";
340799
341609
  import { Readable } from "stream";
340800
341610
  import { finished } from "stream/promises";
340801
341611
  import { promises as fs72 } from "fs";
@@ -350883,7 +351693,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
350883
351693
  mkdirSync4(destDir, { recursive: true });
350884
351694
  }
350885
351695
  const filename = getFilenameFromUrl(url);
350886
- const localPath = join7(destDir, filename);
351696
+ const localPath = join72(destDir, filename);
350887
351697
  if (existsSync7(localPath)) {
350888
351698
  downloadPathCache.set(url, localPath);
350889
351699
  return localPath;
@@ -398898,14 +399708,14 @@ ${sourceUrlComment}
398898
399708
  return value;
398899
399709
  };
398900
399710
  var createToken = (name, value, isGlobal) => {
398901
- const safe = makeSafeRegex(value);
399711
+ const safe2 = makeSafeRegex(value);
398902
399712
  const index = R2++;
398903
399713
  debug62(name, index, value);
398904
399714
  t2[name] = index;
398905
399715
  src[index] = value;
398906
- safeSrc[index] = safe;
399716
+ safeSrc[index] = safe2;
398907
399717
  re2[index] = new RegExp(value, isGlobal ? "g" : void 0);
398908
- safeRe[index] = new RegExp(safe, isGlobal ? "g" : void 0);
399718
+ safeRe[index] = new RegExp(safe2, isGlobal ? "g" : void 0);
398909
399719
  };
398910
399720
  createToken("NUMERICIDENTIFIER", "0|[1-9]\\d*");
398911
399721
  createToken("NUMERICIDENTIFIERLOOSE", "\\d+");
@@ -433132,7 +433942,7 @@ If you think this is a bug, please report it on the Puppeteer issue tracker.`;
433132
433942
  node_default2 = {
433133
433943
  fs: {
433134
433944
  readFileSync: readFileSync32,
433135
- writeFile: writeFile9
433945
+ writeFile: writeFile10
433136
433946
  },
433137
433947
  format: format22,
433138
433948
  resolve: resolve42,
@@ -450055,14 +450865,14 @@ ${right22.raw}`)
450055
450865
  });
450056
450866
 
450057
450867
  // ../cli/src/commands/_shared/scene-lint.ts
450058
- import { readdir as readdir4, readFile as readFile7, writeFile as writeFile10, stat as stat2 } from "node:fs/promises";
450868
+ import { readdir as readdir4, readFile as readFile7, writeFile as writeFile11, stat as stat2 } from "node:fs/promises";
450059
450869
  import { existsSync as existsSync18 } from "node:fs";
450060
- import { resolve as resolve14, relative as relative4, join as join18 } from "node:path";
450870
+ import { resolve as resolve15, relative as relative4, join as join18 } from "node:path";
450061
450871
  async function discoverSceneFiles(opts) {
450062
- const projectDir = resolve14(opts.projectDir);
450063
- const rootAbs = resolve14(projectDir, opts.rootRel ?? "index.html");
450872
+ const projectDir = resolve15(opts.projectDir);
450873
+ const rootAbs = resolve15(projectDir, opts.rootRel ?? "index.html");
450064
450874
  const root2 = existsSync18(rootAbs) ? rootAbs : null;
450065
- const compsDir = resolve14(projectDir, "compositions");
450875
+ const compsDir = resolve15(projectDir, "compositions");
450066
450876
  const subs = [];
450067
450877
  if (existsSync18(compsDir)) {
450068
450878
  await collectHtmlRecursive(compsDir, subs);
@@ -450148,7 +450958,7 @@ function addClipClassToTimedElements(html) {
450148
450958
  });
450149
450959
  }
450150
450960
  async function runProjectLint(opts) {
450151
- const projectDir = resolve14(opts.projectDir);
450961
+ const projectDir = resolve15(opts.projectDir);
450152
450962
  const { root: root2, subs } = await discoverSceneFiles({ projectDir, rootRel: opts.rootRel });
450153
450963
  const files = [];
450154
450964
  const fixed = [];
@@ -450172,7 +450982,7 @@ async function runProjectLint(opts) {
450172
450982
  if (opts.fix && findings.length > 0) {
450173
450983
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
450174
450984
  if (fixedCodes.length > 0) {
450175
- await writeFile10(abs, nextHtml, "utf-8");
450985
+ await writeFile11(abs, nextHtml, "utf-8");
450176
450986
  fixed.push({ file: rel, codes: fixedCodes });
450177
450987
  const reLinted = runHyperframeLint({ ...prepared, html: nextHtml });
450178
450988
  files.push({
@@ -450209,7 +451019,7 @@ async function runProjectLint(opts) {
450209
451019
  };
450210
451020
  }
450211
451021
  async function rootExists(projectDir, rootRel = "index.html") {
450212
- const abs = resolve14(projectDir, rootRel);
451022
+ const abs = resolve15(projectDir, rootRel);
450213
451023
  try {
450214
451024
  const s2 = await stat2(abs);
450215
451025
  return s2.isFile();
@@ -469209,12 +470019,12 @@ var init_dist3 = __esm({
469209
470019
 
469210
470020
  // ../cli/src/commands/_shared/root-sync.ts
469211
470021
  import { existsSync as existsSync23 } from "node:fs";
469212
- import { readFile as readFile8, writeFile as writeFile12 } from "node:fs/promises";
469213
- import { join as join23, resolve as resolve16 } from "node:path";
470022
+ import { readFile as readFile8, writeFile as writeFile13 } from "node:fs/promises";
470023
+ import { join as join23, resolve as resolve17 } from "node:path";
469214
470024
  async function createRootSyncPlan(opts) {
469215
- const projectDir = resolve16(opts.projectDir);
470025
+ const projectDir = resolve17(opts.projectDir);
469216
470026
  const rootRel = opts.rootRel ?? "index.html";
469217
- const rootPath = resolve16(projectDir, rootRel);
470027
+ const rootPath = resolve17(projectDir, rootRel);
469218
470028
  if (!existsSync23(rootPath)) {
469219
470029
  return { rootPath, rootRel, exists: false, changed: false, fixCodes: [], issues: [] };
469220
470030
  }
@@ -469272,11 +470082,11 @@ async function createProjectRootSyncPlan(opts) {
469272
470082
  }
469273
470083
  async function syncRootComposition(opts) {
469274
470084
  const plan = await createRootSyncPlan(opts);
469275
- if (plan.nextHtml) await writeFile12(plan.rootPath, plan.nextHtml, "utf-8");
470085
+ if (plan.nextHtml) await writeFile13(plan.rootPath, plan.nextHtml, "utf-8");
469276
470086
  return plan;
469277
470087
  }
469278
470088
  async function loadProjectRootSyncBeats(projectDir) {
469279
- const root2 = resolve16(projectDir);
470089
+ const root2 = resolve17(projectDir);
469280
470090
  const storyboardPath = join23(root2, "STORYBOARD.md");
469281
470091
  if (!existsSync23(storyboardPath)) return [];
469282
470092
  const parsed = parseStoryboard(await readFile8(storyboardPath, "utf-8"));
@@ -469353,7 +470163,7 @@ async function resolveSyncedBeatDuration(opts) {
469353
470163
  }
469354
470164
  }
469355
470165
  async function resolveProjectBeatDurations(projectDir) {
469356
- const root2 = resolve16(projectDir);
470166
+ const root2 = resolve17(projectDir);
469357
470167
  const beats = await loadProjectRootSyncBeats(root2);
469358
470168
  const durations = /* @__PURE__ */ new Map();
469359
470169
  for (const beat of beats) {
@@ -469602,8 +470412,8 @@ __export(compose_scenes_skills_exports, {
469602
470412
  });
469603
470413
  import { createHash as createHash5 } from "node:crypto";
469604
470414
  import { existsSync as existsSync24, mkdirSync as mkdirSync12 } from "node:fs";
469605
- import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile13 } from "node:fs/promises";
469606
- import { dirname as dirname13, join as join24, resolve as resolve17 } from "node:path";
470415
+ import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile14 } from "node:fs/promises";
470416
+ import { dirname as dirname13, join as join24, resolve as resolve18 } from "node:path";
469607
470417
  function buildSystemPrompt(ctx) {
469608
470418
  return `You are a Hyperframes composition author. The skill content below
469609
470419
  defines the framework rules, motion principles, and quality standards.
@@ -469696,7 +470506,15 @@ ${finalDurationBullet}
469696
470506
  itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
469697
470507
  inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
469698
470508
  phase B in, on the same timeline.
469699
- - If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
470509
+ - If \`assets/video-${ctx.beat.id}.mp4\` exists, it is an AI-generated clip for
470510
+ this beat \u2014 use it as the FULL-FRAME visual and prefer it over any backdrop
470511
+ image or CSS. Put one full-bleed \`<video>\` inside a full-window \`.clip\`:
470512
+ \`<video src="assets/video-${ctx.beat.id}.mp4" data-start="0" data-duration="${BEAT_DURATION}" data-media-start="0" muted playsinline style="width:100%;height:100%;object-fit:cover"></video>\`
470513
+ Use the exact path (no \`./\` or \`../\`). The framework seeks the clip to each
470514
+ timeline frame, so do NOT add transform tweens to the \`<video>\` or its
470515
+ ancestors; layer text/motion graphics on top instead.
470516
+ - If \`assets/backdrop-${ctx.beat.id}.png\` exists (and no video clip above),
470517
+ use that local file as the
469700
470518
  full-frame visual backdrop. The exact path string is
469701
470519
  \`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
469702
470520
  because fragments are mounted from the project root.
@@ -469863,7 +470681,7 @@ async function composeBeatHtml(ctx, overrides) {
469863
470681
  const html = extractHtml(result.text);
469864
470682
  try {
469865
470683
  mkdirSync12(cacheDir, { recursive: true });
469866
- await writeFile13(cachePath, html, "utf-8");
470684
+ await writeFile14(cachePath, html, "utf-8");
469867
470685
  } catch {
469868
470686
  }
469869
470687
  const costUsd = result.inputTokens / 1e6 * settings.costPerMTokIn + result.outputTokens / 1e6 * settings.costPerMTokOut;
@@ -469962,9 +470780,9 @@ async function resolveComposerConfigAware(explicit, cwd) {
469962
470780
  return resolveComposer();
469963
470781
  }
469964
470782
  async function executeComposeScenesWithSkills(params, outputDir, overrides) {
469965
- const projectRoot = params.project ? resolve17(outputDir, params.project) : resolve17(outputDir);
469966
- const designPath = resolve17(projectRoot, params.design ?? "DESIGN.md");
469967
- const storyboardPath = resolve17(projectRoot, params.storyboard ?? "STORYBOARD.md");
470783
+ const projectRoot = params.project ? resolve18(outputDir, params.project) : resolve18(outputDir);
470784
+ const designPath = resolve18(projectRoot, params.design ?? "DESIGN.md");
470785
+ const storyboardPath = resolve18(projectRoot, params.storyboard ?? "STORYBOARD.md");
469968
470786
  if (!existsSync24(designPath)) {
469969
470787
  return { success: false, error: `DESIGN.md not found at ${designPath}` };
469970
470788
  }
@@ -470022,7 +470840,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
470022
470840
  );
470023
470841
  const compositionPath = join24(compositionsDir, `scene-${beat.id}.html`);
470024
470842
  await mkdir9(dirname13(compositionPath), { recursive: true });
470025
- await writeFile13(compositionPath, result.html, "utf-8");
470843
+ await writeFile14(compositionPath, result.html, "utf-8");
470026
470844
  if (result.cached) {
470027
470845
  onProgress({
470028
470846
  type: "beat-cached",
@@ -470225,9 +471043,9 @@ var init_compose_scenes_skills = __esm({
470225
471043
  // ../cli/src/commands/_shared/compose-prompts.ts
470226
471044
  import { existsSync as existsSync25 } from "node:fs";
470227
471045
  import { readFile as readFile10 } from "node:fs/promises";
470228
- import { join as join25, relative as relative6, resolve as resolve18 } from "node:path";
471046
+ import { join as join25, relative as relative6, resolve as resolve19 } from "node:path";
470229
471047
  async function getComposePrompts(opts) {
470230
- const projectDir = resolve18(opts.projectDir);
471048
+ const projectDir = resolve19(opts.projectDir);
470231
471049
  const designPath = join25(projectDir, "DESIGN.md");
470232
471050
  const storyboardPath = join25(projectDir, "STORYBOARD.md");
470233
471051
  const skillPath = join25(projectDir, "SKILL.md");
@@ -470354,7 +471172,7 @@ var init_compose_prompts = __esm({
470354
471172
  // ../cli/src/commands/_shared/sub-comp-duration-sync.ts
470355
471173
  import { existsSync as existsSync27 } from "node:fs";
470356
471174
  import { readFile as readFile12 } from "node:fs/promises";
470357
- import { join as join27, resolve as resolve20 } from "node:path";
471175
+ import { join as join27, resolve as resolve21 } from "node:path";
470358
471176
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
470359
471177
  const compositionId = `scene-${beatId}`;
470360
471178
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -470442,7 +471260,7 @@ function rewriteTimelineConst(html, oldDurationSec, newDurationSec) {
470442
471260
  return { html, rewritten: false, needsManualFix };
470443
471261
  }
470444
471262
  async function createSubCompDurationSyncPlans(opts) {
470445
- const projectDir = resolve20(opts.projectDir);
471263
+ const projectDir = resolve21(opts.projectDir);
470446
471264
  const results = [];
470447
471265
  for (const beat of opts.beats) {
470448
471266
  const fileRel = `compositions/scene-${beat.id}.html`;
@@ -470477,88 +471295,6 @@ var init_sub_comp_duration_sync = __esm({
470477
471295
  }
470478
471296
  });
470479
471297
 
470480
- // ../cli/src/commands/_shared/review-report.ts
470481
- import { writeFile as writeFile15 } from "node:fs/promises";
470482
- import { join as join28, resolve as resolve21 } from "node:path";
470483
- function statusFromIssues(issues) {
470484
- if (issues.some((issue) => issue.severity === "error")) return "fail";
470485
- if (issues.some((issue) => issue.severity === "warning")) return "warn";
470486
- return "pass";
470487
- }
470488
- function scoreIssues(issues) {
470489
- let score = 100;
470490
- for (const issue of issues) {
470491
- if (issue.severity === "error") score -= 25;
470492
- else if (issue.severity === "warning") score -= 8;
470493
- else score -= 2;
470494
- }
470495
- return Math.max(0, Math.min(100, score));
470496
- }
470497
- function uniqueRetryWith(items) {
470498
- return [
470499
- ...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
470500
- ];
470501
- }
470502
- function fixOwnerForIssue(issue) {
470503
- if (issue.fixOwner) return issue.fixOwner;
470504
- if (issue.code.startsWith("AI_REVIEW_")) return "host-agent";
470505
- if (issue.code.startsWith("STORYBOARD_")) return "host-agent";
470506
- if (issue.code === "PROJECT_NOT_FOUND" || issue.code === "MISSING_STORYBOARD" || issue.code === "BEAT_NOT_FOUND" || issue.code === "MISSING_DESIGN") {
470507
- return "host-agent";
470508
- }
470509
- return "vibe";
470510
- }
470511
- function normalizeReviewIssues(issues, fallbackOwner) {
470512
- return issues.map((issue) => ({
470513
- ...issue,
470514
- fixOwner: issue.fixOwner ?? fallbackOwner ?? fixOwnerForIssue(issue)
470515
- }));
470516
- }
470517
- function summarizeReviewIssues(issues) {
470518
- const normalized = normalizeReviewIssues(issues);
470519
- return {
470520
- issueCount: normalized.length,
470521
- errorCount: normalized.filter((issue) => issue.severity === "error").length,
470522
- warningCount: normalized.filter((issue) => issue.severity === "warning").length,
470523
- infoCount: normalized.filter((issue) => issue.severity === "info").length,
470524
- fixOwners: {
470525
- vibe: normalized.filter((issue) => issue.fixOwner === "vibe").length,
470526
- hostAgent: normalized.filter((issue) => issue.fixOwner === "host-agent").length
470527
- }
470528
- };
470529
- }
470530
- function buildReviewReport(opts) {
470531
- const issues = normalizeReviewIssues(opts.issues);
470532
- return stripUndefined2({
470533
- schemaVersion: "1",
470534
- kind: "review",
470535
- project: resolve21(opts.project),
470536
- mode: opts.mode,
470537
- beat: opts.beat,
470538
- status: opts.status,
470539
- score: opts.score,
470540
- issues,
470541
- summary: summarizeReviewIssues(issues),
470542
- retryWith: uniqueRetryWith(opts.retryWith),
470543
- sourceReports: opts.sourceReports ?? [],
470544
- reportPath: opts.reportPath
470545
- });
470546
- }
470547
- function defaultReviewReportPath(projectDir) {
470548
- return join28(resolve21(projectDir), "review-report.json");
470549
- }
470550
- async function writeReviewReport(path14, report) {
470551
- await writeFile15(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
470552
- }
470553
- function stripUndefined2(value) {
470554
- return Object.fromEntries(Object.entries(value).filter(([, v2]) => v2 !== void 0));
470555
- }
470556
- var init_review_report = __esm({
470557
- "../cli/src/commands/_shared/review-report.ts"() {
470558
- "use strict";
470559
- }
470560
- });
470561
-
470562
471298
  // ../cli/src/commands/_shared/scene-repair.ts
470563
471299
  import { readFile as readFile13, writeFile as writeFile16 } from "node:fs/promises";
470564
471300
  import { relative as relative7, resolve as resolve23 } from "node:path";
@@ -470685,16 +471421,307 @@ var init_scene_repair = __esm({
470685
471421
  }
470686
471422
  });
470687
471423
 
471424
+ // ../cli/src/commands/_shared/storyboard-edit.ts
471425
+ function validateStoryboardMarkdown(markdown) {
471426
+ const parsed = parseStoryboard(markdown);
471427
+ const sections = splitBeatSections(markdown);
471428
+ const issues = [];
471429
+ if (parsed.beats.length === 0) {
471430
+ issues.push({
471431
+ severity: "error",
471432
+ code: "NO_BEATS",
471433
+ message: "STORYBOARD.md must contain at least one `## Beat ...` heading."
471434
+ });
471435
+ }
471436
+ const seen = /* @__PURE__ */ new Map();
471437
+ for (const beat of parsed.beats) {
471438
+ seen.set(beat.id, (seen.get(beat.id) ?? 0) + 1);
471439
+ }
471440
+ for (const [id, count] of seen.entries()) {
471441
+ if (count > 1) {
471442
+ issues.push({
471443
+ severity: "error",
471444
+ code: "DUPLICATE_BEAT_ID",
471445
+ beatId: id,
471446
+ message: `Beat id "${id}" appears ${count} times. Beat ids must be unique.`
471447
+ });
471448
+ }
471449
+ }
471450
+ for (const section of sections) {
471451
+ const cueBlock = readLeadingCueBlock(section.body);
471452
+ if (cueBlock?.error) {
471453
+ issues.push({
471454
+ severity: "error",
471455
+ code: "MALFORMED_CUE_YAML",
471456
+ beatId: section.id,
471457
+ message: `Beat "${section.id}" has malformed YAML cues: ${cueBlock.error}`
471458
+ });
471459
+ }
471460
+ }
471461
+ const characterPool = new Set(
471462
+ Object.keys(
471463
+ parsed.frontmatter?.characters ?? {}
471464
+ )
471465
+ );
471466
+ for (const beat of parsed.beats) {
471467
+ const cues = beat.cues ?? {};
471468
+ for (const [key2, value] of Object.entries(cues)) {
471469
+ if (!ALLOWED_CUE_KEYS.has(key2)) {
471470
+ issues.push({
471471
+ severity: "warning",
471472
+ code: "UNKNOWN_CUE",
471473
+ beatId: beat.id,
471474
+ message: `Beat "${beat.id}" uses unknown cue "${key2}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`
471475
+ });
471476
+ continue;
471477
+ }
471478
+ if (key2 === "characters") {
471479
+ const validShape = typeof value === "string" || Array.isArray(value) && value.every((v2) => typeof v2 === "string");
471480
+ if (!validShape) {
471481
+ issues.push({
471482
+ severity: "error",
471483
+ code: "INVALID_CHARACTERS_VALUE",
471484
+ beatId: beat.id,
471485
+ message: `Beat "${beat.id}" cue "characters" must be a character name or a list of names.`
471486
+ });
471487
+ continue;
471488
+ }
471489
+ for (const name of beatCharacterNames({ characters: value })) {
471490
+ if (!characterPool.has(name)) {
471491
+ issues.push({
471492
+ severity: "warning",
471493
+ code: "UNKNOWN_CHARACTER",
471494
+ beatId: beat.id,
471495
+ message: `Beat "${beat.id}" references character "${name}" not defined in the frontmatter \`characters:\` pool.`
471496
+ });
471497
+ }
471498
+ }
471499
+ continue;
471500
+ }
471501
+ if (key2 === "duration") {
471502
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
471503
+ issues.push({
471504
+ severity: "error",
471505
+ code: "INVALID_DURATION",
471506
+ beatId: beat.id,
471507
+ message: `Beat "${beat.id}" cue "duration" must be a positive number of seconds.`
471508
+ });
471509
+ }
471510
+ continue;
471511
+ }
471512
+ if (STRING_CUE_KEYS.has(key2) && typeof value !== "string") {
471513
+ issues.push({
471514
+ severity: "error",
471515
+ code: "INVALID_CUE_VALUE",
471516
+ beatId: beat.id,
471517
+ message: `Beat "${beat.id}" cue "${key2}" must be a string.`
471518
+ });
471519
+ }
471520
+ }
471521
+ }
471522
+ for (const beat of parsed.beats) {
471523
+ if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
471524
+ issues.push({
471525
+ severity: "warning",
471526
+ code: "BEAT_DURATION_TOO_LONG",
471527
+ beatId: beat.id,
471528
+ message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
471529
+ });
471530
+ }
471531
+ }
471532
+ return {
471533
+ ok: !issues.some((i2) => i2.severity === "error"),
471534
+ beats: parsed.beats,
471535
+ issues
471536
+ };
471537
+ }
471538
+ function getStoryboardBeat(markdown, beatId) {
471539
+ return parseStoryboard(markdown).beats.find((beat) => beat.id === beatId) ?? null;
471540
+ }
471541
+ function upsertStoryboardBeat(markdown, opts) {
471542
+ const beatId = normalizeBeatId(opts.beatId);
471543
+ const sections = splitBeatSections(markdown);
471544
+ const existing = sections.find((section2) => section2.id === beatId);
471545
+ const cues = storyboardCuesFromInput(opts);
471546
+ if (existing) {
471547
+ let next = markdown;
471548
+ for (const [key2, value] of Object.entries(cues)) {
471549
+ next = setStoryboardCue(next, { beatId, key: key2, value });
471550
+ }
471551
+ return { markdown: next, action: "updated" };
471552
+ }
471553
+ const section = buildStoryboardBeatSection({ ...opts, beatId, cues });
471554
+ if (sections.length > 0 && isStarterStoryboard(markdown)) {
471555
+ const global3 = markdown.slice(0, sections[0].start).trimEnd();
471556
+ return {
471557
+ markdown: `${global3}
471558
+
471559
+ ${section}`,
471560
+ action: "replaced-starter"
471561
+ };
471562
+ }
471563
+ const prefix = markdown.trimEnd();
471564
+ return {
471565
+ markdown: `${prefix}${prefix ? "\n\n" : ""}${section}`,
471566
+ action: "appended"
471567
+ };
471568
+ }
471569
+ function setStoryboardCue(markdown, opts) {
471570
+ const section = findBeatSection(markdown, opts.beatId);
471571
+ if (!section) {
471572
+ throw new Error(`Beat "${opts.beatId}" not found.`);
471573
+ }
471574
+ if (!ALLOWED_CUE_KEYS.has(opts.key)) {
471575
+ throw new Error(`Unsupported cue "${opts.key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`);
471576
+ }
471577
+ const cue = readLeadingCueBlock(section.body);
471578
+ if (cue?.error) {
471579
+ throw new Error(`Cannot edit malformed YAML cues for beat "${opts.beatId}": ${cue.error}`);
471580
+ }
471581
+ const cues = cue?.value ? { ...cue.value } : {};
471582
+ if (opts.unset) {
471583
+ delete cues[opts.key];
471584
+ } else {
471585
+ cues[opts.key] = normalizeCueValue(opts.key, opts.value);
471586
+ }
471587
+ const remainingBody = cue ? section.body.slice(cue.full.length).replace(/^\s*\n/, "") : section.body.replace(/^\s*\n/, "");
471588
+ const cueBlock = Object.keys(cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
471589
+ const nextBody = cueBlock + remainingBody.trimStart();
471590
+ const nextSection = section.raw.slice(0, section.headingEnd - section.start) + "\n\n" + nextBody.trimEnd() + "\n";
471591
+ return markdown.slice(0, section.start) + nextSection + markdown.slice(section.end);
471592
+ }
471593
+ function moveStoryboardBeat(markdown, opts) {
471594
+ if (opts.beatId === opts.afterBeatId) return markdown;
471595
+ const sections = splitBeatSections(markdown);
471596
+ const movingIndex = sections.findIndex((section) => section.id === opts.beatId);
471597
+ if (movingIndex === -1) throw new Error(`Beat "${opts.beatId}" not found.`);
471598
+ const afterIndex = sections.findIndex((section) => section.id === opts.afterBeatId);
471599
+ if (afterIndex === -1) throw new Error(`Beat "${opts.afterBeatId}" not found.`);
471600
+ const global3 = sections.length > 0 ? markdown.slice(0, sections[0].start) : markdown;
471601
+ const chunks = sections.map((section) => section.raw);
471602
+ const [moving] = chunks.splice(movingIndex, 1);
471603
+ const adjustedAfterIndex = movingIndex < afterIndex ? afterIndex - 1 : afterIndex;
471604
+ chunks.splice(adjustedAfterIndex + 1, 0, moving);
471605
+ return global3 + normalizeBeatChunks(chunks);
471606
+ }
471607
+ function normalizeBeatChunks(chunks) {
471608
+ return chunks.map((chunk) => chunk.trim()).filter(Boolean).join("\n\n") + "\n";
471609
+ }
471610
+ function normalizeBeatId(id) {
471611
+ return deriveBeatId(id);
471612
+ }
471613
+ function storyboardCuesFromInput(opts) {
471614
+ const cues = {};
471615
+ if (opts.duration !== void 0) cues.duration = opts.duration;
471616
+ if (opts.narration && opts.narration.trim()) cues.narration = opts.narration.trim();
471617
+ if (opts.backdrop && opts.backdrop.trim()) cues.backdrop = opts.backdrop.trim();
471618
+ return cues;
471619
+ }
471620
+ function buildStoryboardBeatSection(opts) {
471621
+ const title = normalizeTitle(opts.title ?? humanizeBeatId(opts.beatId));
471622
+ const cueBlock = Object.keys(opts.cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(opts.cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
471623
+ const body = opts.body?.trim() || "Scene created with `vibe scene add`. Edit this body if the build flow should recompose it.";
471624
+ return `## Beat ${opts.beatId} - ${title}
471625
+
471626
+ ${cueBlock}${body.trimEnd()}
471627
+ `;
471628
+ }
471629
+ function normalizeTitle(value) {
471630
+ return value.replace(/\s+/g, " ").trim() || "Scene";
471631
+ }
471632
+ function humanizeBeatId(id) {
471633
+ return id.split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || "Scene";
471634
+ }
471635
+ function isStarterStoryboard(markdown) {
471636
+ const parsed = parseStoryboard(markdown);
471637
+ if (parsed.beats.length !== 3) return false;
471638
+ const ids = parsed.beats.map((beat) => beat.id);
471639
+ if (ids.join(",") !== "hook,proof,close") return false;
471640
+ return markdown.includes("Introduce the promise in one crisp sentence.") && markdown.includes("Show the mechanism or proof point that makes the promise believable.") && markdown.includes("Close with the action the viewer should remember.");
471641
+ }
471642
+ function normalizeCueValue(key2, value) {
471643
+ if (key2 === "duration") {
471644
+ const n2 = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
471645
+ if (!Number.isFinite(n2) || n2 <= 0) {
471646
+ throw new Error("Cue duration must be a positive number.");
471647
+ }
471648
+ return n2;
471649
+ }
471650
+ if (typeof value !== "string") return value;
471651
+ return value.trim();
471652
+ }
471653
+ function splitBeatSections(markdown) {
471654
+ const normalized = markdown.replace(/\r\n/g, "\n");
471655
+ const headings = [];
471656
+ HEADING_RE2.lastIndex = 0;
471657
+ let match2;
471658
+ while ((match2 = HEADING_RE2.exec(normalized)) !== null) {
471659
+ headings.push({ start: match2.index, end: match2.index + match2[0].length, line: match2[1].trim() });
471660
+ }
471661
+ return headings.map((heading, index) => {
471662
+ const end = index + 1 < headings.length ? headings[index + 1].start : normalized.length;
471663
+ return {
471664
+ id: deriveBeatId(heading.line),
471665
+ heading: heading.line,
471666
+ start: heading.start,
471667
+ headingEnd: heading.end,
471668
+ end,
471669
+ raw: normalized.slice(heading.start, end),
471670
+ body: normalized.slice(heading.end, end).trim()
471671
+ };
471672
+ });
471673
+ }
471674
+ function findBeatSection(markdown, beatId) {
471675
+ return splitBeatSections(markdown).find((section) => section.id === beatId) ?? null;
471676
+ }
471677
+ function readLeadingCueBlock(body) {
471678
+ const match2 = body.match(LEADING_CUE_RE);
471679
+ if (!match2) return null;
471680
+ try {
471681
+ const parsed = (0, import_yaml5.parse)(match2[2]);
471682
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
471683
+ return { full: match2[0], error: "cue block must parse to a YAML object" };
471684
+ }
471685
+ return { full: match2[0], value: parsed };
471686
+ } catch (error) {
471687
+ return { full: match2[0], error: error instanceof Error ? error.message : String(error) };
471688
+ }
471689
+ }
471690
+ var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
471691
+ var init_storyboard_edit = __esm({
471692
+ "../cli/src/commands/_shared/storyboard-edit.ts"() {
471693
+ "use strict";
471694
+ import_yaml5 = __toESM(require_dist3(), 1);
471695
+ init_storyboard_parse();
471696
+ STORYBOARD_CUE_KEYS = [
471697
+ "duration",
471698
+ "narration",
471699
+ "backdrop",
471700
+ "video",
471701
+ "motion",
471702
+ "voice",
471703
+ "music",
471704
+ "asset",
471705
+ "characters"
471706
+ ];
471707
+ HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
471708
+ LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
471709
+ ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
471710
+ STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
471711
+ MAX_RECOMMENDED_BEAT_SEC = 15;
471712
+ }
471713
+ });
471714
+
470688
471715
  // ../cli/src/pipeline/renderers/chrome.ts
470689
471716
  import { existsSync as existsSync29 } from "node:fs";
470690
471717
  import { homedir as homedir7 } from "node:os";
470691
- import { join as join29 } from "node:path";
471718
+ import { join as join28 } from "node:path";
470692
471719
  function findChrome() {
470693
471720
  const candidates = [
470694
471721
  process.env.HYPERFRAMES_CHROME_PATH,
470695
471722
  process.env.CHROME_PATH,
470696
471723
  // puppeteer auto-downloaded headless shell
470697
- join29(
471724
+ join28(
470698
471725
  homedir7(),
470699
471726
  ".cache",
470700
471727
  "puppeteer",
@@ -470987,7 +472014,7 @@ __export(scene_render_exports, {
470987
472014
  import { execFile as execFile2 } from "node:child_process";
470988
472015
  import { existsSync as existsSync30 } from "node:fs";
470989
472016
  import { mkdir as mkdir12, readFile as readFile16, stat as stat3, writeFile as writeFile18 } from "node:fs/promises";
470990
- import { resolve as resolve27, relative as relative9, dirname as dirname16, basename as basename7, join as join30, isAbsolute as isAbsolute3 } from "node:path";
472017
+ import { resolve as resolve27, relative as relative9, dirname as dirname16, basename as basename7, join as join29, isAbsolute as isAbsolute3 } from "node:path";
470991
472018
  import { promisify as promisify2 } from "node:util";
470992
472019
  function qualityToCrf(quality = "standard") {
470993
472020
  return quality === "draft" ? 28 : quality === "high" ? 18 : 23;
@@ -471075,7 +472102,7 @@ async function executeSceneRender(opts = {}) {
471075
472102
  }
471076
472103
  let autoSyncApplied = false;
471077
472104
  let autoSyncWarning;
471078
- if (!opts.beatId && existsSync30(join30(projectDir, "STORYBOARD.md"))) {
472105
+ if (!opts.beatId && existsSync30(join29(projectDir, "STORYBOARD.md"))) {
471079
472106
  try {
471080
472107
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
471081
472108
  const subPlans = await createSubCompDurationSyncPlans({
@@ -471252,7 +472279,7 @@ async function safeStat(p2) {
471252
472279
  }
471253
472280
  async function prepareBeatRenderRoot(opts) {
471254
472281
  const root2 = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
471255
- const reportPath = join30(opts.projectDir, "build-report.json");
472282
+ const reportPath = join29(opts.projectDir, "build-report.json");
471256
472283
  const retryWith = [
471257
472284
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
471258
472285
  ];
@@ -471336,8 +472363,8 @@ async function prepareBeatRenderRoot(opts) {
471336
472363
  durationSec,
471337
472364
  narrationPath
471338
472365
  });
471339
- await mkdir12(dirname16(join30(opts.projectDir, root2)), { recursive: true });
471340
- await writeFile18(join30(opts.projectDir, root2), html, "utf-8");
472366
+ await mkdir12(dirname16(join29(opts.projectDir, root2)), { recursive: true });
472367
+ await writeFile18(join29(opts.projectDir, root2), html, "utf-8");
471341
472368
  return {
471342
472369
  success: true,
471343
472370
  root: root2,
@@ -471401,7 +472428,7 @@ function reportedBeatAssetPaths(beat) {
471401
472428
  ]);
471402
472429
  }
471403
472430
  async function writeRenderReport(projectDir, result) {
471404
- const reportPath = join30(projectDir, "render-report.json");
472431
+ const reportPath = join29(projectDir, "render-report.json");
471405
472432
  try {
471406
472433
  await writeFile18(
471407
472434
  reportPath,
@@ -471598,6 +472625,7 @@ var init_build_asset_reference = __esm({
471598
472625
  EXTENSIONS = {
471599
472626
  narration: [".mp3", ".wav", ".m4a"],
471600
472627
  backdrop: [".png", ".jpg", ".jpeg", ".webp"],
472628
+ character: [".png", ".jpg", ".jpeg", ".webp"],
471601
472629
  video: [".mp4", ".mov", ".webm"],
471602
472630
  music: [".mp3", ".wav", ".m4a"]
471603
472631
  };
@@ -471635,6 +472663,16 @@ function backdropCacheDescriptor(opts) {
471635
472663
  ext: "png"
471636
472664
  });
471637
472665
  }
472666
+ function characterCacheDescriptor(opts) {
472667
+ return cacheAssetDescriptor("character", {
472668
+ name: opts.name,
472669
+ cue: opts.cue,
472670
+ provider: opts.provider,
472671
+ quality: opts.quality,
472672
+ size: opts.size,
472673
+ ext: "png"
472674
+ });
472675
+ }
471638
472676
  function imageRatioForSize(size) {
471639
472677
  switch (size) {
471640
472678
  case "1024x1024":
@@ -471654,6 +472692,8 @@ function videoCacheDescriptor(opts) {
471654
472692
  provider: opts.provider,
471655
472693
  duration: normalizeVideoDuration(opts.duration),
471656
472694
  ratio: "16:9",
472695
+ // Omit when empty so existing (character-less) clips keep their cache key.
472696
+ characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
471657
472697
  ext: "mp4"
471658
472698
  });
471659
472699
  }
@@ -471706,212 +472746,10 @@ var init_build_backdrop_prompt = __esm({
471706
472746
  }
471707
472747
  });
471708
472748
 
471709
- // ../cli/src/commands/_shared/storyboard-edit.ts
471710
- function validateStoryboardMarkdown(markdown) {
471711
- const parsed = parseStoryboard(markdown);
471712
- const sections = splitBeatSections(markdown);
471713
- const issues = [];
471714
- if (parsed.beats.length === 0) {
471715
- issues.push({
471716
- severity: "error",
471717
- code: "NO_BEATS",
471718
- message: "STORYBOARD.md must contain at least one `## Beat ...` heading."
471719
- });
471720
- }
471721
- const seen = /* @__PURE__ */ new Map();
471722
- for (const beat of parsed.beats) {
471723
- seen.set(beat.id, (seen.get(beat.id) ?? 0) + 1);
471724
- }
471725
- for (const [id, count] of seen.entries()) {
471726
- if (count > 1) {
471727
- issues.push({
471728
- severity: "error",
471729
- code: "DUPLICATE_BEAT_ID",
471730
- beatId: id,
471731
- message: `Beat id "${id}" appears ${count} times. Beat ids must be unique.`
471732
- });
471733
- }
471734
- }
471735
- for (const section of sections) {
471736
- const cueBlock = readLeadingCueBlock(section.body);
471737
- if (cueBlock?.error) {
471738
- issues.push({
471739
- severity: "error",
471740
- code: "MALFORMED_CUE_YAML",
471741
- beatId: section.id,
471742
- message: `Beat "${section.id}" has malformed YAML cues: ${cueBlock.error}`
471743
- });
471744
- }
471745
- }
471746
- for (const beat of parsed.beats) {
471747
- const cues = beat.cues ?? {};
471748
- for (const [key2, value] of Object.entries(cues)) {
471749
- if (!ALLOWED_CUE_KEYS.has(key2)) {
471750
- issues.push({
471751
- severity: "warning",
471752
- code: "UNKNOWN_CUE",
471753
- beatId: beat.id,
471754
- message: `Beat "${beat.id}" uses unknown cue "${key2}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`
471755
- });
471756
- continue;
471757
- }
471758
- if (key2 === "duration") {
471759
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
471760
- issues.push({
471761
- severity: "error",
471762
- code: "INVALID_DURATION",
471763
- beatId: beat.id,
471764
- message: `Beat "${beat.id}" cue "duration" must be a positive number of seconds.`
471765
- });
471766
- }
471767
- continue;
471768
- }
471769
- if (STRING_CUE_KEYS.has(key2) && typeof value !== "string") {
471770
- issues.push({
471771
- severity: "error",
471772
- code: "INVALID_CUE_VALUE",
471773
- beatId: beat.id,
471774
- message: `Beat "${beat.id}" cue "${key2}" must be a string.`
471775
- });
471776
- }
471777
- }
471778
- }
471779
- for (const beat of parsed.beats) {
471780
- if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
471781
- issues.push({
471782
- severity: "warning",
471783
- code: "BEAT_DURATION_TOO_LONG",
471784
- beatId: beat.id,
471785
- message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
471786
- });
471787
- }
471788
- }
471789
- return {
471790
- ok: !issues.some((i2) => i2.severity === "error"),
471791
- beats: parsed.beats,
471792
- issues
471793
- };
471794
- }
471795
- function getStoryboardBeat(markdown, beatId) {
471796
- return parseStoryboard(markdown).beats.find((beat) => beat.id === beatId) ?? null;
471797
- }
471798
- function setStoryboardCue(markdown, opts) {
471799
- const section = findBeatSection(markdown, opts.beatId);
471800
- if (!section) {
471801
- throw new Error(`Beat "${opts.beatId}" not found.`);
471802
- }
471803
- if (!ALLOWED_CUE_KEYS.has(opts.key)) {
471804
- throw new Error(`Unsupported cue "${opts.key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`);
471805
- }
471806
- const cue = readLeadingCueBlock(section.body);
471807
- if (cue?.error) {
471808
- throw new Error(`Cannot edit malformed YAML cues for beat "${opts.beatId}": ${cue.error}`);
471809
- }
471810
- const cues = cue?.value ? { ...cue.value } : {};
471811
- if (opts.unset) {
471812
- delete cues[opts.key];
471813
- } else {
471814
- cues[opts.key] = normalizeCueValue(opts.key, opts.value);
471815
- }
471816
- const remainingBody = cue ? section.body.slice(cue.full.length).replace(/^\s*\n/, "") : section.body.replace(/^\s*\n/, "");
471817
- const cueBlock = Object.keys(cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
471818
- const nextBody = cueBlock + remainingBody.trimStart();
471819
- const nextSection = section.raw.slice(0, section.headingEnd - section.start) + "\n\n" + nextBody.trimEnd() + "\n";
471820
- return markdown.slice(0, section.start) + nextSection + markdown.slice(section.end);
471821
- }
471822
- function moveStoryboardBeat(markdown, opts) {
471823
- if (opts.beatId === opts.afterBeatId) return markdown;
471824
- const sections = splitBeatSections(markdown);
471825
- const movingIndex = sections.findIndex((section) => section.id === opts.beatId);
471826
- if (movingIndex === -1) throw new Error(`Beat "${opts.beatId}" not found.`);
471827
- const afterIndex = sections.findIndex((section) => section.id === opts.afterBeatId);
471828
- if (afterIndex === -1) throw new Error(`Beat "${opts.afterBeatId}" not found.`);
471829
- const global3 = sections.length > 0 ? markdown.slice(0, sections[0].start) : markdown;
471830
- const chunks = sections.map((section) => section.raw);
471831
- const [moving] = chunks.splice(movingIndex, 1);
471832
- const adjustedAfterIndex = movingIndex < afterIndex ? afterIndex - 1 : afterIndex;
471833
- chunks.splice(adjustedAfterIndex + 1, 0, moving);
471834
- return global3 + normalizeBeatChunks(chunks);
471835
- }
471836
- function normalizeBeatChunks(chunks) {
471837
- return chunks.map((chunk) => chunk.trim()).filter(Boolean).join("\n\n") + "\n";
471838
- }
471839
- function normalizeCueValue(key2, value) {
471840
- if (key2 === "duration") {
471841
- const n2 = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
471842
- if (!Number.isFinite(n2) || n2 <= 0) {
471843
- throw new Error("Cue duration must be a positive number.");
471844
- }
471845
- return n2;
471846
- }
471847
- if (typeof value !== "string") return value;
471848
- return value.trim();
471849
- }
471850
- function splitBeatSections(markdown) {
471851
- const normalized = markdown.replace(/\r\n/g, "\n");
471852
- const headings = [];
471853
- HEADING_RE2.lastIndex = 0;
471854
- let match2;
471855
- while ((match2 = HEADING_RE2.exec(normalized)) !== null) {
471856
- headings.push({ start: match2.index, end: match2.index + match2[0].length, line: match2[1].trim() });
471857
- }
471858
- return headings.map((heading, index) => {
471859
- const end = index + 1 < headings.length ? headings[index + 1].start : normalized.length;
471860
- return {
471861
- id: deriveBeatId(heading.line),
471862
- heading: heading.line,
471863
- start: heading.start,
471864
- headingEnd: heading.end,
471865
- end,
471866
- raw: normalized.slice(heading.start, end),
471867
- body: normalized.slice(heading.end, end).trim()
471868
- };
471869
- });
471870
- }
471871
- function findBeatSection(markdown, beatId) {
471872
- return splitBeatSections(markdown).find((section) => section.id === beatId) ?? null;
471873
- }
471874
- function readLeadingCueBlock(body) {
471875
- const match2 = body.match(LEADING_CUE_RE);
471876
- if (!match2) return null;
471877
- try {
471878
- const parsed = (0, import_yaml5.parse)(match2[2]);
471879
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
471880
- return { full: match2[0], error: "cue block must parse to a YAML object" };
471881
- }
471882
- return { full: match2[0], value: parsed };
471883
- } catch (error) {
471884
- return { full: match2[0], error: error instanceof Error ? error.message : String(error) };
471885
- }
471886
- }
471887
- var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
471888
- var init_storyboard_edit = __esm({
471889
- "../cli/src/commands/_shared/storyboard-edit.ts"() {
471890
- "use strict";
471891
- import_yaml5 = __toESM(require_dist3(), 1);
471892
- init_storyboard_parse();
471893
- STORYBOARD_CUE_KEYS = [
471894
- "duration",
471895
- "narration",
471896
- "backdrop",
471897
- "video",
471898
- "motion",
471899
- "voice",
471900
- "music",
471901
- "asset"
471902
- ];
471903
- HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
471904
- LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
471905
- ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
471906
- STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
471907
- MAX_RECOMMENDED_BEAT_SEC = 15;
471908
- }
471909
- });
471910
-
471911
472749
  // ../cli/src/commands/_shared/build-plan.ts
471912
472750
  import { existsSync as existsSync33 } from "node:fs";
471913
472751
  import { readFile as readFile17 } from "node:fs/promises";
471914
- import { dirname as dirname17, join as join31, resolve as resolve29 } from "node:path";
472752
+ import { dirname as dirname17, join as join30, resolve as resolve29 } from "node:path";
471915
472753
  function backdropCostUsd(quality) {
471916
472754
  return quality === "hd" ? 0.2 : 0.05;
471917
472755
  }
@@ -471920,7 +472758,7 @@ async function createBuildPlan(opts) {
471920
472758
  loadPlanEnv(projectDir);
471921
472759
  const stage = opts.stage ?? "all";
471922
472760
  const config4 = await readProjectConfig(projectDir);
471923
- const storyboardPath = join31(projectDir, "STORYBOARD.md");
472761
+ const storyboardPath = join30(projectDir, "STORYBOARD.md");
471924
472762
  const warnings = [];
471925
472763
  const retryWith = [];
471926
472764
  const providerResolution = [];
@@ -471979,7 +472817,7 @@ async function createBuildPlan(opts) {
471979
472817
  const imageQuality = opts.imageQuality ?? config4.config.build.imageQuality ?? "hd";
471980
472818
  const imageSize2 = opts.imageSize ?? config4.config.build.imageSize ?? "1536x1024";
471981
472819
  const imageRatio = imageRatioForSize(imageSize2);
471982
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync33(join31(projectDir, `compositions/scene-${beat.id}.html`)));
472820
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync33(join30(projectDir, `compositions/scene-${beat.id}.html`)));
471983
472821
  const resolved = await resolvePlanProviders({
471984
472822
  projectDir,
471985
472823
  config: config4,
@@ -472016,13 +472854,13 @@ async function createBuildPlan(opts) {
472016
472854
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
472017
472855
  const videoCue = videoPrompt ?? videoReference?.raw;
472018
472856
  const musicCue = musicPrompt ?? musicReference?.raw;
472019
- const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : 0;
472857
+ const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
472020
472858
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
472021
472859
  beatId: beat.id,
472022
472860
  cue: narrationText,
472023
472861
  provider: resolved.narration.resolved,
472024
472862
  voice,
472025
- ext: resolved.narration.resolved === "elevenlabs" ? "mp3" : "wav"
472863
+ ext: resolved.narration.resolved === "kokoro" ? "wav" : "mp3"
472026
472864
  }) : null;
472027
472865
  const backdropCache = augmentedBackdropPrompt && !backdropReference ? backdropCacheDescriptor({
472028
472866
  beatId: beat.id,
@@ -472083,7 +472921,11 @@ async function createBuildPlan(opts) {
472083
472921
  reference: videoReference,
472084
472922
  projectDir,
472085
472923
  force: opts.force,
472086
- cost: VIDEO_COST_USD,
472924
+ cost: resolved.video.resolved === "seedance" || resolved.video.resolved === "fal" ? estimateSeedanceVideoCostUsd({
472925
+ durationSec: normalizeVideoDuration(beat.duration),
472926
+ resolution: "720p",
472927
+ aspectRatio: "16:9"
472928
+ }) : VIDEO_COST_USD,
472087
472929
  active: includeAssets
472088
472930
  }) : null;
472089
472931
  const music = musicCue && !opts.skipMusic ? assetPlan({
@@ -472100,7 +472942,7 @@ async function createBuildPlan(opts) {
472100
472942
  active: includeAssets
472101
472943
  }) : null;
472102
472944
  const compositionPath = `compositions/scene-${beat.id}.html`;
472103
- const compositionExists = existsSync33(join31(projectDir, compositionPath));
472945
+ const compositionExists = existsSync33(join30(projectDir, compositionPath));
472104
472946
  for (const asset of [narration, backdrop, video, music]) {
472105
472947
  if (!asset) continue;
472106
472948
  if (asset.referenceError) {
@@ -472167,7 +473009,7 @@ async function createBuildPlan(opts) {
472167
473009
  }
472168
473010
  }
472169
473011
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
472170
- if (!existsSync33(join31(projectDir, config4.config.composition.entry))) {
473012
+ if (!existsSync33(join30(projectDir, config4.config.composition.entry))) {
472171
473013
  missing.add("root-composition");
472172
473014
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
472173
473015
  }
@@ -472194,6 +473036,21 @@ async function createBuildPlan(opts) {
472194
473036
  retryWith.push(skipCommand);
472195
473037
  }
472196
473038
  }
473039
+ if (validationOk && includeAssets && !opts.skipVideo) {
473040
+ const referenced = /* @__PURE__ */ new Set();
473041
+ for (const beat of sourceBeats) {
473042
+ for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
473043
+ }
473044
+ if (referenced.size > 0) {
473045
+ const toGenerate = resolveCharacters(parsed.frontmatter).filter(
473046
+ (c2) => referenced.has(c2.name) && c2.prompt && (opts.force || !existsSync33(join30(projectDir, `assets/character-${c2.name}.png`)))
473047
+ );
473048
+ if (toGenerate.length > 0) {
473049
+ estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
473050
+ missing.add("assets");
473051
+ }
473052
+ }
473053
+ }
472197
473054
  return finalizeBuildPlan({
472198
473055
  projectDir,
472199
473056
  config: config4,
@@ -472299,8 +473156,8 @@ function assetPlan(opts) {
472299
473156
  reason: ready ? "referenced-asset" : "invalid-reference"
472300
473157
  };
472301
473158
  }
472302
- const canonicalExists = existsSync33(join31(opts.projectDir, opts.path));
472303
- const cacheHit = opts.cache ? existsSync33(join31(opts.projectDir, opts.cache.path)) : false;
473159
+ const canonicalExists = existsSync33(join30(opts.projectDir, opts.path));
473160
+ const cacheHit = opts.cache ? existsSync33(join30(opts.projectDir, opts.cache.path)) : false;
472304
473161
  const freshness = assetFreshnessFromMetadata({
472305
473162
  projectDir: opts.projectDir,
472306
473163
  kind: opts.kind,
@@ -472440,17 +473297,20 @@ function providerInput(opts) {
472440
473297
  async function resolveNarrationProvider(input3, projectDir) {
472441
473298
  const requested = input3.value.toLowerCase();
472442
473299
  const hasElevenLabs = Boolean(await getApiKeyFromConfig("elevenlabs", { cwd: projectDir }));
472443
- const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : "kokoro";
473300
+ const hasOpenAi = Boolean(await getApiKeyFromConfig("openai", { cwd: projectDir }));
473301
+ const resolved = requested === "elevenlabs" ? "elevenlabs" : requested === "openai" ? "openai" : requested === "kokoro" ? "kokoro" : hasElevenLabs ? "elevenlabs" : hasOpenAi ? "openai" : "kokoro";
473302
+ const requiresKey = resolved !== "kokoro";
473303
+ const hasKey = resolved === "elevenlabs" ? hasElevenLabs : resolved === "openai" ? hasOpenAi : true;
472444
473304
  return {
472445
473305
  kind: "narration",
472446
473306
  requested: input3.requested,
472447
473307
  resolved,
472448
473308
  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"] : []
473309
+ requiresKey,
473310
+ configured: hasKey,
473311
+ ...requiresKey ? { configKey: resolved } : {},
473312
+ ...requiresKey && !hasKey ? { missingKey: providerEnvVar(resolved) } : {},
473313
+ retryWith: requiresKey && !hasKey ? ["vibe setup --full"] : []
472454
473314
  };
472455
473315
  }
472456
473316
  async function keyedProviderResolution(opts, projectDir) {
@@ -472494,7 +473354,7 @@ async function firstConfiguredComposer(projectDir) {
472494
473354
  }
472495
473355
  function firstExisting2(projectDir, paths) {
472496
473356
  for (const path14 of paths) {
472497
- if (existsSync33(join31(projectDir, path14))) return path14;
473357
+ if (existsSync33(join30(projectDir, path14))) return path14;
472498
473358
  }
472499
473359
  return null;
472500
473360
  }
@@ -472535,18 +473395,18 @@ function unique3(items) {
472535
473395
  return [...new Set(items.filter((item) => item.length > 0))];
472536
473396
  }
472537
473397
  function loadPlanEnv(projectDir) {
472538
- (0, import_dotenv2.config)({ path: join31(projectDir, ".env"), quiet: true });
473398
+ (0, import_dotenv2.config)({ path: join30(projectDir, ".env"), quiet: true });
472539
473399
  (0, import_dotenv2.config)({ path: resolve29(process.cwd(), ".env"), quiet: true });
472540
473400
  let dir = process.cwd();
472541
473401
  while (dir !== dirname17(dir)) {
472542
- if (existsSync33(join31(dir, "pnpm-workspace.yaml"))) {
472543
- (0, import_dotenv2.config)({ path: join31(dir, ".env"), quiet: true });
473402
+ if (existsSync33(join30(dir, "pnpm-workspace.yaml"))) {
473403
+ (0, import_dotenv2.config)({ path: join30(dir, ".env"), quiet: true });
472544
473404
  return;
472545
473405
  }
472546
473406
  dir = dirname17(dir);
472547
473407
  }
472548
473408
  }
472549
- var import_dotenv2, VIDEO_COST_USD, MUSIC_COST_USD, ELEVENLABS_NARRATION_COST_USD, COMPOSE_COST_USD, SUPPORTED_BUILD_IMAGE_PROVIDERS;
473409
+ 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
473410
  var init_build_plan = __esm({
472551
473411
  "../cli/src/commands/_shared/build-plan.ts"() {
472552
473412
  "use strict";
@@ -472555,6 +473415,7 @@ var init_build_plan = __esm({
472555
473415
  init_schema();
472556
473416
  init_build_asset_reference();
472557
473417
  init_build_cache();
473418
+ init_dist();
472558
473419
  init_build_asset_metadata();
472559
473420
  init_build_backdrop_prompt();
472560
473421
  init_composer_resolve();
@@ -472564,6 +473425,7 @@ var init_build_plan = __esm({
472564
473425
  VIDEO_COST_USD = 5;
472565
473426
  MUSIC_COST_USD = 0.5;
472566
473427
  ELEVENLABS_NARRATION_COST_USD = 0.05;
473428
+ OPENAI_NARRATION_COST_USD = 0.01;
472567
473429
  COMPOSE_COST_USD = 0.06;
472568
473430
  SUPPORTED_BUILD_IMAGE_PROVIDERS = ["openai", "gemini", "grok"];
472569
473431
  }
@@ -472886,9 +473748,9 @@ __export(scene_build_exports, {
472886
473748
  executeSceneBuild: () => executeSceneBuild,
472887
473749
  resolveSceneBuildMode: () => resolveSceneBuildMode
472888
473750
  });
472889
- import { existsSync as existsSync35 } from "node:fs";
473751
+ import { existsSync as existsSync35, readFileSync as readFileSync12 } from "node:fs";
472890
473752
  import { copyFile as copyFile4, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile20 } from "node:fs/promises";
472891
- import { dirname as dirname18, join as join33, resolve as resolve33 } from "node:path";
473753
+ import { dirname as dirname18, join as join31, resolve as resolve33 } from "node:path";
472892
473754
  async function executeSceneBuild(opts) {
472893
473755
  const startedAt = Date.now();
472894
473756
  const projectDir = resolve33(opts.projectDir);
@@ -472900,7 +473762,7 @@ async function executeSceneBuild(opts) {
472900
473762
  const warnings = [];
472901
473763
  const retryWith = [];
472902
473764
  let sceneRepair = skippedSceneRepairSummary();
472903
- const storyboardPath = join33(projectDir, "STORYBOARD.md");
473765
+ const storyboardPath = join31(projectDir, "STORYBOARD.md");
472904
473766
  const buildPlan = await createBuildPlan({
472905
473767
  projectDir,
472906
473768
  stage: selectedStage,
@@ -473053,8 +473915,32 @@ async function executeSceneBuild(opts) {
473053
473915
  const voice = opts.voice ?? parsed.frontmatter?.voice;
473054
473916
  let beatOutcomes = collectExistingBeatOutcomes(parsed.beats, projectDir);
473055
473917
  let pendingJobs = [];
473918
+ let characterCostUsd = 0;
473056
473919
  if (shouldRunStage(selectedStage, "assets")) {
473057
473920
  onProgress({ type: "phase-start", phase: "primitives" });
473921
+ const characterPaths = /* @__PURE__ */ new Map();
473922
+ if (!(opts.skipVideo ?? false)) {
473923
+ const referenced = /* @__PURE__ */ new Set();
473924
+ for (const beat of activeBeats) {
473925
+ for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
473926
+ }
473927
+ if (referenced.size > 0) {
473928
+ const charResult = await buildCharacters(
473929
+ resolveCharacters(parsed.frontmatter).filter((c2) => referenced.has(c2.name)),
473930
+ {
473931
+ projectDir,
473932
+ imageProvider,
473933
+ imageSize: opts.imageSize ?? "1536x1024",
473934
+ imageQuality: opts.imageQuality ?? "hd",
473935
+ force: opts.force ?? false,
473936
+ onProgress
473937
+ }
473938
+ );
473939
+ for (const [name, path14] of charResult.paths) characterPaths.set(name, path14);
473940
+ characterCostUsd = charResult.costUsd;
473941
+ warnings.push(...charResult.failures);
473942
+ }
473943
+ }
473058
473944
  const primitiveResults = await mapWithConcurrency(
473059
473945
  activeBeats,
473060
473946
  BEAT_PRIMITIVES_CONCURRENCY,
@@ -473072,7 +473958,8 @@ async function executeSceneBuild(opts) {
473072
473958
  skipVideo: opts.skipVideo ?? false,
473073
473959
  skipMusic: opts.skipMusic ?? false,
473074
473960
  force: opts.force ?? false,
473075
- onProgress
473961
+ onProgress,
473962
+ characterPaths
473076
473963
  })
473077
473964
  );
473078
473965
  beatOutcomes = primitiveResults.map((result) => result.outcome);
@@ -473081,10 +473968,7 @@ async function executeSceneBuild(opts) {
473081
473968
  (beat) => beat.narrationStatus === "failed" || beat.backdropStatus === "failed" || beat.videoStatus === "failed" || beat.musicStatus === "failed"
473082
473969
  );
473083
473970
  stageReports.assets.status = assetFailed ? "failed" : pendingJobs.length > 0 ? "pending-jobs" : "done";
473084
- stageReports.assets.costUsd = estimateActualAssetCost(
473085
- beatOutcomes,
473086
- opts.imageQuality ?? "hd"
473087
- );
473971
+ stageReports.assets.costUsd = estimateActualAssetCost(beatOutcomes, opts.imageQuality ?? "hd") + characterCostUsd;
473088
473972
  stageReports.assets.retryWith = pendingJobs.map(
473089
473973
  (job) => `vibe status job ${job.id} --project ${projectDir} --json`
473090
473974
  );
@@ -473160,9 +474044,9 @@ async function executeSceneBuild(opts) {
473160
474044
  let composeData;
473161
474045
  if (shouldRunStage(selectedStage, "compose")) {
473162
474046
  if (mode === "agent") {
473163
- const compositionsDir = join33(projectDir, "compositions");
474047
+ const compositionsDir = join31(projectDir, "compositions");
473164
474048
  const missingBeats = activeBeats.filter(
473165
- (b2) => !existsSync35(join33(compositionsDir, `scene-${b2.id}.html`))
474049
+ (b2) => !existsSync35(join31(compositionsDir, `scene-${b2.id}.html`))
473166
474050
  );
473167
474051
  if (missingBeats.length > 0) {
473168
474052
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -473249,6 +474133,18 @@ async function executeSceneBuild(opts) {
473249
474133
  stageReports.compose.status = "skipped";
473250
474134
  }
473251
474135
  if (stageReports.compose.status === "done") {
474136
+ for (const beat of activeBeats) {
474137
+ const videoRel = `assets/video-${beat.id}.mp4`;
474138
+ const compositionAbs = join31(projectDir, "compositions", `scene-${beat.id}.html`);
474139
+ if (!existsSync35(join31(projectDir, videoRel)) || !existsSync35(compositionAbs)) continue;
474140
+ const html = readFileSync12(compositionAbs, "utf-8");
474141
+ if (!html.includes(videoRel)) {
474142
+ warnings.push(
474143
+ `Beat "${beat.id}" has a generated clip (${videoRel}) but its composition does not reference it, so the clip will not appear. Recompose the beat.`
474144
+ );
474145
+ retryWith.push(`vibe build ${projectDir} --beat ${beat.id} --stage compose --force --json`);
474146
+ }
474147
+ }
473252
474148
  const repair = await runBuildSceneRepair(projectDir, "compose", false);
473253
474149
  sceneRepair = mergeSceneRepairSummaries(sceneRepair, repair);
473254
474150
  applySceneRepairToStage(stageReports.compose, repair);
@@ -473298,7 +474194,7 @@ async function executeSceneBuild(opts) {
473298
474194
  });
473299
474195
  }
473300
474196
  if (shouldRunStage(selectedStage, "sync")) {
473301
- if (!existsSync35(join33(projectDir, "index.html"))) {
474197
+ if (!existsSync35(join31(projectDir, "index.html"))) {
473302
474198
  await scaffoldSceneProject({
473303
474199
  dir: projectDir,
473304
474200
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -473557,7 +474453,7 @@ async function dispatchNarration(beat, ctx) {
473557
474453
  return {
473558
474454
  status: "cached",
473559
474455
  path: existingRel,
473560
- durationSec: await safeAudioDuration(join33(ctx.projectDir, existingRel)),
474456
+ durationSec: await safeAudioDuration(join31(ctx.projectDir, existingRel)),
473561
474457
  provider: metadata?.provider,
473562
474458
  cachePath: metadata?.cachePath,
473563
474459
  cacheKey: metadata?.cacheKey,
@@ -473581,9 +474477,9 @@ async function dispatchNarration(beat, ctx) {
473581
474477
  voice,
473582
474478
  ext: resolution.audioExtension
473583
474479
  });
473584
- const cacheAbs = join33(ctx.projectDir, cache.path);
474480
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473585
474481
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
473586
- const abs = join33(ctx.projectDir, rel);
474482
+ const abs = join31(ctx.projectDir, rel);
473587
474483
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
473588
474484
  projectDir: ctx.projectDir,
473589
474485
  kind: "narration",
@@ -473597,7 +474493,7 @@ async function dispatchNarration(beat, ctx) {
473597
474493
  return {
473598
474494
  status: "cached",
473599
474495
  path: existingRel,
473600
- durationSec: await safeAudioDuration(join33(ctx.projectDir, existingRel)),
474496
+ durationSec: await safeAudioDuration(join31(ctx.projectDir, existingRel)),
473601
474497
  provider: resolution.provider,
473602
474498
  cachePath: cache.path,
473603
474499
  cacheKey: cache.key,
@@ -473669,6 +474565,120 @@ async function dispatchNarration(beat, ctx) {
473669
474565
  freshness: "fresh"
473670
474566
  };
473671
474567
  }
474568
+ function characterSheetPrompt(name, description) {
474569
+ return [
474570
+ `Character turnaround reference sheet of an original fictional character named ${name}: ${description}.`,
474571
+ "Show three full-body views side by side on one clean canvas \u2014 front view, side profile, and back view \u2014",
474572
+ "with identical outfit, hairstyle, hair color, and body proportions across all three views.",
474573
+ "Plain light-grey studio background, even neutral lighting, no props.",
474574
+ "Photorealistic digital character, professional concept-art character sheet layout."
474575
+ ].join(" ");
474576
+ }
474577
+ async function buildCharacters(characters, ctx) {
474578
+ const paths = /* @__PURE__ */ new Map();
474579
+ const failures = [];
474580
+ let costUsd = 0;
474581
+ const size = ctx.imageSize ?? "1536x1024";
474582
+ for (const character of characters) {
474583
+ if (character.imagePath) {
474584
+ if (existsSync35(join31(ctx.projectDir, character.imagePath))) {
474585
+ paths.set(character.name, character.imagePath);
474586
+ ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
474587
+ } else {
474588
+ const error = `character "${character.name}" image not found: ${character.imagePath}`;
474589
+ failures.push(error);
474590
+ ctx.onProgress({ type: "character-failed", name: character.name, error });
474591
+ }
474592
+ continue;
474593
+ }
474594
+ if (!character.prompt) continue;
474595
+ const prompt3 = characterSheetPrompt(character.name, character.prompt);
474596
+ const rel = `assets/character-${character.name}.png`;
474597
+ const abs = join31(ctx.projectDir, rel);
474598
+ const metadataOptions = { quality: ctx.imageQuality, size };
474599
+ const cache = characterCacheDescriptor({
474600
+ name: character.name,
474601
+ cue: prompt3,
474602
+ provider: ctx.imageProvider,
474603
+ quality: ctx.imageQuality,
474604
+ size
474605
+ });
474606
+ if (existsSync35(abs) && !ctx.force) {
474607
+ const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
474608
+ if (!metadata || isFreshCanonicalAsset({
474609
+ projectDir: ctx.projectDir,
474610
+ kind: "character",
474611
+ beatId: character.name,
474612
+ cue: prompt3,
474613
+ provider: ctx.imageProvider,
474614
+ options: metadataOptions,
474615
+ cacheKey: cache.key
474616
+ })) {
474617
+ paths.set(character.name, rel);
474618
+ ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
474619
+ continue;
474620
+ }
474621
+ }
474622
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474623
+ if (existsSync35(cacheAbs) && !ctx.force) {
474624
+ await mkdir13(dirname18(abs), { recursive: true });
474625
+ await copyFile4(cacheAbs, abs);
474626
+ await writeAssetMetadata({
474627
+ projectDir: ctx.projectDir,
474628
+ kind: "character",
474629
+ beatId: character.name,
474630
+ cue: prompt3,
474631
+ provider: ctx.imageProvider,
474632
+ options: metadataOptions,
474633
+ cacheKey: cache.key,
474634
+ canonicalPath: rel,
474635
+ cachePath: cache.path
474636
+ });
474637
+ paths.set(character.name, rel);
474638
+ ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
474639
+ continue;
474640
+ }
474641
+ const generated = await generateBackdropImage(
474642
+ prompt3,
474643
+ {
474644
+ projectDir: ctx.projectDir,
474645
+ imageProvider: ctx.imageProvider,
474646
+ imageSize: size,
474647
+ imageQuality: ctx.imageQuality
474648
+ },
474649
+ imageRatioForSize(size)
474650
+ );
474651
+ if (!generated.success) {
474652
+ failures.push(`character "${character.name}": ${generated.error}`);
474653
+ ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
474654
+ continue;
474655
+ }
474656
+ await mkdir13(dirname18(abs), { recursive: true });
474657
+ await writeFile20(abs, generated.buffer);
474658
+ await mkdir13(dirname18(cacheAbs), { recursive: true });
474659
+ await writeFile20(cacheAbs, generated.buffer);
474660
+ await writeAssetMetadata({
474661
+ projectDir: ctx.projectDir,
474662
+ kind: "character",
474663
+ beatId: character.name,
474664
+ cue: prompt3,
474665
+ provider: ctx.imageProvider,
474666
+ options: metadataOptions,
474667
+ cacheKey: cache.key,
474668
+ canonicalPath: rel,
474669
+ cachePath: cache.path
474670
+ });
474671
+ costUsd += backdropCostUsd(ctx.imageQuality);
474672
+ paths.set(character.name, rel);
474673
+ ctx.onProgress({
474674
+ type: "character-generated",
474675
+ name: character.name,
474676
+ path: rel,
474677
+ provider: ctx.imageProvider
474678
+ });
474679
+ }
474680
+ return { paths, costUsd: Number(costUsd.toFixed(2)), failures };
474681
+ }
473672
474682
  async function dispatchBackdrop(beat, ctx) {
473673
474683
  const reference = assetReferenceForBeat(ctx.projectDir, "backdrop", beat);
473674
474684
  if (reference) return referencePrimitiveOutcome("backdrop", beat, ctx, reference);
@@ -473676,7 +474686,7 @@ async function dispatchBackdrop(beat, ctx) {
473676
474686
  if (!cue) return { status: "no-cue" };
473677
474687
  const prompt3 = augmentBackdropPrompt(cue);
473678
474688
  const rel = `assets/backdrop-${beat.id}.png`;
473679
- const abs = join33(ctx.projectDir, rel);
474689
+ const abs = join31(ctx.projectDir, rel);
473680
474690
  const size = ctx.imageSize ?? "1536x1024";
473681
474691
  const ratio = imageRatioForSize(size);
473682
474692
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -473712,7 +474722,7 @@ async function dispatchBackdrop(beat, ctx) {
473712
474722
  };
473713
474723
  }
473714
474724
  }
473715
- const cacheAbs = join33(ctx.projectDir, cache.path);
474725
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473716
474726
  if (existsSync35(cacheAbs) && !ctx.force) {
473717
474727
  await mkdir13(dirname18(abs), { recursive: true });
473718
474728
  await copyFile4(cacheAbs, abs);
@@ -473838,13 +474848,16 @@ async function dispatchVideo(beat, ctx) {
473838
474848
  if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
473839
474849
  const prompt3 = stringOrUndefined4(beat.cues?.video);
473840
474850
  if (!prompt3) return { status: "no-cue" };
474851
+ const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p2) => typeof p2 === "string");
474852
+ const characterRefsAbs = characterRefsRel.map((p2) => join31(ctx.projectDir, p2));
473841
474853
  const rel = `assets/video-${beat.id}.mp4`;
473842
- const abs = join33(ctx.projectDir, rel);
474854
+ const abs = join31(ctx.projectDir, rel);
473843
474855
  const cache = videoCacheDescriptor({
473844
474856
  beatId: beat.id,
473845
474857
  cue: prompt3,
473846
474858
  provider: ctx.videoProvider,
473847
- duration: normalizeVideoDuration(beat.duration)
474859
+ duration: normalizeVideoDuration(beat.duration),
474860
+ characters: characterRefsRel
473848
474861
  });
473849
474862
  const metadataPath = assetMetadataPath("video", beat.id);
473850
474863
  if (existsSync35(abs) && !ctx.force) {
@@ -473870,7 +474883,7 @@ async function dispatchVideo(beat, ctx) {
473870
474883
  };
473871
474884
  }
473872
474885
  }
473873
- const cacheAbs = join33(ctx.projectDir, cache.path);
474886
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473874
474887
  if (existsSync35(cacheAbs) && !ctx.force) {
473875
474888
  await mkdir13(dirname18(abs), { recursive: true });
473876
474889
  await copyFile4(cacheAbs, abs);
@@ -473904,6 +474917,7 @@ async function dispatchVideo(beat, ctx) {
473904
474917
  ratio: "16:9",
473905
474918
  output: abs,
473906
474919
  wait: false,
474920
+ refImages: characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
473907
474921
  apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
473908
474922
  });
473909
474923
  if (!result.success || !result.taskId) {
@@ -473979,7 +474993,7 @@ async function dispatchMusic(beat, ctx) {
473979
474993
  const prompt3 = stringOrUndefined4(beat.cues?.music);
473980
474994
  if (!prompt3) return { status: "no-cue" };
473981
474995
  const rel = `assets/music-${beat.id}.mp3`;
473982
- const abs = join33(ctx.projectDir, rel);
474996
+ const abs = join31(ctx.projectDir, rel);
473983
474997
  const duration = normalizeMusicDuration(beat.duration);
473984
474998
  const cache = musicCacheDescriptor({
473985
474999
  beatId: beat.id,
@@ -474012,7 +475026,7 @@ async function dispatchMusic(beat, ctx) {
474012
475026
  };
474013
475027
  }
474014
475028
  }
474015
- const cacheAbs = join33(ctx.projectDir, cache.path);
475029
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474016
475030
  if (existsSync35(cacheAbs) && !ctx.force) {
474017
475031
  await mkdir13(dirname18(abs), { recursive: true });
474018
475032
  await copyFile4(cacheAbs, abs);
@@ -474123,12 +475137,12 @@ async function dispatchMusic(beat, ctx) {
474123
475137
  };
474124
475138
  }
474125
475139
  function loadSceneBuildEnv(projectDir) {
474126
- (0, import_dotenv3.config)({ path: join33(projectDir, ".env"), quiet: true });
475140
+ (0, import_dotenv3.config)({ path: join31(projectDir, ".env"), quiet: true });
474127
475141
  (0, import_dotenv3.config)({ path: resolve33(process.cwd(), ".env"), quiet: true });
474128
475142
  let dir = process.cwd();
474129
475143
  while (dir !== dirname18(dir)) {
474130
- if (existsSync35(join33(dir, "pnpm-workspace.yaml"))) {
474131
- (0, import_dotenv3.config)({ path: join33(dir, ".env"), quiet: true });
475144
+ if (existsSync35(join31(dir, "pnpm-workspace.yaml"))) {
475145
+ (0, import_dotenv3.config)({ path: join31(dir, ".env"), quiet: true });
474132
475146
  return;
474133
475147
  }
474134
475148
  dir = dirname18(dir);
@@ -474272,7 +475286,13 @@ function estimateActualAssetCost(outcomes, imageQuality) {
474272
475286
  for (const outcome of outcomes) {
474273
475287
  if (outcome.narrationStatus === "generated") cost += 0.05;
474274
475288
  if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
474275
- if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") cost += 5;
475289
+ if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
475290
+ cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
475291
+ durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
475292
+ resolution: "720p",
475293
+ aspectRatio: "16:9"
475294
+ }) : 5;
475295
+ }
474276
475296
  if (outcome.musicStatus === "generated" || outcome.musicStatus === "pending") cost += 0.5;
474277
475297
  }
474278
475298
  return Number(cost.toFixed(2));
@@ -474337,7 +475357,7 @@ function mergeBeatOutcomes(base, updates) {
474337
475357
  }
474338
475358
  function firstExisting3(projectDir, relPaths) {
474339
475359
  for (const rel of relPaths) {
474340
- if (existsSync35(join33(projectDir, rel))) return rel;
475360
+ if (existsSync35(join31(projectDir, rel))) return rel;
474341
475361
  }
474342
475362
  return null;
474343
475363
  }
@@ -474368,7 +475388,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
474368
475388
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
474369
475389
  }
474370
475390
  async function finalizeBuildResult(projectDir, startedAt, result) {
474371
- const reportPath = join33(projectDir, "build-report.json");
475391
+ const reportPath = join31(projectDir, "build-report.json");
474372
475392
  if (result.stageReports) {
474373
475393
  for (const report of Object.values(result.stageReports)) {
474374
475394
  if (report.status === "pending") report.status = "skipped";
@@ -474527,7 +475547,7 @@ function normalizeReportDuration(value) {
474527
475547
  }
474528
475548
  function buildReportComposition(projectDir, result, beatId) {
474529
475549
  const path14 = `compositions/scene-${beatId}.html`;
474530
- const exists = existsSync35(join33(projectDir, path14));
475550
+ const exists = existsSync35(join31(projectDir, path14));
474531
475551
  const written = composeWrittenEntry(result, beatId);
474532
475552
  if (written) {
474533
475553
  return {
@@ -474584,7 +475604,7 @@ function summarizeBuildBeats(projectDir, beats) {
474584
475604
  let compositionsReady = 0;
474585
475605
  for (const beat of beats) {
474586
475606
  if (beatAssetsReady(beat)) assetsReady += 1;
474587
- const compositionPath = join33(projectDir, "compositions", `scene-${beat.beatId}.html`);
475607
+ const compositionPath = join31(projectDir, "compositions", `scene-${beat.beatId}.html`);
474588
475608
  if (existsSync35(compositionPath)) {
474589
475609
  compositionsReady += 1;
474590
475610
  } else {
@@ -475240,7 +476260,7 @@ var init_silence_cut = __esm({
475240
476260
  // ../cli/src/commands/_shared/edit/jump-cut.ts
475241
476261
  import { existsSync as existsSync38 } from "node:fs";
475242
476262
  import { readFile as readFile21, writeFile as writeFile22, mkdir as mkdir14 } from "node:fs/promises";
475243
- import { join as join34 } from "node:path";
476263
+ import { join as join33 } from "node:path";
475244
476264
  async function transcribeWithWords(audioPath, apiKey, language) {
475245
476265
  const audioBuffer = await readFile21(audioPath);
475246
476266
  const audioBlob = new Blob([audioBuffer]);
@@ -475320,7 +476340,7 @@ async function executeJumpCut(options) {
475320
476340
  try {
475321
476341
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
475322
476342
  await mkdir14(tmpDir, { recursive: true });
475323
- const audioPath = join34(tmpDir, "audio.wav");
476343
+ const audioPath = join33(tmpDir, "audio.wav");
475324
476344
  try {
475325
476345
  await execSafe(
475326
476346
  "ffmpeg",
@@ -475374,7 +476394,7 @@ async function executeJumpCut(options) {
475374
476394
  const segmentPaths = [];
475375
476395
  for (let i2 = 0; i2 < segments.length; i2++) {
475376
476396
  const seg = segments[i2];
475377
- const segPath = join34(tmpDir, `seg-${i2.toString().padStart(4, "0")}.ts`);
476397
+ const segPath = join33(tmpDir, `seg-${i2.toString().padStart(4, "0")}.ts`);
475378
476398
  const duration = seg.end - seg.start;
475379
476399
  await execSafe(
475380
476400
  "ffmpeg",
@@ -475397,7 +476417,7 @@ async function executeJumpCut(options) {
475397
476417
  segmentPaths.push(segPath);
475398
476418
  }
475399
476419
  const concatList = segmentPaths.map((p2) => `file '${p2}'`).join("\n");
475400
- const listPath = join34(tmpDir, "concat.txt");
476420
+ const listPath = join33(tmpDir, "concat.txt");
475401
476421
  await writeFile22(listPath, concatList);
475402
476422
  await execSafe(
475403
476423
  "ffmpeg",
@@ -475505,7 +476525,7 @@ __export(remotion_exports, {
475505
476525
  });
475506
476526
  import { writeFile as writeFile23, mkdir as mkdir15, rm as rm3, copyFile as copyFile5 } from "node:fs/promises";
475507
476527
  import { existsSync as existsSync39 } from "node:fs";
475508
- import { join as join35 } from "node:path";
476528
+ import { join as join34 } from "node:path";
475509
476529
  import { tmpdir as tmpdir3 } from "node:os";
475510
476530
  import { createHash as createHash7 } from "node:crypto";
475511
476531
  async function ensureRemotionInstalled() {
@@ -475524,7 +476544,7 @@ async function ensureRemotionInstalled() {
475524
476544
  }
475525
476545
  }
475526
476546
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
475527
- const dir = join35(tmpdir3(), `vibe_motion_${Date.now()}`);
476547
+ const dir = join34(tmpdir3(), `vibe_motion_${Date.now()}`);
475528
476548
  await mkdir15(dir, { recursive: true });
475529
476549
  const deps = {
475530
476550
  remotion: REMOTION_VERSION,
@@ -475543,7 +476563,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
475543
476563
  dependencies: deps
475544
476564
  };
475545
476565
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
475546
- await writeFile23(join35(dir, "package.json"), packageJsonStr);
476566
+ await writeFile23(join34(dir, "package.json"), packageJsonStr);
475547
476567
  const tsconfig = {
475548
476568
  compilerOptions: {
475549
476569
  target: "ES2020",
@@ -475555,8 +476575,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
475555
476575
  skipLibCheck: true
475556
476576
  }
475557
476577
  };
475558
- await writeFile23(join35(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
475559
- await writeFile23(join35(dir, "Component.tsx"), componentCode);
476578
+ await writeFile23(join34(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
476579
+ await writeFile23(join34(dir, "Component.tsx"), componentCode);
475560
476580
  const rootCode = `import { registerRoot, Composition } from "remotion";
475561
476581
  import { ${componentName} } from "./Component";
475562
476582
 
@@ -475575,14 +476595,14 @@ const Root = () => {
475575
476595
 
475576
476596
  registerRoot(Root);
475577
476597
  `;
475578
- await writeFile23(join35(dir, "Root.tsx"), rootCode);
476598
+ await writeFile23(join34(dir, "Root.tsx"), rootCode);
475579
476599
  const depsHash = createHash7("md5").update(packageJsonStr).digest("hex").slice(0, 12);
475580
- const cacheMarker = join35(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
475581
- const cachedModules = join35(REMOTION_CACHE_DIR, "node_modules");
476600
+ const cacheMarker = join34(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
476601
+ const cachedModules = join34(REMOTION_CACHE_DIR, "node_modules");
475582
476602
  if (existsSync39(cacheMarker) && existsSync39(cachedModules)) {
475583
476603
  const { symlink } = await import("node:fs/promises");
475584
476604
  try {
475585
- await symlink(cachedModules, join35(dir, "node_modules"), "dir");
476605
+ await symlink(cachedModules, join34(dir, "node_modules"), "dir");
475586
476606
  } catch {
475587
476607
  await this_npmInstall(dir);
475588
476608
  }
@@ -475590,12 +476610,12 @@ registerRoot(Root);
475590
476610
  await this_npmInstall(dir);
475591
476611
  try {
475592
476612
  await mkdir15(REMOTION_CACHE_DIR, { recursive: true });
475593
- await writeFile23(join35(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
475594
- if (existsSync39(join35(dir, "node_modules"))) {
476613
+ await writeFile23(join34(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
476614
+ if (existsSync39(join34(dir, "node_modules"))) {
475595
476615
  const { rename: rename7, symlink } = await import("node:fs/promises");
475596
- await rename7(join35(dir, "node_modules"), cachedModules).catch(() => {
476616
+ await rename7(join34(dir, "node_modules"), cachedModules).catch(() => {
475597
476617
  });
475598
- await symlink(cachedModules, join35(dir, "node_modules"), "dir").catch(() => {
476618
+ await symlink(cachedModules, join34(dir, "node_modules"), "dir").catch(() => {
475599
476619
  });
475600
476620
  await writeFile23(cacheMarker, depsHash);
475601
476621
  }
@@ -475632,7 +476652,7 @@ async function renderMotion(options) {
475632
476652
  }
475633
476653
  );
475634
476654
  try {
475635
- const entryPoint = join35(dir, "Root.tsx");
476655
+ const entryPoint = join34(dir, "Root.tsx");
475636
476656
  const { execFile: execFile3 } = await import("node:child_process");
475637
476657
  const { promisify: promisify3 } = await import("node:util");
475638
476658
  const execFileAsync3 = promisify3(execFile3);
@@ -475765,10 +476785,10 @@ async function renderWithEmbeddedImage(options) {
475765
476785
  }
475766
476786
  );
475767
476787
  try {
475768
- const publicDir = join35(dir, "public");
476788
+ const publicDir = join34(dir, "public");
475769
476789
  await mkdir15(publicDir, { recursive: true });
475770
- await copyFile5(options.imagePath, join35(publicDir, options.imageFileName));
475771
- const entryPoint = join35(dir, "Root.tsx");
476790
+ await copyFile5(options.imagePath, join34(publicDir, options.imageFileName));
476791
+ const entryPoint = join34(dir, "Root.tsx");
475772
476792
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
475773
476793
  const { execFile: execFileImg } = await import("node:child_process");
475774
476794
  const { promisify: promisifyImg } = await import("node:util");
@@ -475829,10 +476849,10 @@ async function renderWithEmbeddedVideo(options) {
475829
476849
  }
475830
476850
  );
475831
476851
  try {
475832
- const publicDir = join35(dir, "public");
476852
+ const publicDir = join34(dir, "public");
475833
476853
  await mkdir15(publicDir, { recursive: true });
475834
- await copyFile5(options.videoPath, join35(publicDir, options.videoFileName));
475835
- const entryPoint = join35(dir, "Root.tsx");
476854
+ await copyFile5(options.videoPath, join34(publicDir, options.videoFileName));
476855
+ const entryPoint = join34(dir, "Root.tsx");
475836
476856
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
475837
476857
  const { execFile: execFileVid } = await import("node:child_process");
475838
476858
  const { promisify: promisifyVid } = await import("node:util");
@@ -476248,14 +477268,14 @@ var init_remotion = __esm({
476248
477268
  "use strict";
476249
477269
  init_exec_safe();
476250
477270
  REMOTION_VERSION = "4.0.447";
476251
- REMOTION_CACHE_DIR = join35(tmpdir3(), "vibe_remotion_cache");
477271
+ REMOTION_CACHE_DIR = join34(tmpdir3(), "vibe_remotion_cache");
476252
477272
  }
476253
477273
  });
476254
477274
 
476255
477275
  // ../cli/src/commands/_shared/edit/caption.ts
476256
477276
  import { existsSync as existsSync40 } from "node:fs";
476257
477277
  import { readFile as readFile22, writeFile as writeFile24, mkdir as mkdir16 } from "node:fs/promises";
476258
- import { dirname as dirname20, basename as basename9, extname as extname7, join as join36 } from "node:path";
477278
+ import { dirname as dirname20, basename as basename9, extname as extname7, join as join35 } from "node:path";
476259
477279
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
476260
477280
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
476261
477281
  const marginV = position === "center" ? 0 : 30;
@@ -476302,8 +477322,8 @@ async function executeCaption(options) {
476302
477322
  try {
476303
477323
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
476304
477324
  await mkdir16(tmpDir, { recursive: true });
476305
- const audioPath = join36(tmpDir, "audio.wav");
476306
- const srtPath = join36(tmpDir, "captions.srt");
477325
+ const audioPath = join35(tmpDir, "audio.wav");
477326
+ const srtPath = join35(tmpDir, "captions.srt");
476307
477327
  try {
476308
477328
  await execSafe(
476309
477329
  "ffmpeg",
@@ -476372,7 +477392,7 @@ async function executeCaption(options) {
476372
477392
  const remotionErr = await ensureRemotionInstalled2();
476373
477393
  if (remotionErr) {
476374
477394
  const outputDir2 = dirname20(outputPath);
476375
- const outputSrtPath2 = join36(
477395
+ const outputSrtPath2 = join35(
476376
477396
  outputDir2,
476377
477397
  basename9(outputPath, extname7(outputPath)) + ".srt"
476378
477398
  );
@@ -476411,7 +477431,7 @@ SRT saved to: ${outputSrtPath2}` };
476411
477431
  });
476412
477432
  if (!renderResult.success) {
476413
477433
  const outputDir2 = dirname20(outputPath);
476414
- const outputSrtPath2 = join36(
477434
+ const outputSrtPath2 = join35(
476415
477435
  outputDir2,
476416
477436
  basename9(outputPath, extname7(outputPath)) + ".srt"
476417
477437
  );
@@ -476424,7 +477444,7 @@ SRT saved to: ${outputSrtPath2}`
476424
477444
  }
476425
477445
  }
476426
477446
  const outputDir = dirname20(outputPath);
476427
- const outputSrtPath = join36(
477447
+ const outputSrtPath = join35(
476428
477448
  outputDir,
476429
477449
  basename9(outputPath, extname7(outputPath)) + ".srt"
476430
477450
  );
@@ -484392,10 +485412,18 @@ Examples:
484392
485412
  options.ratio = "16:9";
484393
485413
  }
484394
485414
  if (options.dryRun) {
485415
+ const costUsd = provider === "seedance" || provider === "fal" ? estimateSeedanceVideoCostUsd({
485416
+ durationSec: Number(options.duration) || 5,
485417
+ resolution: options.resolution,
485418
+ aspectRatio: options.ratio,
485419
+ fast: options.seedanceModel === "fast",
485420
+ hasVideoReference: Boolean(options.refVideos)
485421
+ }) : void 0;
484395
485422
  outputSuccess({
484396
485423
  command: "generate video",
484397
485424
  startedAt,
484398
485425
  dryRun: true,
485426
+ costUsd,
484399
485427
  data: {
484400
485428
  params: {
484401
485429
  prompt: prompt3,
@@ -486565,7 +487593,7 @@ function pickOutputPath(data) {
486565
487593
  }
486566
487594
 
486567
487595
  // ../cli/src/tools/_shared/elicit.ts
486568
- var NARRATION_CHOICES = ["kokoro", "elevenlabs"];
487596
+ var NARRATION_CHOICES = ["kokoro", "openai", "elevenlabs"];
486569
487597
  var BACKDROP_CHOICES = ["skip", "openai"];
486570
487598
  function planBuildElicitation(args) {
486571
487599
  const assetsWillRun = args.stage === void 0 || args.stage === "all" || args.stage === "assets";
@@ -486580,8 +487608,9 @@ function planBuildElicitation(args) {
486580
487608
  description: "Which TTS engine narrates the video?",
486581
487609
  enum: [...NARRATION_CHOICES],
486582
487610
  enumNames: [
486583
- "Kokoro \u2014 free, runs locally on this machine",
486584
- "ElevenLabs \u2014 cloud TTS, uses your ElevenLabs credits"
487611
+ "Kokoro \u2014 free, runs on this machine (first use downloads a ~90 MB voice model; slower)",
487612
+ "OpenAI \u2014 fast cloud voice, uses your OpenAI API key (~$0.02 per video)",
487613
+ "ElevenLabs \u2014 premium cloud voices, uses your ElevenLabs credits"
486585
487614
  ]
486586
487615
  };
486587
487616
  required.push("narration");
@@ -486844,6 +487873,9 @@ function clean(text) {
486844
487873
  function humanise(id) {
486845
487874
  return id.split(/[-_]+/).filter(Boolean).map((w2) => w2.charAt(0).toUpperCase() + w2.slice(1)).join(" ");
486846
487875
  }
487876
+ function sceneCompositionId(beatId) {
487877
+ return `scene-${beatId}`;
487878
+ }
486847
487879
  function slugifySceneName(name) {
486848
487880
  const normalised = name.normalize("NFKD").replace(/[̀-ͯ]/g, "");
486849
487881
  const slug = normalised.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -486881,10 +487913,16 @@ function fitKineticFontSize(words) {
486881
487913
  function kenBurnsTween(scope, dur, endScale = 1.08) {
486882
487914
  return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
486883
487915
  }
487916
+ function backdropMotionTweens(scope, dur, hasImage, endScale = 1.08) {
487917
+ const sweep = hasImage ? "" : `
487918
+ tl.fromTo('${scope} .ambient-sweep', { xPercent: -75, opacity: 0.18 }, { xPercent: 75, opacity: 0.62, duration: ${dur.toFixed(2)}, ease: 'sine.inOut' }, 0);`;
487919
+ return `${kenBurnsTween(scope, dur, endScale)}${sweep}`;
487920
+ }
486884
487921
  function buildPreset(input3) {
486885
487922
  const id = input3.id;
486886
487923
  const scope = `[data-composition-id="${id}"]`;
486887
- const headline = clean(input3.headline) || humanise(id);
487924
+ const displayId = input3.beatId ?? id;
487925
+ const headline = clean(input3.headline) || humanise(displayId);
486888
487926
  const subhead = clean(input3.subhead);
486889
487927
  const kicker = clean(input3.kicker);
486890
487928
  const dur = input3.duration;
@@ -486907,8 +487945,17 @@ function buildPreset(input3) {
486907
487945
  position: absolute;
486908
487946
  inset: 0;
486909
487947
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 70%);
487948
+ overflow: hidden;
487949
+ }
487950
+ ${scope} .ambient-sweep {
487951
+ position: absolute;
487952
+ inset: -22%;
487953
+ display: block;
487954
+ background: linear-gradient(115deg, rgba(255,255,255,0) 20%, rgba(0,201,255,0.34) 48%, rgba(142,45,226,0.26) 58%, rgba(255,255,255,0) 78%);
487955
+ filter: blur(12px);
487956
+ transform: rotate(8deg);
486910
487957
  }`;
486911
- const backdropMarkup = `<div class="backdrop"></div>`;
487958
+ const backdropMarkup = hasImage ? `<div class="backdrop"></div>` : `<div class="backdrop"><div class="ambient-sweep"></div></div>`;
486912
487959
  switch (input3.preset) {
486913
487960
  case "simple": {
486914
487961
  const transcript = input3.transcript;
@@ -486940,7 +487987,7 @@ function buildPreset(input3) {
486940
487987
  body: `${backdropMarkup}
486941
487988
  <div class="caption" id="caption">${captionInner}</div>`,
486942
487989
  timeline: `${crossfadeTweens(scope, dur)}
486943
- ${kenBurnsTween(scope, dur)}
487990
+ ${backdropMotionTweens(scope, dur, hasImage)}
486944
487991
  ${captionTween}`
486945
487992
  };
486946
487993
  }
@@ -486979,12 +488026,12 @@ function buildPreset(input3) {
486979
488026
  // scene's own fade-in handles the visual transition. Backdrop
486980
488027
  // Ken-Burns fills what used to be 80% dead static frame.
486981
488028
  timeline: `${crossfadeTweens(scope, dur)}
486982
- ${kenBurnsTween(scope, dur)}
488029
+ ${backdropMotionTweens(scope, dur, hasImage)}
486983
488030
  tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);`
486984
488031
  };
486985
488032
  }
486986
488033
  case "explainer": {
486987
- const k2 = kicker || humanise(id).toUpperCase();
488034
+ const k2 = kicker || humanise(displayId).toUpperCase();
486988
488035
  const sub = subhead || "";
486989
488036
  const transcript = input3.transcript;
486990
488037
  const useWordSync = !!(transcript && transcript.length > 0 && sub);
@@ -487026,7 +488073,7 @@ function buildPreset(input3) {
487026
488073
  <div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
487027
488074
  </div>`,
487028
488075
  timeline: `${crossfadeTweens(scope, dur)}
487029
- ${kenBurnsTween(scope, dur)}
488076
+ ${backdropMotionTweens(scope, dur, hasImage)}
487030
488077
  tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
487031
488078
  tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
487032
488079
  ${subtitleTween}`
@@ -487067,12 +488114,12 @@ function buildPreset(input3) {
487067
488114
  body: `${backdropMarkup}
487068
488115
  <div class="kinetic">${wordSpans}</div>`,
487069
488116
  timeline: `${crossfadeTweens(scope, dur)}
487070
- ${kenBurnsTween(scope, dur)}
488117
+ ${backdropMotionTweens(scope, dur, hasImage)}
487071
488118
  ${tweens}`
487072
488119
  };
487073
488120
  }
487074
488121
  case "product-shot": {
487075
- const label = kicker || humanise(id);
488122
+ const label = kicker || humanise(displayId);
487076
488123
  const headlineFontPx = fitFontSize(headline, 72, 40, 50);
487077
488124
  return {
487078
488125
  css: `${scope} {
@@ -487106,7 +488153,7 @@ function buildPreset(input3) {
487106
488153
  <div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
487107
488154
  <div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
487108
488155
  timeline: `${crossfadeTweens(scope, dur)}
487109
- tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
488156
+ ${backdropMotionTweens(scope, dur, hasImage, 1.12)}
487110
488157
  tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
487111
488158
  tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
487112
488159
  tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`
@@ -487171,9 +488218,10 @@ function emitSceneHtml(input3) {
487171
488218
  if (input3.width <= 0 || input3.height <= 0) {
487172
488219
  throw new Error(`Invalid canvas dims: ${input3.width}x${input3.height}`);
487173
488220
  }
487174
- const id = input3.id;
488221
+ const beatId = input3.id;
488222
+ const id = sceneCompositionId(beatId);
487175
488223
  const dur = Number(input3.duration.toFixed(3));
487176
- const parts = buildPreset({ ...input3, id, duration: dur });
488224
+ const parts = buildPreset({ ...input3, id, beatId, duration: dur });
487177
488225
  const audioBlock = input3.audioPath ? `
487178
488226
  <audio
487179
488227
  id="narration"
@@ -487189,7 +488237,7 @@ function emitSceneHtml(input3) {
487189
488237
  ${lottieLayer.markup}` : "";
487190
488238
  const lottieScript = lottieLayer ? `
487191
488239
  ${lottieLayer.script}` : "";
487192
- return `<template id="scene-${id}-template">
488240
+ return `<template id="${id}-template">
487193
488241
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input3.width}" data-height="${input3.height}">
487194
488242
  <style>
487195
488243
  ${parts.css}
@@ -487225,8 +488273,9 @@ function buildClipReference(opts) {
487225
488273
  const duration = Number(opts.duration.toFixed(3));
487226
488274
  const track = opts.trackIndex ?? 1;
487227
488275
  const src = opts.src ?? `compositions/scene-${opts.id}.html`;
488276
+ const compositionId = sceneCompositionId(opts.id);
487228
488277
  const zIndex = Math.max(1, 1e6 - Math.floor(start * 1e3));
487229
- return `<div class="clip" data-composition-id="${esc(opts.id)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
488278
+ return `<div class="clip" data-composition-id="${esc(compositionId)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
487230
488279
  }
487231
488280
  function insertClipIntoRoot(rootHtml, clip) {
487232
488281
  const clipDiv = buildClipReference(clip);
@@ -487270,9 +488319,9 @@ init_agent_host_detect();
487270
488319
  // ../cli/src/commands/_shared/install-skill.ts
487271
488320
  init_bundle_content();
487272
488321
  init_bundle();
487273
- import { mkdir as mkdir8, writeFile as writeFile11 } from "node:fs/promises";
488322
+ import { mkdir as mkdir8, writeFile as writeFile12 } from "node:fs/promises";
487274
488323
  import { existsSync as existsSync21 } from "node:fs";
487275
- import { dirname as dirname12, join as join21, relative as relative5, resolve as resolve15 } from "node:path";
488324
+ import { dirname as dirname12, join as join21, relative as relative5, resolve as resolve16 } from "node:path";
487276
488325
  function universalFiles() {
487277
488326
  return [
487278
488327
  { relPath: "SKILL.md", content: SKILL_MD },
@@ -487322,7 +488371,7 @@ function selectHostFiles(hosts) {
487322
488371
  return out;
487323
488372
  }
487324
488373
  async function installHyperframesSkill(opts) {
487325
- const projectDir = resolve15(opts.projectDir);
488374
+ const projectDir = resolve16(opts.projectDir);
487326
488375
  const force = opts.force ?? false;
487327
488376
  const dryRun = opts.dryRun ?? false;
487328
488377
  const files = [...universalFiles(), ...selectHostFiles(opts.hosts)];
@@ -487347,7 +488396,7 @@ async function installHyperframesSkill(opts) {
487347
488396
  continue;
487348
488397
  }
487349
488398
  await mkdir8(dirname12(absPath), { recursive: true });
487350
- await writeFile11(absPath, file.content, "utf-8");
488399
+ await writeFile12(absPath, file.content, "utf-8");
487351
488400
  actions.push({ path: file.relPath, status: "wrote", bytes });
487352
488401
  }
487353
488402
  return {
@@ -487372,10 +488421,10 @@ init_scene_lint();
487372
488421
  init_storyboard_parse();
487373
488422
  init_root_sync();
487374
488423
  import { existsSync as existsSync26 } from "node:fs";
487375
- import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile14 } from "node:fs/promises";
487376
- import { join as join26, resolve as resolve19 } from "node:path";
488424
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile15 } from "node:fs/promises";
488425
+ import { join as join26, resolve as resolve20 } from "node:path";
487377
488426
  async function executeSceneSubmit(opts) {
487378
- const projectDir = resolve19(opts.projectDir);
488427
+ const projectDir = resolve20(opts.projectDir);
487379
488428
  const beatId = opts.beatId.trim();
487380
488429
  const scenePath = `compositions/scene-${beatId}.html`;
487381
488430
  const base = {
@@ -487467,7 +488516,7 @@ async function executeSceneSubmit(opts) {
487467
488516
  }
487468
488517
  const absPath = join26(projectDir, scenePath);
487469
488518
  await mkdir10(join26(projectDir, "compositions"), { recursive: true });
487470
- await writeFile14(absPath, html.endsWith("\n") ? html : `${html}
488519
+ await writeFile15(absPath, html.endsWith("\n") ? html : `${html}
487471
488520
  `, "utf-8");
487472
488521
  return {
487473
488522
  ...base,
@@ -487482,6 +488531,8 @@ async function executeSceneSubmit(opts) {
487482
488531
 
487483
488532
  // ../cli/src/commands/scene.ts
487484
488533
  init_scene_repair();
488534
+ init_root_sync();
488535
+ init_storyboard_edit();
487485
488536
  function validateDuration(value) {
487486
488537
  const n2 = parseFloat(value);
487487
488538
  if (!Number.isFinite(n2) || n2 <= 0 || n2 > 3600) {
@@ -487784,11 +488835,14 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487784
488835
  ).option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option(
487785
488836
  "--visuals <prompt>",
487786
488837
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
487787
- ).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(
488838
+ ).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(
488839
+ "--no-storyboard",
488840
+ "Do not sync STORYBOARD.md; insert this scene directly into the root composition only"
488841
+ ).option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option(
487788
488842
  "--tts <provider>",
487789
- "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)",
488843
+ "TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
487790
488844
  "auto"
487791
- ).option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option(
488845
+ ).option("--voice <id>", "Voice id (ElevenLabs name/id, OpenAI voice like marin, or Kokoro id like af_heart)").option(
487792
488846
  "--no-audio",
487793
488847
  "Skip TTS even when --narration is provided (useful for tests/agent dry runs)"
487794
488848
  ).option("--no-image", "Skip image generation even when --visuals is provided").option(
@@ -487853,6 +488907,7 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487853
488907
  kicker: options.kicker,
487854
488908
  project: options.project,
487855
488909
  insertInto: options.insertInto,
488910
+ syncStoryboard: options.storyboard !== false,
487856
488911
  imageProvider: options.imageProvider,
487857
488912
  tts,
487858
488913
  audio: options.audio,
@@ -487880,6 +488935,7 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487880
488935
  kicker: options.kicker,
487881
488936
  projectDir: options.project,
487882
488937
  insertInto: options.insertInto,
488938
+ syncStoryboard: options.storyboard !== false,
487883
488939
  imageProvider: options.imageProvider,
487884
488940
  tts,
487885
488941
  voice: options.voice,
@@ -487919,6 +488975,13 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487919
488975
  if (result.audioPath) console.log(source_default.green(" +"), result.audioPath);
487920
488976
  if (result.imagePath) console.log(source_default.green(" +"), result.imagePath);
487921
488977
  if (result.transcriptPath) console.log(source_default.green(" +"), result.transcriptPath);
488978
+ if (result.storyboardPath && result.storyboardSynced) {
488979
+ console.log(
488980
+ result.storyboardAction === "updated" ? source_default.yellow(" ~") : source_default.green(" +"),
488981
+ result.storyboardPath,
488982
+ source_default.dim(`(${result.storyboardAction})`)
488983
+ );
488984
+ }
487922
488985
  console.log(source_default.yellow(" ~"), result.rootPath, source_default.dim("(updated)"));
487923
488986
  console.log();
487924
488987
  console.log(source_default.bold.cyan("Composition"));
@@ -487965,6 +489028,21 @@ async function loadVibeProjectConfig(projectDir) {
487965
489028
  }
487966
489029
  };
487967
489030
  }
489031
+ function escapeRegExp3(value) {
489032
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
489033
+ }
489034
+ function readClipStart(rootHtml, beatId) {
489035
+ const compositionId = sceneCompositionId(beatId);
489036
+ const match2 = rootHtml.match(
489037
+ new RegExp(
489038
+ `<div\\s+class="clip"[^>]*\\bdata-composition-id="${escapeRegExp3(compositionId)}"[^>]*\\bdata-start="([\\d.]+)"`,
489039
+ "i"
489040
+ )
489041
+ );
489042
+ if (!match2) return void 0;
489043
+ const start = Number.parseFloat(match2[1]);
489044
+ return Number.isFinite(start) ? start : void 0;
489045
+ }
487968
489046
  async function resolveNarrationText(value) {
487969
489047
  if (!value) return void 0;
487970
489048
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
@@ -487991,6 +489069,7 @@ async function executeSceneAdd(opts) {
487991
489069
  const id = slugifySceneName(opts.name);
487992
489070
  const sceneRel = `compositions/scene-${id}.html`;
487993
489071
  const scenePath = resolve24(projectDir, sceneRel);
489072
+ const storyboardAbsPath = resolve24(projectDir, "STORYBOARD.md");
487994
489073
  const errResult = (error) => ({
487995
489074
  success: false,
487996
489075
  id,
@@ -488049,7 +489128,7 @@ async function executeSceneAdd(opts) {
488049
489128
  throw error;
488050
489129
  }
488051
489130
  opts.onProgress?.(
488052
- resolution.provider === "kokoro" ? "Generating narration with Kokoro (local \u2014 first run downloads ~330MB)..." : "Generating narration with ElevenLabs..."
489131
+ 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
489132
  );
488054
489133
  const tts = await resolution.call(narrationText, {
488055
489134
  voice: opts.voice,
@@ -488210,6 +489289,27 @@ async function executeSceneAdd(opts) {
488210
489289
  duration = fallback2;
488211
489290
  }
488212
489291
  duration = Number(duration.toFixed(2));
489292
+ let storyboardPath;
489293
+ let storyboardSynced = false;
489294
+ let storyboardAction = "skipped";
489295
+ const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
489296
+ if (shouldSyncStoryboard) {
489297
+ opts.onProgress?.("Syncing STORYBOARD.md...");
489298
+ const before2 = await readFile14(storyboardAbsPath, "utf-8");
489299
+ const synced = upsertStoryboardBeat(before2, {
489300
+ beatId: id,
489301
+ title: opts.headline,
489302
+ duration,
489303
+ narration: narrationText && !opts.skipAudio ? narrationText : void 0,
489304
+ backdrop: opts.visuals && !opts.skipImage ? opts.visuals : void 0
489305
+ });
489306
+ if (synced.markdown !== before2) {
489307
+ await writeFile17(storyboardAbsPath, synced.markdown, "utf-8");
489308
+ }
489309
+ storyboardPath = relative8(process.cwd(), storyboardAbsPath) || storyboardAbsPath;
489310
+ storyboardSynced = true;
489311
+ storyboardAction = synced.action;
489312
+ }
488213
489313
  opts.onProgress?.("Emitting scene HTML...");
488214
489314
  const sceneHtml = emitSceneHtml({
488215
489315
  id,
@@ -488228,11 +489328,24 @@ async function executeSceneAdd(opts) {
488228
489328
  await mkdir11(dirname14(scenePath), { recursive: true });
488229
489329
  await writeFile17(scenePath, sceneHtml, "utf-8");
488230
489330
  opts.onProgress?.("Updating root composition...");
488231
- const start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
488232
- const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
488233
- const trackIndex = existingClipCount % 2 + 1;
488234
- const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
488235
- await writeFile17(rootPath, updated, "utf-8");
489331
+ let start = 0;
489332
+ if (storyboardSynced) {
489333
+ const syncPlan = await syncRootComposition({
489334
+ projectDir,
489335
+ rootRel,
489336
+ beats: await loadProjectRootSyncBeats(projectDir)
489337
+ });
489338
+ const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
489339
+ if (blockingIssue) return errResult(blockingIssue.message);
489340
+ const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
489341
+ start = readClipStart(rootHtmlAfterSync, id) ?? 0;
489342
+ } else {
489343
+ start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
489344
+ const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
489345
+ const trackIndex = existingClipCount % 2 + 1;
489346
+ const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
489347
+ await writeFile17(rootPath, updated, "utf-8");
489348
+ }
488236
489349
  const transcriptAbsPath = transcriptRelPath ? resolve24(projectDir, transcriptRelPath) : void 0;
488237
489350
  return {
488238
489351
  success: true,
@@ -488245,6 +489358,9 @@ async function executeSceneAdd(opts) {
488245
489358
  audioPath: audioAbsPath ? relative8(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
488246
489359
  imagePath: imageAbsPath ? relative8(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
488247
489360
  lottiePath: lottieAbsPath ? relative8(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
489361
+ storyboardPath,
489362
+ storyboardSynced,
489363
+ storyboardAction,
488248
489364
  transcriptPath: transcriptAbsPath ? relative8(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
488249
489365
  transcriptWordCount
488250
489366
  };
@@ -488542,6 +489658,9 @@ var sceneAddSchema = z2.object({
488542
489658
  kicker: z2.string().optional().describe("Small label above the headline (used by 'explainer' and 'product-shot' presets)."),
488543
489659
  projectDir: z2.string().optional().describe(PROJECT_DIR_DESCRIPTION),
488544
489660
  insertInto: z2.string().optional().describe("Root composition file (relative to projectDir). Default 'index.html'."),
489661
+ syncStoryboard: z2.boolean().optional().describe(
489662
+ "Sync STORYBOARD.md when present, then rebuild root refs from it. Default true; set false for root-only insertion."
489663
+ ),
488545
489664
  imageProvider: z2.enum(["gemini", "openai"]).optional().describe("Image provider for visuals. Default 'gemini'."),
488546
489665
  voice: z2.string().optional().describe("ElevenLabs voice id or name."),
488547
489666
  skipAudio: z2.boolean().optional().describe("Skip TTS even if narration is provided."),
@@ -488554,7 +489673,7 @@ var sceneAddTool = defineTool({
488554
489673
  cost: "low",
488555
489674
  title: "Add Scene from Preset",
488556
489675
  annotations: { readOnly: false, openWorld: true },
488557
- description: "Add a single scene to an existing scene project. Optionally generates narration audio (ElevenLabs) and/or a backdrop image (Gemini/OpenAI), then emits compositions/scene-<id>.html with a paused GSAP timeline and splices a clip reference into the root index.html. Use skipAudio:true and skipImage:true for text-only scenes that need no API calls.",
489676
+ description: "Add a single scene to an existing scene project. Optionally generates narration audio (ElevenLabs) and/or a backdrop image (Gemini/OpenAI), then emits compositions/scene-<id>.html with a paused GSAP timeline. By default, syncs STORYBOARD.md and rebuilds root refs from it; set syncStoryboard:false for root-only insertion. Use skipAudio:true and skipImage:true for text-only scenes that need no API calls.",
488558
489677
  schema: sceneAddSchema,
488559
489678
  async execute(args, ctx) {
488560
489679
  const projectDir = args.projectDir ? resolve34(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
@@ -488568,6 +489687,7 @@ var sceneAddTool = defineTool({
488568
489687
  kicker: args.kicker,
488569
489688
  projectDir,
488570
489689
  insertInto: args.insertInto,
489690
+ syncStoryboard: args.syncStoryboard,
488571
489691
  imageProvider: args.imageProvider,
488572
489692
  voice: args.voice,
488573
489693
  skipAudio: args.skipAudio,
@@ -488586,6 +489706,9 @@ var sceneAddTool = defineTool({
488586
489706
  ];
488587
489707
  if (result.audioPath) lines.push(` audio: ${result.audioPath}`);
488588
489708
  if (result.imagePath) lines.push(` image: ${result.imagePath}`);
489709
+ if (result.storyboardPath && result.storyboardSynced) {
489710
+ lines.push(` story: ${result.storyboardPath} (${result.storyboardAction})`);
489711
+ }
488589
489712
  return {
488590
489713
  success: true,
488591
489714
  data: {
@@ -488596,7 +489719,10 @@ var sceneAddTool = defineTool({
488596
489719
  scenePath: result.scenePath,
488597
489720
  rootPath: result.rootPath,
488598
489721
  audioPath: result.audioPath,
488599
- imagePath: result.imagePath
489722
+ imagePath: result.imagePath,
489723
+ storyboardPath: result.storyboardPath,
489724
+ storyboardSynced: result.storyboardSynced,
489725
+ storyboardAction: result.storyboardAction
488600
489726
  },
488601
489727
  humanLines: lines
488602
489728
  };
@@ -488809,7 +489935,9 @@ var sceneBuildSchema = z2.object({
488809
489935
  skipVideo: z2.boolean().optional().describe("Skip video generation for every beat (use existing MP4 assets if present)."),
488810
489936
  skipMusic: z2.boolean().optional().describe("Skip music generation for every beat (use existing audio assets if present)."),
488811
489937
  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'."),
489938
+ ttsProvider: z2.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe(
489939
+ "TTS provider override. Default 'auto' (elevenlabs key > openai key > local kokoro)."
489940
+ ),
488813
489941
  voice: z2.string().optional().describe("TTS voice id (provider-specific)."),
488814
489942
  imageProvider: z2.enum(["openai"]).optional().describe("Image provider for backdrops. Default 'openai' (gpt-image-2)."),
488815
489943
  videoProvider: z2.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
@@ -490029,7 +491157,7 @@ init_root_sync();
490029
491157
  init_review_report();
490030
491158
  import { existsSync as existsSync56 } from "node:fs";
490031
491159
  import { readdir as readdir6, readFile as readFile31, stat as stat5 } from "node:fs/promises";
490032
- import { basename as basename14, isAbsolute as isAbsolute7, join as join38, relative as relative13, resolve as resolve50 } from "node:path";
491160
+ import { basename as basename14, isAbsolute as isAbsolute7, join as join37, relative as relative13, resolve as resolve50 } from "node:path";
490033
491161
  async function inspectProject(opts) {
490034
491162
  const projectDir = resolve50(opts.projectDir);
490035
491163
  const issues = [];
@@ -490058,12 +491186,12 @@ async function inspectProject(opts) {
490058
491186
  );
490059
491187
  }
490060
491188
  const coreFiles = {
490061
- storyboard: join38(projectDir, "STORYBOARD.md"),
490062
- design: join38(projectDir, "DESIGN.md"),
490063
- config: join38(projectDir, "vibe.config.json"),
490064
- legacyConfig: join38(projectDir, "vibe.project.yaml"),
490065
- buildReport: join38(projectDir, "build-report.json"),
490066
- root: join38(projectDir, "index.html")
491189
+ storyboard: join37(projectDir, "STORYBOARD.md"),
491190
+ design: join37(projectDir, "DESIGN.md"),
491191
+ config: join37(projectDir, "vibe.config.json"),
491192
+ legacyConfig: join37(projectDir, "vibe.project.yaml"),
491193
+ buildReport: join37(projectDir, "build-report.json"),
491194
+ root: join37(projectDir, "index.html")
490067
491195
  };
490068
491196
  for (const [name, path14] of Object.entries(coreFiles)) {
490069
491197
  checks.files[name] = existsSync56(path14);
@@ -490147,7 +491275,7 @@ async function inspectProject(opts) {
490147
491275
  });
490148
491276
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
490149
491277
  }
490150
- const compositionsDir = join38(projectDir, "compositions");
491278
+ const compositionsDir = join37(projectDir, "compositions");
490151
491279
  const existingComps = existsSync56(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
490152
491280
  checks.compositions.expected = inspectedBeatIds.length;
490153
491281
  for (const beatId of inspectedBeatIds) {
@@ -490155,12 +491283,12 @@ async function inspectProject(opts) {
490155
491283
  if (existingComps.has(file)) {
490156
491284
  checks.compositions.found++;
490157
491285
  } else {
490158
- checks.compositions.missing.push(join38("compositions", file));
491286
+ checks.compositions.missing.push(join37("compositions", file));
490159
491287
  issues.push({
490160
491288
  severity: "error",
490161
491289
  code: "MISSING_COMPOSITION",
490162
491290
  message: `Composition for beat "${beatId}" is missing.`,
490163
- file: join38("compositions", file),
491291
+ file: join37("compositions", file),
490164
491292
  scene: beatId,
490165
491293
  beatId,
490166
491294
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -490222,7 +491350,7 @@ async function inspectProject(opts) {
490222
491350
  try {
490223
491351
  const lint = await runProjectLint({ projectDir });
490224
491352
  const lintFiles = opts.beatId ? lint.files.filter(
490225
- (file) => file.file === "index.html" || file.file === join38("compositions", `scene-${opts.beatId}.html`)
491353
+ (file) => file.file === "index.html" || file.file === join37("compositions", `scene-${opts.beatId}.html`)
490226
491354
  ) : lint.files;
490227
491355
  const lintCounts = countLintFindings(lintFiles);
490228
491356
  checks.lint = {
@@ -490258,7 +491386,11 @@ async function inspectProject(opts) {
490258
491386
  );
490259
491387
  }
490260
491388
  function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
490261
- const normalizedIssues = normalizeReviewIssues(issues);
491389
+ const normalizedRetryWith = uniqueRetryWith(retryWith);
491390
+ const normalizedIssues = normalizeReviewIssues(issues, {
491391
+ projectDir,
491392
+ retryWith: normalizedRetryWith
491393
+ });
490262
491394
  const status = statusFromIssues(normalizedIssues);
490263
491395
  return {
490264
491396
  schemaVersion: "1",
@@ -490270,9 +491402,14 @@ function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
490270
491402
  score: scoreIssues(normalizedIssues),
490271
491403
  issues: normalizedIssues,
490272
491404
  summary: summarizeReviewIssues(normalizedIssues),
491405
+ nextActions: deriveNextReviewActions({
491406
+ issues: normalizedIssues,
491407
+ retryWith: normalizedRetryWith,
491408
+ projectDir
491409
+ }),
490273
491410
  sourceReports: projectSourceReports(checks),
490274
491411
  checks,
490275
- retryWith: uniqueRetryWith(retryWith)
491412
+ retryWith: normalizedRetryWith
490276
491413
  };
490277
491414
  }
490278
491415
  function storyboardPlaceholderIssues(storyboard) {
@@ -490592,7 +491729,7 @@ init_storyboard_parse();
490592
491729
  init_review_report();
490593
491730
  import { existsSync as existsSync57 } from "node:fs";
490594
491731
  import { readdir as readdir7, readFile as readFile32, stat as stat6 } from "node:fs/promises";
490595
- import { basename as basename15, isAbsolute as isAbsolute8, join as join39, relative as relative14, resolve as resolve51 } from "node:path";
491732
+ import { basename as basename15, isAbsolute as isAbsolute8, join as join38, relative as relative14, resolve as resolve51 } from "node:path";
490596
491733
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
490597
491734
  var DEFAULT_AI_MODEL = "flash";
490598
491735
  var BLACK_FRAME_MIN_DURATION_SEC = 1;
@@ -490824,6 +491961,8 @@ async function inspectRender(opts) {
490824
491961
  severity: "error",
490825
491962
  code: "RENDER_NOT_FOUND",
490826
491963
  message: opts.beatId ? `No rendered video was found for beat "${opts.beatId}". Pass --video or render the beat first.` : "No rendered video was found. Pass --video or render the project first.",
491964
+ beatId: opts.beatId,
491965
+ scene: opts.beatId,
490827
491966
  suggestedFix: opts.beatId ? "Run `vibe render <project> --beat <id> --json`." : "Run `vibe build --stage render --json` or `vibe render --json`."
490828
491967
  });
490829
491968
  retryWith.push(
@@ -490846,6 +491985,8 @@ async function inspectRender(opts) {
490846
491985
  code: "EMPTY_RENDER",
490847
491986
  message: "Rendered video file is empty.",
490848
491987
  file: displayPath(videoPath),
491988
+ beatId: opts.beatId,
491989
+ scene: opts.beatId,
490849
491990
  suggestedFix: opts.beatId ? "Render again with `vibe render --beat <id> --json`." : "Render again with `vibe render --json`."
490850
491991
  });
490851
491992
  retryWith.push(
@@ -490855,6 +491996,8 @@ async function inspectRender(opts) {
490855
491996
  const beatTimings = await beatTimingsFromBuildReport(projectDir, opts.beatId);
490856
491997
  const expectedDurationSec = beatTimings.length > 0 ? Number(beatTimings.reduce((sum, beat) => sum + beat.sceneDurationSec, 0).toFixed(3)) : await expectedDurationFromBuildReport(projectDir, opts.beatId);
490857
491998
  if (expectedDurationSec !== void 0) checks.expectedDurationSec = expectedDurationSec;
491999
+ const expectedAudio = await renderExpectsAudio(projectDir, opts.beatId);
492000
+ checks.expectedAudio = expectedAudio;
490858
492001
  if (!commandExists("ffprobe")) {
490859
492002
  issues.push({
490860
492003
  severity: "error",
@@ -490880,7 +492023,7 @@ async function inspectRender(opts) {
490880
492023
  file: displayPath(videoPath)
490881
492024
  });
490882
492025
  }
490883
- if (!audioStream) {
492026
+ if (!audioStream && expectedAudio) {
490884
492027
  issues.push({
490885
492028
  severity: "warning",
490886
492029
  code: "NO_AUDIO_STREAM",
@@ -491085,8 +492228,86 @@ async function inspectRender(opts) {
491085
492228
  );
491086
492229
  return maybeWriteRenderReport(projectDir, opts, result);
491087
492230
  }
492231
+ async function renderExpectsAudio(projectDir, beatId) {
492232
+ const root2 = resolve51(projectDir);
492233
+ const rootHtml = await readTextIfExists(join38(root2, "index.html"));
492234
+ if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
492235
+ const buildReport = await readJsonIfExists(join38(root2, "build-report.json"));
492236
+ if (buildReportExpectsAudio(buildReport, beatId)) return true;
492237
+ const storyboard = await readTextIfExists(join38(root2, "STORYBOARD.md"));
492238
+ if (!storyboard) return false;
492239
+ try {
492240
+ const parsed = parseStoryboard(storyboard);
492241
+ const beats = beatId ? parsed.beats.filter((beat) => beat.id === beatId) : parsed.beats;
492242
+ return beats.some(
492243
+ (beat) => [
492244
+ `assets/narration-${beat.id}.mp3`,
492245
+ `assets/narration-${beat.id}.wav`,
492246
+ `assets/music-${beat.id}.mp3`,
492247
+ `assets/music-${beat.id}.wav`
492248
+ ].some((asset) => existsSync57(join38(root2, asset)))
492249
+ );
492250
+ } catch {
492251
+ return false;
492252
+ }
492253
+ }
492254
+ function rootHtmlExpectsAudio(html, beatId) {
492255
+ const audioTags = html.match(/<audio\b[^>]*>/gi) ?? [];
492256
+ if (!beatId) return audioTags.length > 0;
492257
+ const escaped = escapeRegExp4(beatId);
492258
+ const beatAudioRe = new RegExp(
492259
+ `(id="(?:narration|music)-${escaped}"|src="[^"]*(?:narration|music)-${escaped}\\.(?:mp3|wav)")`,
492260
+ "i"
492261
+ );
492262
+ return audioTags.some((tag) => beatAudioRe.test(tag));
492263
+ }
492264
+ function buildReportExpectsAudio(report, beatId) {
492265
+ if (!report || typeof report !== "object") return false;
492266
+ const beats = Array.isArray(report.beats) ? report.beats ?? [] : [];
492267
+ const selected = beatId === void 0 ? beats : beats.filter((beat) => {
492268
+ if (!beat || typeof beat !== "object") return false;
492269
+ return beat.id === beatId;
492270
+ });
492271
+ return selected.some((beat) => beatReportExpectsAudio(beat));
492272
+ }
492273
+ function beatReportExpectsAudio(beat) {
492274
+ if (!beat || typeof beat !== "object") return false;
492275
+ const record = beat;
492276
+ if (typeof record.narrationPath === "string" && record.narrationPath.length > 0) return true;
492277
+ if (typeof record.musicPath === "string" && record.musicPath.length > 0) return true;
492278
+ if (parseOptionalNumber2(record.narrationDurationSec) !== void 0) return true;
492279
+ const narration = record.narration;
492280
+ if (narration && typeof narration === "object") {
492281
+ const n2 = narration;
492282
+ if (typeof n2.path === "string" && n2.path.length > 0) return true;
492283
+ if (parseOptionalNumber2(n2.durationSec) !== void 0) return true;
492284
+ }
492285
+ const music = record.music;
492286
+ if (music && typeof music === "object") {
492287
+ const m2 = music;
492288
+ if (typeof m2.path === "string" && m2.path.length > 0) return true;
492289
+ if (parseOptionalNumber2(m2.durationSec) !== void 0) return true;
492290
+ }
492291
+ return false;
492292
+ }
492293
+ async function readJsonIfExists(path14) {
492294
+ const text = await readTextIfExists(path14);
492295
+ if (!text) return null;
492296
+ try {
492297
+ return JSON.parse(text);
492298
+ } catch {
492299
+ return null;
492300
+ }
492301
+ }
492302
+ function escapeRegExp4(value) {
492303
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
492304
+ }
491088
492305
  function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith, score) {
491089
- const normalizedIssues = normalizeReviewIssues(issues);
492306
+ const normalizedRetryWith = uniqueRetryWith(retryWith);
492307
+ const normalizedIssues = normalizeReviewIssues(issues, {
492308
+ projectDir,
492309
+ retryWith: normalizedRetryWith
492310
+ });
491090
492311
  const status = statusFromIssues(normalizedIssues);
491091
492312
  return {
491092
492313
  schemaVersion: "1",
@@ -491099,9 +492320,14 @@ function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith
491099
492320
  score,
491100
492321
  issues: normalizedIssues,
491101
492322
  summary: summarizeReviewIssues(normalizedIssues),
492323
+ nextActions: deriveNextReviewActions({
492324
+ issues: normalizedIssues,
492325
+ retryWith: normalizedRetryWith,
492326
+ projectDir
492327
+ }),
491102
492328
  sourceReports: renderSourceReports(projectDir, videoPath, checks),
491103
492329
  checks,
491104
- retryWith: uniqueRetryWith(retryWith)
492330
+ retryWith: normalizedRetryWith
491105
492331
  };
491106
492332
  }
491107
492333
  async function maybeWriteRenderReport(projectDir, opts, result) {
@@ -491127,8 +492353,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
491127
492353
  }
491128
492354
  function renderSourceReports(projectDir, videoPath, checks) {
491129
492355
  const reports = [];
491130
- if (existsSync57(join39(projectDir, "build-report.json"))) reports.push("build-report.json");
491131
- if (existsSync57(join39(projectDir, "render-report.json"))) reports.push("render-report.json");
492356
+ if (existsSync57(join38(projectDir, "build-report.json"))) reports.push("build-report.json");
492357
+ if (existsSync57(join38(projectDir, "render-report.json"))) reports.push("render-report.json");
491132
492358
  if (videoPath) reports.push(displayPath(videoPath));
491133
492359
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
491134
492360
  reports.push("ffprobe");
@@ -491143,7 +492369,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491143
492369
  const candidate = resolve51(process.cwd(), explicit);
491144
492370
  return existsSync57(candidate) ? candidate : null;
491145
492371
  }
491146
- const renderReportPath = join39(projectDir, "render-report.json");
492372
+ const renderReportPath = join38(projectDir, "render-report.json");
491147
492373
  if (existsSync57(renderReportPath)) {
491148
492374
  try {
491149
492375
  const report = JSON.parse(await readFile32(renderReportPath, "utf-8"));
@@ -491155,7 +492381,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491155
492381
  } catch {
491156
492382
  }
491157
492383
  }
491158
- const reportPath = join39(projectDir, "build-report.json");
492384
+ const reportPath = join38(projectDir, "build-report.json");
491159
492385
  if (!beatId && existsSync57(reportPath)) {
491160
492386
  try {
491161
492387
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491164,7 +492390,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491164
492390
  } catch {
491165
492391
  }
491166
492392
  }
491167
- const rendersDir = join39(projectDir, "renders");
492393
+ const rendersDir = join38(projectDir, "renders");
491168
492394
  if (!existsSync57(rendersDir)) return null;
491169
492395
  const entries2 = await readdir7(rendersDir, { withFileTypes: true });
491170
492396
  const candidates = [];
@@ -491173,7 +492399,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491173
492399
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
491174
492400
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
491175
492401
  if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
491176
- const full = join39(rendersDir, entry.name);
492402
+ const full = join38(rendersDir, entry.name);
491177
492403
  const info = await stat6(full);
491178
492404
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
491179
492405
  }
@@ -491191,11 +492417,11 @@ async function runAiRenderReview(projectDir, videoPath, model) {
491191
492417
  });
491192
492418
  }
491193
492419
  function resolveStoryboardPath(projectDir) {
491194
- const storyboardPath = join39(projectDir, "STORYBOARD.md");
492420
+ const storyboardPath = join38(projectDir, "STORYBOARD.md");
491195
492421
  return existsSync57(storyboardPath) ? storyboardPath : null;
491196
492422
  }
491197
492423
  async function expectedDurationFromBuildReport(projectDir, beatId) {
491198
- const reportPath = join39(projectDir, "build-report.json");
492424
+ const reportPath = join38(projectDir, "build-report.json");
491199
492425
  if (!existsSync57(reportPath)) return void 0;
491200
492426
  try {
491201
492427
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491210,7 +492436,7 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
491210
492436
  }
491211
492437
  }
491212
492438
  async function beatTimingsFromBuildReport(projectDir, beatId) {
491213
- const reportPath = join39(projectDir, "build-report.json");
492439
+ const reportPath = join38(projectDir, "build-report.json");
491214
492440
  if (!existsSync57(reportPath)) return [];
491215
492441
  try {
491216
492442
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491239,17 +492465,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
491239
492465
  }
491240
492466
  async function buildRenderReviewProjectContext(projectDir) {
491241
492467
  const parts = [];
491242
- const storyboard = await readTextIfExists(join39(projectDir, "STORYBOARD.md"));
492468
+ const storyboard = await readTextIfExists(join38(projectDir, "STORYBOARD.md"));
491243
492469
  if (storyboard) {
491244
492470
  parts.push(`STORYBOARD.md:
491245
492471
  ${truncateText(storyboard, 4e3)}`);
491246
492472
  parts.push(`Storyboard beat cue summary:
491247
492473
  ${storyboardBeatCueSummary(storyboard)}`);
491248
492474
  }
491249
- const design = await readTextIfExists(join39(projectDir, "DESIGN.md"));
492475
+ const design = await readTextIfExists(join38(projectDir, "DESIGN.md"));
491250
492476
  if (design) parts.push(`DESIGN.md:
491251
492477
  ${truncateText(design, 4e3)}`);
491252
- const buildReport = await readTextIfExists(join39(projectDir, "build-report.json"));
492478
+ const buildReport = await readTextIfExists(join38(projectDir, "build-report.json"));
491253
492479
  if (buildReport) parts.push(`build-report.json:
491254
492480
  ${truncateText(buildReport, 6e3)}`);
491255
492481
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -492108,7 +493334,7 @@ init_storyboard_edit();
492108
493334
  // ../cli/src/commands/_shared/storyboard-revise.ts
492109
493335
  import { existsSync as existsSync59 } from "node:fs";
492110
493336
  import { readFile as readFile35, writeFile as writeFile42 } from "node:fs/promises";
492111
- import { join as join40, resolve as resolve64 } from "node:path";
493337
+ import { join as join39, resolve as resolve64 } from "node:path";
492112
493338
 
492113
493339
  // ../cli/src/agent/adapters/index.ts
492114
493340
  init_openai3();
@@ -492155,7 +493381,7 @@ init_storyboard_parse();
492155
493381
  init_storyboard_edit();
492156
493382
  async function executeStoryboardRevision(opts) {
492157
493383
  const projectDir = resolve64(opts.projectDir);
492158
- const storyboardPath = join40(projectDir, "STORYBOARD.md");
493384
+ const storyboardPath = join39(projectDir, "STORYBOARD.md");
492159
493385
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
492160
493386
  if (!existsSync59(storyboardPath)) {
492161
493387
  return failureResult({
@@ -492390,8 +493616,8 @@ function diffChangedBeats(before2, after2) {
492390
493616
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
492391
493617
  }
492392
493618
  async function readRevisionContext(projectDir) {
492393
- const designMd = await readOptionalText(join40(projectDir, "DESIGN.md"));
492394
- const buildReportJson = await readOptionalJsonText(join40(projectDir, "build-report.json"));
493619
+ const designMd = await readOptionalText(join39(projectDir, "DESIGN.md"));
493620
+ const buildReportJson = await readOptionalJsonText(join39(projectDir, "build-report.json"));
492395
493621
  return { designMd, buildReportJson };
492396
493622
  }
492397
493623
  async function readOptionalText(path14) {
@@ -492652,7 +493878,7 @@ var planTool = defineTool({
492652
493878
  skipBackdrop: z7.boolean().optional().describe("Don't include backdrop image generation in the plan."),
492653
493879
  skipVideo: z7.boolean().optional().describe("Don't include video generation in the plan."),
492654
493880
  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."),
493881
+ ttsProvider: z7.enum(["auto", "elevenlabs", "openai", "kokoro"]).optional().describe("TTS provider override."),
492656
493882
  voice: z7.string().optional().describe("Voice id."),
492657
493883
  imageProvider: z7.string().optional().describe("Image provider override."),
492658
493884
  imageQuality: z7.enum(["standard", "hd"]).optional().describe("Image quality override."),
@@ -492706,7 +493932,7 @@ var storyboardTools = [
492706
493932
  // ../cli/src/tools/manifest/remix.ts
492707
493933
  import { writeFile as writeFile48 } from "node:fs/promises";
492708
493934
  import { tmpdir as tmpdir6 } from "node:os";
492709
- import { join as join41 } from "node:path";
493935
+ import { join as join40 } from "node:path";
492710
493936
  import { z as z8 } from "zod";
492711
493937
 
492712
493938
  // ../cli/src/commands/ai-script-pipeline.ts
@@ -494440,7 +495666,7 @@ var pipelineRunTool = defineTool({
494440
495666
  let resolvedPath = args.pipelinePath;
494441
495667
  let tempPath;
494442
495668
  if (args.pipelineYaml) {
494443
- tempPath = join41(tmpdir6(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
495669
+ tempPath = join40(tmpdir6(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
494444
495670
  await writeFile48(tempPath, args.pipelineYaml, "utf-8");
494445
495671
  resolvedPath = tempPath;
494446
495672
  }
@@ -494972,7 +496198,7 @@ init_engine();
494972
496198
  init_storyboard_parse();
494973
496199
  import { readdir as readdir8, readFile as readFile42, stat as stat7 } from "node:fs/promises";
494974
496200
  import { existsSync as existsSync65 } from "node:fs";
494975
- import { join as join43, resolve as resolve71 } from "node:path";
496201
+ import { join as join41, resolve as resolve71 } from "node:path";
494976
496202
  async function executeProjectList(opts = {}) {
494977
496203
  const workspaceDir = resolve71(opts.workspaceDir ?? ".");
494978
496204
  if (!existsSync65(workspaceDir)) {
@@ -494992,11 +496218,11 @@ async function executeProjectList(opts = {}) {
494992
496218
  if (entry.name === "node_modules") continue;
494993
496219
  if (entry.name.startsWith(".")) continue;
494994
496220
  if (entry.name.startsWith("_")) {
494995
- archivedCount += await countProjects(join43(workspaceDir, entry.name));
496221
+ archivedCount += await countProjects(join41(workspaceDir, entry.name));
494996
496222
  continue;
494997
496223
  }
494998
- const projectDir = join43(workspaceDir, entry.name);
494999
- if (!existsSync65(join43(projectDir, "STORYBOARD.md"))) continue;
496224
+ const projectDir = join41(workspaceDir, entry.name);
496225
+ if (!existsSync65(join41(projectDir, "STORYBOARD.md"))) continue;
495000
496226
  projects.push(await describeProject(entry.name, projectDir));
495001
496227
  }
495002
496228
  projects.sort((a2, b2) => b2.updatedAt.localeCompare(a2.updatedAt));
@@ -495006,7 +496232,7 @@ async function countProjects(dir) {
495006
496232
  try {
495007
496233
  const entries2 = await readdir8(dir, { withFileTypes: true });
495008
496234
  return entries2.filter(
495009
- (e2) => e2.isDirectory() && existsSync65(join43(dir, e2.name, "STORYBOARD.md"))
496235
+ (e2) => e2.isDirectory() && existsSync65(join41(dir, e2.name, "STORYBOARD.md"))
495010
496236
  ).length;
495011
496237
  } catch {
495012
496238
  return 0;
@@ -495017,7 +496243,7 @@ async function describeProject(name, projectDir) {
495017
496243
  let storyboardDurationSec = 0;
495018
496244
  const timestamps = [];
495019
496245
  try {
495020
- const storyboardPath = join43(projectDir, "STORYBOARD.md");
496246
+ const storyboardPath = join41(projectDir, "STORYBOARD.md");
495021
496247
  const parsed = parseStoryboard(await readFile42(storyboardPath, "utf-8"));
495022
496248
  beats = parsed.beats.length;
495023
496249
  storyboardDurationSec = Number(
@@ -495029,7 +496255,7 @@ async function describeProject(name, projectDir) {
495029
496255
  let buildStatus;
495030
496256
  let buildUpdatedAt;
495031
496257
  try {
495032
- const reportPath = join43(projectDir, "build-report.json");
496258
+ const reportPath = join41(projectDir, "build-report.json");
495033
496259
  const report = JSON.parse(await readFile42(reportPath, "utf-8"));
495034
496260
  buildStatus = report.status ?? report.phase;
495035
496261
  buildUpdatedAt = report.updatedAt;
@@ -495038,12 +496264,12 @@ async function describeProject(name, projectDir) {
495038
496264
  }
495039
496265
  let latestRender;
495040
496266
  try {
495041
- const rendersDir = join43(projectDir, "renders");
496267
+ const rendersDir = join41(projectDir, "renders");
495042
496268
  const files = await readdir8(rendersDir);
495043
496269
  let newest;
495044
496270
  for (const file of files) {
495045
496271
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
495046
- const { mtimeMs } = await stat7(join43(rendersDir, file));
496272
+ const { mtimeMs } = await stat7(join41(rendersDir, file));
495047
496273
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
495048
496274
  }
495049
496275
  if (newest) {
@@ -496054,7 +497280,7 @@ var exportTools = [exportVideoTool];
496054
497280
 
496055
497281
  // ../cli/src/tools/manifest/agent-only.ts
496056
497282
  import { readFile as readFile44, writeFile as writeFile52, readdir as readdir9, stat as stat8, access as access7, unlink as unlink7 } from "node:fs/promises";
496057
- import { resolve as resolve76, join as join44, basename as basename21, extname as extname14 } from "node:path";
497283
+ import { resolve as resolve76, join as join43, basename as basename21, extname as extname14 } from "node:path";
496058
497284
  import { z as z13 } from "zod";
496059
497285
  init_engine();
496060
497286
  init_exec_safe();
@@ -496133,7 +497359,7 @@ var fsListTool = defineTool({
496133
497359
  const results = [];
496134
497360
  for (const entry of entries2) {
496135
497361
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
496136
- const fullPath = join44(absPath, entry.name);
497362
+ const fullPath = join43(absPath, entry.name);
496137
497363
  const stats = await stat8(fullPath);
496138
497364
  if (entry.isDirectory()) {
496139
497365
  results.push(`[DIR] ${entry.name}/`);
@@ -496246,7 +497472,7 @@ var batchImportTool = defineTool({
496246
497472
  const scanDir = async (dir) => {
496247
497473
  const entries2 = await readdir9(dir, { withFileTypes: true });
496248
497474
  for (const entry of entries2) {
496249
- const entryPath = join44(dir, entry.name);
497475
+ const entryPath = join43(dir, entry.name);
496250
497476
  if (entry.isDirectory() && recursive) {
496251
497477
  await scanDir(entryPath);
496252
497478
  } else if (entry.isFile()) {
@@ -498289,7 +499515,7 @@ function buildServerInstructions(cwd = resolveServerWorkspaceRoot()) {
498289
499515
  "When creating projects with init, use a project name or workspace-relative path under the workspace root.",
498290
499516
  "Do not create projects in /tmp, /home/claude, /workspace, or other synthetic roots unless the user explicitly asks.",
498291
499517
  "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.",
499518
+ "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
499519
  "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
499520
  ].join("\n");
498295
499521
  }
@@ -498316,7 +499542,7 @@ var server2 = new Server(
498316
499542
  {
498317
499543
  name: "vibeframe",
498318
499544
  // Stamped with the package version by build.js at bundle time.
498319
- version: "0.112.1"
499545
+ version: "0.113.2"
498320
499546
  },
498321
499547
  {
498322
499548
  capabilities: {