@vibeframe/cli 0.115.0 → 0.115.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.
package/dist/index.js CHANGED
@@ -7768,7 +7768,7 @@ function errorMessage(error) {
7768
7768
  return error instanceof Error ? error.message : "Unknown error";
7769
7769
  }
7770
7770
  function sleep(ms) {
7771
- return new Promise((resolve86) => setTimeout(resolve86, ms));
7771
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
7772
7772
  }
7773
7773
  var ProviderHttpError;
7774
7774
  var init_http = __esm({
@@ -11655,14 +11655,14 @@ async function loadKokoroRuntime() {
11655
11655
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
11656
11656
  try {
11657
11657
  const { readFile: readFile55 } = await import("node:fs/promises");
11658
- const { join: join33, resolve: resolve86 } = await import("node:path");
11658
+ const { join: join34, resolve: resolve87 } = await import("node:path");
11659
11659
  const { pathToFileURL } = await import("node:url");
11660
- const pkgDir = resolve86(baseDir, "node_modules", "kokoro-js");
11661
- const pkg = JSON.parse(await readFile55(join33(pkgDir, "package.json"), "utf-8"));
11660
+ const pkgDir = resolve87(baseDir, "node_modules", "kokoro-js");
11661
+ const pkg = JSON.parse(await readFile55(join34(pkgDir, "package.json"), "utf-8"));
11662
11662
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
11663
11663
  if (!entry)
11664
11664
  return null;
11665
- const mod = await import(pathToFileURL(join33(pkgDir, entry)).href);
11665
+ const mod = await import(pathToFileURL(join34(pkgDir, entry)).href);
11666
11666
  return mod.KokoroTTS ?? null;
11667
11667
  } catch {
11668
11668
  return null;
@@ -11673,20 +11673,20 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
11673
11673
  if (!base)
11674
11674
  return null;
11675
11675
  try {
11676
- const { readFile: readFile55, writeFile: writeFile56, mkdir: mkdir30 } = await import("node:fs/promises");
11677
- const { join: join33, resolve: resolve86 } = await import("node:path");
11676
+ const { readFile: readFile55, writeFile: writeFile57, mkdir: mkdir31 } = await import("node:fs/promises");
11677
+ const { join: join34, resolve: resolve87 } = await import("node:path");
11678
11678
  const { pathToFileURL } = await import("node:url");
11679
11679
  const { homedir: homedir8 } = await import("node:os");
11680
- const transformersDir = resolve86(base, "node_modules", "@huggingface", "transformers");
11681
- const ortDistDir = resolve86(base, "node_modules", "onnxruntime-web", "dist");
11682
- const transformers = await import(pathToFileURL(join33(transformersDir, "dist", "transformers.web.js")).href);
11683
- const cacheRoot = join33(homedir8(), ".cache", "vibeframe", "models");
11680
+ const transformersDir = resolve87(base, "node_modules", "@huggingface", "transformers");
11681
+ const ortDistDir = resolve87(base, "node_modules", "onnxruntime-web", "dist");
11682
+ const transformers = await import(pathToFileURL(join34(transformersDir, "dist", "transformers.web.js")).href);
11683
+ const cacheRoot = join34(homedir8(), ".cache", "vibeframe", "models");
11684
11684
  transformers.env.allowLocalModels = false;
11685
11685
  transformers.env.useCustomCache = true;
11686
11686
  transformers.env.customCache = {
11687
11687
  async match(key) {
11688
11688
  try {
11689
- return new Response(await readFile55(join33(cacheRoot, encodeURIComponent(String(key)))));
11689
+ return new Response(await readFile55(join34(cacheRoot, encodeURIComponent(String(key)))));
11690
11690
  } catch {
11691
11691
  return void 0;
11692
11692
  }
@@ -11694,8 +11694,8 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
11694
11694
  async put(key, response) {
11695
11695
  try {
11696
11696
  const buf = Buffer.from(await response.arrayBuffer());
11697
- await mkdir30(cacheRoot, { recursive: true });
11698
- await writeFile56(join33(cacheRoot, encodeURIComponent(String(key))), buf);
11697
+ await mkdir31(cacheRoot, { recursive: true });
11698
+ await writeFile57(join34(cacheRoot, encodeURIComponent(String(key))), buf);
11699
11699
  } catch {
11700
11700
  }
11701
11701
  }
@@ -12470,7 +12470,7 @@ var init_RunwayProvider = __esm({
12470
12470
  * Sleep helper
12471
12471
  */
12472
12472
  sleep(ms) {
12473
- return new Promise((resolve86) => setTimeout(resolve86, ms));
12473
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
12474
12474
  }
12475
12475
  };
12476
12476
  RunwayProvider.API_VERSION = "2024-11-06";
@@ -12911,7 +12911,7 @@ var init_KlingProvider = __esm({
12911
12911
  * Sleep helper
12912
12912
  */
12913
12913
  sleep(ms) {
12914
- return new Promise((resolve86) => setTimeout(resolve86, ms));
12914
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
12915
12915
  }
12916
12916
  };
12917
12917
  klingProvider = new KlingProvider();
@@ -13229,7 +13229,7 @@ var init_GrokProvider = __esm({
13229
13229
  }
13230
13230
  }
13231
13231
  sleep(ms) {
13232
- return new Promise((resolve86) => setTimeout(resolve86, ms));
13232
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
13233
13233
  }
13234
13234
  };
13235
13235
  grokProvider = new GrokProvider();
@@ -13264,11 +13264,11 @@ var require_middleware = __commonJS({
13264
13264
  "use strict";
13265
13265
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
13266
13266
  function adopt(value) {
13267
- return value instanceof P ? value : new P(function(resolve86) {
13268
- resolve86(value);
13267
+ return value instanceof P ? value : new P(function(resolve87) {
13268
+ resolve87(value);
13269
13269
  });
13270
13270
  }
13271
- return new (P || (P = Promise))(function(resolve86, reject) {
13271
+ return new (P || (P = Promise))(function(resolve87, reject) {
13272
13272
  function fulfilled(value) {
13273
13273
  try {
13274
13274
  step(generator.next(value));
@@ -13284,7 +13284,7 @@ var require_middleware = __commonJS({
13284
13284
  }
13285
13285
  }
13286
13286
  function step(result) {
13287
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
13287
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
13288
13288
  }
13289
13289
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13290
13290
  });
@@ -13369,11 +13369,11 @@ var require_response = __commonJS({
13369
13369
  "use strict";
13370
13370
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
13371
13371
  function adopt(value) {
13372
- return value instanceof P ? value : new P(function(resolve86) {
13373
- resolve86(value);
13372
+ return value instanceof P ? value : new P(function(resolve87) {
13373
+ resolve87(value);
13374
13374
  });
13375
13375
  }
13376
- return new (P || (P = Promise))(function(resolve86, reject) {
13376
+ return new (P || (P = Promise))(function(resolve87, reject) {
13377
13377
  function fulfilled(value) {
13378
13378
  try {
13379
13379
  step(generator.next(value));
@@ -13389,7 +13389,7 @@ var require_response = __commonJS({
13389
13389
  }
13390
13390
  }
13391
13391
  function step(result) {
13392
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
13392
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
13393
13393
  }
13394
13394
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13395
13395
  });
@@ -13496,11 +13496,11 @@ var require_utils = __commonJS({
13496
13496
  "use strict";
13497
13497
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
13498
13498
  function adopt(value) {
13499
- return value instanceof P ? value : new P(function(resolve86) {
13500
- resolve86(value);
13499
+ return value instanceof P ? value : new P(function(resolve87) {
13500
+ resolve87(value);
13501
13501
  });
13502
13502
  }
13503
- return new (P || (P = Promise))(function(resolve86, reject) {
13503
+ return new (P || (P = Promise))(function(resolve87, reject) {
13504
13504
  function fulfilled(value) {
13505
13505
  try {
13506
13506
  step(generator.next(value));
@@ -13516,7 +13516,7 @@ var require_utils = __commonJS({
13516
13516
  }
13517
13517
  }
13518
13518
  function step(result) {
13519
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
13519
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
13520
13520
  }
13521
13521
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13522
13522
  });
@@ -13609,7 +13609,7 @@ var require_utils = __commonJS({
13609
13609
  }
13610
13610
  function sleep3(ms) {
13611
13611
  return __awaiter(this, void 0, void 0, function* () {
13612
- return new Promise((resolve86) => setTimeout(resolve86, ms));
13612
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
13613
13613
  });
13614
13614
  }
13615
13615
  }
@@ -13621,11 +13621,11 @@ var require_retry = __commonJS({
13621
13621
  "use strict";
13622
13622
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
13623
13623
  function adopt(value) {
13624
- return value instanceof P ? value : new P(function(resolve86) {
13625
- resolve86(value);
13624
+ return value instanceof P ? value : new P(function(resolve87) {
13625
+ resolve87(value);
13626
13626
  });
13627
13627
  }
13628
- return new (P || (P = Promise))(function(resolve86, reject) {
13628
+ return new (P || (P = Promise))(function(resolve87, reject) {
13629
13629
  function fulfilled(value) {
13630
13630
  try {
13631
13631
  step(generator.next(value));
@@ -13641,7 +13641,7 @@ var require_retry = __commonJS({
13641
13641
  }
13642
13642
  }
13643
13643
  function step(result) {
13644
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
13644
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
13645
13645
  }
13646
13646
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13647
13647
  });
@@ -13848,11 +13848,11 @@ var require_request = __commonJS({
13848
13848
  "use strict";
13849
13849
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
13850
13850
  function adopt(value) {
13851
- return value instanceof P ? value : new P(function(resolve86) {
13852
- resolve86(value);
13851
+ return value instanceof P ? value : new P(function(resolve87) {
13852
+ resolve87(value);
13853
13853
  });
13854
13854
  }
13855
- return new (P || (P = Promise))(function(resolve86, reject) {
13855
+ return new (P || (P = Promise))(function(resolve87, reject) {
13856
13856
  function fulfilled(value) {
13857
13857
  try {
13858
13858
  step(generator.next(value));
@@ -13868,7 +13868,7 @@ var require_request = __commonJS({
13868
13868
  }
13869
13869
  }
13870
13870
  function step(result) {
13871
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
13871
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
13872
13872
  }
13873
13873
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13874
13874
  });
@@ -13955,11 +13955,11 @@ var require_storage = __commonJS({
13955
13955
  "use strict";
13956
13956
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
13957
13957
  function adopt(value) {
13958
- return value instanceof P ? value : new P(function(resolve86) {
13959
- resolve86(value);
13958
+ return value instanceof P ? value : new P(function(resolve87) {
13959
+ resolve87(value);
13960
13960
  });
13961
13961
  }
13962
- return new (P || (P = Promise))(function(resolve86, reject) {
13962
+ return new (P || (P = Promise))(function(resolve87, reject) {
13963
13963
  function fulfilled(value) {
13964
13964
  try {
13965
13965
  step(generator.next(value));
@@ -13975,7 +13975,7 @@ var require_storage = __commonJS({
13975
13975
  }
13976
13976
  }
13977
13977
  function step(result) {
13978
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
13978
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
13979
13979
  }
13980
13980
  step((generator = generator.apply(thisArg, _arguments || [])).next());
13981
13981
  });
@@ -14303,11 +14303,11 @@ var require_auth = __commonJS({
14303
14303
  "use strict";
14304
14304
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
14305
14305
  function adopt(value) {
14306
- return value instanceof P ? value : new P(function(resolve86) {
14307
- resolve86(value);
14306
+ return value instanceof P ? value : new P(function(resolve87) {
14307
+ resolve87(value);
14308
14308
  });
14309
14309
  }
14310
- return new (P || (P = Promise))(function(resolve86, reject) {
14310
+ return new (P || (P = Promise))(function(resolve87, reject) {
14311
14311
  function fulfilled(value) {
14312
14312
  try {
14313
14313
  step(generator.next(value));
@@ -14323,7 +14323,7 @@ var require_auth = __commonJS({
14323
14323
  }
14324
14324
  }
14325
14325
  function step(result) {
14326
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
14326
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
14327
14327
  }
14328
14328
  step((generator = generator.apply(thisArg, _arguments || [])).next());
14329
14329
  });
@@ -14362,11 +14362,11 @@ var require_streaming = __commonJS({
14362
14362
  "use strict";
14363
14363
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
14364
14364
  function adopt(value) {
14365
- return value instanceof P ? value : new P(function(resolve86) {
14366
- resolve86(value);
14365
+ return value instanceof P ? value : new P(function(resolve87) {
14366
+ resolve87(value);
14367
14367
  });
14368
14368
  }
14369
- return new (P || (P = Promise))(function(resolve86, reject) {
14369
+ return new (P || (P = Promise))(function(resolve87, reject) {
14370
14370
  function fulfilled(value) {
14371
14371
  try {
14372
14372
  step(generator.next(value));
@@ -14382,7 +14382,7 @@ var require_streaming = __commonJS({
14382
14382
  }
14383
14383
  }
14384
14384
  function step(result) {
14385
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
14385
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
14386
14386
  }
14387
14387
  step((generator = generator.apply(thisArg, _arguments || [])).next());
14388
14388
  });
@@ -14615,7 +14615,7 @@ var require_streaming = __commonJS({
14615
14615
  query: options.queryParams
14616
14616
  });
14617
14617
  this.options = options;
14618
- this.donePromise = new Promise((resolve86, reject) => {
14618
+ this.donePromise = new Promise((resolve87, reject) => {
14619
14619
  if (this.streamClosed) {
14620
14620
  reject(new response_1.ApiError({
14621
14621
  message: "Streaming connection is already closed.",
@@ -14626,11 +14626,11 @@ var require_streaming = __commonJS({
14626
14626
  }
14627
14627
  this.signal.addEventListener("abort", () => {
14628
14628
  var _a2;
14629
- resolve86((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
14629
+ resolve87((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
14630
14630
  });
14631
14631
  this.on("done", (data) => {
14632
14632
  this.streamClosed = true;
14633
- resolve86(data);
14633
+ resolve87(data);
14634
14634
  });
14635
14635
  this.on("error", (error) => {
14636
14636
  this.streamClosed = true;
@@ -14655,7 +14655,7 @@ var require_streaming = __commonJS({
14655
14655
  if (data) {
14656
14656
  yield yield __await(data);
14657
14657
  }
14658
- yield __await(new Promise((resolve86) => setTimeout(resolve86, 16)));
14658
+ yield __await(new Promise((resolve87) => setTimeout(resolve87, 16)));
14659
14659
  }
14660
14660
  });
14661
14661
  }
@@ -14700,11 +14700,11 @@ var require_queue = __commonJS({
14700
14700
  "use strict";
14701
14701
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
14702
14702
  function adopt(value) {
14703
- return value instanceof P ? value : new P(function(resolve86) {
14704
- resolve86(value);
14703
+ return value instanceof P ? value : new P(function(resolve87) {
14704
+ resolve87(value);
14705
14705
  });
14706
14706
  }
14707
- return new (P || (P = Promise))(function(resolve86, reject) {
14707
+ return new (P || (P = Promise))(function(resolve87, reject) {
14708
14708
  function fulfilled(value) {
14709
14709
  try {
14710
14710
  step(generator.next(value));
@@ -14720,7 +14720,7 @@ var require_queue = __commonJS({
14720
14720
  }
14721
14721
  }
14722
14722
  function step(result) {
14723
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
14723
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
14724
14724
  }
14725
14725
  step((generator = generator.apply(thisArg, _arguments || [])).next());
14726
14726
  });
@@ -14855,7 +14855,7 @@ var require_queue = __commonJS({
14855
14855
  }
14856
14856
  return doneStatus;
14857
14857
  }
14858
- return new Promise((resolve86, reject) => {
14858
+ return new Promise((resolve87, reject) => {
14859
14859
  var _a;
14860
14860
  let pollingTimeoutId;
14861
14861
  const pollInterval = "pollInterval" in options && typeof options.pollInterval === "number" ? (_a = options.pollInterval) !== null && _a !== void 0 ? _a : DEFAULT_POLL_INTERVAL : DEFAULT_POLL_INTERVAL;
@@ -14887,7 +14887,7 @@ var require_queue = __commonJS({
14887
14887
  }
14888
14888
  if (requestStatus.status === "COMPLETED") {
14889
14889
  clearScheduledTasks();
14890
- resolve86(requestStatus);
14890
+ resolve87(requestStatus);
14891
14891
  return;
14892
14892
  }
14893
14893
  pollingTimeoutId = setTimeout(poll, pollInterval);
@@ -16848,11 +16848,11 @@ var require_realtime = __commonJS({
16848
16848
  "use strict";
16849
16849
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
16850
16850
  function adopt(value) {
16851
- return value instanceof P ? value : new P(function(resolve86) {
16852
- resolve86(value);
16851
+ return value instanceof P ? value : new P(function(resolve87) {
16852
+ resolve87(value);
16853
16853
  });
16854
16854
  }
16855
- return new (P || (P = Promise))(function(resolve86, reject) {
16855
+ return new (P || (P = Promise))(function(resolve87, reject) {
16856
16856
  function fulfilled(value) {
16857
16857
  try {
16858
16858
  step(generator.next(value));
@@ -16868,7 +16868,7 @@ var require_realtime = __commonJS({
16868
16868
  }
16869
16869
  }
16870
16870
  function step(result) {
16871
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
16871
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
16872
16872
  }
16873
16873
  step((generator = generator.apply(thisArg, _arguments || [])).next());
16874
16874
  });
@@ -17193,11 +17193,11 @@ var require_client = __commonJS({
17193
17193
  "use strict";
17194
17194
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
17195
17195
  function adopt(value) {
17196
- return value instanceof P ? value : new P(function(resolve86) {
17197
- resolve86(value);
17196
+ return value instanceof P ? value : new P(function(resolve87) {
17197
+ resolve87(value);
17198
17198
  });
17199
17199
  }
17200
- return new (P || (P = Promise))(function(resolve86, reject) {
17200
+ return new (P || (P = Promise))(function(resolve87, reject) {
17201
17201
  function fulfilled(value) {
17202
17202
  try {
17203
17203
  step(generator.next(value));
@@ -17213,7 +17213,7 @@ var require_client = __commonJS({
17213
17213
  }
17214
17214
  }
17215
17215
  function step(result) {
17216
- result.done ? resolve86(result.value) : adopt(result.value).then(fulfilled, rejected);
17216
+ result.done ? resolve87(result.value) : adopt(result.value).then(fulfilled, rejected);
17217
17217
  }
17218
17218
  step((generator = generator.apply(thisArg, _arguments || [])).next());
17219
17219
  });
@@ -17915,7 +17915,7 @@ var init_ReplicateProvider = __esm({
17915
17915
  * Sleep helper
17916
17916
  */
17917
17917
  sleep(ms) {
17918
- return new Promise((resolve86) => setTimeout(resolve86, ms));
17918
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
17919
17919
  }
17920
17920
  /**
17921
17921
  * Generate music from text prompt using MusicGen
@@ -22744,10 +22744,10 @@ var require_resolve_block_map = __commonJS({
22744
22744
  let offset = bm.offset;
22745
22745
  let commentEnd = null;
22746
22746
  for (const collItem of bm.items) {
22747
- const { start, key, sep: sep2, value } = collItem;
22747
+ const { start, key, sep: sep3, value } = collItem;
22748
22748
  const keyProps = resolveProps.resolveProps(start, {
22749
22749
  indicator: "explicit-key-ind",
22750
- next: key ?? sep2?.[0],
22750
+ next: key ?? sep3?.[0],
22751
22751
  offset,
22752
22752
  onError,
22753
22753
  parentIndent: bm.indent,
@@ -22761,7 +22761,7 @@ var require_resolve_block_map = __commonJS({
22761
22761
  else if ("indent" in key && key.indent !== bm.indent)
22762
22762
  onError(offset, "BAD_INDENT", startColMsg);
22763
22763
  }
22764
- if (!keyProps.anchor && !keyProps.tag && !sep2) {
22764
+ if (!keyProps.anchor && !keyProps.tag && !sep3) {
22765
22765
  commentEnd = keyProps.end;
22766
22766
  if (keyProps.comment) {
22767
22767
  if (map.comment)
@@ -22785,7 +22785,7 @@ var require_resolve_block_map = __commonJS({
22785
22785
  ctx.atKey = false;
22786
22786
  if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
22787
22787
  onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
22788
- const valueProps = resolveProps.resolveProps(sep2 ?? [], {
22788
+ const valueProps = resolveProps.resolveProps(sep3 ?? [], {
22789
22789
  indicator: "map-value-ind",
22790
22790
  next: value,
22791
22791
  offset: keyNode.range[2],
@@ -22801,7 +22801,7 @@ var require_resolve_block_map = __commonJS({
22801
22801
  if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
22802
22802
  onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
22803
22803
  }
22804
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep2, null, valueProps, onError);
22804
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep3, null, valueProps, onError);
22805
22805
  if (ctx.schema.compat)
22806
22806
  utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
22807
22807
  offset = valueNode.range[2];
@@ -22892,7 +22892,7 @@ var require_resolve_end = __commonJS({
22892
22892
  let comment = "";
22893
22893
  if (end) {
22894
22894
  let hasSpace = false;
22895
- let sep2 = "";
22895
+ let sep3 = "";
22896
22896
  for (const token of end) {
22897
22897
  const { source, type } = token;
22898
22898
  switch (type) {
@@ -22906,13 +22906,13 @@ var require_resolve_end = __commonJS({
22906
22906
  if (!comment)
22907
22907
  comment = cb;
22908
22908
  else
22909
- comment += sep2 + cb;
22910
- sep2 = "";
22909
+ comment += sep3 + cb;
22910
+ sep3 = "";
22911
22911
  break;
22912
22912
  }
22913
22913
  case "newline":
22914
22914
  if (comment)
22915
- sep2 += source;
22915
+ sep3 += source;
22916
22916
  hasSpace = true;
22917
22917
  break;
22918
22918
  default:
@@ -22955,18 +22955,18 @@ var require_resolve_flow_collection = __commonJS({
22955
22955
  let offset = fc.offset + fc.start.source.length;
22956
22956
  for (let i = 0; i < fc.items.length; ++i) {
22957
22957
  const collItem = fc.items[i];
22958
- const { start, key, sep: sep2, value } = collItem;
22958
+ const { start, key, sep: sep3, value } = collItem;
22959
22959
  const props = resolveProps.resolveProps(start, {
22960
22960
  flow: fcName,
22961
22961
  indicator: "explicit-key-ind",
22962
- next: key ?? sep2?.[0],
22962
+ next: key ?? sep3?.[0],
22963
22963
  offset,
22964
22964
  onError,
22965
22965
  parentIndent: fc.indent,
22966
22966
  startOnNewline: false
22967
22967
  });
22968
22968
  if (!props.found) {
22969
- if (!props.anchor && !props.tag && !sep2 && !value) {
22969
+ if (!props.anchor && !props.tag && !sep3 && !value) {
22970
22970
  if (i === 0 && props.comma)
22971
22971
  onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
22972
22972
  else if (i < fc.items.length - 1)
@@ -23020,8 +23020,8 @@ var require_resolve_flow_collection = __commonJS({
23020
23020
  }
23021
23021
  }
23022
23022
  }
23023
- if (!isMap && !sep2 && !props.found) {
23024
- const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep2, null, props, onError);
23023
+ if (!isMap && !sep3 && !props.found) {
23024
+ const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep3, null, props, onError);
23025
23025
  coll.items.push(valueNode);
23026
23026
  offset = valueNode.range[2];
23027
23027
  if (isBlock(value))
@@ -23033,7 +23033,7 @@ var require_resolve_flow_collection = __commonJS({
23033
23033
  if (isBlock(key))
23034
23034
  onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
23035
23035
  ctx.atKey = false;
23036
- const valueProps = resolveProps.resolveProps(sep2 ?? [], {
23036
+ const valueProps = resolveProps.resolveProps(sep3 ?? [], {
23037
23037
  flow: fcName,
23038
23038
  indicator: "map-value-ind",
23039
23039
  next: value,
@@ -23044,8 +23044,8 @@ var require_resolve_flow_collection = __commonJS({
23044
23044
  });
23045
23045
  if (valueProps.found) {
23046
23046
  if (!isMap && !props.found && ctx.options.strict) {
23047
- if (sep2)
23048
- for (const st of sep2) {
23047
+ if (sep3)
23048
+ for (const st of sep3) {
23049
23049
  if (st === valueProps.found)
23050
23050
  break;
23051
23051
  if (st.type === "newline") {
@@ -23062,7 +23062,7 @@ var require_resolve_flow_collection = __commonJS({
23062
23062
  else
23063
23063
  onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
23064
23064
  }
23065
- const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep2, null, valueProps, onError) : null;
23065
+ const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep3, null, valueProps, onError) : null;
23066
23066
  if (valueNode) {
23067
23067
  if (isBlock(value))
23068
23068
  onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
@@ -23242,7 +23242,7 @@ var require_resolve_block_scalar = __commonJS({
23242
23242
  chompStart = i + 1;
23243
23243
  }
23244
23244
  let value = "";
23245
- let sep2 = "";
23245
+ let sep3 = "";
23246
23246
  let prevMoreIndented = false;
23247
23247
  for (let i = 0; i < contentStart; ++i)
23248
23248
  value += lines[i][0].slice(trimIndent) + "\n";
@@ -23259,24 +23259,24 @@ var require_resolve_block_scalar = __commonJS({
23259
23259
  indent = "";
23260
23260
  }
23261
23261
  if (type === Scalar.Scalar.BLOCK_LITERAL) {
23262
- value += sep2 + indent.slice(trimIndent) + content;
23263
- sep2 = "\n";
23262
+ value += sep3 + indent.slice(trimIndent) + content;
23263
+ sep3 = "\n";
23264
23264
  } else if (indent.length > trimIndent || content[0] === " ") {
23265
- if (sep2 === " ")
23266
- sep2 = "\n";
23267
- else if (!prevMoreIndented && sep2 === "\n")
23268
- sep2 = "\n\n";
23269
- value += sep2 + indent.slice(trimIndent) + content;
23270
- sep2 = "\n";
23265
+ if (sep3 === " ")
23266
+ sep3 = "\n";
23267
+ else if (!prevMoreIndented && sep3 === "\n")
23268
+ sep3 = "\n\n";
23269
+ value += sep3 + indent.slice(trimIndent) + content;
23270
+ sep3 = "\n";
23271
23271
  prevMoreIndented = true;
23272
23272
  } else if (content === "") {
23273
- if (sep2 === "\n")
23273
+ if (sep3 === "\n")
23274
23274
  value += "\n";
23275
23275
  else
23276
- sep2 = "\n";
23276
+ sep3 = "\n";
23277
23277
  } else {
23278
- value += sep2 + content;
23279
- sep2 = " ";
23278
+ value += sep3 + content;
23279
+ sep3 = " ";
23280
23280
  prevMoreIndented = false;
23281
23281
  }
23282
23282
  }
@@ -23458,25 +23458,25 @@ var require_resolve_flow_scalar = __commonJS({
23458
23458
  if (!match)
23459
23459
  return source;
23460
23460
  let res = match[1];
23461
- let sep2 = " ";
23461
+ let sep3 = " ";
23462
23462
  let pos = first.lastIndex;
23463
23463
  line.lastIndex = pos;
23464
23464
  while (match = line.exec(source)) {
23465
23465
  if (match[1] === "") {
23466
- if (sep2 === "\n")
23467
- res += sep2;
23466
+ if (sep3 === "\n")
23467
+ res += sep3;
23468
23468
  else
23469
- sep2 = "\n";
23469
+ sep3 = "\n";
23470
23470
  } else {
23471
- res += sep2 + match[1];
23472
- sep2 = " ";
23471
+ res += sep3 + match[1];
23472
+ sep3 = " ";
23473
23473
  }
23474
23474
  pos = line.lastIndex;
23475
23475
  }
23476
23476
  const last = /[ \t]*(.*)/sy;
23477
23477
  last.lastIndex = pos;
23478
23478
  match = last.exec(source);
23479
- return res + sep2 + (match?.[1] ?? "");
23479
+ return res + sep3 + (match?.[1] ?? "");
23480
23480
  }
23481
23481
  function doubleQuotedValue(source, onError) {
23482
23482
  let res = "";
@@ -24278,14 +24278,14 @@ var require_cst_stringify = __commonJS({
24278
24278
  }
24279
24279
  }
24280
24280
  }
24281
- function stringifyItem({ start, key, sep: sep2, value }) {
24281
+ function stringifyItem({ start, key, sep: sep3, value }) {
24282
24282
  let res = "";
24283
24283
  for (const st of start)
24284
24284
  res += st.source;
24285
24285
  if (key)
24286
24286
  res += stringifyToken(key);
24287
- if (sep2)
24288
- for (const st of sep2)
24287
+ if (sep3)
24288
+ for (const st of sep3)
24289
24289
  res += st.source;
24290
24290
  if (value)
24291
24291
  res += stringifyToken(value);
@@ -25435,18 +25435,18 @@ var require_parser = __commonJS({
25435
25435
  if (this.type === "map-value-ind") {
25436
25436
  const prev = getPrevProps(this.peek(2));
25437
25437
  const start = getFirstKeyStartProps(prev);
25438
- let sep2;
25438
+ let sep3;
25439
25439
  if (scalar.end) {
25440
- sep2 = scalar.end;
25441
- sep2.push(this.sourceToken);
25440
+ sep3 = scalar.end;
25441
+ sep3.push(this.sourceToken);
25442
25442
  delete scalar.end;
25443
25443
  } else
25444
- sep2 = [this.sourceToken];
25444
+ sep3 = [this.sourceToken];
25445
25445
  const map = {
25446
25446
  type: "block-map",
25447
25447
  offset: scalar.offset,
25448
25448
  indent: scalar.indent,
25449
- items: [{ start, key: scalar, sep: sep2 }]
25449
+ items: [{ start, key: scalar, sep: sep3 }]
25450
25450
  };
25451
25451
  this.onKeyLine = true;
25452
25452
  this.stack[this.stack.length - 1] = map;
@@ -25599,15 +25599,15 @@ var require_parser = __commonJS({
25599
25599
  } else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
25600
25600
  const start2 = getFirstKeyStartProps(it.start);
25601
25601
  const key = it.key;
25602
- const sep2 = it.sep;
25603
- sep2.push(this.sourceToken);
25602
+ const sep3 = it.sep;
25603
+ sep3.push(this.sourceToken);
25604
25604
  delete it.key;
25605
25605
  delete it.sep;
25606
25606
  this.stack.push({
25607
25607
  type: "block-map",
25608
25608
  offset: this.offset,
25609
25609
  indent: this.indent,
25610
- items: [{ start: start2, key, sep: sep2 }]
25610
+ items: [{ start: start2, key, sep: sep3 }]
25611
25611
  });
25612
25612
  } else if (start.length > 0) {
25613
25613
  it.sep = it.sep.concat(start, this.sourceToken);
@@ -25801,13 +25801,13 @@ var require_parser = __commonJS({
25801
25801
  const prev = getPrevProps(parent);
25802
25802
  const start = getFirstKeyStartProps(prev);
25803
25803
  fixFlowSeqItems(fc);
25804
- const sep2 = fc.end.splice(1, fc.end.length);
25805
- sep2.push(this.sourceToken);
25804
+ const sep3 = fc.end.splice(1, fc.end.length);
25805
+ sep3.push(this.sourceToken);
25806
25806
  const map = {
25807
25807
  type: "block-map",
25808
25808
  offset: fc.offset,
25809
25809
  indent: fc.indent,
25810
- items: [{ start, key: fc, sep: sep2 }]
25810
+ items: [{ start, key: fc, sep: sep3 }]
25811
25811
  };
25812
25812
  this.onKeyLine = true;
25813
25813
  this.stack[this.stack.length - 1] = map;
@@ -26399,7 +26399,7 @@ async function prompt(question, hidden = false) {
26399
26399
  input: process.stdin,
26400
26400
  output: process.stdout
26401
26401
  });
26402
- return new Promise((resolve86) => {
26402
+ return new Promise((resolve87) => {
26403
26403
  if (hidden && process.stdin.isTTY) {
26404
26404
  process.stdout.write(question);
26405
26405
  let input = "";
@@ -26413,7 +26413,7 @@ async function prompt(question, hidden = false) {
26413
26413
  process.stdin.removeListener("data", onData);
26414
26414
  process.stdout.write("\n");
26415
26415
  rl.close();
26416
- resolve86(input);
26416
+ resolve87(input);
26417
26417
  } else if (char === "") {
26418
26418
  process.exit(1);
26419
26419
  } else if (char === "\x7F" || char === "\b") {
@@ -26428,7 +26428,7 @@ async function prompt(question, hidden = false) {
26428
26428
  } else {
26429
26429
  rl.question(question, (answer) => {
26430
26430
  rl.close();
26431
- resolve86(answer);
26431
+ resolve87(answer);
26432
26432
  });
26433
26433
  }
26434
26434
  });
@@ -26802,6 +26802,7 @@ var init_storyboard_parse = __esm({
26802
26802
  "backdrop",
26803
26803
  "video",
26804
26804
  "keyframe",
26805
+ "provider",
26805
26806
  "motion",
26806
26807
  "voice",
26807
26808
  "music",
@@ -27097,7 +27098,7 @@ var init_storyboard_edit = __esm({
27097
27098
  HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
27098
27099
  LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
27099
27100
  ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
27100
- STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset", "eyebrow", "title", "caption", "kicker", "sub"]);
27101
+ STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "keyframe", "provider", "motion", "voice", "music", "asset", "eyebrow", "title", "caption", "kicker", "sub"]);
27101
27102
  MAX_RECOMMENDED_BEAT_SEC = 15;
27102
27103
  }
27103
27104
  });
@@ -33448,16 +33449,16 @@ async function prompt2(question) {
33448
33449
  input,
33449
33450
  output: process.stdout
33450
33451
  });
33451
- return new Promise((resolve86) => {
33452
+ return new Promise((resolve87) => {
33452
33453
  rl.question(question, (answer) => {
33453
33454
  rl.close();
33454
- resolve86(answer);
33455
+ resolve87(answer);
33455
33456
  });
33456
33457
  });
33457
33458
  }
33458
33459
  async function promptHidden(question, opts = {}) {
33459
33460
  const input = getTTYInputStream();
33460
- return new Promise((resolve86) => {
33461
+ return new Promise((resolve87) => {
33461
33462
  let value = "";
33462
33463
  if (typeof input.setRawMode === "function") {
33463
33464
  input.setRawMode(true);
@@ -33471,7 +33472,7 @@ async function promptHidden(question, opts = {}) {
33471
33472
  input.setRawMode(false);
33472
33473
  input.removeListener("data", onData);
33473
33474
  process.stdout.write("\n");
33474
- resolve86(value);
33475
+ resolve87(value);
33475
33476
  } else if (char === "") {
33476
33477
  input.setRawMode(false);
33477
33478
  process.stdout.write("\n");
@@ -33497,7 +33498,7 @@ async function promptHidden(question, opts = {}) {
33497
33498
  const rl = createInterface2({ input, output: process.stdout });
33498
33499
  rl.question(question, (answer) => {
33499
33500
  rl.close();
33500
- resolve86(answer);
33501
+ resolve87(answer);
33501
33502
  });
33502
33503
  }
33503
33504
  });
@@ -33505,7 +33506,7 @@ async function promptHidden(question, opts = {}) {
33505
33506
  async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
33506
33507
  const input = getTTYInputStream();
33507
33508
  if (typeof input.setRawMode === "function") {
33508
- return new Promise((resolve86) => {
33509
+ return new Promise((resolve87) => {
33509
33510
  let selected = defaultIndex;
33510
33511
  const render = () => {
33511
33512
  if (renderCount > 0) {
@@ -33533,7 +33534,7 @@ async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
33533
33534
  input.setRawMode(false);
33534
33535
  input.removeListener("data", onData);
33535
33536
  process.stdout.write("\n");
33536
- resolve86(selected);
33537
+ resolve87(selected);
33537
33538
  } else if (char === "") {
33538
33539
  input.setRawMode(false);
33539
33540
  process.stdout.write("\n");
@@ -33543,7 +33544,7 @@ async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
33543
33544
  input.setRawMode(false);
33544
33545
  input.removeListener("data", onData);
33545
33546
  process.stdout.write("\n");
33546
- resolve86(opts.backIndex);
33547
+ resolve87(opts.backIndex);
33547
33548
  } else if (char === "\x1B[A" || char === "k") {
33548
33549
  selected = (selected - 1 + options.length) % options.length;
33549
33550
  render();
@@ -33557,7 +33558,7 @@ async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
33557
33558
  input.removeListener("data", onData);
33558
33559
  render();
33559
33560
  process.stdout.write("\n");
33560
- resolve86(selected);
33561
+ resolve87(selected);
33561
33562
  }
33562
33563
  };
33563
33564
  const onData = (chunk) => {
@@ -33581,7 +33582,7 @@ async function promptSelect(_question, options, defaultIndex = 0, opts = {}) {
33581
33582
  async function promptConfirm(question, defaultYes = true) {
33582
33583
  const input = getTTYInputStream();
33583
33584
  if (typeof input.setRawMode === "function") {
33584
- return new Promise((resolve86) => {
33585
+ return new Promise((resolve87) => {
33585
33586
  const options = ["Yes", "No"];
33586
33587
  let selected = defaultYes ? 0 : 1;
33587
33588
  let renderCount = 0;
@@ -33611,7 +33612,7 @@ async function promptConfirm(question, defaultYes = true) {
33611
33612
  finished = true;
33612
33613
  input.setRawMode(false);
33613
33614
  input.removeListener("data", onData);
33614
- resolve86(selected === 0);
33615
+ resolve87(selected === 0);
33615
33616
  } else if (char === "") {
33616
33617
  input.setRawMode(false);
33617
33618
  process.stdout.write("\n");
@@ -33625,14 +33626,14 @@ async function promptConfirm(question, defaultYes = true) {
33625
33626
  input.setRawMode(false);
33626
33627
  input.removeListener("data", onData);
33627
33628
  render();
33628
- resolve86(true);
33629
+ resolve87(true);
33629
33630
  } else if (char === "n" || char === "N") {
33630
33631
  selected = 1;
33631
33632
  finished = true;
33632
33633
  input.setRawMode(false);
33633
33634
  input.removeListener("data", onData);
33634
33635
  render();
33635
- resolve86(false);
33636
+ resolve87(false);
33636
33637
  }
33637
33638
  };
33638
33639
  const onData = (chunk) => {
@@ -33651,7 +33652,7 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
33651
33652
  const input = getTTYInputStream();
33652
33653
  const selected = options.map((_, i) => Boolean(defaultSelected[i]));
33653
33654
  if (typeof input.setRawMode === "function") {
33654
- return new Promise((resolve86) => {
33655
+ return new Promise((resolve87) => {
33655
33656
  let cursor = 0;
33656
33657
  let renderCount = 0;
33657
33658
  let userToggled = false;
@@ -33681,7 +33682,7 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
33681
33682
  for (let i = 0; i < selected.length; i++) {
33682
33683
  if (selected[i]) result.push(i);
33683
33684
  }
33684
- resolve86(result);
33685
+ resolve87(result);
33685
33686
  };
33686
33687
  let finished = false;
33687
33688
  const handleChar = (char) => {
@@ -35648,7 +35649,7 @@ function unique(items) {
35648
35649
  ];
35649
35650
  }
35650
35651
  function sleep2(ms) {
35651
- return new Promise((resolve86) => setTimeout(resolve86, ms));
35652
+ return new Promise((resolve87) => setTimeout(resolve87, ms));
35652
35653
  }
35653
35654
  var LOCAL_JOB_ORPHAN_AFTER_MS;
35654
35655
  var init_status_jobs = __esm({
@@ -38639,9 +38640,12 @@ Examples:
38639
38640
  outputPath = resolve28(process.cwd(), options.output);
38640
38641
  await writeFile25(outputPath, buffer);
38641
38642
  }
38643
+ const cost = realRunCost(provider, options);
38642
38644
  outputSuccess({
38643
38645
  command: "generate video",
38644
38646
  startedAt,
38647
+ costUsd: cost.costUsd,
38648
+ warnings: cost.warnings,
38645
38649
  data: {
38646
38650
  provider,
38647
38651
  taskId: result?.id,
@@ -38678,6 +38682,26 @@ Examples:
38678
38682
  }
38679
38683
  });
38680
38684
  }
38685
+ function realRunCost(provider, options) {
38686
+ if (provider === "seedance" || provider === "fal") {
38687
+ return {
38688
+ costUsd: estimateSeedanceVideoCostUsd({
38689
+ durationSec: Number(options.duration) || 5,
38690
+ resolution: options.resolution,
38691
+ aspectRatio: options.ratio,
38692
+ fast: options.seedanceModel === "fast",
38693
+ hasVideoReference: Boolean(options.refVideos)
38694
+ }),
38695
+ warnings: []
38696
+ };
38697
+ }
38698
+ return {
38699
+ costUsd: lookupCostEstimateUpperBound("generate video") ?? 5,
38700
+ warnings: [
38701
+ `costUsd is the tier upper bound for ${provider} - actual provider billing is typically lower and is not metered here.`
38702
+ ]
38703
+ };
38704
+ }
38681
38705
  async function prepareSeedanceReferences2(opts) {
38682
38706
  const references = [];
38683
38707
  for (const sourcePath of opts.refImages ?? []) {
@@ -58155,7 +58179,7 @@ var init_design_parse = __esm({
58155
58179
 
58156
58180
  // src/commands/_shared/build-asset-reference.ts
58157
58181
  import { existsSync as existsSync45 } from "node:fs";
58158
- import { extname as extname12, isAbsolute as isAbsolute5, relative as relative9, resolve as resolve54, sep } from "node:path";
58182
+ import { extname as extname12, isAbsolute as isAbsolute5, relative as relative9, resolve as resolve54, sep as sep2 } from "node:path";
58159
58183
  function resolveTypedAssetReference(projectDir, kind, value) {
58160
58184
  const raw = stringOrUndefined2(value);
58161
58185
  if (!raw || !looksLikeAssetPath(raw)) return null;
@@ -58202,7 +58226,7 @@ function resolveReference(projectDir, kind, raw, source, forcedError) {
58202
58226
  return {
58203
58227
  kind,
58204
58228
  raw,
58205
- relPath: withinProject ? rel.split(sep).join("/") : void 0,
58229
+ relPath: withinProject ? rel.split(sep2).join("/") : void 0,
58206
58230
  absPath: withinProject ? absPath : void 0,
58207
58231
  ext,
58208
58232
  exists,
@@ -58268,6 +58292,7 @@ function backdropCacheDescriptor(opts) {
58268
58292
  beatId: opts.beatId,
58269
58293
  cue: opts.cue,
58270
58294
  provider: opts.provider,
58295
+ model: opts.model,
58271
58296
  quality: opts.quality,
58272
58297
  size: opts.size,
58273
58298
  ratio: opts.ratio,
@@ -58279,6 +58304,7 @@ function characterCacheDescriptor(opts) {
58279
58304
  name: opts.name,
58280
58305
  cue: opts.cue,
58281
58306
  provider: opts.provider,
58307
+ model: opts.model,
58282
58308
  quality: opts.quality,
58283
58309
  size: opts.size,
58284
58310
  ext: "png"
@@ -58288,6 +58314,7 @@ function keyframeCacheDescriptor(opts) {
58288
58314
  return cacheAssetDescriptor("keyframe", {
58289
58315
  beatId: opts.beatId,
58290
58316
  cue: opts.cue,
58317
+ model: opts.model,
58291
58318
  provider: opts.provider,
58292
58319
  quality: opts.quality,
58293
58320
  size: opts.size,
@@ -58388,7 +58415,8 @@ async function createBuildPlan(opts) {
58388
58415
  const stage = opts.stage ?? "all";
58389
58416
  const config2 = await readProjectConfig(projectDir);
58390
58417
  const kindPolicy = kindAssetPolicy(config2.config.kind);
58391
- const skipBackdrop = opts.skipBackdrop ?? kindPolicy.skipBackdrop ?? false;
58418
+ const composerIsFootage = stringOrUndefined3(opts.composer)?.toLowerCase() === "footage";
58419
+ const skipBackdrop = opts.skipBackdrop ?? (composerIsFootage ? true : kindPolicy.skipBackdrop ?? false);
58392
58420
  const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
58393
58421
  const skipVideo = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
58394
58422
  const storyboardPath2 = join24(projectDir, "STORYBOARD.md");
@@ -58507,7 +58535,8 @@ async function createBuildPlan(opts) {
58507
58535
  provider: resolved.image.resolved,
58508
58536
  quality: imageQuality,
58509
58537
  size: imageSize2,
58510
- ratio: imageRatio
58538
+ ratio: imageRatio,
58539
+ model: opts.imageModel
58511
58540
  }) : null;
58512
58541
  const videoCache = (videoPrompt || keyframePrompt) && !videoReference ? videoCacheDescriptor({
58513
58542
  beatId: beat.id,
@@ -58522,7 +58551,8 @@ async function createBuildPlan(opts) {
58522
58551
  provider: resolved.image.resolved,
58523
58552
  quality: imageQuality,
58524
58553
  size: imageSize2,
58525
- ratio: imageRatio
58554
+ ratio: imageRatio,
58555
+ model: opts.imageModel
58526
58556
  }) : null;
58527
58557
  const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
58528
58558
  beatId: beat.id,
@@ -58997,6 +59027,17 @@ async function keyedProviderResolution(opts, projectDir) {
58997
59027
  }
58998
59028
  async function resolveComposerProvider(input, projectDir) {
58999
59029
  const requested = input.value.toLowerCase();
59030
+ if (requested === "template" || requested === "footage") {
59031
+ return {
59032
+ kind: "composer",
59033
+ requested: input.requested,
59034
+ resolved: requested,
59035
+ source: input.source,
59036
+ requiresKey: false,
59037
+ configured: true,
59038
+ retryWith: []
59039
+ };
59040
+ }
59000
59041
  const explicit = isComposerProvider(requested) ? requested : void 0;
59001
59042
  const resolved = explicit ?? await firstConfiguredComposer(projectDir) ?? "claude";
59002
59043
  const configKey = composerConfigKey(resolved);
@@ -59471,9 +59512,422 @@ var init_compose_aivideo = __esm({
59471
59512
  }
59472
59513
  });
59473
59514
 
59515
+ // src/commands/_shared/footage-assemble.ts
59516
+ import { existsSync as existsSync48 } from "node:fs";
59517
+ import { mkdir as mkdir24, writeFile as writeFile44 } from "node:fs/promises";
59518
+ import { dirname as dirname19, isAbsolute as isAbsolute6, join as join26, resolve as resolve58 } from "node:path";
59519
+ function planFootageTimeline(inputs, opts = {}) {
59520
+ if (inputs.length === 0) {
59521
+ throw new Error("No clips to assemble.");
59522
+ }
59523
+ const fadeSec = opts.fadeSec ?? FOOTAGE_DEFAULTS.fadeSec;
59524
+ const narrationLeadSec = opts.narrationLeadSec ?? FOOTAGE_DEFAULTS.narrationLeadSec;
59525
+ const narrationTailSec = opts.narrationTailSec ?? FOOTAGE_DEFAULTS.narrationTailSec;
59526
+ const warnings = [];
59527
+ let transitionSec = opts.transitionSec ?? FOOTAGE_DEFAULTS.transitionSec;
59528
+ if (inputs.length > 1) {
59529
+ const shortest = Math.min(...inputs.map((c) => c.clipDurationSec));
59530
+ const maxTransition = round3(Math.max(0, shortest / 2 - 0.05));
59531
+ if (transitionSec > maxTransition) {
59532
+ warnings.push(
59533
+ `Transition ${transitionSec}s exceeds half the shortest clip (${shortest}s); clamped to ${maxTransition}s.`
59534
+ );
59535
+ transitionSec = maxTransition;
59536
+ }
59537
+ }
59538
+ const beats = [];
59539
+ let cursor = 0;
59540
+ for (let i = 0; i < inputs.length; i++) {
59541
+ const input = inputs[i];
59542
+ const isFirst = i === 0;
59543
+ const isLast = i === inputs.length - 1;
59544
+ const startSec = round3(cursor);
59545
+ const inSegmentOffset = (isFirst ? 0 : transitionSec / 2) + narrationLeadSec;
59546
+ let segmentDurationSec = input.clipDurationSec;
59547
+ let narrationStartSec;
59548
+ if (input.narrationDurationSec !== void 0) {
59549
+ narrationStartSec = round3(startSec + inSegmentOffset);
59550
+ const tail = isLast ? Math.max(narrationTailSec, fadeSec) : narrationTailSec;
59551
+ const required = inSegmentOffset + input.narrationDurationSec + tail;
59552
+ segmentDurationSec = Math.max(segmentDurationSec, required);
59553
+ }
59554
+ segmentDurationSec = round3(segmentDurationSec);
59555
+ const extendedSec = round3(segmentDurationSec - input.clipDurationSec);
59556
+ if (extendedSec > 0) {
59557
+ warnings.push(
59558
+ `Beat "${input.id}": narration outruns the clip; last frame held for ${extendedSec}s.`
59559
+ );
59560
+ }
59561
+ beats.push({
59562
+ id: input.id,
59563
+ clipDurationSec: input.clipDurationSec,
59564
+ narrationDurationSec: input.narrationDurationSec,
59565
+ segmentDurationSec,
59566
+ extendedSec,
59567
+ startSec,
59568
+ narrationStartSec
59569
+ });
59570
+ cursor = startSec + segmentDurationSec - (isLast ? 0 : transitionSec);
59571
+ }
59572
+ return {
59573
+ beats,
59574
+ transitionSec,
59575
+ fadeSec,
59576
+ totalDurationSec: round3(cursor),
59577
+ warnings
59578
+ };
59579
+ }
59580
+ function buildFootageAssembleArgs(opts) {
59581
+ const { plan, sources, narrationPaths, musicPath, outputPath } = opts;
59582
+ const width = opts.width ?? FOOTAGE_DEFAULTS.width;
59583
+ const height = opts.height ?? FOOTAGE_DEFAULTS.height;
59584
+ const fps = opts.fps ?? FOOTAGE_DEFAULTS.fps;
59585
+ const crf = opts.crf ?? (opts.finish ? FOOTAGE_DEFAULTS.finishCrf : FOOTAGE_DEFAULTS.crf);
59586
+ const musicVolume = opts.musicVolume ?? FOOTAGE_DEFAULTS.musicVolume;
59587
+ const total = plan.totalDurationSec;
59588
+ const fade = plan.fadeSec;
59589
+ const args = ["-y"];
59590
+ for (let i = 0; i < sources.length; i++) {
59591
+ const source = sources[i];
59592
+ if (source.kind === "image") {
59593
+ args.push(
59594
+ "-loop",
59595
+ "1",
59596
+ "-framerate",
59597
+ String(fps),
59598
+ "-t",
59599
+ String(plan.beats[i].segmentDurationSec),
59600
+ "-i",
59601
+ source.path
59602
+ );
59603
+ } else {
59604
+ args.push("-i", source.path);
59605
+ }
59606
+ }
59607
+ const narrationEntries = [...narrationPaths.entries()].sort(([a], [b]) => a - b);
59608
+ const narrationInputIndex = /* @__PURE__ */ new Map();
59609
+ for (const [beatIndex, path2] of narrationEntries) {
59610
+ narrationInputIndex.set(beatIndex, sources.length + narrationInputIndex.size);
59611
+ args.push("-i", path2);
59612
+ }
59613
+ let musicInputIndex;
59614
+ if (musicPath) {
59615
+ musicInputIndex = sources.length + narrationInputIndex.size;
59616
+ args.push("-stream_loop", "-1", "-i", musicPath);
59617
+ }
59618
+ const filters = [];
59619
+ for (let i = 0; i < sources.length; i++) {
59620
+ const seg = plan.beats[i].segmentDurationSec;
59621
+ if (sources[i].kind === "image") {
59622
+ const rate = round6((FOOTAGE_DEFAULTS.kenburnsMaxZoom - 1) / Math.max(1, seg * fps));
59623
+ filters.push(
59624
+ `[${i}:v]scale=${width * 2}:${height * 2}:force_original_aspect_ratio=increase,crop=${width * 2}:${height * 2},zoompan=z='min(pzoom+${rate},${FOOTAGE_DEFAULTS.kenburnsMaxZoom})':x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':d=1:s=${width}x${height}:fps=${fps},setsar=1,settb=AVTB,format=yuv420p[v${i}]`
59625
+ );
59626
+ continue;
59627
+ }
59628
+ const extend = plan.beats[i].extendedSec;
59629
+ const tpad = extend > 0 ? `,tpad=stop_mode=clone:stop_duration=${extend}` : "";
59630
+ filters.push(
59631
+ `[${i}:v]scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height},setsar=1,fps=${fps},settb=AVTB,format=yuv420p${tpad}[v${i}]`
59632
+ );
59633
+ }
59634
+ let videoLabel = "[v0]";
59635
+ for (let i = 1; i < sources.length; i++) {
59636
+ const out = `[vx${i}]`;
59637
+ filters.push(
59638
+ `${videoLabel}[v${i}]xfade=transition=fade:duration=${plan.transitionSec}:offset=${plan.beats[i].startSec}${out}`
59639
+ );
59640
+ videoLabel = out;
59641
+ }
59642
+ const finishChain = opts.finish ? `${FOOTAGE_DEFAULTS.finish.join(",")},` : "";
59643
+ filters.push(
59644
+ `${videoLabel}${finishChain}fade=t=in:st=0:d=${fade},fade=t=out:st=${round3(Math.max(0, total - fade))}:d=${fade}[vout]`
59645
+ );
59646
+ let audioLabel;
59647
+ if (narrationInputIndex.size > 0) {
59648
+ const delayed = [];
59649
+ for (const [beatIndex, inputIndex] of narrationInputIndex) {
59650
+ const ms = Math.round((plan.beats[beatIndex].narrationStartSec ?? 0) * 1e3);
59651
+ const label = `[nd${beatIndex}]`;
59652
+ filters.push(`[${inputIndex}:a]adelay=${ms}:all=1${label}`);
59653
+ delayed.push(label);
59654
+ }
59655
+ if (delayed.length === 1) {
59656
+ filters.push(`${delayed[0]}anull[nar]`);
59657
+ } else {
59658
+ filters.push(`${delayed.join("")}amix=inputs=${delayed.length}:normalize=0[nar]`);
59659
+ }
59660
+ audioLabel = "[nar]";
59661
+ }
59662
+ if (musicInputIndex !== void 0) {
59663
+ filters.push(
59664
+ `[${musicInputIndex}:a]atrim=0:${total},asetpts=PTS-STARTPTS,volume=${musicVolume}[mb]`
59665
+ );
59666
+ if (audioLabel) {
59667
+ const { thresholdDb, ratio, attackMs, releaseMs } = FOOTAGE_DEFAULTS.duck;
59668
+ const thresholdLinear = round3(Math.pow(10, thresholdDb / 20));
59669
+ filters.push(
59670
+ `${audioLabel}asplit=2[narA][narSC]`,
59671
+ `[mb][narSC]sidechaincompress=threshold=${thresholdLinear}:ratio=${ratio}:attack=${attackMs}:release=${releaseMs}[mduck]`,
59672
+ `[mduck][narA]amix=inputs=2:normalize=0[mix]`
59673
+ );
59674
+ audioLabel = "[mix]";
59675
+ } else {
59676
+ audioLabel = "[mb]";
59677
+ }
59678
+ }
59679
+ if (audioLabel) {
59680
+ filters.push(
59681
+ `${audioLabel}apad,atrim=0:${total},afade=t=out:st=${round3(Math.max(0, total - fade))}:d=${fade}[aout]`
59682
+ );
59683
+ }
59684
+ args.push("-filter_complex", filters.join(";"), "-map", "[vout]");
59685
+ if (audioLabel) {
59686
+ args.push("-map", "[aout]", "-c:a", "aac", "-b:a", "192k");
59687
+ } else {
59688
+ args.push("-an");
59689
+ }
59690
+ args.push(
59691
+ "-c:v",
59692
+ "libx264",
59693
+ "-crf",
59694
+ String(crf),
59695
+ "-pix_fmt",
59696
+ "yuv420p",
59697
+ "-movflags",
59698
+ "+faststart",
59699
+ "-t",
59700
+ String(total),
59701
+ outputPath
59702
+ );
59703
+ return args;
59704
+ }
59705
+ async function executeFootageAssemble(opts) {
59706
+ const projectDir = resolve58(opts.projectDir);
59707
+ const outputRel = opts.output ?? DEFAULT_FOOTAGE_OUTPUT;
59708
+ const outputPath = isAbsolute6(outputRel) ? outputRel : resolve58(projectDir, outputRel);
59709
+ if (!ffmpegToolsAvailable()) {
59710
+ return {
59711
+ success: false,
59712
+ dryRun: Boolean(opts.dryRun),
59713
+ outputPath,
59714
+ error: "FFmpeg toolchain (ffmpeg + ffprobe) not found in PATH.",
59715
+ suggestion: "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)."
59716
+ };
59717
+ }
59718
+ let beats;
59719
+ try {
59720
+ const md = await loadStoryboardMarkdown(projectDir);
59721
+ beats = parseStoryboard(md).beats;
59722
+ } catch (err) {
59723
+ return {
59724
+ success: false,
59725
+ dryRun: Boolean(opts.dryRun),
59726
+ outputPath,
59727
+ error: err instanceof Error ? err.message : String(err),
59728
+ suggestion: "Run inside a vibe scene project (needs STORYBOARD.md / scenes/)."
59729
+ };
59730
+ }
59731
+ if (beats.length === 0) {
59732
+ return {
59733
+ success: false,
59734
+ dryRun: Boolean(opts.dryRun),
59735
+ outputPath,
59736
+ error: "STORYBOARD.md has no beats to assemble."
59737
+ };
59738
+ }
59739
+ const missing = [];
59740
+ const sourceRels = [];
59741
+ const kenburnsBeatIds = [];
59742
+ for (const beat of beats) {
59743
+ const clipRel = `assets/video-${beat.id}.mp4`;
59744
+ if (existsSync48(join26(projectDir, clipRel))) {
59745
+ sourceRels.push({ rel: clipRel, kind: "video" });
59746
+ continue;
59747
+ }
59748
+ const keyframeRel = `assets/keyframe-${beat.id}.png`;
59749
+ if (existsSync48(join26(projectDir, keyframeRel))) {
59750
+ sourceRels.push({ rel: keyframeRel, kind: "image" });
59751
+ kenburnsBeatIds.push(beat.id);
59752
+ continue;
59753
+ }
59754
+ missing.push(`${beat.id} (${clipRel})`);
59755
+ sourceRels.push({ rel: clipRel, kind: "video" });
59756
+ }
59757
+ if (missing.length > 0) {
59758
+ return {
59759
+ success: false,
59760
+ dryRun: Boolean(opts.dryRun),
59761
+ outputPath,
59762
+ error: `No clip or keyframe for beat(s): ${missing.join(", ")}.`,
59763
+ suggestion: `Generate them first: vibe build ${projectDir} --stage assets --json`
59764
+ };
59765
+ }
59766
+ const narrationRels = /* @__PURE__ */ new Map();
59767
+ for (let i = 0; i < beats.length; i++) {
59768
+ const rel = ["mp3", "wav"].map((ext) => `assets/narration-${beats[i].id}.${ext}`).find((r) => existsSync48(join26(projectDir, r)));
59769
+ if (rel) narrationRels.set(i, rel);
59770
+ }
59771
+ const music = resolveMusicBed(projectDir, beats[0]?.id, opts.music);
59772
+ if (!music.ok) {
59773
+ return {
59774
+ success: false,
59775
+ dryRun: Boolean(opts.dryRun),
59776
+ outputPath,
59777
+ error: music.error
59778
+ };
59779
+ }
59780
+ const inputs = [];
59781
+ for (let i = 0; i < beats.length; i++) {
59782
+ const clipDurationSec = sourceRels[i].kind === "image" ? beats[i].duration ?? FOOTAGE_DEFAULTS.kenburnsSec : round3(await ffprobeDuration(join26(projectDir, sourceRels[i].rel)));
59783
+ const narrationRel = narrationRels.get(i);
59784
+ const narrationDurationSec = narrationRel ? round3(await ffprobeDuration(join26(projectDir, narrationRel))) : void 0;
59785
+ inputs.push({ id: beats[i].id, clipDurationSec, narrationDurationSec });
59786
+ }
59787
+ const plan = planFootageTimeline(inputs, {
59788
+ transitionSec: opts.transitionSec,
59789
+ fadeSec: opts.fadeSec
59790
+ });
59791
+ const musicVolume = opts.musicVolume ?? FOOTAGE_DEFAULTS.musicVolume;
59792
+ const report = {
59793
+ schemaVersion: "1",
59794
+ kind: "footage-assemble",
59795
+ project: projectDir,
59796
+ output: outputPath,
59797
+ totalDurationSec: plan.totalDurationSec,
59798
+ transitionSec: plan.transitionSec,
59799
+ fadeSec: plan.fadeSec,
59800
+ beats: plan.beats.map((b, i) => ({
59801
+ ...b,
59802
+ clip: sourceRels[i].rel,
59803
+ source: sourceRels[i].kind === "image" ? "keyframe-kenburns" : "clip",
59804
+ narration: narrationRels.get(i)
59805
+ })),
59806
+ music: music.rel ? {
59807
+ path: music.rel,
59808
+ source: music.source,
59809
+ volume: musicVolume,
59810
+ ducked: narrationRels.size > 0
59811
+ } : null,
59812
+ finish: opts.finish ? { filters: [...FOOTAGE_DEFAULTS.finish] } : null,
59813
+ warnings: [
59814
+ ...plan.warnings,
59815
+ ...kenburnsBeatIds.map(
59816
+ (id) => `Beat "${id}": no generated clip; keyframe ken-burns fallback used.`
59817
+ )
59818
+ ],
59819
+ nextActions: [
59820
+ {
59821
+ id: "inspect-footage-cut",
59822
+ kind: "command",
59823
+ label: "Inspect the assembled cut",
59824
+ command: `vibe inspect render ${outputPath} --cheap --json`,
59825
+ fixOwner: "vibe",
59826
+ costTier: "free",
59827
+ safeToAutoRun: true,
59828
+ requiresConfirmation: false,
59829
+ reason: "Verify duration, black frames, and audio coverage without watching the video."
59830
+ },
59831
+ ...kenburnsBeatIds.map((id) => ({
59832
+ id: `regenerate-clip-${id}`,
59833
+ kind: "command",
59834
+ label: `Regenerate the missing clip for beat "${id}"`,
59835
+ command: `vibe build ${projectDir} --beat ${id} --stage assets --force --json`,
59836
+ fixOwner: "vibe",
59837
+ costTier: "very-high",
59838
+ safeToAutoRun: false,
59839
+ requiresConfirmation: true,
59840
+ reason: `Beat "${id}" shipped as a ken-burns still; regenerating the clip restores real motion.`
59841
+ }))
59842
+ ]
59843
+ };
59844
+ if (opts.dryRun) {
59845
+ return { success: true, dryRun: true, outputPath, report };
59846
+ }
59847
+ await mkdir24(dirname19(outputPath), { recursive: true });
59848
+ const args = buildFootageAssembleArgs({
59849
+ sources: sourceRels.map((s) => ({ path: join26(projectDir, s.rel), kind: s.kind })),
59850
+ plan,
59851
+ narrationPaths: new Map(
59852
+ [...narrationRels.entries()].map(([i, rel]) => [i, join26(projectDir, rel)])
59853
+ ),
59854
+ musicPath: music.rel ? isAbsolute6(music.rel) ? music.rel : join26(projectDir, music.rel) : void 0,
59855
+ outputPath,
59856
+ musicVolume,
59857
+ finish: opts.finish
59858
+ });
59859
+ try {
59860
+ await execSafe("ffmpeg", args, { timeout: 6e5 });
59861
+ } catch (err) {
59862
+ return {
59863
+ success: false,
59864
+ dryRun: false,
59865
+ outputPath,
59866
+ report,
59867
+ error: `FFmpeg assembly failed: ${err instanceof Error ? err.message : String(err)}`,
59868
+ suggestion: "Re-run with --dry-run --json to inspect the planned timeline."
59869
+ };
59870
+ }
59871
+ const reportPath = join26(projectDir, "assemble-report.json");
59872
+ await writeFile44(reportPath, `${JSON.stringify(report, null, 2)}
59873
+ `, "utf-8");
59874
+ return { success: true, dryRun: false, outputPath, reportPath, report };
59875
+ }
59876
+ function resolveMusicBed(projectDir, firstBeatId, flag) {
59877
+ if (flag === "none") return { ok: true, source: "flag" };
59878
+ if (flag) {
59879
+ const abs = isAbsolute6(flag) ? flag : join26(projectDir, flag);
59880
+ if (!existsSync48(abs)) return { ok: false, error: `Music file not found: ${abs}` };
59881
+ return { ok: true, rel: flag, source: "flag" };
59882
+ }
59883
+ const candidates = ["assets/music.mp3", "assets/music.wav"];
59884
+ if (firstBeatId) {
59885
+ candidates.push(`assets/music-${firstBeatId}.mp3`, `assets/music-${firstBeatId}.wav`);
59886
+ }
59887
+ const rel = candidates.find((r) => existsSync48(join26(projectDir, r)));
59888
+ return { ok: true, rel, source: "auto" };
59889
+ }
59890
+ var FOOTAGE_DEFAULTS, round3, round6, DEFAULT_FOOTAGE_OUTPUT;
59891
+ var init_footage_assemble = __esm({
59892
+ "src/commands/_shared/footage-assemble.ts"() {
59893
+ "use strict";
59894
+ init_exec_safe();
59895
+ init_ffmpeg_gate();
59896
+ init_storyboard_parse();
59897
+ init_storyboard_source();
59898
+ FOOTAGE_DEFAULTS = {
59899
+ transitionSec: 0.5,
59900
+ fadeSec: 0.6,
59901
+ narrationLeadSec: 0.35,
59902
+ narrationTailSec: 0.4,
59903
+ width: 1920,
59904
+ height: 1080,
59905
+ fps: 30,
59906
+ crf: 18,
59907
+ finishCrf: 23,
59908
+ musicVolume: 0.35,
59909
+ duck: { thresholdDb: -30, ratio: 3, attackMs: 20, releaseMs: 200 },
59910
+ /**
59911
+ * The cinematic finish pass: subtle vignette + temporal film grain, applied
59912
+ * before the head/tail fades. These are the field-tested values that pull
59913
+ * provider clips away from the too-clean "AI look"; the fades multiply over
59914
+ * them so blacks stay black.
59915
+ */
59916
+ finish: ["vignette=angle=PI/5", "noise=alls=7:allf=t"],
59917
+ /** Base segment length for a ken-burns keyframe fallback (no clip to probe). */
59918
+ kenburnsSec: 4,
59919
+ /** Slow-push ceiling for the ken-burns zoom. */
59920
+ kenburnsMaxZoom: 1.1
59921
+ };
59922
+ round3 = (n) => Math.round(n * 1e3) / 1e3;
59923
+ round6 = (n) => Math.round(n * 1e6) / 1e6;
59924
+ DEFAULT_FOOTAGE_OUTPUT = "renders/footage.mp4";
59925
+ }
59926
+ });
59927
+
59474
59928
  // src/commands/_shared/scene-audio-scan.ts
59475
59929
  import { readFile as readFile41 } from "node:fs/promises";
59476
- import { resolve as resolve58 } from "node:path";
59930
+ import { resolve as resolve59 } from "node:path";
59477
59931
  function parseRootClips(rootHtml) {
59478
59932
  const clips = [];
59479
59933
  const clipRegex = /<div\b[^>]*class="clip"[^>]*>/gi;
@@ -59511,7 +59965,7 @@ function parseSceneAudios(compositionHtml) {
59511
59965
  }
59512
59966
  function makeFsCompositionReader(projectDir) {
59513
59967
  return async (compositionSrcRel) => {
59514
- const abs = resolve58(projectDir, compositionSrcRel);
59968
+ const abs = resolve59(projectDir, compositionSrcRel);
59515
59969
  try {
59516
59970
  return await readFile41(abs, "utf-8");
59517
59971
  } catch {
@@ -59530,7 +59984,7 @@ async function scanSceneAudio(opts) {
59530
59984
  for (const audio of audios) {
59531
59985
  out.push({
59532
59986
  srcRel: audio.srcRel,
59533
- srcAbs: resolve58(opts.projectDir, audio.srcRel),
59987
+ srcAbs: resolve59(opts.projectDir, audio.srcRel),
59534
59988
  absoluteStart: clip.start + audio.localStart,
59535
59989
  durationHint: audio.durationHint,
59536
59990
  clipDurationCap: clip.duration - audio.localStart,
@@ -59545,7 +59999,7 @@ async function scanSceneAudio(opts) {
59545
59999
  for (const audio of rootAudios) {
59546
60000
  out.push({
59547
60001
  srcRel: audio.srcRel,
59548
- srcAbs: resolve58(opts.projectDir, audio.srcRel),
60002
+ srcAbs: resolve59(opts.projectDir, audio.srcRel),
59549
60003
  absoluteStart: audio.localStart,
59550
60004
  durationHint: audio.durationHint,
59551
60005
  clipDurationCap: Math.max(0, maxClipEnd - audio.localStart),
@@ -59580,7 +60034,7 @@ var init_scene_audio_scan = __esm({
59580
60034
 
59581
60035
  // src/commands/_shared/scene-audio-mux.ts
59582
60036
  import { rename as rename4, unlink } from "node:fs/promises";
59583
- import { resolve as resolve59, dirname as dirname19, extname as extname13, basename as basename20 } from "node:path";
60037
+ import { resolve as resolve60, dirname as dirname20, extname as extname13, basename as basename20 } from "node:path";
59584
60038
  function buildAudioMuxFilter(audios) {
59585
60039
  if (audios.length === 0) return null;
59586
60040
  const labels = [];
@@ -59640,8 +60094,8 @@ async function muxAudioIntoVideo(opts) {
59640
60094
  return { success: true, outputPath: opts.videoPath, audioCount: 0 };
59641
60095
  }
59642
60096
  const ext = extname13(opts.videoPath) || `.${opts.format}`;
59643
- const tmpPath = resolve59(
59644
- dirname19(opts.videoPath),
60097
+ const tmpPath = resolve60(
60098
+ dirname20(opts.videoPath),
59645
60099
  `.${basename20(opts.videoPath, ext)}.muxing${ext}`
59646
60100
  );
59647
60101
  const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
@@ -59703,15 +60157,15 @@ var init_scene_audio_mux = __esm({
59703
60157
  });
59704
60158
 
59705
60159
  // src/commands/_shared/scene-assemble.ts
59706
- import { existsSync as existsSync48 } from "node:fs";
60160
+ import { existsSync as existsSync49 } from "node:fs";
59707
60161
  import { readFile as readFile42 } from "node:fs/promises";
59708
- import { resolve as resolve60, isAbsolute as isAbsolute6 } from "node:path";
60162
+ import { resolve as resolve61, isAbsolute as isAbsolute7 } from "node:path";
59709
60163
  async function executeSceneAssemble(opts) {
59710
- const projectDir = resolve60(opts.projectDir);
60164
+ const projectDir = resolve61(opts.projectDir);
59711
60165
  const root = opts.root ?? "index.html";
59712
60166
  const format = opts.format ?? "mp4";
59713
- const videoPath = isAbsolute6(opts.videoPath) ? opts.videoPath : resolve60(projectDir, opts.videoPath);
59714
- if (!existsSync48(videoPath)) {
60167
+ const videoPath = isAbsolute7(opts.videoPath) ? opts.videoPath : resolve61(projectDir, opts.videoPath);
60168
+ if (!existsSync49(videoPath)) {
59715
60169
  return {
59716
60170
  success: false,
59717
60171
  audioCount: 0,
@@ -59720,8 +60174,8 @@ async function executeSceneAssemble(opts) {
59720
60174
  error: `Video not found: ${videoPath}. Render it first (e.g. \`vibe render --silent\`).`
59721
60175
  };
59722
60176
  }
59723
- const rootPath = resolve60(projectDir, root);
59724
- if (!existsSync48(rootPath)) {
60177
+ const rootPath = resolve61(projectDir, root);
60178
+ if (!existsSync49(rootPath)) {
59725
60179
  return {
59726
60180
  success: false,
59727
60181
  audioCount: 0,
@@ -59788,9 +60242,9 @@ __export(scene_render_exports, {
59788
60242
  qualityToCrf: () => qualityToCrf2
59789
60243
  });
59790
60244
  import { execFile as execFile2 } from "node:child_process";
59791
- import { existsSync as existsSync49 } from "node:fs";
59792
- import { mkdir as mkdir24, readFile as readFile43, stat as stat8, writeFile as writeFile44 } from "node:fs/promises";
59793
- import { resolve as resolve61, relative as relative10, dirname as dirname20, basename as basename21, join as join26, isAbsolute as isAbsolute7 } from "node:path";
60245
+ import { existsSync as existsSync50 } from "node:fs";
60246
+ import { mkdir as mkdir25, readFile as readFile43, stat as stat8, writeFile as writeFile45 } from "node:fs/promises";
60247
+ import { resolve as resolve62, relative as relative10, dirname as dirname21, basename as basename21, join as join27, isAbsolute as isAbsolute8 } from "node:path";
59794
60248
  import { promisify as promisify2 } from "node:util";
59795
60249
  import {
59796
60250
  createRenderJob,
@@ -59803,9 +60257,9 @@ function defaultOutputPath(opts) {
59803
60257
  const fmt = opts.format ?? "mp4";
59804
60258
  const now = opts.now ?? /* @__PURE__ */ new Date();
59805
60259
  const stamp = now.toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
59806
- const name = (opts.projectName ?? basename21(resolve61(opts.projectDir))) || "scene";
60260
+ const name = (opts.projectName ?? basename21(resolve62(opts.projectDir))) || "scene";
59807
60261
  const beat = opts.beatId ? `-${sanitizeFileSegment2(opts.beatId)}` : "";
59808
- return resolve61(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
60262
+ return resolve62(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
59809
60263
  }
59810
60264
  function buildRenderConfig(opts) {
59811
60265
  const quality = opts.quality ?? "standard";
@@ -59843,7 +60297,7 @@ function buildMediaOpenCommand(action, filePath, platform3 = process.platform) {
59843
60297
  display: ["cmd", ...args].map(quoteCliArg).join(" ")
59844
60298
  };
59845
60299
  }
59846
- const target = action === "reveal" ? dirname20(filePath) : filePath;
60300
+ const target = action === "reveal" ? dirname21(filePath) : filePath;
59847
60301
  return {
59848
60302
  command: "xdg-open",
59849
60303
  args: [target],
@@ -59851,7 +60305,7 @@ function buildMediaOpenCommand(action, filePath, platform3 = process.platform) {
59851
60305
  };
59852
60306
  }
59853
60307
  async function executeSceneRender(opts = {}) {
59854
- const projectDir = resolve61(opts.projectDir ?? ".");
60308
+ const projectDir = resolve62(opts.projectDir ?? ".");
59855
60309
  const projectConfig = await readProjectConfig(projectDir);
59856
60310
  const engine = projectConfig.config.composition.engine;
59857
60311
  if (engine !== "hyperframes") {
@@ -59882,7 +60336,7 @@ async function executeSceneRender(opts = {}) {
59882
60336
  }
59883
60337
  let autoSyncApplied = false;
59884
60338
  let autoSyncWarning;
59885
- if (!opts.beatId && existsSync49(join26(projectDir, "STORYBOARD.md"))) {
60339
+ if (!opts.beatId && existsSync50(join27(projectDir, "STORYBOARD.md"))) {
59886
60340
  try {
59887
60341
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
59888
60342
  const subPlans = await createSubCompDurationSyncPlans({
@@ -59924,7 +60378,7 @@ async function executeSceneRender(opts = {}) {
59924
60378
  kind: "render",
59925
60379
  beat: opts.beatId ?? null,
59926
60380
  root,
59927
- error: `Root composition not found: ${resolve61(projectDir, root)}. Run \`vibe build ${projectDir}\` once to create the render scaffold, or scaffold it up front with \`vibe init <dir> --profile full\`.`
60381
+ error: `Root composition not found: ${resolve62(projectDir, root)}. Run \`vibe build ${projectDir}\` once to create the render scaffold, or scaffold it up front with \`vibe init <dir> --profile full\`.`
59928
60382
  };
59929
60383
  }
59930
60384
  const chrome = await preflightChrome();
@@ -59938,8 +60392,8 @@ async function executeSceneRender(opts = {}) {
59938
60392
  };
59939
60393
  }
59940
60394
  const projectName = projectConfig.config.name;
59941
- const outputPath = opts.output ? resolve61(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
59942
- await mkdir24(dirname20(outputPath), { recursive: true });
60395
+ const outputPath = opts.output ? resolve62(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
60396
+ await mkdir25(dirname21(outputPath), { recursive: true });
59943
60397
  const config2 = buildRenderConfig({
59944
60398
  fps: opts.fps,
59945
60399
  quality: opts.quality,
@@ -60051,11 +60505,11 @@ async function safeStat(p) {
60051
60505
  }
60052
60506
  async function prepareBeatRenderRoot(opts) {
60053
60507
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment2(opts.beatId)}.html`;
60054
- const reportPath = join26(opts.projectDir, "build-report.json");
60508
+ const reportPath = join27(opts.projectDir, "build-report.json");
60055
60509
  const retryWith = [
60056
60510
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
60057
60511
  ];
60058
- if (!existsSync49(reportPath)) {
60512
+ if (!existsSync50(reportPath)) {
60059
60513
  return {
60060
60514
  success: false,
60061
60515
  root,
@@ -60135,8 +60589,8 @@ async function prepareBeatRenderRoot(opts) {
60135
60589
  durationSec,
60136
60590
  narrationPath
60137
60591
  });
60138
- await mkdir24(dirname20(join26(opts.projectDir, root)), { recursive: true });
60139
- await writeFile44(join26(opts.projectDir, root), html, "utf-8");
60592
+ await mkdir25(dirname21(join27(opts.projectDir, root)), { recursive: true });
60593
+ await writeFile45(join27(opts.projectDir, root), html, "utf-8");
60140
60594
  return {
60141
60595
  success: true,
60142
60596
  root,
@@ -60200,9 +60654,9 @@ function reportedBeatAssetPaths(beat) {
60200
60654
  ]);
60201
60655
  }
60202
60656
  async function writeRenderReport(projectDir, result) {
60203
- const reportPath = join26(projectDir, "render-report.json");
60657
+ const reportPath = join27(projectDir, "render-report.json");
60204
60658
  try {
60205
- await writeFile44(
60659
+ await writeFile45(
60206
60660
  reportPath,
60207
60661
  JSON.stringify(
60208
60662
  {
@@ -60241,8 +60695,8 @@ async function writeRenderReport(projectDir, result) {
60241
60695
  }
60242
60696
  }
60243
60697
  function pathExists3(projectDir, value) {
60244
- const abs = isAbsolute7(value) ? value : resolve61(projectDir, value);
60245
- return existsSync49(abs);
60698
+ const abs = isAbsolute8(value) ? value : resolve62(projectDir, value);
60699
+ return existsSync50(abs);
60246
60700
  }
60247
60701
  function stringValue(value) {
60248
60702
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -60295,14 +60749,15 @@ var init_scene_build_mode = __esm({
60295
60749
  var scene_build_exports = {};
60296
60750
  __export(scene_build_exports, {
60297
60751
  executeSceneBuild: () => executeSceneBuild,
60752
+ isLikenessRejection: () => isLikenessRejection,
60298
60753
  resolveSceneBuildMode: () => resolveSceneBuildMode
60299
60754
  });
60300
- import { existsSync as existsSync50, readFileSync as readFileSync2 } from "node:fs";
60301
- import { copyFile as copyFile5, mkdir as mkdir25, readFile as readFile44, rm as rm5, writeFile as writeFile45 } from "node:fs/promises";
60302
- import { dirname as dirname21, join as join27, resolve as resolve62 } from "node:path";
60755
+ import { existsSync as existsSync51, readFileSync as readFileSync2 } from "node:fs";
60756
+ import { copyFile as copyFile5, mkdir as mkdir26, readFile as readFile44, rm as rm5, writeFile as writeFile46 } from "node:fs/promises";
60757
+ import { dirname as dirname22, join as join28, resolve as resolve63 } from "node:path";
60303
60758
  function readProjectKindSync(projectDir) {
60304
60759
  try {
60305
- const raw = readFileSync2(join27(projectDir, "vibe.config.json"), "utf-8");
60760
+ const raw = readFileSync2(join28(projectDir, "vibe.config.json"), "utf-8");
60306
60761
  const k = JSON.parse(raw).kind;
60307
60762
  return typeof k === "string" && isSceneKind(k) ? k : "cinema";
60308
60763
  } catch {
@@ -60311,16 +60766,17 @@ function readProjectKindSync(projectDir) {
60311
60766
  }
60312
60767
  async function executeSceneBuild(opts) {
60313
60768
  const startedAt = Date.now();
60314
- const projectDir = resolve62(opts.projectDir);
60769
+ const projectDir = resolve63(opts.projectDir);
60315
60770
  const onProgress = opts.onProgress ?? (() => {
60316
60771
  });
60317
60772
  const projectKind = readProjectKindSync(projectDir);
60318
60773
  const kindPolicy = kindAssetPolicy(projectKind);
60319
60774
  const composer = opts.composer ?? composerDefaultForKind(projectKind);
60320
- const skipBackdrop = opts.skipBackdrop ?? kindPolicy.skipBackdrop ?? false;
60775
+ const skipBackdrop = opts.skipBackdrop ?? (composer === "footage" ? true : kindPolicy.skipBackdrop ?? false);
60321
60776
  const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
60322
60777
  const skipVideoAssets = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
60323
- const mode = composer === "template" ? "batch" : resolveSceneBuildMode(opts);
60778
+ const skipTranscript = opts.skipTranscript ?? composer === "footage";
60779
+ const mode = composer === "template" || composer === "footage" ? "batch" : resolveSceneBuildMode(opts);
60324
60780
  const selectedStage = opts.stage ?? (opts.skipRender ? "sync" : "all");
60325
60781
  const skipVideoStopsRender = (opts.skipVideo ?? false) && (selectedStage === "all" || selectedStage === "render");
60326
60782
  const effectiveSkipRender = (opts.skipRender ?? false) || skipVideoStopsRender;
@@ -60333,7 +60789,7 @@ async function executeSceneBuild(opts) {
60333
60789
  );
60334
60790
  }
60335
60791
  let sceneRepair = skippedSceneRepairSummary();
60336
- const storyboardPath2 = join27(projectDir, "STORYBOARD.md");
60792
+ const storyboardPath2 = join28(projectDir, "STORYBOARD.md");
60337
60793
  const buildPlan = await createBuildPlan({
60338
60794
  projectDir,
60339
60795
  stage: selectedStage,
@@ -60349,17 +60805,38 @@ async function executeSceneBuild(opts) {
60349
60805
  imageProvider: opts.imageProvider,
60350
60806
  imageQuality: opts.imageQuality,
60351
60807
  imageSize: opts.imageSize,
60808
+ imageModel: opts.imageModel,
60352
60809
  videoProvider: opts.videoProvider,
60353
60810
  musicProvider: opts.musicProvider,
60811
+ skipTranscript,
60354
60812
  composer,
60355
60813
  force: opts.force
60356
60814
  });
60357
60815
  warnings.push(...buildPlan.warnings);
60358
60816
  retryWith.push(...buildPlan.retryWith);
60359
- const finishBuildResult = (result) => finalizeBuildResult(projectDir, startedAt, {
60360
- providerResolution: buildPlan.providerResolution,
60361
- ...result
60362
- });
60817
+ const finishBuildResult = async (result) => {
60818
+ const withResolution = {
60819
+ providerResolution: buildPlan.providerResolution,
60820
+ ...result
60821
+ };
60822
+ let reportResult = withResolution;
60823
+ if (opts.beatId && result.beats?.length) {
60824
+ try {
60825
+ const parsedAll = parseStoryboard(await loadStoryboardMarkdown(projectDir));
60826
+ reportResult = {
60827
+ ...withResolution,
60828
+ beats: mergeBeatOutcomes(
60829
+ collectExistingBeatOutcomes(parsedAll.beats, projectDir),
60830
+ result.beats
60831
+ ),
60832
+ beatSummary: void 0
60833
+ // recomputed from the merged beats
60834
+ };
60835
+ } catch {
60836
+ }
60837
+ }
60838
+ return finalizeBuildResult(projectDir, startedAt, withResolution, reportResult);
60839
+ };
60363
60840
  if (shouldRunStage(selectedStage, "assets") || shouldRunStage(selectedStage, "sync")) {
60364
60841
  if (!ffmpegToolsAvailable()) {
60365
60842
  return finishBuildResult({
@@ -60384,7 +60861,7 @@ async function executeSceneBuild(opts) {
60384
60861
  }
60385
60862
  if (!buildPlan.validation.ok) {
60386
60863
  let invalidBeats = [];
60387
- if (existsSync50(storyboardPath2)) {
60864
+ if (existsSync51(storyboardPath2)) {
60388
60865
  invalidBeats = parseStoryboard(await loadStoryboardMarkdown(projectDir)).beats;
60389
60866
  }
60390
60867
  return finishBuildResult({
@@ -60409,7 +60886,7 @@ async function executeSceneBuild(opts) {
60409
60886
  totalLatencyMs: Date.now() - startedAt
60410
60887
  });
60411
60888
  }
60412
- if (!existsSync50(storyboardPath2)) {
60889
+ if (!existsSync51(storyboardPath2)) {
60413
60890
  return finishBuildResult({
60414
60891
  success: false,
60415
60892
  phase: "failed",
@@ -60504,6 +60981,7 @@ async function executeSceneBuild(opts) {
60504
60981
  imageProvider,
60505
60982
  imageSize: opts.imageSize ?? "1536x1024",
60506
60983
  imageQuality: opts.imageQuality ?? "hd",
60984
+ imageModel: opts.imageModel,
60507
60985
  force: opts.force ?? false,
60508
60986
  onProgress
60509
60987
  }
@@ -60525,6 +61003,7 @@ async function executeSceneBuild(opts) {
60525
61003
  musicProvider,
60526
61004
  imageQuality: opts.imageQuality ?? "hd",
60527
61005
  imageSize: opts.imageSize ?? "1536x1024",
61006
+ imageModel: opts.imageModel,
60528
61007
  skipNarration: opts.skipNarration ?? false,
60529
61008
  skipBackdrop,
60530
61009
  skipVideo: skipVideoAssets,
@@ -60614,7 +61093,7 @@ async function executeSceneBuild(opts) {
60614
61093
  totalLatencyMs: Date.now() - startedAt
60615
61094
  });
60616
61095
  }
60617
- if (shouldRunStage(selectedStage, "transcript") && !(opts.skipTranscript ?? false)) {
61096
+ if (shouldRunStage(selectedStage, "transcript") && !skipTranscript) {
60618
61097
  const { statuses } = await runTranscriptStage(activeBeats, beatOutcomes, {
60619
61098
  projectDir,
60620
61099
  force: opts.force ?? false,
@@ -60643,9 +61122,9 @@ async function executeSceneBuild(opts) {
60643
61122
  }
60644
61123
  if (shouldRunStage(selectedStage, "compose")) {
60645
61124
  if (mode === "agent") {
60646
- const compositionsDir = join27(projectDir, "compositions");
61125
+ const compositionsDir = join28(projectDir, "compositions");
60647
61126
  const missingBeats = activeBeats.filter(
60648
- (b) => !existsSync50(join27(compositionsDir, `scene-${b.id}.html`))
61127
+ (b) => !existsSync51(join28(compositionsDir, `scene-${b.id}.html`))
60649
61128
  );
60650
61129
  if (missingBeats.length > 0) {
60651
61130
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -60679,6 +61158,8 @@ async function executeSceneBuild(opts) {
60679
61158
  }
60680
61159
  onProgress({ type: "phase-start", phase: "compose" });
60681
61160
  stageReports.compose.status = "done";
61161
+ } else if (composer === "footage") {
61162
+ stageReports.compose.status = "skipped";
60682
61163
  } else if (composer === "template") {
60683
61164
  onProgress({ type: "phase-start", phase: "compose" });
60684
61165
  try {
@@ -60718,8 +61199,8 @@ async function executeSceneBuild(opts) {
60718
61199
  if (stageReports.compose.status === "done") {
60719
61200
  for (const beat of composer === "template" ? [] : activeBeats) {
60720
61201
  const videoRel = `assets/video-${beat.id}.mp4`;
60721
- const compositionAbs = join27(projectDir, "compositions", `scene-${beat.id}.html`);
60722
- if (!existsSync50(join27(projectDir, videoRel)) || !existsSync50(compositionAbs)) continue;
61202
+ const compositionAbs = join28(projectDir, "compositions", `scene-${beat.id}.html`);
61203
+ if (!existsSync51(join28(projectDir, videoRel)) || !existsSync51(compositionAbs)) continue;
60723
61204
  const html = readFileSync2(compositionAbs, "utf-8");
60724
61205
  if (!html.includes(videoRel)) {
60725
61206
  warnings.push(
@@ -60774,8 +61255,8 @@ async function executeSceneBuild(opts) {
60774
61255
  totalLatencyMs: Date.now() - startedAt
60775
61256
  });
60776
61257
  }
60777
- if (shouldRunStage(selectedStage, "sync")) {
60778
- if (!existsSync50(join27(projectDir, "index.html"))) {
61258
+ if (composer !== "footage" && shouldRunStage(selectedStage, "sync")) {
61259
+ if (!existsSync51(join28(projectDir, "index.html"))) {
60779
61260
  await scaffoldSceneProject({
60780
61261
  dir: projectDir,
60781
61262
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -60844,7 +61325,50 @@ async function executeSceneBuild(opts) {
60844
61325
  }
60845
61326
  let outputPath;
60846
61327
  let renderResult;
60847
- if (shouldRunStage(selectedStage, "render")) {
61328
+ let footageReport;
61329
+ if (composer === "footage" && shouldRunStage(selectedStage, "render")) {
61330
+ onProgress({ type: "phase-start", phase: "render" });
61331
+ onProgress({ type: "render-start" });
61332
+ const assembled = await executeFootageAssemble({ projectDir, finish: true });
61333
+ if (!assembled.success) {
61334
+ stageReports.render.status = "failed";
61335
+ const renderRetryWith = unique5([
61336
+ ...retryWith,
61337
+ `vibe build ${projectDir} --stage assets --json`,
61338
+ `vibe assemble ${projectDir} --footage --dry-run --json`
61339
+ ]);
61340
+ stageReports.render.retryWith = unique5([
61341
+ ...stageReports.render.retryWith,
61342
+ ...renderRetryWith
61343
+ ]);
61344
+ return finishBuildResult({
61345
+ success: false,
61346
+ phase: "failed",
61347
+ mode,
61348
+ selectedStage,
61349
+ code: "FOOTAGE_ASSEMBLE_FAILED",
61350
+ error: `footage assemble failed: ${assembled.error ?? "unknown"}`,
61351
+ message: `footage assemble failed: ${assembled.error ?? "unknown"}`,
61352
+ suggestion: assembled.suggestion ?? "Ensure every beat has a generated clip (run --stage assets), then retry.",
61353
+ recoverable: true,
61354
+ beats: beatOutcomes,
61355
+ estimatedCostUsd: buildPlan.estimatedCostUsd,
61356
+ costUsd: stageReports.assets.costUsd + stageReports.compose.costUsd,
61357
+ stageReports,
61358
+ sceneRepair,
61359
+ warnings,
61360
+ retryWith: renderRetryWith,
61361
+ status: "failed",
61362
+ currentStage: "render",
61363
+ totalLatencyMs: Date.now() - startedAt
61364
+ });
61365
+ }
61366
+ outputPath = assembled.outputPath;
61367
+ footageReport = assembled.report;
61368
+ warnings.push(...assembled.report?.warnings ?? []);
61369
+ onProgress({ type: "render-done", outputPath });
61370
+ stageReports.render.status = "done";
61371
+ } else if (shouldRunStage(selectedStage, "render")) {
60848
61372
  onProgress({ type: "phase-start", phase: "render" });
60849
61373
  onProgress({ type: "render-start" });
60850
61374
  renderResult = await executeSceneRender({ projectDir });
@@ -60899,6 +61423,7 @@ async function executeSceneBuild(opts) {
60899
61423
  beats: beatOutcomes,
60900
61424
  outputPath,
60901
61425
  renderResult,
61426
+ footageReport,
60902
61427
  estimatedCostUsd: buildPlan.estimatedCostUsd,
60903
61428
  costUsd: stageReports.assets.costUsd + stageReports.compose.costUsd,
60904
61429
  stageReports,
@@ -61033,7 +61558,7 @@ async function dispatchNarration(beat, ctx) {
61033
61558
  return {
61034
61559
  status: "cached",
61035
61560
  path: existingRel,
61036
- durationSec: await safeAudioDuration(join27(ctx.projectDir, existingRel)),
61561
+ durationSec: await safeAudioDuration(join28(ctx.projectDir, existingRel)),
61037
61562
  provider: metadata?.provider,
61038
61563
  cachePath: metadata?.cachePath,
61039
61564
  cacheKey: metadata?.cacheKey,
@@ -61057,9 +61582,9 @@ async function dispatchNarration(beat, ctx) {
61057
61582
  voice,
61058
61583
  ext: resolution.audioExtension
61059
61584
  });
61060
- const cacheAbs = join27(ctx.projectDir, cache.path);
61585
+ const cacheAbs = join28(ctx.projectDir, cache.path);
61061
61586
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
61062
- const abs = join27(ctx.projectDir, rel);
61587
+ const abs = join28(ctx.projectDir, rel);
61063
61588
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
61064
61589
  projectDir: ctx.projectDir,
61065
61590
  kind: "narration",
@@ -61073,7 +61598,7 @@ async function dispatchNarration(beat, ctx) {
61073
61598
  return {
61074
61599
  status: "cached",
61075
61600
  path: existingRel,
61076
- durationSec: await safeAudioDuration(join27(ctx.projectDir, existingRel)),
61601
+ durationSec: await safeAudioDuration(join28(ctx.projectDir, existingRel)),
61077
61602
  provider: resolution.provider,
61078
61603
  cachePath: cache.path,
61079
61604
  cacheKey: cache.key,
@@ -61081,8 +61606,8 @@ async function dispatchNarration(beat, ctx) {
61081
61606
  freshness: "fresh"
61082
61607
  };
61083
61608
  }
61084
- if (existsSync50(cacheAbs) && !ctx.force) {
61085
- await mkdir25(dirname21(abs), { recursive: true });
61609
+ if (existsSync51(cacheAbs) && !ctx.force) {
61610
+ await mkdir26(dirname22(abs), { recursive: true });
61086
61611
  await copyFile5(cacheAbs, abs);
61087
61612
  await writeAssetMetadata({
61088
61613
  projectDir: ctx.projectDir,
@@ -61113,14 +61638,14 @@ async function dispatchNarration(beat, ctx) {
61113
61638
  ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
61114
61639
  return { status: "failed", error };
61115
61640
  }
61116
- await mkdir25(dirname21(abs), { recursive: true });
61117
- await writeFile45(abs, result.audioBuffer);
61641
+ await mkdir26(dirname22(abs), { recursive: true });
61642
+ await writeFile46(abs, result.audioBuffer);
61118
61643
  for (const otherExt of ["mp3", "wav"]) {
61119
- const sibling = join27(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
61120
- if (sibling !== abs && existsSync50(sibling)) await rm5(sibling, { force: true });
61644
+ const sibling = join28(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
61645
+ if (sibling !== abs && existsSync51(sibling)) await rm5(sibling, { force: true });
61121
61646
  }
61122
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61123
- await writeFile45(cacheAbs, result.audioBuffer);
61647
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61648
+ await writeFile46(cacheAbs, result.audioBuffer);
61124
61649
  await writeAssetMetadata({
61125
61650
  projectDir: ctx.projectDir,
61126
61651
  kind: "narration",
@@ -61152,8 +61677,8 @@ async function dispatchNarration(beat, ctx) {
61152
61677
  async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
61153
61678
  if (!narrationPath) return "skipped";
61154
61679
  const relPath = beatTranscriptRelPath(beat.id);
61155
- const abs = join27(ctx.projectDir, relPath);
61156
- if (existsSync50(abs) && !ctx.force && !narrationFresh) {
61680
+ const abs = join28(ctx.projectDir, relPath);
61681
+ if (existsSync51(abs) && !ctx.force && !narrationFresh) {
61157
61682
  ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
61158
61683
  return "cached";
61159
61684
  }
@@ -61166,7 +61691,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
61166
61691
  });
61167
61692
  return "skipped";
61168
61693
  }
61169
- const words = await transcribeNarrationWords(join27(ctx.projectDir, narrationPath), { apiKey });
61694
+ const words = await transcribeNarrationWords(join28(ctx.projectDir, narrationPath), { apiKey });
61170
61695
  if (words.length === 0) {
61171
61696
  ctx.onProgress({
61172
61697
  type: "transcript-skipped",
@@ -61176,8 +61701,8 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
61176
61701
  return "skipped";
61177
61702
  }
61178
61703
  try {
61179
- await mkdir25(dirname21(abs), { recursive: true });
61180
- await writeFile45(abs, JSON.stringify(words, null, 2), "utf-8");
61704
+ await mkdir26(dirname22(abs), { recursive: true });
61705
+ await writeFile46(abs, JSON.stringify(words, null, 2), "utf-8");
61181
61706
  } catch (err) {
61182
61707
  ctx.onProgress({
61183
61708
  type: "transcript-skipped",
@@ -61224,7 +61749,7 @@ async function buildCharacters(characters, ctx) {
61224
61749
  const size = ctx.imageSize ?? "1536x1024";
61225
61750
  for (const character of characters) {
61226
61751
  if (character.imagePath) {
61227
- if (existsSync50(join27(ctx.projectDir, character.imagePath))) {
61752
+ if (existsSync51(join28(ctx.projectDir, character.imagePath))) {
61228
61753
  paths.set(character.name, character.imagePath);
61229
61754
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
61230
61755
  } else {
@@ -61237,16 +61762,17 @@ async function buildCharacters(characters, ctx) {
61237
61762
  if (!character.prompt) continue;
61238
61763
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
61239
61764
  const rel = `assets/character-${character.name}.png`;
61240
- const abs = join27(ctx.projectDir, rel);
61765
+ const abs = join28(ctx.projectDir, rel);
61241
61766
  const metadataOptions = { quality: ctx.imageQuality, size };
61242
61767
  const cache = characterCacheDescriptor({
61243
61768
  name: character.name,
61244
61769
  cue: prompt3,
61245
61770
  provider: ctx.imageProvider,
61246
61771
  quality: ctx.imageQuality,
61247
- size
61772
+ size,
61773
+ model: ctx.imageModel
61248
61774
  });
61249
- if (existsSync50(abs) && !ctx.force) {
61775
+ if (existsSync51(abs) && !ctx.force) {
61250
61776
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
61251
61777
  if (!metadata || isFreshCanonicalAsset({
61252
61778
  projectDir: ctx.projectDir,
@@ -61262,9 +61788,9 @@ async function buildCharacters(characters, ctx) {
61262
61788
  continue;
61263
61789
  }
61264
61790
  }
61265
- const cacheAbs = join27(ctx.projectDir, cache.path);
61266
- if (existsSync50(cacheAbs) && !ctx.force) {
61267
- await mkdir25(dirname21(abs), { recursive: true });
61791
+ const cacheAbs = join28(ctx.projectDir, cache.path);
61792
+ if (existsSync51(cacheAbs) && !ctx.force) {
61793
+ await mkdir26(dirname22(abs), { recursive: true });
61268
61794
  await copyFile5(cacheAbs, abs);
61269
61795
  await writeAssetMetadata({
61270
61796
  projectDir: ctx.projectDir,
@@ -61287,7 +61813,8 @@ async function buildCharacters(characters, ctx) {
61287
61813
  projectDir: ctx.projectDir,
61288
61814
  imageProvider: ctx.imageProvider,
61289
61815
  imageSize: size,
61290
- imageQuality: ctx.imageQuality
61816
+ imageQuality: ctx.imageQuality,
61817
+ imageModel: ctx.imageModel
61291
61818
  },
61292
61819
  imageRatioForSize(size)
61293
61820
  );
@@ -61296,10 +61823,10 @@ async function buildCharacters(characters, ctx) {
61296
61823
  ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
61297
61824
  continue;
61298
61825
  }
61299
- await mkdir25(dirname21(abs), { recursive: true });
61300
- await writeFile45(abs, generated.buffer);
61301
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61302
- await writeFile45(cacheAbs, generated.buffer);
61826
+ await mkdir26(dirname22(abs), { recursive: true });
61827
+ await writeFile46(abs, generated.buffer);
61828
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61829
+ await writeFile46(cacheAbs, generated.buffer);
61303
61830
  await writeAssetMetadata({
61304
61831
  projectDir: ctx.projectDir,
61305
61832
  kind: "character",
@@ -61329,7 +61856,7 @@ async function dispatchBackdrop(beat, ctx) {
61329
61856
  if (!cue) return { status: "no-cue" };
61330
61857
  const prompt3 = augmentBackdropPrompt(cue);
61331
61858
  const rel = `assets/backdrop-${beat.id}.png`;
61332
- const abs = join27(ctx.projectDir, rel);
61859
+ const abs = join28(ctx.projectDir, rel);
61333
61860
  const size = ctx.imageSize ?? "1536x1024";
61334
61861
  const ratio = imageRatioForSize(size);
61335
61862
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -61339,10 +61866,11 @@ async function dispatchBackdrop(beat, ctx) {
61339
61866
  provider: ctx.imageProvider,
61340
61867
  quality: ctx.imageQuality,
61341
61868
  size,
61342
- ratio
61869
+ ratio,
61870
+ model: ctx.imageModel
61343
61871
  });
61344
61872
  const metadataPath = assetMetadataPath("backdrop", beat.id);
61345
- if (existsSync50(abs) && !ctx.force) {
61873
+ if (existsSync51(abs) && !ctx.force) {
61346
61874
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
61347
61875
  if (!metadata || isFreshCanonicalAsset({
61348
61876
  projectDir: ctx.projectDir,
@@ -61365,9 +61893,9 @@ async function dispatchBackdrop(beat, ctx) {
61365
61893
  };
61366
61894
  }
61367
61895
  }
61368
- const cacheAbs = join27(ctx.projectDir, cache.path);
61369
- if (existsSync50(cacheAbs) && !ctx.force) {
61370
- await mkdir25(dirname21(abs), { recursive: true });
61896
+ const cacheAbs = join28(ctx.projectDir, cache.path);
61897
+ if (existsSync51(cacheAbs) && !ctx.force) {
61898
+ await mkdir26(dirname22(abs), { recursive: true });
61371
61899
  await copyFile5(cacheAbs, abs);
61372
61900
  await writeAssetMetadata({
61373
61901
  projectDir: ctx.projectDir,
@@ -61397,11 +61925,11 @@ async function dispatchBackdrop(beat, ctx) {
61397
61925
  ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
61398
61926
  return { status: "failed", error };
61399
61927
  }
61400
- await mkdir25(dirname21(abs), { recursive: true });
61928
+ await mkdir26(dirname22(abs), { recursive: true });
61401
61929
  const buffer = generated.buffer;
61402
- await writeFile45(abs, buffer);
61403
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61404
- await writeFile45(cacheAbs, buffer);
61930
+ await writeFile46(abs, buffer);
61931
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61932
+ await writeFile46(cacheAbs, buffer);
61405
61933
  await writeAssetMetadata({
61406
61934
  projectDir: ctx.projectDir,
61407
61935
  kind: "backdrop",
@@ -61443,7 +61971,9 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
61443
61971
  const provider2 = new OpenAIImageProvider();
61444
61972
  await provider2.initialize({ apiKey });
61445
61973
  const result2 = await provider2.generateImage(prompt3, {
61446
- model: "gpt-image-2",
61974
+ // CLI input is free-form; an alias the provider doesn't know fails the
61975
+ // API call and surfaces as a beat-scoped backdrop/keyframe error.
61976
+ model: ctx.imageModel ?? "gpt-image-2",
61447
61977
  size: ctx.imageSize,
61448
61978
  quality: ctx.imageQuality
61449
61979
  });
@@ -61453,7 +61983,7 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
61453
61983
  const provider2 = new GeminiProvider();
61454
61984
  await provider2.initialize({ apiKey });
61455
61985
  const result2 = await provider2.generateImage(prompt3, {
61456
- model: "flash",
61986
+ model: ctx.imageModel ?? "flash",
61457
61987
  aspectRatio: ratio
61458
61988
  });
61459
61989
  return imageBufferFromResult(result2);
@@ -61486,7 +62016,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
61486
62016
  const provider2 = new GeminiProvider();
61487
62017
  await provider2.initialize({ apiKey });
61488
62018
  const result2 = await provider2.editImage(sheetBuffers, prompt3, {
61489
- model: "flash",
62019
+ model: ctx.imageModel ?? "flash",
61490
62020
  aspectRatio: ratio
61491
62021
  });
61492
62022
  return imageBufferFromResult(result2);
@@ -61498,7 +62028,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
61498
62028
  }
61499
62029
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61500
62030
  const rel = `assets/keyframe-${beat.id}.png`;
61501
- const abs = join27(ctx.projectDir, rel);
62031
+ const abs = join28(ctx.projectDir, rel);
61502
62032
  const size = ctx.imageSize ?? "1536x1024";
61503
62033
  const ratio = imageRatioForSize(size);
61504
62034
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -61509,9 +62039,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61509
62039
  quality: ctx.imageQuality,
61510
62040
  size,
61511
62041
  ratio,
61512
- characters: characterRefsRel
62042
+ characters: characterRefsRel,
62043
+ model: ctx.imageModel
61513
62044
  });
61514
- if (existsSync50(abs) && !ctx.force) {
62045
+ if (existsSync51(abs) && !ctx.force) {
61515
62046
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
61516
62047
  if (!metadata || isFreshCanonicalAsset({
61517
62048
  projectDir: ctx.projectDir,
@@ -61525,9 +62056,9 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61525
62056
  return { status: "cached", rel };
61526
62057
  }
61527
62058
  }
61528
- const cacheAbs = join27(ctx.projectDir, cache.path);
61529
- if (existsSync50(cacheAbs) && !ctx.force) {
61530
- await mkdir25(dirname21(abs), { recursive: true });
62059
+ const cacheAbs = join28(ctx.projectDir, cache.path);
62060
+ if (existsSync51(cacheAbs) && !ctx.force) {
62061
+ await mkdir26(dirname22(abs), { recursive: true });
61531
62062
  await copyFile5(cacheAbs, abs);
61532
62063
  await writeAssetMetadata({
61533
62064
  projectDir: ctx.projectDir,
@@ -61544,22 +62075,23 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61544
62075
  }
61545
62076
  const sheetBuffers = [];
61546
62077
  for (const relPath of characterRefsRel) {
61547
- const sheetAbs = join27(ctx.projectDir, relPath);
61548
- if (existsSync50(sheetAbs)) sheetBuffers.push(await readFile44(sheetAbs));
62078
+ const sheetAbs = join28(ctx.projectDir, relPath);
62079
+ if (existsSync51(sheetAbs)) sheetBuffers.push(await readFile44(sheetAbs));
61549
62080
  }
61550
62081
  const imgCtx = {
61551
62082
  projectDir: ctx.projectDir,
61552
62083
  imageProvider: ctx.imageProvider,
61553
62084
  imageSize: size,
61554
- imageQuality: ctx.imageQuality
62085
+ imageQuality: ctx.imageQuality,
62086
+ imageModel: ctx.imageModel
61555
62087
  };
61556
62088
  const identityLock = "Keep the EXACT same person and face as the reference image(s): identical facial features (eyes, nose shape, jawline), skin tone and complexion, hairstyle, hair color, and wardrobe. Do not restyle or change the face. Change only the scene, pose, framing, and lighting. ";
61557
62089
  const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
61558
62090
  if (!generated.success) return { status: "failed", error: generated.error };
61559
- await mkdir25(dirname21(abs), { recursive: true });
61560
- await writeFile45(abs, generated.buffer);
61561
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61562
- await writeFile45(cacheAbs, generated.buffer);
62091
+ await mkdir26(dirname22(abs), { recursive: true });
62092
+ await writeFile46(abs, generated.buffer);
62093
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
62094
+ await writeFile46(cacheAbs, generated.buffer);
61563
62095
  await writeAssetMetadata({
61564
62096
  projectDir: ctx.projectDir,
61565
62097
  kind: "keyframe",
@@ -61613,14 +62145,16 @@ async function dispatchVideo(beat, ctx, keyframe) {
61613
62145
  const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
61614
62146
  const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
61615
62147
  if (!prompt3) return { status: "no-cue" };
62148
+ const providerCue = stringOrUndefined4(beat.cues?.provider);
62149
+ const requestedProvider = providerCue ? resolveBuildVideoProvider(providerCue) : ctx.videoProvider;
61616
62150
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
61617
- const characterRefsAbs = characterRefsRel.map((p) => join27(ctx.projectDir, p));
62151
+ const characterRefsAbs = characterRefsRel.map((p) => join28(ctx.projectDir, p));
61618
62152
  const rel = `assets/video-${beat.id}.mp4`;
61619
- const abs = join27(ctx.projectDir, rel);
62153
+ const abs = join28(ctx.projectDir, rel);
61620
62154
  const cache = videoCacheDescriptor({
61621
62155
  beatId: beat.id,
61622
62156
  cue: prompt3,
61623
- provider: ctx.videoProvider,
62157
+ provider: requestedProvider,
61624
62158
  duration: normalizeVideoDuration(beat.duration),
61625
62159
  // Keyframe mode keys the clip to the keyframe prompt; refs feed the keyframe,
61626
62160
  // not the clip directly.
@@ -61628,14 +62162,14 @@ async function dispatchVideo(beat, ctx, keyframe) {
61628
62162
  keyframe: keyframeCue
61629
62163
  });
61630
62164
  const metadataPath = assetMetadataPath("video", beat.id);
61631
- if (existsSync50(abs) && !ctx.force) {
62165
+ if (existsSync51(abs) && !ctx.force) {
61632
62166
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
61633
62167
  if (!metadata || isFreshCanonicalAsset({
61634
62168
  projectDir: ctx.projectDir,
61635
62169
  kind: "video",
61636
62170
  beatId: beat.id,
61637
62171
  cue: prompt3,
61638
- provider: ctx.videoProvider,
62172
+ provider: requestedProvider,
61639
62173
  options: { duration: normalizeVideoDuration(beat.duration), ratio: "16:9" },
61640
62174
  cacheKey: cache.key
61641
62175
  })) {
@@ -61643,7 +62177,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61643
62177
  return {
61644
62178
  status: "cached",
61645
62179
  path: rel,
61646
- provider: metadata?.provider ?? ctx.videoProvider,
62180
+ provider: metadata?.provider ?? requestedProvider,
61647
62181
  cachePath: metadata?.cachePath ?? cache.path,
61648
62182
  cacheKey: metadata?.cacheKey ?? cache.key,
61649
62183
  metadataPath,
@@ -61651,16 +62185,16 @@ async function dispatchVideo(beat, ctx, keyframe) {
61651
62185
  };
61652
62186
  }
61653
62187
  }
61654
- const cacheAbs = join27(ctx.projectDir, cache.path);
61655
- if (existsSync50(cacheAbs) && !ctx.force) {
61656
- await mkdir25(dirname21(abs), { recursive: true });
62188
+ const cacheAbs = join28(ctx.projectDir, cache.path);
62189
+ if (existsSync51(cacheAbs) && !ctx.force) {
62190
+ await mkdir26(dirname22(abs), { recursive: true });
61657
62191
  await copyFile5(cacheAbs, abs);
61658
62192
  await writeAssetMetadata({
61659
62193
  projectDir: ctx.projectDir,
61660
62194
  kind: "video",
61661
62195
  beatId: beat.id,
61662
62196
  cue: prompt3,
61663
- provider: ctx.videoProvider,
62197
+ provider: requestedProvider,
61664
62198
  options: { duration: normalizeVideoDuration(beat.duration), ratio: "16:9" },
61665
62199
  cacheKey: cache.key,
61666
62200
  canonicalPath: rel,
@@ -61670,7 +62204,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61670
62204
  return {
61671
62205
  status: "cached",
61672
62206
  path: rel,
61673
- provider: ctx.videoProvider,
62207
+ provider: requestedProvider,
61674
62208
  cachePath: cache.path,
61675
62209
  cacheKey: cache.key,
61676
62210
  metadataPath,
@@ -61683,11 +62217,11 @@ async function dispatchVideo(beat, ctx, keyframe) {
61683
62217
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
61684
62218
  return { status: "skipped" };
61685
62219
  }
61686
- const keyframeImageAbs = keyframeImageRel ? join27(ctx.projectDir, keyframeImageRel) : void 0;
62220
+ const keyframeImageAbs = keyframeImageRel ? join28(ctx.projectDir, keyframeImageRel) : void 0;
61687
62221
  loadSceneBuildEnv(ctx.projectDir);
61688
- const result = await executeVideoGenerate({
62222
+ const generateWith = (provider, apiKey) => executeVideoGenerate({
61689
62223
  prompt: prompt3,
61690
- provider: ctx.videoProvider,
62224
+ provider,
61691
62225
  duration: normalizeVideoDuration(beat.duration),
61692
62226
  ratio: "16:9",
61693
62227
  output: abs,
@@ -61700,22 +62234,44 @@ async function dispatchVideo(beat, ctx, keyframe) {
61700
62234
  // track carries audio), so the model's generated audio is never used. Disable
61701
62235
  // it: it's wasted, and Seedance's audio moderation can spuriously fail a beat.
61702
62236
  generateAudio: false,
61703
- apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
62237
+ apiKey
61704
62238
  });
62239
+ let activeProvider = requestedProvider;
62240
+ let result = await generateWith(
62241
+ activeProvider,
62242
+ await apiKeyForVideoProvider(activeProvider, ctx.projectDir)
62243
+ );
61705
62244
  if (!result.success || !result.taskId) {
61706
62245
  const error = result.error ?? "video generation did not return a task id";
61707
- ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
61708
- return { status: "failed", error };
62246
+ const runwayKey = isLikenessRejection(error) && activeProvider !== "runway" ? await apiKeyForVideoProvider("runway", ctx.projectDir) : void 0;
62247
+ if (!runwayKey) {
62248
+ ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
62249
+ return { status: "failed", error };
62250
+ }
62251
+ ctx.onProgress({
62252
+ type: "video-fallback",
62253
+ beatId: beat.id,
62254
+ from: activeProvider,
62255
+ to: "runway",
62256
+ reason: error
62257
+ });
62258
+ activeProvider = "runway";
62259
+ result = await generateWith(activeProvider, runwayKey);
62260
+ if (!result.success || !result.taskId) {
62261
+ const fallbackError = result.error ?? "video generation did not return a task id";
62262
+ ctx.onProgress({ type: "video-failed", beatId: beat.id, error: fallbackError });
62263
+ return { status: "failed", error: fallbackError };
62264
+ }
61709
62265
  }
61710
- if (result.status === "completed" && existsSync50(abs)) {
61711
- await mkdir25(dirname21(cacheAbs), { recursive: true });
62266
+ if (result.status === "completed" && existsSync51(abs)) {
62267
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61712
62268
  await copyFile5(abs, cacheAbs);
61713
62269
  await writeAssetMetadata({
61714
62270
  projectDir: ctx.projectDir,
61715
62271
  kind: "video",
61716
62272
  beatId: beat.id,
61717
62273
  cue: prompt3,
61718
- provider: result.provider ?? ctx.videoProvider,
62274
+ provider: result.provider ?? activeProvider,
61719
62275
  options: { duration: normalizeVideoDuration(beat.duration), ratio: "16:9" },
61720
62276
  cacheKey: cache.key,
61721
62277
  canonicalPath: rel,
@@ -61725,12 +62281,12 @@ async function dispatchVideo(beat, ctx, keyframe) {
61725
62281
  type: "video-generated",
61726
62282
  beatId: beat.id,
61727
62283
  path: rel,
61728
- provider: result.provider ?? ctx.videoProvider
62284
+ provider: result.provider ?? activeProvider
61729
62285
  });
61730
62286
  return {
61731
62287
  status: "generated",
61732
62288
  path: rel,
61733
- provider: result.provider ?? ctx.videoProvider,
62289
+ provider: result.provider ?? activeProvider,
61734
62290
  cachePath: cache.path,
61735
62291
  cacheKey: cache.key,
61736
62292
  metadataPath,
@@ -61739,7 +62295,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61739
62295
  }
61740
62296
  const job = await createAndWriteJobRecord({
61741
62297
  jobType: "generate-video",
61742
- provider: result.provider ?? ctx.videoProvider,
62298
+ provider: result.provider ?? activeProvider,
61743
62299
  providerTaskId: result.taskId,
61744
62300
  providerTaskType: keyframeImageAbs ? "image2video" : "text2video",
61745
62301
  status: "running",
@@ -61775,7 +62331,7 @@ async function dispatchMusic(beat, ctx) {
61775
62331
  const prompt3 = stringOrUndefined4(beat.cues?.music);
61776
62332
  if (!prompt3) return { status: "no-cue" };
61777
62333
  const rel = `assets/music-${beat.id}.mp3`;
61778
- const abs = join27(ctx.projectDir, rel);
62334
+ const abs = join28(ctx.projectDir, rel);
61779
62335
  const duration = normalizeMusicDuration(beat.duration);
61780
62336
  const cache = musicCacheDescriptor({
61781
62337
  beatId: beat.id,
@@ -61784,7 +62340,7 @@ async function dispatchMusic(beat, ctx) {
61784
62340
  duration
61785
62341
  });
61786
62342
  const metadataPath = assetMetadataPath("music", beat.id);
61787
- if (existsSync50(abs) && !ctx.force) {
62343
+ if (existsSync51(abs) && !ctx.force) {
61788
62344
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
61789
62345
  if (!metadata || isFreshCanonicalAsset({
61790
62346
  projectDir: ctx.projectDir,
@@ -61808,9 +62364,9 @@ async function dispatchMusic(beat, ctx) {
61808
62364
  };
61809
62365
  }
61810
62366
  }
61811
- const cacheAbs = join27(ctx.projectDir, cache.path);
61812
- if (existsSync50(cacheAbs) && !ctx.force) {
61813
- await mkdir25(dirname21(abs), { recursive: true });
62367
+ const cacheAbs = join28(ctx.projectDir, cache.path);
62368
+ if (existsSync51(cacheAbs) && !ctx.force) {
62369
+ await mkdir26(dirname22(abs), { recursive: true });
61814
62370
  await copyFile5(cacheAbs, abs);
61815
62371
  await writeAssetMetadata({
61816
62372
  projectDir: ctx.projectDir,
@@ -61883,12 +62439,12 @@ async function dispatchMusic(beat, ctx) {
61883
62439
  metadataPath
61884
62440
  };
61885
62441
  }
61886
- if (!existsSync50(abs)) {
62442
+ if (!existsSync51(abs)) {
61887
62443
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
61888
62444
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
61889
62445
  return { status: "failed", error };
61890
62446
  }
61891
- await mkdir25(dirname21(cacheAbs), { recursive: true });
62447
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61892
62448
  await copyFile5(abs, cacheAbs);
61893
62449
  await writeAssetMetadata({
61894
62450
  projectDir: ctx.projectDir,
@@ -61919,15 +62475,15 @@ async function dispatchMusic(beat, ctx) {
61919
62475
  };
61920
62476
  }
61921
62477
  function loadSceneBuildEnv(projectDir) {
61922
- (0, import_dotenv4.config)({ path: join27(projectDir, ".env"), quiet: true });
61923
- (0, import_dotenv4.config)({ path: resolve62(process.cwd(), ".env"), quiet: true });
62478
+ (0, import_dotenv4.config)({ path: join28(projectDir, ".env"), quiet: true });
62479
+ (0, import_dotenv4.config)({ path: resolve63(process.cwd(), ".env"), quiet: true });
61924
62480
  let dir = process.cwd();
61925
- while (dir !== dirname21(dir)) {
61926
- if (existsSync50(join27(dir, "pnpm-workspace.yaml"))) {
61927
- (0, import_dotenv4.config)({ path: join27(dir, ".env"), quiet: true });
62481
+ while (dir !== dirname22(dir)) {
62482
+ if (existsSync51(join28(dir, "pnpm-workspace.yaml"))) {
62483
+ (0, import_dotenv4.config)({ path: join28(dir, ".env"), quiet: true });
61928
62484
  return;
61929
62485
  }
61930
- dir = dirname21(dir);
62486
+ dir = dirname22(dir);
61931
62487
  }
61932
62488
  }
61933
62489
  async function skipped(kind, beatId, reason, ctx) {
@@ -62141,7 +62697,7 @@ function mergeBeatOutcomes(base, updates) {
62141
62697
  }
62142
62698
  function firstExisting3(projectDir, relPaths) {
62143
62699
  for (const rel of relPaths) {
62144
- if (existsSync50(join27(projectDir, rel))) return rel;
62700
+ if (existsSync51(join28(projectDir, rel))) return rel;
62145
62701
  }
62146
62702
  return null;
62147
62703
  }
@@ -62164,6 +62720,9 @@ function resolveBuildMusicProvider(value) {
62164
62720
  const provider = String(value ?? "elevenlabs").toLowerCase();
62165
62721
  return provider === "replicate" ? "replicate" : "elevenlabs";
62166
62722
  }
62723
+ function isLikenessRejection(error) {
62724
+ return typeof error === "string" && /likeness(es)? of real people/i.test(error);
62725
+ }
62167
62726
  function stringOrUndefined4(value) {
62168
62727
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
62169
62728
  }
@@ -62171,26 +62730,30 @@ async function apiKeyForVideoProvider(provider, projectDir) {
62171
62730
  const providerKey = provider === "seedance" ? "fal" : provider === "grok" ? "xai" : provider === "veo" ? "google" : provider;
62172
62731
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
62173
62732
  }
62174
- async function finalizeBuildResult(projectDir, startedAt, result) {
62175
- const reportPath = join27(projectDir, "build-report.json");
62176
- if (result.stageReports) {
62177
- for (const report of Object.values(result.stageReports)) {
62178
- if (report.status === "pending") report.status = "skipped";
62733
+ async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
62734
+ const reportPath = join28(projectDir, "build-report.json");
62735
+ for (const source of /* @__PURE__ */ new Set([result, reportResult])) {
62736
+ if (source.stageReports) {
62737
+ for (const report of Object.values(source.stageReports)) {
62738
+ if (report.status === "pending") report.status = "skipped";
62739
+ }
62179
62740
  }
62180
62741
  }
62181
- const withMeta = {
62182
- ...result,
62183
- status: result.status ?? buildWorkflowStatus(result),
62184
- currentStage: result.currentStage ?? buildCurrentStage(result),
62185
- beatSummary: result.beatSummary ?? summarizeBuildBeats(projectDir, result.beats),
62186
- sceneRepair: result.sceneRepair ?? skippedSceneRepairSummary(),
62742
+ const decorate = (base) => ({
62743
+ ...base,
62744
+ status: base.status ?? buildWorkflowStatus(base),
62745
+ currentStage: base.currentStage ?? buildCurrentStage(base),
62746
+ beatSummary: base.beatSummary ?? summarizeBuildBeats(projectDir, base.beats),
62747
+ sceneRepair: base.sceneRepair ?? skippedSceneRepairSummary(),
62187
62748
  reportPath,
62188
62749
  totalLatencyMs: Date.now() - startedAt
62189
- };
62750
+ });
62751
+ const withMeta = decorate(result);
62752
+ const reportMeta = reportResult === result ? withMeta : decorate(reportResult);
62190
62753
  try {
62191
- await writeFile45(
62754
+ await writeFile46(
62192
62755
  reportPath,
62193
- JSON.stringify(toBuildReport(projectDir, withMeta), null, 2) + "\n",
62756
+ JSON.stringify(toBuildReport(projectDir, reportMeta), null, 2) + "\n",
62194
62757
  "utf-8"
62195
62758
  );
62196
62759
  } catch {
@@ -62331,7 +62894,7 @@ function normalizeReportDuration(value) {
62331
62894
  }
62332
62895
  function buildReportComposition(projectDir, result, beatId) {
62333
62896
  const path2 = `compositions/scene-${beatId}.html`;
62334
- const exists = existsSync50(join27(projectDir, path2));
62897
+ const exists = existsSync51(join28(projectDir, path2));
62335
62898
  if (result.stageReports?.compose.status === "failed") return { path: path2, exists, status: "failed" };
62336
62899
  if (result.phase === "needs-author" && !exists) return { path: path2, exists, status: "needs-author" };
62337
62900
  if (exists) return { path: path2, exists, status: "exists" };
@@ -62378,8 +62941,8 @@ function summarizeBuildBeats(projectDir, beats) {
62378
62941
  let compositionsReady = 0;
62379
62942
  for (const beat of beats) {
62380
62943
  if (beatAssetsReady(beat)) assetsReady += 1;
62381
- const compositionPath = join27(projectDir, "compositions", `scene-${beat.beatId}.html`);
62382
- if (existsSync50(compositionPath)) {
62944
+ const compositionPath = join28(projectDir, "compositions", `scene-${beat.beatId}.html`);
62945
+ if (existsSync51(compositionPath)) {
62383
62946
  compositionsReady += 1;
62384
62947
  } else {
62385
62948
  needsAuthor.push(beat.beatId);
@@ -62419,6 +62982,7 @@ var init_scene_build = __esm({
62419
62982
  init_ffmpeg_gate();
62420
62983
  init_concurrency();
62421
62984
  init_compose_aivideo();
62985
+ init_footage_assemble();
62422
62986
  init_scene_project();
62423
62987
  init_compose_prompts();
62424
62988
  init_config();
@@ -62514,16 +63078,16 @@ __export(ai_audio_exports, {
62514
63078
  executeTranscribe: () => executeTranscribe,
62515
63079
  executeVoiceClone: () => executeVoiceClone
62516
63080
  });
62517
- import { resolve as resolve72, dirname as dirname23, basename as basename22, extname as extname14 } from "node:path";
62518
- import { readFile as readFile46, writeFile as writeFile46 } from "node:fs/promises";
62519
- import { existsSync as existsSync55 } from "node:fs";
63081
+ import { resolve as resolve73, dirname as dirname24, basename as basename22, extname as extname14 } from "node:path";
63082
+ import { readFile as readFile46, writeFile as writeFile47 } from "node:fs/promises";
63083
+ import { existsSync as existsSync56 } from "node:fs";
62520
63084
  async function executeTranscribe(options) {
62521
63085
  const { audioPath, language, output, format, apiKey } = options;
62522
63086
  try {
62523
63087
  const key = apiKey || process.env.OPENAI_API_KEY;
62524
63088
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
62525
- const absPath = resolve72(process.cwd(), audioPath);
62526
- if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
63089
+ const absPath = resolve73(process.cwd(), audioPath);
63090
+ if (!existsSync56(absPath)) return { success: false, error: `File not found: ${absPath}` };
62527
63091
  const whisper = new WhisperProvider();
62528
63092
  await whisper.initialize({ apiKey: key });
62529
63093
  const audioBuffer = await readFile46(absPath);
@@ -62534,10 +63098,10 @@ async function executeTranscribe(options) {
62534
63098
  }
62535
63099
  let outputPath;
62536
63100
  if (output) {
62537
- outputPath = resolve72(process.cwd(), output);
63101
+ outputPath = resolve73(process.cwd(), output);
62538
63102
  const fmt = detectFormat(output, format);
62539
63103
  const content = formatTranscript(result, fmt);
62540
- await writeFile46(outputPath, content, "utf-8");
63104
+ await writeFile47(outputPath, content, "utf-8");
62541
63105
  }
62542
63106
  return {
62543
63107
  success: true,
@@ -62555,8 +63119,8 @@ async function executeIsolate(options) {
62555
63119
  try {
62556
63120
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
62557
63121
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
62558
- const absPath = resolve72(process.cwd(), audioPath);
62559
- if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
63122
+ const absPath = resolve73(process.cwd(), audioPath);
63123
+ if (!existsSync56(absPath)) return { success: false, error: `File not found: ${absPath}` };
62560
63124
  const audioBuffer = await readFile46(absPath);
62561
63125
  const elevenlabs = new ElevenLabsProvider();
62562
63126
  await elevenlabs.initialize({ apiKey: key });
@@ -62564,8 +63128,8 @@ async function executeIsolate(options) {
62564
63128
  if (!result.success || !result.audioBuffer) {
62565
63129
  return { success: false, error: result.error || "Audio isolation failed" };
62566
63130
  }
62567
- const outputPath = resolve72(process.cwd(), output);
62568
- await writeFile46(outputPath, result.audioBuffer);
63131
+ const outputPath = resolve73(process.cwd(), output);
63132
+ await writeFile47(outputPath, result.audioBuffer);
62569
63133
  return { success: true, outputPath };
62570
63134
  } catch (error) {
62571
63135
  return { success: false, error: `Audio isolation failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -62581,8 +63145,8 @@ async function executeVoiceClone(options) {
62581
63145
  }
62582
63146
  const audioBuffers = [];
62583
63147
  for (const samplePath of samplePaths) {
62584
- const absPath = resolve72(process.cwd(), samplePath);
62585
- if (!existsSync55(absPath)) return { success: false, error: `File not found: ${samplePath}` };
63148
+ const absPath = resolve73(process.cwd(), samplePath);
63149
+ if (!existsSync56(absPath)) return { success: false, error: `File not found: ${samplePath}` };
62586
63150
  const buffer = await readFile46(absPath);
62587
63151
  audioBuffers.push(buffer);
62588
63152
  }
@@ -62603,8 +63167,8 @@ async function executeVoiceClone(options) {
62603
63167
  async function executeDub(options) {
62604
63168
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
62605
63169
  try {
62606
- const absPath = resolve72(process.cwd(), mediaPath);
62607
- if (!existsSync55(absPath)) return { success: false, error: `File not found: ${absPath}` };
63170
+ const absPath = resolve73(process.cwd(), mediaPath);
63171
+ if (!existsSync56(absPath)) return { success: false, error: `File not found: ${absPath}` };
62608
63172
  const openaiKey = process.env.OPENAI_API_KEY;
62609
63173
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
62610
63174
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -62615,7 +63179,7 @@ async function executeDub(options) {
62615
63179
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
62616
63180
  let audioPath = absPath;
62617
63181
  if (isVideo) {
62618
- const tempAudioPath = resolve72(dirname23(absPath), `temp-audio-${Date.now()}.mp3`);
63182
+ const tempAudioPath = resolve73(dirname24(absPath), `temp-audio-${Date.now()}.mp3`);
62619
63183
  execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
62620
63184
  audioPath = tempAudioPath;
62621
63185
  }
@@ -62664,7 +63228,7 @@ ${segmentTexts}`,
62664
63228
  }
62665
63229
  if (analyzeOnly) {
62666
63230
  if (output) {
62667
- const timingPath = resolve72(process.cwd(), output);
63231
+ const timingPath = resolve73(process.cwd(), output);
62668
63232
  const timingData = {
62669
63233
  sourcePath: absPath,
62670
63234
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
@@ -62676,14 +63240,14 @@ ${segmentTexts}`,
62676
63240
  translated: translatedSegments[i]?.text || s.text
62677
63241
  }))
62678
63242
  };
62679
- await writeFile46(timingPath, JSON.stringify(timingData, null, 2));
63243
+ await writeFile47(timingPath, JSON.stringify(timingData, null, 2));
62680
63244
  }
62681
63245
  return {
62682
63246
  success: true,
62683
63247
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
62684
63248
  targetLanguage: language,
62685
63249
  segmentCount: segments.length,
62686
- outputPath: output ? resolve72(process.cwd(), output) : void 0
63250
+ outputPath: output ? resolve73(process.cwd(), output) : void 0
62687
63251
  };
62688
63252
  }
62689
63253
  const elevenlabs = new ElevenLabsProvider();
@@ -62697,9 +63261,9 @@ ${segmentTexts}`,
62697
63261
  }
62698
63262
  const combinedBuffer = Buffer.concat(dubbedBuffers);
62699
63263
  const outputExt = isVideo ? ".mp3" : extname14(absPath);
62700
- const defaultOutputPath2 = resolve72(dirname23(absPath), `${basename22(absPath, extname14(absPath))}-${language}${outputExt}`);
62701
- const finalOutputPath = resolve72(process.cwd(), output || defaultOutputPath2);
62702
- await writeFile46(finalOutputPath, combinedBuffer);
63264
+ const defaultOutputPath2 = resolve73(dirname24(absPath), `${basename22(absPath, extname14(absPath))}-${language}${outputExt}`);
63265
+ const finalOutputPath = resolve73(process.cwd(), output || defaultOutputPath2);
63266
+ await writeFile47(finalOutputPath, combinedBuffer);
62703
63267
  if (isVideo && audioPath !== absPath) {
62704
63268
  try {
62705
63269
  const { unlink: unlink3 } = await import("node:fs/promises");
@@ -62730,12 +63294,12 @@ async function executeDuck(options) {
62730
63294
  } = options;
62731
63295
  try {
62732
63296
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
62733
- const absMusicPath = resolve72(process.cwd(), musicPath);
62734
- const absVoicePath = resolve72(process.cwd(), voicePath);
62735
- if (!existsSync55(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
62736
- if (!existsSync55(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
62737
- const defaultOutput = resolve72(dirname23(absMusicPath), `${basename22(absMusicPath, extname14(absMusicPath))}-ducked${extname14(absMusicPath)}`);
62738
- const outputPath = resolve72(process.cwd(), output || defaultOutput);
63297
+ const absMusicPath = resolve73(process.cwd(), musicPath);
63298
+ const absVoicePath = resolve73(process.cwd(), voicePath);
63299
+ if (!existsSync56(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
63300
+ if (!existsSync56(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
63301
+ const defaultOutput = resolve73(dirname24(absMusicPath), `${basename22(absMusicPath, extname14(absMusicPath))}-ducked${extname14(absMusicPath)}`);
63302
+ const outputPath = resolve73(process.cwd(), output || defaultOutput);
62739
63303
  const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
62740
63304
  await execSafe("ffmpeg", [
62741
63305
  "-i",
@@ -62747,7 +63311,7 @@ async function executeDuck(options) {
62747
63311
  "-y",
62748
63312
  outputPath
62749
63313
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
62750
- if (!existsSync55(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
63314
+ if (!existsSync56(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
62751
63315
  return { success: true, outputPath };
62752
63316
  } catch (error) {
62753
63317
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -62763,9 +63327,9 @@ var init_ai_audio = __esm({
62763
63327
  });
62764
63328
 
62765
63329
  // src/pipeline/executor.ts
62766
- import { resolve as resolve73 } from "node:path";
62767
- import { readFile as readFile47, writeFile as writeFile47, mkdir as mkdir27 } from "node:fs/promises";
62768
- import { existsSync as existsSync56 } from "node:fs";
63330
+ import { resolve as resolve74 } from "node:path";
63331
+ import { readFile as readFile47, writeFile as writeFile48, mkdir as mkdir28 } from "node:fs/promises";
63332
+ import { existsSync as existsSync57 } from "node:fs";
62769
63333
  function maxCostFor(action) {
62770
63334
  const cmd = ACTION_METADATA[action]?.command;
62771
63335
  if (!cmd) return 0;
@@ -62776,9 +63340,9 @@ function registerAction(action, handler) {
62776
63340
  }
62777
63341
  function getOutput(params, outputDir, defaultName) {
62778
63342
  if (params.output && typeof params.output === "string") {
62779
- return resolve73(outputDir, params.output);
63343
+ return resolve74(outputDir, params.output);
62780
63344
  }
62781
- return resolve73(outputDir, defaultName);
63345
+ return resolve74(outputDir, defaultName);
62782
63346
  }
62783
63347
  function stringParam(value) {
62784
63348
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
@@ -63008,7 +63572,7 @@ async function ensureActionsRegistered() {
63008
63572
  const { executeSceneBuild: executeSceneBuild2 } = await Promise.resolve().then(() => (init_scene_build(), scene_build_exports));
63009
63573
  const projectRel = params.project ?? ".";
63010
63574
  const r = await executeSceneBuild2({
63011
- projectDir: resolve73(outputDir, projectRel),
63575
+ projectDir: resolve74(outputDir, projectRel),
63012
63576
  mode: params.mode,
63013
63577
  composer: params.composer,
63014
63578
  skipNarration: params.skipNarration,
@@ -63018,6 +63582,7 @@ async function ensureActionsRegistered() {
63018
63582
  voice: params.voice,
63019
63583
  imageProvider: params.imageProvider,
63020
63584
  imageQuality: params.quality,
63585
+ imageModel: params.imageModel,
63021
63586
  force: params.force
63022
63587
  });
63023
63588
  return {
@@ -63033,7 +63598,7 @@ async function ensureActionsRegistered() {
63033
63598
  const { executeSceneRender: executeSceneRender2 } = await Promise.resolve().then(() => (init_scene_render(), scene_render_exports));
63034
63599
  const projectRel = params.project ?? ".";
63035
63600
  const r = await executeSceneRender2({
63036
- projectDir: resolve73(outputDir, projectRel),
63601
+ projectDir: resolve74(outputDir, projectRel),
63037
63602
  root: params.root,
63038
63603
  output: params.output,
63039
63604
  fps: params.fps,
@@ -63057,8 +63622,8 @@ async function ensureActionsRegistered() {
63057
63622
  });
63058
63623
  }
63059
63624
  async function loadPipeline(filePath) {
63060
- const absPath = resolve73(process.cwd(), filePath);
63061
- if (!existsSync56(absPath)) {
63625
+ const absPath = resolve74(process.cwd(), filePath);
63626
+ if (!existsSync57(absPath)) {
63062
63627
  throw new Error(`Pipeline file not found: ${absPath}`);
63063
63628
  }
63064
63629
  const content = await readFile47(absPath, "utf-8");
@@ -63076,14 +63641,14 @@ async function loadPipeline(filePath) {
63076
63641
  }
63077
63642
  async function executePipeline(manifest2, options = {}) {
63078
63643
  await ensureActionsRegistered();
63079
- const outputDir = resolve73(process.cwd(), options.outputDir || `${manifest2.name}-output`);
63080
- await mkdir27(outputDir, { recursive: true });
63644
+ const outputDir = resolve74(process.cwd(), options.outputDir || `${manifest2.name}-output`);
63645
+ await mkdir28(outputDir, { recursive: true });
63081
63646
  const completedSteps = /* @__PURE__ */ new Map();
63082
63647
  const results = [];
63083
63648
  const startTime = Date.now();
63084
63649
  if (options.resume) {
63085
- const checkpointPath = resolve73(outputDir, CHECKPOINT_FILE);
63086
- if (existsSync56(checkpointPath)) {
63650
+ const checkpointPath = resolve74(outputDir, CHECKPOINT_FILE);
63651
+ if (existsSync57(checkpointPath)) {
63087
63652
  const checkpointContent = await readFile47(checkpointPath, "utf-8");
63088
63653
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
63089
63654
  for (const cs of checkpoint.completedSteps) {
@@ -63202,8 +63767,8 @@ async function executePipeline(manifest2, options = {}) {
63202
63767
  data: s.data
63203
63768
  }))
63204
63769
  };
63205
- await writeFile47(
63206
- resolve73(outputDir, CHECKPOINT_FILE),
63770
+ await writeFile48(
63771
+ resolve74(outputDir, CHECKPOINT_FILE),
63207
63772
  (0, import_yaml8.stringify)(checkpoint, { indent: 2 }),
63208
63773
  "utf-8"
63209
63774
  );
@@ -67671,7 +68236,7 @@ var init_elicit = __esm({
67671
68236
  });
67672
68237
 
67673
68238
  // src/tools/manifest/scene.ts
67674
- import { resolve as resolve74, relative as relative11 } from "node:path";
68239
+ import { resolve as resolve75, relative as relative11 } from "node:path";
67675
68240
  function summariseLint(result) {
67676
68241
  return {
67677
68242
  ok: result.ok,
@@ -67906,7 +68471,7 @@ var init_scene2 = __esm({
67906
68471
  description: "Scaffold a new VibeFrame video scene project. Supports minimal, agent, and full profiles; full includes the current HTML render backend metadata. Idempotent: re-running keeps user-authored files and merges backend config instead of overwriting. No API keys required.",
67907
68472
  schema: sceneInitSchema,
67908
68473
  async execute(args, ctx) {
67909
- const dir = resolve74(ctx.workingDirectory, args.dir);
68474
+ const dir = resolve75(ctx.workingDirectory, args.dir);
67910
68475
  const result = await scaffoldSceneProject({
67911
68476
  dir,
67912
68477
  name: args.name,
@@ -67961,7 +68526,7 @@ var init_scene2 = __esm({
67961
68526
  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.",
67962
68527
  schema: sceneAddSchema,
67963
68528
  async execute(args, ctx) {
67964
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68529
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
67965
68530
  const result = await executeSceneAdd({
67966
68531
  name: args.name,
67967
68532
  preset: args.preset ?? "simple",
@@ -68027,7 +68592,7 @@ var init_scene2 = __esm({
68027
68592
  description: "Validate every scene file in a project against the public Hyperframes lint rules (in-process, no Chrome required). Returns errors, warnings, and info findings per file. Optional fix:true mechanically repairs `timed_element_missing_clip_class` only \u2014 other issues surface with fixHints.",
68028
68593
  schema: sceneLintSchema,
68029
68594
  async execute(args, ctx) {
68030
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68595
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68031
68596
  const result = await runProjectLint({
68032
68597
  projectDir,
68033
68598
  rootRel: args.root,
@@ -68066,7 +68631,7 @@ var init_scene2 = __esm({
68066
68631
  description: "Apply deterministic mechanical scene repairs. Currently uses the safe lint auto-fix allow-list and never performs semantic creative rewrites.",
68067
68632
  schema: sceneRepairSchema,
68068
68633
  async execute(args, ctx) {
68069
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68634
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68070
68635
  const result = await executeSceneRepair({
68071
68636
  projectDir,
68072
68637
  rootRel: args.root,
@@ -68104,7 +68669,7 @@ var init_scene2 = __esm({
68104
68669
  description: "Render a scene project to MP4/WebM/MOV via the Hyperframes producer. Requires Chrome installed locally. Output defaults to renders/<projectName>-<isoStamp>.<format>. Long runs: over MCP, calls exceeding ~45s return immediately with { promoted: true, jobId, status: 'running' } \u2014 poll status_job every 15-30s until completed/failed instead of re-invoking render. Emits MCP progress notifications when the client supplies a progressToken.",
68105
68670
  schema: sceneRenderSchema,
68106
68671
  async execute(args, ctx) {
68107
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68672
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68108
68673
  const runRender = (report) => executeSceneRender({
68109
68674
  projectDir,
68110
68675
  root: args.root,
@@ -68157,6 +68722,9 @@ var init_scene2 = __esm({
68157
68722
  videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
68158
68723
  musicProvider: external_exports.enum(["elevenlabs", "replicate"]).optional().describe("Music provider for per-beat music cues. Default elevenlabs."),
68159
68724
  imageQuality: external_exports.enum(["standard", "hd"]).optional().describe("OpenAI image quality. Default 'standard'."),
68725
+ imageModel: external_exports.string().optional().describe(
68726
+ "Image model for keyframes/backdrops/character sheets (gemini: flash|pro, openai: gpt-image-2). Provider default when omitted."
68727
+ ),
68160
68728
  imageSize: external_exports.enum(["1024x1024", "1536x1024", "1024x1536"]).optional().describe("OpenAI image size. Default '1536x1024' (cinematic 16:9-ish)."),
68161
68729
  maxCostUsd: external_exports.number().optional().describe("Fail before provider spend when estimated cost exceeds this USD cap."),
68162
68730
  force: external_exports.boolean().optional().describe("Re-dispatch primitives even when cached assets exist.")
@@ -68193,7 +68761,7 @@ var init_scene2 = __esm({
68193
68761
  }
68194
68762
  }
68195
68763
  }
68196
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68764
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68197
68765
  const runBuild = (report) => executeSceneBuild({
68198
68766
  projectDir,
68199
68767
  stage: args.stage,
@@ -68211,6 +68779,7 @@ var init_scene2 = __esm({
68211
68779
  videoProvider: args.videoProvider,
68212
68780
  musicProvider: args.musicProvider,
68213
68781
  imageQuality: args.imageQuality,
68782
+ imageModel: args.imageModel,
68214
68783
  imageSize: args.imageSize,
68215
68784
  maxCostUsd: args.maxCostUsd,
68216
68785
  force: args.force,
@@ -68249,7 +68818,7 @@ var init_scene2 = __esm({
68249
68818
  description: "Install the Hyperframes composition rules into a scene project by running upstream's installer (`npx skills add heygen-com/hyperframes`). Lands at .agents/skills/hyperframes/ and is picked up by ~19 agents, with symlinks for the ones that want their own directory. Skips when the rules are already available in the project or globally unless force is set. Needs network access. Build and render do not read these files - they exist so you can read the rules while authoring scene HTML.",
68250
68819
  schema: sceneInstallSkillSchema,
68251
68820
  async execute(args, ctx) {
68252
- const projectDir = resolve74(ctx.workingDirectory, args.projectDir);
68821
+ const projectDir = resolve75(ctx.workingDirectory, args.projectDir);
68253
68822
  const result = await installHyperframesSkill({
68254
68823
  projectDir,
68255
68824
  global: args.global ?? false,
@@ -68294,7 +68863,7 @@ var init_scene2 = __esm({
68294
68863
  description: "Emit the per-beat compose plan for the host agent to author scene HTML itself. Reads STORYBOARD.md + DESIGN.md and returns each beat's outputPath + userPrompt + cues + body, plus the path to the Hyperframes rules (skillReference) and DESIGN.md (visual identity). The host agent writes each compositions/scene-<id>.html file directly \u2014 VibeFrame makes NO LLM call here, and there is no internal-LLM fallback: authoring is always the host agent's job. Hosts that cannot write files (e.g. Claude Desktop) submit each authored beat with scene_submit instead. Pairs with scene_install_skill, which fetches the rules from upstream when the agent does not already have them.",
68295
68864
  schema: sceneComposePromptsSchema,
68296
68865
  async execute(args, ctx) {
68297
- const projectDir = resolve74(ctx.workingDirectory, args.projectDir);
68866
+ const projectDir = resolve75(ctx.workingDirectory, args.projectDir);
68298
68867
  const result = await getComposePrompts({
68299
68868
  projectDir,
68300
68869
  beatId: args.beat
@@ -68338,7 +68907,7 @@ var init_scene2 = __esm({
68338
68907
  description: "Submit host-authored Hyperframes scene HTML for one beat. Validates with the same lint as the batch composer and writes compositions/scene-<id>.html on pass; on lint errors it returns the findings WITHOUT writing so you can fix and resubmit. This completes agent-mode compose for hosts that cannot write files (e.g. Claude Desktop): build (mode agent) \u2192 needs-author plan \u2192 author each beat from its composePrompts userPrompt \u2192 scene_submit per beat \u2192 build (stage sync) \u2192 render. No internal LLM call \u2014 the submitting agent is the composer.",
68339
68908
  schema: sceneSubmitSchema,
68340
68909
  async execute(args, ctx) {
68341
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68910
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68342
68911
  const result = await executeSceneSubmit({
68343
68912
  projectDir,
68344
68913
  beatId: args.beat,
@@ -69001,13 +69570,13 @@ var init_edit = __esm({
69001
69570
  });
69002
69571
 
69003
69572
  // src/commands/ai-suggest-edit.ts
69004
- import { readFile as readFile48, writeFile as writeFile48 } from "node:fs/promises";
69005
- import { resolve as resolve75 } from "node:path";
69573
+ import { readFile as readFile48, writeFile as writeFile49 } from "node:fs/promises";
69574
+ import { resolve as resolve76 } from "node:path";
69006
69575
  async function executeSuggestEdit(options) {
69007
69576
  try {
69008
69577
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
69009
69578
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
69010
- const filePath = resolve75(process.cwd(), options.projectPath);
69579
+ const filePath = resolve76(process.cwd(), options.projectPath);
69011
69580
  const content = await readFile48(filePath, "utf-8");
69012
69581
  const data = JSON.parse(content);
69013
69582
  const project = Project.fromJSON(data);
@@ -69019,7 +69588,7 @@ async function executeSuggestEdit(options) {
69019
69588
  const first = suggestions[0];
69020
69589
  const applied = applySuggestion(project, first);
69021
69590
  if (applied) {
69022
- await writeFile48(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
69591
+ await writeFile49(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
69023
69592
  return {
69024
69593
  success: true,
69025
69594
  suggestions,
@@ -69048,7 +69617,7 @@ var init_ai_suggest_edit = __esm({
69048
69617
  });
69049
69618
 
69050
69619
  // src/tools/manifest/inspect.ts
69051
- import { resolve as resolve76 } from "node:path";
69620
+ import { resolve as resolve77 } from "node:path";
69052
69621
  var PROJECT_DIR_DESCRIPTION2, inspectProjectTool, inspectRenderTool, analyzeMediaTool, analyzeSuggestTool, inspectTools;
69053
69622
  var init_inspect = __esm({
69054
69623
  "src/tools/manifest/inspect.ts"() {
@@ -69074,11 +69643,11 @@ var init_inspect = __esm({
69074
69643
  report: external_exports.boolean().optional().describe("Write review-report.json. Default true.")
69075
69644
  }),
69076
69645
  async execute(args, ctx) {
69077
- const projectDir = args.projectDir ? resolve76(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69646
+ const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69078
69647
  const result = await inspectProject({
69079
69648
  projectDir,
69080
69649
  beatId: args.beat,
69081
- outputPath: args.outputPath ? resolve76(ctx.workingDirectory, args.outputPath) : void 0,
69650
+ outputPath: args.outputPath ? resolve77(ctx.workingDirectory, args.outputPath) : void 0,
69082
69651
  writeReport: args.report !== false
69083
69652
  });
69084
69653
  return {
@@ -69114,13 +69683,13 @@ var init_inspect = __esm({
69114
69683
  dryRun: external_exports.boolean().optional().describe("Preview resolved inputs without probing video or calling Gemini.")
69115
69684
  }),
69116
69685
  async execute(args, ctx) {
69117
- const projectDir = args.projectDir ? resolve76(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69686
+ const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69118
69687
  if (args.dryRun) {
69119
69688
  const result2 = await previewInspectRender({
69120
69689
  projectDir,
69121
69690
  beatId: args.beat,
69122
- videoPath: args.videoPath ? resolve76(ctx.workingDirectory, args.videoPath) : void 0,
69123
- outputPath: args.outputPath ? resolve76(ctx.workingDirectory, args.outputPath) : void 0,
69691
+ videoPath: args.videoPath ? resolve77(ctx.workingDirectory, args.videoPath) : void 0,
69692
+ outputPath: args.outputPath ? resolve77(ctx.workingDirectory, args.outputPath) : void 0,
69124
69693
  writeReport: args.report !== false,
69125
69694
  ai: args.ai === true,
69126
69695
  model: args.model
@@ -69141,8 +69710,8 @@ var init_inspect = __esm({
69141
69710
  const result = await inspectRender({
69142
69711
  projectDir,
69143
69712
  beatId: args.beat,
69144
- videoPath: args.videoPath ? resolve76(ctx.workingDirectory, args.videoPath) : void 0,
69145
- outputPath: args.outputPath ? resolve76(ctx.workingDirectory, args.outputPath) : void 0,
69713
+ videoPath: args.videoPath ? resolve77(ctx.workingDirectory, args.videoPath) : void 0,
69714
+ outputPath: args.outputPath ? resolve77(ctx.workingDirectory, args.outputPath) : void 0,
69146
69715
  writeReport: args.report !== false,
69147
69716
  ai: args.ai === true,
69148
69717
  model: args.model
@@ -69575,12 +70144,12 @@ var init_generate2 = __esm({
69575
70144
  });
69576
70145
 
69577
70146
  // src/tools/manifest/storyboard.ts
69578
- import { existsSync as existsSync57 } from "node:fs";
69579
- import { readFile as readFile49, writeFile as writeFile49 } from "node:fs/promises";
69580
- import { resolve as resolve77 } from "node:path";
70147
+ import { existsSync as existsSync58 } from "node:fs";
70148
+ import { readFile as readFile49, writeFile as writeFile50 } from "node:fs/promises";
70149
+ import { resolve as resolve78 } from "node:path";
69581
70150
  async function readStoryboard2(projectDir) {
69582
- const path2 = resolve77(projectDir, "STORYBOARD.md");
69583
- if (!existsSync57(path2)) return null;
70151
+ const path2 = resolve78(projectDir, "STORYBOARD.md");
70152
+ if (!existsSync58(path2)) return null;
69584
70153
  return { path: path2, markdown: await readFile49(path2, "utf-8") };
69585
70154
  }
69586
70155
  var PROJECT_DIR_DESCRIPTION3, projectDirSchema, storyboardListTool, storyboardValidateTool, storyboardGetTool, storyboardSetTool, storyboardMoveTool, storyboardReviseTool, planTool, storyboardTools;
@@ -69606,7 +70175,7 @@ var init_storyboard2 = __esm({
69606
70175
  description: "List beats, ids, durations, and cue blocks from a project's STORYBOARD.md.",
69607
70176
  schema: projectDirSchema,
69608
70177
  async execute(args, ctx) {
69609
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70178
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69610
70179
  const file = await readStoryboard2(projectDir);
69611
70180
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69612
70181
  const parsed = parseStoryboard(file.markdown);
@@ -69634,7 +70203,7 @@ var init_storyboard2 = __esm({
69634
70203
  description: "Validate STORYBOARD.md beat ids and cue blocks.",
69635
70204
  schema: projectDirSchema,
69636
70205
  async execute(args, ctx) {
69637
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70206
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69638
70207
  const file = await readStoryboard2(projectDir);
69639
70208
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69640
70209
  const result = validateStoryboardMarkdown(file.markdown);
@@ -69669,7 +70238,7 @@ var init_storyboard2 = __esm({
69669
70238
  beat: external_exports.string().describe("Beat id to return.")
69670
70239
  }),
69671
70240
  async execute(args, ctx) {
69672
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70241
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69673
70242
  const file = await readStoryboard2(projectDir);
69674
70243
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69675
70244
  const beat = getStoryboardBeat(file.markdown, args.beat);
@@ -69697,7 +70266,7 @@ var init_storyboard2 = __esm({
69697
70266
  unset: external_exports.boolean().optional().describe("Remove the cue key.")
69698
70267
  }),
69699
70268
  async execute(args, ctx) {
69700
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70269
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69701
70270
  const file = await readStoryboard2(projectDir);
69702
70271
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69703
70272
  try {
@@ -69708,7 +70277,7 @@ var init_storyboard2 = __esm({
69708
70277
  value,
69709
70278
  unset: args.unset
69710
70279
  });
69711
- await writeFile49(file.path, next, "utf-8");
70280
+ await writeFile50(file.path, next, "utf-8");
69712
70281
  return {
69713
70282
  success: true,
69714
70283
  data: { beat: args.beat, key: args.key },
@@ -69732,7 +70301,7 @@ var init_storyboard2 = __esm({
69732
70301
  after: external_exports.string().describe("Beat id that should precede the moved beat.")
69733
70302
  }),
69734
70303
  async execute(args, ctx) {
69735
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70304
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69736
70305
  const file = await readStoryboard2(projectDir);
69737
70306
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69738
70307
  try {
@@ -69740,7 +70309,7 @@ var init_storyboard2 = __esm({
69740
70309
  beatId: args.beat,
69741
70310
  afterBeatId: args.after
69742
70311
  });
69743
- await writeFile49(file.path, next, "utf-8");
70312
+ await writeFile50(file.path, next, "utf-8");
69744
70313
  return {
69745
70314
  success: true,
69746
70315
  data: { beat: args.beat, after: args.after },
@@ -69766,7 +70335,7 @@ var init_storyboard2 = __esm({
69766
70335
  dryRun: external_exports.boolean().optional().describe("Preview the revised storyboard without writing.")
69767
70336
  }),
69768
70337
  async execute(args, ctx) {
69769
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70338
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69770
70339
  const result = await executeStoryboardRevision({
69771
70340
  projectDir,
69772
70341
  request: args.request,
@@ -69811,7 +70380,7 @@ var init_storyboard2 = __esm({
69811
70380
  force: external_exports.boolean().optional().describe("Plan regeneration even when outputs exist.")
69812
70381
  }),
69813
70382
  async execute(args, ctx) {
69814
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70383
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69815
70384
  const plan = await createBuildPlan({
69816
70385
  projectDir,
69817
70386
  stage: args.stage,
@@ -69854,9 +70423,9 @@ var init_storyboard2 = __esm({
69854
70423
  });
69855
70424
 
69856
70425
  // src/tools/manifest/design.ts
69857
- import { existsSync as existsSync58 } from "node:fs";
70426
+ import { existsSync as existsSync59 } from "node:fs";
69858
70427
  import { readFile as readFile50 } from "node:fs/promises";
69859
- import { resolve as resolve78, join as join29 } from "node:path";
70428
+ import { resolve as resolve79, join as join30 } from "node:path";
69860
70429
  var PROJECT_DIR_DESCRIPTION4, projectDirSchema2, designValidateTool, designTools;
69861
70430
  var init_design = __esm({
69862
70431
  "src/tools/manifest/design.ts"() {
@@ -69877,9 +70446,9 @@ var init_design = __esm({
69877
70446
  description: "Validate DESIGN.md front-matter color/typography tokens and sections (google-labs design.md format).",
69878
70447
  schema: projectDirSchema2,
69879
70448
  async execute(args, ctx) {
69880
- const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69881
- const path2 = join29(projectDir, "DESIGN.md");
69882
- if (!existsSync58(path2)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
70449
+ const projectDir = args.projectDir ? resolve79(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70450
+ const path2 = join30(projectDir, "DESIGN.md");
70451
+ if (!existsSync59(path2)) return { success: false, error: `DESIGN.md not found in ${projectDir}` };
69883
70452
  const md = await readFile50(path2, "utf-8");
69884
70453
  const issues = validateDesignMarkdown(md);
69885
70454
  const design = parseDesign(md);
@@ -69903,9 +70472,9 @@ var init_design = __esm({
69903
70472
  });
69904
70473
 
69905
70474
  // src/tools/manifest/remix.ts
69906
- import { writeFile as writeFile50 } from "node:fs/promises";
70475
+ import { writeFile as writeFile51 } from "node:fs/promises";
69907
70476
  import { tmpdir as tmpdir4 } from "node:os";
69908
- import { join as join30 } from "node:path";
70477
+ import { join as join31 } from "node:path";
69909
70478
  var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, remixTools;
69910
70479
  var init_remix = __esm({
69911
70480
  "src/tools/manifest/remix.ts"() {
@@ -70018,8 +70587,8 @@ var init_remix = __esm({
70018
70587
  let resolvedPath = args.pipelinePath;
70019
70588
  let tempPath;
70020
70589
  if (args.pipelineYaml) {
70021
- tempPath = join30(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
70022
- await writeFile50(tempPath, args.pipelineYaml, "utf-8");
70590
+ tempPath = join31(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
70591
+ await writeFile51(tempPath, args.pipelineYaml, "utf-8");
70023
70592
  resolvedPath = tempPath;
70024
70593
  }
70025
70594
  try {
@@ -70164,7 +70733,7 @@ var init_detect2 = __esm({
70164
70733
  });
70165
70734
 
70166
70735
  // src/tools/manifest/_project-io.ts
70167
- import { readFile as readFile51, writeFile as writeFile51 } from "node:fs/promises";
70736
+ import { readFile as readFile51, writeFile as writeFile52 } from "node:fs/promises";
70168
70737
  async function loadProject2(projectPath, cwd) {
70169
70738
  const absPath = await resolveTimelineFile(projectPath, cwd);
70170
70739
  const content = await readFile51(absPath, "utf-8");
@@ -70172,7 +70741,7 @@ async function loadProject2(projectPath, cwd) {
70172
70741
  return { project: Project.fromJSON(data), absPath };
70173
70742
  }
70174
70743
  async function saveProject(absPath, project) {
70175
- await writeFile51(absPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70744
+ await writeFile52(absPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70176
70745
  }
70177
70746
  var init_project_io = __esm({
70178
70747
  "src/tools/manifest/_project-io.ts"() {
@@ -70183,8 +70752,8 @@ var init_project_io = __esm({
70183
70752
  });
70184
70753
 
70185
70754
  // src/tools/manifest/timeline.ts
70186
- import { mkdir as mkdir28, writeFile as writeFile52 } from "node:fs/promises";
70187
- import { basename as basename23, resolve as resolve79 } from "node:path";
70755
+ import { mkdir as mkdir29, writeFile as writeFile53 } from "node:fs/promises";
70756
+ import { basename as basename23, resolve as resolve80 } from "node:path";
70188
70757
  var MEDIA_TYPES, timelineCreateTool, timelineInfoTool, timelineAddSourceTool, timelineAddClipTool, timelineSplitClipTool, timelineTrimClipTool, timelineMoveClipTool, timelineDeleteClipTool, timelineDuplicateClipTool, timelineAddEffectTool, timelineAddTrackTool, timelineListTool, timelineTools;
70189
70758
  var init_timeline = __esm({
70190
70759
  "src/tools/manifest/timeline.ts"() {
@@ -70222,18 +70791,18 @@ var init_timeline = __esm({
70222
70791
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
70223
70792
  }),
70224
70793
  async execute(args, ctx) {
70225
- const projectName = args.name === "." ? basename23(ctx.workingDirectory) : basename23(resolve79(ctx.workingDirectory, args.name));
70794
+ const projectName = args.name === "." ? basename23(ctx.workingDirectory) : basename23(resolve80(ctx.workingDirectory, args.name));
70226
70795
  const project = new Project(projectName);
70227
70796
  if (args.fps) project.setFrameRate(args.fps);
70228
70797
  let outputPath;
70229
70798
  if (args.outputPath) {
70230
- outputPath = resolve79(ctx.workingDirectory, args.outputPath);
70799
+ outputPath = resolve80(ctx.workingDirectory, args.outputPath);
70231
70800
  } else {
70232
- const dirPath = resolve79(ctx.workingDirectory, args.name);
70233
- await mkdir28(dirPath, { recursive: true });
70234
- outputPath = resolve79(dirPath, TIMELINE_FILENAME);
70801
+ const dirPath = resolve80(ctx.workingDirectory, args.name);
70802
+ await mkdir29(dirPath, { recursive: true });
70803
+ outputPath = resolve80(dirPath, TIMELINE_FILENAME);
70235
70804
  }
70236
- await writeFile52(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70805
+ await writeFile53(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70237
70806
  return {
70238
70807
  success: true,
70239
70808
  data: { name: projectName, outputPath },
@@ -70285,7 +70854,7 @@ var init_timeline = __esm({
70285
70854
  }),
70286
70855
  async execute(args, ctx) {
70287
70856
  const { project, absPath } = await loadProject2(args.projectPath, ctx.workingDirectory);
70288
- const mediaPath = resolve79(ctx.workingDirectory, args.mediaPath);
70857
+ const mediaPath = resolve80(ctx.workingDirectory, args.mediaPath);
70289
70858
  const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
70290
70859
  const source = project.addSource({
70291
70860
  name: args.name ?? mediaPath.split("/").pop() ?? "media",
@@ -70532,11 +71101,11 @@ var init_timeline = __esm({
70532
71101
 
70533
71102
  // src/commands/_shared/project-list.ts
70534
71103
  import { readdir as readdir8, readFile as readFile52, stat as stat9 } from "node:fs/promises";
70535
- import { existsSync as existsSync59 } from "node:fs";
70536
- import { join as join31, resolve as resolve80 } from "node:path";
71104
+ import { existsSync as existsSync60 } from "node:fs";
71105
+ import { join as join32, resolve as resolve81 } from "node:path";
70537
71106
  async function executeProjectList(opts = {}) {
70538
- const workspaceDir = resolve80(opts.workspaceDir ?? ".");
70539
- if (!existsSync59(workspaceDir)) {
71107
+ const workspaceDir = resolve81(opts.workspaceDir ?? ".");
71108
+ if (!existsSync60(workspaceDir)) {
70540
71109
  return {
70541
71110
  success: false,
70542
71111
  workspaceDir,
@@ -70553,11 +71122,11 @@ async function executeProjectList(opts = {}) {
70553
71122
  if (entry.name === "node_modules") continue;
70554
71123
  if (entry.name.startsWith(".")) continue;
70555
71124
  if (entry.name.startsWith("_")) {
70556
- archivedCount += await countProjects(join31(workspaceDir, entry.name));
71125
+ archivedCount += await countProjects(join32(workspaceDir, entry.name));
70557
71126
  continue;
70558
71127
  }
70559
- const projectDir = join31(workspaceDir, entry.name);
70560
- if (!existsSync59(join31(projectDir, "STORYBOARD.md"))) continue;
71128
+ const projectDir = join32(workspaceDir, entry.name);
71129
+ if (!existsSync60(join32(projectDir, "STORYBOARD.md"))) continue;
70561
71130
  projects.push(await describeProject(entry.name, projectDir));
70562
71131
  }
70563
71132
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -70567,7 +71136,7 @@ async function countProjects(dir) {
70567
71136
  try {
70568
71137
  const entries = await readdir8(dir, { withFileTypes: true });
70569
71138
  return entries.filter(
70570
- (e) => e.isDirectory() && existsSync59(join31(dir, e.name, "STORYBOARD.md"))
71139
+ (e) => e.isDirectory() && existsSync60(join32(dir, e.name, "STORYBOARD.md"))
70571
71140
  ).length;
70572
71141
  } catch {
70573
71142
  return 0;
@@ -70578,7 +71147,7 @@ async function describeProject(name, projectDir) {
70578
71147
  let storyboardDurationSec = 0;
70579
71148
  const timestamps = [];
70580
71149
  try {
70581
- const storyboardPath2 = join31(projectDir, "STORYBOARD.md");
71150
+ const storyboardPath2 = join32(projectDir, "STORYBOARD.md");
70582
71151
  const parsed = parseStoryboard(await loadStoryboardMarkdown(projectDir));
70583
71152
  beats = parsed.beats.length;
70584
71153
  storyboardDurationSec = Number(
@@ -70590,7 +71159,7 @@ async function describeProject(name, projectDir) {
70590
71159
  let buildStatus;
70591
71160
  let buildUpdatedAt;
70592
71161
  try {
70593
- const reportPath = join31(projectDir, "build-report.json");
71162
+ const reportPath = join32(projectDir, "build-report.json");
70594
71163
  const report = JSON.parse(await readFile52(reportPath, "utf-8"));
70595
71164
  buildStatus = report.status ?? report.phase;
70596
71165
  buildUpdatedAt = report.updatedAt;
@@ -70599,12 +71168,12 @@ async function describeProject(name, projectDir) {
70599
71168
  }
70600
71169
  let latestRender;
70601
71170
  try {
70602
- const rendersDir = join31(projectDir, "renders");
71171
+ const rendersDir = join32(projectDir, "renders");
70603
71172
  const files = await readdir8(rendersDir);
70604
71173
  let newest;
70605
71174
  for (const file of files) {
70606
71175
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
70607
- const { mtimeMs } = await stat9(join31(rendersDir, file));
71176
+ const { mtimeMs } = await stat9(join32(rendersDir, file));
70608
71177
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
70609
71178
  }
70610
71179
  if (newest) {
@@ -70636,8 +71205,8 @@ var init_project_list = __esm({
70636
71205
  });
70637
71206
 
70638
71207
  // src/tools/manifest/project.ts
70639
- import { resolve as resolve81 } from "node:path";
70640
- import { writeFile as writeFile53 } from "node:fs/promises";
71208
+ import { resolve as resolve82 } from "node:path";
71209
+ import { writeFile as writeFile54 } from "node:fs/promises";
70641
71210
  var projectListTool, projectCreateTool, projectInfoTool, projectTools;
70642
71211
  var init_project2 = __esm({
70643
71212
  "src/tools/manifest/project.ts"() {
@@ -70661,7 +71230,7 @@ var init_project2 = __esm({
70661
71230
  }),
70662
71231
  async execute(args, ctx) {
70663
71232
  const result = await executeProjectList({
70664
- workspaceDir: args.dir ? resolve81(ctx.workingDirectory, args.dir) : ctx.workingDirectory
71233
+ workspaceDir: args.dir ? resolve82(ctx.workingDirectory, args.dir) : ctx.workingDirectory
70665
71234
  });
70666
71235
  if (!result.success) {
70667
71236
  return { success: false, error: result.error ?? "project list failed" };
@@ -70698,10 +71267,10 @@ var init_project2 = __esm({
70698
71267
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
70699
71268
  }),
70700
71269
  async execute(args, ctx) {
70701
- const outputPath = resolve81(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
71270
+ const outputPath = resolve82(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
70702
71271
  const project = new Project(args.name);
70703
71272
  if (args.fps) project.setFrameRate(args.fps);
70704
- await writeFile53(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71273
+ await writeFile54(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70705
71274
  return {
70706
71275
  success: true,
70707
71276
  data: { name: args.name, outputPath },
@@ -70748,7 +71317,7 @@ var init_project2 = __esm({
70748
71317
 
70749
71318
  // src/commands/export.ts
70750
71319
  import { readFile as readFile53, access as access7 } from "node:fs/promises";
70751
- import { resolve as resolve82 } from "node:path";
71320
+ import { resolve as resolve83 } from "node:path";
70752
71321
  import { spawn as spawn3 } from "node:child_process";
70753
71322
  async function getMediaDuration(filePath, mediaType, defaultImageDuration = 5) {
70754
71323
  if (mediaType === "image") {
@@ -70820,7 +71389,7 @@ async function runExport(projectPath, outputPath, options = {}) {
70820
71389
  message: "Project has no clips to export"
70821
71390
  };
70822
71391
  }
70823
- const finalOutputPath = resolve82(process.cwd(), outputPath);
71392
+ const finalOutputPath = resolve83(process.cwd(), outputPath);
70824
71393
  const basePresetSettings = getPresetSettings(preset, summary.aspectRatio);
70825
71394
  const presetSettings = applyCustomOverrides(basePresetSettings, { bitrate, fps, resolution, codec });
70826
71395
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -71216,7 +71785,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
71216
71785
  return args;
71217
71786
  }
71218
71787
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
71219
- return new Promise((resolve86, reject) => {
71788
+ return new Promise((resolve87, reject) => {
71220
71789
  const ffmpeg = spawn3(ffmpegPath, args, {
71221
71790
  stdio: ["pipe", "pipe", "pipe"]
71222
71791
  });
@@ -71240,7 +71809,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
71240
71809
  });
71241
71810
  ffmpeg.on("close", (code) => {
71242
71811
  if (code === 0) {
71243
- resolve86();
71812
+ resolve87();
71244
71813
  } else {
71245
71814
  const errorMatch = stderr.match(/Error.*$/m);
71246
71815
  const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
@@ -71393,7 +71962,7 @@ var init_export = __esm({
71393
71962
  });
71394
71963
 
71395
71964
  // src/tools/manifest/export.ts
71396
- import { resolve as resolve83 } from "node:path";
71965
+ import { resolve as resolve84 } from "node:path";
71397
71966
  var exportVideoTool, exportTools;
71398
71967
  var init_export2 = __esm({
71399
71968
  "src/tools/manifest/export.ts"() {
@@ -71416,8 +71985,8 @@ var init_export2 = __esm({
71416
71985
  overwrite: external_exports.boolean().optional().describe("Overwrite existing output file (default: false)")
71417
71986
  }),
71418
71987
  async execute(args, ctx) {
71419
- const projectPath = resolve83(ctx.workingDirectory, args.projectPath);
71420
- const outputPath = resolve83(ctx.workingDirectory, args.outputPath);
71988
+ const projectPath = resolve84(ctx.workingDirectory, args.projectPath);
71989
+ const outputPath = resolve84(ctx.workingDirectory, args.outputPath);
71421
71990
  const result = await runExport(projectPath, outputPath, {
71422
71991
  preset: args.preset,
71423
71992
  format: args.format,
@@ -71436,8 +72005,8 @@ var init_export2 = __esm({
71436
72005
  });
71437
72006
 
71438
72007
  // src/tools/manifest/agent-only.ts
71439
- import { readFile as readFile54, writeFile as writeFile54, readdir as readdir9, stat as stat10, access as access8, unlink as unlink2 } from "node:fs/promises";
71440
- import { resolve as resolve84, join as join32, basename as basename24, extname as extname15 } from "node:path";
72008
+ import { readFile as readFile54, writeFile as writeFile55, readdir as readdir9, stat as stat10, access as access8, unlink as unlink2 } from "node:fs/promises";
72009
+ import { resolve as resolve85, join as join33, basename as basename24, extname as extname15 } from "node:path";
71441
72010
  function matchPattern(filename, pattern) {
71442
72011
  const regex2 = new RegExp(
71443
72012
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -71517,12 +72086,12 @@ var init_agent_only = __esm({
71517
72086
  async execute(args, ctx) {
71518
72087
  const dirPath = args.path ?? ".";
71519
72088
  try {
71520
- const absPath = resolve84(ctx.workingDirectory, dirPath);
72089
+ const absPath = resolve85(ctx.workingDirectory, dirPath);
71521
72090
  const entries = await readdir9(absPath, { withFileTypes: true });
71522
72091
  const results = [];
71523
72092
  for (const entry of entries) {
71524
72093
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
71525
- const fullPath = join32(absPath, entry.name);
72094
+ const fullPath = join33(absPath, entry.name);
71526
72095
  const stats = await stat10(fullPath);
71527
72096
  if (entry.isDirectory()) {
71528
72097
  results.push(`[DIR] ${entry.name}/`);
@@ -71550,7 +72119,7 @@ var init_agent_only = __esm({
71550
72119
  }),
71551
72120
  async execute(args, ctx) {
71552
72121
  try {
71553
- const absPath = resolve84(ctx.workingDirectory, args.path);
72122
+ const absPath = resolve85(ctx.workingDirectory, args.path);
71554
72123
  const content = await readFile54(absPath, "utf-8");
71555
72124
  const maxLength = 4e3;
71556
72125
  const truncated = content.length > maxLength;
@@ -71575,8 +72144,8 @@ var init_agent_only = __esm({
71575
72144
  }),
71576
72145
  async execute(args, ctx) {
71577
72146
  try {
71578
- const absPath = resolve84(ctx.workingDirectory, args.path);
71579
- await writeFile54(absPath, args.content, "utf-8");
72147
+ const absPath = resolve85(ctx.workingDirectory, args.path);
72148
+ await writeFile55(absPath, args.content, "utf-8");
71580
72149
  return { success: true, data: { bytes: args.content.length }, humanLines: [`File written: ${args.path} (${formatSize(args.content.length)})`] };
71581
72150
  } catch (error) {
71582
72151
  return { success: false, error: `Failed to write file: ${error instanceof Error ? error.message : String(error)}` };
@@ -71595,7 +72164,7 @@ var init_agent_only = __esm({
71595
72164
  path: external_exports.string().describe("Path to check")
71596
72165
  }),
71597
72166
  async execute(args, ctx) {
71598
- const absPath = resolve84(ctx.workingDirectory, args.path);
72167
+ const absPath = resolve85(ctx.workingDirectory, args.path);
71599
72168
  try {
71600
72169
  await access8(absPath);
71601
72170
  const stats = await stat10(absPath);
@@ -71623,11 +72192,11 @@ var init_agent_only = __esm({
71623
72192
  }),
71624
72193
  async execute(args, ctx) {
71625
72194
  try {
71626
- const filePath = resolve84(ctx.workingDirectory, args.project);
72195
+ const filePath = resolve85(ctx.workingDirectory, args.project);
71627
72196
  const content = await readFile54(filePath, "utf-8");
71628
72197
  const data = JSON.parse(content);
71629
72198
  const project = Project.fromJSON(data);
71630
- const dirPath = resolve84(ctx.workingDirectory, args.directory);
72199
+ const dirPath = resolve85(ctx.workingDirectory, args.directory);
71631
72200
  const filterExts = args.filter ? args.filter.split(",").map((e) => e.trim().toLowerCase()) : null;
71632
72201
  const imageDuration = args.imageDuration ?? 5;
71633
72202
  const recursive = args.recursive ?? false;
@@ -71635,7 +72204,7 @@ var init_agent_only = __esm({
71635
72204
  const scanDir = async (dir) => {
71636
72205
  const entries = await readdir9(dir, { withFileTypes: true });
71637
72206
  for (const entry of entries) {
71638
- const entryPath = join32(dir, entry.name);
72207
+ const entryPath = join33(dir, entry.name);
71639
72208
  if (entry.isDirectory() && recursive) {
71640
72209
  await scanDir(entryPath);
71641
72210
  } else if (entry.isFile()) {
@@ -71662,7 +72231,7 @@ var init_agent_only = __esm({
71662
72231
  const source = project.addSource({ name: mediaName, type: mediaType, url: mediaFile, duration });
71663
72232
  addedSources.push({ id: source.id, name: mediaName, type: mediaType });
71664
72233
  }
71665
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72234
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71666
72235
  return {
71667
72236
  success: true,
71668
72237
  data: { count: addedSources.length, sources: addedSources },
@@ -71695,7 +72264,7 @@ var init_agent_only = __esm({
71695
72264
  }),
71696
72265
  async execute(args, ctx) {
71697
72266
  try {
71698
- const filePath = resolve84(ctx.workingDirectory, args.project);
72267
+ const filePath = resolve85(ctx.workingDirectory, args.project);
71699
72268
  const content = await readFile54(filePath, "utf-8");
71700
72269
  const data = JSON.parse(content);
71701
72270
  const project = Project.fromJSON(data);
@@ -71732,7 +72301,7 @@ var init_agent_only = __esm({
71732
72301
  createdClips.push({ id: clip.id, sourceName: source.name, startTime: currentTime, duration: source.duration });
71733
72302
  currentTime += source.duration + gap;
71734
72303
  }
71735
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72304
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71736
72305
  const totalDuration = currentTime - gap - startTime;
71737
72306
  return {
71738
72307
  success: true,
@@ -71766,7 +72335,7 @@ var init_agent_only = __esm({
71766
72335
  }),
71767
72336
  async execute(args, ctx) {
71768
72337
  try {
71769
- const filePath = resolve84(ctx.workingDirectory, args.project);
72338
+ const filePath = resolve85(ctx.workingDirectory, args.project);
71770
72339
  const content = await readFile54(filePath, "utf-8");
71771
72340
  const data = JSON.parse(content);
71772
72341
  const project = Project.fromJSON(data);
@@ -71795,7 +72364,7 @@ var init_agent_only = __esm({
71795
72364
  });
71796
72365
  if (effect) appliedEffects.push({ clipId: clip.id, effectId: effect.id });
71797
72366
  }
71798
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72367
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71799
72368
  return {
71800
72369
  success: true,
71801
72370
  data: { applied: appliedEffects.length, effectType: args.effectType },
@@ -71864,7 +72433,7 @@ var init_agent_only = __esm({
71864
72433
  removed.sources++;
71865
72434
  }
71866
72435
  }
71867
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72436
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71868
72437
  const parts = [];
71869
72438
  if (removed.clips > 0) parts.push(`${removed.clips} clips`);
71870
72439
  if (removed.tracks > 0) parts.push(`${removed.tracks} tracks`);
@@ -71915,7 +72484,7 @@ var init_agent_only = __esm({
71915
72484
  project.setFrameRate(args.fps);
71916
72485
  updates.push(`Frame Rate: ${args.fps} fps`);
71917
72486
  }
71918
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72487
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71919
72488
  return {
71920
72489
  success: true,
71921
72490
  data: { updates },
@@ -72058,7 +72627,7 @@ var init_agent_only = __esm({
72058
72627
  }),
72059
72628
  async execute(args, ctx) {
72060
72629
  try {
72061
- const absPath = resolve84(ctx.workingDirectory, args.path);
72630
+ const absPath = resolve85(ctx.workingDirectory, args.path);
72062
72631
  const { stdout } = await execSafe(
72063
72632
  "ffprobe",
72064
72633
  ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", absPath],
@@ -72147,10 +72716,10 @@ Audio:`);
72147
72716
  }),
72148
72717
  async execute(args, ctx) {
72149
72718
  try {
72150
- const absInput = resolve84(ctx.workingDirectory, args.input);
72719
+ const absInput = resolve85(ctx.workingDirectory, args.input);
72151
72720
  const ext = args.input.split(".").pop() || "mp4";
72152
72721
  const baseName = args.input.replace(/\.[^/.]+$/, "");
72153
- const outputPath = args.output ? resolve84(ctx.workingDirectory, args.output) : resolve84(ctx.workingDirectory, `${baseName}-compressed.${ext}`);
72722
+ const outputPath = args.output ? resolve85(ctx.workingDirectory, args.output) : resolve85(ctx.workingDirectory, `${baseName}-compressed.${ext}`);
72154
72723
  const crfValues = { low: 28, medium: 23, high: 18 };
72155
72724
  const crf = crfValues[args.quality ?? "medium"];
72156
72725
  await execSafe(
@@ -72210,8 +72779,8 @@ Audio:`);
72210
72779
  }),
72211
72780
  async execute(args, ctx) {
72212
72781
  try {
72213
- const absInput = resolve84(ctx.workingDirectory, args.input);
72214
- const absOutput = resolve84(ctx.workingDirectory, args.output);
72782
+ const absInput = resolve85(ctx.workingDirectory, args.input);
72783
+ const absOutput = resolve85(ctx.workingDirectory, args.output);
72215
72784
  const codecMap = {
72216
72785
  h264: "libx264",
72217
72786
  h265: "libx265",
@@ -72264,11 +72833,11 @@ Audio:`);
72264
72833
  };
72265
72834
  }
72266
72835
  try {
72267
- const absOutput = resolve84(ctx.workingDirectory, args.output);
72836
+ const absOutput = resolve85(ctx.workingDirectory, args.output);
72268
72837
  if (args.reencode) {
72269
72838
  const ffmpegArgs = [];
72270
72839
  for (const i of args.inputs) {
72271
- ffmpegArgs.push("-i", resolve84(ctx.workingDirectory, i));
72840
+ ffmpegArgs.push("-i", resolve85(ctx.workingDirectory, i));
72272
72841
  }
72273
72842
  const filterComplex = args.inputs.map((_, i) => `[${i}:v][${i}:a]`).join("");
72274
72843
  ffmpegArgs.push(
@@ -72283,9 +72852,9 @@ Audio:`);
72283
72852
  );
72284
72853
  await execSafe("ffmpeg", ffmpegArgs, { maxBuffer: 100 * 1024 * 1024 });
72285
72854
  } else {
72286
- const tempList = resolve84(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
72287
- const listContent = args.inputs.map((i) => `file '${resolve84(ctx.workingDirectory, i)}'`).join("\n");
72288
- await writeFile54(tempList, listContent, "utf-8");
72855
+ const tempList = resolve85(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
72856
+ const listContent = args.inputs.map((i) => `file '${resolve85(ctx.workingDirectory, i)}'`).join("\n");
72857
+ await writeFile55(tempList, listContent, "utf-8");
72289
72858
  try {
72290
72859
  await execSafe(
72291
72860
  "ffmpeg",
@@ -72381,7 +72950,7 @@ var init_walkthrough = __esm({
72381
72950
  });
72382
72951
 
72383
72952
  // src/tools/manifest/status.ts
72384
- import { resolve as resolve85 } from "node:path";
72953
+ import { resolve as resolve86 } from "node:path";
72385
72954
  var PROJECT_DIR_DESCRIPTION5, statusJobTool, statusProjectTool, statusTools;
72386
72955
  var init_status = __esm({
72387
72956
  "src/tools/manifest/status.ts"() {
@@ -72405,7 +72974,7 @@ var init_status = __esm({
72405
72974
  output: external_exports.string().optional().describe("Download result media to this path when complete.")
72406
72975
  }),
72407
72976
  async execute(args, ctx) {
72408
- const projectDir = args.projectDir ? resolve85(ctx.workingDirectory, args.projectDir) : findProjectRoot(ctx.workingDirectory);
72977
+ const projectDir = args.projectDir ? resolve86(ctx.workingDirectory, args.projectDir) : findProjectRoot(ctx.workingDirectory);
72409
72978
  const record = await readJobRecord(args.jobId, projectDir);
72410
72979
  if (!record) return { success: false, error: `Job not found: ${args.jobId}` };
72411
72980
  const result = args.refresh === false ? makeJobStatusResult(record, {
@@ -72414,7 +72983,7 @@ var init_status = __esm({
72414
72983
  warnings: []
72415
72984
  }) : await refreshJobRecord(record, {
72416
72985
  wait: args.wait,
72417
- output: args.output ? resolve85(ctx.workingDirectory, args.output) : void 0,
72986
+ output: args.output ? resolve86(ctx.workingDirectory, args.output) : void 0,
72418
72987
  workingDirectory: ctx.workingDirectory
72419
72988
  });
72420
72989
  return {
@@ -72439,7 +73008,7 @@ var init_status = __esm({
72439
73008
  refresh: external_exports.boolean().optional().describe("Refresh active supported jobs before summarizing. Default false.")
72440
73009
  }),
72441
73010
  async execute(args, ctx) {
72442
- const projectDir = args.projectDir ? resolve85(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
73011
+ const projectDir = args.projectDir ? resolve86(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
72443
73012
  const result = await inspectProjectStatus(projectDir, { refresh: args.refresh === true });
72444
73013
  return {
72445
73014
  success: true,
@@ -72728,7 +73297,7 @@ init_source();
72728
73297
  // package.json
72729
73298
  var package_default = {
72730
73299
  name: "@vibeframe/cli",
72731
- version: "0.115.0",
73300
+ version: "0.115.2",
72732
73301
  description: "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
72733
73302
  private: false,
72734
73303
  license: "MIT",
@@ -79603,12 +80172,12 @@ function resolveCopier() {
79603
80172
  async function copyToClipboard(text) {
79604
80173
  const opener = resolveCopier();
79605
80174
  if (!opener) return false;
79606
- return new Promise((resolve86) => {
80175
+ return new Promise((resolve87) => {
79607
80176
  let settled = false;
79608
80177
  const finish = (ok) => {
79609
80178
  if (settled) return;
79610
80179
  settled = true;
79611
- resolve86(ok);
80180
+ resolve87(ok);
79612
80181
  };
79613
80182
  try {
79614
80183
  const child = spawn2(opener.command, opener.args, {
@@ -79739,8 +80308,8 @@ var HOST_CONFIG_DIRS = {
79739
80308
  };
79740
80309
  function isOnPath(binary, env2) {
79741
80310
  const path2 = env2.PATH ?? env2.Path ?? "";
79742
- const sep2 = process.platform === "win32" ? ";" : ":";
79743
- for (const dir of path2.split(sep2)) {
80311
+ const sep3 = process.platform === "win32" ? ";" : ":";
80312
+ for (const dir of path2.split(sep3)) {
79744
80313
  if (!dir) continue;
79745
80314
  if (existsSync39(join20(dir, binary))) return true;
79746
80315
  if (process.platform === "win32" && existsSync39(join20(dir, `${binary}.exe`))) return true;
@@ -80986,7 +81555,7 @@ init_esm();
80986
81555
  init_source();
80987
81556
  import { existsSync as existsSync41 } from "node:fs";
80988
81557
  import { mkdir as mkdir22, readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
80989
- import { basename as basename19, dirname as dirname17, resolve as resolve50 } from "node:path";
81558
+ import { basename as basename19, dirname as dirname17, resolve as resolve50, sep } from "node:path";
80990
81559
  init_scene_project();
80991
81560
  init_visual_styles();
80992
81561
 
@@ -82166,10 +82735,14 @@ async function runSceneInit(projectDirArg, options, startedAt) {
82166
82735
  const storyboardPath2 = resolve50(projectDir, "STORYBOARD.md");
82167
82736
  const designPath = resolve50(projectDir, "DESIGN.md");
82168
82737
  if (options.force || result.created.includes(storyboardPath2) || !existsSync41(storyboardPath2)) {
82169
- const plan = await writeStoryboardAsBundle(projectDir, draft.storyboardMd);
82738
+ const plan = await rewriteBundleFromMarkdown(projectDir, draft.storyboardMd);
82170
82739
  if (!result.created.includes(storyboardPath2)) result.created.push(storyboardPath2);
82171
- for (const file of plan.scenes) {
82172
- const scenePath = resolve50(projectDir, file.path);
82740
+ const planScenePaths = new Set(plan.scenes.map((file) => resolve50(projectDir, file.path)));
82741
+ const scenesDir = resolve50(projectDir, "scenes") + sep;
82742
+ result.created = result.created.filter(
82743
+ (path2) => !path2.startsWith(scenesDir) || planScenePaths.has(path2)
82744
+ );
82745
+ for (const scenePath of planScenePaths) {
82173
82746
  if (!result.created.includes(scenePath)) result.created.push(scenePath);
82174
82747
  }
82175
82748
  }
@@ -82941,8 +83514,8 @@ init_scene_build();
82941
83514
  init_build_plan();
82942
83515
  init_storyboard_parse();
82943
83516
  init_output();
82944
- import { resolve as resolve63, join as join28 } from "node:path";
82945
- import { readFileSync as readFileSync3, existsSync as existsSync51 } from "node:fs";
83517
+ import { resolve as resolve64, join as join29 } from "node:path";
83518
+ import { readFileSync as readFileSync3, existsSync as existsSync52 } from "node:fs";
82946
83519
  var VALID_MODES2 = ["agent", "batch", "auto"];
82947
83520
  var VALID_STAGES2 = ["assets", "transcript", "compose", "sync", "render", "all"];
82948
83521
  var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
@@ -82950,14 +83523,17 @@ var VALID_VIDEO_PROVIDERS2 = ["seedance", "grok", "kling", "runway", "veo"];
82950
83523
  var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
82951
83524
  var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--stage <stage>", `Build stage: ${VALID_STAGES2.join("|")}`, "all").option("--beat <id>", "Restrict asset/compose work to one beat id").option("--mode <mode>", "Retained for compatibility; scene HTML is always authored by your host agent unless --composer template is set", "auto").option(
82952
83525
  "--composer <provider>",
82953
- "Set to `template` for the deterministic AI-video composer (concat bg + lower-thirds, no model). Omit to let the host agent author each scene."
83526
+ "Set to `template` for the deterministic AI-video composer (concat bg + lower-thirds, no model) or `footage` for the crossfade footage cut (clips + narration + music, no HTML render). Omit to let the host agent author each scene."
82954
83527
  ).option("--max-cost <usd>", "Fail before provider spend when estimated cost exceeds this USD cap").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-video", "Don't dispatch video generation even when beats declare video cues").option(
82955
83528
  "--skip-keyframe",
82956
83529
  "Don't generate keyframe stills (review keyframes first with --skip-video, then build)"
82957
83530
  ).option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option(
82958
83531
  "--skip-transcript",
82959
83532
  "Don't transcribe narration for word-sync (default: transcribe when narration + OpenAI key exist)"
82960
- ).option("--skip-render", "Compose only - don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
83533
+ ).option("--skip-render", "Compose only - don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|openai|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", `Image provider: ${VALID_IMAGE_PROVIDERS2.join("|")}`).option(
83534
+ "--image-model <model>",
83535
+ "Image model for keyframes/backdrops/character sheets (gemini: flash|pro, openai: gpt-image-2). Provider default when omitted."
83536
+ ).option("--video-provider <name>", `Video provider: ${VALID_VIDEO_PROVIDERS2.join("|")}`).option("--music-provider <name>", `Music provider: ${VALID_MUSIC_PROVIDERS2.join("|")}`).option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText(
82961
83537
  "after",
82962
83538
  `
82963
83539
  Examples:
@@ -82968,7 +83544,7 @@ Examples:
82968
83544
  Advanced equivalent: \`vibe scene build\`.`
82969
83545
  ).action(async (projectDirArg, options) => {
82970
83546
  const startedAt = Date.now();
82971
- const projectDir = resolve63(projectDirArg);
83547
+ const projectDir = resolve64(projectDirArg);
82972
83548
  const mode = String(options.mode ?? "auto");
82973
83549
  if (!VALID_MODES2.includes(mode)) {
82974
83550
  exitWithError(
@@ -83025,6 +83601,7 @@ Advanced equivalent: \`vibe scene build\`.`
83025
83601
  videoProvider,
83026
83602
  musicProvider,
83027
83603
  imageQuality: options.quality,
83604
+ imageModel: options.imageModel,
83028
83605
  imageSize: options.imageSize,
83029
83606
  force: options.force ?? false
83030
83607
  };
@@ -83044,6 +83621,7 @@ Advanced equivalent: \`vibe scene build\`.`
83044
83621
  voice: options.voice,
83045
83622
  imageProvider,
83046
83623
  imageQuality: options.quality,
83624
+ imageModel: options.imageModel,
83047
83625
  imageSize: options.imageSize,
83048
83626
  videoProvider,
83049
83627
  musicProvider,
@@ -83118,6 +83696,7 @@ Advanced equivalent: \`vibe scene build\`.`
83118
83696
  videoProvider,
83119
83697
  musicProvider,
83120
83698
  imageQuality: options.quality,
83699
+ imageModel: options.imageModel,
83121
83700
  imageSize: options.imageSize,
83122
83701
  force: options.force,
83123
83702
  onProgress: (e) => {
@@ -83300,8 +83879,8 @@ function buildPlanValidationFailureData(plan) {
83300
83879
  };
83301
83880
  }
83302
83881
  function estimateBuildCost(params) {
83303
- const storyboardPath2 = join28(params.projectDir, "STORYBOARD.md");
83304
- if (!existsSync51(storyboardPath2)) return null;
83882
+ const storyboardPath2 = join29(params.projectDir, "STORYBOARD.md");
83883
+ if (!existsSync52(storyboardPath2)) return null;
83305
83884
  let beats = 0;
83306
83885
  try {
83307
83886
  const md = readFileSync3(storyboardPath2, "utf-8");
@@ -83435,7 +84014,7 @@ init_source();
83435
84014
  init_ora();
83436
84015
  init_scene_render();
83437
84016
  init_output();
83438
- import { resolve as resolve64 } from "node:path";
84017
+ import { resolve as resolve65 } from "node:path";
83439
84018
  var VALID_FPS = [24, 30, 60];
83440
84019
  var VALID_QUALITIES = ["draft", "standard", "high"];
83441
84020
  var VALID_FORMATS = ["mp4", "webm", "mov"];
@@ -83446,7 +84025,7 @@ Examples:
83446
84025
 
83447
84026
  Alias note: this is the project-level entrypoint for \`vibe scene render\`.`).action(async (projectDirArg, options) => {
83448
84027
  const startedAt = Date.now();
83449
- const projectDir = resolve64(projectDirArg);
84028
+ const projectDir = resolve65(projectDirArg);
83450
84029
  const fps = parseFps(String(options.fps));
83451
84030
  const quality = parseQuality(String(options.quality));
83452
84031
  const format = parseFormat(String(options.format));
@@ -83588,7 +84167,7 @@ init_source();
83588
84167
  init_ora();
83589
84168
  init_scene_render();
83590
84169
  init_output();
83591
- import { resolve as resolve65 } from "node:path";
84170
+ import { resolve as resolve66 } from "node:path";
83592
84171
  var VALID_FPS2 = [24, 30, 60];
83593
84172
  var VALID_QUALITIES2 = ["draft", "standard", "high"];
83594
84173
  var VALID_FORMATS2 = ["mp4", "webm", "mov"];
@@ -83606,7 +84185,7 @@ Examples:
83606
84185
  $ vibe preview my-video --beat hook
83607
84186
  $ vibe preview my-video --fps 30 # override the cheap default`).action(async (projectDirArg, options) => {
83608
84187
  const startedAt = Date.now();
83609
- const projectDir = resolve65(projectDirArg);
84188
+ const projectDir = resolve66(projectDirArg);
83610
84189
  const fps = parseFps2(String(options.fps));
83611
84190
  const quality = parseQuality2(String(options.quality));
83612
84191
  const format = parseFormat2(String(options.format));
@@ -83743,21 +84322,35 @@ init_esm();
83743
84322
  init_source();
83744
84323
  init_ora();
83745
84324
  init_scene_assemble();
84325
+ init_footage_assemble();
83746
84326
  init_output();
83747
- import { resolve as resolve66 } from "node:path";
84327
+ import { resolve as resolve67 } from "node:path";
83748
84328
  var VALID_FORMATS3 = ["mp4", "webm", "mov"];
83749
84329
  var DEFAULT_VIDEO = "preview.mp4";
83750
- var assembleCommand = new Command("assemble").description("Mux a scene project's audio onto an already-rendered (silent) video").argument("[project-dir]", "Video project directory", ".").option("--video <path>", `Rendered video to add audio to (default: ${DEFAULT_VIDEO})`, DEFAULT_VIDEO).option("--root <file>", "Root composition file", "index.html").option("--format <f>", `Output container: ${VALID_FORMATS3.join("|")}`, "mp4").option("--dry-run", "Preview parameters without muxing").addHelpText("after", `
83751
- The assemble stage lays the project's <audio> elements onto a silent video in
83752
- one FFmpeg pass (-c:v copy, no re-encode). Pair it with \`vibe render --silent\`:
84330
+ var assembleCommand = new Command("assemble").description(
84331
+ "Mux a scene project's audio onto a rendered video, or crossfade-cut its generated clips (--footage)"
84332
+ ).argument("[project-dir]", "Video project directory", ".").option("--video <path>", `Rendered video to add audio to (default: ${DEFAULT_VIDEO})`, DEFAULT_VIDEO).option("--root <file>", "Root composition file", "index.html").option("--format <f>", `Output container: ${VALID_FORMATS3.join("|")}`, "mp4").option("--footage", "Assemble assets/video-<beat>.mp4 clips into one cinematic cut (no HTML render)").option("-o, --output <path>", "Footage-cut output path (default: renders/footage.mp4)").option("--transition <sec>", "Footage crossfade length in seconds", "0.5").option("--fade <sec>", "Footage fade-from/to-black length in seconds", "0.6").option("--music <path>", 'Footage music bed: a file path or "none" (default: auto-detect assets/music*)').option("--music-volume <v>", "Footage music-bed volume before ducking (0-1)", "0.35").option("--finish", "Footage cinematic finish: vignette + film grain before the fades").option("--dry-run", "Preview parameters without muxing").addHelpText("after", `
84333
+ The default assemble stage lays the project's <audio> elements onto a silent
84334
+ video in one FFmpeg pass (-c:v copy, no re-encode). Pair it with \`vibe render
84335
+ --silent\`:
83753
84336
 
83754
84337
  $ vibe render my-video --silent -o draft.mp4
83755
84338
  $ vibe assemble my-video --video draft.mp4
83756
84339
 
83757
- Plain \`vibe render\` already renders + assembles in one shot - use this only when
83758
- you want the two steps separated (e.g. swap the audio bed without re-rendering).`).action(async (projectDirArg, options) => {
84340
+ With --footage the project's generated clips become the picture: they are
84341
+ crossfade-concatenated in beat order, narration is laid at computed offsets,
84342
+ an optional music bed is ducked under it, and the head/tail fade to black -
84343
+ one FFmpeg pass, no HTML composition or browser capture. Timing comes from
84344
+ the real files (ffprobe), and assemble-report.json records every offset:
84345
+
84346
+ $ vibe build my-video --stage assets --json # clips + narration
84347
+ $ vibe assemble my-video --footage --json`).action(async (projectDirArg, options) => {
83759
84348
  const startedAt = Date.now();
83760
- const projectDir = resolve66(projectDirArg);
84349
+ const projectDir = resolve67(projectDirArg);
84350
+ if (options.footage) {
84351
+ await runFootageAssemble(projectDir, options, startedAt);
84352
+ return;
84353
+ }
83761
84354
  const format = parseFormat3(String(options.format));
83762
84355
  const video = String(options.video ?? DEFAULT_VIDEO);
83763
84356
  const params = { projectDir, root: options.root, video, format };
@@ -83794,6 +84387,73 @@ you want the two steps separated (e.g. swap the audio bed without re-rendering).
83794
84387
  }
83795
84388
  printAssembleResult(spinner2, result);
83796
84389
  });
84390
+ async function runFootageAssemble(projectDir, options, startedAt) {
84391
+ const transitionSec = parseSeconds("--transition", options.transition);
84392
+ const fadeSec = parseSeconds("--fade", options.fade);
84393
+ const musicVolume = parseSeconds("--music-volume", options.musicVolume);
84394
+ const spinner2 = isJsonMode() || isQuietMode() || options.dryRun ? null : ora("Assembling footage cut...").start();
84395
+ const result = await executeFootageAssemble({
84396
+ projectDir,
84397
+ output: options.output,
84398
+ transitionSec,
84399
+ fadeSec,
84400
+ music: options.music,
84401
+ musicVolume,
84402
+ finish: Boolean(options.finish),
84403
+ dryRun: Boolean(options.dryRun)
84404
+ });
84405
+ if (!result.success) {
84406
+ spinner2?.fail("Footage assemble failed");
84407
+ exitWithError(generalError(result.error ?? "Footage assemble failed", result.suggestion));
84408
+ }
84409
+ if (isJsonMode() || isQuietMode()) {
84410
+ outputSuccess({ command: "assemble", startedAt, dryRun: result.dryRun, data: { ...result } });
84411
+ return;
84412
+ }
84413
+ printFootageResult(spinner2, result);
84414
+ }
84415
+ function printFootageResult(spinner2, result) {
84416
+ const report = result.report;
84417
+ if (result.dryRun) {
84418
+ console.log();
84419
+ console.log(source_default.bold.cyan("VibeFrame Assemble - footage dry run"));
84420
+ } else {
84421
+ spinner2?.succeed(source_default.green(`Footage cut assembled: ${result.outputPath}`));
84422
+ console.log();
84423
+ console.log(source_default.bold.cyan("Footage cut"));
84424
+ }
84425
+ console.log(source_default.dim("-".repeat(60)));
84426
+ if (report) {
84427
+ console.log(` Output: ${source_default.bold(report.output)}`);
84428
+ console.log(` Duration: ${source_default.bold(`${report.totalDurationSec}s`)}`);
84429
+ console.log(` Transition: ${report.transitionSec}s crossfade, ${report.fadeSec}s head/tail fade`);
84430
+ console.log(
84431
+ ` Music: ${report.music ? `${report.music.path}${report.music.ducked ? " (ducked)" : ""}` : source_default.dim("none")}`
84432
+ );
84433
+ for (const beat of report.beats) {
84434
+ const narration = beat.narrationStartSec !== void 0 ? ` nar@${beat.narrationStartSec}s` : "";
84435
+ const extended = beat.extendedSec > 0 ? source_default.yellow(` +${beat.extendedSec}s hold`) : "";
84436
+ console.log(
84437
+ ` ${beat.id.padEnd(12)} ${String(beat.startSec).padStart(7)}s ${beat.segmentDurationSec}s${narration}${extended}`
84438
+ );
84439
+ }
84440
+ for (const warning of report.warnings) console.log(source_default.yellow(` Warning: ${warning}`));
84441
+ }
84442
+ if (result.dryRun) {
84443
+ console.log();
84444
+ console.log(source_default.dim("No FFmpeg pass ran; nothing was written."));
84445
+ } else if (result.reportPath) {
84446
+ console.log(` Report: ${result.reportPath}`);
84447
+ }
84448
+ }
84449
+ function parseSeconds(flag, value) {
84450
+ if (value === void 0) return void 0;
84451
+ const n = Number.parseFloat(String(value));
84452
+ if (!Number.isFinite(n) || n < 0) {
84453
+ exitWithError(usageError(`Invalid ${flag}: ${String(value)}`, "Expected a non-negative number."));
84454
+ }
84455
+ return n;
84456
+ }
83797
84457
  function printAssembleDryRun(projectDirArg, params) {
83798
84458
  console.log();
83799
84459
  console.log(source_default.bold.cyan("VibeFrame Assemble - dry run"));
@@ -83829,7 +84489,7 @@ init_source();
83829
84489
  init_cost_tier();
83830
84490
  init_status_jobs();
83831
84491
  init_output();
83832
- import { resolve as resolve67 } from "node:path";
84492
+ import { resolve as resolve68 } from "node:path";
83833
84493
  var statusCommand = new Command("status").description("Inspect local async jobs and project workflow status").addHelpText("after", `
83834
84494
  Examples:
83835
84495
  $ vibe status job job_abc123 --json
@@ -83840,7 +84500,7 @@ Examples:
83840
84500
  statusCommand.command("job").description("Show one async job status").argument("<job-id>", "Local job id from a no-wait command").option("--project <dir>", "Project directory containing .vibeframe/jobs").option("--no-refresh", "Read local job record only; do not call provider APIs").option("--wait", "Wait for completion when the provider status helper supports it").option("-o, --output <path>", "Download result media when complete").action(async (jobId, options) => {
83841
84501
  const startedAt = Date.now();
83842
84502
  try {
83843
- const projectDir = options.project ? resolve67(options.project) : findProjectRoot();
84503
+ const projectDir = options.project ? resolve68(options.project) : findProjectRoot();
83844
84504
  const record = await readJobRecord(jobId, projectDir);
83845
84505
  if (!record) {
83846
84506
  exitWithError(generalError(`Job not found: ${jobId}`, `Run 'vibe status project ${projectDir} --json' to list known jobs.`));
@@ -83868,7 +84528,7 @@ applyTier(statusCommand.commands[statusCommand.commands.length - 1], "free");
83868
84528
  statusCommand.command("project").description("Summarize build, review, and async job status for a project").argument("[project-dir]", "VibeFrame project directory", ".").option("--refresh", "Refresh active supported jobs before summarizing").action(async (projectDirArg, options) => {
83869
84529
  const startedAt = Date.now();
83870
84530
  try {
83871
- const result = await inspectProjectStatus(resolve67(projectDirArg), {
84531
+ const result = await inspectProjectStatus(resolve68(projectDirArg), {
83872
84532
  refresh: options.refresh === true
83873
84533
  });
83874
84534
  if (isJsonMode()) {
@@ -83975,8 +84635,8 @@ init_dist2();
83975
84635
  init_exec_safe();
83976
84636
  init_exec_safe();
83977
84637
  init_api_key();
83978
- import { existsSync as existsSync52 } from "node:fs";
83979
- import { resolve as resolve68 } from "node:path";
84638
+ import { existsSync as existsSync53 } from "node:fs";
84639
+ import { resolve as resolve69 } from "node:path";
83980
84640
  init_composer_resolve();
83981
84641
  init_cost_tier();
83982
84642
  init_install_skill();
@@ -84107,13 +84767,13 @@ async function runDiagnostics() {
84107
84767
  installed = false;
84108
84768
  }
84109
84769
  } else if (toolName === "chrome") {
84110
- const { existsSync: existsSync60 } = await import("node:fs");
84770
+ const { existsSync: existsSync61 } = await import("node:fs");
84111
84771
  const { homedir: homedir8 } = await import("node:os");
84112
- const { join: join33 } = await import("node:path");
84772
+ const { join: join34 } = await import("node:path");
84113
84773
  const chromePaths = [
84114
84774
  process.env.HYPERFRAMES_CHROME_PATH,
84115
84775
  process.env.CHROME_PATH,
84116
- join33(
84776
+ join34(
84117
84777
  homedir8(),
84118
84778
  ".cache",
84119
84779
  "puppeteer",
@@ -84128,7 +84788,7 @@ async function runDiagnostics() {
84128
84788
  "/usr/bin/chromium",
84129
84789
  "/usr/bin/chromium-browser"
84130
84790
  ];
84131
- installed = chromePaths.some((p) => p && existsSync60(p));
84791
+ installed = chromePaths.some((p) => p && existsSync61(p));
84132
84792
  } else {
84133
84793
  installed = commandExists(toolName);
84134
84794
  }
@@ -84168,7 +84828,7 @@ async function runDiagnostics() {
84168
84828
  }
84169
84829
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
84170
84830
  path: rel,
84171
- exists: existsSync52(resolve68(cwd, rel))
84831
+ exists: existsSync53(resolve69(cwd, rel))
84172
84832
  }));
84173
84833
  const projectInitialized = projectFiles.some((f) => f.exists);
84174
84834
  const nearestProjectConfigPath = await findProjectConfigPath(cwd);
@@ -84186,7 +84846,7 @@ async function runDiagnostics() {
84186
84846
  } catch (err) {
84187
84847
  if (!(err instanceof ComposerResolveError)) throw err;
84188
84848
  }
84189
- const sceneProjectInCwd = existsSync52(resolve68(cwd, "STORYBOARD.md"));
84849
+ const sceneProjectInCwd = existsSync53(resolve69(cwd, "STORYBOARD.md"));
84190
84850
  const skillInstalled = hyperframesSkillAvailable(cwd);
84191
84851
  const skillPath = resolveSkillReference(cwd);
84192
84852
  return {
@@ -84539,7 +85199,7 @@ init_esm();
84539
85199
  init_source();
84540
85200
  init_exec_safe();
84541
85201
  init_cost_tier();
84542
- import { resolve as resolve69 } from "node:path";
85202
+ import { resolve as resolve70 } from "node:path";
84543
85203
  init_output();
84544
85204
  var hostCommand = new Command("host").description("Set up Codex, Claude, and Cursor app integrations").addHelpText(
84545
85205
  "after",
@@ -84553,7 +85213,7 @@ Examples:
84553
85213
  );
84554
85214
  hostCommand.command("list").description("List supported app hosts and integration surfaces").action(() => {
84555
85215
  const startedAt = Date.now();
84556
- const hosts = hostDefinitions(resolve69(".")).map((host) => ({
85216
+ const hosts = hostDefinitions(resolve70(".")).map((host) => ({
84557
85217
  id: host.id,
84558
85218
  label: host.label,
84559
85219
  surfaces: host.surfaces,
@@ -84580,7 +85240,7 @@ applyTier(hostCommand.commands[hostCommand.commands.length - 1], "free");
84580
85240
  hostCommand.command("setup").description("Print or write Codex/Claude/Cursor MCP and project integration config").argument("[host]", `Host target: ${HOST_SELECTIONS.join(" | ")}`, "all").argument("[project-dir]", "Project directory for project-scoped config", ".").option("--write", "Write config files instead of printing snippets only").option("--dry-run", "Show file actions without writing").option("--force", "Replace an existing vibeframe MCP entry").action(async (hostArg, projectDirArg, options) => {
84581
85241
  const startedAt = Date.now();
84582
85242
  const selection = validateHostSelection(hostArg);
84583
- const projectDir = resolve69(projectDirArg);
85243
+ const projectDir = resolve70(projectDirArg);
84584
85244
  const ids = resolveHostSelection(selection);
84585
85245
  const defs = hostDefinitions(projectDir).filter((host) => ids.includes(host.id));
84586
85246
  const plans = [];
@@ -84614,7 +85274,7 @@ applyTier(hostCommand.commands[hostCommand.commands.length - 1], "free");
84614
85274
  hostCommand.command("doctor").description("Check Codex/Claude/Cursor app integration readiness").argument("[host]", `Host target: ${HOST_SELECTIONS.join(" | ")}`, "all").argument("[project-dir]", "Project directory to inspect", ".").action(async (hostArg, projectDirArg) => {
84615
85275
  const startedAt = Date.now();
84616
85276
  const selection = validateHostSelection(hostArg);
84617
- const projectDir = resolve69(projectDirArg);
85277
+ const projectDir = resolve70(projectDirArg);
84618
85278
  const ids = resolveHostSelection(selection);
84619
85279
  const defs = hostDefinitions(projectDir).filter((host) => ids.includes(host.id));
84620
85280
  const hosts = await Promise.all(defs.map((host) => inspectHost(host, projectDir)));
@@ -84734,10 +85394,10 @@ init_exec_safe();
84734
85394
  init_detect();
84735
85395
  init_ai_edit();
84736
85396
  init_output();
84737
- import { resolve as resolve70 } from "node:path";
84738
- import { existsSync as existsSync53 } from "node:fs";
84739
- import { mkdir as mkdir26, rm as rm6 } from "node:fs/promises";
84740
- var DEMO_DIR = resolve70(process.cwd(), ".vibeframe-demo");
85397
+ import { resolve as resolve71 } from "node:path";
85398
+ import { existsSync as existsSync54 } from "node:fs";
85399
+ import { mkdir as mkdir27, rm as rm6 } from "node:fs/promises";
85400
+ var DEMO_DIR = resolve71(process.cwd(), ".vibeframe-demo");
84741
85401
  async function generateTestVideo(outputPath) {
84742
85402
  const filter = [
84743
85403
  // Video: color bars with text
@@ -84766,7 +85426,7 @@ async function generateTestVideo(outputPath) {
84766
85426
  "-y",
84767
85427
  outputPath
84768
85428
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
84769
- return existsSync53(outputPath);
85429
+ return existsSync54(outputPath);
84770
85430
  } catch {
84771
85431
  try {
84772
85432
  await execSafe("ffmpeg", [
@@ -84792,7 +85452,7 @@ async function generateTestVideo(outputPath) {
84792
85452
  "-y",
84793
85453
  outputPath
84794
85454
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
84795
- return existsSync53(outputPath);
85455
+ return existsSync54(outputPath);
84796
85456
  } catch {
84797
85457
  return false;
84798
85458
  }
@@ -84816,8 +85476,8 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84816
85476
  console.log(source_default.dim(" " + "\u2500".repeat(50)));
84817
85477
  console.log();
84818
85478
  }
84819
- await mkdir26(DEMO_DIR, { recursive: true });
84820
- const testVideo = resolve70(DEMO_DIR, "test-input.mp4");
85479
+ await mkdir27(DEMO_DIR, { recursive: true });
85480
+ const testVideo = resolve71(DEMO_DIR, "test-input.mp4");
84821
85481
  const genSpinner = !isJson ? ora("Generating test video...").start() : null;
84822
85482
  const generated = await generateTestVideo(testVideo);
84823
85483
  if (!generated) {
@@ -84846,7 +85506,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84846
85506
  name: "Silence Cut",
84847
85507
  description: "Removing silent segments",
84848
85508
  run: async () => {
84849
- const output = resolve70(DEMO_DIR, "silence-cut.mp4");
85509
+ const output = resolve71(DEMO_DIR, "silence-cut.mp4");
84850
85510
  const r = await executeSilenceCut({ videoPath: testVideo, outputPath: output, padding: 0.1 });
84851
85511
  if (r.success && r.totalDuration && r.silentDuration) {
84852
85512
  return { success: true, detail: `Removed ${r.silentDuration.toFixed(1)}s silence (${r.totalDuration.toFixed(1)}s \u2192 ${(r.totalDuration - r.silentDuration).toFixed(1)}s)` };
@@ -84858,7 +85518,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84858
85518
  name: "Noise Reduce",
84859
85519
  description: "Reducing background noise",
84860
85520
  run: async () => {
84861
- const output = resolve70(DEMO_DIR, "noise-reduced.mp4");
85521
+ const output = resolve71(DEMO_DIR, "noise-reduced.mp4");
84862
85522
  const r = await executeNoiseReduce({ inputPath: testVideo, outputPath: output, strength: "medium" });
84863
85523
  return { success: r.success, detail: r.success ? `Cleaned audio \u2192 ${output}` : r.error };
84864
85524
  }
@@ -84867,7 +85527,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84867
85527
  name: "Fade Effects",
84868
85528
  description: "Adding fade in/out",
84869
85529
  run: async () => {
84870
- const output = resolve70(DEMO_DIR, "faded.mp4");
85530
+ const output = resolve71(DEMO_DIR, "faded.mp4");
84871
85531
  const r = await executeFade({ videoPath: testVideo, outputPath: output, fadeIn: 1, fadeOut: 1 });
84872
85532
  return { success: r.success, detail: r.success ? `1s fade in + 1s fade out \u2192 ${output}` : r.error };
84873
85533
  }
@@ -84934,20 +85594,20 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84934
85594
 
84935
85595
  // src/commands/context.ts
84936
85596
  init_esm();
84937
- import { existsSync as existsSync54 } from "node:fs";
85597
+ import { existsSync as existsSync55 } from "node:fs";
84938
85598
  import { readFile as readFile45 } from "node:fs/promises";
84939
- import { resolve as resolve71, dirname as dirname22 } from "node:path";
85599
+ import { resolve as resolve72, dirname as dirname23 } from "node:path";
84940
85600
  import { fileURLToPath as fileURLToPath2 } from "node:url";
84941
85601
  var __filename = fileURLToPath2(import.meta.url);
84942
- var __dirname = dirname22(__filename);
85602
+ var __dirname = dirname23(__filename);
84943
85603
  function resolveContextPath(baseDir = __dirname) {
84944
85604
  const candidates = [
84945
85605
  // Bundled package layout: packages/cli/dist/index.js -> packages/cli/CONTEXT.md
84946
- resolve71(baseDir, "../CONTEXT.md"),
85606
+ resolve72(baseDir, "../CONTEXT.md"),
84947
85607
  // Source/dev layout: packages/cli/src/commands/context.ts -> packages/cli/CONTEXT.md
84948
- resolve71(baseDir, "../../CONTEXT.md")
85608
+ resolve72(baseDir, "../../CONTEXT.md")
84949
85609
  ];
84950
- return candidates.find((candidate) => existsSync54(candidate)) ?? null;
85610
+ return candidates.find((candidate) => existsSync55(candidate)) ?? null;
84951
85611
  }
84952
85612
  var contextCommand = new Command("context").description("Print CLI context/guidelines for AI agent integration").option("--format <format>", "Output format: markdown | json", "markdown").action(async (_options, cmd) => {
84953
85613
  const options = {
@@ -86002,7 +86662,7 @@ async function startAgent(options = {}) {
86002
86662
  console.log();
86003
86663
  console.log(source_default.dim(" Commands: exit \xB7 reset \xB7 tools \xB7 context"));
86004
86664
  console.log();
86005
- return new Promise((resolve86) => {
86665
+ return new Promise((resolve87) => {
86006
86666
  if (typeof process.stdin.ref === "function") {
86007
86667
  process.stdin.ref();
86008
86668
  }
@@ -86109,7 +86769,7 @@ async function startAgent(options = {}) {
86109
86769
  clearInterval(keepalive);
86110
86770
  console.log();
86111
86771
  console.log(source_default.dim("Goodbye!"));
86112
- resolve86();
86772
+ resolve87();
86113
86773
  });
86114
86774
  rl.prompt();
86115
86775
  });
@@ -86383,8 +87043,8 @@ init_source();
86383
87043
  init_config();
86384
87044
  init_schema();
86385
87045
  init_api_key();
86386
- import { access as access9, writeFile as writeFile55, mkdir as mkdir29 } from "node:fs/promises";
86387
- import { dirname as dirname24 } from "node:path";
87046
+ import { access as access9, writeFile as writeFile56, mkdir as mkdir30 } from "node:fs/promises";
87047
+ import { dirname as dirname25 } from "node:path";
86388
87048
  var BANNER_SHOWN_PATH = `${USER_CONFIG_DIR}/.banner-shown`;
86389
87049
  async function isFirstRun() {
86390
87050
  try {
@@ -86403,8 +87063,8 @@ async function isFirstRun() {
86403
87063
  }
86404
87064
  async function markBannerShown() {
86405
87065
  try {
86406
- await mkdir29(dirname24(BANNER_SHOWN_PATH), { recursive: true });
86407
- await writeFile55(BANNER_SHOWN_PATH, (/* @__PURE__ */ new Date()).toISOString());
87066
+ await mkdir30(dirname25(BANNER_SHOWN_PATH), { recursive: true });
87067
+ await writeFile56(BANNER_SHOWN_PATH, (/* @__PURE__ */ new Date()).toISOString());
86408
87068
  } catch {
86409
87069
  }
86410
87070
  }