@vibeframe/cli 0.115.1 → 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
@@ -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({
@@ -58291,6 +58292,7 @@ function backdropCacheDescriptor(opts) {
58291
58292
  beatId: opts.beatId,
58292
58293
  cue: opts.cue,
58293
58294
  provider: opts.provider,
58295
+ model: opts.model,
58294
58296
  quality: opts.quality,
58295
58297
  size: opts.size,
58296
58298
  ratio: opts.ratio,
@@ -58302,6 +58304,7 @@ function characterCacheDescriptor(opts) {
58302
58304
  name: opts.name,
58303
58305
  cue: opts.cue,
58304
58306
  provider: opts.provider,
58307
+ model: opts.model,
58305
58308
  quality: opts.quality,
58306
58309
  size: opts.size,
58307
58310
  ext: "png"
@@ -58311,6 +58314,7 @@ function keyframeCacheDescriptor(opts) {
58311
58314
  return cacheAssetDescriptor("keyframe", {
58312
58315
  beatId: opts.beatId,
58313
58316
  cue: opts.cue,
58317
+ model: opts.model,
58314
58318
  provider: opts.provider,
58315
58319
  quality: opts.quality,
58316
58320
  size: opts.size,
@@ -58411,7 +58415,8 @@ async function createBuildPlan(opts) {
58411
58415
  const stage = opts.stage ?? "all";
58412
58416
  const config2 = await readProjectConfig(projectDir);
58413
58417
  const kindPolicy = kindAssetPolicy(config2.config.kind);
58414
- 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);
58415
58420
  const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
58416
58421
  const skipVideo = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
58417
58422
  const storyboardPath2 = join24(projectDir, "STORYBOARD.md");
@@ -58530,7 +58535,8 @@ async function createBuildPlan(opts) {
58530
58535
  provider: resolved.image.resolved,
58531
58536
  quality: imageQuality,
58532
58537
  size: imageSize2,
58533
- ratio: imageRatio
58538
+ ratio: imageRatio,
58539
+ model: opts.imageModel
58534
58540
  }) : null;
58535
58541
  const videoCache = (videoPrompt || keyframePrompt) && !videoReference ? videoCacheDescriptor({
58536
58542
  beatId: beat.id,
@@ -58545,7 +58551,8 @@ async function createBuildPlan(opts) {
58545
58551
  provider: resolved.image.resolved,
58546
58552
  quality: imageQuality,
58547
58553
  size: imageSize2,
58548
- ratio: imageRatio
58554
+ ratio: imageRatio,
58555
+ model: opts.imageModel
58549
58556
  }) : null;
58550
58557
  const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
58551
58558
  beatId: beat.id,
@@ -59020,6 +59027,17 @@ async function keyedProviderResolution(opts, projectDir) {
59020
59027
  }
59021
59028
  async function resolveComposerProvider(input, projectDir) {
59022
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
+ }
59023
59041
  const explicit = isComposerProvider(requested) ? requested : void 0;
59024
59042
  const resolved = explicit ?? await firstConfiguredComposer(projectDir) ?? "claude";
59025
59043
  const configKey = composerConfigKey(resolved);
@@ -59494,9 +59512,422 @@ var init_compose_aivideo = __esm({
59494
59512
  }
59495
59513
  });
59496
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
+
59497
59928
  // src/commands/_shared/scene-audio-scan.ts
59498
59929
  import { readFile as readFile41 } from "node:fs/promises";
59499
- import { resolve as resolve58 } from "node:path";
59930
+ import { resolve as resolve59 } from "node:path";
59500
59931
  function parseRootClips(rootHtml) {
59501
59932
  const clips = [];
59502
59933
  const clipRegex = /<div\b[^>]*class="clip"[^>]*>/gi;
@@ -59534,7 +59965,7 @@ function parseSceneAudios(compositionHtml) {
59534
59965
  }
59535
59966
  function makeFsCompositionReader(projectDir) {
59536
59967
  return async (compositionSrcRel) => {
59537
- const abs = resolve58(projectDir, compositionSrcRel);
59968
+ const abs = resolve59(projectDir, compositionSrcRel);
59538
59969
  try {
59539
59970
  return await readFile41(abs, "utf-8");
59540
59971
  } catch {
@@ -59553,7 +59984,7 @@ async function scanSceneAudio(opts) {
59553
59984
  for (const audio of audios) {
59554
59985
  out.push({
59555
59986
  srcRel: audio.srcRel,
59556
- srcAbs: resolve58(opts.projectDir, audio.srcRel),
59987
+ srcAbs: resolve59(opts.projectDir, audio.srcRel),
59557
59988
  absoluteStart: clip.start + audio.localStart,
59558
59989
  durationHint: audio.durationHint,
59559
59990
  clipDurationCap: clip.duration - audio.localStart,
@@ -59568,7 +59999,7 @@ async function scanSceneAudio(opts) {
59568
59999
  for (const audio of rootAudios) {
59569
60000
  out.push({
59570
60001
  srcRel: audio.srcRel,
59571
- srcAbs: resolve58(opts.projectDir, audio.srcRel),
60002
+ srcAbs: resolve59(opts.projectDir, audio.srcRel),
59572
60003
  absoluteStart: audio.localStart,
59573
60004
  durationHint: audio.durationHint,
59574
60005
  clipDurationCap: Math.max(0, maxClipEnd - audio.localStart),
@@ -59603,7 +60034,7 @@ var init_scene_audio_scan = __esm({
59603
60034
 
59604
60035
  // src/commands/_shared/scene-audio-mux.ts
59605
60036
  import { rename as rename4, unlink } from "node:fs/promises";
59606
- 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";
59607
60038
  function buildAudioMuxFilter(audios) {
59608
60039
  if (audios.length === 0) return null;
59609
60040
  const labels = [];
@@ -59663,8 +60094,8 @@ async function muxAudioIntoVideo(opts) {
59663
60094
  return { success: true, outputPath: opts.videoPath, audioCount: 0 };
59664
60095
  }
59665
60096
  const ext = extname13(opts.videoPath) || `.${opts.format}`;
59666
- const tmpPath = resolve59(
59667
- dirname19(opts.videoPath),
60097
+ const tmpPath = resolve60(
60098
+ dirname20(opts.videoPath),
59668
60099
  `.${basename20(opts.videoPath, ext)}.muxing${ext}`
59669
60100
  );
59670
60101
  const args = ["-y", "-loglevel", "error", "-i", opts.videoPath];
@@ -59726,15 +60157,15 @@ var init_scene_audio_mux = __esm({
59726
60157
  });
59727
60158
 
59728
60159
  // src/commands/_shared/scene-assemble.ts
59729
- import { existsSync as existsSync48 } from "node:fs";
60160
+ import { existsSync as existsSync49 } from "node:fs";
59730
60161
  import { readFile as readFile42 } from "node:fs/promises";
59731
- import { resolve as resolve60, isAbsolute as isAbsolute6 } from "node:path";
60162
+ import { resolve as resolve61, isAbsolute as isAbsolute7 } from "node:path";
59732
60163
  async function executeSceneAssemble(opts) {
59733
- const projectDir = resolve60(opts.projectDir);
60164
+ const projectDir = resolve61(opts.projectDir);
59734
60165
  const root = opts.root ?? "index.html";
59735
60166
  const format = opts.format ?? "mp4";
59736
- const videoPath = isAbsolute6(opts.videoPath) ? opts.videoPath : resolve60(projectDir, opts.videoPath);
59737
- if (!existsSync48(videoPath)) {
60167
+ const videoPath = isAbsolute7(opts.videoPath) ? opts.videoPath : resolve61(projectDir, opts.videoPath);
60168
+ if (!existsSync49(videoPath)) {
59738
60169
  return {
59739
60170
  success: false,
59740
60171
  audioCount: 0,
@@ -59743,8 +60174,8 @@ async function executeSceneAssemble(opts) {
59743
60174
  error: `Video not found: ${videoPath}. Render it first (e.g. \`vibe render --silent\`).`
59744
60175
  };
59745
60176
  }
59746
- const rootPath = resolve60(projectDir, root);
59747
- if (!existsSync48(rootPath)) {
60177
+ const rootPath = resolve61(projectDir, root);
60178
+ if (!existsSync49(rootPath)) {
59748
60179
  return {
59749
60180
  success: false,
59750
60181
  audioCount: 0,
@@ -59811,9 +60242,9 @@ __export(scene_render_exports, {
59811
60242
  qualityToCrf: () => qualityToCrf2
59812
60243
  });
59813
60244
  import { execFile as execFile2 } from "node:child_process";
59814
- import { existsSync as existsSync49 } from "node:fs";
59815
- import { mkdir as mkdir24, readFile as readFile43, stat as stat8, writeFile as writeFile44 } from "node:fs/promises";
59816
- 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";
59817
60248
  import { promisify as promisify2 } from "node:util";
59818
60249
  import {
59819
60250
  createRenderJob,
@@ -59826,9 +60257,9 @@ function defaultOutputPath(opts) {
59826
60257
  const fmt = opts.format ?? "mp4";
59827
60258
  const now = opts.now ?? /* @__PURE__ */ new Date();
59828
60259
  const stamp = now.toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
59829
- const name = (opts.projectName ?? basename21(resolve61(opts.projectDir))) || "scene";
60260
+ const name = (opts.projectName ?? basename21(resolve62(opts.projectDir))) || "scene";
59830
60261
  const beat = opts.beatId ? `-${sanitizeFileSegment2(opts.beatId)}` : "";
59831
- return resolve61(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
60262
+ return resolve62(opts.projectDir, "renders", `${name}${beat}-${stamp}.${fmt}`);
59832
60263
  }
59833
60264
  function buildRenderConfig(opts) {
59834
60265
  const quality = opts.quality ?? "standard";
@@ -59866,7 +60297,7 @@ function buildMediaOpenCommand(action, filePath, platform3 = process.platform) {
59866
60297
  display: ["cmd", ...args].map(quoteCliArg).join(" ")
59867
60298
  };
59868
60299
  }
59869
- const target = action === "reveal" ? dirname20(filePath) : filePath;
60300
+ const target = action === "reveal" ? dirname21(filePath) : filePath;
59870
60301
  return {
59871
60302
  command: "xdg-open",
59872
60303
  args: [target],
@@ -59874,7 +60305,7 @@ function buildMediaOpenCommand(action, filePath, platform3 = process.platform) {
59874
60305
  };
59875
60306
  }
59876
60307
  async function executeSceneRender(opts = {}) {
59877
- const projectDir = resolve61(opts.projectDir ?? ".");
60308
+ const projectDir = resolve62(opts.projectDir ?? ".");
59878
60309
  const projectConfig = await readProjectConfig(projectDir);
59879
60310
  const engine = projectConfig.config.composition.engine;
59880
60311
  if (engine !== "hyperframes") {
@@ -59905,7 +60336,7 @@ async function executeSceneRender(opts = {}) {
59905
60336
  }
59906
60337
  let autoSyncApplied = false;
59907
60338
  let autoSyncWarning;
59908
- if (!opts.beatId && existsSync49(join26(projectDir, "STORYBOARD.md"))) {
60339
+ if (!opts.beatId && existsSync50(join27(projectDir, "STORYBOARD.md"))) {
59909
60340
  try {
59910
60341
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
59911
60342
  const subPlans = await createSubCompDurationSyncPlans({
@@ -59947,7 +60378,7 @@ async function executeSceneRender(opts = {}) {
59947
60378
  kind: "render",
59948
60379
  beat: opts.beatId ?? null,
59949
60380
  root,
59950
- 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\`.`
59951
60382
  };
59952
60383
  }
59953
60384
  const chrome = await preflightChrome();
@@ -59961,8 +60392,8 @@ async function executeSceneRender(opts = {}) {
59961
60392
  };
59962
60393
  }
59963
60394
  const projectName = projectConfig.config.name;
59964
- const outputPath = opts.output ? resolve61(projectDir, opts.output) : defaultOutputPath({ projectDir, projectName, format: opts.format, beatId: opts.beatId });
59965
- 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 });
59966
60397
  const config2 = buildRenderConfig({
59967
60398
  fps: opts.fps,
59968
60399
  quality: opts.quality,
@@ -60074,11 +60505,11 @@ async function safeStat(p) {
60074
60505
  }
60075
60506
  async function prepareBeatRenderRoot(opts) {
60076
60507
  const root = `.vibeframe/tmp/render-beat-${sanitizeFileSegment2(opts.beatId)}.html`;
60077
- const reportPath = join26(opts.projectDir, "build-report.json");
60508
+ const reportPath = join27(opts.projectDir, "build-report.json");
60078
60509
  const retryWith = [
60079
60510
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
60080
60511
  ];
60081
- if (!existsSync49(reportPath)) {
60512
+ if (!existsSync50(reportPath)) {
60082
60513
  return {
60083
60514
  success: false,
60084
60515
  root,
@@ -60158,8 +60589,8 @@ async function prepareBeatRenderRoot(opts) {
60158
60589
  durationSec,
60159
60590
  narrationPath
60160
60591
  });
60161
- await mkdir24(dirname20(join26(opts.projectDir, root)), { recursive: true });
60162
- 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");
60163
60594
  return {
60164
60595
  success: true,
60165
60596
  root,
@@ -60223,9 +60654,9 @@ function reportedBeatAssetPaths(beat) {
60223
60654
  ]);
60224
60655
  }
60225
60656
  async function writeRenderReport(projectDir, result) {
60226
- const reportPath = join26(projectDir, "render-report.json");
60657
+ const reportPath = join27(projectDir, "render-report.json");
60227
60658
  try {
60228
- await writeFile44(
60659
+ await writeFile45(
60229
60660
  reportPath,
60230
60661
  JSON.stringify(
60231
60662
  {
@@ -60264,8 +60695,8 @@ async function writeRenderReport(projectDir, result) {
60264
60695
  }
60265
60696
  }
60266
60697
  function pathExists3(projectDir, value) {
60267
- const abs = isAbsolute7(value) ? value : resolve61(projectDir, value);
60268
- return existsSync49(abs);
60698
+ const abs = isAbsolute8(value) ? value : resolve62(projectDir, value);
60699
+ return existsSync50(abs);
60269
60700
  }
60270
60701
  function stringValue(value) {
60271
60702
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -60318,14 +60749,15 @@ var init_scene_build_mode = __esm({
60318
60749
  var scene_build_exports = {};
60319
60750
  __export(scene_build_exports, {
60320
60751
  executeSceneBuild: () => executeSceneBuild,
60752
+ isLikenessRejection: () => isLikenessRejection,
60321
60753
  resolveSceneBuildMode: () => resolveSceneBuildMode
60322
60754
  });
60323
- import { existsSync as existsSync50, readFileSync as readFileSync2 } from "node:fs";
60324
- import { copyFile as copyFile5, mkdir as mkdir25, readFile as readFile44, rm as rm5, writeFile as writeFile45 } from "node:fs/promises";
60325
- 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";
60326
60758
  function readProjectKindSync(projectDir) {
60327
60759
  try {
60328
- const raw = readFileSync2(join27(projectDir, "vibe.config.json"), "utf-8");
60760
+ const raw = readFileSync2(join28(projectDir, "vibe.config.json"), "utf-8");
60329
60761
  const k = JSON.parse(raw).kind;
60330
60762
  return typeof k === "string" && isSceneKind(k) ? k : "cinema";
60331
60763
  } catch {
@@ -60334,16 +60766,17 @@ function readProjectKindSync(projectDir) {
60334
60766
  }
60335
60767
  async function executeSceneBuild(opts) {
60336
60768
  const startedAt = Date.now();
60337
- const projectDir = resolve62(opts.projectDir);
60769
+ const projectDir = resolve63(opts.projectDir);
60338
60770
  const onProgress = opts.onProgress ?? (() => {
60339
60771
  });
60340
60772
  const projectKind = readProjectKindSync(projectDir);
60341
60773
  const kindPolicy = kindAssetPolicy(projectKind);
60342
60774
  const composer = opts.composer ?? composerDefaultForKind(projectKind);
60343
- const skipBackdrop = opts.skipBackdrop ?? kindPolicy.skipBackdrop ?? false;
60775
+ const skipBackdrop = opts.skipBackdrop ?? (composer === "footage" ? true : kindPolicy.skipBackdrop ?? false);
60344
60776
  const skipKeyframe = opts.skipKeyframe ?? kindPolicy.skipKeyframe ?? false;
60345
60777
  const skipVideoAssets = opts.skipVideo ?? kindPolicy.skipVideo ?? false;
60346
- const mode = composer === "template" ? "batch" : resolveSceneBuildMode(opts);
60778
+ const skipTranscript = opts.skipTranscript ?? composer === "footage";
60779
+ const mode = composer === "template" || composer === "footage" ? "batch" : resolveSceneBuildMode(opts);
60347
60780
  const selectedStage = opts.stage ?? (opts.skipRender ? "sync" : "all");
60348
60781
  const skipVideoStopsRender = (opts.skipVideo ?? false) && (selectedStage === "all" || selectedStage === "render");
60349
60782
  const effectiveSkipRender = (opts.skipRender ?? false) || skipVideoStopsRender;
@@ -60356,7 +60789,7 @@ async function executeSceneBuild(opts) {
60356
60789
  );
60357
60790
  }
60358
60791
  let sceneRepair = skippedSceneRepairSummary();
60359
- const storyboardPath2 = join27(projectDir, "STORYBOARD.md");
60792
+ const storyboardPath2 = join28(projectDir, "STORYBOARD.md");
60360
60793
  const buildPlan = await createBuildPlan({
60361
60794
  projectDir,
60362
60795
  stage: selectedStage,
@@ -60372,8 +60805,10 @@ async function executeSceneBuild(opts) {
60372
60805
  imageProvider: opts.imageProvider,
60373
60806
  imageQuality: opts.imageQuality,
60374
60807
  imageSize: opts.imageSize,
60808
+ imageModel: opts.imageModel,
60375
60809
  videoProvider: opts.videoProvider,
60376
60810
  musicProvider: opts.musicProvider,
60811
+ skipTranscript,
60377
60812
  composer,
60378
60813
  force: opts.force
60379
60814
  });
@@ -60426,7 +60861,7 @@ async function executeSceneBuild(opts) {
60426
60861
  }
60427
60862
  if (!buildPlan.validation.ok) {
60428
60863
  let invalidBeats = [];
60429
- if (existsSync50(storyboardPath2)) {
60864
+ if (existsSync51(storyboardPath2)) {
60430
60865
  invalidBeats = parseStoryboard(await loadStoryboardMarkdown(projectDir)).beats;
60431
60866
  }
60432
60867
  return finishBuildResult({
@@ -60451,7 +60886,7 @@ async function executeSceneBuild(opts) {
60451
60886
  totalLatencyMs: Date.now() - startedAt
60452
60887
  });
60453
60888
  }
60454
- if (!existsSync50(storyboardPath2)) {
60889
+ if (!existsSync51(storyboardPath2)) {
60455
60890
  return finishBuildResult({
60456
60891
  success: false,
60457
60892
  phase: "failed",
@@ -60546,6 +60981,7 @@ async function executeSceneBuild(opts) {
60546
60981
  imageProvider,
60547
60982
  imageSize: opts.imageSize ?? "1536x1024",
60548
60983
  imageQuality: opts.imageQuality ?? "hd",
60984
+ imageModel: opts.imageModel,
60549
60985
  force: opts.force ?? false,
60550
60986
  onProgress
60551
60987
  }
@@ -60567,6 +61003,7 @@ async function executeSceneBuild(opts) {
60567
61003
  musicProvider,
60568
61004
  imageQuality: opts.imageQuality ?? "hd",
60569
61005
  imageSize: opts.imageSize ?? "1536x1024",
61006
+ imageModel: opts.imageModel,
60570
61007
  skipNarration: opts.skipNarration ?? false,
60571
61008
  skipBackdrop,
60572
61009
  skipVideo: skipVideoAssets,
@@ -60656,7 +61093,7 @@ async function executeSceneBuild(opts) {
60656
61093
  totalLatencyMs: Date.now() - startedAt
60657
61094
  });
60658
61095
  }
60659
- if (shouldRunStage(selectedStage, "transcript") && !(opts.skipTranscript ?? false)) {
61096
+ if (shouldRunStage(selectedStage, "transcript") && !skipTranscript) {
60660
61097
  const { statuses } = await runTranscriptStage(activeBeats, beatOutcomes, {
60661
61098
  projectDir,
60662
61099
  force: opts.force ?? false,
@@ -60685,9 +61122,9 @@ async function executeSceneBuild(opts) {
60685
61122
  }
60686
61123
  if (shouldRunStage(selectedStage, "compose")) {
60687
61124
  if (mode === "agent") {
60688
- const compositionsDir = join27(projectDir, "compositions");
61125
+ const compositionsDir = join28(projectDir, "compositions");
60689
61126
  const missingBeats = activeBeats.filter(
60690
- (b) => !existsSync50(join27(compositionsDir, `scene-${b.id}.html`))
61127
+ (b) => !existsSync51(join28(compositionsDir, `scene-${b.id}.html`))
60691
61128
  );
60692
61129
  if (missingBeats.length > 0) {
60693
61130
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -60721,6 +61158,8 @@ async function executeSceneBuild(opts) {
60721
61158
  }
60722
61159
  onProgress({ type: "phase-start", phase: "compose" });
60723
61160
  stageReports.compose.status = "done";
61161
+ } else if (composer === "footage") {
61162
+ stageReports.compose.status = "skipped";
60724
61163
  } else if (composer === "template") {
60725
61164
  onProgress({ type: "phase-start", phase: "compose" });
60726
61165
  try {
@@ -60760,8 +61199,8 @@ async function executeSceneBuild(opts) {
60760
61199
  if (stageReports.compose.status === "done") {
60761
61200
  for (const beat of composer === "template" ? [] : activeBeats) {
60762
61201
  const videoRel = `assets/video-${beat.id}.mp4`;
60763
- const compositionAbs = join27(projectDir, "compositions", `scene-${beat.id}.html`);
60764
- 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;
60765
61204
  const html = readFileSync2(compositionAbs, "utf-8");
60766
61205
  if (!html.includes(videoRel)) {
60767
61206
  warnings.push(
@@ -60816,8 +61255,8 @@ async function executeSceneBuild(opts) {
60816
61255
  totalLatencyMs: Date.now() - startedAt
60817
61256
  });
60818
61257
  }
60819
- if (shouldRunStage(selectedStage, "sync")) {
60820
- if (!existsSync50(join27(projectDir, "index.html"))) {
61258
+ if (composer !== "footage" && shouldRunStage(selectedStage, "sync")) {
61259
+ if (!existsSync51(join28(projectDir, "index.html"))) {
60821
61260
  await scaffoldSceneProject({
60822
61261
  dir: projectDir,
60823
61262
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -60886,7 +61325,50 @@ async function executeSceneBuild(opts) {
60886
61325
  }
60887
61326
  let outputPath;
60888
61327
  let renderResult;
60889
- 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")) {
60890
61372
  onProgress({ type: "phase-start", phase: "render" });
60891
61373
  onProgress({ type: "render-start" });
60892
61374
  renderResult = await executeSceneRender({ projectDir });
@@ -60941,6 +61423,7 @@ async function executeSceneBuild(opts) {
60941
61423
  beats: beatOutcomes,
60942
61424
  outputPath,
60943
61425
  renderResult,
61426
+ footageReport,
60944
61427
  estimatedCostUsd: buildPlan.estimatedCostUsd,
60945
61428
  costUsd: stageReports.assets.costUsd + stageReports.compose.costUsd,
60946
61429
  stageReports,
@@ -61075,7 +61558,7 @@ async function dispatchNarration(beat, ctx) {
61075
61558
  return {
61076
61559
  status: "cached",
61077
61560
  path: existingRel,
61078
- durationSec: await safeAudioDuration(join27(ctx.projectDir, existingRel)),
61561
+ durationSec: await safeAudioDuration(join28(ctx.projectDir, existingRel)),
61079
61562
  provider: metadata?.provider,
61080
61563
  cachePath: metadata?.cachePath,
61081
61564
  cacheKey: metadata?.cacheKey,
@@ -61099,9 +61582,9 @@ async function dispatchNarration(beat, ctx) {
61099
61582
  voice,
61100
61583
  ext: resolution.audioExtension
61101
61584
  });
61102
- const cacheAbs = join27(ctx.projectDir, cache.path);
61585
+ const cacheAbs = join28(ctx.projectDir, cache.path);
61103
61586
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
61104
- const abs = join27(ctx.projectDir, rel);
61587
+ const abs = join28(ctx.projectDir, rel);
61105
61588
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
61106
61589
  projectDir: ctx.projectDir,
61107
61590
  kind: "narration",
@@ -61115,7 +61598,7 @@ async function dispatchNarration(beat, ctx) {
61115
61598
  return {
61116
61599
  status: "cached",
61117
61600
  path: existingRel,
61118
- durationSec: await safeAudioDuration(join27(ctx.projectDir, existingRel)),
61601
+ durationSec: await safeAudioDuration(join28(ctx.projectDir, existingRel)),
61119
61602
  provider: resolution.provider,
61120
61603
  cachePath: cache.path,
61121
61604
  cacheKey: cache.key,
@@ -61123,8 +61606,8 @@ async function dispatchNarration(beat, ctx) {
61123
61606
  freshness: "fresh"
61124
61607
  };
61125
61608
  }
61126
- if (existsSync50(cacheAbs) && !ctx.force) {
61127
- await mkdir25(dirname21(abs), { recursive: true });
61609
+ if (existsSync51(cacheAbs) && !ctx.force) {
61610
+ await mkdir26(dirname22(abs), { recursive: true });
61128
61611
  await copyFile5(cacheAbs, abs);
61129
61612
  await writeAssetMetadata({
61130
61613
  projectDir: ctx.projectDir,
@@ -61155,14 +61638,14 @@ async function dispatchNarration(beat, ctx) {
61155
61638
  ctx.onProgress({ type: "narration-failed", beatId: beat.id, error });
61156
61639
  return { status: "failed", error };
61157
61640
  }
61158
- await mkdir25(dirname21(abs), { recursive: true });
61159
- await writeFile45(abs, result.audioBuffer);
61641
+ await mkdir26(dirname22(abs), { recursive: true });
61642
+ await writeFile46(abs, result.audioBuffer);
61160
61643
  for (const otherExt of ["mp3", "wav"]) {
61161
- const sibling = join27(ctx.projectDir, `assets/narration-${beat.id}.${otherExt}`);
61162
- 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 });
61163
61646
  }
61164
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61165
- await writeFile45(cacheAbs, result.audioBuffer);
61647
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61648
+ await writeFile46(cacheAbs, result.audioBuffer);
61166
61649
  await writeAssetMetadata({
61167
61650
  projectDir: ctx.projectDir,
61168
61651
  kind: "narration",
@@ -61194,8 +61677,8 @@ async function dispatchNarration(beat, ctx) {
61194
61677
  async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
61195
61678
  if (!narrationPath) return "skipped";
61196
61679
  const relPath = beatTranscriptRelPath(beat.id);
61197
- const abs = join27(ctx.projectDir, relPath);
61198
- if (existsSync50(abs) && !ctx.force && !narrationFresh) {
61680
+ const abs = join28(ctx.projectDir, relPath);
61681
+ if (existsSync51(abs) && !ctx.force && !narrationFresh) {
61199
61682
  ctx.onProgress({ type: "transcript-cached", beatId: beat.id, path: relPath });
61200
61683
  return "cached";
61201
61684
  }
@@ -61208,7 +61691,7 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
61208
61691
  });
61209
61692
  return "skipped";
61210
61693
  }
61211
- const words = await transcribeNarrationWords(join27(ctx.projectDir, narrationPath), { apiKey });
61694
+ const words = await transcribeNarrationWords(join28(ctx.projectDir, narrationPath), { apiKey });
61212
61695
  if (words.length === 0) {
61213
61696
  ctx.onProgress({
61214
61697
  type: "transcript-skipped",
@@ -61218,8 +61701,8 @@ async function dispatchTranscript(beat, narrationPath, narrationFresh, ctx) {
61218
61701
  return "skipped";
61219
61702
  }
61220
61703
  try {
61221
- await mkdir25(dirname21(abs), { recursive: true });
61222
- 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");
61223
61706
  } catch (err) {
61224
61707
  ctx.onProgress({
61225
61708
  type: "transcript-skipped",
@@ -61266,7 +61749,7 @@ async function buildCharacters(characters, ctx) {
61266
61749
  const size = ctx.imageSize ?? "1536x1024";
61267
61750
  for (const character of characters) {
61268
61751
  if (character.imagePath) {
61269
- if (existsSync50(join27(ctx.projectDir, character.imagePath))) {
61752
+ if (existsSync51(join28(ctx.projectDir, character.imagePath))) {
61270
61753
  paths.set(character.name, character.imagePath);
61271
61754
  ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
61272
61755
  } else {
@@ -61279,16 +61762,17 @@ async function buildCharacters(characters, ctx) {
61279
61762
  if (!character.prompt) continue;
61280
61763
  const prompt3 = characterSheetPrompt(character.name, character.prompt);
61281
61764
  const rel = `assets/character-${character.name}.png`;
61282
- const abs = join27(ctx.projectDir, rel);
61765
+ const abs = join28(ctx.projectDir, rel);
61283
61766
  const metadataOptions = { quality: ctx.imageQuality, size };
61284
61767
  const cache = characterCacheDescriptor({
61285
61768
  name: character.name,
61286
61769
  cue: prompt3,
61287
61770
  provider: ctx.imageProvider,
61288
61771
  quality: ctx.imageQuality,
61289
- size
61772
+ size,
61773
+ model: ctx.imageModel
61290
61774
  });
61291
- if (existsSync50(abs) && !ctx.force) {
61775
+ if (existsSync51(abs) && !ctx.force) {
61292
61776
  const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
61293
61777
  if (!metadata || isFreshCanonicalAsset({
61294
61778
  projectDir: ctx.projectDir,
@@ -61304,9 +61788,9 @@ async function buildCharacters(characters, ctx) {
61304
61788
  continue;
61305
61789
  }
61306
61790
  }
61307
- const cacheAbs = join27(ctx.projectDir, cache.path);
61308
- if (existsSync50(cacheAbs) && !ctx.force) {
61309
- 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 });
61310
61794
  await copyFile5(cacheAbs, abs);
61311
61795
  await writeAssetMetadata({
61312
61796
  projectDir: ctx.projectDir,
@@ -61329,7 +61813,8 @@ async function buildCharacters(characters, ctx) {
61329
61813
  projectDir: ctx.projectDir,
61330
61814
  imageProvider: ctx.imageProvider,
61331
61815
  imageSize: size,
61332
- imageQuality: ctx.imageQuality
61816
+ imageQuality: ctx.imageQuality,
61817
+ imageModel: ctx.imageModel
61333
61818
  },
61334
61819
  imageRatioForSize(size)
61335
61820
  );
@@ -61338,10 +61823,10 @@ async function buildCharacters(characters, ctx) {
61338
61823
  ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
61339
61824
  continue;
61340
61825
  }
61341
- await mkdir25(dirname21(abs), { recursive: true });
61342
- await writeFile45(abs, generated.buffer);
61343
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61344
- 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);
61345
61830
  await writeAssetMetadata({
61346
61831
  projectDir: ctx.projectDir,
61347
61832
  kind: "character",
@@ -61371,7 +61856,7 @@ async function dispatchBackdrop(beat, ctx) {
61371
61856
  if (!cue) return { status: "no-cue" };
61372
61857
  const prompt3 = augmentBackdropPrompt(cue);
61373
61858
  const rel = `assets/backdrop-${beat.id}.png`;
61374
- const abs = join27(ctx.projectDir, rel);
61859
+ const abs = join28(ctx.projectDir, rel);
61375
61860
  const size = ctx.imageSize ?? "1536x1024";
61376
61861
  const ratio = imageRatioForSize(size);
61377
61862
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -61381,10 +61866,11 @@ async function dispatchBackdrop(beat, ctx) {
61381
61866
  provider: ctx.imageProvider,
61382
61867
  quality: ctx.imageQuality,
61383
61868
  size,
61384
- ratio
61869
+ ratio,
61870
+ model: ctx.imageModel
61385
61871
  });
61386
61872
  const metadataPath = assetMetadataPath("backdrop", beat.id);
61387
- if (existsSync50(abs) && !ctx.force) {
61873
+ if (existsSync51(abs) && !ctx.force) {
61388
61874
  const metadata = readAssetMetadata(ctx.projectDir, "backdrop", beat.id);
61389
61875
  if (!metadata || isFreshCanonicalAsset({
61390
61876
  projectDir: ctx.projectDir,
@@ -61407,9 +61893,9 @@ async function dispatchBackdrop(beat, ctx) {
61407
61893
  };
61408
61894
  }
61409
61895
  }
61410
- const cacheAbs = join27(ctx.projectDir, cache.path);
61411
- if (existsSync50(cacheAbs) && !ctx.force) {
61412
- 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 });
61413
61899
  await copyFile5(cacheAbs, abs);
61414
61900
  await writeAssetMetadata({
61415
61901
  projectDir: ctx.projectDir,
@@ -61439,11 +61925,11 @@ async function dispatchBackdrop(beat, ctx) {
61439
61925
  ctx.onProgress({ type: "backdrop-failed", beatId: beat.id, error });
61440
61926
  return { status: "failed", error };
61441
61927
  }
61442
- await mkdir25(dirname21(abs), { recursive: true });
61928
+ await mkdir26(dirname22(abs), { recursive: true });
61443
61929
  const buffer = generated.buffer;
61444
- await writeFile45(abs, buffer);
61445
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61446
- await writeFile45(cacheAbs, buffer);
61930
+ await writeFile46(abs, buffer);
61931
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61932
+ await writeFile46(cacheAbs, buffer);
61447
61933
  await writeAssetMetadata({
61448
61934
  projectDir: ctx.projectDir,
61449
61935
  kind: "backdrop",
@@ -61485,7 +61971,9 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
61485
61971
  const provider2 = new OpenAIImageProvider();
61486
61972
  await provider2.initialize({ apiKey });
61487
61973
  const result2 = await provider2.generateImage(prompt3, {
61488
- 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",
61489
61977
  size: ctx.imageSize,
61490
61978
  quality: ctx.imageQuality
61491
61979
  });
@@ -61495,7 +61983,7 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
61495
61983
  const provider2 = new GeminiProvider();
61496
61984
  await provider2.initialize({ apiKey });
61497
61985
  const result2 = await provider2.generateImage(prompt3, {
61498
- model: "flash",
61986
+ model: ctx.imageModel ?? "flash",
61499
61987
  aspectRatio: ratio
61500
61988
  });
61501
61989
  return imageBufferFromResult(result2);
@@ -61528,7 +62016,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
61528
62016
  const provider2 = new GeminiProvider();
61529
62017
  await provider2.initialize({ apiKey });
61530
62018
  const result2 = await provider2.editImage(sheetBuffers, prompt3, {
61531
- model: "flash",
62019
+ model: ctx.imageModel ?? "flash",
61532
62020
  aspectRatio: ratio
61533
62021
  });
61534
62022
  return imageBufferFromResult(result2);
@@ -61540,7 +62028,7 @@ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
61540
62028
  }
61541
62029
  async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61542
62030
  const rel = `assets/keyframe-${beat.id}.png`;
61543
- const abs = join27(ctx.projectDir, rel);
62031
+ const abs = join28(ctx.projectDir, rel);
61544
62032
  const size = ctx.imageSize ?? "1536x1024";
61545
62033
  const ratio = imageRatioForSize(size);
61546
62034
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -61551,9 +62039,10 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61551
62039
  quality: ctx.imageQuality,
61552
62040
  size,
61553
62041
  ratio,
61554
- characters: characterRefsRel
62042
+ characters: characterRefsRel,
62043
+ model: ctx.imageModel
61555
62044
  });
61556
- if (existsSync50(abs) && !ctx.force) {
62045
+ if (existsSync51(abs) && !ctx.force) {
61557
62046
  const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
61558
62047
  if (!metadata || isFreshCanonicalAsset({
61559
62048
  projectDir: ctx.projectDir,
@@ -61567,9 +62056,9 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61567
62056
  return { status: "cached", rel };
61568
62057
  }
61569
62058
  }
61570
- const cacheAbs = join27(ctx.projectDir, cache.path);
61571
- if (existsSync50(cacheAbs) && !ctx.force) {
61572
- 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 });
61573
62062
  await copyFile5(cacheAbs, abs);
61574
62063
  await writeAssetMetadata({
61575
62064
  projectDir: ctx.projectDir,
@@ -61586,22 +62075,23 @@ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
61586
62075
  }
61587
62076
  const sheetBuffers = [];
61588
62077
  for (const relPath of characterRefsRel) {
61589
- const sheetAbs = join27(ctx.projectDir, relPath);
61590
- if (existsSync50(sheetAbs)) sheetBuffers.push(await readFile44(sheetAbs));
62078
+ const sheetAbs = join28(ctx.projectDir, relPath);
62079
+ if (existsSync51(sheetAbs)) sheetBuffers.push(await readFile44(sheetAbs));
61591
62080
  }
61592
62081
  const imgCtx = {
61593
62082
  projectDir: ctx.projectDir,
61594
62083
  imageProvider: ctx.imageProvider,
61595
62084
  imageSize: size,
61596
- imageQuality: ctx.imageQuality
62085
+ imageQuality: ctx.imageQuality,
62086
+ imageModel: ctx.imageModel
61597
62087
  };
61598
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. ";
61599
62089
  const generated = sheetBuffers.length > 0 ? await editKeyframeImage(`${identityLock}${keyframeCue}`, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
61600
62090
  if (!generated.success) return { status: "failed", error: generated.error };
61601
- await mkdir25(dirname21(abs), { recursive: true });
61602
- await writeFile45(abs, generated.buffer);
61603
- await mkdir25(dirname21(cacheAbs), { recursive: true });
61604
- 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);
61605
62095
  await writeAssetMetadata({
61606
62096
  projectDir: ctx.projectDir,
61607
62097
  kind: "keyframe",
@@ -61655,14 +62145,16 @@ async function dispatchVideo(beat, ctx, keyframe) {
61655
62145
  const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
61656
62146
  const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
61657
62147
  if (!prompt3) return { status: "no-cue" };
62148
+ const providerCue = stringOrUndefined4(beat.cues?.provider);
62149
+ const requestedProvider = providerCue ? resolveBuildVideoProvider(providerCue) : ctx.videoProvider;
61658
62150
  const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p) => typeof p === "string");
61659
- const characterRefsAbs = characterRefsRel.map((p) => join27(ctx.projectDir, p));
62151
+ const characterRefsAbs = characterRefsRel.map((p) => join28(ctx.projectDir, p));
61660
62152
  const rel = `assets/video-${beat.id}.mp4`;
61661
- const abs = join27(ctx.projectDir, rel);
62153
+ const abs = join28(ctx.projectDir, rel);
61662
62154
  const cache = videoCacheDescriptor({
61663
62155
  beatId: beat.id,
61664
62156
  cue: prompt3,
61665
- provider: ctx.videoProvider,
62157
+ provider: requestedProvider,
61666
62158
  duration: normalizeVideoDuration(beat.duration),
61667
62159
  // Keyframe mode keys the clip to the keyframe prompt; refs feed the keyframe,
61668
62160
  // not the clip directly.
@@ -61670,14 +62162,14 @@ async function dispatchVideo(beat, ctx, keyframe) {
61670
62162
  keyframe: keyframeCue
61671
62163
  });
61672
62164
  const metadataPath = assetMetadataPath("video", beat.id);
61673
- if (existsSync50(abs) && !ctx.force) {
62165
+ if (existsSync51(abs) && !ctx.force) {
61674
62166
  const metadata = readAssetMetadata(ctx.projectDir, "video", beat.id);
61675
62167
  if (!metadata || isFreshCanonicalAsset({
61676
62168
  projectDir: ctx.projectDir,
61677
62169
  kind: "video",
61678
62170
  beatId: beat.id,
61679
62171
  cue: prompt3,
61680
- provider: ctx.videoProvider,
62172
+ provider: requestedProvider,
61681
62173
  options: { duration: normalizeVideoDuration(beat.duration), ratio: "16:9" },
61682
62174
  cacheKey: cache.key
61683
62175
  })) {
@@ -61685,7 +62177,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61685
62177
  return {
61686
62178
  status: "cached",
61687
62179
  path: rel,
61688
- provider: metadata?.provider ?? ctx.videoProvider,
62180
+ provider: metadata?.provider ?? requestedProvider,
61689
62181
  cachePath: metadata?.cachePath ?? cache.path,
61690
62182
  cacheKey: metadata?.cacheKey ?? cache.key,
61691
62183
  metadataPath,
@@ -61693,16 +62185,16 @@ async function dispatchVideo(beat, ctx, keyframe) {
61693
62185
  };
61694
62186
  }
61695
62187
  }
61696
- const cacheAbs = join27(ctx.projectDir, cache.path);
61697
- if (existsSync50(cacheAbs) && !ctx.force) {
61698
- 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 });
61699
62191
  await copyFile5(cacheAbs, abs);
61700
62192
  await writeAssetMetadata({
61701
62193
  projectDir: ctx.projectDir,
61702
62194
  kind: "video",
61703
62195
  beatId: beat.id,
61704
62196
  cue: prompt3,
61705
- provider: ctx.videoProvider,
62197
+ provider: requestedProvider,
61706
62198
  options: { duration: normalizeVideoDuration(beat.duration), ratio: "16:9" },
61707
62199
  cacheKey: cache.key,
61708
62200
  canonicalPath: rel,
@@ -61712,7 +62204,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61712
62204
  return {
61713
62205
  status: "cached",
61714
62206
  path: rel,
61715
- provider: ctx.videoProvider,
62207
+ provider: requestedProvider,
61716
62208
  cachePath: cache.path,
61717
62209
  cacheKey: cache.key,
61718
62210
  metadataPath,
@@ -61725,11 +62217,11 @@ async function dispatchVideo(beat, ctx, keyframe) {
61725
62217
  ctx.onProgress({ type: "video-skipped", beatId: beat.id, reason });
61726
62218
  return { status: "skipped" };
61727
62219
  }
61728
- const keyframeImageAbs = keyframeImageRel ? join27(ctx.projectDir, keyframeImageRel) : void 0;
62220
+ const keyframeImageAbs = keyframeImageRel ? join28(ctx.projectDir, keyframeImageRel) : void 0;
61729
62221
  loadSceneBuildEnv(ctx.projectDir);
61730
- const result = await executeVideoGenerate({
62222
+ const generateWith = (provider, apiKey) => executeVideoGenerate({
61731
62223
  prompt: prompt3,
61732
- provider: ctx.videoProvider,
62224
+ provider,
61733
62225
  duration: normalizeVideoDuration(beat.duration),
61734
62226
  ratio: "16:9",
61735
62227
  output: abs,
@@ -61742,22 +62234,44 @@ async function dispatchVideo(beat, ctx, keyframe) {
61742
62234
  // track carries audio), so the model's generated audio is never used. Disable
61743
62235
  // it: it's wasted, and Seedance's audio moderation can spuriously fail a beat.
61744
62236
  generateAudio: false,
61745
- apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
62237
+ apiKey
61746
62238
  });
62239
+ let activeProvider = requestedProvider;
62240
+ let result = await generateWith(
62241
+ activeProvider,
62242
+ await apiKeyForVideoProvider(activeProvider, ctx.projectDir)
62243
+ );
61747
62244
  if (!result.success || !result.taskId) {
61748
62245
  const error = result.error ?? "video generation did not return a task id";
61749
- ctx.onProgress({ type: "video-failed", beatId: beat.id, error });
61750
- 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
+ }
61751
62265
  }
61752
- if (result.status === "completed" && existsSync50(abs)) {
61753
- await mkdir25(dirname21(cacheAbs), { recursive: true });
62266
+ if (result.status === "completed" && existsSync51(abs)) {
62267
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61754
62268
  await copyFile5(abs, cacheAbs);
61755
62269
  await writeAssetMetadata({
61756
62270
  projectDir: ctx.projectDir,
61757
62271
  kind: "video",
61758
62272
  beatId: beat.id,
61759
62273
  cue: prompt3,
61760
- provider: result.provider ?? ctx.videoProvider,
62274
+ provider: result.provider ?? activeProvider,
61761
62275
  options: { duration: normalizeVideoDuration(beat.duration), ratio: "16:9" },
61762
62276
  cacheKey: cache.key,
61763
62277
  canonicalPath: rel,
@@ -61767,12 +62281,12 @@ async function dispatchVideo(beat, ctx, keyframe) {
61767
62281
  type: "video-generated",
61768
62282
  beatId: beat.id,
61769
62283
  path: rel,
61770
- provider: result.provider ?? ctx.videoProvider
62284
+ provider: result.provider ?? activeProvider
61771
62285
  });
61772
62286
  return {
61773
62287
  status: "generated",
61774
62288
  path: rel,
61775
- provider: result.provider ?? ctx.videoProvider,
62289
+ provider: result.provider ?? activeProvider,
61776
62290
  cachePath: cache.path,
61777
62291
  cacheKey: cache.key,
61778
62292
  metadataPath,
@@ -61781,7 +62295,7 @@ async function dispatchVideo(beat, ctx, keyframe) {
61781
62295
  }
61782
62296
  const job = await createAndWriteJobRecord({
61783
62297
  jobType: "generate-video",
61784
- provider: result.provider ?? ctx.videoProvider,
62298
+ provider: result.provider ?? activeProvider,
61785
62299
  providerTaskId: result.taskId,
61786
62300
  providerTaskType: keyframeImageAbs ? "image2video" : "text2video",
61787
62301
  status: "running",
@@ -61817,7 +62331,7 @@ async function dispatchMusic(beat, ctx) {
61817
62331
  const prompt3 = stringOrUndefined4(beat.cues?.music);
61818
62332
  if (!prompt3) return { status: "no-cue" };
61819
62333
  const rel = `assets/music-${beat.id}.mp3`;
61820
- const abs = join27(ctx.projectDir, rel);
62334
+ const abs = join28(ctx.projectDir, rel);
61821
62335
  const duration = normalizeMusicDuration(beat.duration);
61822
62336
  const cache = musicCacheDescriptor({
61823
62337
  beatId: beat.id,
@@ -61826,7 +62340,7 @@ async function dispatchMusic(beat, ctx) {
61826
62340
  duration
61827
62341
  });
61828
62342
  const metadataPath = assetMetadataPath("music", beat.id);
61829
- if (existsSync50(abs) && !ctx.force) {
62343
+ if (existsSync51(abs) && !ctx.force) {
61830
62344
  const metadata = readAssetMetadata(ctx.projectDir, "music", beat.id);
61831
62345
  if (!metadata || isFreshCanonicalAsset({
61832
62346
  projectDir: ctx.projectDir,
@@ -61850,9 +62364,9 @@ async function dispatchMusic(beat, ctx) {
61850
62364
  };
61851
62365
  }
61852
62366
  }
61853
- const cacheAbs = join27(ctx.projectDir, cache.path);
61854
- if (existsSync50(cacheAbs) && !ctx.force) {
61855
- 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 });
61856
62370
  await copyFile5(cacheAbs, abs);
61857
62371
  await writeAssetMetadata({
61858
62372
  projectDir: ctx.projectDir,
@@ -61925,12 +62439,12 @@ async function dispatchMusic(beat, ctx) {
61925
62439
  metadataPath
61926
62440
  };
61927
62441
  }
61928
- if (!existsSync50(abs)) {
62442
+ if (!existsSync51(abs)) {
61929
62443
  const error = result.outputPath ? `music output was not written at ${abs}` : "music generation did not return an output file";
61930
62444
  ctx.onProgress({ type: "music-failed", beatId: beat.id, error });
61931
62445
  return { status: "failed", error };
61932
62446
  }
61933
- await mkdir25(dirname21(cacheAbs), { recursive: true });
62447
+ await mkdir26(dirname22(cacheAbs), { recursive: true });
61934
62448
  await copyFile5(abs, cacheAbs);
61935
62449
  await writeAssetMetadata({
61936
62450
  projectDir: ctx.projectDir,
@@ -61961,15 +62475,15 @@ async function dispatchMusic(beat, ctx) {
61961
62475
  };
61962
62476
  }
61963
62477
  function loadSceneBuildEnv(projectDir) {
61964
- (0, import_dotenv4.config)({ path: join27(projectDir, ".env"), quiet: true });
61965
- (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 });
61966
62480
  let dir = process.cwd();
61967
- while (dir !== dirname21(dir)) {
61968
- if (existsSync50(join27(dir, "pnpm-workspace.yaml"))) {
61969
- (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 });
61970
62484
  return;
61971
62485
  }
61972
- dir = dirname21(dir);
62486
+ dir = dirname22(dir);
61973
62487
  }
61974
62488
  }
61975
62489
  async function skipped(kind, beatId, reason, ctx) {
@@ -62183,7 +62697,7 @@ function mergeBeatOutcomes(base, updates) {
62183
62697
  }
62184
62698
  function firstExisting3(projectDir, relPaths) {
62185
62699
  for (const rel of relPaths) {
62186
- if (existsSync50(join27(projectDir, rel))) return rel;
62700
+ if (existsSync51(join28(projectDir, rel))) return rel;
62187
62701
  }
62188
62702
  return null;
62189
62703
  }
@@ -62206,6 +62720,9 @@ function resolveBuildMusicProvider(value) {
62206
62720
  const provider = String(value ?? "elevenlabs").toLowerCase();
62207
62721
  return provider === "replicate" ? "replicate" : "elevenlabs";
62208
62722
  }
62723
+ function isLikenessRejection(error) {
62724
+ return typeof error === "string" && /likeness(es)? of real people/i.test(error);
62725
+ }
62209
62726
  function stringOrUndefined4(value) {
62210
62727
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
62211
62728
  }
@@ -62214,7 +62731,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
62214
62731
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
62215
62732
  }
62216
62733
  async function finalizeBuildResult(projectDir, startedAt, result, reportResult = result) {
62217
- const reportPath = join27(projectDir, "build-report.json");
62734
+ const reportPath = join28(projectDir, "build-report.json");
62218
62735
  for (const source of /* @__PURE__ */ new Set([result, reportResult])) {
62219
62736
  if (source.stageReports) {
62220
62737
  for (const report of Object.values(source.stageReports)) {
@@ -62234,7 +62751,7 @@ async function finalizeBuildResult(projectDir, startedAt, result, reportResult =
62234
62751
  const withMeta = decorate(result);
62235
62752
  const reportMeta = reportResult === result ? withMeta : decorate(reportResult);
62236
62753
  try {
62237
- await writeFile45(
62754
+ await writeFile46(
62238
62755
  reportPath,
62239
62756
  JSON.stringify(toBuildReport(projectDir, reportMeta), null, 2) + "\n",
62240
62757
  "utf-8"
@@ -62377,7 +62894,7 @@ function normalizeReportDuration(value) {
62377
62894
  }
62378
62895
  function buildReportComposition(projectDir, result, beatId) {
62379
62896
  const path2 = `compositions/scene-${beatId}.html`;
62380
- const exists = existsSync50(join27(projectDir, path2));
62897
+ const exists = existsSync51(join28(projectDir, path2));
62381
62898
  if (result.stageReports?.compose.status === "failed") return { path: path2, exists, status: "failed" };
62382
62899
  if (result.phase === "needs-author" && !exists) return { path: path2, exists, status: "needs-author" };
62383
62900
  if (exists) return { path: path2, exists, status: "exists" };
@@ -62424,8 +62941,8 @@ function summarizeBuildBeats(projectDir, beats) {
62424
62941
  let compositionsReady = 0;
62425
62942
  for (const beat of beats) {
62426
62943
  if (beatAssetsReady(beat)) assetsReady += 1;
62427
- const compositionPath = join27(projectDir, "compositions", `scene-${beat.beatId}.html`);
62428
- if (existsSync50(compositionPath)) {
62944
+ const compositionPath = join28(projectDir, "compositions", `scene-${beat.beatId}.html`);
62945
+ if (existsSync51(compositionPath)) {
62429
62946
  compositionsReady += 1;
62430
62947
  } else {
62431
62948
  needsAuthor.push(beat.beatId);
@@ -62465,6 +62982,7 @@ var init_scene_build = __esm({
62465
62982
  init_ffmpeg_gate();
62466
62983
  init_concurrency();
62467
62984
  init_compose_aivideo();
62985
+ init_footage_assemble();
62468
62986
  init_scene_project();
62469
62987
  init_compose_prompts();
62470
62988
  init_config();
@@ -62560,16 +63078,16 @@ __export(ai_audio_exports, {
62560
63078
  executeTranscribe: () => executeTranscribe,
62561
63079
  executeVoiceClone: () => executeVoiceClone
62562
63080
  });
62563
- import { resolve as resolve72, dirname as dirname23, basename as basename22, extname as extname14 } from "node:path";
62564
- import { readFile as readFile46, writeFile as writeFile46 } from "node:fs/promises";
62565
- 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";
62566
63084
  async function executeTranscribe(options) {
62567
63085
  const { audioPath, language, output, format, apiKey } = options;
62568
63086
  try {
62569
63087
  const key = apiKey || process.env.OPENAI_API_KEY;
62570
63088
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
62571
- const absPath = resolve72(process.cwd(), audioPath);
62572
- 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}` };
62573
63091
  const whisper = new WhisperProvider();
62574
63092
  await whisper.initialize({ apiKey: key });
62575
63093
  const audioBuffer = await readFile46(absPath);
@@ -62580,10 +63098,10 @@ async function executeTranscribe(options) {
62580
63098
  }
62581
63099
  let outputPath;
62582
63100
  if (output) {
62583
- outputPath = resolve72(process.cwd(), output);
63101
+ outputPath = resolve73(process.cwd(), output);
62584
63102
  const fmt = detectFormat(output, format);
62585
63103
  const content = formatTranscript(result, fmt);
62586
- await writeFile46(outputPath, content, "utf-8");
63104
+ await writeFile47(outputPath, content, "utf-8");
62587
63105
  }
62588
63106
  return {
62589
63107
  success: true,
@@ -62601,8 +63119,8 @@ async function executeIsolate(options) {
62601
63119
  try {
62602
63120
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
62603
63121
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
62604
- const absPath = resolve72(process.cwd(), audioPath);
62605
- 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}` };
62606
63124
  const audioBuffer = await readFile46(absPath);
62607
63125
  const elevenlabs = new ElevenLabsProvider();
62608
63126
  await elevenlabs.initialize({ apiKey: key });
@@ -62610,8 +63128,8 @@ async function executeIsolate(options) {
62610
63128
  if (!result.success || !result.audioBuffer) {
62611
63129
  return { success: false, error: result.error || "Audio isolation failed" };
62612
63130
  }
62613
- const outputPath = resolve72(process.cwd(), output);
62614
- await writeFile46(outputPath, result.audioBuffer);
63131
+ const outputPath = resolve73(process.cwd(), output);
63132
+ await writeFile47(outputPath, result.audioBuffer);
62615
63133
  return { success: true, outputPath };
62616
63134
  } catch (error) {
62617
63135
  return { success: false, error: `Audio isolation failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -62627,8 +63145,8 @@ async function executeVoiceClone(options) {
62627
63145
  }
62628
63146
  const audioBuffers = [];
62629
63147
  for (const samplePath of samplePaths) {
62630
- const absPath = resolve72(process.cwd(), samplePath);
62631
- 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}` };
62632
63150
  const buffer = await readFile46(absPath);
62633
63151
  audioBuffers.push(buffer);
62634
63152
  }
@@ -62649,8 +63167,8 @@ async function executeVoiceClone(options) {
62649
63167
  async function executeDub(options) {
62650
63168
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
62651
63169
  try {
62652
- const absPath = resolve72(process.cwd(), mediaPath);
62653
- 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}` };
62654
63172
  const openaiKey = process.env.OPENAI_API_KEY;
62655
63173
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
62656
63174
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -62661,7 +63179,7 @@ async function executeDub(options) {
62661
63179
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
62662
63180
  let audioPath = absPath;
62663
63181
  if (isVideo) {
62664
- const tempAudioPath = resolve72(dirname23(absPath), `temp-audio-${Date.now()}.mp3`);
63182
+ const tempAudioPath = resolve73(dirname24(absPath), `temp-audio-${Date.now()}.mp3`);
62665
63183
  execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
62666
63184
  audioPath = tempAudioPath;
62667
63185
  }
@@ -62710,7 +63228,7 @@ ${segmentTexts}`,
62710
63228
  }
62711
63229
  if (analyzeOnly) {
62712
63230
  if (output) {
62713
- const timingPath = resolve72(process.cwd(), output);
63231
+ const timingPath = resolve73(process.cwd(), output);
62714
63232
  const timingData = {
62715
63233
  sourcePath: absPath,
62716
63234
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
@@ -62722,14 +63240,14 @@ ${segmentTexts}`,
62722
63240
  translated: translatedSegments[i]?.text || s.text
62723
63241
  }))
62724
63242
  };
62725
- await writeFile46(timingPath, JSON.stringify(timingData, null, 2));
63243
+ await writeFile47(timingPath, JSON.stringify(timingData, null, 2));
62726
63244
  }
62727
63245
  return {
62728
63246
  success: true,
62729
63247
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
62730
63248
  targetLanguage: language,
62731
63249
  segmentCount: segments.length,
62732
- outputPath: output ? resolve72(process.cwd(), output) : void 0
63250
+ outputPath: output ? resolve73(process.cwd(), output) : void 0
62733
63251
  };
62734
63252
  }
62735
63253
  const elevenlabs = new ElevenLabsProvider();
@@ -62743,9 +63261,9 @@ ${segmentTexts}`,
62743
63261
  }
62744
63262
  const combinedBuffer = Buffer.concat(dubbedBuffers);
62745
63263
  const outputExt = isVideo ? ".mp3" : extname14(absPath);
62746
- const defaultOutputPath2 = resolve72(dirname23(absPath), `${basename22(absPath, extname14(absPath))}-${language}${outputExt}`);
62747
- const finalOutputPath = resolve72(process.cwd(), output || defaultOutputPath2);
62748
- 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);
62749
63267
  if (isVideo && audioPath !== absPath) {
62750
63268
  try {
62751
63269
  const { unlink: unlink3 } = await import("node:fs/promises");
@@ -62776,12 +63294,12 @@ async function executeDuck(options) {
62776
63294
  } = options;
62777
63295
  try {
62778
63296
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
62779
- const absMusicPath = resolve72(process.cwd(), musicPath);
62780
- const absVoicePath = resolve72(process.cwd(), voicePath);
62781
- if (!existsSync55(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
62782
- if (!existsSync55(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
62783
- const defaultOutput = resolve72(dirname23(absMusicPath), `${basename22(absMusicPath, extname14(absMusicPath))}-ducked${extname14(absMusicPath)}`);
62784
- 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);
62785
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`;
62786
63304
  await execSafe("ffmpeg", [
62787
63305
  "-i",
@@ -62793,7 +63311,7 @@ async function executeDuck(options) {
62793
63311
  "-y",
62794
63312
  outputPath
62795
63313
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
62796
- 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" };
62797
63315
  return { success: true, outputPath };
62798
63316
  } catch (error) {
62799
63317
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -62809,9 +63327,9 @@ var init_ai_audio = __esm({
62809
63327
  });
62810
63328
 
62811
63329
  // src/pipeline/executor.ts
62812
- import { resolve as resolve73 } from "node:path";
62813
- import { readFile as readFile47, writeFile as writeFile47, mkdir as mkdir27 } from "node:fs/promises";
62814
- 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";
62815
63333
  function maxCostFor(action) {
62816
63334
  const cmd = ACTION_METADATA[action]?.command;
62817
63335
  if (!cmd) return 0;
@@ -62822,9 +63340,9 @@ function registerAction(action, handler) {
62822
63340
  }
62823
63341
  function getOutput(params, outputDir, defaultName) {
62824
63342
  if (params.output && typeof params.output === "string") {
62825
- return resolve73(outputDir, params.output);
63343
+ return resolve74(outputDir, params.output);
62826
63344
  }
62827
- return resolve73(outputDir, defaultName);
63345
+ return resolve74(outputDir, defaultName);
62828
63346
  }
62829
63347
  function stringParam(value) {
62830
63348
  return typeof value === "string" && value.trim() !== "" ? value : void 0;
@@ -63054,7 +63572,7 @@ async function ensureActionsRegistered() {
63054
63572
  const { executeSceneBuild: executeSceneBuild2 } = await Promise.resolve().then(() => (init_scene_build(), scene_build_exports));
63055
63573
  const projectRel = params.project ?? ".";
63056
63574
  const r = await executeSceneBuild2({
63057
- projectDir: resolve73(outputDir, projectRel),
63575
+ projectDir: resolve74(outputDir, projectRel),
63058
63576
  mode: params.mode,
63059
63577
  composer: params.composer,
63060
63578
  skipNarration: params.skipNarration,
@@ -63064,6 +63582,7 @@ async function ensureActionsRegistered() {
63064
63582
  voice: params.voice,
63065
63583
  imageProvider: params.imageProvider,
63066
63584
  imageQuality: params.quality,
63585
+ imageModel: params.imageModel,
63067
63586
  force: params.force
63068
63587
  });
63069
63588
  return {
@@ -63079,7 +63598,7 @@ async function ensureActionsRegistered() {
63079
63598
  const { executeSceneRender: executeSceneRender2 } = await Promise.resolve().then(() => (init_scene_render(), scene_render_exports));
63080
63599
  const projectRel = params.project ?? ".";
63081
63600
  const r = await executeSceneRender2({
63082
- projectDir: resolve73(outputDir, projectRel),
63601
+ projectDir: resolve74(outputDir, projectRel),
63083
63602
  root: params.root,
63084
63603
  output: params.output,
63085
63604
  fps: params.fps,
@@ -63103,8 +63622,8 @@ async function ensureActionsRegistered() {
63103
63622
  });
63104
63623
  }
63105
63624
  async function loadPipeline(filePath) {
63106
- const absPath = resolve73(process.cwd(), filePath);
63107
- if (!existsSync56(absPath)) {
63625
+ const absPath = resolve74(process.cwd(), filePath);
63626
+ if (!existsSync57(absPath)) {
63108
63627
  throw new Error(`Pipeline file not found: ${absPath}`);
63109
63628
  }
63110
63629
  const content = await readFile47(absPath, "utf-8");
@@ -63122,14 +63641,14 @@ async function loadPipeline(filePath) {
63122
63641
  }
63123
63642
  async function executePipeline(manifest2, options = {}) {
63124
63643
  await ensureActionsRegistered();
63125
- const outputDir = resolve73(process.cwd(), options.outputDir || `${manifest2.name}-output`);
63126
- await mkdir27(outputDir, { recursive: true });
63644
+ const outputDir = resolve74(process.cwd(), options.outputDir || `${manifest2.name}-output`);
63645
+ await mkdir28(outputDir, { recursive: true });
63127
63646
  const completedSteps = /* @__PURE__ */ new Map();
63128
63647
  const results = [];
63129
63648
  const startTime = Date.now();
63130
63649
  if (options.resume) {
63131
- const checkpointPath = resolve73(outputDir, CHECKPOINT_FILE);
63132
- if (existsSync56(checkpointPath)) {
63650
+ const checkpointPath = resolve74(outputDir, CHECKPOINT_FILE);
63651
+ if (existsSync57(checkpointPath)) {
63133
63652
  const checkpointContent = await readFile47(checkpointPath, "utf-8");
63134
63653
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
63135
63654
  for (const cs of checkpoint.completedSteps) {
@@ -63248,8 +63767,8 @@ async function executePipeline(manifest2, options = {}) {
63248
63767
  data: s.data
63249
63768
  }))
63250
63769
  };
63251
- await writeFile47(
63252
- resolve73(outputDir, CHECKPOINT_FILE),
63770
+ await writeFile48(
63771
+ resolve74(outputDir, CHECKPOINT_FILE),
63253
63772
  (0, import_yaml8.stringify)(checkpoint, { indent: 2 }),
63254
63773
  "utf-8"
63255
63774
  );
@@ -67717,7 +68236,7 @@ var init_elicit = __esm({
67717
68236
  });
67718
68237
 
67719
68238
  // src/tools/manifest/scene.ts
67720
- import { resolve as resolve74, relative as relative11 } from "node:path";
68239
+ import { resolve as resolve75, relative as relative11 } from "node:path";
67721
68240
  function summariseLint(result) {
67722
68241
  return {
67723
68242
  ok: result.ok,
@@ -67952,7 +68471,7 @@ var init_scene2 = __esm({
67952
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.",
67953
68472
  schema: sceneInitSchema,
67954
68473
  async execute(args, ctx) {
67955
- const dir = resolve74(ctx.workingDirectory, args.dir);
68474
+ const dir = resolve75(ctx.workingDirectory, args.dir);
67956
68475
  const result = await scaffoldSceneProject({
67957
68476
  dir,
67958
68477
  name: args.name,
@@ -68007,7 +68526,7 @@ var init_scene2 = __esm({
68007
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.",
68008
68527
  schema: sceneAddSchema,
68009
68528
  async execute(args, ctx) {
68010
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68529
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68011
68530
  const result = await executeSceneAdd({
68012
68531
  name: args.name,
68013
68532
  preset: args.preset ?? "simple",
@@ -68073,7 +68592,7 @@ var init_scene2 = __esm({
68073
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.",
68074
68593
  schema: sceneLintSchema,
68075
68594
  async execute(args, ctx) {
68076
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68595
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68077
68596
  const result = await runProjectLint({
68078
68597
  projectDir,
68079
68598
  rootRel: args.root,
@@ -68112,7 +68631,7 @@ var init_scene2 = __esm({
68112
68631
  description: "Apply deterministic mechanical scene repairs. Currently uses the safe lint auto-fix allow-list and never performs semantic creative rewrites.",
68113
68632
  schema: sceneRepairSchema,
68114
68633
  async execute(args, ctx) {
68115
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68634
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68116
68635
  const result = await executeSceneRepair({
68117
68636
  projectDir,
68118
68637
  rootRel: args.root,
@@ -68150,7 +68669,7 @@ var init_scene2 = __esm({
68150
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.",
68151
68670
  schema: sceneRenderSchema,
68152
68671
  async execute(args, ctx) {
68153
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68672
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68154
68673
  const runRender = (report) => executeSceneRender({
68155
68674
  projectDir,
68156
68675
  root: args.root,
@@ -68203,6 +68722,9 @@ var init_scene2 = __esm({
68203
68722
  videoProvider: external_exports.enum(["seedance", "grok", "kling", "runway", "veo"]).optional().describe("Video provider for per-beat video cues. Default seedance."),
68204
68723
  musicProvider: external_exports.enum(["elevenlabs", "replicate"]).optional().describe("Music provider for per-beat music cues. Default elevenlabs."),
68205
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
+ ),
68206
68728
  imageSize: external_exports.enum(["1024x1024", "1536x1024", "1024x1536"]).optional().describe("OpenAI image size. Default '1536x1024' (cinematic 16:9-ish)."),
68207
68729
  maxCostUsd: external_exports.number().optional().describe("Fail before provider spend when estimated cost exceeds this USD cap."),
68208
68730
  force: external_exports.boolean().optional().describe("Re-dispatch primitives even when cached assets exist.")
@@ -68239,7 +68761,7 @@ var init_scene2 = __esm({
68239
68761
  }
68240
68762
  }
68241
68763
  }
68242
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68764
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68243
68765
  const runBuild = (report) => executeSceneBuild({
68244
68766
  projectDir,
68245
68767
  stage: args.stage,
@@ -68257,6 +68779,7 @@ var init_scene2 = __esm({
68257
68779
  videoProvider: args.videoProvider,
68258
68780
  musicProvider: args.musicProvider,
68259
68781
  imageQuality: args.imageQuality,
68782
+ imageModel: args.imageModel,
68260
68783
  imageSize: args.imageSize,
68261
68784
  maxCostUsd: args.maxCostUsd,
68262
68785
  force: args.force,
@@ -68295,7 +68818,7 @@ var init_scene2 = __esm({
68295
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.",
68296
68819
  schema: sceneInstallSkillSchema,
68297
68820
  async execute(args, ctx) {
68298
- const projectDir = resolve74(ctx.workingDirectory, args.projectDir);
68821
+ const projectDir = resolve75(ctx.workingDirectory, args.projectDir);
68299
68822
  const result = await installHyperframesSkill({
68300
68823
  projectDir,
68301
68824
  global: args.global ?? false,
@@ -68340,7 +68863,7 @@ var init_scene2 = __esm({
68340
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.",
68341
68864
  schema: sceneComposePromptsSchema,
68342
68865
  async execute(args, ctx) {
68343
- const projectDir = resolve74(ctx.workingDirectory, args.projectDir);
68866
+ const projectDir = resolve75(ctx.workingDirectory, args.projectDir);
68344
68867
  const result = await getComposePrompts({
68345
68868
  projectDir,
68346
68869
  beatId: args.beat
@@ -68384,7 +68907,7 @@ var init_scene2 = __esm({
68384
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.",
68385
68908
  schema: sceneSubmitSchema,
68386
68909
  async execute(args, ctx) {
68387
- const projectDir = args.projectDir ? resolve74(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68910
+ const projectDir = args.projectDir ? resolve75(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
68388
68911
  const result = await executeSceneSubmit({
68389
68912
  projectDir,
68390
68913
  beatId: args.beat,
@@ -69047,13 +69570,13 @@ var init_edit = __esm({
69047
69570
  });
69048
69571
 
69049
69572
  // src/commands/ai-suggest-edit.ts
69050
- import { readFile as readFile48, writeFile as writeFile48 } from "node:fs/promises";
69051
- 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";
69052
69575
  async function executeSuggestEdit(options) {
69053
69576
  try {
69054
69577
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
69055
69578
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
69056
- const filePath = resolve75(process.cwd(), options.projectPath);
69579
+ const filePath = resolve76(process.cwd(), options.projectPath);
69057
69580
  const content = await readFile48(filePath, "utf-8");
69058
69581
  const data = JSON.parse(content);
69059
69582
  const project = Project.fromJSON(data);
@@ -69065,7 +69588,7 @@ async function executeSuggestEdit(options) {
69065
69588
  const first = suggestions[0];
69066
69589
  const applied = applySuggestion(project, first);
69067
69590
  if (applied) {
69068
- await writeFile48(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
69591
+ await writeFile49(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
69069
69592
  return {
69070
69593
  success: true,
69071
69594
  suggestions,
@@ -69094,7 +69617,7 @@ var init_ai_suggest_edit = __esm({
69094
69617
  });
69095
69618
 
69096
69619
  // src/tools/manifest/inspect.ts
69097
- import { resolve as resolve76 } from "node:path";
69620
+ import { resolve as resolve77 } from "node:path";
69098
69621
  var PROJECT_DIR_DESCRIPTION2, inspectProjectTool, inspectRenderTool, analyzeMediaTool, analyzeSuggestTool, inspectTools;
69099
69622
  var init_inspect = __esm({
69100
69623
  "src/tools/manifest/inspect.ts"() {
@@ -69120,11 +69643,11 @@ var init_inspect = __esm({
69120
69643
  report: external_exports.boolean().optional().describe("Write review-report.json. Default true.")
69121
69644
  }),
69122
69645
  async execute(args, ctx) {
69123
- const projectDir = args.projectDir ? resolve76(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69646
+ const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69124
69647
  const result = await inspectProject({
69125
69648
  projectDir,
69126
69649
  beatId: args.beat,
69127
- outputPath: args.outputPath ? resolve76(ctx.workingDirectory, args.outputPath) : void 0,
69650
+ outputPath: args.outputPath ? resolve77(ctx.workingDirectory, args.outputPath) : void 0,
69128
69651
  writeReport: args.report !== false
69129
69652
  });
69130
69653
  return {
@@ -69160,13 +69683,13 @@ var init_inspect = __esm({
69160
69683
  dryRun: external_exports.boolean().optional().describe("Preview resolved inputs without probing video or calling Gemini.")
69161
69684
  }),
69162
69685
  async execute(args, ctx) {
69163
- const projectDir = args.projectDir ? resolve76(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69686
+ const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69164
69687
  if (args.dryRun) {
69165
69688
  const result2 = await previewInspectRender({
69166
69689
  projectDir,
69167
69690
  beatId: args.beat,
69168
- videoPath: args.videoPath ? resolve76(ctx.workingDirectory, args.videoPath) : void 0,
69169
- 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,
69170
69693
  writeReport: args.report !== false,
69171
69694
  ai: args.ai === true,
69172
69695
  model: args.model
@@ -69187,8 +69710,8 @@ var init_inspect = __esm({
69187
69710
  const result = await inspectRender({
69188
69711
  projectDir,
69189
69712
  beatId: args.beat,
69190
- videoPath: args.videoPath ? resolve76(ctx.workingDirectory, args.videoPath) : void 0,
69191
- 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,
69192
69715
  writeReport: args.report !== false,
69193
69716
  ai: args.ai === true,
69194
69717
  model: args.model
@@ -69621,12 +70144,12 @@ var init_generate2 = __esm({
69621
70144
  });
69622
70145
 
69623
70146
  // src/tools/manifest/storyboard.ts
69624
- import { existsSync as existsSync57 } from "node:fs";
69625
- import { readFile as readFile49, writeFile as writeFile49 } from "node:fs/promises";
69626
- 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";
69627
70150
  async function readStoryboard2(projectDir) {
69628
- const path2 = resolve77(projectDir, "STORYBOARD.md");
69629
- if (!existsSync57(path2)) return null;
70151
+ const path2 = resolve78(projectDir, "STORYBOARD.md");
70152
+ if (!existsSync58(path2)) return null;
69630
70153
  return { path: path2, markdown: await readFile49(path2, "utf-8") };
69631
70154
  }
69632
70155
  var PROJECT_DIR_DESCRIPTION3, projectDirSchema, storyboardListTool, storyboardValidateTool, storyboardGetTool, storyboardSetTool, storyboardMoveTool, storyboardReviseTool, planTool, storyboardTools;
@@ -69652,7 +70175,7 @@ var init_storyboard2 = __esm({
69652
70175
  description: "List beats, ids, durations, and cue blocks from a project's STORYBOARD.md.",
69653
70176
  schema: projectDirSchema,
69654
70177
  async execute(args, ctx) {
69655
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70178
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69656
70179
  const file = await readStoryboard2(projectDir);
69657
70180
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69658
70181
  const parsed = parseStoryboard(file.markdown);
@@ -69680,7 +70203,7 @@ var init_storyboard2 = __esm({
69680
70203
  description: "Validate STORYBOARD.md beat ids and cue blocks.",
69681
70204
  schema: projectDirSchema,
69682
70205
  async execute(args, ctx) {
69683
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70206
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69684
70207
  const file = await readStoryboard2(projectDir);
69685
70208
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69686
70209
  const result = validateStoryboardMarkdown(file.markdown);
@@ -69715,7 +70238,7 @@ var init_storyboard2 = __esm({
69715
70238
  beat: external_exports.string().describe("Beat id to return.")
69716
70239
  }),
69717
70240
  async execute(args, ctx) {
69718
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70241
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69719
70242
  const file = await readStoryboard2(projectDir);
69720
70243
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69721
70244
  const beat = getStoryboardBeat(file.markdown, args.beat);
@@ -69743,7 +70266,7 @@ var init_storyboard2 = __esm({
69743
70266
  unset: external_exports.boolean().optional().describe("Remove the cue key.")
69744
70267
  }),
69745
70268
  async execute(args, ctx) {
69746
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70269
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69747
70270
  const file = await readStoryboard2(projectDir);
69748
70271
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69749
70272
  try {
@@ -69754,7 +70277,7 @@ var init_storyboard2 = __esm({
69754
70277
  value,
69755
70278
  unset: args.unset
69756
70279
  });
69757
- await writeFile49(file.path, next, "utf-8");
70280
+ await writeFile50(file.path, next, "utf-8");
69758
70281
  return {
69759
70282
  success: true,
69760
70283
  data: { beat: args.beat, key: args.key },
@@ -69778,7 +70301,7 @@ var init_storyboard2 = __esm({
69778
70301
  after: external_exports.string().describe("Beat id that should precede the moved beat.")
69779
70302
  }),
69780
70303
  async execute(args, ctx) {
69781
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70304
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69782
70305
  const file = await readStoryboard2(projectDir);
69783
70306
  if (!file) return { success: false, error: `STORYBOARD.md not found in ${projectDir}` };
69784
70307
  try {
@@ -69786,7 +70309,7 @@ var init_storyboard2 = __esm({
69786
70309
  beatId: args.beat,
69787
70310
  afterBeatId: args.after
69788
70311
  });
69789
- await writeFile49(file.path, next, "utf-8");
70312
+ await writeFile50(file.path, next, "utf-8");
69790
70313
  return {
69791
70314
  success: true,
69792
70315
  data: { beat: args.beat, after: args.after },
@@ -69812,7 +70335,7 @@ var init_storyboard2 = __esm({
69812
70335
  dryRun: external_exports.boolean().optional().describe("Preview the revised storyboard without writing.")
69813
70336
  }),
69814
70337
  async execute(args, ctx) {
69815
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70338
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69816
70339
  const result = await executeStoryboardRevision({
69817
70340
  projectDir,
69818
70341
  request: args.request,
@@ -69857,7 +70380,7 @@ var init_storyboard2 = __esm({
69857
70380
  force: external_exports.boolean().optional().describe("Plan regeneration even when outputs exist.")
69858
70381
  }),
69859
70382
  async execute(args, ctx) {
69860
- const projectDir = args.projectDir ? resolve77(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
70383
+ const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69861
70384
  const plan = await createBuildPlan({
69862
70385
  projectDir,
69863
70386
  stage: args.stage,
@@ -69900,9 +70423,9 @@ var init_storyboard2 = __esm({
69900
70423
  });
69901
70424
 
69902
70425
  // src/tools/manifest/design.ts
69903
- import { existsSync as existsSync58 } from "node:fs";
70426
+ import { existsSync as existsSync59 } from "node:fs";
69904
70427
  import { readFile as readFile50 } from "node:fs/promises";
69905
- import { resolve as resolve78, join as join29 } from "node:path";
70428
+ import { resolve as resolve79, join as join30 } from "node:path";
69906
70429
  var PROJECT_DIR_DESCRIPTION4, projectDirSchema2, designValidateTool, designTools;
69907
70430
  var init_design = __esm({
69908
70431
  "src/tools/manifest/design.ts"() {
@@ -69923,9 +70446,9 @@ var init_design = __esm({
69923
70446
  description: "Validate DESIGN.md front-matter color/typography tokens and sections (google-labs design.md format).",
69924
70447
  schema: projectDirSchema2,
69925
70448
  async execute(args, ctx) {
69926
- const projectDir = args.projectDir ? resolve78(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
69927
- const path2 = join29(projectDir, "DESIGN.md");
69928
- 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}` };
69929
70452
  const md = await readFile50(path2, "utf-8");
69930
70453
  const issues = validateDesignMarkdown(md);
69931
70454
  const design = parseDesign(md);
@@ -69949,9 +70472,9 @@ var init_design = __esm({
69949
70472
  });
69950
70473
 
69951
70474
  // src/tools/manifest/remix.ts
69952
- import { writeFile as writeFile50 } from "node:fs/promises";
70475
+ import { writeFile as writeFile51 } from "node:fs/promises";
69953
70476
  import { tmpdir as tmpdir4 } from "node:os";
69954
- import { join as join30 } from "node:path";
70477
+ import { join as join31 } from "node:path";
69955
70478
  var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, remixTools;
69956
70479
  var init_remix = __esm({
69957
70480
  "src/tools/manifest/remix.ts"() {
@@ -70064,8 +70587,8 @@ var init_remix = __esm({
70064
70587
  let resolvedPath = args.pipelinePath;
70065
70588
  let tempPath;
70066
70589
  if (args.pipelineYaml) {
70067
- tempPath = join30(tmpdir4(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
70068
- 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");
70069
70592
  resolvedPath = tempPath;
70070
70593
  }
70071
70594
  try {
@@ -70210,7 +70733,7 @@ var init_detect2 = __esm({
70210
70733
  });
70211
70734
 
70212
70735
  // src/tools/manifest/_project-io.ts
70213
- import { readFile as readFile51, writeFile as writeFile51 } from "node:fs/promises";
70736
+ import { readFile as readFile51, writeFile as writeFile52 } from "node:fs/promises";
70214
70737
  async function loadProject2(projectPath, cwd) {
70215
70738
  const absPath = await resolveTimelineFile(projectPath, cwd);
70216
70739
  const content = await readFile51(absPath, "utf-8");
@@ -70218,7 +70741,7 @@ async function loadProject2(projectPath, cwd) {
70218
70741
  return { project: Project.fromJSON(data), absPath };
70219
70742
  }
70220
70743
  async function saveProject(absPath, project) {
70221
- await writeFile51(absPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70744
+ await writeFile52(absPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70222
70745
  }
70223
70746
  var init_project_io = __esm({
70224
70747
  "src/tools/manifest/_project-io.ts"() {
@@ -70229,8 +70752,8 @@ var init_project_io = __esm({
70229
70752
  });
70230
70753
 
70231
70754
  // src/tools/manifest/timeline.ts
70232
- import { mkdir as mkdir28, writeFile as writeFile52 } from "node:fs/promises";
70233
- 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";
70234
70757
  var MEDIA_TYPES, timelineCreateTool, timelineInfoTool, timelineAddSourceTool, timelineAddClipTool, timelineSplitClipTool, timelineTrimClipTool, timelineMoveClipTool, timelineDeleteClipTool, timelineDuplicateClipTool, timelineAddEffectTool, timelineAddTrackTool, timelineListTool, timelineTools;
70235
70758
  var init_timeline = __esm({
70236
70759
  "src/tools/manifest/timeline.ts"() {
@@ -70268,18 +70791,18 @@ var init_timeline = __esm({
70268
70791
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
70269
70792
  }),
70270
70793
  async execute(args, ctx) {
70271
- 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));
70272
70795
  const project = new Project(projectName);
70273
70796
  if (args.fps) project.setFrameRate(args.fps);
70274
70797
  let outputPath;
70275
70798
  if (args.outputPath) {
70276
- outputPath = resolve79(ctx.workingDirectory, args.outputPath);
70799
+ outputPath = resolve80(ctx.workingDirectory, args.outputPath);
70277
70800
  } else {
70278
- const dirPath = resolve79(ctx.workingDirectory, args.name);
70279
- await mkdir28(dirPath, { recursive: true });
70280
- 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);
70281
70804
  }
70282
- await writeFile52(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70805
+ await writeFile53(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70283
70806
  return {
70284
70807
  success: true,
70285
70808
  data: { name: projectName, outputPath },
@@ -70331,7 +70854,7 @@ var init_timeline = __esm({
70331
70854
  }),
70332
70855
  async execute(args, ctx) {
70333
70856
  const { project, absPath } = await loadProject2(args.projectPath, ctx.workingDirectory);
70334
- const mediaPath = resolve79(ctx.workingDirectory, args.mediaPath);
70857
+ const mediaPath = resolve80(ctx.workingDirectory, args.mediaPath);
70335
70858
  const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
70336
70859
  const source = project.addSource({
70337
70860
  name: args.name ?? mediaPath.split("/").pop() ?? "media",
@@ -70578,11 +71101,11 @@ var init_timeline = __esm({
70578
71101
 
70579
71102
  // src/commands/_shared/project-list.ts
70580
71103
  import { readdir as readdir8, readFile as readFile52, stat as stat9 } from "node:fs/promises";
70581
- import { existsSync as existsSync59 } from "node:fs";
70582
- 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";
70583
71106
  async function executeProjectList(opts = {}) {
70584
- const workspaceDir = resolve80(opts.workspaceDir ?? ".");
70585
- if (!existsSync59(workspaceDir)) {
71107
+ const workspaceDir = resolve81(opts.workspaceDir ?? ".");
71108
+ if (!existsSync60(workspaceDir)) {
70586
71109
  return {
70587
71110
  success: false,
70588
71111
  workspaceDir,
@@ -70599,11 +71122,11 @@ async function executeProjectList(opts = {}) {
70599
71122
  if (entry.name === "node_modules") continue;
70600
71123
  if (entry.name.startsWith(".")) continue;
70601
71124
  if (entry.name.startsWith("_")) {
70602
- archivedCount += await countProjects(join31(workspaceDir, entry.name));
71125
+ archivedCount += await countProjects(join32(workspaceDir, entry.name));
70603
71126
  continue;
70604
71127
  }
70605
- const projectDir = join31(workspaceDir, entry.name);
70606
- if (!existsSync59(join31(projectDir, "STORYBOARD.md"))) continue;
71128
+ const projectDir = join32(workspaceDir, entry.name);
71129
+ if (!existsSync60(join32(projectDir, "STORYBOARD.md"))) continue;
70607
71130
  projects.push(await describeProject(entry.name, projectDir));
70608
71131
  }
70609
71132
  projects.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@@ -70613,7 +71136,7 @@ async function countProjects(dir) {
70613
71136
  try {
70614
71137
  const entries = await readdir8(dir, { withFileTypes: true });
70615
71138
  return entries.filter(
70616
- (e) => e.isDirectory() && existsSync59(join31(dir, e.name, "STORYBOARD.md"))
71139
+ (e) => e.isDirectory() && existsSync60(join32(dir, e.name, "STORYBOARD.md"))
70617
71140
  ).length;
70618
71141
  } catch {
70619
71142
  return 0;
@@ -70624,7 +71147,7 @@ async function describeProject(name, projectDir) {
70624
71147
  let storyboardDurationSec = 0;
70625
71148
  const timestamps = [];
70626
71149
  try {
70627
- const storyboardPath2 = join31(projectDir, "STORYBOARD.md");
71150
+ const storyboardPath2 = join32(projectDir, "STORYBOARD.md");
70628
71151
  const parsed = parseStoryboard(await loadStoryboardMarkdown(projectDir));
70629
71152
  beats = parsed.beats.length;
70630
71153
  storyboardDurationSec = Number(
@@ -70636,7 +71159,7 @@ async function describeProject(name, projectDir) {
70636
71159
  let buildStatus;
70637
71160
  let buildUpdatedAt;
70638
71161
  try {
70639
- const reportPath = join31(projectDir, "build-report.json");
71162
+ const reportPath = join32(projectDir, "build-report.json");
70640
71163
  const report = JSON.parse(await readFile52(reportPath, "utf-8"));
70641
71164
  buildStatus = report.status ?? report.phase;
70642
71165
  buildUpdatedAt = report.updatedAt;
@@ -70645,12 +71168,12 @@ async function describeProject(name, projectDir) {
70645
71168
  }
70646
71169
  let latestRender;
70647
71170
  try {
70648
- const rendersDir = join31(projectDir, "renders");
71171
+ const rendersDir = join32(projectDir, "renders");
70649
71172
  const files = await readdir8(rendersDir);
70650
71173
  let newest;
70651
71174
  for (const file of files) {
70652
71175
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
70653
- const { mtimeMs } = await stat9(join31(rendersDir, file));
71176
+ const { mtimeMs } = await stat9(join32(rendersDir, file));
70654
71177
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
70655
71178
  }
70656
71179
  if (newest) {
@@ -70682,8 +71205,8 @@ var init_project_list = __esm({
70682
71205
  });
70683
71206
 
70684
71207
  // src/tools/manifest/project.ts
70685
- import { resolve as resolve81 } from "node:path";
70686
- 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";
70687
71210
  var projectListTool, projectCreateTool, projectInfoTool, projectTools;
70688
71211
  var init_project2 = __esm({
70689
71212
  "src/tools/manifest/project.ts"() {
@@ -70707,7 +71230,7 @@ var init_project2 = __esm({
70707
71230
  }),
70708
71231
  async execute(args, ctx) {
70709
71232
  const result = await executeProjectList({
70710
- workspaceDir: args.dir ? resolve81(ctx.workingDirectory, args.dir) : ctx.workingDirectory
71233
+ workspaceDir: args.dir ? resolve82(ctx.workingDirectory, args.dir) : ctx.workingDirectory
70711
71234
  });
70712
71235
  if (!result.success) {
70713
71236
  return { success: false, error: result.error ?? "project list failed" };
@@ -70744,10 +71267,10 @@ var init_project2 = __esm({
70744
71267
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
70745
71268
  }),
70746
71269
  async execute(args, ctx) {
70747
- const outputPath = resolve81(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
71270
+ const outputPath = resolve82(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
70748
71271
  const project = new Project(args.name);
70749
71272
  if (args.fps) project.setFrameRate(args.fps);
70750
- await writeFile53(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71273
+ await writeFile54(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
70751
71274
  return {
70752
71275
  success: true,
70753
71276
  data: { name: args.name, outputPath },
@@ -70794,7 +71317,7 @@ var init_project2 = __esm({
70794
71317
 
70795
71318
  // src/commands/export.ts
70796
71319
  import { readFile as readFile53, access as access7 } from "node:fs/promises";
70797
- import { resolve as resolve82 } from "node:path";
71320
+ import { resolve as resolve83 } from "node:path";
70798
71321
  import { spawn as spawn3 } from "node:child_process";
70799
71322
  async function getMediaDuration(filePath, mediaType, defaultImageDuration = 5) {
70800
71323
  if (mediaType === "image") {
@@ -70866,7 +71389,7 @@ async function runExport(projectPath, outputPath, options = {}) {
70866
71389
  message: "Project has no clips to export"
70867
71390
  };
70868
71391
  }
70869
- const finalOutputPath = resolve82(process.cwd(), outputPath);
71392
+ const finalOutputPath = resolve83(process.cwd(), outputPath);
70870
71393
  const basePresetSettings = getPresetSettings(preset, summary.aspectRatio);
70871
71394
  const presetSettings = applyCustomOverrides(basePresetSettings, { bitrate, fps, resolution, codec });
70872
71395
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
@@ -71262,7 +71785,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
71262
71785
  return args;
71263
71786
  }
71264
71787
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
71265
- return new Promise((resolve86, reject) => {
71788
+ return new Promise((resolve87, reject) => {
71266
71789
  const ffmpeg = spawn3(ffmpegPath, args, {
71267
71790
  stdio: ["pipe", "pipe", "pipe"]
71268
71791
  });
@@ -71286,7 +71809,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
71286
71809
  });
71287
71810
  ffmpeg.on("close", (code) => {
71288
71811
  if (code === 0) {
71289
- resolve86();
71812
+ resolve87();
71290
71813
  } else {
71291
71814
  const errorMatch = stderr.match(/Error.*$/m);
71292
71815
  const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
@@ -71439,7 +71962,7 @@ var init_export = __esm({
71439
71962
  });
71440
71963
 
71441
71964
  // src/tools/manifest/export.ts
71442
- import { resolve as resolve83 } from "node:path";
71965
+ import { resolve as resolve84 } from "node:path";
71443
71966
  var exportVideoTool, exportTools;
71444
71967
  var init_export2 = __esm({
71445
71968
  "src/tools/manifest/export.ts"() {
@@ -71462,8 +71985,8 @@ var init_export2 = __esm({
71462
71985
  overwrite: external_exports.boolean().optional().describe("Overwrite existing output file (default: false)")
71463
71986
  }),
71464
71987
  async execute(args, ctx) {
71465
- const projectPath = resolve83(ctx.workingDirectory, args.projectPath);
71466
- const outputPath = resolve83(ctx.workingDirectory, args.outputPath);
71988
+ const projectPath = resolve84(ctx.workingDirectory, args.projectPath);
71989
+ const outputPath = resolve84(ctx.workingDirectory, args.outputPath);
71467
71990
  const result = await runExport(projectPath, outputPath, {
71468
71991
  preset: args.preset,
71469
71992
  format: args.format,
@@ -71482,8 +72005,8 @@ var init_export2 = __esm({
71482
72005
  });
71483
72006
 
71484
72007
  // src/tools/manifest/agent-only.ts
71485
- import { readFile as readFile54, writeFile as writeFile54, readdir as readdir9, stat as stat10, access as access8, unlink as unlink2 } from "node:fs/promises";
71486
- 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";
71487
72010
  function matchPattern(filename, pattern) {
71488
72011
  const regex2 = new RegExp(
71489
72012
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -71563,12 +72086,12 @@ var init_agent_only = __esm({
71563
72086
  async execute(args, ctx) {
71564
72087
  const dirPath = args.path ?? ".";
71565
72088
  try {
71566
- const absPath = resolve84(ctx.workingDirectory, dirPath);
72089
+ const absPath = resolve85(ctx.workingDirectory, dirPath);
71567
72090
  const entries = await readdir9(absPath, { withFileTypes: true });
71568
72091
  const results = [];
71569
72092
  for (const entry of entries) {
71570
72093
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
71571
- const fullPath = join32(absPath, entry.name);
72094
+ const fullPath = join33(absPath, entry.name);
71572
72095
  const stats = await stat10(fullPath);
71573
72096
  if (entry.isDirectory()) {
71574
72097
  results.push(`[DIR] ${entry.name}/`);
@@ -71596,7 +72119,7 @@ var init_agent_only = __esm({
71596
72119
  }),
71597
72120
  async execute(args, ctx) {
71598
72121
  try {
71599
- const absPath = resolve84(ctx.workingDirectory, args.path);
72122
+ const absPath = resolve85(ctx.workingDirectory, args.path);
71600
72123
  const content = await readFile54(absPath, "utf-8");
71601
72124
  const maxLength = 4e3;
71602
72125
  const truncated = content.length > maxLength;
@@ -71621,8 +72144,8 @@ var init_agent_only = __esm({
71621
72144
  }),
71622
72145
  async execute(args, ctx) {
71623
72146
  try {
71624
- const absPath = resolve84(ctx.workingDirectory, args.path);
71625
- await writeFile54(absPath, args.content, "utf-8");
72147
+ const absPath = resolve85(ctx.workingDirectory, args.path);
72148
+ await writeFile55(absPath, args.content, "utf-8");
71626
72149
  return { success: true, data: { bytes: args.content.length }, humanLines: [`File written: ${args.path} (${formatSize(args.content.length)})`] };
71627
72150
  } catch (error) {
71628
72151
  return { success: false, error: `Failed to write file: ${error instanceof Error ? error.message : String(error)}` };
@@ -71641,7 +72164,7 @@ var init_agent_only = __esm({
71641
72164
  path: external_exports.string().describe("Path to check")
71642
72165
  }),
71643
72166
  async execute(args, ctx) {
71644
- const absPath = resolve84(ctx.workingDirectory, args.path);
72167
+ const absPath = resolve85(ctx.workingDirectory, args.path);
71645
72168
  try {
71646
72169
  await access8(absPath);
71647
72170
  const stats = await stat10(absPath);
@@ -71669,11 +72192,11 @@ var init_agent_only = __esm({
71669
72192
  }),
71670
72193
  async execute(args, ctx) {
71671
72194
  try {
71672
- const filePath = resolve84(ctx.workingDirectory, args.project);
72195
+ const filePath = resolve85(ctx.workingDirectory, args.project);
71673
72196
  const content = await readFile54(filePath, "utf-8");
71674
72197
  const data = JSON.parse(content);
71675
72198
  const project = Project.fromJSON(data);
71676
- const dirPath = resolve84(ctx.workingDirectory, args.directory);
72199
+ const dirPath = resolve85(ctx.workingDirectory, args.directory);
71677
72200
  const filterExts = args.filter ? args.filter.split(",").map((e) => e.trim().toLowerCase()) : null;
71678
72201
  const imageDuration = args.imageDuration ?? 5;
71679
72202
  const recursive = args.recursive ?? false;
@@ -71681,7 +72204,7 @@ var init_agent_only = __esm({
71681
72204
  const scanDir = async (dir) => {
71682
72205
  const entries = await readdir9(dir, { withFileTypes: true });
71683
72206
  for (const entry of entries) {
71684
- const entryPath = join32(dir, entry.name);
72207
+ const entryPath = join33(dir, entry.name);
71685
72208
  if (entry.isDirectory() && recursive) {
71686
72209
  await scanDir(entryPath);
71687
72210
  } else if (entry.isFile()) {
@@ -71708,7 +72231,7 @@ var init_agent_only = __esm({
71708
72231
  const source = project.addSource({ name: mediaName, type: mediaType, url: mediaFile, duration });
71709
72232
  addedSources.push({ id: source.id, name: mediaName, type: mediaType });
71710
72233
  }
71711
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72234
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71712
72235
  return {
71713
72236
  success: true,
71714
72237
  data: { count: addedSources.length, sources: addedSources },
@@ -71741,7 +72264,7 @@ var init_agent_only = __esm({
71741
72264
  }),
71742
72265
  async execute(args, ctx) {
71743
72266
  try {
71744
- const filePath = resolve84(ctx.workingDirectory, args.project);
72267
+ const filePath = resolve85(ctx.workingDirectory, args.project);
71745
72268
  const content = await readFile54(filePath, "utf-8");
71746
72269
  const data = JSON.parse(content);
71747
72270
  const project = Project.fromJSON(data);
@@ -71778,7 +72301,7 @@ var init_agent_only = __esm({
71778
72301
  createdClips.push({ id: clip.id, sourceName: source.name, startTime: currentTime, duration: source.duration });
71779
72302
  currentTime += source.duration + gap;
71780
72303
  }
71781
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72304
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71782
72305
  const totalDuration = currentTime - gap - startTime;
71783
72306
  return {
71784
72307
  success: true,
@@ -71812,7 +72335,7 @@ var init_agent_only = __esm({
71812
72335
  }),
71813
72336
  async execute(args, ctx) {
71814
72337
  try {
71815
- const filePath = resolve84(ctx.workingDirectory, args.project);
72338
+ const filePath = resolve85(ctx.workingDirectory, args.project);
71816
72339
  const content = await readFile54(filePath, "utf-8");
71817
72340
  const data = JSON.parse(content);
71818
72341
  const project = Project.fromJSON(data);
@@ -71841,7 +72364,7 @@ var init_agent_only = __esm({
71841
72364
  });
71842
72365
  if (effect) appliedEffects.push({ clipId: clip.id, effectId: effect.id });
71843
72366
  }
71844
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72367
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71845
72368
  return {
71846
72369
  success: true,
71847
72370
  data: { applied: appliedEffects.length, effectType: args.effectType },
@@ -71910,7 +72433,7 @@ var init_agent_only = __esm({
71910
72433
  removed.sources++;
71911
72434
  }
71912
72435
  }
71913
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72436
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71914
72437
  const parts = [];
71915
72438
  if (removed.clips > 0) parts.push(`${removed.clips} clips`);
71916
72439
  if (removed.tracks > 0) parts.push(`${removed.tracks} tracks`);
@@ -71961,7 +72484,7 @@ var init_agent_only = __esm({
71961
72484
  project.setFrameRate(args.fps);
71962
72485
  updates.push(`Frame Rate: ${args.fps} fps`);
71963
72486
  }
71964
- await writeFile54(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
72487
+ await writeFile55(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
71965
72488
  return {
71966
72489
  success: true,
71967
72490
  data: { updates },
@@ -72104,7 +72627,7 @@ var init_agent_only = __esm({
72104
72627
  }),
72105
72628
  async execute(args, ctx) {
72106
72629
  try {
72107
- const absPath = resolve84(ctx.workingDirectory, args.path);
72630
+ const absPath = resolve85(ctx.workingDirectory, args.path);
72108
72631
  const { stdout } = await execSafe(
72109
72632
  "ffprobe",
72110
72633
  ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", absPath],
@@ -72193,10 +72716,10 @@ Audio:`);
72193
72716
  }),
72194
72717
  async execute(args, ctx) {
72195
72718
  try {
72196
- const absInput = resolve84(ctx.workingDirectory, args.input);
72719
+ const absInput = resolve85(ctx.workingDirectory, args.input);
72197
72720
  const ext = args.input.split(".").pop() || "mp4";
72198
72721
  const baseName = args.input.replace(/\.[^/.]+$/, "");
72199
- 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}`);
72200
72723
  const crfValues = { low: 28, medium: 23, high: 18 };
72201
72724
  const crf = crfValues[args.quality ?? "medium"];
72202
72725
  await execSafe(
@@ -72256,8 +72779,8 @@ Audio:`);
72256
72779
  }),
72257
72780
  async execute(args, ctx) {
72258
72781
  try {
72259
- const absInput = resolve84(ctx.workingDirectory, args.input);
72260
- const absOutput = resolve84(ctx.workingDirectory, args.output);
72782
+ const absInput = resolve85(ctx.workingDirectory, args.input);
72783
+ const absOutput = resolve85(ctx.workingDirectory, args.output);
72261
72784
  const codecMap = {
72262
72785
  h264: "libx264",
72263
72786
  h265: "libx265",
@@ -72310,11 +72833,11 @@ Audio:`);
72310
72833
  };
72311
72834
  }
72312
72835
  try {
72313
- const absOutput = resolve84(ctx.workingDirectory, args.output);
72836
+ const absOutput = resolve85(ctx.workingDirectory, args.output);
72314
72837
  if (args.reencode) {
72315
72838
  const ffmpegArgs = [];
72316
72839
  for (const i of args.inputs) {
72317
- ffmpegArgs.push("-i", resolve84(ctx.workingDirectory, i));
72840
+ ffmpegArgs.push("-i", resolve85(ctx.workingDirectory, i));
72318
72841
  }
72319
72842
  const filterComplex = args.inputs.map((_, i) => `[${i}:v][${i}:a]`).join("");
72320
72843
  ffmpegArgs.push(
@@ -72329,9 +72852,9 @@ Audio:`);
72329
72852
  );
72330
72853
  await execSafe("ffmpeg", ffmpegArgs, { maxBuffer: 100 * 1024 * 1024 });
72331
72854
  } else {
72332
- const tempList = resolve84(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
72333
- const listContent = args.inputs.map((i) => `file '${resolve84(ctx.workingDirectory, i)}'`).join("\n");
72334
- 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");
72335
72858
  try {
72336
72859
  await execSafe(
72337
72860
  "ffmpeg",
@@ -72427,7 +72950,7 @@ var init_walkthrough = __esm({
72427
72950
  });
72428
72951
 
72429
72952
  // src/tools/manifest/status.ts
72430
- import { resolve as resolve85 } from "node:path";
72953
+ import { resolve as resolve86 } from "node:path";
72431
72954
  var PROJECT_DIR_DESCRIPTION5, statusJobTool, statusProjectTool, statusTools;
72432
72955
  var init_status = __esm({
72433
72956
  "src/tools/manifest/status.ts"() {
@@ -72451,7 +72974,7 @@ var init_status = __esm({
72451
72974
  output: external_exports.string().optional().describe("Download result media to this path when complete.")
72452
72975
  }),
72453
72976
  async execute(args, ctx) {
72454
- 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);
72455
72978
  const record = await readJobRecord(args.jobId, projectDir);
72456
72979
  if (!record) return { success: false, error: `Job not found: ${args.jobId}` };
72457
72980
  const result = args.refresh === false ? makeJobStatusResult(record, {
@@ -72460,7 +72983,7 @@ var init_status = __esm({
72460
72983
  warnings: []
72461
72984
  }) : await refreshJobRecord(record, {
72462
72985
  wait: args.wait,
72463
- output: args.output ? resolve85(ctx.workingDirectory, args.output) : void 0,
72986
+ output: args.output ? resolve86(ctx.workingDirectory, args.output) : void 0,
72464
72987
  workingDirectory: ctx.workingDirectory
72465
72988
  });
72466
72989
  return {
@@ -72485,7 +73008,7 @@ var init_status = __esm({
72485
73008
  refresh: external_exports.boolean().optional().describe("Refresh active supported jobs before summarizing. Default false.")
72486
73009
  }),
72487
73010
  async execute(args, ctx) {
72488
- const projectDir = args.projectDir ? resolve85(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
73011
+ const projectDir = args.projectDir ? resolve86(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
72489
73012
  const result = await inspectProjectStatus(projectDir, { refresh: args.refresh === true });
72490
73013
  return {
72491
73014
  success: true,
@@ -72774,7 +73297,7 @@ init_source();
72774
73297
  // package.json
72775
73298
  var package_default = {
72776
73299
  name: "@vibeframe/cli",
72777
- version: "0.115.1",
73300
+ version: "0.115.2",
72778
73301
  description: "Frontier AI video generation for coding agents - Seedance, Runway, Veo, and Kling on your own keys, behind a hard cost cap",
72779
73302
  private: false,
72780
73303
  license: "MIT",
@@ -79649,12 +80172,12 @@ function resolveCopier() {
79649
80172
  async function copyToClipboard(text) {
79650
80173
  const opener = resolveCopier();
79651
80174
  if (!opener) return false;
79652
- return new Promise((resolve86) => {
80175
+ return new Promise((resolve87) => {
79653
80176
  let settled = false;
79654
80177
  const finish = (ok) => {
79655
80178
  if (settled) return;
79656
80179
  settled = true;
79657
- resolve86(ok);
80180
+ resolve87(ok);
79658
80181
  };
79659
80182
  try {
79660
80183
  const child = spawn2(opener.command, opener.args, {
@@ -82991,8 +83514,8 @@ init_scene_build();
82991
83514
  init_build_plan();
82992
83515
  init_storyboard_parse();
82993
83516
  init_output();
82994
- import { resolve as resolve63, join as join28 } from "node:path";
82995
- 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";
82996
83519
  var VALID_MODES2 = ["agent", "batch", "auto"];
82997
83520
  var VALID_STAGES2 = ["assets", "transcript", "compose", "sync", "render", "all"];
82998
83521
  var VALID_IMAGE_PROVIDERS2 = ["openai", "gemini", "grok"];
@@ -83000,14 +83523,17 @@ var VALID_VIDEO_PROVIDERS2 = ["seedance", "grok", "kling", "runway", "veo"];
83000
83523
  var VALID_MUSIC_PROVIDERS2 = ["elevenlabs", "replicate"];
83001
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(
83002
83525
  "--composer <provider>",
83003
- "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."
83004
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(
83005
83528
  "--skip-keyframe",
83006
83529
  "Don't generate keyframe stills (review keyframes first with --skip-video, then build)"
83007
83530
  ).option("--skip-music", "Don't dispatch music generation even when beats declare music cues").option(
83008
83531
  "--skip-transcript",
83009
83532
  "Don't transcribe narration for word-sync (default: transcribe when narration + OpenAI key exist)"
83010
- ).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(
83011
83537
  "after",
83012
83538
  `
83013
83539
  Examples:
@@ -83018,7 +83544,7 @@ Examples:
83018
83544
  Advanced equivalent: \`vibe scene build\`.`
83019
83545
  ).action(async (projectDirArg, options) => {
83020
83546
  const startedAt = Date.now();
83021
- const projectDir = resolve63(projectDirArg);
83547
+ const projectDir = resolve64(projectDirArg);
83022
83548
  const mode = String(options.mode ?? "auto");
83023
83549
  if (!VALID_MODES2.includes(mode)) {
83024
83550
  exitWithError(
@@ -83075,6 +83601,7 @@ Advanced equivalent: \`vibe scene build\`.`
83075
83601
  videoProvider,
83076
83602
  musicProvider,
83077
83603
  imageQuality: options.quality,
83604
+ imageModel: options.imageModel,
83078
83605
  imageSize: options.imageSize,
83079
83606
  force: options.force ?? false
83080
83607
  };
@@ -83094,6 +83621,7 @@ Advanced equivalent: \`vibe scene build\`.`
83094
83621
  voice: options.voice,
83095
83622
  imageProvider,
83096
83623
  imageQuality: options.quality,
83624
+ imageModel: options.imageModel,
83097
83625
  imageSize: options.imageSize,
83098
83626
  videoProvider,
83099
83627
  musicProvider,
@@ -83168,6 +83696,7 @@ Advanced equivalent: \`vibe scene build\`.`
83168
83696
  videoProvider,
83169
83697
  musicProvider,
83170
83698
  imageQuality: options.quality,
83699
+ imageModel: options.imageModel,
83171
83700
  imageSize: options.imageSize,
83172
83701
  force: options.force,
83173
83702
  onProgress: (e) => {
@@ -83350,8 +83879,8 @@ function buildPlanValidationFailureData(plan) {
83350
83879
  };
83351
83880
  }
83352
83881
  function estimateBuildCost(params) {
83353
- const storyboardPath2 = join28(params.projectDir, "STORYBOARD.md");
83354
- if (!existsSync51(storyboardPath2)) return null;
83882
+ const storyboardPath2 = join29(params.projectDir, "STORYBOARD.md");
83883
+ if (!existsSync52(storyboardPath2)) return null;
83355
83884
  let beats = 0;
83356
83885
  try {
83357
83886
  const md = readFileSync3(storyboardPath2, "utf-8");
@@ -83485,7 +84014,7 @@ init_source();
83485
84014
  init_ora();
83486
84015
  init_scene_render();
83487
84016
  init_output();
83488
- import { resolve as resolve64 } from "node:path";
84017
+ import { resolve as resolve65 } from "node:path";
83489
84018
  var VALID_FPS = [24, 30, 60];
83490
84019
  var VALID_QUALITIES = ["draft", "standard", "high"];
83491
84020
  var VALID_FORMATS = ["mp4", "webm", "mov"];
@@ -83496,7 +84025,7 @@ Examples:
83496
84025
 
83497
84026
  Alias note: this is the project-level entrypoint for \`vibe scene render\`.`).action(async (projectDirArg, options) => {
83498
84027
  const startedAt = Date.now();
83499
- const projectDir = resolve64(projectDirArg);
84028
+ const projectDir = resolve65(projectDirArg);
83500
84029
  const fps = parseFps(String(options.fps));
83501
84030
  const quality = parseQuality(String(options.quality));
83502
84031
  const format = parseFormat(String(options.format));
@@ -83638,7 +84167,7 @@ init_source();
83638
84167
  init_ora();
83639
84168
  init_scene_render();
83640
84169
  init_output();
83641
- import { resolve as resolve65 } from "node:path";
84170
+ import { resolve as resolve66 } from "node:path";
83642
84171
  var VALID_FPS2 = [24, 30, 60];
83643
84172
  var VALID_QUALITIES2 = ["draft", "standard", "high"];
83644
84173
  var VALID_FORMATS2 = ["mp4", "webm", "mov"];
@@ -83656,7 +84185,7 @@ Examples:
83656
84185
  $ vibe preview my-video --beat hook
83657
84186
  $ vibe preview my-video --fps 30 # override the cheap default`).action(async (projectDirArg, options) => {
83658
84187
  const startedAt = Date.now();
83659
- const projectDir = resolve65(projectDirArg);
84188
+ const projectDir = resolve66(projectDirArg);
83660
84189
  const fps = parseFps2(String(options.fps));
83661
84190
  const quality = parseQuality2(String(options.quality));
83662
84191
  const format = parseFormat2(String(options.format));
@@ -83793,21 +84322,35 @@ init_esm();
83793
84322
  init_source();
83794
84323
  init_ora();
83795
84324
  init_scene_assemble();
84325
+ init_footage_assemble();
83796
84326
  init_output();
83797
- import { resolve as resolve66 } from "node:path";
84327
+ import { resolve as resolve67 } from "node:path";
83798
84328
  var VALID_FORMATS3 = ["mp4", "webm", "mov"];
83799
84329
  var DEFAULT_VIDEO = "preview.mp4";
83800
- 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", `
83801
- The assemble stage lays the project's <audio> elements onto a silent video in
83802
- 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\`:
83803
84336
 
83804
84337
  $ vibe render my-video --silent -o draft.mp4
83805
84338
  $ vibe assemble my-video --video draft.mp4
83806
84339
 
83807
- Plain \`vibe render\` already renders + assembles in one shot - use this only when
83808
- 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) => {
83809
84348
  const startedAt = Date.now();
83810
- const projectDir = resolve66(projectDirArg);
84349
+ const projectDir = resolve67(projectDirArg);
84350
+ if (options.footage) {
84351
+ await runFootageAssemble(projectDir, options, startedAt);
84352
+ return;
84353
+ }
83811
84354
  const format = parseFormat3(String(options.format));
83812
84355
  const video = String(options.video ?? DEFAULT_VIDEO);
83813
84356
  const params = { projectDir, root: options.root, video, format };
@@ -83844,6 +84387,73 @@ you want the two steps separated (e.g. swap the audio bed without re-rendering).
83844
84387
  }
83845
84388
  printAssembleResult(spinner2, result);
83846
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
+ }
83847
84457
  function printAssembleDryRun(projectDirArg, params) {
83848
84458
  console.log();
83849
84459
  console.log(source_default.bold.cyan("VibeFrame Assemble - dry run"));
@@ -83879,7 +84489,7 @@ init_source();
83879
84489
  init_cost_tier();
83880
84490
  init_status_jobs();
83881
84491
  init_output();
83882
- import { resolve as resolve67 } from "node:path";
84492
+ import { resolve as resolve68 } from "node:path";
83883
84493
  var statusCommand = new Command("status").description("Inspect local async jobs and project workflow status").addHelpText("after", `
83884
84494
  Examples:
83885
84495
  $ vibe status job job_abc123 --json
@@ -83890,7 +84500,7 @@ Examples:
83890
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) => {
83891
84501
  const startedAt = Date.now();
83892
84502
  try {
83893
- const projectDir = options.project ? resolve67(options.project) : findProjectRoot();
84503
+ const projectDir = options.project ? resolve68(options.project) : findProjectRoot();
83894
84504
  const record = await readJobRecord(jobId, projectDir);
83895
84505
  if (!record) {
83896
84506
  exitWithError(generalError(`Job not found: ${jobId}`, `Run 'vibe status project ${projectDir} --json' to list known jobs.`));
@@ -83918,7 +84528,7 @@ applyTier(statusCommand.commands[statusCommand.commands.length - 1], "free");
83918
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) => {
83919
84529
  const startedAt = Date.now();
83920
84530
  try {
83921
- const result = await inspectProjectStatus(resolve67(projectDirArg), {
84531
+ const result = await inspectProjectStatus(resolve68(projectDirArg), {
83922
84532
  refresh: options.refresh === true
83923
84533
  });
83924
84534
  if (isJsonMode()) {
@@ -84025,8 +84635,8 @@ init_dist2();
84025
84635
  init_exec_safe();
84026
84636
  init_exec_safe();
84027
84637
  init_api_key();
84028
- import { existsSync as existsSync52 } from "node:fs";
84029
- import { resolve as resolve68 } from "node:path";
84638
+ import { existsSync as existsSync53 } from "node:fs";
84639
+ import { resolve as resolve69 } from "node:path";
84030
84640
  init_composer_resolve();
84031
84641
  init_cost_tier();
84032
84642
  init_install_skill();
@@ -84157,13 +84767,13 @@ async function runDiagnostics() {
84157
84767
  installed = false;
84158
84768
  }
84159
84769
  } else if (toolName === "chrome") {
84160
- const { existsSync: existsSync60 } = await import("node:fs");
84770
+ const { existsSync: existsSync61 } = await import("node:fs");
84161
84771
  const { homedir: homedir8 } = await import("node:os");
84162
- const { join: join33 } = await import("node:path");
84772
+ const { join: join34 } = await import("node:path");
84163
84773
  const chromePaths = [
84164
84774
  process.env.HYPERFRAMES_CHROME_PATH,
84165
84775
  process.env.CHROME_PATH,
84166
- join33(
84776
+ join34(
84167
84777
  homedir8(),
84168
84778
  ".cache",
84169
84779
  "puppeteer",
@@ -84178,7 +84788,7 @@ async function runDiagnostics() {
84178
84788
  "/usr/bin/chromium",
84179
84789
  "/usr/bin/chromium-browser"
84180
84790
  ];
84181
- installed = chromePaths.some((p) => p && existsSync60(p));
84791
+ installed = chromePaths.some((p) => p && existsSync61(p));
84182
84792
  } else {
84183
84793
  installed = commandExists(toolName);
84184
84794
  }
@@ -84218,7 +84828,7 @@ async function runDiagnostics() {
84218
84828
  }
84219
84829
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
84220
84830
  path: rel,
84221
- exists: existsSync52(resolve68(cwd, rel))
84831
+ exists: existsSync53(resolve69(cwd, rel))
84222
84832
  }));
84223
84833
  const projectInitialized = projectFiles.some((f) => f.exists);
84224
84834
  const nearestProjectConfigPath = await findProjectConfigPath(cwd);
@@ -84236,7 +84846,7 @@ async function runDiagnostics() {
84236
84846
  } catch (err) {
84237
84847
  if (!(err instanceof ComposerResolveError)) throw err;
84238
84848
  }
84239
- const sceneProjectInCwd = existsSync52(resolve68(cwd, "STORYBOARD.md"));
84849
+ const sceneProjectInCwd = existsSync53(resolve69(cwd, "STORYBOARD.md"));
84240
84850
  const skillInstalled = hyperframesSkillAvailable(cwd);
84241
84851
  const skillPath = resolveSkillReference(cwd);
84242
84852
  return {
@@ -84589,7 +85199,7 @@ init_esm();
84589
85199
  init_source();
84590
85200
  init_exec_safe();
84591
85201
  init_cost_tier();
84592
- import { resolve as resolve69 } from "node:path";
85202
+ import { resolve as resolve70 } from "node:path";
84593
85203
  init_output();
84594
85204
  var hostCommand = new Command("host").description("Set up Codex, Claude, and Cursor app integrations").addHelpText(
84595
85205
  "after",
@@ -84603,7 +85213,7 @@ Examples:
84603
85213
  );
84604
85214
  hostCommand.command("list").description("List supported app hosts and integration surfaces").action(() => {
84605
85215
  const startedAt = Date.now();
84606
- const hosts = hostDefinitions(resolve69(".")).map((host) => ({
85216
+ const hosts = hostDefinitions(resolve70(".")).map((host) => ({
84607
85217
  id: host.id,
84608
85218
  label: host.label,
84609
85219
  surfaces: host.surfaces,
@@ -84630,7 +85240,7 @@ applyTier(hostCommand.commands[hostCommand.commands.length - 1], "free");
84630
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) => {
84631
85241
  const startedAt = Date.now();
84632
85242
  const selection = validateHostSelection(hostArg);
84633
- const projectDir = resolve69(projectDirArg);
85243
+ const projectDir = resolve70(projectDirArg);
84634
85244
  const ids = resolveHostSelection(selection);
84635
85245
  const defs = hostDefinitions(projectDir).filter((host) => ids.includes(host.id));
84636
85246
  const plans = [];
@@ -84664,7 +85274,7 @@ applyTier(hostCommand.commands[hostCommand.commands.length - 1], "free");
84664
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) => {
84665
85275
  const startedAt = Date.now();
84666
85276
  const selection = validateHostSelection(hostArg);
84667
- const projectDir = resolve69(projectDirArg);
85277
+ const projectDir = resolve70(projectDirArg);
84668
85278
  const ids = resolveHostSelection(selection);
84669
85279
  const defs = hostDefinitions(projectDir).filter((host) => ids.includes(host.id));
84670
85280
  const hosts = await Promise.all(defs.map((host) => inspectHost(host, projectDir)));
@@ -84784,10 +85394,10 @@ init_exec_safe();
84784
85394
  init_detect();
84785
85395
  init_ai_edit();
84786
85396
  init_output();
84787
- import { resolve as resolve70 } from "node:path";
84788
- import { existsSync as existsSync53 } from "node:fs";
84789
- import { mkdir as mkdir26, rm as rm6 } from "node:fs/promises";
84790
- 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");
84791
85401
  async function generateTestVideo(outputPath) {
84792
85402
  const filter = [
84793
85403
  // Video: color bars with text
@@ -84816,7 +85426,7 @@ async function generateTestVideo(outputPath) {
84816
85426
  "-y",
84817
85427
  outputPath
84818
85428
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
84819
- return existsSync53(outputPath);
85429
+ return existsSync54(outputPath);
84820
85430
  } catch {
84821
85431
  try {
84822
85432
  await execSafe("ffmpeg", [
@@ -84842,7 +85452,7 @@ async function generateTestVideo(outputPath) {
84842
85452
  "-y",
84843
85453
  outputPath
84844
85454
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
84845
- return existsSync53(outputPath);
85455
+ return existsSync54(outputPath);
84846
85456
  } catch {
84847
85457
  return false;
84848
85458
  }
@@ -84866,8 +85476,8 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84866
85476
  console.log(source_default.dim(" " + "\u2500".repeat(50)));
84867
85477
  console.log();
84868
85478
  }
84869
- await mkdir26(DEMO_DIR, { recursive: true });
84870
- const testVideo = resolve70(DEMO_DIR, "test-input.mp4");
85479
+ await mkdir27(DEMO_DIR, { recursive: true });
85480
+ const testVideo = resolve71(DEMO_DIR, "test-input.mp4");
84871
85481
  const genSpinner = !isJson ? ora("Generating test video...").start() : null;
84872
85482
  const generated = await generateTestVideo(testVideo);
84873
85483
  if (!generated) {
@@ -84896,7 +85506,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84896
85506
  name: "Silence Cut",
84897
85507
  description: "Removing silent segments",
84898
85508
  run: async () => {
84899
- const output = resolve70(DEMO_DIR, "silence-cut.mp4");
85509
+ const output = resolve71(DEMO_DIR, "silence-cut.mp4");
84900
85510
  const r = await executeSilenceCut({ videoPath: testVideo, outputPath: output, padding: 0.1 });
84901
85511
  if (r.success && r.totalDuration && r.silentDuration) {
84902
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)` };
@@ -84908,7 +85518,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84908
85518
  name: "Noise Reduce",
84909
85519
  description: "Reducing background noise",
84910
85520
  run: async () => {
84911
- const output = resolve70(DEMO_DIR, "noise-reduced.mp4");
85521
+ const output = resolve71(DEMO_DIR, "noise-reduced.mp4");
84912
85522
  const r = await executeNoiseReduce({ inputPath: testVideo, outputPath: output, strength: "medium" });
84913
85523
  return { success: r.success, detail: r.success ? `Cleaned audio \u2192 ${output}` : r.error };
84914
85524
  }
@@ -84917,7 +85527,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84917
85527
  name: "Fade Effects",
84918
85528
  description: "Adding fade in/out",
84919
85529
  run: async () => {
84920
- const output = resolve70(DEMO_DIR, "faded.mp4");
85530
+ const output = resolve71(DEMO_DIR, "faded.mp4");
84921
85531
  const r = await executeFade({ videoPath: testVideo, outputPath: output, fadeIn: 1, fadeOut: 1 });
84922
85532
  return { success: r.success, detail: r.success ? `1s fade in + 1s fade out \u2192 ${output}` : r.error };
84923
85533
  }
@@ -84984,20 +85594,20 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
84984
85594
 
84985
85595
  // src/commands/context.ts
84986
85596
  init_esm();
84987
- import { existsSync as existsSync54 } from "node:fs";
85597
+ import { existsSync as existsSync55 } from "node:fs";
84988
85598
  import { readFile as readFile45 } from "node:fs/promises";
84989
- import { resolve as resolve71, dirname as dirname22 } from "node:path";
85599
+ import { resolve as resolve72, dirname as dirname23 } from "node:path";
84990
85600
  import { fileURLToPath as fileURLToPath2 } from "node:url";
84991
85601
  var __filename = fileURLToPath2(import.meta.url);
84992
- var __dirname = dirname22(__filename);
85602
+ var __dirname = dirname23(__filename);
84993
85603
  function resolveContextPath(baseDir = __dirname) {
84994
85604
  const candidates = [
84995
85605
  // Bundled package layout: packages/cli/dist/index.js -> packages/cli/CONTEXT.md
84996
- resolve71(baseDir, "../CONTEXT.md"),
85606
+ resolve72(baseDir, "../CONTEXT.md"),
84997
85607
  // Source/dev layout: packages/cli/src/commands/context.ts -> packages/cli/CONTEXT.md
84998
- resolve71(baseDir, "../../CONTEXT.md")
85608
+ resolve72(baseDir, "../../CONTEXT.md")
84999
85609
  ];
85000
- return candidates.find((candidate) => existsSync54(candidate)) ?? null;
85610
+ return candidates.find((candidate) => existsSync55(candidate)) ?? null;
85001
85611
  }
85002
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) => {
85003
85613
  const options = {
@@ -86052,7 +86662,7 @@ async function startAgent(options = {}) {
86052
86662
  console.log();
86053
86663
  console.log(source_default.dim(" Commands: exit \xB7 reset \xB7 tools \xB7 context"));
86054
86664
  console.log();
86055
- return new Promise((resolve86) => {
86665
+ return new Promise((resolve87) => {
86056
86666
  if (typeof process.stdin.ref === "function") {
86057
86667
  process.stdin.ref();
86058
86668
  }
@@ -86159,7 +86769,7 @@ async function startAgent(options = {}) {
86159
86769
  clearInterval(keepalive);
86160
86770
  console.log();
86161
86771
  console.log(source_default.dim("Goodbye!"));
86162
- resolve86();
86772
+ resolve87();
86163
86773
  });
86164
86774
  rl.prompt();
86165
86775
  });
@@ -86433,8 +87043,8 @@ init_source();
86433
87043
  init_config();
86434
87044
  init_schema();
86435
87045
  init_api_key();
86436
- import { access as access9, writeFile as writeFile55, mkdir as mkdir29 } from "node:fs/promises";
86437
- 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";
86438
87048
  var BANNER_SHOWN_PATH = `${USER_CONFIG_DIR}/.banner-shown`;
86439
87049
  async function isFirstRun() {
86440
87050
  try {
@@ -86453,8 +87063,8 @@ async function isFirstRun() {
86453
87063
  }
86454
87064
  async function markBannerShown() {
86455
87065
  try {
86456
- await mkdir29(dirname24(BANNER_SHOWN_PATH), { recursive: true });
86457
- 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());
86458
87068
  } catch {
86459
87069
  }
86460
87070
  }