@vibeframe/mcp-server 0.113.0 → 0.113.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +131 -120
  2. package/dist/index.js +1687 -652
  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,307 @@ 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
+ "motion",
471702
+ "voice",
471703
+ "music",
471704
+ "asset",
471705
+ "characters"
471706
+ ];
471707
+ HEADING_RE2 = /^##\s+(.+?)\s*$/gm;
471708
+ LEADING_CUE_RE = /^(\s*)```ya?ml\s*\n([\s\S]*?)\n```\s*(?:\n|$)/;
471709
+ ALLOWED_CUE_KEYS = new Set(STORYBOARD_CUE_KEYS);
471710
+ STRING_CUE_KEYS = /* @__PURE__ */ new Set(["narration", "backdrop", "video", "motion", "voice", "music", "asset"]);
471711
+ MAX_RECOMMENDED_BEAT_SEC = 15;
471712
+ }
471713
+ });
471714
+
470872
471715
  // ../cli/src/pipeline/renderers/chrome.ts
470873
471716
  import { existsSync as existsSync29 } from "node:fs";
470874
471717
  import { homedir as homedir7 } from "node:os";
470875
- import { join as join29 } from "node:path";
471718
+ import { join as join28 } from "node:path";
470876
471719
  function findChrome() {
470877
471720
  const candidates = [
470878
471721
  process.env.HYPERFRAMES_CHROME_PATH,
470879
471722
  process.env.CHROME_PATH,
470880
471723
  // puppeteer auto-downloaded headless shell
470881
- join29(
471724
+ join28(
470882
471725
  homedir7(),
470883
471726
  ".cache",
470884
471727
  "puppeteer",
@@ -471171,7 +472014,7 @@ __export(scene_render_exports, {
471171
472014
  import { execFile as execFile2 } from "node:child_process";
471172
472015
  import { existsSync as existsSync30 } from "node:fs";
471173
472016
  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";
472017
+ import { resolve as resolve27, relative as relative9, dirname as dirname16, basename as basename7, join as join29, isAbsolute as isAbsolute3 } from "node:path";
471175
472018
  import { promisify as promisify2 } from "node:util";
471176
472019
  function qualityToCrf(quality = "standard") {
471177
472020
  return quality === "draft" ? 28 : quality === "high" ? 18 : 23;
@@ -471259,7 +472102,7 @@ async function executeSceneRender(opts = {}) {
471259
472102
  }
471260
472103
  let autoSyncApplied = false;
471261
472104
  let autoSyncWarning;
471262
- if (!opts.beatId && existsSync30(join30(projectDir, "STORYBOARD.md"))) {
472105
+ if (!opts.beatId && existsSync30(join29(projectDir, "STORYBOARD.md"))) {
471263
472106
  try {
471264
472107
  const rootPlan = await createProjectRootSyncPlan({ projectDir });
471265
472108
  const subPlans = await createSubCompDurationSyncPlans({
@@ -471436,7 +472279,7 @@ async function safeStat(p2) {
471436
472279
  }
471437
472280
  async function prepareBeatRenderRoot(opts) {
471438
472281
  const root2 = `.vibeframe/tmp/render-beat-${sanitizeFileSegment(opts.beatId)}.html`;
471439
- const reportPath = join30(opts.projectDir, "build-report.json");
472282
+ const reportPath = join29(opts.projectDir, "build-report.json");
471440
472283
  const retryWith = [
471441
472284
  `vibe build ${opts.projectDir} --beat ${opts.beatId} --stage sync --json`
471442
472285
  ];
@@ -471520,8 +472363,8 @@ async function prepareBeatRenderRoot(opts) {
471520
472363
  durationSec,
471521
472364
  narrationPath
471522
472365
  });
471523
- await mkdir12(dirname16(join30(opts.projectDir, root2)), { recursive: true });
471524
- await writeFile18(join30(opts.projectDir, root2), html, "utf-8");
472366
+ await mkdir12(dirname16(join29(opts.projectDir, root2)), { recursive: true });
472367
+ await writeFile18(join29(opts.projectDir, root2), html, "utf-8");
471525
472368
  return {
471526
472369
  success: true,
471527
472370
  root: root2,
@@ -471585,7 +472428,7 @@ function reportedBeatAssetPaths(beat) {
471585
472428
  ]);
471586
472429
  }
471587
472430
  async function writeRenderReport(projectDir, result) {
471588
- const reportPath = join30(projectDir, "render-report.json");
472431
+ const reportPath = join29(projectDir, "render-report.json");
471589
472432
  try {
471590
472433
  await writeFile18(
471591
472434
  reportPath,
@@ -471782,6 +472625,7 @@ var init_build_asset_reference = __esm({
471782
472625
  EXTENSIONS = {
471783
472626
  narration: [".mp3", ".wav", ".m4a"],
471784
472627
  backdrop: [".png", ".jpg", ".jpeg", ".webp"],
472628
+ character: [".png", ".jpg", ".jpeg", ".webp"],
471785
472629
  video: [".mp4", ".mov", ".webm"],
471786
472630
  music: [".mp3", ".wav", ".m4a"]
471787
472631
  };
@@ -471819,6 +472663,16 @@ function backdropCacheDescriptor(opts) {
471819
472663
  ext: "png"
471820
472664
  });
471821
472665
  }
472666
+ function characterCacheDescriptor(opts) {
472667
+ return cacheAssetDescriptor("character", {
472668
+ name: opts.name,
472669
+ cue: opts.cue,
472670
+ provider: opts.provider,
472671
+ quality: opts.quality,
472672
+ size: opts.size,
472673
+ ext: "png"
472674
+ });
472675
+ }
471822
472676
  function imageRatioForSize(size) {
471823
472677
  switch (size) {
471824
472678
  case "1024x1024":
@@ -471838,6 +472692,8 @@ function videoCacheDescriptor(opts) {
471838
472692
  provider: opts.provider,
471839
472693
  duration: normalizeVideoDuration(opts.duration),
471840
472694
  ratio: "16:9",
472695
+ // Omit when empty so existing (character-less) clips keep their cache key.
472696
+ characters: opts.characters && opts.characters.length > 0 ? opts.characters : void 0,
471841
472697
  ext: "mp4"
471842
472698
  });
471843
472699
  }
@@ -471890,212 +472746,10 @@ var init_build_backdrop_prompt = __esm({
471890
472746
  }
471891
472747
  });
471892
472748
 
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
472749
  // ../cli/src/commands/_shared/build-plan.ts
472096
472750
  import { existsSync as existsSync33 } from "node:fs";
472097
472751
  import { readFile as readFile17 } from "node:fs/promises";
472098
- import { dirname as dirname17, join as join31, resolve as resolve29 } from "node:path";
472752
+ import { dirname as dirname17, join as join30, resolve as resolve29 } from "node:path";
472099
472753
  function backdropCostUsd(quality) {
472100
472754
  return quality === "hd" ? 0.2 : 0.05;
472101
472755
  }
@@ -472104,7 +472758,7 @@ async function createBuildPlan(opts) {
472104
472758
  loadPlanEnv(projectDir);
472105
472759
  const stage = opts.stage ?? "all";
472106
472760
  const config4 = await readProjectConfig(projectDir);
472107
- const storyboardPath = join31(projectDir, "STORYBOARD.md");
472761
+ const storyboardPath = join30(projectDir, "STORYBOARD.md");
472108
472762
  const warnings = [];
472109
472763
  const retryWith = [];
472110
472764
  const providerResolution = [];
@@ -472163,7 +472817,7 @@ async function createBuildPlan(opts) {
472163
472817
  const imageQuality = opts.imageQuality ?? config4.config.build.imageQuality ?? "hd";
472164
472818
  const imageSize2 = opts.imageSize ?? config4.config.build.imageSize ?? "1536x1024";
472165
472819
  const imageRatio = imageRatioForSize(imageSize2);
472166
- const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync33(join31(projectDir, `compositions/scene-${beat.id}.html`)));
472820
+ const needsComposer = includeCompose && mode !== "agent" && sourceBeats.some((beat) => !existsSync33(join30(projectDir, `compositions/scene-${beat.id}.html`)));
472167
472821
  const resolved = await resolvePlanProviders({
472168
472822
  projectDir,
472169
472823
  config: config4,
@@ -472267,7 +472921,11 @@ async function createBuildPlan(opts) {
472267
472921
  reference: videoReference,
472268
472922
  projectDir,
472269
472923
  force: opts.force,
472270
- cost: VIDEO_COST_USD,
472924
+ cost: resolved.video.resolved === "seedance" || resolved.video.resolved === "fal" ? estimateSeedanceVideoCostUsd({
472925
+ durationSec: normalizeVideoDuration(beat.duration),
472926
+ resolution: "720p",
472927
+ aspectRatio: "16:9"
472928
+ }) : VIDEO_COST_USD,
472271
472929
  active: includeAssets
472272
472930
  }) : null;
472273
472931
  const music = musicCue && !opts.skipMusic ? assetPlan({
@@ -472284,7 +472942,7 @@ async function createBuildPlan(opts) {
472284
472942
  active: includeAssets
472285
472943
  }) : null;
472286
472944
  const compositionPath = `compositions/scene-${beat.id}.html`;
472287
- const compositionExists = existsSync33(join31(projectDir, compositionPath));
472945
+ const compositionExists = existsSync33(join30(projectDir, compositionPath));
472288
472946
  for (const asset of [narration, backdrop, video, music]) {
472289
472947
  if (!asset) continue;
472290
472948
  if (asset.referenceError) {
@@ -472351,7 +473009,7 @@ async function createBuildPlan(opts) {
472351
473009
  }
472352
473010
  }
472353
473011
  const validationOk = !validationIssues.some((issue) => issue.severity === "error");
472354
- if (!existsSync33(join31(projectDir, config4.config.composition.entry))) {
473012
+ if (!existsSync33(join30(projectDir, config4.config.composition.entry))) {
472355
473013
  missing.add("root-composition");
472356
473014
  if (validationOk) retryWith.push(`vibe build ${projectDir} --stage sync --json`);
472357
473015
  }
@@ -472378,6 +473036,21 @@ async function createBuildPlan(opts) {
472378
473036
  retryWith.push(skipCommand);
472379
473037
  }
472380
473038
  }
473039
+ if (validationOk && includeAssets && !opts.skipVideo) {
473040
+ const referenced = /* @__PURE__ */ new Set();
473041
+ for (const beat of sourceBeats) {
473042
+ for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
473043
+ }
473044
+ if (referenced.size > 0) {
473045
+ const toGenerate = resolveCharacters(parsed.frontmatter).filter(
473046
+ (c2) => referenced.has(c2.name) && c2.prompt && (opts.force || !existsSync33(join30(projectDir, `assets/character-${c2.name}.png`)))
473047
+ );
473048
+ if (toGenerate.length > 0) {
473049
+ estimatedCostUsd += toGenerate.length * backdropCostUsd(opts.imageQuality ?? "hd");
473050
+ missing.add("assets");
473051
+ }
473052
+ }
473053
+ }
472381
473054
  return finalizeBuildPlan({
472382
473055
  projectDir,
472383
473056
  config: config4,
@@ -472483,8 +473156,8 @@ function assetPlan(opts) {
472483
473156
  reason: ready ? "referenced-asset" : "invalid-reference"
472484
473157
  };
472485
473158
  }
472486
- const canonicalExists = existsSync33(join31(opts.projectDir, opts.path));
472487
- const cacheHit = opts.cache ? existsSync33(join31(opts.projectDir, opts.cache.path)) : false;
473159
+ const canonicalExists = existsSync33(join30(opts.projectDir, opts.path));
473160
+ const cacheHit = opts.cache ? existsSync33(join30(opts.projectDir, opts.cache.path)) : false;
472488
473161
  const freshness = assetFreshnessFromMetadata({
472489
473162
  projectDir: opts.projectDir,
472490
473163
  kind: opts.kind,
@@ -472681,7 +473354,7 @@ async function firstConfiguredComposer(projectDir) {
472681
473354
  }
472682
473355
  function firstExisting2(projectDir, paths) {
472683
473356
  for (const path14 of paths) {
472684
- if (existsSync33(join31(projectDir, path14))) return path14;
473357
+ if (existsSync33(join30(projectDir, path14))) return path14;
472685
473358
  }
472686
473359
  return null;
472687
473360
  }
@@ -472722,12 +473395,12 @@ function unique3(items) {
472722
473395
  return [...new Set(items.filter((item) => item.length > 0))];
472723
473396
  }
472724
473397
  function loadPlanEnv(projectDir) {
472725
- (0, import_dotenv2.config)({ path: join31(projectDir, ".env"), quiet: true });
473398
+ (0, import_dotenv2.config)({ path: join30(projectDir, ".env"), quiet: true });
472726
473399
  (0, import_dotenv2.config)({ path: resolve29(process.cwd(), ".env"), quiet: true });
472727
473400
  let dir = process.cwd();
472728
473401
  while (dir !== dirname17(dir)) {
472729
- if (existsSync33(join31(dir, "pnpm-workspace.yaml"))) {
472730
- (0, import_dotenv2.config)({ path: join31(dir, ".env"), quiet: true });
473402
+ if (existsSync33(join30(dir, "pnpm-workspace.yaml"))) {
473403
+ (0, import_dotenv2.config)({ path: join30(dir, ".env"), quiet: true });
472731
473404
  return;
472732
473405
  }
472733
473406
  dir = dirname17(dir);
@@ -472742,6 +473415,7 @@ var init_build_plan = __esm({
472742
473415
  init_schema();
472743
473416
  init_build_asset_reference();
472744
473417
  init_build_cache();
473418
+ init_dist();
472745
473419
  init_build_asset_metadata();
472746
473420
  init_build_backdrop_prompt();
472747
473421
  init_composer_resolve();
@@ -473074,9 +473748,9 @@ __export(scene_build_exports, {
473074
473748
  executeSceneBuild: () => executeSceneBuild,
473075
473749
  resolveSceneBuildMode: () => resolveSceneBuildMode
473076
473750
  });
473077
- import { existsSync as existsSync35 } from "node:fs";
473751
+ import { existsSync as existsSync35, readFileSync as readFileSync12 } from "node:fs";
473078
473752
  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";
473753
+ import { dirname as dirname18, join as join31, resolve as resolve33 } from "node:path";
473080
473754
  async function executeSceneBuild(opts) {
473081
473755
  const startedAt = Date.now();
473082
473756
  const projectDir = resolve33(opts.projectDir);
@@ -473088,7 +473762,7 @@ async function executeSceneBuild(opts) {
473088
473762
  const warnings = [];
473089
473763
  const retryWith = [];
473090
473764
  let sceneRepair = skippedSceneRepairSummary();
473091
- const storyboardPath = join33(projectDir, "STORYBOARD.md");
473765
+ const storyboardPath = join31(projectDir, "STORYBOARD.md");
473092
473766
  const buildPlan = await createBuildPlan({
473093
473767
  projectDir,
473094
473768
  stage: selectedStage,
@@ -473241,8 +473915,32 @@ async function executeSceneBuild(opts) {
473241
473915
  const voice = opts.voice ?? parsed.frontmatter?.voice;
473242
473916
  let beatOutcomes = collectExistingBeatOutcomes(parsed.beats, projectDir);
473243
473917
  let pendingJobs = [];
473918
+ let characterCostUsd = 0;
473244
473919
  if (shouldRunStage(selectedStage, "assets")) {
473245
473920
  onProgress({ type: "phase-start", phase: "primitives" });
473921
+ const characterPaths = /* @__PURE__ */ new Map();
473922
+ if (!(opts.skipVideo ?? false)) {
473923
+ const referenced = /* @__PURE__ */ new Set();
473924
+ for (const beat of activeBeats) {
473925
+ for (const name of beatCharacterNames(beat.cues)) referenced.add(name);
473926
+ }
473927
+ if (referenced.size > 0) {
473928
+ const charResult = await buildCharacters(
473929
+ resolveCharacters(parsed.frontmatter).filter((c2) => referenced.has(c2.name)),
473930
+ {
473931
+ projectDir,
473932
+ imageProvider,
473933
+ imageSize: opts.imageSize ?? "1536x1024",
473934
+ imageQuality: opts.imageQuality ?? "hd",
473935
+ force: opts.force ?? false,
473936
+ onProgress
473937
+ }
473938
+ );
473939
+ for (const [name, path14] of charResult.paths) characterPaths.set(name, path14);
473940
+ characterCostUsd = charResult.costUsd;
473941
+ warnings.push(...charResult.failures);
473942
+ }
473943
+ }
473246
473944
  const primitiveResults = await mapWithConcurrency(
473247
473945
  activeBeats,
473248
473946
  BEAT_PRIMITIVES_CONCURRENCY,
@@ -473260,7 +473958,8 @@ async function executeSceneBuild(opts) {
473260
473958
  skipVideo: opts.skipVideo ?? false,
473261
473959
  skipMusic: opts.skipMusic ?? false,
473262
473960
  force: opts.force ?? false,
473263
- onProgress
473961
+ onProgress,
473962
+ characterPaths
473264
473963
  })
473265
473964
  );
473266
473965
  beatOutcomes = primitiveResults.map((result) => result.outcome);
@@ -473269,10 +473968,7 @@ async function executeSceneBuild(opts) {
473269
473968
  (beat) => beat.narrationStatus === "failed" || beat.backdropStatus === "failed" || beat.videoStatus === "failed" || beat.musicStatus === "failed"
473270
473969
  );
473271
473970
  stageReports.assets.status = assetFailed ? "failed" : pendingJobs.length > 0 ? "pending-jobs" : "done";
473272
- stageReports.assets.costUsd = estimateActualAssetCost(
473273
- beatOutcomes,
473274
- opts.imageQuality ?? "hd"
473275
- );
473971
+ stageReports.assets.costUsd = estimateActualAssetCost(beatOutcomes, opts.imageQuality ?? "hd") + characterCostUsd;
473276
473972
  stageReports.assets.retryWith = pendingJobs.map(
473277
473973
  (job) => `vibe status job ${job.id} --project ${projectDir} --json`
473278
473974
  );
@@ -473348,9 +474044,9 @@ async function executeSceneBuild(opts) {
473348
474044
  let composeData;
473349
474045
  if (shouldRunStage(selectedStage, "compose")) {
473350
474046
  if (mode === "agent") {
473351
- const compositionsDir = join33(projectDir, "compositions");
474047
+ const compositionsDir = join31(projectDir, "compositions");
473352
474048
  const missingBeats = activeBeats.filter(
473353
- (b2) => !existsSync35(join33(compositionsDir, `scene-${b2.id}.html`))
474049
+ (b2) => !existsSync35(join31(compositionsDir, `scene-${b2.id}.html`))
473354
474050
  );
473355
474051
  if (missingBeats.length > 0) {
473356
474052
  const plan = await getComposePrompts({ projectDir, beatId: opts.beatId });
@@ -473437,6 +474133,18 @@ async function executeSceneBuild(opts) {
473437
474133
  stageReports.compose.status = "skipped";
473438
474134
  }
473439
474135
  if (stageReports.compose.status === "done") {
474136
+ for (const beat of activeBeats) {
474137
+ const videoRel = `assets/video-${beat.id}.mp4`;
474138
+ const compositionAbs = join31(projectDir, "compositions", `scene-${beat.id}.html`);
474139
+ if (!existsSync35(join31(projectDir, videoRel)) || !existsSync35(compositionAbs)) continue;
474140
+ const html = readFileSync12(compositionAbs, "utf-8");
474141
+ if (!html.includes(videoRel)) {
474142
+ warnings.push(
474143
+ `Beat "${beat.id}" has a generated clip (${videoRel}) but its composition does not reference it, so the clip will not appear. Recompose the beat.`
474144
+ );
474145
+ retryWith.push(`vibe build ${projectDir} --beat ${beat.id} --stage compose --force --json`);
474146
+ }
474147
+ }
473440
474148
  const repair = await runBuildSceneRepair(projectDir, "compose", false);
473441
474149
  sceneRepair = mergeSceneRepairSummaries(sceneRepair, repair);
473442
474150
  applySceneRepairToStage(stageReports.compose, repair);
@@ -473486,7 +474194,7 @@ async function executeSceneBuild(opts) {
473486
474194
  });
473487
474195
  }
473488
474196
  if (shouldRunStage(selectedStage, "sync")) {
473489
- if (!existsSync35(join33(projectDir, "index.html"))) {
474197
+ if (!existsSync35(join31(projectDir, "index.html"))) {
473490
474198
  await scaffoldSceneProject({
473491
474199
  dir: projectDir,
473492
474200
  name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
@@ -473745,7 +474453,7 @@ async function dispatchNarration(beat, ctx) {
473745
474453
  return {
473746
474454
  status: "cached",
473747
474455
  path: existingRel,
473748
- durationSec: await safeAudioDuration(join33(ctx.projectDir, existingRel)),
474456
+ durationSec: await safeAudioDuration(join31(ctx.projectDir, existingRel)),
473749
474457
  provider: metadata?.provider,
473750
474458
  cachePath: metadata?.cachePath,
473751
474459
  cacheKey: metadata?.cacheKey,
@@ -473769,9 +474477,9 @@ async function dispatchNarration(beat, ctx) {
473769
474477
  voice,
473770
474478
  ext: resolution.audioExtension
473771
474479
  });
473772
- const cacheAbs = join33(ctx.projectDir, cache.path);
474480
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473773
474481
  const rel = `assets/narration-${beat.id}.${resolution.audioExtension}`;
473774
- const abs = join33(ctx.projectDir, rel);
474482
+ const abs = join31(ctx.projectDir, rel);
473775
474483
  if (existingRel && !ctx.force && isFreshCanonicalAsset({
473776
474484
  projectDir: ctx.projectDir,
473777
474485
  kind: "narration",
@@ -473785,7 +474493,7 @@ async function dispatchNarration(beat, ctx) {
473785
474493
  return {
473786
474494
  status: "cached",
473787
474495
  path: existingRel,
473788
- durationSec: await safeAudioDuration(join33(ctx.projectDir, existingRel)),
474496
+ durationSec: await safeAudioDuration(join31(ctx.projectDir, existingRel)),
473789
474497
  provider: resolution.provider,
473790
474498
  cachePath: cache.path,
473791
474499
  cacheKey: cache.key,
@@ -473857,6 +474565,120 @@ async function dispatchNarration(beat, ctx) {
473857
474565
  freshness: "fresh"
473858
474566
  };
473859
474567
  }
474568
+ function characterSheetPrompt(name, description) {
474569
+ return [
474570
+ `Character turnaround reference sheet of an original fictional character named ${name}: ${description}.`,
474571
+ "Show three full-body views side by side on one clean canvas \u2014 front view, side profile, and back view \u2014",
474572
+ "with identical outfit, hairstyle, hair color, and body proportions across all three views.",
474573
+ "Plain light-grey studio background, even neutral lighting, no props.",
474574
+ "Photorealistic digital character, professional concept-art character sheet layout."
474575
+ ].join(" ");
474576
+ }
474577
+ async function buildCharacters(characters, ctx) {
474578
+ const paths = /* @__PURE__ */ new Map();
474579
+ const failures = [];
474580
+ let costUsd = 0;
474581
+ const size = ctx.imageSize ?? "1536x1024";
474582
+ for (const character of characters) {
474583
+ if (character.imagePath) {
474584
+ if (existsSync35(join31(ctx.projectDir, character.imagePath))) {
474585
+ paths.set(character.name, character.imagePath);
474586
+ ctx.onProgress({ type: "character-cached", name: character.name, path: character.imagePath });
474587
+ } else {
474588
+ const error = `character "${character.name}" image not found: ${character.imagePath}`;
474589
+ failures.push(error);
474590
+ ctx.onProgress({ type: "character-failed", name: character.name, error });
474591
+ }
474592
+ continue;
474593
+ }
474594
+ if (!character.prompt) continue;
474595
+ const prompt3 = characterSheetPrompt(character.name, character.prompt);
474596
+ const rel = `assets/character-${character.name}.png`;
474597
+ const abs = join31(ctx.projectDir, rel);
474598
+ const metadataOptions = { quality: ctx.imageQuality, size };
474599
+ const cache = characterCacheDescriptor({
474600
+ name: character.name,
474601
+ cue: prompt3,
474602
+ provider: ctx.imageProvider,
474603
+ quality: ctx.imageQuality,
474604
+ size
474605
+ });
474606
+ if (existsSync35(abs) && !ctx.force) {
474607
+ const metadata = readAssetMetadata(ctx.projectDir, "character", character.name);
474608
+ if (!metadata || isFreshCanonicalAsset({
474609
+ projectDir: ctx.projectDir,
474610
+ kind: "character",
474611
+ beatId: character.name,
474612
+ cue: prompt3,
474613
+ provider: ctx.imageProvider,
474614
+ options: metadataOptions,
474615
+ cacheKey: cache.key
474616
+ })) {
474617
+ paths.set(character.name, rel);
474618
+ ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
474619
+ continue;
474620
+ }
474621
+ }
474622
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474623
+ if (existsSync35(cacheAbs) && !ctx.force) {
474624
+ await mkdir13(dirname18(abs), { recursive: true });
474625
+ await copyFile4(cacheAbs, abs);
474626
+ await writeAssetMetadata({
474627
+ projectDir: ctx.projectDir,
474628
+ kind: "character",
474629
+ beatId: character.name,
474630
+ cue: prompt3,
474631
+ provider: ctx.imageProvider,
474632
+ options: metadataOptions,
474633
+ cacheKey: cache.key,
474634
+ canonicalPath: rel,
474635
+ cachePath: cache.path
474636
+ });
474637
+ paths.set(character.name, rel);
474638
+ ctx.onProgress({ type: "character-cached", name: character.name, path: rel });
474639
+ continue;
474640
+ }
474641
+ const generated = await generateBackdropImage(
474642
+ prompt3,
474643
+ {
474644
+ projectDir: ctx.projectDir,
474645
+ imageProvider: ctx.imageProvider,
474646
+ imageSize: size,
474647
+ imageQuality: ctx.imageQuality
474648
+ },
474649
+ imageRatioForSize(size)
474650
+ );
474651
+ if (!generated.success) {
474652
+ failures.push(`character "${character.name}": ${generated.error}`);
474653
+ ctx.onProgress({ type: "character-failed", name: character.name, error: generated.error });
474654
+ continue;
474655
+ }
474656
+ await mkdir13(dirname18(abs), { recursive: true });
474657
+ await writeFile20(abs, generated.buffer);
474658
+ await mkdir13(dirname18(cacheAbs), { recursive: true });
474659
+ await writeFile20(cacheAbs, generated.buffer);
474660
+ await writeAssetMetadata({
474661
+ projectDir: ctx.projectDir,
474662
+ kind: "character",
474663
+ beatId: character.name,
474664
+ cue: prompt3,
474665
+ provider: ctx.imageProvider,
474666
+ options: metadataOptions,
474667
+ cacheKey: cache.key,
474668
+ canonicalPath: rel,
474669
+ cachePath: cache.path
474670
+ });
474671
+ costUsd += backdropCostUsd(ctx.imageQuality);
474672
+ paths.set(character.name, rel);
474673
+ ctx.onProgress({
474674
+ type: "character-generated",
474675
+ name: character.name,
474676
+ path: rel,
474677
+ provider: ctx.imageProvider
474678
+ });
474679
+ }
474680
+ return { paths, costUsd: Number(costUsd.toFixed(2)), failures };
474681
+ }
473860
474682
  async function dispatchBackdrop(beat, ctx) {
473861
474683
  const reference = assetReferenceForBeat(ctx.projectDir, "backdrop", beat);
473862
474684
  if (reference) return referencePrimitiveOutcome("backdrop", beat, ctx, reference);
@@ -473864,7 +474686,7 @@ async function dispatchBackdrop(beat, ctx) {
473864
474686
  if (!cue) return { status: "no-cue" };
473865
474687
  const prompt3 = augmentBackdropPrompt(cue);
473866
474688
  const rel = `assets/backdrop-${beat.id}.png`;
473867
- const abs = join33(ctx.projectDir, rel);
474689
+ const abs = join31(ctx.projectDir, rel);
473868
474690
  const size = ctx.imageSize ?? "1536x1024";
473869
474691
  const ratio = imageRatioForSize(size);
473870
474692
  const metadataOptions = { quality: ctx.imageQuality, size, ratio };
@@ -473900,7 +474722,7 @@ async function dispatchBackdrop(beat, ctx) {
473900
474722
  };
473901
474723
  }
473902
474724
  }
473903
- const cacheAbs = join33(ctx.projectDir, cache.path);
474725
+ const cacheAbs = join31(ctx.projectDir, cache.path);
473904
474726
  if (existsSync35(cacheAbs) && !ctx.force) {
473905
474727
  await mkdir13(dirname18(abs), { recursive: true });
473906
474728
  await copyFile4(cacheAbs, abs);
@@ -474026,13 +474848,16 @@ async function dispatchVideo(beat, ctx) {
474026
474848
  if (reference) return referencePrimitiveOutcome("video", beat, ctx, reference);
474027
474849
  const prompt3 = stringOrUndefined4(beat.cues?.video);
474028
474850
  if (!prompt3) return { status: "no-cue" };
474851
+ const characterRefsRel = beatCharacterNames(beat.cues).map((name) => ctx.characterPaths?.get(name)).filter((p2) => typeof p2 === "string");
474852
+ const characterRefsAbs = characterRefsRel.map((p2) => join31(ctx.projectDir, p2));
474029
474853
  const rel = `assets/video-${beat.id}.mp4`;
474030
- const abs = join33(ctx.projectDir, rel);
474854
+ const abs = join31(ctx.projectDir, rel);
474031
474855
  const cache = videoCacheDescriptor({
474032
474856
  beatId: beat.id,
474033
474857
  cue: prompt3,
474034
474858
  provider: ctx.videoProvider,
474035
- duration: normalizeVideoDuration(beat.duration)
474859
+ duration: normalizeVideoDuration(beat.duration),
474860
+ characters: characterRefsRel
474036
474861
  });
474037
474862
  const metadataPath = assetMetadataPath("video", beat.id);
474038
474863
  if (existsSync35(abs) && !ctx.force) {
@@ -474058,7 +474883,7 @@ async function dispatchVideo(beat, ctx) {
474058
474883
  };
474059
474884
  }
474060
474885
  }
474061
- const cacheAbs = join33(ctx.projectDir, cache.path);
474886
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474062
474887
  if (existsSync35(cacheAbs) && !ctx.force) {
474063
474888
  await mkdir13(dirname18(abs), { recursive: true });
474064
474889
  await copyFile4(cacheAbs, abs);
@@ -474092,6 +474917,7 @@ async function dispatchVideo(beat, ctx) {
474092
474917
  ratio: "16:9",
474093
474918
  output: abs,
474094
474919
  wait: false,
474920
+ refImages: characterRefsAbs.length > 0 ? characterRefsAbs : void 0,
474095
474921
  apiKey: await apiKeyForVideoProvider(ctx.videoProvider, ctx.projectDir)
474096
474922
  });
474097
474923
  if (!result.success || !result.taskId) {
@@ -474167,7 +474993,7 @@ async function dispatchMusic(beat, ctx) {
474167
474993
  const prompt3 = stringOrUndefined4(beat.cues?.music);
474168
474994
  if (!prompt3) return { status: "no-cue" };
474169
474995
  const rel = `assets/music-${beat.id}.mp3`;
474170
- const abs = join33(ctx.projectDir, rel);
474996
+ const abs = join31(ctx.projectDir, rel);
474171
474997
  const duration = normalizeMusicDuration(beat.duration);
474172
474998
  const cache = musicCacheDescriptor({
474173
474999
  beatId: beat.id,
@@ -474200,7 +475026,7 @@ async function dispatchMusic(beat, ctx) {
474200
475026
  };
474201
475027
  }
474202
475028
  }
474203
- const cacheAbs = join33(ctx.projectDir, cache.path);
475029
+ const cacheAbs = join31(ctx.projectDir, cache.path);
474204
475030
  if (existsSync35(cacheAbs) && !ctx.force) {
474205
475031
  await mkdir13(dirname18(abs), { recursive: true });
474206
475032
  await copyFile4(cacheAbs, abs);
@@ -474311,12 +475137,12 @@ async function dispatchMusic(beat, ctx) {
474311
475137
  };
474312
475138
  }
474313
475139
  function loadSceneBuildEnv(projectDir) {
474314
- (0, import_dotenv3.config)({ path: join33(projectDir, ".env"), quiet: true });
475140
+ (0, import_dotenv3.config)({ path: join31(projectDir, ".env"), quiet: true });
474315
475141
  (0, import_dotenv3.config)({ path: resolve33(process.cwd(), ".env"), quiet: true });
474316
475142
  let dir = process.cwd();
474317
475143
  while (dir !== dirname18(dir)) {
474318
- if (existsSync35(join33(dir, "pnpm-workspace.yaml"))) {
474319
- (0, import_dotenv3.config)({ path: join33(dir, ".env"), quiet: true });
475144
+ if (existsSync35(join31(dir, "pnpm-workspace.yaml"))) {
475145
+ (0, import_dotenv3.config)({ path: join31(dir, ".env"), quiet: true });
474320
475146
  return;
474321
475147
  }
474322
475148
  dir = dirname18(dir);
@@ -474460,7 +475286,13 @@ function estimateActualAssetCost(outcomes, imageQuality) {
474460
475286
  for (const outcome of outcomes) {
474461
475287
  if (outcome.narrationStatus === "generated") cost += 0.05;
474462
475288
  if (outcome.backdropStatus === "generated") cost += backdropCostUsd(imageQuality);
474463
- if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") cost += 5;
475289
+ if (outcome.videoStatus === "generated" || outcome.videoStatus === "pending") {
475290
+ cost += outcome.videoProvider === "seedance" || outcome.videoProvider === "fal" ? estimateSeedanceVideoCostUsd({
475291
+ durationSec: normalizeVideoDuration(outcome.sceneDurationSec),
475292
+ resolution: "720p",
475293
+ aspectRatio: "16:9"
475294
+ }) : 5;
475295
+ }
474464
475296
  if (outcome.musicStatus === "generated" || outcome.musicStatus === "pending") cost += 0.5;
474465
475297
  }
474466
475298
  return Number(cost.toFixed(2));
@@ -474525,7 +475357,7 @@ function mergeBeatOutcomes(base, updates) {
474525
475357
  }
474526
475358
  function firstExisting3(projectDir, relPaths) {
474527
475359
  for (const rel of relPaths) {
474528
- if (existsSync35(join33(projectDir, rel))) return rel;
475360
+ if (existsSync35(join31(projectDir, rel))) return rel;
474529
475361
  }
474530
475362
  return null;
474531
475363
  }
@@ -474556,7 +475388,7 @@ async function apiKeyForVideoProvider(provider, projectDir) {
474556
475388
  return getApiKeyFromConfig(providerKey, { cwd: projectDir });
474557
475389
  }
474558
475390
  async function finalizeBuildResult(projectDir, startedAt, result) {
474559
- const reportPath = join33(projectDir, "build-report.json");
475391
+ const reportPath = join31(projectDir, "build-report.json");
474560
475392
  if (result.stageReports) {
474561
475393
  for (const report of Object.values(result.stageReports)) {
474562
475394
  if (report.status === "pending") report.status = "skipped";
@@ -474715,7 +475547,7 @@ function normalizeReportDuration(value) {
474715
475547
  }
474716
475548
  function buildReportComposition(projectDir, result, beatId) {
474717
475549
  const path14 = `compositions/scene-${beatId}.html`;
474718
- const exists = existsSync35(join33(projectDir, path14));
475550
+ const exists = existsSync35(join31(projectDir, path14));
474719
475551
  const written = composeWrittenEntry(result, beatId);
474720
475552
  if (written) {
474721
475553
  return {
@@ -474772,7 +475604,7 @@ function summarizeBuildBeats(projectDir, beats) {
474772
475604
  let compositionsReady = 0;
474773
475605
  for (const beat of beats) {
474774
475606
  if (beatAssetsReady(beat)) assetsReady += 1;
474775
- const compositionPath = join33(projectDir, "compositions", `scene-${beat.beatId}.html`);
475607
+ const compositionPath = join31(projectDir, "compositions", `scene-${beat.beatId}.html`);
474776
475608
  if (existsSync35(compositionPath)) {
474777
475609
  compositionsReady += 1;
474778
475610
  } else {
@@ -475428,7 +476260,7 @@ var init_silence_cut = __esm({
475428
476260
  // ../cli/src/commands/_shared/edit/jump-cut.ts
475429
476261
  import { existsSync as existsSync38 } from "node:fs";
475430
476262
  import { readFile as readFile21, writeFile as writeFile22, mkdir as mkdir14 } from "node:fs/promises";
475431
- import { join as join34 } from "node:path";
476263
+ import { join as join33 } from "node:path";
475432
476264
  async function transcribeWithWords(audioPath, apiKey, language) {
475433
476265
  const audioBuffer = await readFile21(audioPath);
475434
476266
  const audioBlob = new Blob([audioBuffer]);
@@ -475508,7 +476340,7 @@ async function executeJumpCut(options) {
475508
476340
  try {
475509
476341
  const tmpDir = `/tmp/vibe_jumpcut_${Date.now()}`;
475510
476342
  await mkdir14(tmpDir, { recursive: true });
475511
- const audioPath = join34(tmpDir, "audio.wav");
476343
+ const audioPath = join33(tmpDir, "audio.wav");
475512
476344
  try {
475513
476345
  await execSafe(
475514
476346
  "ffmpeg",
@@ -475562,7 +476394,7 @@ async function executeJumpCut(options) {
475562
476394
  const segmentPaths = [];
475563
476395
  for (let i2 = 0; i2 < segments.length; i2++) {
475564
476396
  const seg = segments[i2];
475565
- const segPath = join34(tmpDir, `seg-${i2.toString().padStart(4, "0")}.ts`);
476397
+ const segPath = join33(tmpDir, `seg-${i2.toString().padStart(4, "0")}.ts`);
475566
476398
  const duration = seg.end - seg.start;
475567
476399
  await execSafe(
475568
476400
  "ffmpeg",
@@ -475585,7 +476417,7 @@ async function executeJumpCut(options) {
475585
476417
  segmentPaths.push(segPath);
475586
476418
  }
475587
476419
  const concatList = segmentPaths.map((p2) => `file '${p2}'`).join("\n");
475588
- const listPath = join34(tmpDir, "concat.txt");
476420
+ const listPath = join33(tmpDir, "concat.txt");
475589
476421
  await writeFile22(listPath, concatList);
475590
476422
  await execSafe(
475591
476423
  "ffmpeg",
@@ -475693,7 +476525,7 @@ __export(remotion_exports, {
475693
476525
  });
475694
476526
  import { writeFile as writeFile23, mkdir as mkdir15, rm as rm3, copyFile as copyFile5 } from "node:fs/promises";
475695
476527
  import { existsSync as existsSync39 } from "node:fs";
475696
- import { join as join35 } from "node:path";
476528
+ import { join as join34 } from "node:path";
475697
476529
  import { tmpdir as tmpdir3 } from "node:os";
475698
476530
  import { createHash as createHash7 } from "node:crypto";
475699
476531
  async function ensureRemotionInstalled() {
@@ -475712,7 +476544,7 @@ async function ensureRemotionInstalled() {
475712
476544
  }
475713
476545
  }
475714
476546
  async function scaffoldRemotionProject(componentCode, componentName, opts) {
475715
- const dir = join35(tmpdir3(), `vibe_motion_${Date.now()}`);
476547
+ const dir = join34(tmpdir3(), `vibe_motion_${Date.now()}`);
475716
476548
  await mkdir15(dir, { recursive: true });
475717
476549
  const deps = {
475718
476550
  remotion: REMOTION_VERSION,
@@ -475731,7 +476563,7 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
475731
476563
  dependencies: deps
475732
476564
  };
475733
476565
  const packageJsonStr = JSON.stringify(packageJson, null, 2);
475734
- await writeFile23(join35(dir, "package.json"), packageJsonStr);
476566
+ await writeFile23(join34(dir, "package.json"), packageJsonStr);
475735
476567
  const tsconfig = {
475736
476568
  compilerOptions: {
475737
476569
  target: "ES2020",
@@ -475743,8 +476575,8 @@ async function scaffoldRemotionProject(componentCode, componentName, opts) {
475743
476575
  skipLibCheck: true
475744
476576
  }
475745
476577
  };
475746
- await writeFile23(join35(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
475747
- await writeFile23(join35(dir, "Component.tsx"), componentCode);
476578
+ await writeFile23(join34(dir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2));
476579
+ await writeFile23(join34(dir, "Component.tsx"), componentCode);
475748
476580
  const rootCode = `import { registerRoot, Composition } from "remotion";
475749
476581
  import { ${componentName} } from "./Component";
475750
476582
 
@@ -475763,14 +476595,14 @@ const Root = () => {
475763
476595
 
475764
476596
  registerRoot(Root);
475765
476597
  `;
475766
- await writeFile23(join35(dir, "Root.tsx"), rootCode);
476598
+ await writeFile23(join34(dir, "Root.tsx"), rootCode);
475767
476599
  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");
476600
+ const cacheMarker = join34(REMOTION_CACHE_DIR, `.deps-${depsHash}`);
476601
+ const cachedModules = join34(REMOTION_CACHE_DIR, "node_modules");
475770
476602
  if (existsSync39(cacheMarker) && existsSync39(cachedModules)) {
475771
476603
  const { symlink } = await import("node:fs/promises");
475772
476604
  try {
475773
- await symlink(cachedModules, join35(dir, "node_modules"), "dir");
476605
+ await symlink(cachedModules, join34(dir, "node_modules"), "dir");
475774
476606
  } catch {
475775
476607
  await this_npmInstall(dir);
475776
476608
  }
@@ -475778,12 +476610,12 @@ registerRoot(Root);
475778
476610
  await this_npmInstall(dir);
475779
476611
  try {
475780
476612
  await mkdir15(REMOTION_CACHE_DIR, { recursive: true });
475781
- await writeFile23(join35(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
475782
- if (existsSync39(join35(dir, "node_modules"))) {
476613
+ await writeFile23(join34(REMOTION_CACHE_DIR, "package.json"), packageJsonStr);
476614
+ if (existsSync39(join34(dir, "node_modules"))) {
475783
476615
  const { rename: rename7, symlink } = await import("node:fs/promises");
475784
- await rename7(join35(dir, "node_modules"), cachedModules).catch(() => {
476616
+ await rename7(join34(dir, "node_modules"), cachedModules).catch(() => {
475785
476617
  });
475786
- await symlink(cachedModules, join35(dir, "node_modules"), "dir").catch(() => {
476618
+ await symlink(cachedModules, join34(dir, "node_modules"), "dir").catch(() => {
475787
476619
  });
475788
476620
  await writeFile23(cacheMarker, depsHash);
475789
476621
  }
@@ -475820,7 +476652,7 @@ async function renderMotion(options) {
475820
476652
  }
475821
476653
  );
475822
476654
  try {
475823
- const entryPoint = join35(dir, "Root.tsx");
476655
+ const entryPoint = join34(dir, "Root.tsx");
475824
476656
  const { execFile: execFile3 } = await import("node:child_process");
475825
476657
  const { promisify: promisify3 } = await import("node:util");
475826
476658
  const execFileAsync3 = promisify3(execFile3);
@@ -475953,10 +476785,10 @@ async function renderWithEmbeddedImage(options) {
475953
476785
  }
475954
476786
  );
475955
476787
  try {
475956
- const publicDir = join35(dir, "public");
476788
+ const publicDir = join34(dir, "public");
475957
476789
  await mkdir15(publicDir, { recursive: true });
475958
- await copyFile5(options.imagePath, join35(publicDir, options.imageFileName));
475959
- const entryPoint = join35(dir, "Root.tsx");
476790
+ await copyFile5(options.imagePath, join34(publicDir, options.imageFileName));
476791
+ const entryPoint = join34(dir, "Root.tsx");
475960
476792
  const mp4Out = options.outputPath.replace(/\.\w+$/, ".mp4");
475961
476793
  const { execFile: execFileImg } = await import("node:child_process");
475962
476794
  const { promisify: promisifyImg } = await import("node:util");
@@ -476017,10 +476849,10 @@ async function renderWithEmbeddedVideo(options) {
476017
476849
  }
476018
476850
  );
476019
476851
  try {
476020
- const publicDir = join35(dir, "public");
476852
+ const publicDir = join34(dir, "public");
476021
476853
  await mkdir15(publicDir, { recursive: true });
476022
- await copyFile5(options.videoPath, join35(publicDir, options.videoFileName));
476023
- const entryPoint = join35(dir, "Root.tsx");
476854
+ await copyFile5(options.videoPath, join34(publicDir, options.videoFileName));
476855
+ const entryPoint = join34(dir, "Root.tsx");
476024
476856
  const mp4VideoOnly = options.outputPath.replace(/\.\w+$/, "_video_only.mp4");
476025
476857
  const { execFile: execFileVid } = await import("node:child_process");
476026
476858
  const { promisify: promisifyVid } = await import("node:util");
@@ -476436,14 +477268,14 @@ var init_remotion = __esm({
476436
477268
  "use strict";
476437
477269
  init_exec_safe();
476438
477270
  REMOTION_VERSION = "4.0.447";
476439
- REMOTION_CACHE_DIR = join35(tmpdir3(), "vibe_remotion_cache");
477271
+ REMOTION_CACHE_DIR = join34(tmpdir3(), "vibe_remotion_cache");
476440
477272
  }
476441
477273
  });
476442
477274
 
476443
477275
  // ../cli/src/commands/_shared/edit/caption.ts
476444
477276
  import { existsSync as existsSync40 } from "node:fs";
476445
477277
  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";
477278
+ import { dirname as dirname20, basename as basename9, extname as extname7, join as join35 } from "node:path";
476447
477279
  function getCaptionForceStyle(style, fontSize, fontColor, position) {
476448
477280
  const alignment = position === "top" ? 8 : position === "center" ? 5 : 2;
476449
477281
  const marginV = position === "center" ? 0 : 30;
@@ -476490,8 +477322,8 @@ async function executeCaption(options) {
476490
477322
  try {
476491
477323
  const tmpDir = `/tmp/vibe_caption_${Date.now()}`;
476492
477324
  await mkdir16(tmpDir, { recursive: true });
476493
- const audioPath = join36(tmpDir, "audio.wav");
476494
- const srtPath = join36(tmpDir, "captions.srt");
477325
+ const audioPath = join35(tmpDir, "audio.wav");
477326
+ const srtPath = join35(tmpDir, "captions.srt");
476495
477327
  try {
476496
477328
  await execSafe(
476497
477329
  "ffmpeg",
@@ -476560,7 +477392,7 @@ async function executeCaption(options) {
476560
477392
  const remotionErr = await ensureRemotionInstalled2();
476561
477393
  if (remotionErr) {
476562
477394
  const outputDir2 = dirname20(outputPath);
476563
- const outputSrtPath2 = join36(
477395
+ const outputSrtPath2 = join35(
476564
477396
  outputDir2,
476565
477397
  basename9(outputPath, extname7(outputPath)) + ".srt"
476566
477398
  );
@@ -476599,7 +477431,7 @@ SRT saved to: ${outputSrtPath2}` };
476599
477431
  });
476600
477432
  if (!renderResult.success) {
476601
477433
  const outputDir2 = dirname20(outputPath);
476602
- const outputSrtPath2 = join36(
477434
+ const outputSrtPath2 = join35(
476603
477435
  outputDir2,
476604
477436
  basename9(outputPath, extname7(outputPath)) + ".srt"
476605
477437
  );
@@ -476612,7 +477444,7 @@ SRT saved to: ${outputSrtPath2}`
476612
477444
  }
476613
477445
  }
476614
477446
  const outputDir = dirname20(outputPath);
476615
- const outputSrtPath = join36(
477447
+ const outputSrtPath = join35(
476616
477448
  outputDir,
476617
477449
  basename9(outputPath, extname7(outputPath)) + ".srt"
476618
477450
  );
@@ -484580,10 +485412,18 @@ Examples:
484580
485412
  options.ratio = "16:9";
484581
485413
  }
484582
485414
  if (options.dryRun) {
485415
+ const costUsd = provider === "seedance" || provider === "fal" ? estimateSeedanceVideoCostUsd({
485416
+ durationSec: Number(options.duration) || 5,
485417
+ resolution: options.resolution,
485418
+ aspectRatio: options.ratio,
485419
+ fast: options.seedanceModel === "fast",
485420
+ hasVideoReference: Boolean(options.refVideos)
485421
+ }) : void 0;
484583
485422
  outputSuccess({
484584
485423
  command: "generate video",
484585
485424
  startedAt,
484586
485425
  dryRun: true,
485426
+ costUsd,
484587
485427
  data: {
484588
485428
  params: {
484589
485429
  prompt: prompt3,
@@ -487033,6 +487873,9 @@ function clean(text) {
487033
487873
  function humanise(id) {
487034
487874
  return id.split(/[-_]+/).filter(Boolean).map((w2) => w2.charAt(0).toUpperCase() + w2.slice(1)).join(" ");
487035
487875
  }
487876
+ function sceneCompositionId(beatId) {
487877
+ return `scene-${beatId}`;
487878
+ }
487036
487879
  function slugifySceneName(name) {
487037
487880
  const normalised = name.normalize("NFKD").replace(/[̀-ͯ]/g, "");
487038
487881
  const slug = normalised.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
@@ -487070,10 +487913,16 @@ function fitKineticFontSize(words) {
487070
487913
  function kenBurnsTween(scope, dur, endScale = 1.08) {
487071
487914
  return `tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: ${endScale}, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);`;
487072
487915
  }
487916
+ function backdropMotionTweens(scope, dur, hasImage, endScale = 1.08) {
487917
+ const sweep = hasImage ? "" : `
487918
+ tl.fromTo('${scope} .ambient-sweep', { xPercent: -75, opacity: 0.18 }, { xPercent: 75, opacity: 0.62, duration: ${dur.toFixed(2)}, ease: 'sine.inOut' }, 0);`;
487919
+ return `${kenBurnsTween(scope, dur, endScale)}${sweep}`;
487920
+ }
487073
487921
  function buildPreset(input3) {
487074
487922
  const id = input3.id;
487075
487923
  const scope = `[data-composition-id="${id}"]`;
487076
- const headline = clean(input3.headline) || humanise(id);
487924
+ const displayId = input3.beatId ?? id;
487925
+ const headline = clean(input3.headline) || humanise(displayId);
487077
487926
  const subhead = clean(input3.subhead);
487078
487927
  const kicker = clean(input3.kicker);
487079
487928
  const dur = input3.duration;
@@ -487096,8 +487945,17 @@ function buildPreset(input3) {
487096
487945
  position: absolute;
487097
487946
  inset: 0;
487098
487947
  background: radial-gradient(ellipse at center, #1a1a2e 0%, #000 70%);
487948
+ overflow: hidden;
487949
+ }
487950
+ ${scope} .ambient-sweep {
487951
+ position: absolute;
487952
+ inset: -22%;
487953
+ display: block;
487954
+ background: linear-gradient(115deg, rgba(255,255,255,0) 20%, rgba(0,201,255,0.34) 48%, rgba(142,45,226,0.26) 58%, rgba(255,255,255,0) 78%);
487955
+ filter: blur(12px);
487956
+ transform: rotate(8deg);
487099
487957
  }`;
487100
- const backdropMarkup = `<div class="backdrop"></div>`;
487958
+ const backdropMarkup = hasImage ? `<div class="backdrop"></div>` : `<div class="backdrop"><div class="ambient-sweep"></div></div>`;
487101
487959
  switch (input3.preset) {
487102
487960
  case "simple": {
487103
487961
  const transcript = input3.transcript;
@@ -487129,7 +487987,7 @@ function buildPreset(input3) {
487129
487987
  body: `${backdropMarkup}
487130
487988
  <div class="caption" id="caption">${captionInner}</div>`,
487131
487989
  timeline: `${crossfadeTweens(scope, dur)}
487132
- ${kenBurnsTween(scope, dur)}
487990
+ ${backdropMotionTweens(scope, dur, hasImage)}
487133
487991
  ${captionTween}`
487134
487992
  };
487135
487993
  }
@@ -487168,12 +488026,12 @@ function buildPreset(input3) {
487168
488026
  // scene's own fade-in handles the visual transition. Backdrop
487169
488027
  // Ken-Burns fills what used to be 80% dead static frame.
487170
488028
  timeline: `${crossfadeTweens(scope, dur)}
487171
- ${kenBurnsTween(scope, dur)}
488029
+ ${backdropMotionTweens(scope, dur, hasImage)}
487172
488030
  tl.from('${scope} #headline', { opacity: 0, scale: 0.92, duration: 0.55, ease: 'power3.out' }, 0.05);`
487173
488031
  };
487174
488032
  }
487175
488033
  case "explainer": {
487176
- const k2 = kicker || humanise(id).toUpperCase();
488034
+ const k2 = kicker || humanise(displayId).toUpperCase();
487177
488035
  const sub = subhead || "";
487178
488036
  const transcript = input3.transcript;
487179
488037
  const useWordSync = !!(transcript && transcript.length > 0 && sub);
@@ -487215,7 +488073,7 @@ function buildPreset(input3) {
487215
488073
  <div class="subtitle" id="subtitle">${subtitleInner}</div>` : ""}
487216
488074
  </div>`,
487217
488075
  timeline: `${crossfadeTweens(scope, dur)}
487218
- ${kenBurnsTween(scope, dur)}
488076
+ ${backdropMotionTweens(scope, dur, hasImage)}
487219
488077
  tl.from('${scope} #kicker', { opacity: 0, y: 16, duration: 0.4, ease: 'power2.out' }, 0.1);
487220
488078
  tl.from('${scope} #title', { opacity: 0, y: 60, duration: 0.7, ease: 'power3.out' }, 0.25);
487221
488079
  ${subtitleTween}`
@@ -487256,12 +488114,12 @@ function buildPreset(input3) {
487256
488114
  body: `${backdropMarkup}
487257
488115
  <div class="kinetic">${wordSpans}</div>`,
487258
488116
  timeline: `${crossfadeTweens(scope, dur)}
487259
- ${kenBurnsTween(scope, dur)}
488117
+ ${backdropMotionTweens(scope, dur, hasImage)}
487260
488118
  ${tweens}`
487261
488119
  };
487262
488120
  }
487263
488121
  case "product-shot": {
487264
- const label = kicker || humanise(id);
488122
+ const label = kicker || humanise(displayId);
487265
488123
  const headlineFontPx = fitFontSize(headline, 72, 40, 50);
487266
488124
  return {
487267
488125
  css: `${scope} {
@@ -487295,7 +488153,7 @@ function buildPreset(input3) {
487295
488153
  <div class="product-headline" id="headline">${esc(headline)}</div>${subhead ? `
487296
488154
  <div class="product-sub" id="subhead">${esc(subhead)}</div>` : ""}`,
487297
488155
  timeline: `${crossfadeTweens(scope, dur)}
487298
- tl.fromTo('${scope} .backdrop', { scale: 1.0 }, { scale: 1.12, duration: ${dur.toFixed(2)}, ease: 'none' }, 0);
488156
+ ${backdropMotionTweens(scope, dur, hasImage, 1.12)}
487299
488157
  tl.from('${scope} #label', { opacity: 0, x: -30, duration: 0.5, ease: 'power3.out' }, 0.2);
487300
488158
  tl.from('${scope} #headline', { opacity: 0, y: 40, duration: 0.6, ease: 'power3.out' }, 0.4);${subhead ? `
487301
488159
  tl.from('${scope} #subhead', { opacity: 0, y: 20, duration: 0.5, ease: 'power3.out' }, 0.65);` : ""}`
@@ -487360,9 +488218,10 @@ function emitSceneHtml(input3) {
487360
488218
  if (input3.width <= 0 || input3.height <= 0) {
487361
488219
  throw new Error(`Invalid canvas dims: ${input3.width}x${input3.height}`);
487362
488220
  }
487363
- const id = input3.id;
488221
+ const beatId = input3.id;
488222
+ const id = sceneCompositionId(beatId);
487364
488223
  const dur = Number(input3.duration.toFixed(3));
487365
- const parts = buildPreset({ ...input3, id, duration: dur });
488224
+ const parts = buildPreset({ ...input3, id, beatId, duration: dur });
487366
488225
  const audioBlock = input3.audioPath ? `
487367
488226
  <audio
487368
488227
  id="narration"
@@ -487378,7 +488237,7 @@ function emitSceneHtml(input3) {
487378
488237
  ${lottieLayer.markup}` : "";
487379
488238
  const lottieScript = lottieLayer ? `
487380
488239
  ${lottieLayer.script}` : "";
487381
- return `<template id="scene-${id}-template">
488240
+ return `<template id="${id}-template">
487382
488241
  <div data-composition-id="${id}" data-start="0" data-duration="${dur}" data-width="${input3.width}" data-height="${input3.height}">
487383
488242
  <style>
487384
488243
  ${parts.css}
@@ -487414,8 +488273,9 @@ function buildClipReference(opts) {
487414
488273
  const duration = Number(opts.duration.toFixed(3));
487415
488274
  const track = opts.trackIndex ?? 1;
487416
488275
  const src = opts.src ?? `compositions/scene-${opts.id}.html`;
488276
+ const compositionId = sceneCompositionId(opts.id);
487417
488277
  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>`;
488278
+ return `<div class="clip" data-composition-id="${esc(compositionId)}" data-composition-src="${esc(src)}" data-start="${start}" data-duration="${duration}" data-track-index="${track}" style="z-index: ${zIndex};"></div>`;
487419
488279
  }
487420
488280
  function insertClipIntoRoot(rootHtml, clip) {
487421
488281
  const clipDiv = buildClipReference(clip);
@@ -487459,9 +488319,9 @@ init_agent_host_detect();
487459
488319
  // ../cli/src/commands/_shared/install-skill.ts
487460
488320
  init_bundle_content();
487461
488321
  init_bundle();
487462
- import { mkdir as mkdir8, writeFile as writeFile11 } from "node:fs/promises";
488322
+ import { mkdir as mkdir8, writeFile as writeFile12 } from "node:fs/promises";
487463
488323
  import { existsSync as existsSync21 } from "node:fs";
487464
- import { dirname as dirname12, join as join21, relative as relative5, resolve as resolve15 } from "node:path";
488324
+ import { dirname as dirname12, join as join21, relative as relative5, resolve as resolve16 } from "node:path";
487465
488325
  function universalFiles() {
487466
488326
  return [
487467
488327
  { relPath: "SKILL.md", content: SKILL_MD },
@@ -487511,7 +488371,7 @@ function selectHostFiles(hosts) {
487511
488371
  return out;
487512
488372
  }
487513
488373
  async function installHyperframesSkill(opts) {
487514
- const projectDir = resolve15(opts.projectDir);
488374
+ const projectDir = resolve16(opts.projectDir);
487515
488375
  const force = opts.force ?? false;
487516
488376
  const dryRun = opts.dryRun ?? false;
487517
488377
  const files = [...universalFiles(), ...selectHostFiles(opts.hosts)];
@@ -487536,7 +488396,7 @@ async function installHyperframesSkill(opts) {
487536
488396
  continue;
487537
488397
  }
487538
488398
  await mkdir8(dirname12(absPath), { recursive: true });
487539
- await writeFile11(absPath, file.content, "utf-8");
488399
+ await writeFile12(absPath, file.content, "utf-8");
487540
488400
  actions.push({ path: file.relPath, status: "wrote", bytes });
487541
488401
  }
487542
488402
  return {
@@ -487561,10 +488421,10 @@ init_scene_lint();
487561
488421
  init_storyboard_parse();
487562
488422
  init_root_sync();
487563
488423
  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";
488424
+ import { mkdir as mkdir10, readFile as readFile11, writeFile as writeFile15 } from "node:fs/promises";
488425
+ import { join as join26, resolve as resolve20 } from "node:path";
487566
488426
  async function executeSceneSubmit(opts) {
487567
- const projectDir = resolve19(opts.projectDir);
488427
+ const projectDir = resolve20(opts.projectDir);
487568
488428
  const beatId = opts.beatId.trim();
487569
488429
  const scenePath = `compositions/scene-${beatId}.html`;
487570
488430
  const base = {
@@ -487656,7 +488516,7 @@ async function executeSceneSubmit(opts) {
487656
488516
  }
487657
488517
  const absPath = join26(projectDir, scenePath);
487658
488518
  await mkdir10(join26(projectDir, "compositions"), { recursive: true });
487659
- await writeFile14(absPath, html.endsWith("\n") ? html : `${html}
488519
+ await writeFile15(absPath, html.endsWith("\n") ? html : `${html}
487660
488520
  `, "utf-8");
487661
488521
  return {
487662
488522
  ...base,
@@ -487671,6 +488531,8 @@ async function executeSceneSubmit(opts) {
487671
488531
 
487672
488532
  // ../cli/src/commands/scene.ts
487673
488533
  init_scene_repair();
488534
+ init_root_sync();
488535
+ init_storyboard_edit();
487674
488536
  function validateDuration(value) {
487675
488537
  const n2 = parseFloat(value);
487676
488538
  if (!Number.isFinite(n2) || n2 <= 0 || n2 > 3600) {
@@ -487973,7 +488835,10 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
487973
488835
  ).option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option(
487974
488836
  "--visuals <prompt>",
487975
488837
  "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(
488838
+ ).option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option(
488839
+ "--no-storyboard",
488840
+ "Do not sync STORYBOARD.md; insert this scene directly into the root composition only"
488841
+ ).option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option(
487977
488842
  "--tts <provider>",
487978
488843
  "TTS provider: auto, elevenlabs, openai, kokoro (default auto \u2014 ElevenLabs key > OpenAI key > Kokoro local)",
487979
488844
  "auto"
@@ -488042,6 +488907,7 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
488042
488907
  kicker: options.kicker,
488043
488908
  project: options.project,
488044
488909
  insertInto: options.insertInto,
488910
+ syncStoryboard: options.storyboard !== false,
488045
488911
  imageProvider: options.imageProvider,
488046
488912
  tts,
488047
488913
  audio: options.audio,
@@ -488069,6 +488935,7 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
488069
488935
  kicker: options.kicker,
488070
488936
  projectDir: options.project,
488071
488937
  insertInto: options.insertInto,
488938
+ syncStoryboard: options.storyboard !== false,
488072
488939
  imageProvider: options.imageProvider,
488073
488940
  tts,
488074
488941
  voice: options.voice,
@@ -488108,6 +488975,13 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
488108
488975
  if (result.audioPath) console.log(source_default.green(" +"), result.audioPath);
488109
488976
  if (result.imagePath) console.log(source_default.green(" +"), result.imagePath);
488110
488977
  if (result.transcriptPath) console.log(source_default.green(" +"), result.transcriptPath);
488978
+ if (result.storyboardPath && result.storyboardSynced) {
488979
+ console.log(
488980
+ result.storyboardAction === "updated" ? source_default.yellow(" ~") : source_default.green(" +"),
488981
+ result.storyboardPath,
488982
+ source_default.dim(`(${result.storyboardAction})`)
488983
+ );
488984
+ }
488111
488985
  console.log(source_default.yellow(" ~"), result.rootPath, source_default.dim("(updated)"));
488112
488986
  console.log();
488113
488987
  console.log(source_default.bold.cyan("Composition"));
@@ -488154,6 +489028,21 @@ async function loadVibeProjectConfig(projectDir) {
488154
489028
  }
488155
489029
  };
488156
489030
  }
489031
+ function escapeRegExp3(value) {
489032
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
489033
+ }
489034
+ function readClipStart(rootHtml, beatId) {
489035
+ const compositionId = sceneCompositionId(beatId);
489036
+ const match2 = rootHtml.match(
489037
+ new RegExp(
489038
+ `<div\\s+class="clip"[^>]*\\bdata-composition-id="${escapeRegExp3(compositionId)}"[^>]*\\bdata-start="([\\d.]+)"`,
489039
+ "i"
489040
+ )
489041
+ );
489042
+ if (!match2) return void 0;
489043
+ const start = Number.parseFloat(match2[1]);
489044
+ return Number.isFinite(start) ? start : void 0;
489045
+ }
488157
489046
  async function resolveNarrationText(value) {
488158
489047
  if (!value) return void 0;
488159
489048
  const looksLikePath = /\.[a-z]{2,4}$/i.test(value) || value.includes("/") || value.includes("\\");
@@ -488180,6 +489069,7 @@ async function executeSceneAdd(opts) {
488180
489069
  const id = slugifySceneName(opts.name);
488181
489070
  const sceneRel = `compositions/scene-${id}.html`;
488182
489071
  const scenePath = resolve24(projectDir, sceneRel);
489072
+ const storyboardAbsPath = resolve24(projectDir, "STORYBOARD.md");
488183
489073
  const errResult = (error) => ({
488184
489074
  success: false,
488185
489075
  id,
@@ -488399,6 +489289,27 @@ async function executeSceneAdd(opts) {
488399
489289
  duration = fallback2;
488400
489290
  }
488401
489291
  duration = Number(duration.toFixed(2));
489292
+ let storyboardPath;
489293
+ let storyboardSynced = false;
489294
+ let storyboardAction = "skipped";
489295
+ const shouldSyncStoryboard = opts.syncStoryboard !== false && await pathExists2(storyboardAbsPath);
489296
+ if (shouldSyncStoryboard) {
489297
+ opts.onProgress?.("Syncing STORYBOARD.md...");
489298
+ const before2 = await readFile14(storyboardAbsPath, "utf-8");
489299
+ const synced = upsertStoryboardBeat(before2, {
489300
+ beatId: id,
489301
+ title: opts.headline,
489302
+ duration,
489303
+ narration: narrationText && !opts.skipAudio ? narrationText : void 0,
489304
+ backdrop: opts.visuals && !opts.skipImage ? opts.visuals : void 0
489305
+ });
489306
+ if (synced.markdown !== before2) {
489307
+ await writeFile17(storyboardAbsPath, synced.markdown, "utf-8");
489308
+ }
489309
+ storyboardPath = relative8(process.cwd(), storyboardAbsPath) || storyboardAbsPath;
489310
+ storyboardSynced = true;
489311
+ storyboardAction = synced.action;
489312
+ }
488402
489313
  opts.onProgress?.("Emitting scene HTML...");
488403
489314
  const sceneHtml = emitSceneHtml({
488404
489315
  id,
@@ -488417,11 +489328,24 @@ async function executeSceneAdd(opts) {
488417
489328
  await mkdir11(dirname14(scenePath), { recursive: true });
488418
489329
  await writeFile17(scenePath, sceneHtml, "utf-8");
488419
489330
  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");
489331
+ let start = 0;
489332
+ if (storyboardSynced) {
489333
+ const syncPlan = await syncRootComposition({
489334
+ projectDir,
489335
+ rootRel,
489336
+ beats: await loadProjectRootSyncBeats(projectDir)
489337
+ });
489338
+ const blockingIssue = syncPlan.issues.find((issue) => issue.severity === "error");
489339
+ if (blockingIssue) return errResult(blockingIssue.message);
489340
+ const rootHtmlAfterSync = await readFile14(rootPath, "utf-8");
489341
+ start = readClipStart(rootHtmlAfterSync, id) ?? 0;
489342
+ } else {
489343
+ start = nextSceneStart(rootHtmlBefore, SCENE_OVERLAP_SECONDS);
489344
+ const existingClipCount = (rootHtmlBefore.match(/<div\s+class="clip"/g) || []).length;
489345
+ const trackIndex = existingClipCount % 2 + 1;
489346
+ const updated = insertClipIntoRoot(rootHtmlBefore, { id, start, duration, trackIndex });
489347
+ await writeFile17(rootPath, updated, "utf-8");
489348
+ }
488425
489349
  const transcriptAbsPath = transcriptRelPath ? resolve24(projectDir, transcriptRelPath) : void 0;
488426
489350
  return {
488427
489351
  success: true,
@@ -488434,6 +489358,9 @@ async function executeSceneAdd(opts) {
488434
489358
  audioPath: audioAbsPath ? relative8(process.cwd(), audioAbsPath) || audioAbsPath : void 0,
488435
489359
  imagePath: imageAbsPath ? relative8(process.cwd(), imageAbsPath) || imageAbsPath : void 0,
488436
489360
  lottiePath: lottieAbsPath ? relative8(process.cwd(), lottieAbsPath) || lottieAbsPath : void 0,
489361
+ storyboardPath,
489362
+ storyboardSynced,
489363
+ storyboardAction,
488437
489364
  transcriptPath: transcriptAbsPath ? relative8(process.cwd(), transcriptAbsPath) || transcriptAbsPath : void 0,
488438
489365
  transcriptWordCount
488439
489366
  };
@@ -488731,6 +489658,9 @@ var sceneAddSchema = z2.object({
488731
489658
  kicker: z2.string().optional().describe("Small label above the headline (used by 'explainer' and 'product-shot' presets)."),
488732
489659
  projectDir: z2.string().optional().describe(PROJECT_DIR_DESCRIPTION),
488733
489660
  insertInto: z2.string().optional().describe("Root composition file (relative to projectDir). Default 'index.html'."),
489661
+ syncStoryboard: z2.boolean().optional().describe(
489662
+ "Sync STORYBOARD.md when present, then rebuild root refs from it. Default true; set false for root-only insertion."
489663
+ ),
488734
489664
  imageProvider: z2.enum(["gemini", "openai"]).optional().describe("Image provider for visuals. Default 'gemini'."),
488735
489665
  voice: z2.string().optional().describe("ElevenLabs voice id or name."),
488736
489666
  skipAudio: z2.boolean().optional().describe("Skip TTS even if narration is provided."),
@@ -488743,7 +489673,7 @@ var sceneAddTool = defineTool({
488743
489673
  cost: "low",
488744
489674
  title: "Add Scene from Preset",
488745
489675
  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.",
489676
+ description: "Add a single scene to an existing scene project. Optionally generates narration audio (ElevenLabs) and/or a backdrop image (Gemini/OpenAI), then emits compositions/scene-<id>.html with a paused GSAP timeline. By default, syncs STORYBOARD.md and rebuilds root refs from it; set syncStoryboard:false for root-only insertion. Use skipAudio:true and skipImage:true for text-only scenes that need no API calls.",
488747
489677
  schema: sceneAddSchema,
488748
489678
  async execute(args, ctx) {
488749
489679
  const projectDir = args.projectDir ? resolve34(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
@@ -488757,6 +489687,7 @@ var sceneAddTool = defineTool({
488757
489687
  kicker: args.kicker,
488758
489688
  projectDir,
488759
489689
  insertInto: args.insertInto,
489690
+ syncStoryboard: args.syncStoryboard,
488760
489691
  imageProvider: args.imageProvider,
488761
489692
  voice: args.voice,
488762
489693
  skipAudio: args.skipAudio,
@@ -488775,6 +489706,9 @@ var sceneAddTool = defineTool({
488775
489706
  ];
488776
489707
  if (result.audioPath) lines.push(` audio: ${result.audioPath}`);
488777
489708
  if (result.imagePath) lines.push(` image: ${result.imagePath}`);
489709
+ if (result.storyboardPath && result.storyboardSynced) {
489710
+ lines.push(` story: ${result.storyboardPath} (${result.storyboardAction})`);
489711
+ }
488778
489712
  return {
488779
489713
  success: true,
488780
489714
  data: {
@@ -488785,7 +489719,10 @@ var sceneAddTool = defineTool({
488785
489719
  scenePath: result.scenePath,
488786
489720
  rootPath: result.rootPath,
488787
489721
  audioPath: result.audioPath,
488788
- imagePath: result.imagePath
489722
+ imagePath: result.imagePath,
489723
+ storyboardPath: result.storyboardPath,
489724
+ storyboardSynced: result.storyboardSynced,
489725
+ storyboardAction: result.storyboardAction
488789
489726
  },
488790
489727
  humanLines: lines
488791
489728
  };
@@ -490220,7 +491157,7 @@ init_root_sync();
490220
491157
  init_review_report();
490221
491158
  import { existsSync as existsSync56 } from "node:fs";
490222
491159
  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";
491160
+ import { basename as basename14, isAbsolute as isAbsolute7, join as join37, relative as relative13, resolve as resolve50 } from "node:path";
490224
491161
  async function inspectProject(opts) {
490225
491162
  const projectDir = resolve50(opts.projectDir);
490226
491163
  const issues = [];
@@ -490249,12 +491186,12 @@ async function inspectProject(opts) {
490249
491186
  );
490250
491187
  }
490251
491188
  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")
491189
+ storyboard: join37(projectDir, "STORYBOARD.md"),
491190
+ design: join37(projectDir, "DESIGN.md"),
491191
+ config: join37(projectDir, "vibe.config.json"),
491192
+ legacyConfig: join37(projectDir, "vibe.project.yaml"),
491193
+ buildReport: join37(projectDir, "build-report.json"),
491194
+ root: join37(projectDir, "index.html")
490258
491195
  };
490259
491196
  for (const [name, path14] of Object.entries(coreFiles)) {
490260
491197
  checks.files[name] = existsSync56(path14);
@@ -490338,7 +491275,7 @@ async function inspectProject(opts) {
490338
491275
  });
490339
491276
  retryWith.push(`vibe build ${projectDir} --stage sync --json`);
490340
491277
  }
490341
- const compositionsDir = join38(projectDir, "compositions");
491278
+ const compositionsDir = join37(projectDir, "compositions");
490342
491279
  const existingComps = existsSync56(compositionsDir) ? new Set(await listHtmlBasenames(compositionsDir)) : /* @__PURE__ */ new Set();
490343
491280
  checks.compositions.expected = inspectedBeatIds.length;
490344
491281
  for (const beatId of inspectedBeatIds) {
@@ -490346,12 +491283,12 @@ async function inspectProject(opts) {
490346
491283
  if (existingComps.has(file)) {
490347
491284
  checks.compositions.found++;
490348
491285
  } else {
490349
- checks.compositions.missing.push(join38("compositions", file));
491286
+ checks.compositions.missing.push(join37("compositions", file));
490350
491287
  issues.push({
490351
491288
  severity: "error",
490352
491289
  code: "MISSING_COMPOSITION",
490353
491290
  message: `Composition for beat "${beatId}" is missing.`,
490354
- file: join38("compositions", file),
491291
+ file: join37("compositions", file),
490355
491292
  scene: beatId,
490356
491293
  beatId,
490357
491294
  suggestedFix: "Run `vibe build --stage compose --json`."
@@ -490413,7 +491350,7 @@ async function inspectProject(opts) {
490413
491350
  try {
490414
491351
  const lint = await runProjectLint({ projectDir });
490415
491352
  const lintFiles = opts.beatId ? lint.files.filter(
490416
- (file) => file.file === "index.html" || file.file === join38("compositions", `scene-${opts.beatId}.html`)
491353
+ (file) => file.file === "index.html" || file.file === join37("compositions", `scene-${opts.beatId}.html`)
490417
491354
  ) : lint.files;
490418
491355
  const lintCounts = countLintFindings(lintFiles);
490419
491356
  checks.lint = {
@@ -490449,7 +491386,11 @@ async function inspectProject(opts) {
490449
491386
  );
490450
491387
  }
490451
491388
  function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
490452
- const normalizedIssues = normalizeReviewIssues(issues);
491389
+ const normalizedRetryWith = uniqueRetryWith(retryWith);
491390
+ const normalizedIssues = normalizeReviewIssues(issues, {
491391
+ projectDir,
491392
+ retryWith: normalizedRetryWith
491393
+ });
490453
491394
  const status = statusFromIssues(normalizedIssues);
490454
491395
  return {
490455
491396
  schemaVersion: "1",
@@ -490461,9 +491402,14 @@ function makeProjectResult(projectDir, issues, checks, retryWith, beatId) {
490461
491402
  score: scoreIssues(normalizedIssues),
490462
491403
  issues: normalizedIssues,
490463
491404
  summary: summarizeReviewIssues(normalizedIssues),
491405
+ nextActions: deriveNextReviewActions({
491406
+ issues: normalizedIssues,
491407
+ retryWith: normalizedRetryWith,
491408
+ projectDir
491409
+ }),
490464
491410
  sourceReports: projectSourceReports(checks),
490465
491411
  checks,
490466
- retryWith: uniqueRetryWith(retryWith)
491412
+ retryWith: normalizedRetryWith
490467
491413
  };
490468
491414
  }
490469
491415
  function storyboardPlaceholderIssues(storyboard) {
@@ -490783,7 +491729,7 @@ init_storyboard_parse();
490783
491729
  init_review_report();
490784
491730
  import { existsSync as existsSync57 } from "node:fs";
490785
491731
  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";
491732
+ import { basename as basename15, isAbsolute as isAbsolute8, join as join38, relative as relative14, resolve as resolve51 } from "node:path";
490787
491733
  var VIDEO_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".webm", ".m4v"]);
490788
491734
  var DEFAULT_AI_MODEL = "flash";
490789
491735
  var BLACK_FRAME_MIN_DURATION_SEC = 1;
@@ -491015,6 +491961,8 @@ async function inspectRender(opts) {
491015
491961
  severity: "error",
491016
491962
  code: "RENDER_NOT_FOUND",
491017
491963
  message: opts.beatId ? `No rendered video was found for beat "${opts.beatId}". Pass --video or render the beat first.` : "No rendered video was found. Pass --video or render the project first.",
491964
+ beatId: opts.beatId,
491965
+ scene: opts.beatId,
491018
491966
  suggestedFix: opts.beatId ? "Run `vibe render <project> --beat <id> --json`." : "Run `vibe build --stage render --json` or `vibe render --json`."
491019
491967
  });
491020
491968
  retryWith.push(
@@ -491037,6 +491985,8 @@ async function inspectRender(opts) {
491037
491985
  code: "EMPTY_RENDER",
491038
491986
  message: "Rendered video file is empty.",
491039
491987
  file: displayPath(videoPath),
491988
+ beatId: opts.beatId,
491989
+ scene: opts.beatId,
491040
491990
  suggestedFix: opts.beatId ? "Render again with `vibe render --beat <id> --json`." : "Render again with `vibe render --json`."
491041
491991
  });
491042
491992
  retryWith.push(
@@ -491046,6 +491996,8 @@ async function inspectRender(opts) {
491046
491996
  const beatTimings = await beatTimingsFromBuildReport(projectDir, opts.beatId);
491047
491997
  const expectedDurationSec = beatTimings.length > 0 ? Number(beatTimings.reduce((sum, beat) => sum + beat.sceneDurationSec, 0).toFixed(3)) : await expectedDurationFromBuildReport(projectDir, opts.beatId);
491048
491998
  if (expectedDurationSec !== void 0) checks.expectedDurationSec = expectedDurationSec;
491999
+ const expectedAudio = await renderExpectsAudio(projectDir, opts.beatId);
492000
+ checks.expectedAudio = expectedAudio;
491049
492001
  if (!commandExists("ffprobe")) {
491050
492002
  issues.push({
491051
492003
  severity: "error",
@@ -491071,7 +492023,7 @@ async function inspectRender(opts) {
491071
492023
  file: displayPath(videoPath)
491072
492024
  });
491073
492025
  }
491074
- if (!audioStream) {
492026
+ if (!audioStream && expectedAudio) {
491075
492027
  issues.push({
491076
492028
  severity: "warning",
491077
492029
  code: "NO_AUDIO_STREAM",
@@ -491276,8 +492228,86 @@ async function inspectRender(opts) {
491276
492228
  );
491277
492229
  return maybeWriteRenderReport(projectDir, opts, result);
491278
492230
  }
492231
+ async function renderExpectsAudio(projectDir, beatId) {
492232
+ const root2 = resolve51(projectDir);
492233
+ const rootHtml = await readTextIfExists(join38(root2, "index.html"));
492234
+ if (rootHtml && rootHtmlExpectsAudio(rootHtml, beatId)) return true;
492235
+ const buildReport = await readJsonIfExists(join38(root2, "build-report.json"));
492236
+ if (buildReportExpectsAudio(buildReport, beatId)) return true;
492237
+ const storyboard = await readTextIfExists(join38(root2, "STORYBOARD.md"));
492238
+ if (!storyboard) return false;
492239
+ try {
492240
+ const parsed = parseStoryboard(storyboard);
492241
+ const beats = beatId ? parsed.beats.filter((beat) => beat.id === beatId) : parsed.beats;
492242
+ return beats.some(
492243
+ (beat) => [
492244
+ `assets/narration-${beat.id}.mp3`,
492245
+ `assets/narration-${beat.id}.wav`,
492246
+ `assets/music-${beat.id}.mp3`,
492247
+ `assets/music-${beat.id}.wav`
492248
+ ].some((asset) => existsSync57(join38(root2, asset)))
492249
+ );
492250
+ } catch {
492251
+ return false;
492252
+ }
492253
+ }
492254
+ function rootHtmlExpectsAudio(html, beatId) {
492255
+ const audioTags = html.match(/<audio\b[^>]*>/gi) ?? [];
492256
+ if (!beatId) return audioTags.length > 0;
492257
+ const escaped = escapeRegExp4(beatId);
492258
+ const beatAudioRe = new RegExp(
492259
+ `(id="(?:narration|music)-${escaped}"|src="[^"]*(?:narration|music)-${escaped}\\.(?:mp3|wav)")`,
492260
+ "i"
492261
+ );
492262
+ return audioTags.some((tag) => beatAudioRe.test(tag));
492263
+ }
492264
+ function buildReportExpectsAudio(report, beatId) {
492265
+ if (!report || typeof report !== "object") return false;
492266
+ const beats = Array.isArray(report.beats) ? report.beats ?? [] : [];
492267
+ const selected = beatId === void 0 ? beats : beats.filter((beat) => {
492268
+ if (!beat || typeof beat !== "object") return false;
492269
+ return beat.id === beatId;
492270
+ });
492271
+ return selected.some((beat) => beatReportExpectsAudio(beat));
492272
+ }
492273
+ function beatReportExpectsAudio(beat) {
492274
+ if (!beat || typeof beat !== "object") return false;
492275
+ const record = beat;
492276
+ if (typeof record.narrationPath === "string" && record.narrationPath.length > 0) return true;
492277
+ if (typeof record.musicPath === "string" && record.musicPath.length > 0) return true;
492278
+ if (parseOptionalNumber2(record.narrationDurationSec) !== void 0) return true;
492279
+ const narration = record.narration;
492280
+ if (narration && typeof narration === "object") {
492281
+ const n2 = narration;
492282
+ if (typeof n2.path === "string" && n2.path.length > 0) return true;
492283
+ if (parseOptionalNumber2(n2.durationSec) !== void 0) return true;
492284
+ }
492285
+ const music = record.music;
492286
+ if (music && typeof music === "object") {
492287
+ const m2 = music;
492288
+ if (typeof m2.path === "string" && m2.path.length > 0) return true;
492289
+ if (parseOptionalNumber2(m2.durationSec) !== void 0) return true;
492290
+ }
492291
+ return false;
492292
+ }
492293
+ async function readJsonIfExists(path14) {
492294
+ const text = await readTextIfExists(path14);
492295
+ if (!text) return null;
492296
+ try {
492297
+ return JSON.parse(text);
492298
+ } catch {
492299
+ return null;
492300
+ }
492301
+ }
492302
+ function escapeRegExp4(value) {
492303
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
492304
+ }
491279
492305
  function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith, score) {
491280
- const normalizedIssues = normalizeReviewIssues(issues);
492306
+ const normalizedRetryWith = uniqueRetryWith(retryWith);
492307
+ const normalizedIssues = normalizeReviewIssues(issues, {
492308
+ projectDir,
492309
+ retryWith: normalizedRetryWith
492310
+ });
491281
492311
  const status = statusFromIssues(normalizedIssues);
491282
492312
  return {
491283
492313
  schemaVersion: "1",
@@ -491290,9 +492320,14 @@ function makeRenderResult(projectDir, opts, videoPath, issues, checks, retryWith
491290
492320
  score,
491291
492321
  issues: normalizedIssues,
491292
492322
  summary: summarizeReviewIssues(normalizedIssues),
492323
+ nextActions: deriveNextReviewActions({
492324
+ issues: normalizedIssues,
492325
+ retryWith: normalizedRetryWith,
492326
+ projectDir
492327
+ }),
491293
492328
  sourceReports: renderSourceReports(projectDir, videoPath, checks),
491294
492329
  checks,
491295
- retryWith: uniqueRetryWith(retryWith)
492330
+ retryWith: normalizedRetryWith
491296
492331
  };
491297
492332
  }
491298
492333
  async function maybeWriteRenderReport(projectDir, opts, result) {
@@ -491318,8 +492353,8 @@ async function maybeWriteRenderReport(projectDir, opts, result) {
491318
492353
  }
491319
492354
  function renderSourceReports(projectDir, videoPath, checks) {
491320
492355
  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");
492356
+ if (existsSync57(join38(projectDir, "build-report.json"))) reports.push("build-report.json");
492357
+ if (existsSync57(join38(projectDir, "render-report.json"))) reports.push("render-report.json");
491323
492358
  if (videoPath) reports.push(displayPath(videoPath));
491324
492359
  if (checks.durationSec !== void 0 || checks.width !== void 0 || checks.height !== void 0) {
491325
492360
  reports.push("ffprobe");
@@ -491334,7 +492369,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491334
492369
  const candidate = resolve51(process.cwd(), explicit);
491335
492370
  return existsSync57(candidate) ? candidate : null;
491336
492371
  }
491337
- const renderReportPath = join39(projectDir, "render-report.json");
492372
+ const renderReportPath = join38(projectDir, "render-report.json");
491338
492373
  if (existsSync57(renderReportPath)) {
491339
492374
  try {
491340
492375
  const report = JSON.parse(await readFile32(renderReportPath, "utf-8"));
@@ -491346,7 +492381,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491346
492381
  } catch {
491347
492382
  }
491348
492383
  }
491349
- const reportPath = join39(projectDir, "build-report.json");
492384
+ const reportPath = join38(projectDir, "build-report.json");
491350
492385
  if (!beatId && existsSync57(reportPath)) {
491351
492386
  try {
491352
492387
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491355,7 +492390,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491355
492390
  } catch {
491356
492391
  }
491357
492392
  }
491358
- const rendersDir = join39(projectDir, "renders");
492393
+ const rendersDir = join38(projectDir, "renders");
491359
492394
  if (!existsSync57(rendersDir)) return null;
491360
492395
  const entries2 = await readdir7(rendersDir, { withFileTypes: true });
491361
492396
  const candidates = [];
@@ -491364,7 +492399,7 @@ async function resolveRenderVideoPath(projectDir, explicit, beatId) {
491364
492399
  const ext = entry.name.slice(entry.name.lastIndexOf(".")).toLowerCase();
491365
492400
  if (!VIDEO_EXTENSIONS.has(ext)) continue;
491366
492401
  if (beatId && !entry.name.includes(sanitizeFileSegment2(beatId))) continue;
491367
- const full = join39(rendersDir, entry.name);
492402
+ const full = join38(rendersDir, entry.name);
491368
492403
  const info = await stat6(full);
491369
492404
  candidates.push({ path: full, mtimeMs: info.mtimeMs });
491370
492405
  }
@@ -491382,11 +492417,11 @@ async function runAiRenderReview(projectDir, videoPath, model) {
491382
492417
  });
491383
492418
  }
491384
492419
  function resolveStoryboardPath(projectDir) {
491385
- const storyboardPath = join39(projectDir, "STORYBOARD.md");
492420
+ const storyboardPath = join38(projectDir, "STORYBOARD.md");
491386
492421
  return existsSync57(storyboardPath) ? storyboardPath : null;
491387
492422
  }
491388
492423
  async function expectedDurationFromBuildReport(projectDir, beatId) {
491389
- const reportPath = join39(projectDir, "build-report.json");
492424
+ const reportPath = join38(projectDir, "build-report.json");
491390
492425
  if (!existsSync57(reportPath)) return void 0;
491391
492426
  try {
491392
492427
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491401,7 +492436,7 @@ async function expectedDurationFromBuildReport(projectDir, beatId) {
491401
492436
  }
491402
492437
  }
491403
492438
  async function beatTimingsFromBuildReport(projectDir, beatId) {
491404
- const reportPath = join39(projectDir, "build-report.json");
492439
+ const reportPath = join38(projectDir, "build-report.json");
491405
492440
  if (!existsSync57(reportPath)) return [];
491406
492441
  try {
491407
492442
  const report = JSON.parse(await readFile32(reportPath, "utf-8"));
@@ -491430,17 +492465,17 @@ async function beatTimingsFromBuildReport(projectDir, beatId) {
491430
492465
  }
491431
492466
  async function buildRenderReviewProjectContext(projectDir) {
491432
492467
  const parts = [];
491433
- const storyboard = await readTextIfExists(join39(projectDir, "STORYBOARD.md"));
492468
+ const storyboard = await readTextIfExists(join38(projectDir, "STORYBOARD.md"));
491434
492469
  if (storyboard) {
491435
492470
  parts.push(`STORYBOARD.md:
491436
492471
  ${truncateText(storyboard, 4e3)}`);
491437
492472
  parts.push(`Storyboard beat cue summary:
491438
492473
  ${storyboardBeatCueSummary(storyboard)}`);
491439
492474
  }
491440
- const design = await readTextIfExists(join39(projectDir, "DESIGN.md"));
492475
+ const design = await readTextIfExists(join38(projectDir, "DESIGN.md"));
491441
492476
  if (design) parts.push(`DESIGN.md:
491442
492477
  ${truncateText(design, 4e3)}`);
491443
- const buildReport = await readTextIfExists(join39(projectDir, "build-report.json"));
492478
+ const buildReport = await readTextIfExists(join38(projectDir, "build-report.json"));
491444
492479
  if (buildReport) parts.push(`build-report.json:
491445
492480
  ${truncateText(buildReport, 6e3)}`);
491446
492481
  const timings = await beatTimingsFromBuildReport(projectDir);
@@ -492299,7 +493334,7 @@ init_storyboard_edit();
492299
493334
  // ../cli/src/commands/_shared/storyboard-revise.ts
492300
493335
  import { existsSync as existsSync59 } from "node:fs";
492301
493336
  import { readFile as readFile35, writeFile as writeFile42 } from "node:fs/promises";
492302
- import { join as join40, resolve as resolve64 } from "node:path";
493337
+ import { join as join39, resolve as resolve64 } from "node:path";
492303
493338
 
492304
493339
  // ../cli/src/agent/adapters/index.ts
492305
493340
  init_openai3();
@@ -492346,7 +493381,7 @@ init_storyboard_parse();
492346
493381
  init_storyboard_edit();
492347
493382
  async function executeStoryboardRevision(opts) {
492348
493383
  const projectDir = resolve64(opts.projectDir);
492349
- const storyboardPath = join40(projectDir, "STORYBOARD.md");
493384
+ const storyboardPath = join39(projectDir, "STORYBOARD.md");
492350
493385
  const retryWith = [`vibe storyboard revise ${projectDir} --from "<request>" --dry-run --json`];
492351
493386
  if (!existsSync59(storyboardPath)) {
492352
493387
  return failureResult({
@@ -492581,8 +493616,8 @@ function diffChangedBeats(before2, after2) {
492581
493616
  return ids.filter((id) => JSON.stringify(beforeBeats.get(id) ?? null) !== JSON.stringify(afterBeats.get(id) ?? null));
492582
493617
  }
492583
493618
  async function readRevisionContext(projectDir) {
492584
- const designMd = await readOptionalText(join40(projectDir, "DESIGN.md"));
492585
- const buildReportJson = await readOptionalJsonText(join40(projectDir, "build-report.json"));
493619
+ const designMd = await readOptionalText(join39(projectDir, "DESIGN.md"));
493620
+ const buildReportJson = await readOptionalJsonText(join39(projectDir, "build-report.json"));
492586
493621
  return { designMd, buildReportJson };
492587
493622
  }
492588
493623
  async function readOptionalText(path14) {
@@ -492897,7 +493932,7 @@ var storyboardTools = [
492897
493932
  // ../cli/src/tools/manifest/remix.ts
492898
493933
  import { writeFile as writeFile48 } from "node:fs/promises";
492899
493934
  import { tmpdir as tmpdir6 } from "node:os";
492900
- import { join as join41 } from "node:path";
493935
+ import { join as join40 } from "node:path";
492901
493936
  import { z as z8 } from "zod";
492902
493937
 
492903
493938
  // ../cli/src/commands/ai-script-pipeline.ts
@@ -494631,7 +495666,7 @@ var pipelineRunTool = defineTool({
494631
495666
  let resolvedPath = args.pipelinePath;
494632
495667
  let tempPath;
494633
495668
  if (args.pipelineYaml) {
494634
- tempPath = join41(tmpdir6(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
495669
+ tempPath = join40(tmpdir6(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
494635
495670
  await writeFile48(tempPath, args.pipelineYaml, "utf-8");
494636
495671
  resolvedPath = tempPath;
494637
495672
  }
@@ -495163,7 +496198,7 @@ init_engine();
495163
496198
  init_storyboard_parse();
495164
496199
  import { readdir as readdir8, readFile as readFile42, stat as stat7 } from "node:fs/promises";
495165
496200
  import { existsSync as existsSync65 } from "node:fs";
495166
- import { join as join43, resolve as resolve71 } from "node:path";
496201
+ import { join as join41, resolve as resolve71 } from "node:path";
495167
496202
  async function executeProjectList(opts = {}) {
495168
496203
  const workspaceDir = resolve71(opts.workspaceDir ?? ".");
495169
496204
  if (!existsSync65(workspaceDir)) {
@@ -495183,11 +496218,11 @@ async function executeProjectList(opts = {}) {
495183
496218
  if (entry.name === "node_modules") continue;
495184
496219
  if (entry.name.startsWith(".")) continue;
495185
496220
  if (entry.name.startsWith("_")) {
495186
- archivedCount += await countProjects(join43(workspaceDir, entry.name));
496221
+ archivedCount += await countProjects(join41(workspaceDir, entry.name));
495187
496222
  continue;
495188
496223
  }
495189
- const projectDir = join43(workspaceDir, entry.name);
495190
- if (!existsSync65(join43(projectDir, "STORYBOARD.md"))) continue;
496224
+ const projectDir = join41(workspaceDir, entry.name);
496225
+ if (!existsSync65(join41(projectDir, "STORYBOARD.md"))) continue;
495191
496226
  projects.push(await describeProject(entry.name, projectDir));
495192
496227
  }
495193
496228
  projects.sort((a2, b2) => b2.updatedAt.localeCompare(a2.updatedAt));
@@ -495197,7 +496232,7 @@ async function countProjects(dir) {
495197
496232
  try {
495198
496233
  const entries2 = await readdir8(dir, { withFileTypes: true });
495199
496234
  return entries2.filter(
495200
- (e2) => e2.isDirectory() && existsSync65(join43(dir, e2.name, "STORYBOARD.md"))
496235
+ (e2) => e2.isDirectory() && existsSync65(join41(dir, e2.name, "STORYBOARD.md"))
495201
496236
  ).length;
495202
496237
  } catch {
495203
496238
  return 0;
@@ -495208,7 +496243,7 @@ async function describeProject(name, projectDir) {
495208
496243
  let storyboardDurationSec = 0;
495209
496244
  const timestamps = [];
495210
496245
  try {
495211
- const storyboardPath = join43(projectDir, "STORYBOARD.md");
496246
+ const storyboardPath = join41(projectDir, "STORYBOARD.md");
495212
496247
  const parsed = parseStoryboard(await readFile42(storyboardPath, "utf-8"));
495213
496248
  beats = parsed.beats.length;
495214
496249
  storyboardDurationSec = Number(
@@ -495220,7 +496255,7 @@ async function describeProject(name, projectDir) {
495220
496255
  let buildStatus;
495221
496256
  let buildUpdatedAt;
495222
496257
  try {
495223
- const reportPath = join43(projectDir, "build-report.json");
496258
+ const reportPath = join41(projectDir, "build-report.json");
495224
496259
  const report = JSON.parse(await readFile42(reportPath, "utf-8"));
495225
496260
  buildStatus = report.status ?? report.phase;
495226
496261
  buildUpdatedAt = report.updatedAt;
@@ -495229,12 +496264,12 @@ async function describeProject(name, projectDir) {
495229
496264
  }
495230
496265
  let latestRender;
495231
496266
  try {
495232
- const rendersDir = join43(projectDir, "renders");
496267
+ const rendersDir = join41(projectDir, "renders");
495233
496268
  const files = await readdir8(rendersDir);
495234
496269
  let newest;
495235
496270
  for (const file of files) {
495236
496271
  if (!/\.(mp4|webm|mov)$/i.test(file)) continue;
495237
- const { mtimeMs } = await stat7(join43(rendersDir, file));
496272
+ const { mtimeMs } = await stat7(join41(rendersDir, file));
495238
496273
  if (!newest || mtimeMs > newest.mtimeMs) newest = { file, mtimeMs };
495239
496274
  }
495240
496275
  if (newest) {
@@ -496245,7 +497280,7 @@ var exportTools = [exportVideoTool];
496245
497280
 
496246
497281
  // ../cli/src/tools/manifest/agent-only.ts
496247
497282
  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";
497283
+ import { resolve as resolve76, join as join43, basename as basename21, extname as extname14 } from "node:path";
496249
497284
  import { z as z13 } from "zod";
496250
497285
  init_engine();
496251
497286
  init_exec_safe();
@@ -496324,7 +497359,7 @@ var fsListTool = defineTool({
496324
497359
  const results = [];
496325
497360
  for (const entry of entries2) {
496326
497361
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
496327
- const fullPath = join44(absPath, entry.name);
497362
+ const fullPath = join43(absPath, entry.name);
496328
497363
  const stats = await stat8(fullPath);
496329
497364
  if (entry.isDirectory()) {
496330
497365
  results.push(`[DIR] ${entry.name}/`);
@@ -496437,7 +497472,7 @@ var batchImportTool = defineTool({
496437
497472
  const scanDir = async (dir) => {
496438
497473
  const entries2 = await readdir9(dir, { withFileTypes: true });
496439
497474
  for (const entry of entries2) {
496440
- const entryPath = join44(dir, entry.name);
497475
+ const entryPath = join43(dir, entry.name);
496441
497476
  if (entry.isDirectory() && recursive) {
496442
497477
  await scanDir(entryPath);
496443
497478
  } else if (entry.isFile()) {
@@ -498507,7 +499542,7 @@ var server2 = new Server(
498507
499542
  {
498508
499543
  name: "vibeframe",
498509
499544
  // Stamped with the package version by build.js at bundle time.
498510
- version: "0.113.0"
499545
+ version: "0.113.2"
498511
499546
  },
498512
499547
  {
498513
499548
  capabilities: {