@vibeframe/mcp-server 0.113.0 → 0.113.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +131 -120
  2. package/dist/index.js +1852 -660
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -4742,14 +4742,14 @@ async function loadKokoroRuntime() {
4742
4742
  async function loadKokoroFromWorkspace(baseDir = process.cwd()) {
4743
4743
  try {
4744
4744
  const { readFile: readFile46 } = await import("node:fs/promises");
4745
- const { join: join45, resolve: resolve80 } = await import("node:path");
4745
+ const { join: join44, resolve: resolve80 } = await import("node:path");
4746
4746
  const { pathToFileURL } = await import("node:url");
4747
4747
  const pkgDir = resolve80(baseDir, "node_modules", "kokoro-js");
4748
- const pkg = JSON.parse(await readFile46(join45(pkgDir, "package.json"), "utf-8"));
4748
+ const pkg = JSON.parse(await readFile46(join44(pkgDir, "package.json"), "utf-8"));
4749
4749
  const entry = pkg.exports?.node?.import ?? pkg.exports?.default ?? pkg.module ?? pkg.main;
4750
4750
  if (!entry)
4751
4751
  return null;
4752
- const mod = await import(pathToFileURL(join45(pkgDir, entry)).href);
4752
+ const mod = await import(pathToFileURL(join44(pkgDir, entry)).href);
4753
4753
  return mod.KokoroTTS ?? null;
4754
4754
  } catch {
4755
4755
  return null;
@@ -4761,19 +4761,19 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
4761
4761
  return null;
4762
4762
  try {
4763
4763
  const { readFile: readFile46, writeFile: writeFile53, mkdir: mkdir30 } = await import("node:fs/promises");
4764
- const { join: join45, resolve: resolve80 } = await import("node:path");
4764
+ const { join: join44, resolve: resolve80 } = await import("node:path");
4765
4765
  const { pathToFileURL } = await import("node:url");
4766
4766
  const { homedir: homedir8 } = await import("node:os");
4767
4767
  const transformersDir = resolve80(base, "node_modules", "@huggingface", "transformers");
4768
4768
  const ortDistDir = resolve80(base, "node_modules", "onnxruntime-web", "dist");
4769
- const transformers = await import(pathToFileURL(join45(transformersDir, "dist", "transformers.web.js")).href);
4770
- const cacheRoot = join45(homedir8(), ".cache", "vibeframe", "models");
4769
+ const transformers = await import(pathToFileURL(join44(transformersDir, "dist", "transformers.web.js")).href);
4770
+ const cacheRoot = join44(homedir8(), ".cache", "vibeframe", "models");
4771
4771
  transformers.env.allowLocalModels = false;
4772
4772
  transformers.env.useCustomCache = true;
4773
4773
  transformers.env.customCache = {
4774
4774
  async match(key2) {
4775
4775
  try {
4776
- return new Response(await readFile46(join45(cacheRoot, encodeURIComponent(String(key2)))));
4776
+ return new Response(await readFile46(join44(cacheRoot, encodeURIComponent(String(key2)))));
4777
4777
  } catch {
4778
4778
  return void 0;
4779
4779
  }
@@ -4782,7 +4782,7 @@ async function loadBundledKokoroRuntime(runtimeDir = process.env.VIBE_KOKORO_RUN
4782
4782
  try {
4783
4783
  const buf = Buffer.from(await response.arrayBuffer());
4784
4784
  await mkdir30(cacheRoot, { recursive: true });
4785
- await writeFile53(join45(cacheRoot, encodeURIComponent(String(key2))), buf);
4785
+ await writeFile53(join44(cacheRoot, encodeURIComponent(String(key2))), buf);
4786
4786
  } catch {
4787
4787
  }
4788
4788
  }
@@ -10460,6 +10460,19 @@ var require_src = __commonJS({
10460
10460
  });
10461
10461
 
10462
10462
  // ../ai-providers/dist/fal/FalProvider.js
10463
+ function estimateSeedanceVideoCostUsd(opts) {
10464
+ const shortSide = SEEDANCE_SHORT_SIDE[opts.resolution ?? "720p"] ?? 720;
10465
+ const [a2, b2] = (opts.aspectRatio ?? "16:9").split(":").map(Number);
10466
+ const ratioFactor = a2 > 0 && b2 > 0 ? Math.max(a2, b2) / Math.min(a2, b2) : 16 / 9;
10467
+ const longSide = Math.round(shortSide * ratioFactor);
10468
+ const tokensPerSecond = shortSide * longSide * 24 / 1024;
10469
+ let usdPerSecond = tokensPerSecond / 1e3 * SEEDANCE_USD_PER_1K_TOKENS;
10470
+ if (opts.fast && shortSide <= 720)
10471
+ usdPerSecond *= SEEDANCE_FAST_FACTOR;
10472
+ if (opts.hasVideoReference)
10473
+ usdPerSecond *= SEEDANCE_VIDEO_REF_FACTOR;
10474
+ return Number((usdPerSecond * Math.max(0, opts.durationSec)).toFixed(2));
10475
+ }
10463
10476
  function formatFalError(error) {
10464
10477
  if (!(error instanceof Error))
10465
10478
  return String(error);
@@ -10569,7 +10582,7 @@ function normaliseDuration(value) {
10569
10582
  return "auto";
10570
10583
  return Math.max(4, Math.min(15, Math.round(value)));
10571
10584
  }
10572
- var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, FalProvider, falProvider;
10585
+ var import_client, ENDPOINT_TEXT_TO_VIDEO, ENDPOINT_IMAGE_TO_VIDEO, ENDPOINT_REFERENCE_TO_VIDEO, DEFAULT_VARIANT, VALID_RESOLUTIONS, VALID_ASPECTS, SEEDANCE_USD_PER_1K_TOKENS, SEEDANCE_FAST_FACTOR, SEEDANCE_VIDEO_REF_FACTOR, SEEDANCE_SHORT_SIDE, FalProvider, falProvider;
10573
10586
  var init_FalProvider = __esm({
10574
10587
  "../ai-providers/dist/fal/FalProvider.js"() {
10575
10588
  "use strict";
@@ -10589,6 +10602,10 @@ var init_FalProvider = __esm({
10589
10602
  DEFAULT_VARIANT = "seedance-2.0";
10590
10603
  VALID_RESOLUTIONS = ["480p", "720p", "1080p"];
10591
10604
  VALID_ASPECTS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"];
10605
+ SEEDANCE_USD_PER_1K_TOKENS = 0.014;
10606
+ SEEDANCE_FAST_FACTOR = 0.8;
10607
+ SEEDANCE_VIDEO_REF_FACTOR = 0.6;
10608
+ SEEDANCE_SHORT_SIDE = { "480p": 480, "720p": 720, "1080p": 1080 };
10592
10609
  FalProvider = class {
10593
10610
  constructor() {
10594
10611
  this.id = "seedance";
@@ -11413,6 +11430,7 @@ __export(dist_exports, {
11413
11430
  defineApiKey: () => defineApiKey,
11414
11431
  defineProvider: () => defineProvider,
11415
11432
  elevenLabsProvider: () => elevenLabsProvider,
11433
+ estimateSeedanceVideoCostUsd: () => estimateSeedanceVideoCostUsd,
11416
11434
  falProvider: () => falProvider,
11417
11435
  geminiProvider: () => geminiProvider,
11418
11436
  getAllApiKeys: () => getAllApiKeys,
@@ -24219,7 +24237,534 @@ var init_build_asset_metadata = __esm({
24219
24237
  }
24220
24238
  });
24221
24239
 
24240
+ // ../cli/src/commands/_shared/review-report.ts
24241
+ import { writeFile as writeFile6 } from "node:fs/promises";
24242
+ import { join as join2, resolve as resolve5 } from "node:path";
24243
+ function statusFromIssues(issues) {
24244
+ if (issues.some((issue) => issue.severity === "error")) return "fail";
24245
+ if (issues.some((issue) => issue.severity === "warning")) return "warn";
24246
+ return "pass";
24247
+ }
24248
+ function scoreIssues(issues) {
24249
+ let score = 100;
24250
+ for (const issue of issues) {
24251
+ if (issue.severity === "error") score -= 25;
24252
+ else if (issue.severity === "warning") score -= 8;
24253
+ else score -= 2;
24254
+ }
24255
+ return Math.max(0, Math.min(100, score));
24256
+ }
24257
+ function uniqueRetryWith(items) {
24258
+ return [
24259
+ ...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
24260
+ ];
24261
+ }
24262
+ function commandReviewAction(command3, opts) {
24263
+ const classified = classifyReviewCommand(command3);
24264
+ return stripUndefined({
24265
+ id: actionId("command", command3),
24266
+ kind: "command",
24267
+ label: opts.label ?? command3,
24268
+ command: command3,
24269
+ fixOwner: opts.fixOwner ?? classified.fixOwner,
24270
+ costTier: opts.costTier ?? classified.costTier,
24271
+ safeToAutoRun: opts.safeToAutoRun ?? classified.safeToAutoRun,
24272
+ requiresConfirmation: opts.requiresConfirmation ?? classified.requiresConfirmation,
24273
+ reason: opts.reason,
24274
+ sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
24275
+ });
24276
+ }
24277
+ function agentReviewAction(opts) {
24278
+ return stripUndefined({
24279
+ id: actionId("agent", opts.agentPrompt),
24280
+ kind: "agent",
24281
+ label: opts.label,
24282
+ agentPrompt: opts.agentPrompt,
24283
+ fixOwner: "host-agent",
24284
+ costTier: "unknown",
24285
+ safeToAutoRun: false,
24286
+ requiresConfirmation: false,
24287
+ reason: opts.reason,
24288
+ sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
24289
+ });
24290
+ }
24291
+ function manualReviewAction(opts) {
24292
+ return stripUndefined({
24293
+ id: actionId("manual", opts.label),
24294
+ kind: "manual",
24295
+ label: opts.label,
24296
+ fixOwner: opts.fixOwner ?? "host-agent",
24297
+ costTier: "unknown",
24298
+ safeToAutoRun: false,
24299
+ requiresConfirmation: false,
24300
+ reason: opts.reason,
24301
+ sourceIssueCodes: uniqueStrings(opts.sourceIssueCodes ?? [])
24302
+ });
24303
+ }
24304
+ function reviewActionsFromRetryWith(retryWith, sourceIssueCodes = []) {
24305
+ return normalizeReviewActions(
24306
+ retryWith.map((item) => {
24307
+ if (/^codex\s+/i.test(item)) {
24308
+ return agentReviewAction({
24309
+ label: "Ask the host agent to fix review issues",
24310
+ agentPrompt: item,
24311
+ reason: "The review report contains host-agent-owned issues.",
24312
+ sourceIssueCodes
24313
+ });
24314
+ }
24315
+ return commandReviewAction(item, {
24316
+ reason: "Backward-compatible retryWith command.",
24317
+ sourceIssueCodes
24318
+ });
24319
+ })
24320
+ );
24321
+ }
24322
+ function normalizeReviewActions(actions) {
24323
+ const merged = /* @__PURE__ */ new Map();
24324
+ for (const action of actions) {
24325
+ const key2 = [action.kind, action.command ?? action.agentPrompt ?? action.label].join("\0");
24326
+ const existing = merged.get(key2);
24327
+ if (!existing) {
24328
+ merged.set(key2, {
24329
+ ...action,
24330
+ sourceIssueCodes: uniqueStrings(action.sourceIssueCodes ?? [])
24331
+ });
24332
+ continue;
24333
+ }
24334
+ merged.set(key2, {
24335
+ ...existing,
24336
+ safeToAutoRun: existing.safeToAutoRun && action.safeToAutoRun,
24337
+ requiresConfirmation: existing.requiresConfirmation || action.requiresConfirmation,
24338
+ sourceIssueCodes: uniqueStrings([
24339
+ ...existing.sourceIssueCodes ?? [],
24340
+ ...action.sourceIssueCodes ?? []
24341
+ ])
24342
+ });
24343
+ }
24344
+ return [...merged.values()].map(
24345
+ (action) => stripUndefined({
24346
+ ...action,
24347
+ sourceIssueCodes: action.sourceIssueCodes && action.sourceIssueCodes.length > 0 ? action.sourceIssueCodes : void 0
24348
+ })
24349
+ );
24350
+ }
24351
+ function deriveNextReviewActions(opts) {
24352
+ return normalizeReviewActions([
24353
+ ...opts.issues.flatMap((issue) => issue.actions ?? []),
24354
+ ...reviewActionsFromRetryWith(opts.retryWith)
24355
+ ]);
24356
+ }
24357
+ function fixOwnerForIssue(issue) {
24358
+ if (issue.fixOwner) return issue.fixOwner;
24359
+ if (issue.code.startsWith("AI_REVIEW_")) return "host-agent";
24360
+ if (issue.code.startsWith("STORYBOARD_")) return "host-agent";
24361
+ if (issue.code === "PROJECT_NOT_FOUND" || issue.code === "MISSING_STORYBOARD" || issue.code === "BEAT_NOT_FOUND" || issue.code === "MISSING_DESIGN") {
24362
+ return "host-agent";
24363
+ }
24364
+ return "vibe";
24365
+ }
24366
+ function normalizeReviewIssues(issues, opts) {
24367
+ const fallbackOwner = typeof opts === "string" ? opts : opts?.fallbackOwner;
24368
+ const projectDir = typeof opts === "string" ? void 0 : opts?.projectDir;
24369
+ return issues.map((issue) => {
24370
+ const normalized = {
24371
+ ...issue,
24372
+ fixOwner: issue.fixOwner ?? fallbackOwner ?? fixOwnerForIssue(issue)
24373
+ };
24374
+ const actions = normalizeReviewActions([
24375
+ ...issue.actions ?? [],
24376
+ ...projectDir ? defaultActionsForIssue(normalized, projectDir) : []
24377
+ ]);
24378
+ return stripUndefined({
24379
+ ...normalized,
24380
+ actions: actions.length > 0 ? actions : void 0
24381
+ });
24382
+ });
24383
+ }
24384
+ function summarizeReviewIssues(issues) {
24385
+ const normalized = normalizeReviewIssues(issues);
24386
+ return {
24387
+ issueCount: normalized.length,
24388
+ errorCount: normalized.filter((issue) => issue.severity === "error").length,
24389
+ warningCount: normalized.filter((issue) => issue.severity === "warning").length,
24390
+ infoCount: normalized.filter((issue) => issue.severity === "info").length,
24391
+ fixOwners: {
24392
+ vibe: normalized.filter((issue) => issue.fixOwner === "vibe").length,
24393
+ hostAgent: normalized.filter((issue) => issue.fixOwner === "host-agent").length
24394
+ }
24395
+ };
24396
+ }
24397
+ function buildReviewReport(opts) {
24398
+ const retryWith = uniqueRetryWith(opts.retryWith);
24399
+ const issues = normalizeReviewIssues(opts.issues, {
24400
+ projectDir: opts.project,
24401
+ retryWith
24402
+ });
24403
+ return stripUndefined({
24404
+ schemaVersion: "1",
24405
+ kind: "review",
24406
+ project: resolve5(opts.project),
24407
+ mode: opts.mode,
24408
+ beat: opts.beat,
24409
+ status: opts.status,
24410
+ score: opts.score,
24411
+ issues,
24412
+ summary: summarizeReviewIssues(issues),
24413
+ nextActions: deriveNextReviewActions({ issues, retryWith, projectDir: opts.project }),
24414
+ retryWith,
24415
+ sourceReports: opts.sourceReports ?? [],
24416
+ reportPath: opts.reportPath
24417
+ });
24418
+ }
24419
+ function defaultReviewReportPath(projectDir) {
24420
+ return join2(resolve5(projectDir), "review-report.json");
24421
+ }
24422
+ async function writeReviewReport(path14, report) {
24423
+ await writeFile6(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
24424
+ }
24425
+ function stripUndefined(value) {
24426
+ return Object.fromEntries(Object.entries(value).filter(([, v2]) => v2 !== void 0));
24427
+ }
24428
+ function defaultActionsForIssue(issue, projectDir) {
24429
+ const code = issue.code;
24430
+ const beat = issue.beatId ?? issue.scene;
24431
+ const beatFlag = beat ? ` --beat ${beat}` : "";
24432
+ const sourceIssueCodes = [code];
24433
+ if (code === "PROJECT_NOT_FOUND" || code === "MISSING_STORYBOARD") {
24434
+ return [
24435
+ commandReviewAction(`vibe init ${projectDir} --from "<brief>" --json`, {
24436
+ label: "Initialize the project from a brief",
24437
+ fixOwner: "host-agent",
24438
+ reason: "The project cannot be inspected until the core project files exist.",
24439
+ sourceIssueCodes
24440
+ })
24441
+ ];
24442
+ }
24443
+ if (code === "MISSING_DESIGN" || code === "DESIGN_PLACEHOLDER_FIELD") {
24444
+ return [
24445
+ manualReviewAction({
24446
+ label: "Create or complete DESIGN.md",
24447
+ reason: "Visual direction needs host-agent or human input before VibeFrame can repair it.",
24448
+ sourceIssueCodes
24449
+ })
24450
+ ];
24451
+ }
24452
+ if (code === "BEAT_NOT_FOUND" || code.startsWith("STORYBOARD_")) {
24453
+ const actions = [
24454
+ commandReviewAction(`vibe storyboard validate ${projectDir} --json`, {
24455
+ label: "Validate the storyboard",
24456
+ fixOwner: "host-agent",
24457
+ reason: "The storyboard issue needs structured validation before editing.",
24458
+ sourceIssueCodes
24459
+ })
24460
+ ];
24461
+ if (code === "STORYBOARD_PLACEHOLDER_CUE") {
24462
+ actions.push(
24463
+ commandReviewAction(
24464
+ `vibe storyboard revise ${projectDir} --from "<make cues concrete>" --dry-run --json`,
24465
+ {
24466
+ label: "Preview a storyboard revision",
24467
+ fixOwner: "host-agent",
24468
+ reason: "Placeholder cues should be revised into concrete video intent.",
24469
+ sourceIssueCodes
24470
+ }
24471
+ )
24472
+ );
24473
+ }
24474
+ return actions;
24475
+ }
24476
+ if (code === "MISSING_ROOT_COMPOSITION" || code === "ROOT_SYNC_CHECK_FAILED") {
24477
+ return [
24478
+ commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
24479
+ label: "Sync the project root composition",
24480
+ reason: "The root composition is missing or could not be verified.",
24481
+ sourceIssueCodes
24482
+ })
24483
+ ];
24484
+ }
24485
+ if (code.startsWith("ROOT_")) {
24486
+ return issue.fixOwner === "host-agent" ? [
24487
+ agentReviewAction({
24488
+ label: "Repair the root composition shell",
24489
+ agentPrompt: "Fix host-agent-owned root composition issues from review-report.json",
24490
+ reason: "The root composition cannot be repaired deterministically.",
24491
+ sourceIssueCodes
24492
+ })
24493
+ ] : [
24494
+ commandReviewAction(`vibe scene repair ${projectDir} --json`, {
24495
+ label: "Repair deterministic scene/root issues",
24496
+ reason: "The root composition has deterministic sync issues.",
24497
+ sourceIssueCodes
24498
+ }),
24499
+ commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
24500
+ label: "Resync root composition after repair",
24501
+ reason: "The root composition should be synced after deterministic repair.",
24502
+ sourceIssueCodes
24503
+ })
24504
+ ];
24505
+ }
24506
+ if (code === "MISSING_COMPOSITION") {
24507
+ return [
24508
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage compose --json`, {
24509
+ label: beat ? `Compose beat ${beat}` : "Compose missing scenes",
24510
+ reason: "Missing scene compositions must be generated or authored before render.",
24511
+ sourceIssueCodes
24512
+ })
24513
+ ];
24514
+ }
24515
+ if (code === "MISSING_BUILD_REPORT") {
24516
+ return [
24517
+ commandReviewAction(`vibe build ${projectDir} --dry-run --json`, {
24518
+ label: "Preview the build plan",
24519
+ reason: "A dry-run build report is needed before choosing a paid or mutating build step.",
24520
+ sourceIssueCodes
24521
+ })
24522
+ ];
24523
+ }
24524
+ if (code === "BUILD_REPORT_BEAT_MISSING") {
24525
+ return [
24526
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
24527
+ label: beat ? `Sync build report for beat ${beat}` : "Sync the build report",
24528
+ reason: "The build report does not include the inspected beat.",
24529
+ sourceIssueCodes
24530
+ })
24531
+ ];
24532
+ }
24533
+ if (code === "MALFORMED_BUILD_REPORT") {
24534
+ return [
24535
+ commandReviewAction(`vibe build ${projectDir} --json`, {
24536
+ label: "Regenerate the build report",
24537
+ reason: "The existing build report cannot be parsed.",
24538
+ sourceIssueCodes
24539
+ })
24540
+ ];
24541
+ }
24542
+ if (code === "MISSING_REPORTED_ASSET" || code === "STALE_ASSET" || code === "UNKNOWN_ASSET_FRESHNESS") {
24543
+ return [
24544
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage assets --force --json`, {
24545
+ label: beat ? `Regenerate assets for beat ${beat}` : "Regenerate stale or missing assets",
24546
+ reason: "Asset generation may call providers, so the host should confirm first.",
24547
+ sourceIssueCodes
24548
+ })
24549
+ ];
24550
+ }
24551
+ if (code === "MUSIC_CUE_NOT_READY") {
24552
+ return [
24553
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage assets --json`, {
24554
+ label: beat ? `Generate music for beat ${beat}` : "Generate missing music assets",
24555
+ reason: "Music generation may call providers, so the host should confirm first.",
24556
+ sourceIssueCodes
24557
+ }),
24558
+ commandReviewAction(`vibe build ${projectDir} --stage sync --json`, {
24559
+ label: "Sync music into the root composition",
24560
+ reason: "Generated music needs root composition wiring.",
24561
+ sourceIssueCodes
24562
+ })
24563
+ ];
24564
+ }
24565
+ if (code.startsWith("SCENE_LINT_")) {
24566
+ return [
24567
+ commandReviewAction(`vibe scene repair ${projectDir} --json`, {
24568
+ label: "Repair deterministic scene lint issues",
24569
+ reason: "Scene lint reported an issue VibeFrame may be able to repair mechanically.",
24570
+ sourceIssueCodes
24571
+ })
24572
+ ];
24573
+ }
24574
+ if (code === "RENDER_NOT_FOUND" || code === "EMPTY_RENDER") {
24575
+ return [
24576
+ commandReviewAction(
24577
+ beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
24578
+ {
24579
+ label: beat ? `Render beat ${beat}` : "Render the project",
24580
+ reason: "A render file is required before render inspection can pass.",
24581
+ sourceIssueCodes
24582
+ }
24583
+ )
24584
+ ];
24585
+ }
24586
+ if (code === "NO_AUDIO_STREAM" || code === "DURATION_DRIFT") {
24587
+ return [
24588
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
24589
+ label: beat ? `Sync timing/audio for beat ${beat}` : "Sync timing and audio wiring",
24590
+ reason: "The render output no longer matches synced project state.",
24591
+ sourceIssueCodes
24592
+ }),
24593
+ commandReviewAction(
24594
+ beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
24595
+ {
24596
+ label: beat ? `Rerender beat ${beat}` : "Rerender the project",
24597
+ reason: "The render should be regenerated after sync repair.",
24598
+ sourceIssueCodes
24599
+ }
24600
+ )
24601
+ ];
24602
+ }
24603
+ if (code === "BLACK_FRAME_SEGMENT") {
24604
+ return issue.fixOwner === "host-agent" ? [
24605
+ agentReviewAction({
24606
+ label: beat ? `Fix black frames in beat ${beat}` : "Fix black frame segment",
24607
+ agentPrompt: "Fix black-frame issues from review-report.json, then rerender.",
24608
+ reason: "The issue likely needs scene, storyboard, or visual cue changes.",
24609
+ sourceIssueCodes
24610
+ })
24611
+ ] : [
24612
+ commandReviewAction(`vibe scene repair ${projectDir} --json`, {
24613
+ label: "Repair deterministic scene timing",
24614
+ reason: "Black frames outside a specific beat may be recoverable with scene repair.",
24615
+ sourceIssueCodes
24616
+ })
24617
+ ];
24618
+ }
24619
+ if (code === "STATIC_FRAME_SEGMENT") {
24620
+ return [
24621
+ agentReviewAction({
24622
+ label: beat ? `Add motion to beat ${beat}` : "Fix static visual hold",
24623
+ agentPrompt: "Fix static-frame issues from review-report.json, then rerender.",
24624
+ reason: "Static segments usually need creative scene, storyboard, or asset changes.",
24625
+ sourceIssueCodes
24626
+ })
24627
+ ];
24628
+ }
24629
+ if (code === "LONG_SILENCE") {
24630
+ return issue.fixOwner === "host-agent" ? [
24631
+ agentReviewAction({
24632
+ label: beat ? `Fix long silence in beat ${beat}` : "Fix long silence",
24633
+ agentPrompt: "Fix long-silence issues from review-report.json, then rerender.",
24634
+ reason: "The beat duration, narration, or music cue needs host-agent judgment.",
24635
+ sourceIssueCodes
24636
+ })
24637
+ ] : [
24638
+ commandReviewAction(`vibe build ${projectDir}${beatFlag} --stage sync --json`, {
24639
+ label: beat ? `Sync audio for beat ${beat}` : "Sync audio wiring",
24640
+ reason: "The silence may be caused by stale narration or music wiring.",
24641
+ sourceIssueCodes
24642
+ })
24643
+ ];
24644
+ }
24645
+ if (code.startsWith("AI_REVIEW_")) {
24646
+ return [
24647
+ agentReviewAction({
24648
+ label: "Fix AI review findings",
24649
+ agentPrompt: "Fix host-agent-owned AI review issues from review-report.json, then rerender.",
24650
+ reason: "AI review findings require storyboard, design, or composition judgment.",
24651
+ sourceIssueCodes
24652
+ })
24653
+ ];
24654
+ }
24655
+ if (code === "FFPROBE_UNAVAILABLE" || code === "FFMPEG_UNAVAILABLE") {
24656
+ return [
24657
+ manualReviewAction({
24658
+ label: "Install FFmpeg and rerun inspection",
24659
+ reason: "Local media inspection depends on ffmpeg/ffprobe being available.",
24660
+ fixOwner: "host-agent",
24661
+ sourceIssueCodes
24662
+ })
24663
+ ];
24664
+ }
24665
+ if (code === "FFPROBE_FAILED" || code === "NO_VIDEO_STREAM") {
24666
+ return [
24667
+ commandReviewAction(
24668
+ beat ? `vibe render ${projectDir} --beat ${beat} --json` : `vibe render ${projectDir} --json`,
24669
+ {
24670
+ label: beat ? `Rerender beat ${beat}` : "Rerender the project",
24671
+ reason: "The rendered media could not be probed as a valid video.",
24672
+ sourceIssueCodes
24673
+ }
24674
+ )
24675
+ ];
24676
+ }
24677
+ if (code === "ASPECT_MISMATCH") {
24678
+ return [
24679
+ manualReviewAction({
24680
+ label: "Check project aspect and render settings",
24681
+ reason: "Aspect mismatches can come from either config intent or render settings.",
24682
+ fixOwner: issue.fixOwner,
24683
+ sourceIssueCodes
24684
+ })
24685
+ ];
24686
+ }
24687
+ return [];
24688
+ }
24689
+ function classifyReviewCommand(command3) {
24690
+ const lower = command3.trim().toLowerCase();
24691
+ if (lower.startsWith("vibe status ")) return safe("free");
24692
+ if (lower.startsWith("vibe scene repair")) return safe("free");
24693
+ if (lower.startsWith("vibe storyboard validate") || lower.startsWith("vibe storyboard get") || lower.startsWith("vibe storyboard list")) {
24694
+ return safe("free", "host-agent");
24695
+ }
24696
+ if (lower.startsWith("vibe inspect project")) return safe("free");
24697
+ if (lower.startsWith("vibe inspect render")) {
24698
+ if (lower.includes("--ai")) return unsafe("low");
24699
+ return safe("free");
24700
+ }
24701
+ if (lower.startsWith("vibe render ")) return safe("free");
24702
+ if (lower.startsWith("vibe build ")) {
24703
+ if (lower.includes("--dry-run")) return safe("free");
24704
+ if (lower.includes("--stage sync") || lower.includes("--stage render")) return safe("free");
24705
+ return unsafe("unknown");
24706
+ }
24707
+ if (lower.startsWith("vibe generate video") || lower.startsWith("vibe edit fill-gaps") || lower.startsWith("vibe remix ")) {
24708
+ return unsafe("very-high");
24709
+ }
24710
+ if (lower.startsWith("vibe storyboard revise")) return unsafe("unknown", "host-agent");
24711
+ return unsafe("unknown");
24712
+ }
24713
+ function safe(costTier, fixOwner = "vibe") {
24714
+ return { fixOwner, costTier, safeToAutoRun: true, requiresConfirmation: false };
24715
+ }
24716
+ function unsafe(costTier, fixOwner = "vibe") {
24717
+ return { fixOwner, costTier, safeToAutoRun: false, requiresConfirmation: true };
24718
+ }
24719
+ function actionId(kind3, value) {
24720
+ const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
24721
+ return `${kind3}:${slug || "action"}`;
24722
+ }
24723
+ function uniqueStrings(items) {
24724
+ return [
24725
+ ...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
24726
+ ];
24727
+ }
24728
+ var init_review_report = __esm({
24729
+ "../cli/src/commands/_shared/review-report.ts"() {
24730
+ "use strict";
24731
+ }
24732
+ });
24733
+
24222
24734
  // ../cli/src/commands/_shared/storyboard-parse.ts
24735
+ function resolveCharacters(frontmatter) {
24736
+ const pool = frontmatter?.characters;
24737
+ if (!pool || typeof pool !== "object") return [];
24738
+ const resolved = [];
24739
+ for (const [name, spec] of Object.entries(pool)) {
24740
+ if (typeof spec === "string") {
24741
+ const prompt3 = spec.trim();
24742
+ if (prompt3) resolved.push({ name, prompt: prompt3 });
24743
+ continue;
24744
+ }
24745
+ if (spec && typeof spec === "object") {
24746
+ const image = typeof spec.image === "string" ? spec.image.trim() : "";
24747
+ if (image) {
24748
+ resolved.push({ name, imagePath: image });
24749
+ continue;
24750
+ }
24751
+ const description = typeof spec.description === "string" ? spec.description.trim() : "";
24752
+ if (description) resolved.push({ name, prompt: description });
24753
+ }
24754
+ }
24755
+ return resolved;
24756
+ }
24757
+ function beatCharacterNames(cues) {
24758
+ const raw2 = cues?.characters;
24759
+ if (typeof raw2 === "string") {
24760
+ const name = raw2.trim();
24761
+ return name ? [name] : [];
24762
+ }
24763
+ if (Array.isArray(raw2)) {
24764
+ return raw2.filter((v2) => typeof v2 === "string" && v2.trim().length > 0).map((v2) => v2.trim());
24765
+ }
24766
+ return [];
24767
+ }
24223
24768
  function parseStoryboard(md) {
24224
24769
  const normalized = md.replace(/\r\n/g, "\n");
24225
24770
  const { frontmatter, remaining } = extractProjectFrontmatter(normalized);
@@ -24332,28 +24877,28 @@ var init_storyboard_parse = __esm({
24332
24877
  // ../cli/src/commands/_shared/status-jobs.ts
24333
24878
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
24334
24879
  import { existsSync as existsSync2, statSync } from "node:fs";
24335
- import { copyFile, mkdir as mkdir3, readFile as readFile4, readdir, stat, writeFile as writeFile6 } from "node:fs/promises";
24336
- import { dirname as dirname3, join as join2, parse as parse2, relative, resolve as resolve5 } from "node:path";
24880
+ import { copyFile, mkdir as mkdir3, readFile as readFile4, readdir, stat, writeFile as writeFile7 } from "node:fs/promises";
24881
+ import { dirname as dirname3, join as join3, parse as parse2, relative, resolve as resolve6 } from "node:path";
24337
24882
  function isLocalJob(record) {
24338
24883
  return record.jobType === "build" || record.jobType === "render";
24339
24884
  }
24340
24885
  function findProjectRoot(start = process.cwd()) {
24341
- let dir = resolve5(start);
24886
+ let dir = resolve6(start);
24342
24887
  try {
24343
24888
  if (existsSync2(dir) && statSync(dir).isFile()) dir = dirname3(dir);
24344
24889
  } catch {
24345
24890
  }
24346
24891
  for (; ; ) {
24347
- if (existsSync2(join2(dir, "vibe.config.json")) || existsSync2(join2(dir, "STORYBOARD.md"))) {
24892
+ if (existsSync2(join3(dir, "vibe.config.json")) || existsSync2(join3(dir, "STORYBOARD.md"))) {
24348
24893
  return dir;
24349
24894
  }
24350
24895
  const parent = dirname3(dir);
24351
- if (parent === dir) return resolve5(start);
24896
+ if (parent === dir) return resolve6(start);
24352
24897
  dir = parent;
24353
24898
  }
24354
24899
  }
24355
24900
  function jobsDir(projectDir) {
24356
- return join2(resolve5(projectDir), ".vibeframe", "jobs");
24901
+ return join3(resolve6(projectDir), ".vibeframe", "jobs");
24357
24902
  }
24358
24903
  function normalizeJobStatus(status) {
24359
24904
  const value = String(status ?? "").toLowerCase();
@@ -24367,10 +24912,10 @@ function normalizeJobStatus(status) {
24367
24912
  }
24368
24913
  function createJobRecord(opts) {
24369
24914
  const now = opts.now ?? /* @__PURE__ */ new Date();
24370
- const projectDir = resolve5(
24915
+ const projectDir = resolve6(
24371
24916
  opts.projectDir ?? findProjectRoot(opts.workingDirectory ?? process.cwd())
24372
24917
  );
24373
- const workingDirectory = resolve5(opts.workingDirectory ?? process.cwd());
24918
+ const workingDirectory = resolve6(opts.workingDirectory ?? process.cwd());
24374
24919
  const id = opts.id ?? makeJobId(opts.providerTaskId, now);
24375
24920
  const base = {
24376
24921
  schemaVersion: "1",
@@ -24405,7 +24950,7 @@ function createJobRecord(opts) {
24405
24950
  message: opts.message
24406
24951
  };
24407
24952
  base.retryWith = retryWithForJob(base);
24408
- return stripUndefined(base);
24953
+ return stripUndefined2(base);
24409
24954
  }
24410
24955
  async function createAndWriteJobRecord(opts) {
24411
24956
  const record = createJobRecord(opts);
@@ -24415,15 +24960,15 @@ async function createAndWriteJobRecord(opts) {
24415
24960
  async function writeJobRecord(record) {
24416
24961
  const dir = jobsDir(record.projectDir);
24417
24962
  await mkdir3(dir, { recursive: true });
24418
- await writeFile6(
24963
+ await writeFile7(
24419
24964
  jobRecordPath(record.id, record.projectDir),
24420
- JSON.stringify(stripUndefined(record), null, 2) + "\n",
24965
+ JSON.stringify(stripUndefined2(record), null, 2) + "\n",
24421
24966
  "utf-8"
24422
24967
  );
24423
24968
  }
24424
24969
  async function readJobRecord(jobId, projectDir) {
24425
24970
  const id = normalizeJobId(jobId);
24426
- const project = resolve5(projectDir ?? findProjectRoot());
24971
+ const project = resolve6(projectDir ?? findProjectRoot());
24427
24972
  const path14 = jobRecordPath(id, project);
24428
24973
  if (!existsSync2(path14)) return null;
24429
24974
  return parseJobRecord(await readFile4(path14, "utf-8"));
@@ -24436,7 +24981,7 @@ async function listJobRecords(projectDir) {
24436
24981
  for (const entry of entries2) {
24437
24982
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
24438
24983
  try {
24439
- const parsed = parseJobRecord(await readFile4(join2(dir, entry.name), "utf-8"));
24984
+ const parsed = parseJobRecord(await readFile4(join3(dir, entry.name), "utf-8"));
24440
24985
  if (parsed) records.push(parsed);
24441
24986
  } catch {
24442
24987
  }
@@ -24530,9 +25075,9 @@ async function refreshLocalJobRecord(record, opts) {
24530
25075
  const heartbeatAge = record.heartbeatAt ? Date.now() - new Date(record.heartbeatAt).getTime() : Number.POSITIVE_INFINITY;
24531
25076
  const orphaned = !isPidAlive(record.pid) || heartbeatAge > LOCAL_JOB_ORPHAN_AFTER_MS;
24532
25077
  if (orphaned) {
24533
- const hint = `The process that ran this ${record.jobType} job is gone. Check ${join2(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
25078
+ const hint = `The process that ran this ${record.jobType} job is gone. Check ${join3(record.projectDir, record.jobType === "render" ? "render-report.json" : "build-report.json")} \u2014 the work may have completed before the process exited.`;
24534
25079
  warnings.push(hint);
24535
- updated = stripUndefined({
25080
+ updated = stripUndefined2({
24536
25081
  ...record,
24537
25082
  status: "unknown",
24538
25083
  error: record.error ?? hint,
@@ -24549,7 +25094,7 @@ async function refreshLocalJobRecord(record, opts) {
24549
25094
  });
24550
25095
  }
24551
25096
  async function inspectProjectStatus(projectDirArg, opts = {}) {
24552
- const project = resolve5(projectDirArg);
25097
+ const project = resolve6(projectDirArg);
24553
25098
  const warnings = [];
24554
25099
  let records = await listJobRecords(project);
24555
25100
  if (opts.refresh) {
@@ -24579,6 +25124,10 @@ async function inspectProjectStatus(projectDirArg, opts = {}) {
24579
25124
  ...review?.retryWith ?? [],
24580
25125
  ...workflow.retryWith
24581
25126
  ];
25127
+ const nextActions = normalizeReviewActions([
25128
+ ...review?.nextActions ?? [],
25129
+ ...reviewActionsFromRetryWith([...build?.retryWith ?? [], ...workflow.retryWith])
25130
+ ]);
24582
25131
  return {
24583
25132
  schemaVersion: "1",
24584
25133
  kind: "project",
@@ -24590,11 +25139,12 @@ async function inspectProjectStatus(projectDirArg, opts = {}) {
24590
25139
  review,
24591
25140
  jobs,
24592
25141
  warnings,
25142
+ nextActions,
24593
25143
  retryWith: unique(retryWith)
24594
25144
  };
24595
25145
  }
24596
25146
  function summarizeJob(record) {
24597
- return stripUndefined({
25147
+ return stripUndefined2({
24598
25148
  id: record.id,
24599
25149
  jobType: record.jobType,
24600
25150
  status: record.status,
@@ -24618,7 +25168,7 @@ function retryWithForJob(record) {
24618
25168
  ]);
24619
25169
  }
24620
25170
  function makeJobStatusResult(record, opts) {
24621
- return stripUndefined({
25171
+ return stripUndefined2({
24622
25172
  schemaVersion: "1",
24623
25173
  kind: "job",
24624
25174
  id: record.id,
@@ -24642,7 +25192,7 @@ function jobProgress(record) {
24642
25192
  if (record.status === "completed" || record.status === "failed" || record.status === "cancelled") {
24643
25193
  if (record.progress === void 0 && !record.providerStatus) return void 0;
24644
25194
  }
24645
- return stripUndefined({
25195
+ return stripUndefined2({
24646
25196
  percent: record.progress,
24647
25197
  phase: record.stage ?? record.providerStatus ?? statusPhase(record.status),
24648
25198
  providerStatus: record.providerStatus ?? record.message
@@ -24652,7 +25202,7 @@ function jobResult(record) {
24652
25202
  if (!record.resultUrl && !record.outputPath && !record.cachePath && !record.error && !record.resultPayload) {
24653
25203
  return null;
24654
25204
  }
24655
- return stripUndefined({
25205
+ return stripUndefined2({
24656
25206
  url: record.resultUrl,
24657
25207
  outputPath: record.outputPath,
24658
25208
  cachePath: record.cachePath,
@@ -24688,7 +25238,7 @@ function normalizeJobId(jobId) {
24688
25238
  return id;
24689
25239
  }
24690
25240
  function jobRecordPath(jobId, projectDir) {
24691
- return join2(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
25241
+ return join3(jobsDir(projectDir), `${normalizeJobId(jobId)}.json`);
24692
25242
  }
24693
25243
  function parseJobRecord(raw2) {
24694
25244
  const parsed = JSON.parse(raw2);
@@ -24713,11 +25263,11 @@ function updateRecordFromProvider(record, result) {
24713
25263
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
24714
25264
  };
24715
25265
  next.retryWith = retryWithForJob(next);
24716
- return stripUndefined(next);
25266
+ return stripUndefined2(next);
24717
25267
  }
24718
25268
  function resolveRefreshOutput(record, opts) {
24719
- if (opts.output) return resolve5(opts.workingDirectory ?? process.cwd(), opts.output);
24720
- return record.outputPath ? resolve5(record.outputPath) : void 0;
25269
+ if (opts.output) return resolve6(opts.workingDirectory ?? process.cwd(), opts.output);
25270
+ return record.outputPath ? resolve6(record.outputPath) : void 0;
24721
25271
  }
24722
25272
  function liveSupport(record) {
24723
25273
  if (record.jobType === "generate-video" && (record.provider === "grok" || record.provider === "runway" || record.provider === "kling" || record.provider === "veo")) {
@@ -24742,7 +25292,7 @@ async function downloadUrl(url, outputPath) {
24742
25292
  if (!response.ok) throw new Error(`Download failed: HTTP ${response.status}`);
24743
25293
  const buffer = Buffer.from(await response.arrayBuffer());
24744
25294
  await mkdir3(dirname3(outputPath), { recursive: true });
24745
- await writeFile6(outputPath, buffer);
25295
+ await writeFile7(outputPath, buffer);
24746
25296
  return outputPath;
24747
25297
  }
24748
25298
  async function maybeCacheOutput(record) {
@@ -24774,7 +25324,7 @@ async function maybeWriteCompletedAssetMetadata(record) {
24774
25324
  }
24775
25325
  }
24776
25326
  async function refreshBuildReportFromJobs(projectDir, records) {
24777
- const reportPath = join2(projectDir, "build-report.json");
25327
+ const reportPath = join3(projectDir, "build-report.json");
24778
25328
  const report = await readJson(reportPath);
24779
25329
  if (!report || !Array.isArray(report.beats)) return;
24780
25330
  let changed = false;
@@ -24802,7 +25352,7 @@ async function refreshBuildReportFromJobs(projectDir, records) {
24802
25352
  if (kind3 === "music" && record.assetOptions && typeof record.assetOptions.duration === "number") {
24803
25353
  nested.durationSec = record.assetOptions.duration;
24804
25354
  }
24805
- beatRecord[kind3] = stripUndefined(nested);
25355
+ beatRecord[kind3] = stripUndefined2(nested);
24806
25356
  beatRecord[`${kind3}Path`] = outputPath;
24807
25357
  beatRecord[`${kind3}Status`] = "generated";
24808
25358
  beatRecord[`${kind3}JobId`] = record.id;
@@ -24822,17 +25372,17 @@ async function refreshBuildReportFromJobs(projectDir, records) {
24822
25372
  }
24823
25373
  if (!changed) return;
24824
25374
  try {
24825
- await writeFile6(reportPath, JSON.stringify(stripUndefined(report), null, 2) + "\n", "utf-8");
25375
+ await writeFile7(reportPath, JSON.stringify(stripUndefined2(report), null, 2) + "\n", "utf-8");
24826
25376
  } catch {
24827
25377
  }
24828
25378
  }
24829
25379
  async function readBuildSummary(projectDir) {
24830
- const reportPath = join2(projectDir, "build-report.json");
25380
+ const reportPath = join3(projectDir, "build-report.json");
24831
25381
  const report = await readJson(reportPath);
24832
25382
  if (!report) return null;
24833
25383
  const info = await statOrNull(reportPath);
24834
25384
  const beatSummary = projectBeatReadinessFromUnknown(report.beatSummary);
24835
- return stripUndefined({
25385
+ return stripUndefined2({
24836
25386
  reportPath,
24837
25387
  kind: typeof report.kind === "string" ? report.kind : void 0,
24838
25388
  success: typeof report.success === "boolean" ? report.success : void 0,
@@ -24850,7 +25400,7 @@ async function readBuildSummary(projectDir) {
24850
25400
  });
24851
25401
  }
24852
25402
  async function readReviewSummary(projectDir) {
24853
- const reportPath = join2(projectDir, "review-report.json");
25403
+ const reportPath = join3(projectDir, "review-report.json");
24854
25404
  const report = await readJson(reportPath);
24855
25405
  if (!report) return null;
24856
25406
  const info = await statOrNull(reportPath);
@@ -24858,7 +25408,7 @@ async function readReviewSummary(projectDir) {
24858
25408
  const summary = report.summary && typeof report.summary === "object" ? report.summary : {};
24859
25409
  const fixOwners = summary.fixOwners && typeof summary.fixOwners === "object" ? summary.fixOwners : {};
24860
25410
  const computedFixOwners = countIssueFixOwners(issues);
24861
- return stripUndefined({
25411
+ return stripUndefined2({
24862
25412
  reportPath,
24863
25413
  kind: typeof report.kind === "string" ? report.kind : void 0,
24864
25414
  mode: typeof report.mode === "string" ? report.mode : void 0,
@@ -24883,9 +25433,44 @@ async function readReviewSummary(projectDir) {
24883
25433
  },
24884
25434
  sourceReports: stringArray(report.sourceReports),
24885
25435
  updatedAt: info?.mtime.toISOString(),
25436
+ nextActions: reviewActionsFromUnknown(report.nextActions),
24886
25437
  retryWith: stringArray(report.retryWith)
24887
25438
  });
24888
25439
  }
25440
+ function reviewActionsFromUnknown(value) {
25441
+ if (!Array.isArray(value)) return [];
25442
+ return value.flatMap((item) => {
25443
+ if (!item || typeof item !== "object") return [];
25444
+ const record = item;
25445
+ const kind3 = stringEnum(record.kind, ["command", "agent", "manual"]);
25446
+ const fixOwner = stringEnum(record.fixOwner, ["vibe", "host-agent"]);
25447
+ const costTier = stringEnum(record.costTier, [
25448
+ "free",
25449
+ "low",
25450
+ "high",
25451
+ "very-high",
25452
+ "unknown"
25453
+ ]);
25454
+ if (!kind3 || !fixOwner || !costTier || typeof record.id !== "string" || typeof record.label !== "string" || typeof record.safeToAutoRun !== "boolean" || typeof record.requiresConfirmation !== "boolean" || typeof record.reason !== "string") {
25455
+ return [];
25456
+ }
25457
+ return [
25458
+ stripUndefined2({
25459
+ id: record.id,
25460
+ kind: kind3,
25461
+ label: record.label,
25462
+ command: typeof record.command === "string" ? record.command : void 0,
25463
+ agentPrompt: typeof record.agentPrompt === "string" ? record.agentPrompt : void 0,
25464
+ fixOwner,
25465
+ costTier,
25466
+ safeToAutoRun: record.safeToAutoRun,
25467
+ requiresConfirmation: record.requiresConfirmation,
25468
+ reason: record.reason,
25469
+ sourceIssueCodes: stringArray(record.sourceIssueCodes)
25470
+ })
25471
+ ];
25472
+ });
25473
+ }
24889
25474
  function summarizeJobs(records) {
24890
25475
  return {
24891
25476
  total: records.length,
@@ -24904,7 +25489,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
24904
25489
  if (fromBeats) return fromBeats;
24905
25490
  if (build.beats) return augmentBeatReadinessFromJobs(build.beats, records, report?.jobs);
24906
25491
  }
24907
- const storyboardPath = join2(projectDir, "STORYBOARD.md");
25492
+ const storyboardPath = join3(projectDir, "STORYBOARD.md");
24908
25493
  if (!existsSync2(storyboardPath)) {
24909
25494
  return { total: 0, assetsReady: 0, compositionsReady: 0, needsAuthor: [] };
24910
25495
  }
@@ -24913,7 +25498,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
24913
25498
  const needsAuthor = [];
24914
25499
  let compositionsReady = 0;
24915
25500
  for (const beat of parsed.beats) {
24916
- if (existsSync2(join2(projectDir, "compositions", `scene-${beat.id}.html`))) {
25501
+ if (existsSync2(join3(projectDir, "compositions", `scene-${beat.id}.html`))) {
24917
25502
  compositionsReady += 1;
24918
25503
  } else {
24919
25504
  needsAuthor.push(beat.id);
@@ -24931,7 +25516,7 @@ async function readProjectBeatReadiness(projectDir, build, records) {
24931
25516
  }
24932
25517
  function deriveProjectWorkflow(opts) {
24933
25518
  const retryWith = [];
24934
- const hasStoryboard = existsSync2(join2(opts.project, "STORYBOARD.md"));
25519
+ const hasStoryboard = existsSync2(join3(opts.project, "STORYBOARD.md"));
24935
25520
  if (!hasStoryboard && !opts.build) {
24936
25521
  retryWith.push(`vibe init ${opts.project} --from "<brief>" --json`);
24937
25522
  return { status: "empty", currentStage: "init", retryWith };
@@ -25027,7 +25612,7 @@ function beatReadinessFromBuildBeats(projectDir, beats, records = [], reportJobs
25027
25612
  if (buildBeatAssetsReady(record) || (id ? buildBeatJobsReady(id, records, reportJobs) : false))
25028
25613
  assetsReady += 1;
25029
25614
  const compositionPath = typeof record.compositionPath === "string" ? record.compositionPath : id ? `compositions/scene-${id}.html` : void 0;
25030
- if (compositionPath && existsSync2(resolve5(projectDir, compositionPath))) {
25615
+ if (compositionPath && existsSync2(resolve6(projectDir, compositionPath))) {
25031
25616
  compositionsReady += 1;
25032
25617
  } else if (id) {
25033
25618
  needsAuthor.push(id);
@@ -25097,6 +25682,9 @@ function numberOr(fallback2, value) {
25097
25682
  function stringArray(value) {
25098
25683
  return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.length > 0) : [];
25099
25684
  }
25685
+ function stringEnum(value, allowed) {
25686
+ return typeof value === "string" && allowed.includes(value) ? value : void 0;
25687
+ }
25100
25688
  function isActiveStatus(status) {
25101
25689
  return status === "queued" || status === "running";
25102
25690
  }
@@ -25126,7 +25714,7 @@ function assetKindForJob(record) {
25126
25714
  return void 0;
25127
25715
  }
25128
25716
  function projectRelativePath(projectDir, path14) {
25129
- const rel = relative(resolve5(projectDir), resolve5(path14));
25717
+ const rel = relative(resolve6(projectDir), resolve6(path14));
25130
25718
  if (!rel || rel.startsWith("..")) return path14;
25131
25719
  return rel.replace(/\\/g, "/");
25132
25720
  }
@@ -25144,7 +25732,7 @@ async function statOrNull(path14) {
25144
25732
  return null;
25145
25733
  }
25146
25734
  }
25147
- function stripUndefined(value) {
25735
+ function stripUndefined2(value) {
25148
25736
  return Object.fromEntries(Object.entries(value).filter(([, v2]) => v2 !== void 0));
25149
25737
  }
25150
25738
  function unique(items) {
@@ -25162,6 +25750,7 @@ var init_status_jobs = __esm({
25162
25750
  init_ai_video();
25163
25751
  init_music_status();
25164
25752
  init_build_asset_metadata();
25753
+ init_review_report();
25165
25754
  init_storyboard_parse();
25166
25755
  LOCAL_JOB_ORPHAN_AFTER_MS = 9e4;
25167
25756
  }
@@ -25170,7 +25759,7 @@ var init_status_jobs = __esm({
25170
25759
  // ../cli/src/commands/_shared/project-config.ts
25171
25760
  import { existsSync as existsSync3 } from "node:fs";
25172
25761
  import { readFile as readFile5 } from "node:fs/promises";
25173
- import { resolve as resolve6 } from "node:path";
25762
+ import { resolve as resolve7 } from "node:path";
25174
25763
  function defaultProjectConfig(opts) {
25175
25764
  return {
25176
25765
  schemaVersion: "1",
@@ -25221,9 +25810,9 @@ function projectConfigJson(opts) {
25221
25810
  return JSON.stringify(defaultProjectConfig(opts), null, 2) + "\n";
25222
25811
  }
25223
25812
  async function readProjectConfig(projectDir) {
25224
- const dir = resolve6(projectDir);
25225
- const canonicalPath = resolve6(dir, VIBE_CONFIG_FILENAME);
25226
- const legacyPath = resolve6(dir, LEGACY_VIBE_PROJECT_FILENAME);
25813
+ const dir = resolve7(projectDir);
25814
+ const canonicalPath = resolve7(dir, VIBE_CONFIG_FILENAME);
25815
+ const legacyPath = resolve7(dir, LEGACY_VIBE_PROJECT_FILENAME);
25227
25816
  if (existsSync3(canonicalPath)) {
25228
25817
  try {
25229
25818
  const raw2 = await readFile5(canonicalPath, "utf-8");
@@ -25286,8 +25875,8 @@ var init_project_config = __esm({
25286
25875
  });
25287
25876
 
25288
25877
  // ../cli/src/commands/_shared/scene-project.ts
25289
- import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile7, access as access3 } from "node:fs/promises";
25290
- import { resolve as resolve7, basename as basename2 } from "node:path";
25878
+ import { mkdir as mkdir4, readFile as readFile6, writeFile as writeFile8, access as access3 } from "node:fs/promises";
25879
+ import { resolve as resolve8, basename as basename2 } from "node:path";
25291
25880
  function aspectToDims(aspect) {
25292
25881
  return ASPECT_DIMS[aspect];
25293
25882
  }
@@ -25585,6 +26174,43 @@ vibe host setup all --write # write project/app config
25585
26174
  vibe host doctor all --json # verify readiness
25586
26175
  \`\`\`
25587
26176
 
26177
+ ## Native host goal loop
26178
+
26179
+ For long-running work, use Codex Goal mode, Claude Code \`/goal\`, or the
26180
+ host's equivalent as the outer loop. VibeFrame should not run a competing
26181
+ project-level goal loop; it provides \`--json\` commands, dry runs, cost caps,
26182
+ \`build-report.json\`, \`review-report.json\`, \`nextActions\`, \`safeToAutoRun\`,
26183
+ \`requiresConfirmation\`, \`fixOwner\`, deterministic repair, and render
26184
+ inspection for the host agent to reason over.
26185
+
26186
+ Copy-paste examples:
26187
+
26188
+ \`\`\`text
26189
+ /goal Build this VibeFrame project into renders/final.mp4. Use --json for every
26190
+ vibe command, run --dry-run before paid operations, use --max-cost 5 for builds
26191
+ unless the user sets another budget, read build-report.json and
26192
+ review-report.json before deciding the next action, prefer nextActions before
26193
+ guessing, run only safeToAutoRun:true actions automatically, and ask before
26194
+ requiresConfirmation:true actions. Treat retryWith as the compatibility
26195
+ fallback, fixOwner:"vibe" as CLI repair work, and fixOwner:"host-agent" as
26196
+ STORYBOARD.md, DESIGN.md, or composition edits.
26197
+ Stop only when renders/final.mp4 exists, duration and aspect ratio match the
26198
+ brief, inspect render --cheap has no errors, any AI review score is >= 90 when AI review is requested, and every
26199
+ remaining host-agent issue is fixed, accepted with rationale, or reported as
26200
+ blocked.
26201
+ \`\`\`
26202
+
26203
+ \`\`\`text
26204
+ /goal Finish this VibeFrame render using Claude Code goal mode as the outer
26205
+ loop. Use vibe context/schema when unsure, --json everywhere, dry-run before
26206
+ paid operations, budget cap via --max-cost 5, nextActions before guessing,
26207
+ build-report.json/review-report.json as loop state, and safeToAutoRun,
26208
+ requiresConfirmation, and fixOwner to decide between vibe scene repair and
26209
+ host-agent edits. Stop only after final MP4,
26210
+ target duration/aspect ratio, clean render inspection, any AI review score >= 90 when AI review is requested, and
26211
+ no unresolved unacknowledged host-agent issues.
26212
+ \`\`\`
26213
+
25588
26214
  ## Skills \u2014 USE THESE FIRST
25589
26215
 
25590
26216
  @SKILL.md
@@ -25688,35 +26314,35 @@ tmp/
25688
26314
  `;
25689
26315
  }
25690
26316
  function describeSceneScaffold(opts) {
25691
- const dir = resolve7(opts.dir);
26317
+ const dir = resolve8(opts.dir);
25692
26318
  const profile = opts.profile ?? "full";
25693
26319
  const groups = {
25694
26320
  authoring: [
25695
- resolve7(dir, "STORYBOARD.md"),
25696
- resolve7(dir, "DESIGN.md"),
25697
- resolve7(dir, VIBE_CONFIG_FILENAME),
25698
- resolve7(dir, "vibe.project.yaml"),
25699
- resolve7(dir, ".gitignore")
26321
+ resolve8(dir, "STORYBOARD.md"),
26322
+ resolve8(dir, "DESIGN.md"),
26323
+ resolve8(dir, VIBE_CONFIG_FILENAME),
26324
+ resolve8(dir, "vibe.project.yaml"),
26325
+ resolve8(dir, ".gitignore")
25700
26326
  ],
25701
26327
  render: [],
25702
26328
  agent: []
25703
26329
  };
25704
26330
  if (profile === "full") {
25705
26331
  groups.render = [
25706
- resolve7(dir, "index.html"),
25707
- resolve7(dir, "compositions"),
25708
- resolve7(dir, "assets"),
25709
- resolve7(dir, "renders"),
25710
- resolve7(dir, "hyperframes.json"),
25711
- resolve7(dir, "meta.json")
26332
+ resolve8(dir, "index.html"),
26333
+ resolve8(dir, "compositions"),
26334
+ resolve8(dir, "assets"),
26335
+ resolve8(dir, "renders"),
26336
+ resolve8(dir, "hyperframes.json"),
26337
+ resolve8(dir, "meta.json")
25712
26338
  ];
25713
26339
  }
25714
26340
  if (profile === "agent" || profile === "full") {
25715
26341
  groups.agent = [
25716
- resolve7(dir, "AGENTS.md"),
25717
- resolve7(dir, "SKILL.md"),
25718
- resolve7(dir, "references"),
25719
- resolve7(dir, "CLAUDE.md")
26342
+ resolve8(dir, "AGENTS.md"),
26343
+ resolve8(dir, "SKILL.md"),
26344
+ resolve8(dir, "references"),
26345
+ resolve8(dir, "CLAUDE.md")
25720
26346
  ];
25721
26347
  }
25722
26348
  return groups;
@@ -25730,7 +26356,7 @@ async function pathExists(p2) {
25730
26356
  }
25731
26357
  }
25732
26358
  async function scaffoldSceneProject(opts) {
25733
- const dir = resolve7(opts.dir);
26359
+ const dir = resolve8(opts.dir);
25734
26360
  const name = opts.name ?? basename2(dir);
25735
26361
  const aspect = opts.aspect ?? "16:9";
25736
26362
  const duration = opts.duration ?? 10;
@@ -25738,103 +26364,103 @@ async function scaffoldSceneProject(opts) {
25738
26364
  const profile = opts.profile ?? "full";
25739
26365
  await mkdir4(dir, { recursive: true });
25740
26366
  if (profile === "full") {
25741
- await mkdir4(resolve7(dir, "compositions"), { recursive: true });
25742
- await mkdir4(resolve7(dir, "assets"), { recursive: true });
25743
- await mkdir4(resolve7(dir, "renders"), { recursive: true });
26367
+ await mkdir4(resolve8(dir, "compositions"), { recursive: true });
26368
+ await mkdir4(resolve8(dir, "assets"), { recursive: true });
26369
+ await mkdir4(resolve8(dir, "renders"), { recursive: true });
25744
26370
  }
25745
26371
  const created = [];
25746
26372
  const skipped2 = [];
25747
26373
  const merged = [];
25748
26374
  if (profile === "full") {
25749
- const hfPath = resolve7(dir, "hyperframes.json");
26375
+ const hfPath = resolve8(dir, "hyperframes.json");
25750
26376
  const hfDefaults = buildHyperframesConfig();
25751
26377
  if (await pathExists(hfPath)) {
25752
26378
  const existingRaw = await readFile6(hfPath, "utf-8");
25753
26379
  const existing = JSON.parse(existingRaw);
25754
26380
  const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
25755
- await writeFile7(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
26381
+ await writeFile8(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
25756
26382
  merged.push(hfPath);
25757
26383
  } else {
25758
- await writeFile7(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
26384
+ await writeFile8(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
25759
26385
  created.push(hfPath);
25760
26386
  }
25761
- const metaPath = resolve7(dir, "meta.json");
26387
+ const metaPath = resolve8(dir, "meta.json");
25762
26388
  if (await pathExists(metaPath)) {
25763
26389
  skipped2.push(metaPath);
25764
26390
  } else {
25765
- await writeFile7(
26391
+ await writeFile8(
25766
26392
  metaPath,
25767
26393
  JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n",
25768
26394
  "utf-8"
25769
26395
  );
25770
26396
  created.push(metaPath);
25771
26397
  }
25772
- const rootPath = resolve7(dir, "index.html");
26398
+ const rootPath = resolve8(dir, "index.html");
25773
26399
  if (await pathExists(rootPath)) {
25774
26400
  skipped2.push(rootPath);
25775
26401
  } else {
25776
- await writeFile7(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
26402
+ await writeFile8(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
25777
26403
  created.push(rootPath);
25778
26404
  }
25779
26405
  }
25780
- const vibeConfigJsonPath = resolve7(dir, VIBE_CONFIG_FILENAME);
26406
+ const vibeConfigJsonPath = resolve8(dir, VIBE_CONFIG_FILENAME);
25781
26407
  if (await pathExists(vibeConfigJsonPath)) {
25782
26408
  skipped2.push(vibeConfigJsonPath);
25783
26409
  } else {
25784
- await writeFile7(vibeConfigJsonPath, projectConfigJson({ name, aspect }), "utf-8");
26410
+ await writeFile8(vibeConfigJsonPath, projectConfigJson({ name, aspect }), "utf-8");
25785
26411
  created.push(vibeConfigJsonPath);
25786
26412
  }
25787
- const vibePath = resolve7(dir, "vibe.project.yaml");
26413
+ const vibePath = resolve8(dir, "vibe.project.yaml");
25788
26414
  if (await pathExists(vibePath)) {
25789
26415
  skipped2.push(vibePath);
25790
26416
  } else {
25791
26417
  const cfg = { ...defaultVibeProjectConfig(name), aspect };
25792
- await writeFile7(vibePath, (0, import_yaml4.stringify)(cfg), "utf-8");
26418
+ await writeFile8(vibePath, (0, import_yaml4.stringify)(cfg), "utf-8");
25793
26419
  created.push(vibePath);
25794
26420
  }
25795
26421
  if (profile === "agent" || profile === "full") {
25796
- const agentsPath = resolve7(dir, "AGENTS.md");
26422
+ const agentsPath = resolve8(dir, "AGENTS.md");
25797
26423
  if (await pathExists(agentsPath)) {
25798
26424
  skipped2.push(agentsPath);
25799
26425
  } else {
25800
- await writeFile7(agentsPath, buildProjectAgentsMd(name), "utf-8");
26426
+ await writeFile8(agentsPath, buildProjectAgentsMd(name), "utf-8");
25801
26427
  created.push(agentsPath);
25802
26428
  }
25803
- const claudePath = resolve7(dir, "CLAUDE.md");
26429
+ const claudePath = resolve8(dir, "CLAUDE.md");
25804
26430
  if (await pathExists(claudePath)) {
25805
26431
  const existing = await readFile6(claudePath, "utf-8");
25806
26432
  if (existing.includes("@AGENTS.md")) {
25807
26433
  skipped2.push(claudePath);
25808
26434
  } else {
25809
- await writeFile7(claudePath, `@AGENTS.md
26435
+ await writeFile8(claudePath, `@AGENTS.md
25810
26436
 
25811
26437
  ${existing}`, "utf-8");
25812
26438
  merged.push(claudePath);
25813
26439
  }
25814
26440
  } else {
25815
- await writeFile7(claudePath, buildProjectClaudeMd(name), "utf-8");
26441
+ await writeFile8(claudePath, buildProjectClaudeMd(name), "utf-8");
25816
26442
  created.push(claudePath);
25817
26443
  }
25818
26444
  }
25819
- const designPath = resolve7(dir, "DESIGN.md");
26445
+ const designPath = resolve8(dir, "DESIGN.md");
25820
26446
  if (await pathExists(designPath)) {
25821
26447
  skipped2.push(designPath);
25822
26448
  } else {
25823
- await writeFile7(designPath, buildDesignMd({ name, style: opts.visualStyle }), "utf-8");
26449
+ await writeFile8(designPath, buildDesignMd({ name, style: opts.visualStyle }), "utf-8");
25824
26450
  created.push(designPath);
25825
26451
  }
25826
- const storyboardPath = resolve7(dir, "STORYBOARD.md");
26452
+ const storyboardPath = resolve8(dir, "STORYBOARD.md");
25827
26453
  if (await pathExists(storyboardPath)) {
25828
26454
  skipped2.push(storyboardPath);
25829
26455
  } else {
25830
- await writeFile7(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
26456
+ await writeFile8(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
25831
26457
  created.push(storyboardPath);
25832
26458
  }
25833
- const gitignorePath = resolve7(dir, ".gitignore");
26459
+ const gitignorePath = resolve8(dir, ".gitignore");
25834
26460
  if (await pathExists(gitignorePath)) {
25835
26461
  skipped2.push(gitignorePath);
25836
26462
  } else {
25837
- await writeFile7(gitignorePath, buildSceneGitignore(), "utf-8");
26463
+ await writeFile8(gitignorePath, buildSceneGitignore(), "utf-8");
25838
26464
  created.push(gitignorePath);
25839
26465
  }
25840
26466
  return { created, skipped: skipped2, merged, groups: describeSceneScaffold({ dir, profile }) };
@@ -33084,7 +33710,7 @@ var require_util = __commonJS({
33084
33710
  return path14;
33085
33711
  });
33086
33712
  exports.normalize = normalize3;
33087
- function join45(aRoot, aPath) {
33713
+ function join44(aRoot, aPath) {
33088
33714
  if (aRoot === "") {
33089
33715
  aRoot = ".";
33090
33716
  }
@@ -33116,7 +33742,7 @@ var require_util = __commonJS({
33116
33742
  }
33117
33743
  return joined;
33118
33744
  }
33119
- exports.join = join45;
33745
+ exports.join = join44;
33120
33746
  exports.isAbsolute = function(aPath) {
33121
33747
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
33122
33748
  };
@@ -33330,7 +33956,7 @@ var require_util = __commonJS({
33330
33956
  parsed.path = parsed.path.substring(0, index + 1);
33331
33957
  }
33332
33958
  }
33333
- sourceURL = join45(urlGenerate(parsed), sourceURL);
33959
+ sourceURL = join44(urlGenerate(parsed), sourceURL);
33334
33960
  }
33335
33961
  return normalize3(sourceURL);
33336
33962
  }
@@ -34770,8 +35396,8 @@ var require_source_map = __commonJS({
34770
35396
  var require_previous_map = __commonJS({
34771
35397
  "../../node_modules/.pnpm/postcss@8.5.6/node_modules/postcss/lib/previous-map.js"(exports, module) {
34772
35398
  "use strict";
34773
- var { existsSync: existsSync67, readFileSync: readFileSync12 } = __require("fs");
34774
- var { dirname: dirname33, join: join45 } = __require("path");
35399
+ var { existsSync: existsSync67, readFileSync: readFileSync13 } = __require("fs");
35400
+ var { dirname: dirname33, join: join44 } = __require("path");
34775
35401
  var { SourceMapConsumer, SourceMapGenerator } = require_source_map();
34776
35402
  function fromBase64(str2) {
34777
35403
  if (Buffer) {
@@ -34835,7 +35461,7 @@ var require_previous_map = __commonJS({
34835
35461
  this.root = dirname33(path14);
34836
35462
  if (existsSync67(path14)) {
34837
35463
  this.mapFile = path14;
34838
- return readFileSync12(path14, "utf-8").toString().trim();
35464
+ return readFileSync13(path14, "utf-8").toString().trim();
34839
35465
  }
34840
35466
  }
34841
35467
  loadMap(file, prev) {
@@ -34869,7 +35495,7 @@ var require_previous_map = __commonJS({
34869
35495
  return this.decodeInline(this.annotation);
34870
35496
  } else if (this.annotation) {
34871
35497
  let map3 = this.annotation;
34872
- if (file) map3 = join45(dirname33(file), map3);
35498
+ if (file) map3 = join44(dirname33(file), map3);
34873
35499
  return this.loadFile(map3);
34874
35500
  }
34875
35501
  }
@@ -82324,14 +82950,14 @@ var require_re = __commonJS({
82324
82950
  return value;
82325
82951
  };
82326
82952
  var createToken = (name, value, isGlobal) => {
82327
- const safe = makeSafeRegex(value);
82953
+ const safe2 = makeSafeRegex(value);
82328
82954
  const index = R2++;
82329
82955
  debug9(name, index, value);
82330
82956
  t2[name] = index;
82331
82957
  src[index] = value;
82332
- safeSrc[index] = safe;
82958
+ safeSrc[index] = safe2;
82333
82959
  re2[index] = new RegExp(value, isGlobal ? "g" : void 0);
82334
- safeRe[index] = new RegExp(safe, isGlobal ? "g" : void 0);
82960
+ safeRe[index] = new RegExp(safe2, isGlobal ? "g" : void 0);
82335
82961
  };
82336
82962
  createToken("NUMERICIDENTIFIER", "0|[1-9]\\d*");
82337
82963
  createToken("NUMERICIDENTIFIERLOOSE", "\\d+");
@@ -93498,7 +94124,7 @@ var require_util3 = __commonJS({
93498
94124
  return path14;
93499
94125
  }
93500
94126
  exports.normalize = normalize3;
93501
- function join45(aRoot, aPath) {
94127
+ function join44(aRoot, aPath) {
93502
94128
  if (aRoot === "") {
93503
94129
  aRoot = ".";
93504
94130
  }
@@ -93530,7 +94156,7 @@ var require_util3 = __commonJS({
93530
94156
  }
93531
94157
  return joined;
93532
94158
  }
93533
- exports.join = join45;
94159
+ exports.join = join44;
93534
94160
  exports.isAbsolute = function(aPath) {
93535
94161
  return aPath.charAt(0) === "/" || urlRegexp.test(aPath);
93536
94162
  };
@@ -93703,7 +94329,7 @@ var require_util3 = __commonJS({
93703
94329
  parsed.path = parsed.path.substring(0, index + 1);
93704
94330
  }
93705
94331
  }
93706
- sourceURL = join45(urlGenerate(parsed), sourceURL);
94332
+ sourceURL = join44(urlGenerate(parsed), sourceURL);
93707
94333
  }
93708
94334
  return normalize3(sourceURL);
93709
94335
  }
@@ -95505,7 +96131,7 @@ var require_escodegen = __commonJS({
95505
96131
  function noEmptySpace() {
95506
96132
  return space ? space : " ";
95507
96133
  }
95508
- function join45(left3, right3) {
96134
+ function join44(left3, right3) {
95509
96135
  var leftSource, rightSource, leftCharCode, rightCharCode;
95510
96136
  leftSource = toSourceNodeWhenNeeded(left3).toString();
95511
96137
  if (leftSource.length === 0) {
@@ -95836,8 +96462,8 @@ var require_escodegen = __commonJS({
95836
96462
  } else {
95837
96463
  result.push(that.generateExpression(stmt.left, Precedence.Call, E_TTT));
95838
96464
  }
95839
- result = join45(result, operator);
95840
- result = [join45(
96465
+ result = join44(result, operator);
96466
+ result = [join44(
95841
96467
  result,
95842
96468
  that.generateExpression(stmt.right, Precedence.Assignment, E_TTT)
95843
96469
  ), ")"];
@@ -95980,11 +96606,11 @@ var require_escodegen = __commonJS({
95980
96606
  var result, fragment;
95981
96607
  result = ["class"];
95982
96608
  if (stmt.id) {
95983
- result = join45(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
96609
+ result = join44(result, this.generateExpression(stmt.id, Precedence.Sequence, E_TTT));
95984
96610
  }
95985
96611
  if (stmt.superClass) {
95986
- fragment = join45("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
95987
- result = join45(result, fragment);
96612
+ fragment = join44("extends", this.generateExpression(stmt.superClass, Precedence.Unary, E_TTT));
96613
+ result = join44(result, fragment);
95988
96614
  }
95989
96615
  result.push(space);
95990
96616
  result.push(this.generateStatement(stmt.body, S_TFFT));
@@ -95997,9 +96623,9 @@ var require_escodegen = __commonJS({
95997
96623
  return escapeDirective(stmt.directive) + this.semicolon(flags);
95998
96624
  },
95999
96625
  DoWhileStatement: function(stmt, flags) {
96000
- var result = join45("do", this.maybeBlock(stmt.body, S_TFFF));
96626
+ var result = join44("do", this.maybeBlock(stmt.body, S_TFFF));
96001
96627
  result = this.maybeBlockSuffix(stmt.body, result);
96002
- return join45(result, [
96628
+ return join44(result, [
96003
96629
  "while" + space + "(",
96004
96630
  this.generateExpression(stmt.test, Precedence.Sequence, E_TTT),
96005
96631
  ")" + this.semicolon(flags)
@@ -96035,11 +96661,11 @@ var require_escodegen = __commonJS({
96035
96661
  ExportDefaultDeclaration: function(stmt, flags) {
96036
96662
  var result = ["export"], bodyFlags;
96037
96663
  bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF;
96038
- result = join45(result, "default");
96664
+ result = join44(result, "default");
96039
96665
  if (isStatement(stmt.declaration)) {
96040
- result = join45(result, this.generateStatement(stmt.declaration, bodyFlags));
96666
+ result = join44(result, this.generateStatement(stmt.declaration, bodyFlags));
96041
96667
  } else {
96042
- result = join45(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
96668
+ result = join44(result, this.generateExpression(stmt.declaration, Precedence.Assignment, E_TTT) + this.semicolon(flags));
96043
96669
  }
96044
96670
  return result;
96045
96671
  },
@@ -96047,15 +96673,15 @@ var require_escodegen = __commonJS({
96047
96673
  var result = ["export"], bodyFlags, that = this;
96048
96674
  bodyFlags = flags & F_SEMICOLON_OPT ? S_TFFT : S_TFFF;
96049
96675
  if (stmt.declaration) {
96050
- return join45(result, this.generateStatement(stmt.declaration, bodyFlags));
96676
+ return join44(result, this.generateStatement(stmt.declaration, bodyFlags));
96051
96677
  }
96052
96678
  if (stmt.specifiers) {
96053
96679
  if (stmt.specifiers.length === 0) {
96054
- result = join45(result, "{" + space + "}");
96680
+ result = join44(result, "{" + space + "}");
96055
96681
  } else if (stmt.specifiers[0].type === Syntax.ExportBatchSpecifier) {
96056
- result = join45(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
96682
+ result = join44(result, this.generateExpression(stmt.specifiers[0], Precedence.Sequence, E_TTT));
96057
96683
  } else {
96058
- result = join45(result, "{");
96684
+ result = join44(result, "{");
96059
96685
  withIndent(function(indent2) {
96060
96686
  var i2, iz;
96061
96687
  result.push(newline);
@@ -96073,7 +96699,7 @@ var require_escodegen = __commonJS({
96073
96699
  result.push(base + "}");
96074
96700
  }
96075
96701
  if (stmt.source) {
96076
- result = join45(result, [
96702
+ result = join44(result, [
96077
96703
  "from" + space,
96078
96704
  // ModuleSpecifier
96079
96705
  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
@@ -96161,7 +96787,7 @@ var require_escodegen = __commonJS({
96161
96787
  ];
96162
96788
  cursor = 0;
96163
96789
  if (stmt.specifiers[cursor].type === Syntax.ImportDefaultSpecifier) {
96164
- result = join45(result, [
96790
+ result = join44(result, [
96165
96791
  this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
96166
96792
  ]);
96167
96793
  ++cursor;
@@ -96171,7 +96797,7 @@ var require_escodegen = __commonJS({
96171
96797
  result.push(",");
96172
96798
  }
96173
96799
  if (stmt.specifiers[cursor].type === Syntax.ImportNamespaceSpecifier) {
96174
- result = join45(result, [
96800
+ result = join44(result, [
96175
96801
  space,
96176
96802
  this.generateExpression(stmt.specifiers[cursor], Precedence.Sequence, E_TTT)
96177
96803
  ]);
@@ -96200,7 +96826,7 @@ var require_escodegen = __commonJS({
96200
96826
  }
96201
96827
  }
96202
96828
  }
96203
- result = join45(result, [
96829
+ result = join44(result, [
96204
96830
  "from" + space,
96205
96831
  // ModuleSpecifier
96206
96832
  this.generateExpression(stmt.source, Precedence.Sequence, E_TTT),
@@ -96254,7 +96880,7 @@ var require_escodegen = __commonJS({
96254
96880
  return result;
96255
96881
  },
96256
96882
  ThrowStatement: function(stmt, flags) {
96257
- return [join45(
96883
+ return [join44(
96258
96884
  "throw",
96259
96885
  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
96260
96886
  ), this.semicolon(flags)];
@@ -96265,7 +96891,7 @@ var require_escodegen = __commonJS({
96265
96891
  result = this.maybeBlockSuffix(stmt.block, result);
96266
96892
  if (stmt.handlers) {
96267
96893
  for (i2 = 0, iz = stmt.handlers.length; i2 < iz; ++i2) {
96268
- result = join45(result, this.generateStatement(stmt.handlers[i2], S_TFFF));
96894
+ result = join44(result, this.generateStatement(stmt.handlers[i2], S_TFFF));
96269
96895
  if (stmt.finalizer || i2 + 1 !== iz) {
96270
96896
  result = this.maybeBlockSuffix(stmt.handlers[i2].body, result);
96271
96897
  }
@@ -96273,7 +96899,7 @@ var require_escodegen = __commonJS({
96273
96899
  } else {
96274
96900
  guardedHandlers = stmt.guardedHandlers || [];
96275
96901
  for (i2 = 0, iz = guardedHandlers.length; i2 < iz; ++i2) {
96276
- result = join45(result, this.generateStatement(guardedHandlers[i2], S_TFFF));
96902
+ result = join44(result, this.generateStatement(guardedHandlers[i2], S_TFFF));
96277
96903
  if (stmt.finalizer || i2 + 1 !== iz) {
96278
96904
  result = this.maybeBlockSuffix(guardedHandlers[i2].body, result);
96279
96905
  }
@@ -96281,13 +96907,13 @@ var require_escodegen = __commonJS({
96281
96907
  if (stmt.handler) {
96282
96908
  if (Array.isArray(stmt.handler)) {
96283
96909
  for (i2 = 0, iz = stmt.handler.length; i2 < iz; ++i2) {
96284
- result = join45(result, this.generateStatement(stmt.handler[i2], S_TFFF));
96910
+ result = join44(result, this.generateStatement(stmt.handler[i2], S_TFFF));
96285
96911
  if (stmt.finalizer || i2 + 1 !== iz) {
96286
96912
  result = this.maybeBlockSuffix(stmt.handler[i2].body, result);
96287
96913
  }
96288
96914
  }
96289
96915
  } else {
96290
- result = join45(result, this.generateStatement(stmt.handler, S_TFFF));
96916
+ result = join44(result, this.generateStatement(stmt.handler, S_TFFF));
96291
96917
  if (stmt.finalizer) {
96292
96918
  result = this.maybeBlockSuffix(stmt.handler.body, result);
96293
96919
  }
@@ -96295,7 +96921,7 @@ var require_escodegen = __commonJS({
96295
96921
  }
96296
96922
  }
96297
96923
  if (stmt.finalizer) {
96298
- result = join45(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]);
96924
+ result = join44(result, ["finally", this.maybeBlock(stmt.finalizer, S_TFFF)]);
96299
96925
  }
96300
96926
  return result;
96301
96927
  },
@@ -96329,7 +96955,7 @@ var require_escodegen = __commonJS({
96329
96955
  withIndent(function() {
96330
96956
  if (stmt.test) {
96331
96957
  result = [
96332
- join45("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
96958
+ join44("case", that.generateExpression(stmt.test, Precedence.Sequence, E_TTT)),
96333
96959
  ":"
96334
96960
  ];
96335
96961
  } else {
@@ -96377,9 +97003,9 @@ var require_escodegen = __commonJS({
96377
97003
  result.push(this.maybeBlock(stmt.consequent, S_TFFF));
96378
97004
  result = this.maybeBlockSuffix(stmt.consequent, result);
96379
97005
  if (stmt.alternate.type === Syntax.IfStatement) {
96380
- result = join45(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]);
97006
+ result = join44(result, ["else ", this.generateStatement(stmt.alternate, bodyFlags)]);
96381
97007
  } else {
96382
- result = join45(result, join45("else", this.maybeBlock(stmt.alternate, bodyFlags)));
97008
+ result = join44(result, join44("else", this.maybeBlock(stmt.alternate, bodyFlags)));
96383
97009
  }
96384
97010
  } else {
96385
97011
  result.push(this.maybeBlock(stmt.consequent, bodyFlags));
@@ -96480,7 +97106,7 @@ var require_escodegen = __commonJS({
96480
97106
  },
96481
97107
  ReturnStatement: function(stmt, flags) {
96482
97108
  if (stmt.argument) {
96483
- return [join45(
97109
+ return [join44(
96484
97110
  "return",
96485
97111
  this.generateExpression(stmt.argument, Precedence.Sequence, E_TTT)
96486
97112
  ), this.semicolon(flags)];
@@ -96569,14 +97195,14 @@ var require_escodegen = __commonJS({
96569
97195
  if (leftSource.charCodeAt(leftSource.length - 1) === 47 && esutils.code.isIdentifierPartES5(expr.operator.charCodeAt(0))) {
96570
97196
  result = [fragment, noEmptySpace(), expr.operator];
96571
97197
  } else {
96572
- result = join45(fragment, expr.operator);
97198
+ result = join44(fragment, expr.operator);
96573
97199
  }
96574
97200
  fragment = this.generateExpression(expr.right, rightPrecedence, flags);
96575
97201
  if (expr.operator === "/" && fragment.toString().charAt(0) === "/" || expr.operator.slice(-1) === "<" && fragment.toString().slice(0, 3) === "!--") {
96576
97202
  result.push(noEmptySpace());
96577
97203
  result.push(fragment);
96578
97204
  } else {
96579
- result = join45(result, fragment);
97205
+ result = join44(result, fragment);
96580
97206
  }
96581
97207
  if (expr.operator === "in" && !(flags & F_ALLOW_IN)) {
96582
97208
  return ["(", result, ")"];
@@ -96616,7 +97242,7 @@ var require_escodegen = __commonJS({
96616
97242
  var result, length, i2, iz, itemFlags;
96617
97243
  length = expr["arguments"].length;
96618
97244
  itemFlags = flags & F_ALLOW_UNPARATH_NEW && !parentheses && length === 0 ? E_TFT : E_TFF;
96619
- result = join45(
97245
+ result = join44(
96620
97246
  "new",
96621
97247
  this.generateExpression(expr.callee, Precedence.New, itemFlags)
96622
97248
  );
@@ -96666,11 +97292,11 @@ var require_escodegen = __commonJS({
96666
97292
  var result, fragment, rightCharCode, leftSource, leftCharCode;
96667
97293
  fragment = this.generateExpression(expr.argument, Precedence.Unary, E_TTT);
96668
97294
  if (space === "") {
96669
- result = join45(expr.operator, fragment);
97295
+ result = join44(expr.operator, fragment);
96670
97296
  } else {
96671
97297
  result = [expr.operator];
96672
97298
  if (expr.operator.length > 2) {
96673
- result = join45(result, fragment);
97299
+ result = join44(result, fragment);
96674
97300
  } else {
96675
97301
  leftSource = toSourceNodeWhenNeeded(result).toString();
96676
97302
  leftCharCode = leftSource.charCodeAt(leftSource.length - 1);
@@ -96693,7 +97319,7 @@ var require_escodegen = __commonJS({
96693
97319
  result = "yield";
96694
97320
  }
96695
97321
  if (expr.argument) {
96696
- result = join45(
97322
+ result = join44(
96697
97323
  result,
96698
97324
  this.generateExpression(expr.argument, Precedence.Yield, E_TTT)
96699
97325
  );
@@ -96701,7 +97327,7 @@ var require_escodegen = __commonJS({
96701
97327
  return parenthesize(result, Precedence.Yield, precedence);
96702
97328
  },
96703
97329
  AwaitExpression: function(expr, precedence, flags) {
96704
- var result = join45(
97330
+ var result = join44(
96705
97331
  expr.all ? "await*" : "await",
96706
97332
  this.generateExpression(expr.argument, Precedence.Await, E_TTT)
96707
97333
  );
@@ -96784,11 +97410,11 @@ var require_escodegen = __commonJS({
96784
97410
  var result, fragment;
96785
97411
  result = ["class"];
96786
97412
  if (expr.id) {
96787
- result = join45(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
97413
+ result = join44(result, this.generateExpression(expr.id, Precedence.Sequence, E_TTT));
96788
97414
  }
96789
97415
  if (expr.superClass) {
96790
- fragment = join45("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
96791
- result = join45(result, fragment);
97416
+ fragment = join44("extends", this.generateExpression(expr.superClass, Precedence.Unary, E_TTT));
97417
+ result = join44(result, fragment);
96792
97418
  }
96793
97419
  result.push(space);
96794
97420
  result.push(this.generateStatement(expr.body, S_TFFT));
@@ -96803,7 +97429,7 @@ var require_escodegen = __commonJS({
96803
97429
  }
96804
97430
  if (expr.kind === "get" || expr.kind === "set") {
96805
97431
  fragment = [
96806
- join45(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
97432
+ join44(expr.kind, this.generatePropertyKey(expr.key, expr.computed)),
96807
97433
  this.generateFunctionBody(expr.value)
96808
97434
  ];
96809
97435
  } else {
@@ -96813,7 +97439,7 @@ var require_escodegen = __commonJS({
96813
97439
  this.generateFunctionBody(expr.value)
96814
97440
  ];
96815
97441
  }
96816
- return join45(result, fragment);
97442
+ return join44(result, fragment);
96817
97443
  },
96818
97444
  Property: function(expr, precedence, flags) {
96819
97445
  if (expr.kind === "get" || expr.kind === "set") {
@@ -97008,7 +97634,7 @@ var require_escodegen = __commonJS({
97008
97634
  for (i2 = 0, iz = expr.blocks.length; i2 < iz; ++i2) {
97009
97635
  fragment = that.generateExpression(expr.blocks[i2], Precedence.Sequence, E_TTT);
97010
97636
  if (i2 > 0 || extra.moz.comprehensionExpressionStartsWithAssignment) {
97011
- result = join45(result, fragment);
97637
+ result = join44(result, fragment);
97012
97638
  } else {
97013
97639
  result.push(fragment);
97014
97640
  }
@@ -97016,13 +97642,13 @@ var require_escodegen = __commonJS({
97016
97642
  });
97017
97643
  }
97018
97644
  if (expr.filter) {
97019
- result = join45(result, "if" + space);
97645
+ result = join44(result, "if" + space);
97020
97646
  fragment = this.generateExpression(expr.filter, Precedence.Sequence, E_TTT);
97021
- result = join45(result, ["(", fragment, ")"]);
97647
+ result = join44(result, ["(", fragment, ")"]);
97022
97648
  }
97023
97649
  if (!extra.moz.comprehensionExpressionStartsWithAssignment) {
97024
97650
  fragment = this.generateExpression(expr.body, Precedence.Assignment, E_TTT);
97025
- result = join45(result, fragment);
97651
+ result = join44(result, fragment);
97026
97652
  }
97027
97653
  result.push(expr.type === Syntax.GeneratorExpression ? ")" : "]");
97028
97654
  return result;
@@ -97038,8 +97664,8 @@ var require_escodegen = __commonJS({
97038
97664
  } else {
97039
97665
  fragment = this.generateExpression(expr.left, Precedence.Call, E_TTT);
97040
97666
  }
97041
- fragment = join45(fragment, expr.of ? "of" : "in");
97042
- fragment = join45(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
97667
+ fragment = join44(fragment, expr.of ? "of" : "in");
97668
+ fragment = join44(fragment, this.generateExpression(expr.right, Precedence.Sequence, E_TTT));
97043
97669
  return ["for" + space + "(", fragment, ")"];
97044
97670
  },
97045
97671
  SpreadElement: function(expr, precedence, flags) {
@@ -118330,17 +118956,17 @@ var init_cliui = __esm({
118330
118956
  });
118331
118957
 
118332
118958
  // ../../node_modules/.pnpm/escalade@3.2.0/node_modules/escalade/sync/index.mjs
118333
- import { dirname as dirname4, resolve as resolve9 } from "path";
118959
+ import { dirname as dirname4, resolve as resolve10 } from "path";
118334
118960
  import { readdirSync, statSync as statSync2 } from "fs";
118335
118961
  function sync_default(start, callback) {
118336
- let dir = resolve9(".", start);
118962
+ let dir = resolve10(".", start);
118337
118963
  let tmp, stats = statSync2(dir);
118338
118964
  if (!stats.isDirectory()) {
118339
118965
  dir = dirname4(dir);
118340
118966
  }
118341
118967
  while (true) {
118342
118968
  tmp = callback(dir, readdirSync(dir));
118343
- if (tmp) return resolve9(dir, tmp);
118969
+ if (tmp) return resolve10(dir, tmp);
118344
118970
  dir = dirname4(tmp = dir);
118345
118971
  if (tmp === dir) break;
118346
118972
  }
@@ -119306,7 +119932,7 @@ var init_yargs_parser = __esm({
119306
119932
 
119307
119933
  // ../../node_modules/.pnpm/yargs-parser@21.1.1/node_modules/yargs-parser/build/lib/index.js
119308
119934
  import { format } from "util";
119309
- import { normalize, resolve as resolve10 } from "path";
119935
+ import { normalize, resolve as resolve11 } from "path";
119310
119936
  import { readFileSync as readFileSync3 } from "fs";
119311
119937
  var _a3, _b, _c, minNodeVersion, nodeVersion, env2, parser, yargsParser, lib_default;
119312
119938
  var init_lib2 = __esm({
@@ -119329,7 +119955,7 @@ var init_lib2 = __esm({
119329
119955
  },
119330
119956
  format,
119331
119957
  normalize,
119332
- resolve: resolve10,
119958
+ resolve: resolve11,
119333
119959
  // TODO: figure out a way to combine ESM and CJS coverage, such that
119334
119960
  // we can exercise all the lines below:
119335
119961
  require: (path14) => {
@@ -119396,19 +120022,19 @@ var init_yerror = __esm({
119396
120022
  });
119397
120023
 
119398
120024
  // ../../node_modules/.pnpm/y18n@5.0.8/node_modules/y18n/build/lib/platform-shims/node.js
119399
- import { readFileSync as readFileSync4, statSync as statSync3, writeFile as writeFile8 } from "fs";
120025
+ import { readFileSync as readFileSync4, statSync as statSync3, writeFile as writeFile9 } from "fs";
119400
120026
  import { format as format2 } from "util";
119401
- import { resolve as resolve11 } from "path";
120027
+ import { resolve as resolve12 } from "path";
119402
120028
  var node_default;
119403
120029
  var init_node = __esm({
119404
120030
  "../../node_modules/.pnpm/y18n@5.0.8/node_modules/y18n/build/lib/platform-shims/node.js"() {
119405
120031
  node_default = {
119406
120032
  fs: {
119407
120033
  readFileSync: readFileSync4,
119408
- writeFile: writeFile8
120034
+ writeFile: writeFile9
119409
120035
  },
119410
120036
  format: format2,
119411
- resolve: resolve11,
120037
+ resolve: resolve12,
119412
120038
  exists: (file) => {
119413
120039
  try {
119414
120040
  return statSync3(file).isFile();
@@ -119603,7 +120229,7 @@ import { notStrictEqual, strictEqual } from "assert";
119603
120229
  import { inspect } from "util";
119604
120230
  import { readFileSync as readFileSync5 } from "fs";
119605
120231
  import { fileURLToPath } from "url";
119606
- import { basename as basename3, dirname as dirname5, extname as extname2, relative as relative2, resolve as resolve12 } from "path";
120232
+ import { basename as basename3, dirname as dirname5, extname as extname2, relative as relative2, resolve as resolve13 } from "path";
119607
120233
  var REQUIRE_ERROR, REQUIRE_DIRECTORY_ERROR, __dirname2, mainFilename, esm_default;
119608
120234
  var init_esm2 = __esm({
119609
120235
  "../../node_modules/.pnpm/yargs@17.7.2/node_modules/yargs/lib/platform-shims/esm.mjs"() {
@@ -119644,7 +120270,7 @@ var init_esm2 = __esm({
119644
120270
  dirname: dirname5,
119645
120271
  extname: extname2,
119646
120272
  relative: relative2,
119647
- resolve: resolve12
120273
+ resolve: resolve13
119648
120274
  },
119649
120275
  process: {
119650
120276
  argv: () => process.argv,
@@ -119666,7 +120292,7 @@ var init_esm2 = __esm({
119666
120292
  return [...str2].length;
119667
120293
  },
119668
120294
  y18n: y18n_default({
119669
- directory: resolve12(__dirname2, "../../../locales"),
120295
+ directory: resolve13(__dirname2, "../../../locales"),
119670
120296
  updateFiles: false
119671
120297
  })
119672
120298
  };
@@ -123441,9 +124067,9 @@ async function getConnectionTransport(options) {
123441
124067
  throw new Error("Could not detect required browser platform");
123442
124068
  }
123443
124069
  const { convertPuppeteerChannelToBrowsersChannel: convertPuppeteerChannelToBrowsersChannel3 } = await Promise.resolve().then(() => (init_LaunchOptions(), LaunchOptions_exports));
123444
- const { join: join45 } = await import("node:path");
124070
+ const { join: join44 } = await import("node:path");
123445
124071
  const userDataDir = resolveDefaultUserDataDir4(Browser5.CHROME, platform, convertPuppeteerChannelToBrowsersChannel3(options.channel));
123446
- const portPath = join45(userDataDir, "DevToolsActivePort");
124072
+ const portPath = join44(userDataDir, "DevToolsActivePort");
123447
124073
  try {
123448
124074
  const fileContent = await environment.value.fs.promises.readFile(portPath, "ascii");
123449
124075
  const [rawPort, rawPath] = fileContent.split("\n").map((line) => {
@@ -123766,7 +124392,7 @@ var init_PipeTransport = __esm({
123766
124392
  // ../../node_modules/.pnpm/puppeteer-core@24.41.0/node_modules/puppeteer-core/lib/esm/puppeteer/node/BrowserLauncher.js
123767
124393
  import { existsSync as existsSync5 } from "node:fs";
123768
124394
  import { tmpdir } from "node:os";
123769
- import { join as join4 } from "node:path";
124395
+ import { join as join5 } from "node:path";
123770
124396
  var BrowserLauncher;
123771
124397
  var init_BrowserLauncher = __esm({
123772
124398
  "../../node_modules/.pnpm/puppeteer-core@24.41.0/node_modules/puppeteer-core/lib/esm/puppeteer/node/BrowserLauncher.js"() {
@@ -123886,7 +124512,7 @@ var init_BrowserLauncher = __esm({
123886
124512
  if (logs.includes("Failed to create a ProcessSingleton for your profile directory") || // On Windows we will not get logs due to the singleton process
123887
124513
  // handover. See
123888
124514
  // https://source.chromium.org/chromium/chromium/src/+/main:chrome/browser/process_singleton_win.cc;l=46;drc=fc7952f0422b5073515a205a04ec9c3a1ae81658
123889
- process.platform === "win32" && existsSync5(join4(launchArgs.userDataDir, "lockfile"))) {
124515
+ process.platform === "win32" && existsSync5(join5(launchArgs.userDataDir, "lockfile"))) {
123890
124516
  throw new Error(`The browser is already running for ${launchArgs.userDataDir}. Use a different \`userDataDir\` or stop the running browser first.`);
123891
124517
  }
123892
124518
  if (logs.includes("Missing X server") && options.headless === false) {
@@ -124017,7 +124643,7 @@ var init_BrowserLauncher = __esm({
124017
124643
  * @internal
124018
124644
  */
124019
124645
  getProfilePath() {
124020
- return join4(this.puppeteer.configuration.temporaryDirectory ?? tmpdir(), `puppeteer_dev_${this.browser}_profile-`);
124646
+ return join5(this.puppeteer.configuration.temporaryDirectory ?? tmpdir(), `puppeteer_dev_${this.browser}_profile-`);
124021
124647
  }
124022
124648
  /**
124023
124649
  * @internal
@@ -292672,7 +293298,7 @@ ${newComment.split("\n").map((c2) => ` * ${c2}`).join("\n")}
292672
293298
  }
292673
293299
  }
292674
293300
  return result;
292675
- function escapeRegExp3(str2) {
293301
+ function escapeRegExp5(str2) {
292676
293302
  return str2.replace(/[-[\]/{}()*+?.\\^$|]/g, "\\$&");
292677
293303
  }
292678
293304
  function getTodoCommentsRegExp() {
@@ -292680,7 +293306,7 @@ ${newComment.split("\n").map((c2) => ` * ${c2}`).join("\n")}
292680
293306
  const multiLineCommentStart = /(?:\/\*+\s*)/.source;
292681
293307
  const anyNumberOfSpacesAndAsterisksAtStartOfLine = /(?:^(?:\s|\*)*)/.source;
292682
293308
  const preamble = "(" + anyNumberOfSpacesAndAsterisksAtStartOfLine + "|" + singleLineCommentStart + "|" + multiLineCommentStart + ")";
292683
- const literals = "(?:" + map3(descriptors, (d2) => "(" + escapeRegExp3(d2.text) + ")").join("|") + ")";
293309
+ const literals = "(?:" + map3(descriptors, (d2) => "(" + escapeRegExp5(d2.text) + ")").join("|") + ")";
292684
293310
  const endOfLineOrEndOfComment = /(?:$|\*\/)/.source;
292685
293311
  const messageRemainder = /(?:.*?)/.source;
292686
293312
  const messagePortion = "(" + literals + messageRemainder + ")";
@@ -340513,7 +341139,7 @@ var require_dist15 = __commonJS({
340513
341139
 
340514
341140
  // ../../node_modules/.pnpm/puppeteer@24.41.0_typescript@5.9.3/node_modules/puppeteer/lib/esm/puppeteer/getConfiguration.js
340515
341141
  import { homedir as homedir3 } from "node:os";
340516
- import { join as join5 } from "node:path";
341142
+ import { join as join6 } from "node:path";
340517
341143
  function getBooleanEnvVar(name) {
340518
341144
  const env4 = process.env[name];
340519
341145
  if (env4 === void 0) {
@@ -340593,7 +341219,7 @@ var init_getConfiguration = __esm({
340593
341219
  configuration2.firefox = getBrowserSetting("firefox", configuration2, {
340594
341220
  skipDownload: true
340595
341221
  });
340596
- configuration2.cacheDirectory = process.env["PUPPETEER_CACHE_DIR"] ?? configuration2.cacheDirectory ?? join5(homedir3(), ".cache", "puppeteer");
341222
+ configuration2.cacheDirectory = process.env["PUPPETEER_CACHE_DIR"] ?? configuration2.cacheDirectory ?? join6(homedir3(), ".cache", "puppeteer");
340597
341223
  configuration2.temporaryDirectory = process.env["PUPPETEER_TMP_DIR"] ?? configuration2.temporaryDirectory;
340598
341224
  configuration2.experiments ??= {};
340599
341225
  return configuration2;
@@ -340921,7 +341547,7 @@ import { readdirSync as readdirSync2, statSync as statSync4 } from "fs";
340921
341547
  import { format as format3 } from "util";
340922
341548
  import { normalize as normalize2, resolve as resolve32 } from "path";
340923
341549
  import { readFileSync as readFileSync22 } from "fs";
340924
- import { readFileSync as readFileSync32, statSync as statSync22, writeFile as writeFile9 } from "fs";
341550
+ import { readFileSync as readFileSync32, statSync as statSync22, writeFile as writeFile10 } from "fs";
340925
341551
  import { format as format22 } from "util";
340926
341552
  import { resolve as resolve42 } from "path";
340927
341553
  import { notStrictEqual as notStrictEqual2, strictEqual as strictEqual2 } from "assert";
@@ -340979,7 +341605,7 @@ import { join as join8 } from "path";
340979
341605
  import { spawn as spawn7 } from "child_process";
340980
341606
  import { createWriteStream as createWriteStream22, existsSync as existsSync7, mkdirSync as mkdirSync4 } from "fs";
340981
341607
  import { createHash as createHash3 } from "crypto";
340982
- import { join as join7, extname as extname22 } from "path";
341608
+ import { join as join72, extname as extname22 } from "path";
340983
341609
  import { Readable } from "stream";
340984
341610
  import { finished } from "stream/promises";
340985
341611
  import { promises as fs72 } from "fs";
@@ -351067,7 +351693,7 @@ async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
351067
351693
  mkdirSync4(destDir, { recursive: true });
351068
351694
  }
351069
351695
  const filename = getFilenameFromUrl(url);
351070
- const localPath = join7(destDir, filename);
351696
+ const localPath = join72(destDir, filename);
351071
351697
  if (existsSync7(localPath)) {
351072
351698
  downloadPathCache.set(url, localPath);
351073
351699
  return localPath;
@@ -399082,14 +399708,14 @@ ${sourceUrlComment}
399082
399708
  return value;
399083
399709
  };
399084
399710
  var createToken = (name, value, isGlobal) => {
399085
- const safe = makeSafeRegex(value);
399711
+ const safe2 = makeSafeRegex(value);
399086
399712
  const index = R2++;
399087
399713
  debug62(name, index, value);
399088
399714
  t2[name] = index;
399089
399715
  src[index] = value;
399090
- safeSrc[index] = safe;
399716
+ safeSrc[index] = safe2;
399091
399717
  re2[index] = new RegExp(value, isGlobal ? "g" : void 0);
399092
- safeRe[index] = new RegExp(safe, isGlobal ? "g" : void 0);
399718
+ safeRe[index] = new RegExp(safe2, isGlobal ? "g" : void 0);
399093
399719
  };
399094
399720
  createToken("NUMERICIDENTIFIER", "0|[1-9]\\d*");
399095
399721
  createToken("NUMERICIDENTIFIERLOOSE", "\\d+");
@@ -433316,7 +433942,7 @@ If you think this is a bug, please report it on the Puppeteer issue tracker.`;
433316
433942
  node_default2 = {
433317
433943
  fs: {
433318
433944
  readFileSync: readFileSync32,
433319
- writeFile: writeFile9
433945
+ writeFile: writeFile10
433320
433946
  },
433321
433947
  format: format22,
433322
433948
  resolve: resolve42,
@@ -450239,14 +450865,14 @@ ${right22.raw}`)
450239
450865
  });
450240
450866
 
450241
450867
  // ../cli/src/commands/_shared/scene-lint.ts
450242
- import { readdir as readdir4, readFile as readFile7, writeFile as writeFile10, stat as stat2 } from "node:fs/promises";
450868
+ import { readdir as readdir4, readFile as readFile7, writeFile as writeFile11, stat as stat2 } from "node:fs/promises";
450243
450869
  import { existsSync as existsSync18 } from "node:fs";
450244
- import { resolve as resolve14, relative as relative4, join as join18 } from "node:path";
450870
+ import { resolve as resolve15, relative as relative4, join as join18 } from "node:path";
450245
450871
  async function discoverSceneFiles(opts) {
450246
- const projectDir = resolve14(opts.projectDir);
450247
- const rootAbs = resolve14(projectDir, opts.rootRel ?? "index.html");
450872
+ const projectDir = resolve15(opts.projectDir);
450873
+ const rootAbs = resolve15(projectDir, opts.rootRel ?? "index.html");
450248
450874
  const root2 = existsSync18(rootAbs) ? rootAbs : null;
450249
- const compsDir = resolve14(projectDir, "compositions");
450875
+ const compsDir = resolve15(projectDir, "compositions");
450250
450876
  const subs = [];
450251
450877
  if (existsSync18(compsDir)) {
450252
450878
  await collectHtmlRecursive(compsDir, subs);
@@ -450332,7 +450958,7 @@ function addClipClassToTimedElements(html) {
450332
450958
  });
450333
450959
  }
450334
450960
  async function runProjectLint(opts) {
450335
- const projectDir = resolve14(opts.projectDir);
450961
+ const projectDir = resolve15(opts.projectDir);
450336
450962
  const { root: root2, subs } = await discoverSceneFiles({ projectDir, rootRel: opts.rootRel });
450337
450963
  const files = [];
450338
450964
  const fixed = [];
@@ -450356,7 +450982,7 @@ async function runProjectLint(opts) {
450356
450982
  if (opts.fix && findings.length > 0) {
450357
450983
  const { html: nextHtml, fixedCodes } = applyMechanicalFixes(html, findings);
450358
450984
  if (fixedCodes.length > 0) {
450359
- await writeFile10(abs, nextHtml, "utf-8");
450985
+ await writeFile11(abs, nextHtml, "utf-8");
450360
450986
  fixed.push({ file: rel, codes: fixedCodes });
450361
450987
  const reLinted = runHyperframeLint({ ...prepared, html: nextHtml });
450362
450988
  files.push({
@@ -450393,7 +451019,7 @@ async function runProjectLint(opts) {
450393
451019
  };
450394
451020
  }
450395
451021
  async function rootExists(projectDir, rootRel = "index.html") {
450396
- const abs = resolve14(projectDir, rootRel);
451022
+ const abs = resolve15(projectDir, rootRel);
450397
451023
  try {
450398
451024
  const s2 = await stat2(abs);
450399
451025
  return s2.isFile();
@@ -469393,12 +470019,12 @@ var init_dist3 = __esm({
469393
470019
 
469394
470020
  // ../cli/src/commands/_shared/root-sync.ts
469395
470021
  import { existsSync as existsSync23 } from "node:fs";
469396
- import { readFile as readFile8, writeFile as writeFile12 } from "node:fs/promises";
469397
- import { join as join23, resolve as resolve16 } from "node:path";
470022
+ import { readFile as readFile8, writeFile as writeFile13 } from "node:fs/promises";
470023
+ import { join as join23, resolve as resolve17 } from "node:path";
469398
470024
  async function createRootSyncPlan(opts) {
469399
- const projectDir = resolve16(opts.projectDir);
470025
+ const projectDir = resolve17(opts.projectDir);
469400
470026
  const rootRel = opts.rootRel ?? "index.html";
469401
- const rootPath = resolve16(projectDir, rootRel);
470027
+ const rootPath = resolve17(projectDir, rootRel);
469402
470028
  if (!existsSync23(rootPath)) {
469403
470029
  return { rootPath, rootRel, exists: false, changed: false, fixCodes: [], issues: [] };
469404
470030
  }
@@ -469456,11 +470082,11 @@ async function createProjectRootSyncPlan(opts) {
469456
470082
  }
469457
470083
  async function syncRootComposition(opts) {
469458
470084
  const plan = await createRootSyncPlan(opts);
469459
- if (plan.nextHtml) await writeFile12(plan.rootPath, plan.nextHtml, "utf-8");
470085
+ if (plan.nextHtml) await writeFile13(plan.rootPath, plan.nextHtml, "utf-8");
469460
470086
  return plan;
469461
470087
  }
469462
470088
  async function loadProjectRootSyncBeats(projectDir) {
469463
- const root2 = resolve16(projectDir);
470089
+ const root2 = resolve17(projectDir);
469464
470090
  const storyboardPath = join23(root2, "STORYBOARD.md");
469465
470091
  if (!existsSync23(storyboardPath)) return [];
469466
470092
  const parsed = parseStoryboard(await readFile8(storyboardPath, "utf-8"));
@@ -469537,7 +470163,7 @@ async function resolveSyncedBeatDuration(opts) {
469537
470163
  }
469538
470164
  }
469539
470165
  async function resolveProjectBeatDurations(projectDir) {
469540
- const root2 = resolve16(projectDir);
470166
+ const root2 = resolve17(projectDir);
469541
470167
  const beats = await loadProjectRootSyncBeats(root2);
469542
470168
  const durations = /* @__PURE__ */ new Map();
469543
470169
  for (const beat of beats) {
@@ -469786,8 +470412,8 @@ __export(compose_scenes_skills_exports, {
469786
470412
  });
469787
470413
  import { createHash as createHash5 } from "node:crypto";
469788
470414
  import { existsSync as existsSync24, mkdirSync as mkdirSync12 } from "node:fs";
469789
- import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile13 } from "node:fs/promises";
469790
- import { dirname as dirname13, join as join24, resolve as resolve17 } from "node:path";
470415
+ import { mkdir as mkdir9, readFile as readFile9, writeFile as writeFile14 } from "node:fs/promises";
470416
+ import { dirname as dirname13, join as join24, resolve as resolve18 } from "node:path";
469791
470417
  function buildSystemPrompt(ctx) {
469792
470418
  return `You are a Hyperframes composition author. The skill content below
469793
470419
  defines the framework rules, motion principles, and quality standards.
@@ -469880,7 +470506,15 @@ ${finalDurationBullet}
469880
470506
  itself. For multi-phase beats, drive phase changes with GSAP \`autoAlpha\`
469881
470507
  inside full-window clips: animate phase A out (\`autoAlpha: 0\`), then
469882
470508
  phase B in, on the same timeline.
469883
- - If \`assets/backdrop-${ctx.beat.id}.png\` exists, use that local file as the
470509
+ - If \`assets/video-${ctx.beat.id}.mp4\` exists, it is an AI-generated clip for
470510
+ this beat \u2014 use it as the FULL-FRAME visual and prefer it over any backdrop
470511
+ image or CSS. Put one full-bleed \`<video>\` inside a full-window \`.clip\`:
470512
+ \`<video src="assets/video-${ctx.beat.id}.mp4" data-start="0" data-duration="${BEAT_DURATION}" data-media-start="0" muted playsinline style="width:100%;height:100%;object-fit:cover"></video>\`
470513
+ Use the exact path (no \`./\` or \`../\`). The framework seeks the clip to each
470514
+ timeline frame, so do NOT add transform tweens to the \`<video>\` or its
470515
+ ancestors; layer text/motion graphics on top instead.
470516
+ - If \`assets/backdrop-${ctx.beat.id}.png\` exists (and no video clip above),
470517
+ use that local file as the
469884
470518
  full-frame visual backdrop. The exact path string is
469885
470519
  \`assets/backdrop-${ctx.beat.id}.png\`; do NOT prefix it with \`../\` or \`./\`
469886
470520
  because fragments are mounted from the project root.
@@ -470047,7 +470681,7 @@ async function composeBeatHtml(ctx, overrides) {
470047
470681
  const html = extractHtml(result.text);
470048
470682
  try {
470049
470683
  mkdirSync12(cacheDir, { recursive: true });
470050
- await writeFile13(cachePath, html, "utf-8");
470684
+ await writeFile14(cachePath, html, "utf-8");
470051
470685
  } catch {
470052
470686
  }
470053
470687
  const costUsd = result.inputTokens / 1e6 * settings.costPerMTokIn + result.outputTokens / 1e6 * settings.costPerMTokOut;
@@ -470146,9 +470780,9 @@ async function resolveComposerConfigAware(explicit, cwd) {
470146
470780
  return resolveComposer();
470147
470781
  }
470148
470782
  async function executeComposeScenesWithSkills(params, outputDir, overrides) {
470149
- const projectRoot = params.project ? resolve17(outputDir, params.project) : resolve17(outputDir);
470150
- const designPath = resolve17(projectRoot, params.design ?? "DESIGN.md");
470151
- const storyboardPath = resolve17(projectRoot, params.storyboard ?? "STORYBOARD.md");
470783
+ const projectRoot = params.project ? resolve18(outputDir, params.project) : resolve18(outputDir);
470784
+ const designPath = resolve18(projectRoot, params.design ?? "DESIGN.md");
470785
+ const storyboardPath = resolve18(projectRoot, params.storyboard ?? "STORYBOARD.md");
470152
470786
  if (!existsSync24(designPath)) {
470153
470787
  return { success: false, error: `DESIGN.md not found at ${designPath}` };
470154
470788
  }
@@ -470206,7 +470840,7 @@ async function executeComposeScenesWithSkills(params, outputDir, overrides) {
470206
470840
  );
470207
470841
  const compositionPath = join24(compositionsDir, `scene-${beat.id}.html`);
470208
470842
  await mkdir9(dirname13(compositionPath), { recursive: true });
470209
- await writeFile13(compositionPath, result.html, "utf-8");
470843
+ await writeFile14(compositionPath, result.html, "utf-8");
470210
470844
  if (result.cached) {
470211
470845
  onProgress({
470212
470846
  type: "beat-cached",
@@ -470409,9 +471043,9 @@ var init_compose_scenes_skills = __esm({
470409
471043
  // ../cli/src/commands/_shared/compose-prompts.ts
470410
471044
  import { existsSync as existsSync25 } from "node:fs";
470411
471045
  import { readFile as readFile10 } from "node:fs/promises";
470412
- import { join as join25, relative as relative6, resolve as resolve18 } from "node:path";
471046
+ import { join as join25, relative as relative6, resolve as resolve19 } from "node:path";
470413
471047
  async function getComposePrompts(opts) {
470414
- const projectDir = resolve18(opts.projectDir);
471048
+ const projectDir = resolve19(opts.projectDir);
470415
471049
  const designPath = join25(projectDir, "DESIGN.md");
470416
471050
  const storyboardPath = join25(projectDir, "STORYBOARD.md");
470417
471051
  const skillPath = join25(projectDir, "SKILL.md");
@@ -470538,7 +471172,7 @@ var init_compose_prompts = __esm({
470538
471172
  // ../cli/src/commands/_shared/sub-comp-duration-sync.ts
470539
471173
  import { existsSync as existsSync27 } from "node:fs";
470540
471174
  import { readFile as readFile12 } from "node:fs/promises";
470541
- import { join as join27, resolve as resolve20 } from "node:path";
471175
+ import { join as join27, resolve as resolve21 } from "node:path";
470542
471176
  function syncSubCompositionDurationHtml(html, beatId, newDurationSec, fileRel) {
470543
471177
  const compositionId = `scene-${beatId}`;
470544
471178
  const file = fileRel ?? `compositions/${compositionId}.html`;
@@ -470626,7 +471260,7 @@ function rewriteTimelineConst(html, oldDurationSec, newDurationSec) {
470626
471260
  return { html, rewritten: false, needsManualFix };
470627
471261
  }
470628
471262
  async function createSubCompDurationSyncPlans(opts) {
470629
- const projectDir = resolve20(opts.projectDir);
471263
+ const projectDir = resolve21(opts.projectDir);
470630
471264
  const results = [];
470631
471265
  for (const beat of opts.beats) {
470632
471266
  const fileRel = `compositions/scene-${beat.id}.html`;
@@ -470661,88 +471295,6 @@ var init_sub_comp_duration_sync = __esm({
470661
471295
  }
470662
471296
  });
470663
471297
 
470664
- // ../cli/src/commands/_shared/review-report.ts
470665
- import { writeFile as writeFile15 } from "node:fs/promises";
470666
- import { join as join28, resolve as resolve21 } from "node:path";
470667
- function statusFromIssues(issues) {
470668
- if (issues.some((issue) => issue.severity === "error")) return "fail";
470669
- if (issues.some((issue) => issue.severity === "warning")) return "warn";
470670
- return "pass";
470671
- }
470672
- function scoreIssues(issues) {
470673
- let score = 100;
470674
- for (const issue of issues) {
470675
- if (issue.severity === "error") score -= 25;
470676
- else if (issue.severity === "warning") score -= 8;
470677
- else score -= 2;
470678
- }
470679
- return Math.max(0, Math.min(100, score));
470680
- }
470681
- function uniqueRetryWith(items) {
470682
- return [
470683
- ...new Set(items.filter((item) => typeof item === "string" && item.length > 0))
470684
- ];
470685
- }
470686
- function fixOwnerForIssue(issue) {
470687
- if (issue.fixOwner) return issue.fixOwner;
470688
- if (issue.code.startsWith("AI_REVIEW_")) return "host-agent";
470689
- if (issue.code.startsWith("STORYBOARD_")) return "host-agent";
470690
- if (issue.code === "PROJECT_NOT_FOUND" || issue.code === "MISSING_STORYBOARD" || issue.code === "BEAT_NOT_FOUND" || issue.code === "MISSING_DESIGN") {
470691
- return "host-agent";
470692
- }
470693
- return "vibe";
470694
- }
470695
- function normalizeReviewIssues(issues, fallbackOwner) {
470696
- return issues.map((issue) => ({
470697
- ...issue,
470698
- fixOwner: issue.fixOwner ?? fallbackOwner ?? fixOwnerForIssue(issue)
470699
- }));
470700
- }
470701
- function summarizeReviewIssues(issues) {
470702
- const normalized = normalizeReviewIssues(issues);
470703
- return {
470704
- issueCount: normalized.length,
470705
- errorCount: normalized.filter((issue) => issue.severity === "error").length,
470706
- warningCount: normalized.filter((issue) => issue.severity === "warning").length,
470707
- infoCount: normalized.filter((issue) => issue.severity === "info").length,
470708
- fixOwners: {
470709
- vibe: normalized.filter((issue) => issue.fixOwner === "vibe").length,
470710
- hostAgent: normalized.filter((issue) => issue.fixOwner === "host-agent").length
470711
- }
470712
- };
470713
- }
470714
- function buildReviewReport(opts) {
470715
- const issues = normalizeReviewIssues(opts.issues);
470716
- return stripUndefined2({
470717
- schemaVersion: "1",
470718
- kind: "review",
470719
- project: resolve21(opts.project),
470720
- mode: opts.mode,
470721
- beat: opts.beat,
470722
- status: opts.status,
470723
- score: opts.score,
470724
- issues,
470725
- summary: summarizeReviewIssues(issues),
470726
- retryWith: uniqueRetryWith(opts.retryWith),
470727
- sourceReports: opts.sourceReports ?? [],
470728
- reportPath: opts.reportPath
470729
- });
470730
- }
470731
- function defaultReviewReportPath(projectDir) {
470732
- return join28(resolve21(projectDir), "review-report.json");
470733
- }
470734
- async function writeReviewReport(path14, report) {
470735
- await writeFile15(path14, JSON.stringify(report, null, 2) + "\n", "utf-8");
470736
- }
470737
- function stripUndefined2(value) {
470738
- return Object.fromEntries(Object.entries(value).filter(([, v2]) => v2 !== void 0));
470739
- }
470740
- var init_review_report = __esm({
470741
- "../cli/src/commands/_shared/review-report.ts"() {
470742
- "use strict";
470743
- }
470744
- });
470745
-
470746
471298
  // ../cli/src/commands/_shared/scene-repair.ts
470747
471299
  import { readFile as readFile13, writeFile as writeFile16 } from "node:fs/promises";
470748
471300
  import { relative as relative7, resolve as resolve23 } from "node:path";
@@ -470869,16 +471421,308 @@ var init_scene_repair = __esm({
470869
471421
  }
470870
471422
  });
470871
471423
 
471424
+ // ../cli/src/commands/_shared/storyboard-edit.ts
471425
+ function validateStoryboardMarkdown(markdown) {
471426
+ const parsed = parseStoryboard(markdown);
471427
+ const sections = splitBeatSections(markdown);
471428
+ const issues = [];
471429
+ if (parsed.beats.length === 0) {
471430
+ issues.push({
471431
+ severity: "error",
471432
+ code: "NO_BEATS",
471433
+ message: "STORYBOARD.md must contain at least one `## Beat ...` heading."
471434
+ });
471435
+ }
471436
+ const seen = /* @__PURE__ */ new Map();
471437
+ for (const beat of parsed.beats) {
471438
+ seen.set(beat.id, (seen.get(beat.id) ?? 0) + 1);
471439
+ }
471440
+ for (const [id, count] of seen.entries()) {
471441
+ if (count > 1) {
471442
+ issues.push({
471443
+ severity: "error",
471444
+ code: "DUPLICATE_BEAT_ID",
471445
+ beatId: id,
471446
+ message: `Beat id "${id}" appears ${count} times. Beat ids must be unique.`
471447
+ });
471448
+ }
471449
+ }
471450
+ for (const section of sections) {
471451
+ const cueBlock = readLeadingCueBlock(section.body);
471452
+ if (cueBlock?.error) {
471453
+ issues.push({
471454
+ severity: "error",
471455
+ code: "MALFORMED_CUE_YAML",
471456
+ beatId: section.id,
471457
+ message: `Beat "${section.id}" has malformed YAML cues: ${cueBlock.error}`
471458
+ });
471459
+ }
471460
+ }
471461
+ const characterPool = new Set(
471462
+ Object.keys(
471463
+ parsed.frontmatter?.characters ?? {}
471464
+ )
471465
+ );
471466
+ for (const beat of parsed.beats) {
471467
+ const cues = beat.cues ?? {};
471468
+ for (const [key2, value] of Object.entries(cues)) {
471469
+ if (!ALLOWED_CUE_KEYS.has(key2)) {
471470
+ issues.push({
471471
+ severity: "warning",
471472
+ code: "UNKNOWN_CUE",
471473
+ beatId: beat.id,
471474
+ message: `Beat "${beat.id}" uses unknown cue "${key2}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`
471475
+ });
471476
+ continue;
471477
+ }
471478
+ if (key2 === "characters") {
471479
+ const validShape = typeof value === "string" || Array.isArray(value) && value.every((v2) => typeof v2 === "string");
471480
+ if (!validShape) {
471481
+ issues.push({
471482
+ severity: "error",
471483
+ code: "INVALID_CHARACTERS_VALUE",
471484
+ beatId: beat.id,
471485
+ message: `Beat "${beat.id}" cue "characters" must be a character name or a list of names.`
471486
+ });
471487
+ continue;
471488
+ }
471489
+ for (const name of beatCharacterNames({ characters: value })) {
471490
+ if (!characterPool.has(name)) {
471491
+ issues.push({
471492
+ severity: "warning",
471493
+ code: "UNKNOWN_CHARACTER",
471494
+ beatId: beat.id,
471495
+ message: `Beat "${beat.id}" references character "${name}" not defined in the frontmatter \`characters:\` pool.`
471496
+ });
471497
+ }
471498
+ }
471499
+ continue;
471500
+ }
471501
+ if (key2 === "duration") {
471502
+ if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
471503
+ issues.push({
471504
+ severity: "error",
471505
+ code: "INVALID_DURATION",
471506
+ beatId: beat.id,
471507
+ message: `Beat "${beat.id}" cue "duration" must be a positive number of seconds.`
471508
+ });
471509
+ }
471510
+ continue;
471511
+ }
471512
+ if (STRING_CUE_KEYS.has(key2) && typeof value !== "string") {
471513
+ issues.push({
471514
+ severity: "error",
471515
+ code: "INVALID_CUE_VALUE",
471516
+ beatId: beat.id,
471517
+ message: `Beat "${beat.id}" cue "${key2}" must be a string.`
471518
+ });
471519
+ }
471520
+ }
471521
+ }
471522
+ for (const beat of parsed.beats) {
471523
+ if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
471524
+ issues.push({
471525
+ severity: "warning",
471526
+ code: "BEAT_DURATION_TOO_LONG",
471527
+ beatId: beat.id,
471528
+ message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
471529
+ });
471530
+ }
471531
+ }
471532
+ return {
471533
+ ok: !issues.some((i2) => i2.severity === "error"),
471534
+ beats: parsed.beats,
471535
+ issues
471536
+ };
471537
+ }
471538
+ function getStoryboardBeat(markdown, beatId) {
471539
+ return parseStoryboard(markdown).beats.find((beat) => beat.id === beatId) ?? null;
471540
+ }
471541
+ function upsertStoryboardBeat(markdown, opts) {
471542
+ const beatId = normalizeBeatId(opts.beatId);
471543
+ const sections = splitBeatSections(markdown);
471544
+ const existing = sections.find((section2) => section2.id === beatId);
471545
+ const cues = storyboardCuesFromInput(opts);
471546
+ if (existing) {
471547
+ let next = markdown;
471548
+ for (const [key2, value] of Object.entries(cues)) {
471549
+ next = setStoryboardCue(next, { beatId, key: key2, value });
471550
+ }
471551
+ return { markdown: next, action: "updated" };
471552
+ }
471553
+ const section = buildStoryboardBeatSection({ ...opts, beatId, cues });
471554
+ if (sections.length > 0 && isStarterStoryboard(markdown)) {
471555
+ const global3 = markdown.slice(0, sections[0].start).trimEnd();
471556
+ return {
471557
+ markdown: `${global3}
471558
+
471559
+ ${section}`,
471560
+ action: "replaced-starter"
471561
+ };
471562
+ }
471563
+ const prefix = markdown.trimEnd();
471564
+ return {
471565
+ markdown: `${prefix}${prefix ? "\n\n" : ""}${section}`,
471566
+ action: "appended"
471567
+ };
471568
+ }
471569
+ function setStoryboardCue(markdown, opts) {
471570
+ const section = findBeatSection(markdown, opts.beatId);
471571
+ if (!section) {
471572
+ throw new Error(`Beat "${opts.beatId}" not found.`);
471573
+ }
471574
+ if (!ALLOWED_CUE_KEYS.has(opts.key)) {
471575
+ throw new Error(`Unsupported cue "${opts.key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`);
471576
+ }
471577
+ const cue = readLeadingCueBlock(section.body);
471578
+ if (cue?.error) {
471579
+ throw new Error(`Cannot edit malformed YAML cues for beat "${opts.beatId}": ${cue.error}`);
471580
+ }
471581
+ const cues = cue?.value ? { ...cue.value } : {};
471582
+ if (opts.unset) {
471583
+ delete cues[opts.key];
471584
+ } else {
471585
+ cues[opts.key] = normalizeCueValue(opts.key, opts.value);
471586
+ }
471587
+ const remainingBody = cue ? section.body.slice(cue.full.length).replace(/^\s*\n/, "") : section.body.replace(/^\s*\n/, "");
471588
+ const cueBlock = Object.keys(cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
471589
+ const nextBody = cueBlock + remainingBody.trimStart();
471590
+ const nextSection = section.raw.slice(0, section.headingEnd - section.start) + "\n\n" + nextBody.trimEnd() + "\n";
471591
+ return markdown.slice(0, section.start) + nextSection + markdown.slice(section.end);
471592
+ }
471593
+ function moveStoryboardBeat(markdown, opts) {
471594
+ if (opts.beatId === opts.afterBeatId) return markdown;
471595
+ const sections = splitBeatSections(markdown);
471596
+ const movingIndex = sections.findIndex((section) => section.id === opts.beatId);
471597
+ if (movingIndex === -1) throw new Error(`Beat "${opts.beatId}" not found.`);
471598
+ const afterIndex = sections.findIndex((section) => section.id === opts.afterBeatId);
471599
+ if (afterIndex === -1) throw new Error(`Beat "${opts.afterBeatId}" not found.`);
471600
+ const global3 = sections.length > 0 ? markdown.slice(0, sections[0].start) : markdown;
471601
+ const chunks = sections.map((section) => section.raw);
471602
+ const [moving] = chunks.splice(movingIndex, 1);
471603
+ const adjustedAfterIndex = movingIndex < afterIndex ? afterIndex - 1 : afterIndex;
471604
+ chunks.splice(adjustedAfterIndex + 1, 0, moving);
471605
+ return global3 + normalizeBeatChunks(chunks);
471606
+ }
471607
+ function normalizeBeatChunks(chunks) {
471608
+ return chunks.map((chunk) => chunk.trim()).filter(Boolean).join("\n\n") + "\n";
471609
+ }
471610
+ function normalizeBeatId(id) {
471611
+ return deriveBeatId(id);
471612
+ }
471613
+ function storyboardCuesFromInput(opts) {
471614
+ const cues = {};
471615
+ if (opts.duration !== void 0) cues.duration = opts.duration;
471616
+ if (opts.narration && opts.narration.trim()) cues.narration = opts.narration.trim();
471617
+ if (opts.backdrop && opts.backdrop.trim()) cues.backdrop = opts.backdrop.trim();
471618
+ return cues;
471619
+ }
471620
+ function buildStoryboardBeatSection(opts) {
471621
+ const title = normalizeTitle(opts.title ?? humanizeBeatId(opts.beatId));
471622
+ const cueBlock = Object.keys(opts.cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(opts.cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
471623
+ const body = opts.body?.trim() || "Scene created with `vibe scene add`. Edit this body if the build flow should recompose it.";
471624
+ return `## Beat ${opts.beatId} - ${title}
471625
+
471626
+ ${cueBlock}${body.trimEnd()}
471627
+ `;
471628
+ }
471629
+ function normalizeTitle(value) {
471630
+ return value.replace(/\s+/g, " ").trim() || "Scene";
471631
+ }
471632
+ function humanizeBeatId(id) {
471633
+ return id.split(/[-_]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") || "Scene";
471634
+ }
471635
+ function isStarterStoryboard(markdown) {
471636
+ const parsed = parseStoryboard(markdown);
471637
+ if (parsed.beats.length !== 3) return false;
471638
+ const ids = parsed.beats.map((beat) => beat.id);
471639
+ if (ids.join(",") !== "hook,proof,close") return false;
471640
+ return markdown.includes("Introduce the promise in one crisp sentence.") && markdown.includes("Show the mechanism or proof point that makes the promise believable.") && markdown.includes("Close with the action the viewer should remember.");
471641
+ }
471642
+ function normalizeCueValue(key2, value) {
471643
+ if (key2 === "duration") {
471644
+ const n2 = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
471645
+ if (!Number.isFinite(n2) || n2 <= 0) {
471646
+ throw new Error("Cue duration must be a positive number.");
471647
+ }
471648
+ return n2;
471649
+ }
471650
+ if (typeof value !== "string") return value;
471651
+ return value.trim();
471652
+ }
471653
+ function splitBeatSections(markdown) {
471654
+ const normalized = markdown.replace(/\r\n/g, "\n");
471655
+ const headings = [];
471656
+ HEADING_RE2.lastIndex = 0;
471657
+ let match2;
471658
+ while ((match2 = HEADING_RE2.exec(normalized)) !== null) {
471659
+ headings.push({ start: match2.index, end: match2.index + match2[0].length, line: match2[1].trim() });
471660
+ }
471661
+ return headings.map((heading, index) => {
471662
+ const end = index + 1 < headings.length ? headings[index + 1].start : normalized.length;
471663
+ return {
471664
+ id: deriveBeatId(heading.line),
471665
+ heading: heading.line,
471666
+ start: heading.start,
471667
+ headingEnd: heading.end,
471668
+ end,
471669
+ raw: normalized.slice(heading.start, end),
471670
+ body: normalized.slice(heading.end, end).trim()
471671
+ };
471672
+ });
471673
+ }
471674
+ function findBeatSection(markdown, beatId) {
471675
+ return splitBeatSections(markdown).find((section) => section.id === beatId) ?? null;
471676
+ }
471677
+ function readLeadingCueBlock(body) {
471678
+ const match2 = body.match(LEADING_CUE_RE);
471679
+ if (!match2) return null;
471680
+ try {
471681
+ const parsed = (0, import_yaml5.parse)(match2[2]);
471682
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
471683
+ return { full: match2[0], error: "cue block must parse to a YAML object" };
471684
+ }
471685
+ return { full: match2[0], value: parsed };
471686
+ } catch (error) {
471687
+ return { full: match2[0], error: error instanceof Error ? error.message : String(error) };
471688
+ }
471689
+ }
471690
+ var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
471691
+ var init_storyboard_edit = __esm({
471692
+ "../cli/src/commands/_shared/storyboard-edit.ts"() {
471693
+ "use strict";
471694
+ import_yaml5 = __toESM(require_dist3(), 1);
471695
+ init_storyboard_parse();
471696
+ STORYBOARD_CUE_KEYS = [
471697
+ "duration",
471698
+ "narration",
471699
+ "backdrop",
471700
+ "video",
471701
+ "keyframe",
471702
+ "motion",
471703
+ "voice",
471704
+ "music",
471705
+ "asset",
471706
+ "characters"
471707
+ ];
471708
+ HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
471709
+ LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
471710
+ ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
471711
+ STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "keyframe", "motion", "voice", "music", "asset"]);
471712
+ MAX_RECOMMENDED_BEAT_SEC = 15;
471713
+ }
471714
+ });
471715
+
470872
471716
  // ../cli/src/pipeline/renderers/chrome.ts
470873
471717
  import { existsSync as existsSync29 } from "node:fs";
470874
471718
  import { homedir as homedir7 } from "node:os";
470875
- import { join as join29 } from "node:path";
471719
+ import { join as join28 } from "node:path";
470876
471720
  function findChrome() {
470877
471721
  const candidates = [
470878
471722
  process.env.HYPERFRAMES_CHROME_PATH,
470879
471723
  process.env.CHROME_PATH,
470880
471724
  // puppeteer auto-downloaded headless shell
470881
- join29(
471725
+ join28(
470882
471726
  homedir7(),
470883
471727
  ".cache",
470884
471728
  "puppeteer",
@@ -471171,7 +472015,7 @@ __export(scene_render_exports, {
471171
472015
  import { execFile as execFile2 } from "node:child_process";
471172
472016
  import { existsSync as existsSync30 } from "node:fs";
471173
472017
  import { mkdir as mkdir12, readFile as readFile16, stat as stat3, writeFile as writeFile18 } from "node:fs/promises";
471174
- import { resolve as resolve27, relative as relative9, dirname as dirname16, basename as basename7, join as join30, isAbsolute as isAbsolute3 } from "node:path";
472018
+ import { resolve as resolve27, relative as relative9, dirname as dirname16, basename as basename7, join as join29, isAbsolute as isAbsolute3 } from "node:path";
471175
472019
  import { promisify as promisify2 } from "node:util";
471176
472020
  function qualityToCrf(quality = "standard") {
471177
472021
  return quality === "draft" ? 28 : quality === "high" ? 18 : 23;
@@ -471259,7 +472103,7 @@ async function executeSceneRender(opts = {}) {
471259
472103
  }
471260
472104
  let autoSyncApplied = false;
471261
472105
  let autoSyncWarning;
471262
- if (!opts.beatId && existsSync30(join30(projectDir, "STORYBOARD.md"))) {
472106
+ if (!opts.beatId && existsSync30(join29(projectDir, "STORYBOARD.md"))) {
471263
472107
  try {
471264
472108
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
471265
472109
  const subPlans = await createSubCompDurationSyncPlans({
@@ -471436,7 +472280,7 @@ async function safeStat(p2) {
471436
472280
  }
471437
472281
  async function prepareBeatRenderRoot(opts) {
471438
472282
  const root2 = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
471439
- const reportPath = join30(opts.projectDir, "build-report.json");
472283
+ const reportPath = join29(opts.projectDir, "build-report.json");
471440
472284
  const retryWith = [
471441
472285
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
471442
472286
  ];
@@ -471520,8 +472364,8 @@ async function prepareBeatRenderRoot(opts) {
471520
472364
  durationSec,
471521
472365
  narrationPath
471522
472366
  });
471523
- await mkdir12(dirname16(join30(opts.projectDir, root2)), { recursive: true });
471524
- await writeFile18(join30(opts.projectDir, root2), html, "utf-8");
472367
+ await mkdir12(dirname16(join29(opts.projectDir, root2)), { recursive: true });
472368
+ await writeFile18(join29(opts.projectDir, root2), html, "utf-8");
471525
472369
  return {
471526
472370
  success: true,
471527
472371
  root: root2,
@@ -471585,7 +472429,7 @@ function reportedBeatAssetPaths(beat) {
471585
472429
  ]);
471586
472430
  }
471587
472431
  async function writeRenderReport(projectDir, result) {
471588
- const reportPath = join30(projectDir, "render-report.json");
472432
+ const reportPath = join29(projectDir, "render-report.json");
471589
472433
  try {
471590
472434
  await writeFile18(
471591
472435
  reportPath,
@@ -471782,6 +472626,8 @@ var init_build_asset_reference = __esm({
471782
472626
  EXTENSIONS = {
471783
472627
  narration: [".mp3", ".wav", ".m4a"],
471784
472628
  backdrop: [".png", ".jpg", ".jpeg", ".webp"],
472629
+ character: [".png", ".jpg", ".jpeg", ".webp"],
472630
+ keyframe: [".png", ".jpg", ".jpeg", ".webp"],
471785
472631
  video: [".mp4", ".mov", ".webm"],
471786
472632
  music: [".mp3", ".wav", ".m4a"]
471787
472633
  };
@@ -471819,6 +472665,28 @@ function backdropCacheDescriptor(opts) {
471819
472665
  ext: "png"
471820
472666
  });
471821
472667
  }
472668
+ function characterCacheDescriptor(opts) {
472669
+ return cacheAssetDescriptor("character", {
472670
+ name: opts.name,
472671
+ cue: opts.cue,
472672
+ provider: opts.provider,
472673
+ quality: opts.quality,
472674
+ size: opts.size,
472675
+ ext: "png"
472676
+ });
472677
+ }
472678
+ function keyframeCacheDescriptor(opts) {
472679
+ return cacheAssetDescriptor("keyframe", {
472680
+ beatId: opts.beatId,
472681
+ cue: opts.cue,
472682
+ provider: opts.provider,
472683
+ quality: opts.quality,
472684
+ size: opts.size,
472685
+ ratio: opts.ratio,
472686
+ characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
472687
+ ext: "png"
472688
+ });
472689
+ }
471822
472690
  function imageRatioForSize(size) {
471823
472691
  switch (size) {
471824
472692
  case "1024x1024":
@@ -471838,6 +472706,10 @@ function videoCacheDescriptor(opts) {
471838
472706
  provider: opts.provider,
471839
472707
  duration: normalizeVideoDuration(opts.duration),
471840
472708
  ratio: "16:9",
472709
+ // Omit when empty so existing (character-less) clips keep their cache key.
472710
+ characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
472711
+ // Omit when absent so existing (non-keyframe) clips keep their cache key.
472712
+ keyframe: opts.keyframe || void 0,
471841
472713
  ext: "mp4"
471842
472714
  });
471843
472715
  }
@@ -471890,212 +472762,10 @@ var init_build_backdrop_prompt = __esm({
471890
472762
  }
471891
472763
  });
471892
472764
 
471893
- // ../cli/src/commands/_shared/storyboard-edit.ts
471894
- function validateStoryboardMarkdown(markdown) {
471895
- const parsed = parseStoryboard(markdown);
471896
- const sections = splitBeatSections(markdown);
471897
- const issues = [];
471898
- if (parsed.beats.length === 0) {
471899
- issues.push({
471900
- severity: "error",
471901
- code: "NO_BEATS",
471902
- message: "STORYBOARD.md must contain at least one `## Beat ...` heading."
471903
- });
471904
- }
471905
- const seen = /* @__PURE__ */ new Map();
471906
- for (const beat of parsed.beats) {
471907
- seen.set(beat.id, (seen.get(beat.id) ?? 0) + 1);
471908
- }
471909
- for (const [id, count] of seen.entries()) {
471910
- if (count > 1) {
471911
- issues.push({
471912
- severity: "error",
471913
- code: "DUPLICATE_BEAT_ID",
471914
- beatId: id,
471915
- message: `Beat id "${id}" appears ${count} times. Beat ids must be unique.`
471916
- });
471917
- }
471918
- }
471919
- for (const section of sections) {
471920
- const cueBlock = readLeadingCueBlock(section.body);
471921
- if (cueBlock?.error) {
471922
- issues.push({
471923
- severity: "error",
471924
- code: "MALFORMED_CUE_YAML",
471925
- beatId: section.id,
471926
- message: `Beat "${section.id}" has malformed YAML cues: ${cueBlock.error}`
471927
- });
471928
- }
471929
- }
471930
- for (const beat of parsed.beats) {
471931
- const cues = beat.cues ?? {};
471932
- for (const [key2, value] of Object.entries(cues)) {
471933
- if (!ALLOWED_CUE_KEYS.has(key2)) {
471934
- issues.push({
471935
- severity: "warning",
471936
- code: "UNKNOWN_CUE",
471937
- beatId: beat.id,
471938
- message: `Beat "${beat.id}" uses unknown cue "${key2}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`
471939
- });
471940
- continue;
471941
- }
471942
- if (key2 === "duration") {
471943
- if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
471944
- issues.push({
471945
- severity: "error",
471946
- code: "INVALID_DURATION",
471947
- beatId: beat.id,
471948
- message: `Beat "${beat.id}" cue "duration" must be a positive number of seconds.`
471949
- });
471950
- }
471951
- continue;
471952
- }
471953
- if (STRING_CUE_KEYS.has(key2) && typeof value !== "string") {
471954
- issues.push({
471955
- severity: "error",
471956
- code: "INVALID_CUE_VALUE",
471957
- beatId: beat.id,
471958
- message: `Beat "${beat.id}" cue "${key2}" must be a string.`
471959
- });
471960
- }
471961
- }
471962
- }
471963
- for (const beat of parsed.beats) {
471964
- if (beat.duration !== void 0 && beat.duration > MAX_RECOMMENDED_BEAT_SEC) {
471965
- issues.push({
471966
- severity: "warning",
471967
- code: "BEAT_DURATION_TOO_LONG",
471968
- beatId: beat.id,
471969
- message: `Beat "${beat.id}" is ${beat.duration}s \u2014 beats longer than ${MAX_RECOMMENDED_BEAT_SEC}s render static and overstuffed. Split it into 6-15s beats (a 90s video should have 6-8 beats).`
471970
- });
471971
- }
471972
- }
471973
- return {
471974
- ok: !issues.some((i2) => i2.severity === "error"),
471975
- beats: parsed.beats,
471976
- issues
471977
- };
471978
- }
471979
- function getStoryboardBeat(markdown, beatId) {
471980
- return parseStoryboard(markdown).beats.find((beat) => beat.id === beatId) ?? null;
471981
- }
471982
- function setStoryboardCue(markdown, opts) {
471983
- const section = findBeatSection(markdown, opts.beatId);
471984
- if (!section) {
471985
- throw new Error(`Beat "${opts.beatId}" not found.`);
471986
- }
471987
- if (!ALLOWED_CUE_KEYS.has(opts.key)) {
471988
- throw new Error(`Unsupported cue "${opts.key}". Supported cues: ${STORYBOARD_CUE_KEYS.join(", ")}.`);
471989
- }
471990
- const cue = readLeadingCueBlock(section.body);
471991
- if (cue?.error) {
471992
- throw new Error(`Cannot edit malformed YAML cues for beat "${opts.beatId}": ${cue.error}`);
471993
- }
471994
- const cues = cue?.value ? { ...cue.value } : {};
471995
- if (opts.unset) {
471996
- delete cues[opts.key];
471997
- } else {
471998
- cues[opts.key] = normalizeCueValue(opts.key, opts.value);
471999
- }
472000
- const remainingBody = cue ? section.body.slice(cue.full.length).replace(/^\s*\n/, "") : section.body.replace(/^\s*\n/, "");
472001
- const cueBlock = Object.keys(cues).length > 0 ? "```yaml\n" + (0, import_yaml5.stringify)(cues, { lineWidth: 0 }).trimEnd() + "\n```\n\n" : "";
472002
- const nextBody = cueBlock + remainingBody.trimStart();
472003
- const nextSection = section.raw.slice(0, section.headingEnd - section.start) + "\n\n" + nextBody.trimEnd() + "\n";
472004
- return markdown.slice(0, section.start) + nextSection + markdown.slice(section.end);
472005
- }
472006
- function moveStoryboardBeat(markdown, opts) {
472007
- if (opts.beatId === opts.afterBeatId) return markdown;
472008
- const sections = splitBeatSections(markdown);
472009
- const movingIndex = sections.findIndex((section) => section.id === opts.beatId);
472010
- if (movingIndex === -1) throw new Error(`Beat "${opts.beatId}" not found.`);
472011
- const afterIndex = sections.findIndex((section) => section.id === opts.afterBeatId);
472012
- if (afterIndex === -1) throw new Error(`Beat "${opts.afterBeatId}" not found.`);
472013
- const global3 = sections.length > 0 ? markdown.slice(0, sections[0].start) : markdown;
472014
- const chunks = sections.map((section) => section.raw);
472015
- const [moving] = chunks.splice(movingIndex, 1);
472016
- const adjustedAfterIndex = movingIndex < afterIndex ? afterIndex - 1 : afterIndex;
472017
- chunks.splice(adjustedAfterIndex + 1, 0, moving);
472018
- return global3 + normalizeBeatChunks(chunks);
472019
- }
472020
- function normalizeBeatChunks(chunks) {
472021
- return chunks.map((chunk) => chunk.trim()).filter(Boolean).join("\n\n") + "\n";
472022
- }
472023
- function normalizeCueValue(key2, value) {
472024
- if (key2 === "duration") {
472025
- const n2 = typeof value === "number" ? value : Number.parseFloat(String(value ?? ""));
472026
- if (!Number.isFinite(n2) || n2 <= 0) {
472027
- throw new Error("Cue duration must be a positive number.");
472028
- }
472029
- return n2;
472030
- }
472031
- if (typeof value !== "string") return value;
472032
- return value.trim();
472033
- }
472034
- function splitBeatSections(markdown) {
472035
- const normalized = markdown.replace(/\r\n/g, "\n");
472036
- const headings = [];
472037
- HEADING_RE2.lastIndex = 0;
472038
- let match2;
472039
- while ((match2 = HEADING_RE2.exec(normalized)) !== null) {
472040
- headings.push({ start: match2.index, end: match2.index + match2[0].length, line: match2[1].trim() });
472041
- }
472042
- return headings.map((heading, index) => {
472043
- const end = index + 1 < headings.length ? headings[index + 1].start : normalized.length;
472044
- return {
472045
- id: deriveBeatId(heading.line),
472046
- heading: heading.line,
472047
- start: heading.start,
472048
- headingEnd: heading.end,
472049
- end,
472050
- raw: normalized.slice(heading.start, end),
472051
- body: normalized.slice(heading.end, end).trim()
472052
- };
472053
- });
472054
- }
472055
- function findBeatSection(markdown, beatId) {
472056
- return splitBeatSections(markdown).find((section) => section.id === beatId) ?? null;
472057
- }
472058
- function readLeadingCueBlock(body) {
472059
- const match2 = body.match(LEADING_CUE_RE);
472060
- if (!match2) return null;
472061
- try {
472062
- const parsed = (0, import_yaml5.parse)(match2[2]);
472063
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
472064
- return { full: match2[0], error: "cue block must parse to a YAML object" };
472065
- }
472066
- return { full: match2[0], value: parsed };
472067
- } catch (error) {
472068
- return { full: match2[0], error: error instanceof Error ? error.message : String(error) };
472069
- }
472070
- }
472071
- var import_yaml5, STORYBOARD_CUE_KEYS, HEADING_RE2, LEADING_CUE_RE, ALLOWED_CUE_KEYS, STRING_CUE_KEYS, MAX_RECOMMENDED_BEAT_SEC;
472072
- var init_storyboard_edit = __esm({
472073
- "../cli/src/commands/_shared/storyboard-edit.ts"() {
472074
- "use strict";
472075
- import_yaml5 = __toESM(require_dist3(), 1);
472076
- init_storyboard_parse();
472077
- STORYBOARD_CUE_KEYS = [
472078
- "duration",
472079
- "narration",
472080
- "backdrop",
472081
- "video",
472082
- "motion",
472083
- "voice",
472084
- "music",
472085
- "asset"
472086
- ];
472087
- HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
472088
- LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
472089
- ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
472090
- STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
472091
- MAX_RECOMMENDED_BEAT_SEC = 15;
472092
- }
472093
- });
472094
-
472095
472765
  // ../cli/src/commands/_shared/build-plan.ts
472096
472766
  import { existsSync as existsSync33 } from "node:fs";
472097
472767
  import { readFile as readFile17 } from "node:fs/promises";
472098
- import { dirname as dirname17, join as join31, resolve as resolve29 } from "node:path";
472768
+ import { dirname as dirname17, join as join30, resolve as resolve29 } from "node:path";
472099
472769
  function backdropCostUsd(quality) {
472100
472770
  return quality === "hd" ? 0.2 : 0.05;
472101
472771
  }
@@ -472104,7 +472774,7 @@ async function createBuildPlan(opts) {
472104
472774
  loadPlanEnv(projectDir);
472105
472775
  const stage = opts.stage ?? "all";
472106
472776
  const config4 = await readProjectConfig(projectDir);
472107
- const storyboardPath = join31(projectDir, "STORYBOARD.md");
472777
+ const storyboardPath = join30(projectDir, "STORYBOARD.md");
472108
472778
  const warnings = [];
472109
472779
  const retryWith = [];
472110
472780
  const providerResolution = [];
@@ -472163,7 +472833,7 @@ async function createBuildPlan(opts) {
472163
472833
  const imageQuality = opts.imageQuality ?? config4.config.build.imageQuality ?? "hd";
472164
472834
  const imageSize2 = opts.imageSize ?? config4.config.build.imageSize ?? "1536x1024";
472165
472835
  const imageRatio = imageRatioForSize(imageSize2);
472166
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync33(join31(projectDir, `compositions/scene-${beat.id}.html`)));
472836
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync33(join30(projectDir, `compositions/scene-${beat.id}.html`)));
472167
472837
  const resolved = await resolvePlanProviders({
472168
472838
  projectDir,
472169
472839
  config: config4,
@@ -472190,6 +472860,7 @@ async function createBuildPlan(opts) {
472190
472860
  const backdropPrompt = stringOrUndefined3(cue.backdrop);
472191
472861
  const augmentedBackdropPrompt = backdropPrompt ? augmentBackdropPrompt(backdropPrompt) : null;
472192
472862
  const videoPrompt = stringOrUndefined3(cue.video);
472863
+ const keyframePrompt = stringOrUndefined3(cue.keyframe);
472193
472864
  const musicPrompt = stringOrUndefined3(cue.music);
472194
472865
  const genericReference = resolveGenericAssetReference(projectDir, cue.asset);
472195
472866
  const narrationReference = resolveTypedAssetReference(projectDir, "narration", cue.narration);
@@ -472198,7 +472869,7 @@ async function createBuildPlan(opts) {
472198
472869
  const musicReference = resolveTypedAssetReference(projectDir, "music", cue.music) ?? (!musicPrompt && genericReference?.kind === "music" ? genericReference : null);
472199
472870
  const narrationCue = narrationText ?? narrationReference?.raw;
472200
472871
  const backdropCue = backdropPrompt ?? backdropReference?.raw;
472201
- const videoCue = videoPrompt ?? videoReference?.raw;
472872
+ const videoCue = videoPrompt ?? videoReference?.raw ?? keyframePrompt;
472202
472873
  const musicCue = musicPrompt ?? musicReference?.raw;
472203
472874
  const narrationCost = resolved.narration.resolved === "elevenlabs" ? ELEVENLABS_NARRATION_COST_USD : resolved.narration.resolved === "openai" ? OPENAI_NARRATION_COST_USD : 0;
472204
472875
  const narrationCache = narrationText && !narrationReference ? narrationCacheDescriptor({
@@ -472216,11 +472887,12 @@ async function createBuildPlan(opts) {
472216
472887
  size: imageSize2,
472217
472888
  ratio: imageRatio
472218
472889
  }) : null;
472219
- const videoCache = videoPrompt && !videoReference ? videoCacheDescriptor({
472890
+ const videoCache = (videoPrompt || keyframePrompt) && !videoReference ? videoCacheDescriptor({
472220
472891
  beatId: beat.id,
472221
- cue: videoPrompt,
472892
+ cue: videoPrompt ?? keyframePrompt,
472222
472893
  provider: resolved.video.resolved,
472223
- duration: beat.duration
472894
+ duration: beat.duration,
472895
+ keyframe: keyframePrompt
472224
472896
  }) : null;
472225
472897
  const musicCache = musicPrompt && !musicReference ? musicCacheDescriptor({
472226
472898
  beatId: beat.id,
@@ -472267,7 +472939,11 @@ async function createBuildPlan(opts) {
472267
472939
  reference: videoReference,
472268
472940
  projectDir,
472269
472941
  force: opts.force,
472270
- cost: VIDEO_COST_USD,
472942
+ cost: resolved.video.resolved === "seedance" || resolved.video.resolved === "fal" ? estimateSeedanceVideoCostUsd({
472943
+ durationSec: normalizeVideoDuration(beat.duration),
472944
+ resolution: "720p",
472945
+ aspectRatio: "16:9"
472946
+ }) : VIDEO_COST_USD,
472271
472947
  active: includeAssets
472272
472948
  }) : null;
472273
472949
  const music = musicCue && !opts.skipMusic ? assetPlan({
@@ -472284,7 +472960,7 @@ async function createBuildPlan(opts) {
472284
472960
  active: includeAssets
472285
472961
  }) : null;
472286
472962
  const compositionPath = `compositions/scene-${beat.id}.html`;
472287
- const compositionExists = existsSync33(join31(projectDir, compositionPath));
472963
+ const compositionExists = existsSync33(join30(projectDir, compositionPath));
472288
472964
  for (const asset of [narration, backdrop, video, music]) {
472289
472965
  if (!asset) continue;
472290
472966
  if (asset.referenceError) {
@@ -472311,6 +472987,12 @@ async function createBuildPlan(opts) {
472311
472987
  if (backdrop?.willGenerate && isSupportedBuildImageProvider(resolved.image.resolved))
472312
472988
  noteProviderNeed(resolved.image, "Backdrop generation");
472313
472989
  if (video?.willGenerate) noteProviderNeed(resolved.video, "Video generation");
472990
+ if (keyframePrompt && video?.willGenerate) {
472991
+ estimatedCostUsd += backdropCostUsd(imageQuality);
472992
+ providers.add(resolved.image.resolved);
472993
+ if (isSupportedBuildImageProvider(resolved.image.resolved))
472994
+ noteProviderNeed(resolved.image, "Keyframe generation");
472995
+ }
472314
472996
  if (music?.willGenerate) noteProviderNeed(resolved.music, "Music generation");
472315
472997
  if (!compositionExists) missing.add("compositions");
472316
472998
  if (includeCompose && !compositionExists && mode !== "agent") {
@@ -472351,7 +473033,7 @@ async function createBuildPlan(opts) {
472351
473033
  }
472352
473034
  }
472353
473035
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
472354
- if (!existsSync33(join31(projectDir, config4.config.composition.entry))) {
473036
+ if (!existsSync33(join30(projectDir, config4.config.composition.entry))) {
472355
473037
  missing.add("root-composition");
472356
473038
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
472357
473039
  }
@@ -472378,6 +473060,21 @@ async function createBuildPlan(opts) {
472378
473060
  retryWith.push(skipCommand);
472379
473061
  }
472380
473062
  }
473063
+ if (validationOk && includeAssets && !opts.skipVideo) {
473064
+ const referenced = /* @__PURE__ */ new Set();
473065
+ for (const beat of sourceBeats) {
473066
+ for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
473067
+ }
473068
+ if (referenced.size > 0) {
473069
+ const toGenerate = resolveCharacters(parsed.frontmatter).filter(
473070
+ (c2) => referenced.has(c2.name) && c2.prompt && (opts.force || !existsSync33(join30(projectDir, `assets/character-${c2.name}.png`)))
473071
+ );
473072
+ if (toGenerate.length > 0) {
473073
+ estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
473074
+ missing.add("assets");
473075
+ }
473076
+ }
473077
+ }
472381
473078
  return finalizeBuildPlan({
472382
473079
  projectDir,
472383
473080
  config: config4,
@@ -472483,8 +473180,8 @@ function assetPlan(opts) {
472483
473180
  reason: ready ? "referenced-asset" : "invalid-reference"
472484
473181
  };
472485
473182
  }
472486
- const canonicalExists = existsSync33(join31(opts.projectDir, opts.path));
472487
- const cacheHit = opts.cache ? existsSync33(join31(opts.projectDir, opts.cache.path)) : false;
473183
+ const canonicalExists = existsSync33(join30(opts.projectDir, opts.path));
473184
+ const cacheHit = opts.cache ? existsSync33(join30(opts.projectDir, opts.cache.path)) : false;
472488
473185
  const freshness = assetFreshnessFromMetadata({
472489
473186
  projectDir: opts.projectDir,
472490
473187
  kind: opts.kind,
@@ -472681,7 +473378,7 @@ async function firstConfiguredComposer(projectDir) {
472681
473378
  }
472682
473379
  function firstExisting2(projectDir, paths) {
472683
473380
  for (const path14 of paths) {
472684
- if (existsSync33(join31(projectDir, path14))) return path14;
473381
+ if (existsSync33(join30(projectDir, path14))) return path14;
472685
473382
  }
472686
473383
  return null;
472687
473384
  }
@@ -472722,12 +473419,12 @@ function unique3(items) {
472722
473419
  return [...new Set(items.filter((item) => item.length > 0))];
472723
473420
  }
472724
473421
  function loadPlanEnv(projectDir) {
472725
- (0, import_dotenv2.config)({ path: join31(projectDir, ".env"), quiet: true });
473422
+ (0, import_dotenv2.config)({ path: join30(projectDir, ".env"), quiet: true });
472726
473423
  (0, import_dotenv2.config)({ path: resolve29(process.cwd(), ".env"), quiet: true });
472727
473424
  let dir = process.cwd();
472728
473425
  while (dir !== dirname17(dir)) {
472729
- if (existsSync33(join31(dir, "pnpm-workspace.yaml"))) {
472730
- (0, import_dotenv2.config)({ path: join31(dir, ".env"), quiet: true });
473426
+ if (existsSync33(join30(dir, "pnpm-workspace.yaml"))) {
473427
+ (0, import_dotenv2.config)({ path: join30(dir, ".env"), quiet: true });
472731
473428
  return;
472732
473429
  }
472733
473430
  dir = dirname17(dir);
@@ -472742,6 +473439,7 @@ var init_build_plan = __esm({
472742
473439
  init_schema();
472743
473440
  init_build_asset_reference();
472744
473441
  init_build_cache();
473442
+ init_dist();
472745
473443
  init_build_asset_metadata();
472746
473444
  init_build_backdrop_prompt();
472747
473445
  init_composer_resolve();
@@ -473074,9 +473772,9 @@ __export(scene_build_exports, {
473074
473772
  executeSceneBuild: () => executeSceneBuild,
473075
473773
  resolveSceneBuildMode: () => resolveSceneBuildMode
473076
473774
  });
473077
- import { existsSync as existsSync35 } from "node:fs";
473775
+ import { existsSync as existsSync35, readFileSync as readFileSync12 } from "node:fs";
473078
473776
  import { copyFile as copyFile4, mkdir as mkdir13, readFile as readFile18, writeFile as writeFile20 } from "node:fs/promises";
473079
- import { dirname as dirname18, join as join33, resolve as resolve33 } from "node:path";
473777
+ import { dirname as dirname18, join as join31, resolve as resolve33 } from "node:path";
473080
473778
  async function executeSceneBuild(opts) {
473081
473779
  const startedAt = Date.now();
473082
473780
  const projectDir = resolve33(opts.projectDir);
@@ -473088,7 +473786,7 @@ async function executeSceneBuild(opts) {
473088
473786
  const warnings = [];
473089
473787
  const retryWith = [];
473090
473788
  let sceneRepair = skippedSceneRepairSummary();
473091
- const storyboardPath = join33(projectDir, "STORYBOARD.md");
473789
+ const storyboardPath = join31(projectDir, "STORYBOARD.md");
473092
473790
  const buildPlan = await createBuildPlan({
473093
473791
  projectDir,
473094
473792
  stage: selectedStage,
@@ -473241,8 +473939,32 @@ async function executeSceneBuild(opts) {
473241
473939
  const voice = opts.voice ?? parsed.frontmatter?.voice;
473242
473940
  let beatOutcomes = collectExistingBeatOutcomes(parsed.beats, projectDir);
473243
473941
  let pendingJobs = [];
473942
+ let characterCostUsd = 0;
473244
473943
  if (shouldRunStage(selectedStage, "assets")) {
473245
473944
  onProgress({ type: "phase-start", phase: "primitives" });
473945
+ const characterPaths = /* @__PURE__ */ new Map();
473946
+ if (!(opts.skipVideo ?? false)) {
473947
+ const referenced = /* @__PURE__ */ new Set();
473948
+ for (const beat of activeBeats) {
473949
+ for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
473950
+ }
473951
+ if (referenced.size > 0) {
473952
+ const charResult = await buildCharacters(
473953
+ resolveCharacters(parsed.frontmatter).filter((c2) => referenced.has(c2.name)),
473954
+ {
473955
+ projectDir,
473956
+ imageProvider,
473957
+ imageSize: opts.imageSize ?? "1536x1024",
473958
+ imageQuality: opts.imageQuality ?? "hd",
473959
+ force: opts.force ?? false,
473960
+ onProgress
473961
+ }
473962
+ );
473963
+ for (const [name, path14] of charResult.paths) characterPaths.set(name, path14);
473964
+ characterCostUsd = charResult.costUsd;
473965
+ warnings.push(...charResult.failures);
473966
+ }
473967
+ }
473246
473968
  const primitiveResults = await mapWithConcurrency(
473247
473969
  activeBeats,
473248
473970
  BEAT_PRIMITIVES_CONCURRENCY,
@@ -473260,7 +473982,8 @@ async function executeSceneBuild(opts) {
473260
473982
  skipVideo: opts.skipVideo ?? false,
473261
473983
  skipMusic: opts.skipMusic ?? false,
473262
473984
  force: opts.force ?? false,
473263
- onProgress
473985
+ onProgress,
473986
+ characterPaths
473264
473987
  })
473265
473988
  );
473266
473989
  beatOutcomes = primitiveResults.map((result) => result.outcome);
@@ -473269,10 +473992,7 @@ async function executeSceneBuild(opts) {
473269
473992
  (beat) => beat.narrationStatus === "failed" || beat.backdropStatus === "failed" || beat.videoStatus === "failed" || beat.musicStatus === "failed"
473270
473993
  );
473271
473994
  stageReports.assets.status = assetFailed ? "failed" : pendingJobs.length > 0 ? "pending-jobs" : "done";
473272
- stageReports.assets.costUsd = estimateActualAssetCost(
473273
- beatOutcomes,
473274
- opts.imageQuality ?? "hd"
473275
- );
473995
+ stageReports.assets.costUsd = estimateActualAssetCost(beatOutcomes, opts.imageQuality ?? "hd") + characterCostUsd;
473276
473996
  stageReports.assets.retryWith = pendingJobs.map(
473277
473997
  (job) => `vibe status job ${job.id} --project ${projectDir} --json`
473278
473998
  );
@@ -473348,9 +474068,9 @@ async function executeSceneBuild(opts) {
473348
474068
  let composeData;
473349
474069
  if (shouldRunStage(selectedStage, "compose")) {
473350
474070
  if (mode === "agent") {
473351
- const compositionsDir = join33(projectDir, "compositions");
474071
+ const compositionsDir = join31(projectDir, "compositions");
473352
474072
  const missingBeats = activeBeats.filter(
473353
- (b2) => !existsSync35(join33(compositionsDir, `scene-${b2.id}.html`))
474073
+ (b2) => !existsSync35(join31(compositionsDir, `scene-${b2.id}.html`))
473354
474074
  );
473355
474075
  if (missingBeats.length > 0) {
473356
474076
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -473437,6 +474157,18 @@ async function executeSceneBuild(opts) {
473437
474157
  stageReports.compose.status = "skipped";
473438
474158
  }
473439
474159
  if (stageReports.compose.status === "done") {
474160
+ for (const beat of activeBeats) {
474161
+ const videoRel = `assets/video-${beat.id}.mp4`;
474162
+ const compositionAbs = join31(projectDir, "compositions", `scene-${beat.id}.html`);
474163
+ if (!existsSync35(join31(projectDir, videoRel)) || !existsSync35(compositionAbs)) continue;
474164
+ const html = readFileSync12(compositionAbs, "utf-8");
474165
+ if (!html.includes(videoRel)) {
474166
+ warnings.push(
474167
+ `Beat "${beat.id}" has a generated clip (${videoRel}) but its composition does not reference it, so the clip will not appear. Recompose the beat.`
474168
+ );
474169
+ retryWith.push(`vibe build ${projectDir} --beat ${beat.id} --stage compose --force --json`);
474170
+ }
474171
+ }
473440
474172
  const repair = await runBuildSceneRepair(projectDir, "compose", false);
473441
474173
  sceneRepair = mergeSceneRepairSummaries(sceneRepair, repair);
473442
474174
  applySceneRepairToStage(stageReports.compose, repair);
@@ -473486,7 +474218,7 @@ async function executeSceneBuild(opts) {
473486
474218
  });
473487
474219
  }
473488
474220
  if (shouldRunStage(selectedStage, "sync")) {
473489
- if (!existsSync35(join33(projectDir, "index.html"))) {
474221
+ if (!existsSync35(join31(projectDir, "index.html"))) {
473490
474222
  await scaffoldSceneProject({
473491
474223
  dir: projectDir,
473492
474224
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -473671,6 +474403,8 @@ async function buildBeatPrimitives(beat, ctx) {
473671
474403
  videoMetadataPath: video.metadataPath,
473672
474404
  videoFreshness: video.freshness,
473673
474405
  videoSourcePath: video.sourcePath,
474406
+ keyframeStatus: video.keyframeStatus,
474407
+ keyframePath: video.keyframePath,
473674
474408
  musicStatus: music.status,
473675
474409
  musicPath: music.path,
473676
474410
  musicJobId: music.job?.id,
@@ -473745,7 +474479,7 @@ async function dispatchNarration(beat, ctx) {
473745
474479
  return {
473746
474480
  status: "cached",
473747
474481
  path: existingRel,
473748
- durationSec: await safeAudioDuration(join33(ctx.projectDir, existingRel)),
474482
+ durationSec: await safeAudioDuration(join31(ctx.projectDir, existingRel)),
473749
474483
  provider: metadata?.provider,
473750
474484
  cachePath: metadata?.cachePath,
473751
474485
  cacheKey: metadata?.cacheKey,
@@ -473769,9 +474503,9 @@ async function dispatchNarration(beat, ctx) {
473769
474503
  voice,
473770
474504
  ext: resolution.audioExtension
473771
474505
  });
473772
- const cacheAbs = join33(ctx.projectDir, cache.path);
474506
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473773
474507
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
473774
- const abs = join33(ctx.projectDir, rel);
474508
+ const abs = join31(ctx.projectDir, rel);
473775
474509
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
473776
474510
  projectDir: ctx.projectDir,
473777
474511
  kind: "narration",
@@ -473785,7 +474519,7 @@ async function dispatchNarration(beat, ctx) {
473785
474519
  return {
473786
474520
  status: "cached",
473787
474521
  path: existingRel,
473788
- durationSec: await safeAudioDuration(join33(ctx.projectDir, existingRel)),
474522
+ durationSec: await safeAudioDuration(join31(ctx.projectDir, existingRel)),
473789
474523
  provider: resolution.provider,
473790
474524
  cachePath: cache.path,
473791
474525
  cacheKey: cache.key,
@@ -473857,6 +474591,120 @@ async function dispatchNarration(beat, ctx) {
473857
474591
  freshness: "fresh"
473858
474592
  };
473859
474593
  }
474594
+ function characterSheetPrompt(name, description) {
474595
+ return [
474596
+ `Character turnaround reference sheet of an original fictional character named ${name}: ${description}.`,
474597
+ "Show three full-body views side by side on one clean canvas \u2014 front view, side profile, and back view \u2014",
474598
+ "with identical outfit, hairstyle, hair color, and body proportions across all three views.",
474599
+ "Plain light-grey studio background, even neutral lighting, no props.",
474600
+ "Photorealistic digital character, professional concept-art character sheet layout."
474601
+ ].join(" ");
474602
+ }
474603
+ async function buildCharacters(characters, ctx) {
474604
+ const paths = /* @__PURE__ */ new Map();
474605
+ const failures = [];
474606
+ let costUsd = 0;
474607
+ const size = ctx.imageSize ?? "1536x1024";
474608
+ for (const character of characters) {
474609
+ if (character.imagePath) {
474610
+ if (existsSync35(join31(ctx.projectDir, character.imagePath))) {
474611
+ paths.set(character.name, character.imagePath);
474612
+ ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
474613
+ } else {
474614
+ const error = `character "${character.name}" image not found: ${character.imagePath}`;
474615
+ failures.push(error);
474616
+ ctx.onProgress({ type: "character-failed", name: character.name, error });
474617
+ }
474618
+ continue;
474619
+ }
474620
+ if (!character.prompt) continue;
474621
+ const prompt3 = characterSheetPrompt(character.name, character.prompt);
474622
+ const rel = `assets/character-${character.name}.png`;
474623
+ const abs = join31(ctx.projectDir, rel);
474624
+ const metadataOptions = { quality: ctx.imageQuality, size };
474625
+ const cache = characterCacheDescriptor({
474626
+ name: character.name,
474627
+ cue: prompt3,
474628
+ provider: ctx.imageProvider,
474629
+ quality: ctx.imageQuality,
474630
+ size
474631
+ });
474632
+ if (existsSync35(abs) && !ctx.force) {
474633
+ const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
474634
+ if (!metadata || isFreshCanonicalAsset({
474635
+ projectDir: ctx.projectDir,
474636
+ kind: "character",
474637
+ beatId: character.name,
474638
+ cue: prompt3,
474639
+ provider: ctx.imageProvider,
474640
+ options: metadataOptions,
474641
+ cacheKey: cache.key
474642
+ })) {
474643
+ paths.set(character.name, rel);
474644
+ ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
474645
+ continue;
474646
+ }
474647
+ }
474648
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474649
+ if (existsSync35(cacheAbs) && !ctx.force) {
474650
+ await mkdir13(dirname18(abs), { recursive: true });
474651
+ await copyFile4(cacheAbs, abs);
474652
+ await writeAssetMetadata({
474653
+ projectDir: ctx.projectDir,
474654
+ kind: "character",
474655
+ beatId: character.name,
474656
+ cue: prompt3,
474657
+ provider: ctx.imageProvider,
474658
+ options: metadataOptions,
474659
+ cacheKey: cache.key,
474660
+ canonicalPath: rel,
474661
+ cachePath: cache.path
474662
+ });
474663
+ paths.set(character.name, rel);
474664
+ ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
474665
+ continue;
474666
+ }
474667
+ const generated = await generateBackdropImage(
474668
+ prompt3,
474669
+ {
474670
+ projectDir: ctx.projectDir,
474671
+ imageProvider: ctx.imageProvider,
474672
+ imageSize: size,
474673
+ imageQuality: ctx.imageQuality
474674
+ },
474675
+ imageRatioForSize(size)
474676
+ );
474677
+ if (!generated.success) {
474678
+ failures.push(`character "${character.name}": ${generated.error}`);
474679
+ ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
474680
+ continue;
474681
+ }
474682
+ await mkdir13(dirname18(abs), { recursive: true });
474683
+ await writeFile20(abs, generated.buffer);
474684
+ await mkdir13(dirname18(cacheAbs), { recursive: true });
474685
+ await writeFile20(cacheAbs, generated.buffer);
474686
+ await writeAssetMetadata({
474687
+ projectDir: ctx.projectDir,
474688
+ kind: "character",
474689
+ beatId: character.name,
474690
+ cue: prompt3,
474691
+ provider: ctx.imageProvider,
474692
+ options: metadataOptions,
474693
+ cacheKey: cache.key,
474694
+ canonicalPath: rel,
474695
+ cachePath: cache.path
474696
+ });
474697
+ costUsd += backdropCostUsd(ctx.imageQuality);
474698
+ paths.set(character.name, rel);
474699
+ ctx.onProgress({
474700
+ type: "character-generated",
474701
+ name: character.name,
474702
+ path: rel,
474703
+ provider: ctx.imageProvider
474704
+ });
474705
+ }
474706
+ return { paths, costUsd: Number(costUsd.toFixed(2)), failures };
474707
+ }
473860
474708
  async function dispatchBackdrop(beat, ctx) {
473861
474709
  const reference = assetReferenceForBeat(ctx.projectDir, "backdrop", beat);
473862
474710
  if (reference) return referencePrimitiveOutcome("backdrop", beat, ctx, reference);
@@ -473864,7 +474712,7 @@ async function dispatchBackdrop(beat, ctx) {
473864
474712
  if (!cue) return { status: "no-cue" };
473865
474713
  const prompt3 = augmentBackdropPrompt(cue);
473866
474714
  const rel = `assets/backdrop-${beat.id}.png`;
473867
- const abs = join33(ctx.projectDir, rel);
474715
+ const abs = join31(ctx.projectDir, rel);
473868
474716
  const size = ctx.imageSize ?? "1536x1024";
473869
474717
  const ratio = imageRatioForSize(size);
473870
474718
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -473900,7 +474748,7 @@ async function dispatchBackdrop(beat, ctx) {
473900
474748
  };
473901
474749
  }
473902
474750
  }
473903
- const cacheAbs = join33(ctx.projectDir, cache.path);
474751
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473904
474752
  if (existsSync35(cacheAbs) && !ctx.force) {
473905
474753
  await mkdir13(dirname18(abs), { recursive: true });
473906
474754
  await copyFile4(cacheAbs, abs);
@@ -474001,6 +474849,112 @@ async function generateBackdropImage(prompt3, ctx, ratio) {
474001
474849
  });
474002
474850
  return imageBufferFromResult(result);
474003
474851
  }
474852
+ async function editKeyframeImage(prompt3, sheetBuffers, ctx, ratio) {
474853
+ loadSceneBuildEnv(ctx.projectDir);
474854
+ const keyInfo = imageProviderKeyInfo(ctx.imageProvider);
474855
+ const apiKey = await getApiKeyFromConfig(keyInfo.configKey, { cwd: ctx.projectDir }) ?? process.env[keyInfo.envVar] ?? "";
474856
+ if (!apiKey) {
474857
+ return {
474858
+ success: false,
474859
+ error: `${keyInfo.envVar} not set \u2014 cannot generate keyframe with ${ctx.imageProvider}`
474860
+ };
474861
+ }
474862
+ if (ctx.imageProvider === "openai") {
474863
+ const provider2 = new OpenAIImageProvider();
474864
+ await provider2.initialize({ apiKey });
474865
+ const result2 = await provider2.editImage(sheetBuffers, prompt3);
474866
+ return imageBufferFromResult(result2);
474867
+ }
474868
+ if (ctx.imageProvider === "gemini") {
474869
+ const provider2 = new GeminiProvider();
474870
+ await provider2.initialize({ apiKey });
474871
+ const result2 = await provider2.editImage(sheetBuffers, prompt3, {
474872
+ model: "flash",
474873
+ aspectRatio: ratio
474874
+ });
474875
+ return imageBufferFromResult(result2);
474876
+ }
474877
+ const provider = new GrokProvider();
474878
+ await provider.initialize({ apiKey });
474879
+ const result = await provider.editImage(sheetBuffers[0], prompt3, { aspectRatio: ratio });
474880
+ return imageBufferFromResult(result);
474881
+ }
474882
+ async function ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel) {
474883
+ const rel = `assets/keyframe-${beat.id}.png`;
474884
+ const abs = join31(ctx.projectDir, rel);
474885
+ const size = ctx.imageSize ?? "1536x1024";
474886
+ const ratio = imageRatioForSize(size);
474887
+ const metadataOptions = { quality: ctx.imageQuality, size, ratio };
474888
+ const cache = keyframeCacheDescriptor({
474889
+ beatId: beat.id,
474890
+ cue: keyframeCue,
474891
+ provider: ctx.imageProvider,
474892
+ quality: ctx.imageQuality,
474893
+ size,
474894
+ ratio,
474895
+ characters: characterRefsRel
474896
+ });
474897
+ if (existsSync35(abs) && !ctx.force) {
474898
+ const metadata = readAssetMetadata(ctx.projectDir, "keyframe", beat.id);
474899
+ if (!metadata || isFreshCanonicalAsset({
474900
+ projectDir: ctx.projectDir,
474901
+ kind: "keyframe",
474902
+ beatId: beat.id,
474903
+ cue: keyframeCue,
474904
+ provider: ctx.imageProvider,
474905
+ options: metadataOptions,
474906
+ cacheKey: cache.key
474907
+ })) {
474908
+ return { status: "cached", rel };
474909
+ }
474910
+ }
474911
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474912
+ if (existsSync35(cacheAbs) && !ctx.force) {
474913
+ await mkdir13(dirname18(abs), { recursive: true });
474914
+ await copyFile4(cacheAbs, abs);
474915
+ await writeAssetMetadata({
474916
+ projectDir: ctx.projectDir,
474917
+ kind: "keyframe",
474918
+ beatId: beat.id,
474919
+ cue: keyframeCue,
474920
+ provider: ctx.imageProvider,
474921
+ options: metadataOptions,
474922
+ cacheKey: cache.key,
474923
+ canonicalPath: rel,
474924
+ cachePath: cache.path
474925
+ });
474926
+ return { status: "cached", rel };
474927
+ }
474928
+ const sheetBuffers = [];
474929
+ for (const relPath of characterRefsRel) {
474930
+ const sheetAbs = join31(ctx.projectDir, relPath);
474931
+ if (existsSync35(sheetAbs)) sheetBuffers.push(await readFile18(sheetAbs));
474932
+ }
474933
+ const imgCtx = {
474934
+ projectDir: ctx.projectDir,
474935
+ imageProvider: ctx.imageProvider,
474936
+ imageSize: size,
474937
+ imageQuality: ctx.imageQuality
474938
+ };
474939
+ const generated = sheetBuffers.length > 0 ? await editKeyframeImage(keyframeCue, sheetBuffers, imgCtx, ratio) : await generateBackdropImage(keyframeCue, imgCtx, ratio);
474940
+ if (!generated.success) return { status: "failed", error: generated.error };
474941
+ await mkdir13(dirname18(abs), { recursive: true });
474942
+ await writeFile20(abs, generated.buffer);
474943
+ await mkdir13(dirname18(cacheAbs), { recursive: true });
474944
+ await writeFile20(cacheAbs, generated.buffer);
474945
+ await writeAssetMetadata({
474946
+ projectDir: ctx.projectDir,
474947
+ kind: "keyframe",
474948
+ beatId: beat.id,
474949
+ cue: keyframeCue,
474950
+ provider: ctx.imageProvider,
474951
+ options: metadataOptions,
474952
+ cacheKey: cache.key,
474953
+ canonicalPath: rel,
474954
+ cachePath: cache.path
474955
+ });
474956
+ return { status: "generated", rel };
474957
+ }
474004
474958
  async function imageBufferFromResult(result) {
474005
474959
  const image = result.images?.[0];
474006
474960
  if (!result.success || !image) {
@@ -474024,15 +474978,22 @@ function imageProviderKeyInfo(provider) {
474024
474978
  async function dispatchVideo(beat, ctx) {
474025
474979
  const reference = assetReferenceForBeat(ctx.projectDir, "video", beat);
474026
474980
  if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
474027
- const prompt3 = stringOrUndefined4(beat.cues?.video);
474981
+ const keyframeCue = stringOrUndefined4(beat.cues?.keyframe);
474982
+ const prompt3 = stringOrUndefined4(beat.cues?.video) ?? keyframeCue;
474028
474983
  if (!prompt3) return { status: "no-cue" };
474984
+ const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p2) => typeof p2 === "string");
474985
+ const characterRefsAbs = characterRefsRel.map((p2) => join31(ctx.projectDir, p2));
474029
474986
  const rel = `assets/video-${beat.id}.mp4`;
474030
- const abs = join33(ctx.projectDir, rel);
474987
+ const abs = join31(ctx.projectDir, rel);
474031
474988
  const cache = videoCacheDescriptor({
474032
474989
  beatId: beat.id,
474033
474990
  cue: prompt3,
474034
474991
  provider: ctx.videoProvider,
474035
- duration: normalizeVideoDuration(beat.duration)
474992
+ duration: normalizeVideoDuration(beat.duration),
474993
+ // Keyframe mode keys the clip to the keyframe prompt; refs feed the keyframe,
474994
+ // not the clip directly.
474995
+ characters: keyframeCue ? void 0 : characterRefsRel,
474996
+ keyframe: keyframeCue
474036
474997
  });
474037
474998
  const metadataPath = assetMetadataPath("video", beat.id);
474038
474999
  if (existsSync35(abs) && !ctx.force) {
@@ -474058,7 +475019,7 @@ async function dispatchVideo(beat, ctx) {
474058
475019
  };
474059
475020
  }
474060
475021
  }
474061
- const cacheAbs = join33(ctx.projectDir, cache.path);
475022
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474062
475023
  if (existsSync35(cacheAbs) && !ctx.force) {
474063
475024
  await mkdir13(dirname18(abs), { recursive: true });
474064
475025
  await copyFile4(cacheAbs, abs);
@@ -474085,6 +475046,19 @@ async function dispatchVideo(beat, ctx) {
474085
475046
  };
474086
475047
  }
474087
475048
  loadSceneBuildEnv(ctx.projectDir);
475049
+ let keyframeStatus;
475050
+ let keyframeRel;
475051
+ let keyframeImageAbs;
475052
+ if (keyframeCue) {
475053
+ const kf = await ensureKeyframe(beat, ctx, keyframeCue, characterRefsRel);
475054
+ if (kf.status === "failed") {
475055
+ ctx.onProgress({ type: "video-failed", beatId: beat.id, error: kf.error });
475056
+ return { status: "failed", error: kf.error };
475057
+ }
475058
+ keyframeStatus = kf.status;
475059
+ keyframeRel = kf.rel;
475060
+ keyframeImageAbs = join31(ctx.projectDir, kf.rel);
475061
+ }
474088
475062
  const result = await executeVideoGenerate({
474089
475063
  prompt: prompt3,
474090
475064
  provider: ctx.videoProvider,
@@ -474092,6 +475066,10 @@ async function dispatchVideo(beat, ctx) {
474092
475066
  ratio: "16:9",
474093
475067
  output: abs,
474094
475068
  wait: false,
475069
+ // Keyframe mode → single init frame (image-to-video); otherwise character
475070
+ // reference-to-video.
475071
+ image: keyframeImageAbs,
475072
+ refImages: keyframeImageAbs ? void 0 : characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
474095
475073
  apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
474096
475074
  });
474097
475075
  if (!result.success || !result.taskId) {
@@ -474126,14 +475104,16 @@ async function dispatchVideo(beat, ctx) {
474126
475104
  cachePath: cache.path,
474127
475105
  cacheKey: cache.key,
474128
475106
  metadataPath,
474129
- freshness: "fresh"
475107
+ freshness: "fresh",
475108
+ keyframeStatus,
475109
+ keyframePath: keyframeRel
474130
475110
  };
474131
475111
  }
474132
475112
  const job = await createAndWriteJobRecord({
474133
475113
  jobType: "generate-video",
474134
475114
  provider: result.provider ?? ctx.videoProvider,
474135
475115
  providerTaskId: result.taskId,
474136
- providerTaskType: "text2video",
475116
+ providerTaskType: keyframeImageAbs ? "image2video" : "text2video",
474137
475117
  status: "running",
474138
475118
  projectDir: ctx.projectDir,
474139
475119
  workingDirectory: ctx.projectDir,
@@ -474158,7 +475138,9 @@ async function dispatchVideo(beat, ctx) {
474158
475138
  provider: job.provider,
474159
475139
  cachePath: cache.path,
474160
475140
  cacheKey: cache.key,
474161
- metadataPath
475141
+ metadataPath,
475142
+ keyframeStatus,
475143
+ keyframePath: keyframeRel
474162
475144
  };
474163
475145
  }
474164
475146
  async function dispatchMusic(beat, ctx) {
@@ -474167,7 +475149,7 @@ async function dispatchMusic(beat, ctx) {
474167
475149
  const prompt3 = stringOrUndefined4(beat.cues?.music);
474168
475150
  if (!prompt3) return { status: "no-cue" };
474169
475151
  const rel = `assets/music-${beat.id}.mp3`;
474170
- const abs = join33(ctx.projectDir, rel);
475152
+ const abs = join31(ctx.projectDir, rel);
474171
475153
  const duration = normalizeMusicDuration(beat.duration);
474172
475154
  const cache = musicCacheDescriptor({
474173
475155
  beatId: beat.id,
@@ -474200,7 +475182,7 @@ async function dispatchMusic(beat, ctx) {
474200
475182
  };
474201
475183
  }
474202
475184
  }
474203
- const cacheAbs = join33(ctx.projectDir, cache.path);
475185
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474204
475186
  if (existsSync35(cacheAbs) && !ctx.force) {
474205
475187
  await mkdir13(dirname18(abs), { recursive: true });
474206
475188
  await copyFile4(cacheAbs, abs);
@@ -474311,12 +475293,12 @@ async function dispatchMusic(beat, ctx) {
474311
475293
  };
474312
475294
  }
474313
475295
  function loadSceneBuildEnv(projectDir) {
474314
- (0, import_dotenv3.config)({ path: join33(projectDir, ".env"), quiet: true });
475296
+ (0, import_dotenv3.config)({ path: join31(projectDir, ".env"), quiet: true });
474315
475297
  (0, import_dotenv3.config)({ path: resolve33(process.cwd(), ".env"), quiet: true });
474316
475298
  let dir = process.cwd();
474317
475299
  while (dir !== dirname18(dir)) {
474318
- if (existsSync35(join33(dir, "pnpm-workspace.yaml"))) {
474319
- (0, import_dotenv3.config)({ path: join33(dir, ".env"), quiet: true });
475300
+ if (existsSync35(join31(dir, "pnpm-workspace.yaml"))) {
475301
+ (0, import_dotenv3.config)({ path: join31(dir, ".env"), quiet: true });
474320
475302
  return;
474321
475303
  }
474322
475304
  dir = dirname18(dir);
@@ -474460,7 +475442,14 @@ function estimateActualAssetCost(outcomes, imageQuality) {
474460
475442
  for (const outcome of outcomes) {
474461
475443
  if (outcome.narrationStatus === "generated") cost += 0.05;
474462
475444
  if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
474463
- if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") cost += 5;
475445
+ if (outcome.keyframeStatus === "generated") cost += backdropCostUsd(imageQuality);
475446
+ if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
475447
+ cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
475448
+ durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
475449
+ resolution: "720p",
475450
+ aspectRatio: "16:9"
475451
+ }) : 5;
475452
+ }
474464
475453
  if (outcome.musicStatus === "generated" || outcome.musicStatus === "pending") cost += 0.5;
474465
475454
  }
474466
475455
  return Number(cost.toFixed(2));
@@ -474525,7 +475514,7 @@ function mergeBeatOutcomes(base, updates) {
474525
475514
  }
474526
475515
  function firstExisting3(projectDir, relPaths) {
474527
475516
  for (const rel of relPaths) {
474528
- if (existsSync35(join33(projectDir, rel))) return rel;
475517
+ if (existsSync35(join31(projectDir, rel))) return rel;
474529
475518
  }
474530
475519
  return null;
474531
475520
  }
@@ -474556,7 +475545,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
474556
475545
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
474557
475546
  }
474558
475547
  async function finalizeBuildResult(projectDir, startedAt, result) {
474559
- const reportPath = join33(projectDir, "build-report.json");
475548
+ const reportPath = join31(projectDir, "build-report.json");
474560
475549
  if (result.stageReports) {
474561
475550
  for (const report of Object.values(result.stageReports)) {
474562
475551
  if (report.status === "pending") report.status = "skipped";
@@ -474715,7 +475704,7 @@ function normalizeReportDuration(value) {
474715
475704
  }
474716
475705
  function buildReportComposition(projectDir, result, beatId) {
474717
475706
  const path14 = `compositions/scene-${beatId}.html`;
474718
- const exists = existsSync35(join33(projectDir, path14));
475707
+ const exists = existsSync35(join31(projectDir, path14));
474719
475708
  const written = composeWrittenEntry(result, beatId);
474720
475709
  if (written) {
474721
475710
  return {
@@ -474772,7 +475761,7 @@ function summarizeBuildBeats(projectDir, beats) {
474772
475761
  let compositionsReady = 0;
474773
475762
  for (const beat of beats) {
474774
475763
  if (beatAssetsReady(beat)) assetsReady += 1;
474775
- const compositionPath = join33(projectDir, "compositions", `scene-${beat.beatId}.html`);
475764
+ const compositionPath = join31(projectDir, "compositions", `scene-${beat.beatId}.html`);
474776
475765
  if (existsSync35(compositionPath)) {
474777
475766
  compositionsReady += 1;
474778
475767
  } else {
@@ -475428,7 +476417,7 @@ var init_silence_cut = __esm({
475428
476417
  // ../cli/src/commands/_shared/edit/jump-cut.ts
475429
476418
  import { existsSync as existsSync38 } from "node:fs";
475430
476419
  import { readFile as readFile21, writeFile as writeFile22, mkdir as mkdir14 } from "node:fs/promises";
475431
- import { join as join34 } from "node:path";
476420
+ import { join as join33 } from "node:path";
475432
476421
  async function transcribeWithWords(audioPath, apiKey, language) {
475433
476422
  const audioBuffer = await readFile21(audioPath);
475434
476423
  const audioBlob = new Blob([audioBuffer]);
@@ -475508,7 +476497,7 @@ async function executeJumpCut(options) {
475508
476497
  try {
475509
476498
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
475510
476499
  await mkdir14(tmpDir, { recursive: true });
475511
- const audioPath = join34(tmpDir, "audio.wav");
476500
+ const audioPath = join33(tmpDir, "audio.wav");
475512
476501
  try {
475513
476502
  await execSafe(
475514
476503
  "ffmpeg",
@@ -475562,7 +476551,7 @@ async function executeJumpCut(options) {
475562
476551
  const segmentPaths = [];
475563
476552
  for (let i2 = 0; i2 < segments.length; i2++) {
475564
476553
  const seg = segments[i2];
475565
- const segPath = join34(tmpDir, `seg-${i2.toString().padStart(4, "0")}.ts`);
476554
+ const segPath = join33(tmpDir, `seg-${i2.toString().padStart(4, "0")}.ts`);
475566
476555
  const duration = seg.end - seg.start;
475567
476556
  await execSafe(
475568
476557
  "ffmpeg",
@@ -475585,7 +476574,7 @@ async function executeJumpCut(options) {
475585
476574
  segmentPaths.push(segPath);
475586
476575
  }
475587
476576
  const concatList = segmentPaths.map((p2) => `file '${p2}'`).join("\n");
475588
- const listPath = join34(tmpDir, "concat.txt");
476577
+ const listPath = join33(tmpDir, "concat.txt");
475589
476578
  await writeFile22(listPath, concatList);
475590
476579
  await execSafe(
475591
476580
  "ffmpeg",
@@ -475693,7 +476682,7 @@ __export(remotion_exports, {
475693
476682
  });
475694
476683
  import { writeFile as writeFile23, mkdir as mkdir15, rm as rm3, copyFile as copyFile5 } from "node:fs/promises";
475695
476684
  import { existsSync as existsSync39 } from "node:fs";
475696
- import { join as join35 } from "node:path";
476685
+ import { join as join34 } from "node:path";
475697
476686
  import { tmpdir as tmpdir3 } from "node:os";
475698
476687
  import { createHash as createHash7 } from "node:crypto";
475699
476688
  async function ensureRemotionInstalled() {
@@ -475712,7 +476701,7 @@ async function ensureRemotionInstalled() {
475712
476701
  }
475713
476702
  }
475714
476703
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
475715
- const dir = join35(tmpdir3(), `vibe_motion_${Date.now()}`);
476704
+ const dir = join34(tmpdir3(), `vibe_motion_${Date.now()}`);
475716
476705
  await mkdir15(dir, { recursive: true });
475717
476706
  const deps = {
475718
476707
  remotion: REMOTION_VERSION,
@@ -475731,7 +476720,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
475731
476720
  dependencies: deps
475732
476721
  };
475733
476722
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
475734
- await writeFile23(join35(dir, "package.json"), packageJsonStr);
476723
+ await writeFile23(join34(dir, "package.json"), packageJsonStr);
475735
476724
  const tsconfig = {
475736
476725
  compilerOptions: {
475737
476726
  target: "ES2020",
@@ -475743,8 +476732,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
475743
476732
  skipLibCheck: true
475744
476733
  }
475745
476734
  };
475746
- await writeFile23(join35(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
475747
- await writeFile23(join35(dir, "Component.tsx"), componentCode);
476735
+ await writeFile23(join34(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
476736
+ await writeFile23(join34(dir, "Component.tsx"), componentCode);
475748
476737
  const rootCode = `import { registerRoot, Composition } from "remotion";
475749
476738
  import { ${componentName} } from "./Component";
475750
476739
 
@@ -475763,14 +476752,14 @@ const Root = () => {
475763
476752
 
475764
476753
  registerRoot(Root);
475765
476754
  `;
475766
- await writeFile23(join35(dir, "Root.tsx"), rootCode);
476755
+ await writeFile23(join34(dir, "Root.tsx"), rootCode);
475767
476756
  const depsHash = createHash7("md5").update(packageJsonStr).digest("hex").slice(0, 12);
475768
- const cacheMarker = join35(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
475769
- const cachedModules = join35(REMOTION_CACHE_DIR, "node_modules");
476757
+ const cacheMarker = join34(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
476758
+ const cachedModules = join34(REMOTION_CACHE_DIR, "node_modules");
475770
476759
  if (existsSync39(cacheMarker) && existsSync39(cachedModules)) {
475771
476760
  const { symlink } = await import("node:fs/promises");
475772
476761
  try {
475773
- await symlink(cachedModules, join35(dir, "node_modules"), "dir");
476762
+ await symlink(cachedModules, join34(dir, "node_modules"), "dir");
475774
476763
  } catch {
475775
476764
  await this_npmInstall(dir);
475776
476765
  }
@@ -475778,12 +476767,12 @@ registerRoot(Root);
475778
476767
  await this_npmInstall(dir);
475779
476768
  try {
475780
476769
  await mkdir15(REMOTION_CACHE_DIR, { recursive: true });
475781
- await writeFile23(join35(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
475782
- if (existsSync39(join35(dir, "node_modules"))) {
476770
+ await writeFile23(join34(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
476771
+ if (existsSync39(join34(dir, "node_modules"))) {
475783
476772
  const { rename: rename7, symlink } = await import("node:fs/promises");
475784
- await rename7(join35(dir, "node_modules"), cachedModules).catch(() => {
476773
+ await rename7(join34(dir, "node_modules"), cachedModules).catch(() => {
475785
476774
  });
475786
- await symlink(cachedModules, join35(dir, "node_modules"), "dir").catch(() => {
476775
+ await symlink(cachedModules, join34(dir, "node_modules"), "dir").catch(() => {
475787
476776
  });
475788
476777
  await writeFile23(cacheMarker, depsHash);
475789
476778
  }
@@ -475820,7 +476809,7 @@ async function renderMotion(options) {
475820
476809
  }
475821
476810
  );
475822
476811
  try {
475823
- const entryPoint = join35(dir, "Root.tsx");
476812
+ const entryPoint = join34(dir, "Root.tsx");
475824
476813
  const { execFile: execFile3 } = await import("node:child_process");
475825
476814
  const { promisify: promisify3 } = await import("node:util");
475826
476815
  const execFileAsync3 = promisify3(execFile3);
@@ -475953,10 +476942,10 @@ async function renderWithEmbeddedImage(options) {
475953
476942
  }
475954
476943
  );
475955
476944
  try {
475956
- const publicDir = join35(dir, "public");
476945
+ const publicDir = join34(dir, "public");
475957
476946
  await mkdir15(publicDir, { recursive: true });
475958
- await copyFile5(options.imagePath, join35(publicDir, options.imageFileName));
475959
- const entryPoint = join35(dir, "Root.tsx");
476947
+ await copyFile5(options.imagePath, join34(publicDir, options.imageFileName));
476948
+ const entryPoint = join34(dir, "Root.tsx");
475960
476949
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
475961
476950
  const { execFile: execFileImg } = await import("node:child_process");
475962
476951
  const { promisify: promisifyImg } = await import("node:util");
@@ -476017,10 +477006,10 @@ async function renderWithEmbeddedVideo(options) {
476017
477006
  }
476018
477007
  );
476019
477008
  try {
476020
- const publicDir = join35(dir, "public");
477009
+ const publicDir = join34(dir, "public");
476021
477010
  await mkdir15(publicDir, { recursive: true });
476022
- await copyFile5(options.videoPath, join35(publicDir, options.videoFileName));
476023
- const entryPoint = join35(dir, "Root.tsx");
477011
+ await copyFile5(options.videoPath, join34(publicDir, options.videoFileName));
477012
+ const entryPoint = join34(dir, "Root.tsx");
476024
477013
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
476025
477014
  const { execFile: execFileVid } = await import("node:child_process");
476026
477015
  const { promisify: promisifyVid } = await import("node:util");
@@ -476436,14 +477425,14 @@ var init_remotion = __esm({
476436
477425
  "use strict";
476437
477426
  init_exec_safe();
476438
477427
  REMOTION_VERSION = "4.0.447";
476439
- REMOTION_CACHE_DIR = join35(tmpdir3(), "vibe_remotion_cache");
477428
+ REMOTION_CACHE_DIR = join34(tmpdir3(), "vibe_remotion_cache");
476440
477429
  }
476441
477430
  });
476442
477431
 
476443
477432
  // ../cli/src/commands/_shared/edit/caption.ts
476444
477433
  import { existsSync as existsSync40 } from "node:fs";
476445
477434
  import { readFile as readFile22, writeFile as writeFile24, mkdir as mkdir16 } from "node:fs/promises";
476446
- import { dirname as dirname20, basename as basename9, extname as extname7, join as join36 } from "node:path";
477435
+ import { dirname as dirname20, basename as basename9, extname as extname7, join as join35 } from "node:path";
476447
477436
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
476448
477437
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
476449
477438
  const marginV = position === "center" ? 0 : 30;
@@ -476490,8 +477479,8 @@ async function executeCaption(options) {
476490
477479
  try {
476491
477480
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
476492
477481
  await mkdir16(tmpDir, { recursive: true });
476493
- const audioPath = join36(tmpDir, "audio.wav");
476494
- const srtPath = join36(tmpDir, "captions.srt");
477482
+ const audioPath = join35(tmpDir, "audio.wav");
477483
+ const srtPath = join35(tmpDir, "captions.srt");
476495
477484
  try {
476496
477485
  await execSafe(
476497
477486
  "ffmpeg",
@@ -476560,7 +477549,7 @@ async function executeCaption(options) {
476560
477549
  const remotionErr = await ensureRemotionInstalled2();
476561
477550
  if (remotionErr) {
476562
477551
  const outputDir2 = dirname20(outputPath);
476563
- const outputSrtPath2 = join36(
477552
+ const outputSrtPath2 = join35(
476564
477553
  outputDir2,
476565
477554
  basename9(outputPath, extname7(outputPath)) + ".srt"
476566
477555
  );
@@ -476599,7 +477588,7 @@ SRT saved to: ${outputSrtPath2}` };
476599
477588
  });
476600
477589
  if (!renderResult.success) {
476601
477590
  const outputDir2 = dirname20(outputPath);
476602
- const outputSrtPath2 = join36(
477591
+ const outputSrtPath2 = join35(
476603
477592
  outputDir2,
476604
477593
  basename9(outputPath, extname7(outputPath)) + ".srt"
476605
477594
  );
@@ -476612,7 +477601,7 @@ SRT saved to: ${outputSrtPath2}`
476612
477601
  }
476613
477602
  }
476614
477603
  const outputDir = dirname20(outputPath);
476615
- const outputSrtPath = join36(
477604
+ const outputSrtPath = join35(
476616
477605
  outputDir,
476617
477606
  basename9(outputPath, extname7(outputPath)) + ".srt"
476618
477607
  );
@@ -484580,10 +485569,18 @@ Examples:
484580
485569
  options.ratio = "16:9";
484581
485570
  }
484582
485571
  if (options.dryRun) {
485572
+ const costUsd = provider === "seedance" || provider === "fal" ? estimateSeedanceVideoCostUsd({
485573
+ durationSec: Number(options.duration) || 5,
485574
+ resolution: options.resolution,
485575
+ aspectRatio: options.ratio,
485576
+ fast: options.seedanceModel === "fast",
485577
+ hasVideoReference: Boolean(options.refVideos)
485578
+ }) : void 0;
484583
485579
  outputSuccess({
484584
485580
  command: "generate video",
484585
485581
  startedAt,
484586
485582
  dryRun: true,
485583
+ costUsd,
484587
485584
  data: {
484588
485585
  params: {
484589
485586
  prompt: prompt3,
@@ -487033,6 +488030,9 @@ function clean(text) {
487033
488030
  function humanise(id) {
487034
488031
  return id.split(/[-_]+/).filter(Boolean).map((w2) => w2.charAt(0).toUpperCase() + w2.slice(1)).join(" ");
487035
488032
  }
488033
+ function sceneCompositionId(beatId) {
488034
+ return `scene-${beatId}`;
488035
+ }
487036
488036
  function slugifySceneName(name) {
487037
488037
  const normalised = name.normalize("NFKD").replace(/[̀-ͯ]/g, "");
487038
488038
  const slug = normalised.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -487070,10 +488070,16 @@ function fitKineticFontSize(words) {
487070
488070
  function kenBurnsTween(scope, dur, endScale = 1.08) {
487071
488071
  return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
487072
488072
  }
488073
+ function backdropMotionTweens(scope, dur, hasImage, endScale = 1.08) {
488074
+ const sweep = hasImage ? "" : `
488075
+ tl.fromTo('${scope} .ambient-sweep', { xPercent: -75, opacity: 0.18 }, { xPercent: 75, opacity: 0.62, duration: ${dur.toFixed(2)}, ease: 'sine.inOut' }, 0);`;
488076
+ return `${kenBurnsTween(scope, dur, endScale)}${sweep}`;
488077
+ }
487073
488078
  function buildPreset(input3) {
487074
488079
  const id = input3.id;
487075
488080
  const scope = `[data-composition-id="${id}"]`;
487076
- const headline = clean(input3.headline) || humanise(id);
488081
+ const displayId = input3.beatId ?? id;
488082
+ const headline = clean(input3.headline) || humanise(displayId);
487077
488083
  const subhead = clean(input3.subhead);
487078
488084
  const kicker = clean(input3.kicker);
487079
488085
  const dur = input3.duration;
@@ -487096,8 +488102,17 @@ function buildPreset(input3) {
487096
488102
  position: absolute;
487097
488103
  inset: 0;
487098
488104
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 70%);
488105
+ overflow: hidden;
488106
+ }
488107
+ ${scope} .ambient-sweep {
488108
+ position: absolute;
488109
+ inset: -22%;
488110
+ display: block;
488111
+ background: linear-gradient(115deg, rgba(255,255,255,0) 20%, rgba(0,201,255,0.34) 48%, rgba(142,45,226,0.26) 58%, rgba(255,255,255,0) 78%);
488112
+ filter: blur(12px);
488113
+ transform: rotate(8deg);
487099
488114
  }`;
487100
- const backdropMarkup = `<div class="backdrop"></div>`;
488115
+ const backdropMarkup = hasImage ? `<div class="backdrop"></div>` : `<div class="backdrop"><div class="ambient-sweep"></div></div>`;
487101
488116
  switch (input3.preset) {
487102
488117
  case "simple": {
487103
488118
  const transcript = input3.transcript;
@@ -487129,7 +488144,7 @@ function buildPreset(input3) {
487129
488144
  body: `${backdropMarkup}
487130
488145
  <div class="caption" id="caption">${captionInner}</div>`,
487131
488146
  timeline: `${crossfadeTweens(scope, dur)}
487132
- ${kenBurnsTween(scope, dur)}
488147
+ ${backdropMotionTweens(scope, dur, hasImage)}
487133
488148
  ${captionTween}`
487134
488149
  };
487135
488150
  }
@@ -487168,12 +488183,12 @@ function buildPreset(input3) {
487168
488183
  // scene's own fade-in handles the visual transition. Backdrop
487169
488184
  // Ken-Burns fills what used to be 80% dead static frame.
487170
488185
  timeline: `${crossfadeTweens(scope, dur)}
487171
- ${kenBurnsTween(scope, dur)}
488186
+ ${backdropMotionTweens(scope, dur, hasImage)}
487172
488187
  tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);`
487173
488188
  };
487174
488189
  }
487175
488190
  case "explainer": {
487176
- const k2 = kicker || humanise(id).toUpperCase();
488191
+ const k2 = kicker || humanise(displayId).toUpperCase();
487177
488192
  const sub = subhead || "";
487178
488193
  const transcript = input3.transcript;
487179
488194
  const useWordSync = !!(transcript && transcript.length > 0 && sub);
@@ -487215,7 +488230,7 @@ function buildPreset(input3) {
487215
488230
  <div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
487216
488231
  </div>`,
487217
488232
  timeline: `${crossfadeTweens(scope, dur)}
487218
- ${kenBurnsTween(scope, dur)}
488233
+ ${backdropMotionTweens(scope, dur, hasImage)}
487219
488234
  tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
487220
488235
  tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
487221
488236
  ${subtitleTween}`
@@ -487256,12 +488271,12 @@ function buildPreset(input3) {
487256
488271
  body: `${backdropMarkup}
487257
488272
  <div class="kinetic">${wordSpans}</div>`,
487258
488273
  timeline: `${crossfadeTweens(scope, dur)}
487259
- ${kenBurnsTween(scope, dur)}
488274
+ ${backdropMotionTweens(scope, dur, hasImage)}
487260
488275
  ${tweens}`
487261
488276
  };
487262
488277
  }
487263
488278
  case "product-shot": {
487264
- const label = kicker || humanise(id);
488279
+ const label = kicker || humanise(displayId);
487265
488280
  const headlineFontPx = fitFontSize(headline, 72, 40, 50);
487266
488281
  return {
487267
488282
  css: `${scope} {
@@ -487295,7 +488310,7 @@ function buildPreset(input3) {
487295
488310
  <div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
487296
488311
  <div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
487297
488312
  timeline: `${crossfadeTweens(scope, dur)}
487298
- tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
488313
+ ${backdropMotionTweens(scope, dur, hasImage, 1.12)}
487299
488314
  tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
487300
488315
  tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
487301
488316
  tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`
@@ -487360,9 +488375,10 @@ function emitSceneHtml(input3) {
487360
488375
  if (input3.width <= 0 || input3.height <= 0) {
487361
488376
  throw new Error(`Invalid canvas dims: ${input3.width}x${input3.height}`);
487362
488377
  }
487363
- const id = input3.id;
488378
+ const beatId = input3.id;
488379
+ const id = sceneCompositionId(beatId);
487364
488380
  const dur = Number(input3.duration.toFixed(3));
487365
- const parts = buildPreset({ ...input3, id, duration: dur });
488381
+ const parts = buildPreset({ ...input3, id, beatId, duration: dur });
487366
488382
  const audioBlock = input3.audioPath ? `
487367
488383
  <audio
487368
488384
  id="narration"
@@ -487378,7 +488394,7 @@ function emitSceneHtml(input3) {
487378
488394
  ${lottieLayer.markup}` : "";
487379
488395
  const lottieScript = lottieLayer ? `
487380
488396
  ${lottieLayer.script}` : "";
487381
- return `<template id="scene-${id}-template">
488397
+ return `<template id="${id}-template">
487382
488398
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input3.width}" data-height="${input3.height}">
487383
488399
  <style>
487384
488400
  ${parts.css}
@@ -487414,8 +488430,9 @@ function buildClipReference(opts) {
487414
488430
  const duration = Number(opts.duration.toFixed(3));
487415
488431
  const track = opts.trackIndex ?? 1;
487416
488432
  const src = opts.src ?? `compositions/scene-${opts.id}.html`;
488433
+ const compositionId = sceneCompositionId(opts.id);
487417
488434
  const zIndex = Math.max(1, 1e6 - Math.floor(start * 1e3));
487418
- return `<div class="clip" data-composition-id="${esc(opts.id)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
488435
+ return `<div class="clip" data-composition-id="${esc(compositionId)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
487419
488436
  }
487420
488437
  function insertClipIntoRoot(rootHtml, clip) {
487421
488438
  const clipDiv = buildClipReference(clip);
@@ -487459,9 +488476,9 @@ init_agent_host_detect();
487459
488476
  // ../cli/src/commands/_shared/install-skill.ts
487460
488477
  init_bundle_content();
487461
488478
  init_bundle();
487462
- import { mkdir as mkdir8, writeFile as writeFile11 } from "node:fs/promises";
488479
+ import { mkdir as mkdir8, writeFile as writeFile12 } from "node:fs/promises";
487463
488480
  import { existsSync as existsSync21 } from "node:fs";
487464
- import { dirname as dirname12, join as join21, relative as relative5, resolve as resolve15 } from "node:path";
488481
+ import { dirname as dirname12, join as join21, relative as relative5, resolve as resolve16 } from "node:path";
487465
488482
  function universalFiles() {
487466
488483
  return [
487467
488484
  { relPath: "SKILL.md", content: SKILL_MD },
@@ -487511,7 +488528,7 @@ function selectHostFiles(hosts) {
487511
488528
  return out;
487512
488529
  }
487513
488530
  async function installHyperframesSkill(opts) {
487514
- const projectDir = resolve15(opts.projectDir);
488531
+ const projectDir = resolve16(opts.projectDir);
487515
488532
  const force = opts.force ?? false;
487516
488533
  const dryRun = opts.dryRun ?? false;
487517
488534
  const files = [...universalFiles(), ...selectHostFiles(opts.hosts)];
@@ -487536,7 +488553,7 @@ async function installHyperframesSkill(opts) {
487536
488553
  continue;
487537
488554
  }
487538
488555
  await mkdir8(dirname12(absPath), { recursive: true });
487539
- await writeFile11(absPath, file.content, "utf-8");
488556
+ await writeFile12(absPath, file.content, "utf-8");
487540
488557
  actions.push({ path: file.relPath, status: "wrote", bytes });
487541
488558
  }
487542
488559
  return {
@@ -487561,10 +488578,10 @@ init_scene_lint();
487561
488578
  init_storyboard_parse();
487562
488579
  init_root_sync();
487563
488580
  import { existsSync as existsSync26 } from "node:fs";
487564
- import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile14 } from "node:fs/promises";
487565
- import { join as join26, resolve as resolve19 } from "node:path";
488581
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile15 } from "node:fs/promises";
488582
+ import { join as join26, resolve as resolve20 } from "node:path";
487566
488583
  async function executeSceneSubmit(opts) {
487567
- const projectDir = resolve19(opts.projectDir);
488584
+ const projectDir = resolve20(opts.projectDir);
487568
488585
  const beatId = opts.beatId.trim();
487569
488586
  const scenePath = `compositions/scene-${beatId}.html`;
487570
488587
  const base = {
@@ -487656,7 +488673,7 @@ async function executeSceneSubmit(opts) {
487656
488673
  }
487657
488674
  const absPath = join26(projectDir, scenePath);
487658
488675
  await mkdir10(join26(projectDir, "compositions"), { recursive: true });
487659
- await writeFile14(absPath, html.endsWith("\n") ? html : `${html}
488676
+ await writeFile15(absPath, html.endsWith("\n") ? html : `${html}
487660
488677
  `, "utf-8");
487661
488678
  return {
487662
488679
  ...base,
@@ -487671,6 +488688,8 @@ async function executeSceneSubmit(opts) {
487671
488688
 
487672
488689
  // ../cli/src/commands/scene.ts
487673
488690
  init_scene_repair();
488691
+ init_root_sync();
488692
+ init_storyboard_edit();
487674
488693
  function validateDuration(value) {
487675
488694
  const n2 = parseFloat(value);
487676
488695
  if (!Number.isFinite(n2) || n2 <= 0 || n2 > 3600) {
@@ -487973,7 +488992,10 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487973
488992
  ).option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option(
487974
488993
  "--visuals <prompt>",
487975
488994
  "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider"
487976
- ).option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option(
488995
+ ).option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option(
488996
+ "--no-storyboard",
488997
+ "Do not sync STORYBOARD.md; insert this scene directly into the root composition only"
488998
+ ).option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option(
487977
488999
  "--tts <provider>",
487978
489000
  "TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
487979
489001
  "auto"
@@ -488042,6 +489064,7 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
488042
489064
  kicker: options.kicker,
488043
489065
  project: options.project,
488044
489066
  insertInto: options.insertInto,
489067
+ syncStoryboard: options.storyboard !== false,
488045
489068
  imageProvider: options.imageProvider,
488046
489069
  tts,
488047
489070
  audio: options.audio,
@@ -488069,6 +489092,7 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
488069
489092
  kicker: options.kicker,
488070
489093
  projectDir: options.project,
488071
489094
  insertInto: options.insertInto,
489095
+ syncStoryboard: options.storyboard !== false,
488072
489096
  imageProvider: options.imageProvider,
488073
489097
  tts,
488074
489098
  voice: options.voice,
@@ -488108,6 +489132,13 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
488108
489132
  if (result.audioPath) console.log(source_default.green(" +"), result.audioPath);
488109
489133
  if (result.imagePath) console.log(source_default.green(" +"), result.imagePath);
488110
489134
  if (result.transcriptPath) console.log(source_default.green(" +"), result.transcriptPath);
489135
+ if (result.storyboardPath && result.storyboardSynced) {
489136
+ console.log(
489137
+ result.storyboardAction === "updated" ? source_default.yellow(" ~") : source_default.green(" +"),
489138
+ result.storyboardPath,
489139
+ source_default.dim(`(${result.storyboardAction})`)
489140
+ );
489141
+ }
488111
489142
  console.log(source_default.yellow(" ~"), result.rootPath, source_default.dim("(updated)"));
488112
489143
  console.log();
488113
489144
  console.log(source_default.bold.cyan("Composition"));
@@ -488154,6 +489185,21 @@ async function loadVibeProjectConfig(projectDir) {
488154
489185
  }
488155
489186
  };
488156
489187
  }
489188
+ function escapeRegExp3(value) {
489189
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
489190
+ }
489191
+ function readClipStart(rootHtml, beatId) {
489192
+ const compositionId = sceneCompositionId(beatId);
489193
+ const match2 = rootHtml.match(
489194
+ new RegExp(
489195
+ `<div\\s+class="clip"[^>]*\\bdata-composition-id="${escapeRegExp3(compositionId)}"[^>]*\\bdata-start="([\\d.]+)"`,
489196
+ "i"
489197
+ )
489198
+ );
489199
+ if (!match2) return void 0;
489200
+ const start = Number.parseFloat(match2[1]);
489201
+ return Number.isFinite(start) ? start : void 0;
489202
+ }
488157
489203
  async function resolveNarrationText(value) {
488158
489204
  if (!value) return void 0;
488159
489205
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
@@ -488180,6 +489226,7 @@ async function executeSceneAdd(opts) {
488180
489226
  const id = slugifySceneName(opts.name);
488181
489227
  const sceneRel = `compositions/scene-${id}.html`;
488182
489228
  const scenePath = resolve24(projectDir, sceneRel);
489229
+ const storyboardAbsPath = resolve24(projectDir, "STORYBOARD.md");
488183
489230
  const errResult = (error) => ({
488184
489231
  success: false,
488185
489232
  id,
@@ -488399,6 +489446,27 @@ async function executeSceneAdd(opts) {
488399
489446
  duration = fallback2;
488400
489447
  }
488401
489448
  duration = Number(duration.toFixed(2));
489449
+ let storyboardPath;
489450
+ let storyboardSynced = false;
489451
+ let storyboardAction = "skipped";
489452
+ const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
489453
+ if (shouldSyncStoryboard) {
489454
+ opts.onProgress?.("Syncing STORYBOARD.md...");
489455
+ const before2 = await readFile14(storyboardAbsPath, "utf-8");
489456
+ const synced = upsertStoryboardBeat(before2, {
489457
+ beatId: id,
489458
+ title: opts.headline,
489459
+ duration,
489460
+ narration: narrationText && !opts.skipAudio ? narrationText : void 0,
489461
+ backdrop: opts.visuals && !opts.skipImage ? opts.visuals : void 0
489462
+ });
489463
+ if (synced.markdown !== before2) {
489464
+ await writeFile17(storyboardAbsPath, synced.markdown, "utf-8");
489465
+ }
489466
+ storyboardPath = relative8(process.cwd(), storyboardAbsPath) || storyboardAbsPath;
489467
+ storyboardSynced = true;
489468
+ storyboardAction = synced.action;
489469
+ }
488402
489470
  opts.onProgress?.("Emitting scene HTML...");
488403
489471
  const sceneHtml = emitSceneHtml({
488404
489472
  id,
@@ -488417,11 +489485,24 @@ async function executeSceneAdd(opts) {
488417
489485
  await mkdir11(dirname14(scenePath), { recursive: true });
488418
489486
  await writeFile17(scenePath, sceneHtml, "utf-8");
488419
489487
  opts.onProgress?.("Updating root composition...");
488420
- const start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
488421
- const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
488422
- const trackIndex = existingClipCount % 2 + 1;
488423
- const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
488424
- await writeFile17(rootPath, updated, "utf-8");
489488
+ let start = 0;
489489
+ if (storyboardSynced) {
489490
+ const syncPlan = await syncRootComposition({
489491
+ projectDir,
489492
+ rootRel,
489493
+ beats: await loadProjectRootSyncBeats(projectDir)
489494
+ });
489495
+ const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
489496
+ if (blockingIssue) return errResult(blockingIssue.message);
489497
+ const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
489498
+ start = readClipStart(rootHtmlAfterSync, id) ?? 0;
489499
+ } else {
489500
+ start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
489501
+ const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
489502
+ const trackIndex = existingClipCount % 2 + 1;
489503
+ const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
489504
+ await writeFile17(rootPath, updated, "utf-8");
489505
+ }
488425
489506
  const transcriptAbsPath = transcriptRelPath ? resolve24(projectDir, transcriptRelPath) : void 0;
488426
489507
  return {
488427
489508
  success: true,
@@ -488434,6 +489515,9 @@ async function executeSceneAdd(opts) {
488434
489515
  audioPath: audioAbsPath ? relative8(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
488435
489516
  imagePath: imageAbsPath ? relative8(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
488436
489517
  lottiePath: lottieAbsPath ? relative8(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
489518
+ storyboardPath,
489519
+ storyboardSynced,
489520
+ storyboardAction,
488437
489521
  transcriptPath: transcriptAbsPath ? relative8(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
488438
489522
  transcriptWordCount
488439
489523
  };
@@ -488731,6 +489815,9 @@ var sceneAddSchema = z2.object({
488731
489815
  kicker: z2.string().optional().describe("Small label above the headline (used by 'explainer' and 'product-shot' presets)."),
488732
489816
  projectDir: z2.string().optional().describe(PROJECT_DIR_DESCRIPTION),
488733
489817
  insertInto: z2.string().optional().describe("Root composition file (relative to projectDir). Default 'index.html'."),
489818
+ syncStoryboard: z2.boolean().optional().describe(
489819
+ "Sync STORYBOARD.md when present, then rebuild root refs from it. Default true; set false for root-only insertion."
489820
+ ),
488734
489821
  imageProvider: z2.enum(["gemini", "openai"]).optional().describe("Image provider for visuals. Default 'gemini'."),
488735
489822
  voice: z2.string().optional().describe("ElevenLabs voice id or name."),
488736
489823
  skipAudio: z2.boolean().optional().describe("Skip TTS even if narration is provided."),
@@ -488743,7 +489830,7 @@ var sceneAddTool = defineTool({
488743
489830
  cost: "low",
488744
489831
  title: "Add Scene from Preset",
488745
489832
  annotations: { readOnly: false, openWorld: true },
488746
- description: "Add a single scene to an existing scene project. Optionally generates narration audio (ElevenLabs) and/or a backdrop image (Gemini/OpenAI), then emits compositions/scene-<id>.html with a paused GSAP timeline and splices a clip reference into the root index.html. Use skipAudio:true and skipImage:true for text-only scenes that need no API calls.",
489833
+ 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.",
488747
489834
  schema: sceneAddSchema,
488748
489835
  async execute(args, ctx) {
488749
489836
  const projectDir = args.projectDir ? resolve34(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
@@ -488757,6 +489844,7 @@ var sceneAddTool = defineTool({
488757
489844
  kicker: args.kicker,
488758
489845
  projectDir,
488759
489846
  insertInto: args.insertInto,
489847
+ syncStoryboard: args.syncStoryboard,
488760
489848
  imageProvider: args.imageProvider,
488761
489849
  voice: args.voice,
488762
489850
  skipAudio: args.skipAudio,
@@ -488775,6 +489863,9 @@ var sceneAddTool = defineTool({
488775
489863
  ];
488776
489864
  if (result.audioPath) lines.push(` audio: ${result.audioPath}`);
488777
489865
  if (result.imagePath) lines.push(` image: ${result.imagePath}`);
489866
+ if (result.storyboardPath && result.storyboardSynced) {
489867
+ lines.push(` story: ${result.storyboardPath} (${result.storyboardAction})`);
489868
+ }
488778
489869
  return {
488779
489870
  success: true,
488780
489871
  data: {
@@ -488785,7 +489876,10 @@ var sceneAddTool = defineTool({
488785
489876
  scenePath: result.scenePath,
488786
489877
  rootPath: result.rootPath,
488787
489878
  audioPath: result.audioPath,
488788
- imagePath: result.imagePath
489879
+ imagePath: result.imagePath,
489880
+ storyboardPath: result.storyboardPath,
489881
+ storyboardSynced: result.storyboardSynced,
489882
+ storyboardAction: result.storyboardAction
488789
489883
  },
488790
489884
  humanLines: lines
488791
489885
  };
@@ -490220,7 +491314,7 @@ init_root_sync();
490220
491314
  init_review_report();
490221
491315
  import { existsSync as existsSync56 } from "node:fs";
490222
491316
  import { readdir as readdir6, readFile as readFile31, stat as stat5 } from "node:fs/promises";
490223
- import { basename as basename14, isAbsolute as isAbsolute7, join as join38, relative as relative13, resolve as resolve50 } from "node:path";
491317
+ import { basename as basename14, isAbsolute as isAbsolute7, join as join37, relative as relative13, resolve as resolve50 } from "node:path";
490224
491318
  async function inspectProject(opts) {
490225
491319
  const projectDir = resolve50(opts.projectDir);
490226
491320
  const issues = [];
@@ -490249,12 +491343,12 @@ async function inspectProject(opts) {
490249
491343
  );
490250
491344
  }
490251
491345
  const coreFiles = {
490252
- storyboard: join38(projectDir, "STORYBOARD.md"),
490253
- design: join38(projectDir, "DESIGN.md"),
490254
- config: join38(projectDir, "vibe.config.json"),
490255
- legacyConfig: join38(projectDir, "vibe.project.yaml"),
490256
- buildReport: join38(projectDir, "build-report.json"),
490257
- root: join38(projectDir, "index.html")
491346
+ storyboard: join37(projectDir, "STORYBOARD.md"),
491347
+ design: join37(projectDir, "DESIGN.md"),
491348
+ config: join37(projectDir, "vibe.config.json"),
491349
+ legacyConfig: join37(projectDir, "vibe.project.yaml"),
491350
+ buildReport: join37(projectDir, "build-report.json"),
491351
+ root: join37(projectDir, "index.html")
490258
491352
  };
490259
491353
  for (const [name, path14] of Object.entries(coreFiles)) {
490260
491354
  checks.files[name] = existsSync56(path14);
@@ -490338,7 +491432,7 @@ async function inspectProject(opts) {
490338
491432
  });
490339
491433
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
490340
491434
  }
490341
- const compositionsDir = join38(projectDir, "compositions");
491435
+ const compositionsDir = join37(projectDir, "compositions");
490342
491436
  const existingComps = existsSync56(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
490343
491437
  checks.compositions.expected = inspectedBeatIds.length;
490344
491438
  for (const beatId of inspectedBeatIds) {
@@ -490346,12 +491440,12 @@ async function inspectProject(opts) {
490346
491440
  if (existingComps.has(file)) {
490347
491441
  checks.compositions.found++;
490348
491442
  } else {
490349
- checks.compositions.missing.push(join38("compositions", file));
491443
+ checks.compositions.missing.push(join37("compositions", file));
490350
491444
  issues.push({
490351
491445
  severity: "error",
490352
491446
  code: "MISSING_COMPOSITION",
490353
491447
  message: `Composition for beat "${beatId}" is missing.`,
490354
- file: join38("compositions", file),
491448
+ file: join37("compositions", file),
490355
491449
  scene: beatId,
490356
491450
  beatId,
490357
491451
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -490413,7 +491507,7 @@ async function inspectProject(opts) {
490413
491507
  try {
490414
491508
  const lint = await runProjectLint({ projectDir });
490415
491509
  const lintFiles = opts.beatId ? lint.files.filter(
490416
- (file) => file.file === "index.html" || file.file === join38("compositions", `scene-${opts.beatId}.html`)
491510
+ (file) => file.file === "index.html" || file.file === join37("compositions", `scene-${opts.beatId}.html`)
490417
491511
  ) : lint.files;
490418
491512
  const lintCounts = countLintFindings(lintFiles);
490419
491513
  checks.lint = {
@@ -490449,7 +491543,11 @@ async function inspectProject(opts) {
490449
491543
  );
490450
491544
  }
490451
491545
  function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
490452
- const normalizedIssues = normalizeReviewIssues(issues);
491546
+ const normalizedRetryWith = uniqueRetryWith(retryWith);
491547
+ const normalizedIssues = normalizeReviewIssues(issues, {
491548
+ projectDir,
491549
+ retryWith: normalizedRetryWith
491550
+ });
490453
491551
  const status = statusFromIssues(normalizedIssues);
490454
491552
  return {
490455
491553
  schemaVersion: "1",
@@ -490461,9 +491559,14 @@ function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
490461
491559
  score: scoreIssues(normalizedIssues),
490462
491560
  issues: normalizedIssues,
490463
491561
  summary: summarizeReviewIssues(normalizedIssues),
491562
+ nextActions: deriveNextReviewActions({
491563
+ issues: normalizedIssues,
491564
+ retryWith: normalizedRetryWith,
491565
+ projectDir
491566
+ }),
490464
491567
  sourceReports: projectSourceReports(checks),
490465
491568
  checks,
490466
- retryWith: uniqueRetryWith(retryWith)
491569
+ retryWith: normalizedRetryWith
490467
491570
  };
490468
491571
  }
490469
491572
  function storyboardPlaceholderIssues(storyboard) {
@@ -490783,7 +491886,7 @@ init_storyboard_parse();
490783
491886
  init_review_report();
490784
491887
  import { existsSync as existsSync57 } from "node:fs";
490785
491888
  import { readdir as readdir7, readFile as readFile32, stat as stat6 } from "node:fs/promises";
490786
- import { basename as basename15, isAbsolute as isAbsolute8, join as join39, relative as relative14, resolve as resolve51 } from "node:path";
491889
+ import { basename as basename15, isAbsolute as isAbsolute8, join as join38, relative as relative14, resolve as resolve51 } from "node:path";
490787
491890
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
490788
491891
  var DEFAULT_AI_MODEL = "flash";
490789
491892
  var BLACK_FRAME_MIN_DURATION_SEC = 1;
@@ -491015,6 +492118,8 @@ async function inspectRender(opts) {
491015
492118
  severity: "error",
491016
492119
  code: "RENDER_NOT_FOUND",
491017
492120
  message: opts.beatId ? `No rendered video was found for beat "${opts.beatId}". Pass --video or render the beat first.` : "No rendered video was found. Pass --video or render the project first.",
492121
+ beatId: opts.beatId,
492122
+ scene: opts.beatId,
491018
492123
  suggestedFix: opts.beatId ? "Run `vibe render <project> --beat <id> --json`." : "Run `vibe build --stage render --json` or `vibe render --json`."
491019
492124
  });
491020
492125
  retryWith.push(
@@ -491037,6 +492142,8 @@ async function inspectRender(opts) {
491037
492142
  code: "EMPTY_RENDER",
491038
492143
  message: "Rendered video file is empty.",
491039
492144
  file: displayPath(videoPath),
492145
+ beatId: opts.beatId,
492146
+ scene: opts.beatId,
491040
492147
  suggestedFix: opts.beatId ? "Render again with `vibe render --beat <id> --json`." : "Render again with `vibe render --json`."
491041
492148
  });
491042
492149
  retryWith.push(
@@ -491046,6 +492153,8 @@ async function inspectRender(opts) {
491046
492153
  const beatTimings = await beatTimingsFromBuildReport(projectDir, opts.beatId);
491047
492154
  const expectedDurationSec = beatTimings.length > 0 ? Number(beatTimings.reduce((sum, beat) => sum + beat.sceneDurationSec, 0).toFixed(3)) : await expectedDurationFromBuildReport(projectDir, opts.beatId);
491048
492155
  if (expectedDurationSec !== void 0) checks.expectedDurationSec = expectedDurationSec;
492156
+ const expectedAudio = await renderExpectsAudio(projectDir, opts.beatId);
492157
+ checks.expectedAudio = expectedAudio;
491049
492158
  if (!commandExists("ffprobe")) {
491050
492159
  issues.push({
491051
492160
  severity: "error",
@@ -491071,7 +492180,7 @@ async function inspectRender(opts) {
491071
492180
  file: displayPath(videoPath)
491072
492181
  });
491073
492182
  }
491074
- if (!audioStream) {
492183
+ if (!audioStream && expectedAudio) {
491075
492184
  issues.push({
491076
492185
  severity: "warning",
491077
492186
  code: "NO_AUDIO_STREAM",
@@ -491276,8 +492385,86 @@ async function inspectRender(opts) {
491276
492385
  );
491277
492386
  return maybeWriteRenderReport(projectDir, opts, result);
491278
492387
  }
492388
+ async function renderExpectsAudio(projectDir, beatId) {
492389
+ const root2 = resolve51(projectDir);
492390
+ const rootHtml = await readTextIfExists(join38(root2, "index.html"));
492391
+ if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
492392
+ const buildReport = await readJsonIfExists(join38(root2, "build-report.json"));
492393
+ if (buildReportExpectsAudio(buildReport, beatId)) return true;
492394
+ const storyboard = await readTextIfExists(join38(root2, "STORYBOARD.md"));
492395
+ if (!storyboard) return false;
492396
+ try {
492397
+ const parsed = parseStoryboard(storyboard);
492398
+ const beats = beatId ? parsed.beats.filter((beat) => beat.id === beatId) : parsed.beats;
492399
+ return beats.some(
492400
+ (beat) => [
492401
+ `assets/narration-${beat.id}.mp3`,
492402
+ `assets/narration-${beat.id}.wav`,
492403
+ `assets/music-${beat.id}.mp3`,
492404
+ `assets/music-${beat.id}.wav`
492405
+ ].some((asset) => existsSync57(join38(root2, asset)))
492406
+ );
492407
+ } catch {
492408
+ return false;
492409
+ }
492410
+ }
492411
+ function rootHtmlExpectsAudio(html, beatId) {
492412
+ const audioTags = html.match(/<audio\b[^>]*>/gi) ?? [];
492413
+ if (!beatId) return audioTags.length > 0;
492414
+ const escaped = escapeRegExp4(beatId);
492415
+ const beatAudioRe = new RegExp(
492416
+ `(id="(?:narration|music)-${escaped}"|src="[^"]*(?:narration|music)-${escaped}\\.(?:mp3|wav)")`,
492417
+ "i"
492418
+ );
492419
+ return audioTags.some((tag) => beatAudioRe.test(tag));
492420
+ }
492421
+ function buildReportExpectsAudio(report, beatId) {
492422
+ if (!report || typeof report !== "object") return false;
492423
+ const beats = Array.isArray(report.beats) ? report.beats ?? [] : [];
492424
+ const selected = beatId === void 0 ? beats : beats.filter((beat) => {
492425
+ if (!beat || typeof beat !== "object") return false;
492426
+ return beat.id === beatId;
492427
+ });
492428
+ return selected.some((beat) => beatReportExpectsAudio(beat));
492429
+ }
492430
+ function beatReportExpectsAudio(beat) {
492431
+ if (!beat || typeof beat !== "object") return false;
492432
+ const record = beat;
492433
+ if (typeof record.narrationPath === "string" && record.narrationPath.length > 0) return true;
492434
+ if (typeof record.musicPath === "string" && record.musicPath.length > 0) return true;
492435
+ if (parseOptionalNumber2(record.narrationDurationSec) !== void 0) return true;
492436
+ const narration = record.narration;
492437
+ if (narration && typeof narration === "object") {
492438
+ const n2 = narration;
492439
+ if (typeof n2.path === "string" && n2.path.length > 0) return true;
492440
+ if (parseOptionalNumber2(n2.durationSec) !== void 0) return true;
492441
+ }
492442
+ const music = record.music;
492443
+ if (music && typeof music === "object") {
492444
+ const m2 = music;
492445
+ if (typeof m2.path === "string" && m2.path.length > 0) return true;
492446
+ if (parseOptionalNumber2(m2.durationSec) !== void 0) return true;
492447
+ }
492448
+ return false;
492449
+ }
492450
+ async function readJsonIfExists(path14) {
492451
+ const text = await readTextIfExists(path14);
492452
+ if (!text) return null;
492453
+ try {
492454
+ return JSON.parse(text);
492455
+ } catch {
492456
+ return null;
492457
+ }
492458
+ }
492459
+ function escapeRegExp4(value) {
492460
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
492461
+ }
491279
492462
  function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith, score) {
491280
- const normalizedIssues = normalizeReviewIssues(issues);
492463
+ const normalizedRetryWith = uniqueRetryWith(retryWith);
492464
+ const normalizedIssues = normalizeReviewIssues(issues, {
492465
+ projectDir,
492466
+ retryWith: normalizedRetryWith
492467
+ });
491281
492468
  const status = statusFromIssues(normalizedIssues);
491282
492469
  return {
491283
492470
  schemaVersion: "1",
@@ -491290,9 +492477,14 @@ function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith
491290
492477
  score,
491291
492478
  issues: normalizedIssues,
491292
492479
  summary: summarizeReviewIssues(normalizedIssues),
492480
+ nextActions: deriveNextReviewActions({
492481
+ issues: normalizedIssues,
492482
+ retryWith: normalizedRetryWith,
492483
+ projectDir
492484
+ }),
491293
492485
  sourceReports: renderSourceReports(projectDir, videoPath, checks),
491294
492486
  checks,
491295
- retryWith: uniqueRetryWith(retryWith)
492487
+ retryWith: normalizedRetryWith
491296
492488
  };
491297
492489
  }
491298
492490
  async function maybeWriteRenderReport(projectDir, opts, result) {
@@ -491318,8 +492510,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
491318
492510
  }
491319
492511
  function renderSourceReports(projectDir, videoPath, checks) {
491320
492512
  const reports = [];
491321
- if (existsSync57(join39(projectDir, "build-report.json"))) reports.push("build-report.json");
491322
- if (existsSync57(join39(projectDir, "render-report.json"))) reports.push("render-report.json");
492513
+ if (existsSync57(join38(projectDir, "build-report.json"))) reports.push("build-report.json");
492514
+ if (existsSync57(join38(projectDir, "render-report.json"))) reports.push("render-report.json");
491323
492515
  if (videoPath) reports.push(displayPath(videoPath));
491324
492516
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
491325
492517
  reports.push("ffprobe");
@@ -491334,7 +492526,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491334
492526
  const candidate = resolve51(process.cwd(), explicit);
491335
492527
  return existsSync57(candidate) ? candidate : null;
491336
492528
  }
491337
- const renderReportPath = join39(projectDir, "render-report.json");
492529
+ const renderReportPath = join38(projectDir, "render-report.json");
491338
492530
  if (existsSync57(renderReportPath)) {
491339
492531
  try {
491340
492532
  const report = JSON.parse(await readFile32(renderReportPath, "utf-8"));
@@ -491346,7 +492538,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491346
492538
  } catch {
491347
492539
  }
491348
492540
  }
491349
- const reportPath = join39(projectDir, "build-report.json");
492541
+ const reportPath = join38(projectDir, "build-report.json");
491350
492542
  if (!beatId && existsSync57(reportPath)) {
491351
492543
  try {
491352
492544
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491355,7 +492547,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491355
492547
  } catch {
491356
492548
  }
491357
492549
  }
491358
- const rendersDir = join39(projectDir, "renders");
492550
+ const rendersDir = join38(projectDir, "renders");
491359
492551
  if (!existsSync57(rendersDir)) return null;
491360
492552
  const entries2 = await readdir7(rendersDir, { withFileTypes: true });
491361
492553
  const candidates = [];
@@ -491364,7 +492556,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491364
492556
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
491365
492557
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
491366
492558
  if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
491367
- const full = join39(rendersDir, entry.name);
492559
+ const full = join38(rendersDir, entry.name);
491368
492560
  const info = await stat6(full);
491369
492561
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
491370
492562
  }
@@ -491382,11 +492574,11 @@ async function runAiRenderReview(projectDir, videoPath, model) {
491382
492574
  });
491383
492575
  }
491384
492576
  function resolveStoryboardPath(projectDir) {
491385
- const storyboardPath = join39(projectDir, "STORYBOARD.md");
492577
+ const storyboardPath = join38(projectDir, "STORYBOARD.md");
491386
492578
  return existsSync57(storyboardPath) ? storyboardPath : null;
491387
492579
  }
491388
492580
  async function expectedDurationFromBuildReport(projectDir, beatId) {
491389
- const reportPath = join39(projectDir, "build-report.json");
492581
+ const reportPath = join38(projectDir, "build-report.json");
491390
492582
  if (!existsSync57(reportPath)) return void 0;
491391
492583
  try {
491392
492584
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491401,7 +492593,7 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
491401
492593
  }
491402
492594
  }
491403
492595
  async function beatTimingsFromBuildReport(projectDir, beatId) {
491404
- const reportPath = join39(projectDir, "build-report.json");
492596
+ const reportPath = join38(projectDir, "build-report.json");
491405
492597
  if (!existsSync57(reportPath)) return [];
491406
492598
  try {
491407
492599
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491430,17 +492622,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
491430
492622
  }
491431
492623
  async function buildRenderReviewProjectContext(projectDir) {
491432
492624
  const parts = [];
491433
- const storyboard = await readTextIfExists(join39(projectDir, "STORYBOARD.md"));
492625
+ const storyboard = await readTextIfExists(join38(projectDir, "STORYBOARD.md"));
491434
492626
  if (storyboard) {
491435
492627
  parts.push(`STORYBOARD.md:
491436
492628
  ${truncateText(storyboard, 4e3)}`);
491437
492629
  parts.push(`Storyboard beat cue summary:
491438
492630
  ${storyboardBeatCueSummary(storyboard)}`);
491439
492631
  }
491440
- const design = await readTextIfExists(join39(projectDir, "DESIGN.md"));
492632
+ const design = await readTextIfExists(join38(projectDir, "DESIGN.md"));
491441
492633
  if (design) parts.push(`DESIGN.md:
491442
492634
  ${truncateText(design, 4e3)}`);
491443
- const buildReport = await readTextIfExists(join39(projectDir, "build-report.json"));
492635
+ const buildReport = await readTextIfExists(join38(projectDir, "build-report.json"));
491444
492636
  if (buildReport) parts.push(`build-report.json:
491445
492637
  ${truncateText(buildReport, 6e3)}`);
491446
492638
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -492299,7 +493491,7 @@ init_storyboard_edit();
492299
493491
  // ../cli/src/commands/_shared/storyboard-revise.ts
492300
493492
  import { existsSync as existsSync59 } from "node:fs";
492301
493493
  import { readFile as readFile35, writeFile as writeFile42 } from "node:fs/promises";
492302
- import { join as join40, resolve as resolve64 } from "node:path";
493494
+ import { join as join39, resolve as resolve64 } from "node:path";
492303
493495
 
492304
493496
  // ../cli/src/agent/adapters/index.ts
492305
493497
  init_openai3();
@@ -492346,7 +493538,7 @@ init_storyboard_parse();
492346
493538
  init_storyboard_edit();
492347
493539
  async function executeStoryboardRevision(opts) {
492348
493540
  const projectDir = resolve64(opts.projectDir);
492349
- const storyboardPath = join40(projectDir, "STORYBOARD.md");
493541
+ const storyboardPath = join39(projectDir, "STORYBOARD.md");
492350
493542
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
492351
493543
  if (!existsSync59(storyboardPath)) {
492352
493544
  return failureResult({
@@ -492581,8 +493773,8 @@ function diffChangedBeats(before2, after2) {
492581
493773
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
492582
493774
  }
492583
493775
  async function readRevisionContext(projectDir) {
492584
- const designMd = await readOptionalText(join40(projectDir, "DESIGN.md"));
492585
- const buildReportJson = await readOptionalJsonText(join40(projectDir, "build-report.json"));
493776
+ const designMd = await readOptionalText(join39(projectDir, "DESIGN.md"));
493777
+ const buildReportJson = await readOptionalJsonText(join39(projectDir, "build-report.json"));
492586
493778
  return { designMd, buildReportJson };
492587
493779
  }
492588
493780
  async function readOptionalText(path14) {
@@ -492897,7 +494089,7 @@ var storyboardTools = [
492897
494089
  // ../cli/src/tools/manifest/remix.ts
492898
494090
  import { writeFile as writeFile48 } from "node:fs/promises";
492899
494091
  import { tmpdir as tmpdir6 } from "node:os";
492900
- import { join as join41 } from "node:path";
494092
+ import { join as join40 } from "node:path";
492901
494093
  import { z as z8 } from "zod";
492902
494094
 
492903
494095
  // ../cli/src/commands/ai-script-pipeline.ts
@@ -494631,7 +495823,7 @@ var pipelineRunTool = defineTool({
494631
495823
  let resolvedPath = args.pipelinePath;
494632
495824
  let tempPath;
494633
495825
  if (args.pipelineYaml) {
494634
- tempPath = join41(tmpdir6(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
495826
+ tempPath = join40(tmpdir6(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
494635
495827
  await writeFile48(tempPath, args.pipelineYaml, "utf-8");
494636
495828
  resolvedPath = tempPath;
494637
495829
  }
@@ -495163,7 +496355,7 @@ init_engine();
495163
496355
  init_storyboard_parse();
495164
496356
  import { readdir as readdir8, readFile as readFile42, stat as stat7 } from "node:fs/promises";
495165
496357
  import { existsSync as existsSync65 } from "node:fs";
495166
- import { join as join43, resolve as resolve71 } from "node:path";
496358
+ import { join as join41, resolve as resolve71 } from "node:path";
495167
496359
  async function executeProjectList(opts = {}) {
495168
496360
  const workspaceDir = resolve71(opts.workspaceDir ?? ".");
495169
496361
  if (!existsSync65(workspaceDir)) {
@@ -495183,11 +496375,11 @@ async function executeProjectList(opts = {}) {
495183
496375
  if (entry.name === "node_modules") continue;
495184
496376
  if (entry.name.startsWith(".")) continue;
495185
496377
  if (entry.name.startsWith("_")) {
495186
- archivedCount += await countProjects(join43(workspaceDir, entry.name));
496378
+ archivedCount += await countProjects(join41(workspaceDir, entry.name));
495187
496379
  continue;
495188
496380
  }
495189
- const projectDir = join43(workspaceDir, entry.name);
495190
- if (!existsSync65(join43(projectDir, "STORYBOARD.md"))) continue;
496381
+ const projectDir = join41(workspaceDir, entry.name);
496382
+ if (!existsSync65(join41(projectDir, "STORYBOARD.md"))) continue;
495191
496383
  projects.push(await describeProject(entry.name, projectDir));
495192
496384
  }
495193
496385
  projects.sort((a2, b2) => b2.updatedAt.localeCompare(a2.updatedAt));
@@ -495197,7 +496389,7 @@ async function countProjects(dir) {
495197
496389
  try {
495198
496390
  const entries2 = await readdir8(dir, { withFileTypes: true });
495199
496391
  return entries2.filter(
495200
- (e2) => e2.isDirectory() && existsSync65(join43(dir, e2.name, "STORYBOARD.md"))
496392
+ (e2) => e2.isDirectory() && existsSync65(join41(dir, e2.name, "STORYBOARD.md"))
495201
496393
  ).length;
495202
496394
  } catch {
495203
496395
  return 0;
@@ -495208,7 +496400,7 @@ async function describeProject(name, projectDir) {
495208
496400
  let storyboardDurationSec = 0;
495209
496401
  const timestamps = [];
495210
496402
  try {
495211
- const storyboardPath = join43(projectDir, "STORYBOARD.md");
496403
+ const storyboardPath = join41(projectDir, "STORYBOARD.md");
495212
496404
  const parsed = parseStoryboard(await readFile42(storyboardPath, "utf-8"));
495213
496405
  beats = parsed.beats.length;
495214
496406
  storyboardDurationSec = Number(
@@ -495220,7 +496412,7 @@ async function describeProject(name, projectDir) {
495220
496412
  let buildStatus;
495221
496413
  let buildUpdatedAt;
495222
496414
  try {
495223
- const reportPath = join43(projectDir, "build-report.json");
496415
+ const reportPath = join41(projectDir, "build-report.json");
495224
496416
  const report = JSON.parse(await readFile42(reportPath, "utf-8"));
495225
496417
  buildStatus = report.status ?? report.phase;
495226
496418
  buildUpdatedAt = report.updatedAt;
@@ -495229,12 +496421,12 @@ async function describeProject(name, projectDir) {
495229
496421
  }
495230
496422
  let latestRender;
495231
496423
  try {
495232
- const rendersDir = join43(projectDir, "renders");
496424
+ const rendersDir = join41(projectDir, "renders");
495233
496425
  const files = await readdir8(rendersDir);
495234
496426
  let newest;
495235
496427
  for (const file of files) {
495236
496428
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
495237
- const { mtimeMs } = await stat7(join43(rendersDir, file));
496429
+ const { mtimeMs } = await stat7(join41(rendersDir, file));
495238
496430
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
495239
496431
  }
495240
496432
  if (newest) {
@@ -496245,7 +497437,7 @@ var exportTools = [exportVideoTool];
496245
497437
 
496246
497438
  // ../cli/src/tools/manifest/agent-only.ts
496247
497439
  import { readFile as readFile44, writeFile as writeFile52, readdir as readdir9, stat as stat8, access as access7, unlink as unlink7 } from "node:fs/promises";
496248
- import { resolve as resolve76, join as join44, basename as basename21, extname as extname14 } from "node:path";
497440
+ import { resolve as resolve76, join as join43, basename as basename21, extname as extname14 } from "node:path";
496249
497441
  import { z as z13 } from "zod";
496250
497442
  init_engine();
496251
497443
  init_exec_safe();
@@ -496324,7 +497516,7 @@ var fsListTool = defineTool({
496324
497516
  const results = [];
496325
497517
  for (const entry of entries2) {
496326
497518
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
496327
- const fullPath = join44(absPath, entry.name);
497519
+ const fullPath = join43(absPath, entry.name);
496328
497520
  const stats = await stat8(fullPath);
496329
497521
  if (entry.isDirectory()) {
496330
497522
  results.push(`[DIR] ${entry.name}/`);
@@ -496437,7 +497629,7 @@ var batchImportTool = defineTool({
496437
497629
  const scanDir = async (dir) => {
496438
497630
  const entries2 = await readdir9(dir, { withFileTypes: true });
496439
497631
  for (const entry of entries2) {
496440
- const entryPath = join44(dir, entry.name);
497632
+ const entryPath = join43(dir, entry.name);
496441
497633
  if (entry.isDirectory() && recursive) {
496442
497634
  await scanDir(entryPath);
496443
497635
  } else if (entry.isFile()) {
@@ -498507,7 +499699,7 @@ var server2 = new Server(
498507
499699
  {
498508
499700
  name: "vibeframe",
498509
499701
  // Stamped with the package version by build.js at bundle time.
498510
- version: "0.113.0"
499702
+ version: "0.113.3"
498511
499703
  },
498512
499704
  {
498513
499705
  capabilities: {