@vibeframe/cli 0.72.0 → 0.73.0

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 (2) hide show
  1. package/dist/index.js +1363 -390
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -14343,12 +14343,12 @@ var init_api_keys = __esm({
14343
14343
  label: "ImgBB",
14344
14344
  showInSetup: false,
14345
14345
  // not prompted in setup wizard — internal upload host
14346
- envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and fal.ai for image-to-video uploads)",
14346
+ envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
14347
14347
  envExampleUrl: "https://api.imgbb.com/",
14348
14348
  // ImgBB has no provider class (envvar-only); doctor still shows what it
14349
14349
  // unlocks at the apiKey level.
14350
14350
  commandsUnlocked: [
14351
- "generate video -p kling/fal (image-to-video upload host)"
14351
+ "generate video -p kling/seedance (image-to-video upload host)"
14352
14352
  ]
14353
14353
  });
14354
14354
  defineProvider({
@@ -15194,7 +15194,7 @@ var init_GeminiProvider = __esm({
15194
15194
  * Sleep helper
15195
15195
  */
15196
15196
  sleep(ms) {
15197
- return new Promise((resolve62) => setTimeout(resolve62, ms));
15197
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
15198
15198
  }
15199
15199
  /**
15200
15200
  * Extend a previously generated Veo video
@@ -19366,7 +19366,7 @@ var init_RunwayProvider = __esm({
19366
19366
  * Sleep helper
19367
19367
  */
19368
19368
  sleep(ms) {
19369
- return new Promise((resolve62) => setTimeout(resolve62, ms));
19369
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
19370
19370
  }
19371
19371
  };
19372
19372
  RunwayProvider.API_VERSION = "2024-11-06";
@@ -19807,7 +19807,7 @@ var init_KlingProvider = __esm({
19807
19807
  * Sleep helper
19808
19808
  */
19809
19809
  sleep(ms) {
19810
- return new Promise((resolve62) => setTimeout(resolve62, ms));
19810
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
19811
19811
  }
19812
19812
  };
19813
19813
  klingProvider = new KlingProvider();
@@ -20125,7 +20125,7 @@ var init_GrokProvider = __esm({
20125
20125
  }
20126
20126
  }
20127
20127
  sleep(ms) {
20128
- return new Promise((resolve62) => setTimeout(resolve62, ms));
20128
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
20129
20129
  }
20130
20130
  };
20131
20131
  grokProvider = new GrokProvider();
@@ -20160,11 +20160,11 @@ var require_middleware = __commonJS({
20160
20160
  "use strict";
20161
20161
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20162
20162
  function adopt(value) {
20163
- return value instanceof P ? value : new P(function(resolve62) {
20164
- resolve62(value);
20163
+ return value instanceof P ? value : new P(function(resolve64) {
20164
+ resolve64(value);
20165
20165
  });
20166
20166
  }
20167
- return new (P || (P = Promise))(function(resolve62, reject) {
20167
+ return new (P || (P = Promise))(function(resolve64, reject) {
20168
20168
  function fulfilled(value) {
20169
20169
  try {
20170
20170
  step(generator.next(value));
@@ -20180,7 +20180,7 @@ var require_middleware = __commonJS({
20180
20180
  }
20181
20181
  }
20182
20182
  function step(result) {
20183
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20183
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20184
20184
  }
20185
20185
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20186
20186
  });
@@ -20265,11 +20265,11 @@ var require_response = __commonJS({
20265
20265
  "use strict";
20266
20266
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20267
20267
  function adopt(value) {
20268
- return value instanceof P ? value : new P(function(resolve62) {
20269
- resolve62(value);
20268
+ return value instanceof P ? value : new P(function(resolve64) {
20269
+ resolve64(value);
20270
20270
  });
20271
20271
  }
20272
- return new (P || (P = Promise))(function(resolve62, reject) {
20272
+ return new (P || (P = Promise))(function(resolve64, reject) {
20273
20273
  function fulfilled(value) {
20274
20274
  try {
20275
20275
  step(generator.next(value));
@@ -20285,7 +20285,7 @@ var require_response = __commonJS({
20285
20285
  }
20286
20286
  }
20287
20287
  function step(result) {
20288
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20288
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20289
20289
  }
20290
20290
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20291
20291
  });
@@ -20392,11 +20392,11 @@ var require_utils = __commonJS({
20392
20392
  "use strict";
20393
20393
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20394
20394
  function adopt(value) {
20395
- return value instanceof P ? value : new P(function(resolve62) {
20396
- resolve62(value);
20395
+ return value instanceof P ? value : new P(function(resolve64) {
20396
+ resolve64(value);
20397
20397
  });
20398
20398
  }
20399
- return new (P || (P = Promise))(function(resolve62, reject) {
20399
+ return new (P || (P = Promise))(function(resolve64, reject) {
20400
20400
  function fulfilled(value) {
20401
20401
  try {
20402
20402
  step(generator.next(value));
@@ -20412,7 +20412,7 @@ var require_utils = __commonJS({
20412
20412
  }
20413
20413
  }
20414
20414
  function step(result) {
20415
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20415
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20416
20416
  }
20417
20417
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20418
20418
  });
@@ -20505,7 +20505,7 @@ var require_utils = __commonJS({
20505
20505
  }
20506
20506
  function sleep2(ms) {
20507
20507
  return __awaiter(this, void 0, void 0, function* () {
20508
- return new Promise((resolve62) => setTimeout(resolve62, ms));
20508
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
20509
20509
  });
20510
20510
  }
20511
20511
  }
@@ -20517,11 +20517,11 @@ var require_retry = __commonJS({
20517
20517
  "use strict";
20518
20518
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20519
20519
  function adopt(value) {
20520
- return value instanceof P ? value : new P(function(resolve62) {
20521
- resolve62(value);
20520
+ return value instanceof P ? value : new P(function(resolve64) {
20521
+ resolve64(value);
20522
20522
  });
20523
20523
  }
20524
- return new (P || (P = Promise))(function(resolve62, reject) {
20524
+ return new (P || (P = Promise))(function(resolve64, reject) {
20525
20525
  function fulfilled(value) {
20526
20526
  try {
20527
20527
  step(generator.next(value));
@@ -20537,7 +20537,7 @@ var require_retry = __commonJS({
20537
20537
  }
20538
20538
  }
20539
20539
  function step(result) {
20540
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20540
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20541
20541
  }
20542
20542
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20543
20543
  });
@@ -20744,11 +20744,11 @@ var require_request = __commonJS({
20744
20744
  "use strict";
20745
20745
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20746
20746
  function adopt(value) {
20747
- return value instanceof P ? value : new P(function(resolve62) {
20748
- resolve62(value);
20747
+ return value instanceof P ? value : new P(function(resolve64) {
20748
+ resolve64(value);
20749
20749
  });
20750
20750
  }
20751
- return new (P || (P = Promise))(function(resolve62, reject) {
20751
+ return new (P || (P = Promise))(function(resolve64, reject) {
20752
20752
  function fulfilled(value) {
20753
20753
  try {
20754
20754
  step(generator.next(value));
@@ -20764,7 +20764,7 @@ var require_request = __commonJS({
20764
20764
  }
20765
20765
  }
20766
20766
  function step(result) {
20767
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20767
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20768
20768
  }
20769
20769
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20770
20770
  });
@@ -20851,11 +20851,11 @@ var require_storage = __commonJS({
20851
20851
  "use strict";
20852
20852
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20853
20853
  function adopt(value) {
20854
- return value instanceof P ? value : new P(function(resolve62) {
20855
- resolve62(value);
20854
+ return value instanceof P ? value : new P(function(resolve64) {
20855
+ resolve64(value);
20856
20856
  });
20857
20857
  }
20858
- return new (P || (P = Promise))(function(resolve62, reject) {
20858
+ return new (P || (P = Promise))(function(resolve64, reject) {
20859
20859
  function fulfilled(value) {
20860
20860
  try {
20861
20861
  step(generator.next(value));
@@ -20871,7 +20871,7 @@ var require_storage = __commonJS({
20871
20871
  }
20872
20872
  }
20873
20873
  function step(result) {
20874
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20874
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20875
20875
  }
20876
20876
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20877
20877
  });
@@ -21199,11 +21199,11 @@ var require_auth = __commonJS({
21199
21199
  "use strict";
21200
21200
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
21201
21201
  function adopt(value) {
21202
- return value instanceof P ? value : new P(function(resolve62) {
21203
- resolve62(value);
21202
+ return value instanceof P ? value : new P(function(resolve64) {
21203
+ resolve64(value);
21204
21204
  });
21205
21205
  }
21206
- return new (P || (P = Promise))(function(resolve62, reject) {
21206
+ return new (P || (P = Promise))(function(resolve64, reject) {
21207
21207
  function fulfilled(value) {
21208
21208
  try {
21209
21209
  step(generator.next(value));
@@ -21219,7 +21219,7 @@ var require_auth = __commonJS({
21219
21219
  }
21220
21220
  }
21221
21221
  function step(result) {
21222
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
21222
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
21223
21223
  }
21224
21224
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21225
21225
  });
@@ -21258,11 +21258,11 @@ var require_streaming = __commonJS({
21258
21258
  "use strict";
21259
21259
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
21260
21260
  function adopt(value) {
21261
- return value instanceof P ? value : new P(function(resolve62) {
21262
- resolve62(value);
21261
+ return value instanceof P ? value : new P(function(resolve64) {
21262
+ resolve64(value);
21263
21263
  });
21264
21264
  }
21265
- return new (P || (P = Promise))(function(resolve62, reject) {
21265
+ return new (P || (P = Promise))(function(resolve64, reject) {
21266
21266
  function fulfilled(value) {
21267
21267
  try {
21268
21268
  step(generator.next(value));
@@ -21278,7 +21278,7 @@ var require_streaming = __commonJS({
21278
21278
  }
21279
21279
  }
21280
21280
  function step(result) {
21281
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
21281
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
21282
21282
  }
21283
21283
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21284
21284
  });
@@ -21511,7 +21511,7 @@ var require_streaming = __commonJS({
21511
21511
  query: options.queryParams
21512
21512
  });
21513
21513
  this.options = options;
21514
- this.donePromise = new Promise((resolve62, reject) => {
21514
+ this.donePromise = new Promise((resolve64, reject) => {
21515
21515
  if (this.streamClosed) {
21516
21516
  reject(new response_1.ApiError({
21517
21517
  message: "Streaming connection is already closed.",
@@ -21522,11 +21522,11 @@ var require_streaming = __commonJS({
21522
21522
  }
21523
21523
  this.signal.addEventListener("abort", () => {
21524
21524
  var _a2;
21525
- resolve62((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
21525
+ resolve64((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
21526
21526
  });
21527
21527
  this.on("done", (data) => {
21528
21528
  this.streamClosed = true;
21529
- resolve62(data);
21529
+ resolve64(data);
21530
21530
  });
21531
21531
  this.on("error", (error) => {
21532
21532
  this.streamClosed = true;
@@ -21551,7 +21551,7 @@ var require_streaming = __commonJS({
21551
21551
  if (data) {
21552
21552
  yield yield __await(data);
21553
21553
  }
21554
- yield __await(new Promise((resolve62) => setTimeout(resolve62, 16)));
21554
+ yield __await(new Promise((resolve64) => setTimeout(resolve64, 16)));
21555
21555
  }
21556
21556
  });
21557
21557
  }
@@ -21596,11 +21596,11 @@ var require_queue = __commonJS({
21596
21596
  "use strict";
21597
21597
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
21598
21598
  function adopt(value) {
21599
- return value instanceof P ? value : new P(function(resolve62) {
21600
- resolve62(value);
21599
+ return value instanceof P ? value : new P(function(resolve64) {
21600
+ resolve64(value);
21601
21601
  });
21602
21602
  }
21603
- return new (P || (P = Promise))(function(resolve62, reject) {
21603
+ return new (P || (P = Promise))(function(resolve64, reject) {
21604
21604
  function fulfilled(value) {
21605
21605
  try {
21606
21606
  step(generator.next(value));
@@ -21616,7 +21616,7 @@ var require_queue = __commonJS({
21616
21616
  }
21617
21617
  }
21618
21618
  function step(result) {
21619
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
21619
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
21620
21620
  }
21621
21621
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21622
21622
  });
@@ -21751,7 +21751,7 @@ var require_queue = __commonJS({
21751
21751
  }
21752
21752
  return doneStatus;
21753
21753
  }
21754
- return new Promise((resolve62, reject) => {
21754
+ return new Promise((resolve64, reject) => {
21755
21755
  var _a;
21756
21756
  let pollingTimeoutId;
21757
21757
  const pollInterval = "pollInterval" in options && typeof options.pollInterval === "number" ? (_a = options.pollInterval) !== null && _a !== void 0 ? _a : DEFAULT_POLL_INTERVAL : DEFAULT_POLL_INTERVAL;
@@ -21783,7 +21783,7 @@ var require_queue = __commonJS({
21783
21783
  }
21784
21784
  if (requestStatus.status === "COMPLETED") {
21785
21785
  clearScheduledTasks();
21786
- resolve62(requestStatus);
21786
+ resolve64(requestStatus);
21787
21787
  return;
21788
21788
  }
21789
21789
  pollingTimeoutId = setTimeout(poll, pollInterval);
@@ -23744,11 +23744,11 @@ var require_realtime = __commonJS({
23744
23744
  "use strict";
23745
23745
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
23746
23746
  function adopt(value) {
23747
- return value instanceof P ? value : new P(function(resolve62) {
23748
- resolve62(value);
23747
+ return value instanceof P ? value : new P(function(resolve64) {
23748
+ resolve64(value);
23749
23749
  });
23750
23750
  }
23751
- return new (P || (P = Promise))(function(resolve62, reject) {
23751
+ return new (P || (P = Promise))(function(resolve64, reject) {
23752
23752
  function fulfilled(value) {
23753
23753
  try {
23754
23754
  step(generator.next(value));
@@ -23764,7 +23764,7 @@ var require_realtime = __commonJS({
23764
23764
  }
23765
23765
  }
23766
23766
  function step(result) {
23767
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
23767
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
23768
23768
  }
23769
23769
  step((generator = generator.apply(thisArg, _arguments || [])).next());
23770
23770
  });
@@ -24089,11 +24089,11 @@ var require_client = __commonJS({
24089
24089
  "use strict";
24090
24090
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
24091
24091
  function adopt(value) {
24092
- return value instanceof P ? value : new P(function(resolve62) {
24093
- resolve62(value);
24092
+ return value instanceof P ? value : new P(function(resolve64) {
24093
+ resolve64(value);
24094
24094
  });
24095
24095
  }
24096
- return new (P || (P = Promise))(function(resolve62, reject) {
24096
+ return new (P || (P = Promise))(function(resolve64, reject) {
24097
24097
  function fulfilled(value) {
24098
24098
  try {
24099
24099
  step(generator.next(value));
@@ -24109,7 +24109,7 @@ var require_client = __commonJS({
24109
24109
  }
24110
24110
  }
24111
24111
  function step(result) {
24112
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
24112
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
24113
24113
  }
24114
24114
  step((generator = generator.apply(thisArg, _arguments || [])).next());
24115
24115
  });
@@ -24417,13 +24417,18 @@ var init_fal = __esm({
24417
24417
  defineProvider({
24418
24418
  id: "fal",
24419
24419
  label: "fal.ai (Seedance 2.0)",
24420
+ displayName: "Seedance 2.0",
24421
+ gateway: "fal.ai",
24422
+ aliases: ["seedance"],
24423
+ models: ["seedance-2.0", "seedance-2.0-fast"],
24424
+ capabilities: ["text-to-video", "image-to-video", "native-audio"],
24420
24425
  apiKey: "fal",
24421
24426
  kinds: ["video"],
24422
24427
  resolverPriority: { video: 1 },
24423
24428
  commandsUnlocked: [
24424
- "generate video -p fal (Seedance 2.0 \u2014 default since v0.57)",
24425
- "generate video -p fal -m fast (lower-latency variant)",
24426
- "generate video -p fal -i <image> (image-to-video)"
24429
+ "generate video -p seedance (Seedance 2.0 via fal.ai \u2014 default since v0.57)",
24430
+ "generate video -p seedance --seedance-model fast (lower-latency variant)",
24431
+ "generate video -p seedance -i <image> (image-to-video)"
24427
24432
  ]
24428
24433
  });
24429
24434
  }
@@ -24687,7 +24692,7 @@ var init_ReplicateProvider = __esm({
24687
24692
  * Sleep helper
24688
24693
  */
24689
24694
  sleep(ms) {
24690
- return new Promise((resolve62) => setTimeout(resolve62, ms));
24695
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
24691
24696
  }
24692
24697
  /**
24693
24698
  * Generate music from text prompt using MusicGen
@@ -25680,7 +25685,7 @@ async function prompt(question, hidden = false) {
25680
25685
  input: process.stdin,
25681
25686
  output: process.stdout
25682
25687
  });
25683
- return new Promise((resolve62) => {
25688
+ return new Promise((resolve64) => {
25684
25689
  if (hidden && process.stdin.isTTY) {
25685
25690
  process.stdout.write(question);
25686
25691
  let input = "";
@@ -25694,7 +25699,7 @@ async function prompt(question, hidden = false) {
25694
25699
  process.stdin.removeListener("data", onData);
25695
25700
  process.stdout.write("\n");
25696
25701
  rl.close();
25697
- resolve62(input);
25702
+ resolve64(input);
25698
25703
  } else if (char === "") {
25699
25704
  process.exit(1);
25700
25705
  } else if (char === "\x7F" || char === "\b") {
@@ -25709,7 +25714,7 @@ async function prompt(question, hidden = false) {
25709
25714
  } else {
25710
25715
  rl.question(question, (answer) => {
25711
25716
  rl.close();
25712
- resolve62(answer);
25717
+ resolve64(answer);
25713
25718
  });
25714
25719
  }
25715
25720
  });
@@ -26040,6 +26045,54 @@ _Browse other named styles: \`vibe scene styles\`_
26040
26045
  ${style ? `_This file was seeded by \`vibe scene init --visual-style "${style.name}"\`._` : `_Seed this file from a named style: \`vibe scene init <dir> --visual-style "<name>"\`._`}
26041
26046
  `;
26042
26047
  }
26048
+ function buildStoryboardMd(name, duration = 12) {
26049
+ return `---
26050
+ title: ${name}
26051
+ duration: ${duration}
26052
+ aspect: 16:9
26053
+ tts: auto
26054
+ imageProvider: openai
26055
+ ---
26056
+
26057
+ # ${name} \u2014 Storyboard
26058
+
26059
+ Edit these beats before running \`vibe build\`. Each beat starts with
26060
+ YAML cues that drive narration, backdrop generation, and timing.
26061
+
26062
+ ## Beat hook \u2014 Hook
26063
+
26064
+ \`\`\`yaml
26065
+ narration: "Introduce the promise in one crisp sentence."
26066
+ backdrop: "Cinematic abstract technology backdrop, precise light, premium editorial feel"
26067
+ duration: 4
26068
+ \`\`\`
26069
+
26070
+ Show the core visual identity immediately. Keep copy short enough for one
26071
+ screen and one spoken breath.
26072
+
26073
+ ## Beat proof \u2014 Proof
26074
+
26075
+ \`\`\`yaml
26076
+ narration: "Show the mechanism or proof point that makes the promise believable."
26077
+ backdrop: "Layered interface details, subtle motion trails, high-contrast product storytelling"
26078
+ duration: 4
26079
+ \`\`\`
26080
+
26081
+ Use this beat for the concrete differentiator: command, workflow, metric, or
26082
+ before/after.
26083
+
26084
+ ## Beat close \u2014 Close
26085
+
26086
+ \`\`\`yaml
26087
+ narration: "Close with the action the viewer should remember."
26088
+ backdrop: "Resolved hero frame, confident final composition, clean negative space"
26089
+ duration: 4
26090
+ \`\`\`
26091
+
26092
+ End on the product name, offer, or command. Avoid adding a new idea in the
26093
+ final beat.
26094
+ `;
26095
+ }
26043
26096
  function buildProjectClaudeMd(name) {
26044
26097
  return `# ${name} \u2014 Scene Authoring Project
26045
26098
 
@@ -26069,7 +26122,7 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
26069
26122
  | **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
26070
26123
  | **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
26071
26124
 
26072
- Install the Hyperframes skills once per machine:
26125
+ Optional: install the upstream Hyperframes skills once per machine when your agent supports skill commands:
26073
26126
 
26074
26127
  \`\`\`bash
26075
26128
  npx skills add heygen-com/hyperframes
@@ -26082,6 +26135,7 @@ the framework-level minimum, not the cinematic craft layer.
26082
26135
  ## Project structure
26083
26136
 
26084
26137
  - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
26138
+ - \`STORYBOARD.md\` \u2014 per-beat narration/backdrop/duration cues for \`vibe build\`
26085
26139
  - \`index.html\` \u2014 root composition (timeline)
26086
26140
  - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
26087
26141
  - \`assets/\` \u2014 shared media (narration audio, images, video)
@@ -26094,6 +26148,7 @@ the framework-level minimum, not the cinematic craft layer.
26094
26148
 
26095
26149
  \`\`\`bash
26096
26150
  vibe scene add <name> --narration "..." --visuals "..." # Author a new scene via AI
26151
+ vibe build # STORYBOARD.md \u2192 narrated MP4
26097
26152
  vibe scene lint # Validate scenes (in-process HF linter)
26098
26153
  vibe scene render # Render to MP4
26099
26154
 
@@ -26138,6 +26193,41 @@ tmp/
26138
26193
  *.log
26139
26194
  `;
26140
26195
  }
26196
+ function isSceneScaffoldProfile(value) {
26197
+ return value === "minimal" || value === "agent" || value === "full";
26198
+ }
26199
+ function describeSceneScaffold(opts) {
26200
+ const dir = resolve5(opts.dir);
26201
+ const profile = opts.profile ?? "full";
26202
+ const groups = {
26203
+ authoring: [
26204
+ resolve5(dir, "STORYBOARD.md"),
26205
+ resolve5(dir, "DESIGN.md"),
26206
+ resolve5(dir, "vibe.project.yaml"),
26207
+ resolve5(dir, ".gitignore")
26208
+ ],
26209
+ render: [],
26210
+ agent: []
26211
+ };
26212
+ if (profile === "full") {
26213
+ groups.render = [
26214
+ resolve5(dir, "index.html"),
26215
+ resolve5(dir, "compositions"),
26216
+ resolve5(dir, "assets"),
26217
+ resolve5(dir, "renders"),
26218
+ resolve5(dir, "hyperframes.json"),
26219
+ resolve5(dir, "meta.json")
26220
+ ];
26221
+ }
26222
+ if (profile === "agent" || profile === "full") {
26223
+ groups.agent = [
26224
+ resolve5(dir, "SKILL.md"),
26225
+ resolve5(dir, "references"),
26226
+ resolve5(dir, "CLAUDE.md")
26227
+ ];
26228
+ }
26229
+ return groups;
26230
+ }
26141
26231
  async function pathExists(p) {
26142
26232
  try {
26143
26233
  await access3(p);
@@ -26152,37 +26242,43 @@ async function scaffoldSceneProject(opts) {
26152
26242
  const aspect = opts.aspect ?? "16:9";
26153
26243
  const duration = opts.duration ?? 10;
26154
26244
  const now = opts.now ?? /* @__PURE__ */ new Date();
26245
+ const profile = opts.profile ?? "full";
26155
26246
  await mkdir2(dir, { recursive: true });
26156
- await mkdir2(resolve5(dir, "compositions"), { recursive: true });
26157
- await mkdir2(resolve5(dir, "assets"), { recursive: true });
26247
+ if (profile === "full") {
26248
+ await mkdir2(resolve5(dir, "compositions"), { recursive: true });
26249
+ await mkdir2(resolve5(dir, "assets"), { recursive: true });
26250
+ await mkdir2(resolve5(dir, "renders"), { recursive: true });
26251
+ }
26158
26252
  const created = [];
26159
26253
  const skipped2 = [];
26160
26254
  const merged = [];
26161
- const hfPath = resolve5(dir, "hyperframes.json");
26162
- const hfDefaults = buildHyperframesConfig();
26163
- if (await pathExists(hfPath)) {
26164
- const existingRaw = await readFile4(hfPath, "utf-8");
26165
- const existing = JSON.parse(existingRaw);
26166
- const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
26167
- await writeFile4(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
26168
- merged.push(hfPath);
26169
- } else {
26170
- await writeFile4(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
26171
- created.push(hfPath);
26172
- }
26173
- const metaPath = resolve5(dir, "meta.json");
26174
- if (await pathExists(metaPath)) {
26175
- skipped2.push(metaPath);
26176
- } else {
26177
- await writeFile4(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
26178
- created.push(metaPath);
26179
- }
26180
- const rootPath = resolve5(dir, "index.html");
26181
- if (await pathExists(rootPath)) {
26182
- skipped2.push(rootPath);
26183
- } else {
26184
- await writeFile4(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
26185
- created.push(rootPath);
26255
+ if (profile === "full") {
26256
+ const hfPath = resolve5(dir, "hyperframes.json");
26257
+ const hfDefaults = buildHyperframesConfig();
26258
+ if (await pathExists(hfPath)) {
26259
+ const existingRaw = await readFile4(hfPath, "utf-8");
26260
+ const existing = JSON.parse(existingRaw);
26261
+ const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
26262
+ await writeFile4(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
26263
+ merged.push(hfPath);
26264
+ } else {
26265
+ await writeFile4(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
26266
+ created.push(hfPath);
26267
+ }
26268
+ const metaPath = resolve5(dir, "meta.json");
26269
+ if (await pathExists(metaPath)) {
26270
+ skipped2.push(metaPath);
26271
+ } else {
26272
+ await writeFile4(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
26273
+ created.push(metaPath);
26274
+ }
26275
+ const rootPath = resolve5(dir, "index.html");
26276
+ if (await pathExists(rootPath)) {
26277
+ skipped2.push(rootPath);
26278
+ } else {
26279
+ await writeFile4(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
26280
+ created.push(rootPath);
26281
+ }
26186
26282
  }
26187
26283
  const vibePath = resolve5(dir, "vibe.project.yaml");
26188
26284
  if (await pathExists(vibePath)) {
@@ -26192,12 +26288,14 @@ async function scaffoldSceneProject(opts) {
26192
26288
  await writeFile4(vibePath, (0, import_yaml2.stringify)(cfg), "utf-8");
26193
26289
  created.push(vibePath);
26194
26290
  }
26195
- const claudePath = resolve5(dir, "CLAUDE.md");
26196
- if (await pathExists(claudePath)) {
26197
- skipped2.push(claudePath);
26198
- } else {
26199
- await writeFile4(claudePath, buildProjectClaudeMd(name), "utf-8");
26200
- created.push(claudePath);
26291
+ if (profile === "agent" || profile === "full") {
26292
+ const claudePath = resolve5(dir, "CLAUDE.md");
26293
+ if (await pathExists(claudePath)) {
26294
+ skipped2.push(claudePath);
26295
+ } else {
26296
+ await writeFile4(claudePath, buildProjectClaudeMd(name), "utf-8");
26297
+ created.push(claudePath);
26298
+ }
26201
26299
  }
26202
26300
  const designPath = resolve5(dir, "DESIGN.md");
26203
26301
  if (await pathExists(designPath)) {
@@ -26210,6 +26308,13 @@ async function scaffoldSceneProject(opts) {
26210
26308
  );
26211
26309
  created.push(designPath);
26212
26310
  }
26311
+ const storyboardPath = resolve5(dir, "STORYBOARD.md");
26312
+ if (await pathExists(storyboardPath)) {
26313
+ skipped2.push(storyboardPath);
26314
+ } else {
26315
+ await writeFile4(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
26316
+ created.push(storyboardPath);
26317
+ }
26213
26318
  const gitignorePath = resolve5(dir, ".gitignore");
26214
26319
  if (await pathExists(gitignorePath)) {
26215
26320
  skipped2.push(gitignorePath);
@@ -26217,7 +26322,7 @@ async function scaffoldSceneProject(opts) {
26217
26322
  await writeFile4(gitignorePath, buildSceneGitignore(), "utf-8");
26218
26323
  created.push(gitignorePath);
26219
26324
  }
26220
- return { created, skipped: skipped2, merged };
26325
+ return { created, skipped: skipped2, merged, groups: describeSceneScaffold({ dir, profile }) };
26221
26326
  }
26222
26327
  var import_yaml2, ASPECT_DIMS;
26223
26328
  var init_scene_project = __esm({
@@ -27157,7 +27262,9 @@ function buildAudioMuxFilter(audios) {
27157
27262
  const inputIdx = i + 1;
27158
27263
  const delayMs = Math.max(0, Math.round(a.absoluteStart * 1e3));
27159
27264
  const volume = Number.isFinite(a.volume) ? a.volume : 1;
27160
- const trimSec = Math.max(0, a.clipDurationCap);
27265
+ const durationHint = typeof a.durationHint === "number" && Number.isFinite(a.durationHint) ? Math.max(0, a.durationHint) : null;
27266
+ const clipCap = Math.max(0, a.clipDurationCap);
27267
+ const trimSec = durationHint === null ? clipCap : Math.min(durationHint, clipCap);
27161
27268
  const label = `a${i}`;
27162
27269
  const stage = [
27163
27270
  `[${inputIdx}:a]`,
@@ -27419,6 +27526,57 @@ var init_scene_render = __esm({
27419
27526
  }
27420
27527
  });
27421
27528
 
27529
+ // src/utils/audio.ts
27530
+ async function getAudioDuration(filePath) {
27531
+ try {
27532
+ return await ffprobeDuration(filePath);
27533
+ } catch (error) {
27534
+ const message = error instanceof Error ? error.message : String(error);
27535
+ throw new Error(`Failed to get audio duration: ${message}`);
27536
+ }
27537
+ }
27538
+ async function getVideoDuration(filePath) {
27539
+ try {
27540
+ return await ffprobeDuration(filePath);
27541
+ } catch (error) {
27542
+ const message = error instanceof Error ? error.message : String(error);
27543
+ throw new Error(`Failed to get video duration: ${message}`);
27544
+ }
27545
+ }
27546
+ async function extendVideoNaturally(videoPath, targetDuration, outputPath) {
27547
+ const videoDuration = await getVideoDuration(videoPath);
27548
+ const ratio = targetDuration / videoDuration;
27549
+ if (ratio <= 1) {
27550
+ const { copyFile: copyFile4 } = await import("node:fs/promises");
27551
+ await copyFile4(videoPath, outputPath);
27552
+ return;
27553
+ }
27554
+ if (ratio <= 1.15) {
27555
+ const slowFactor = (1 / ratio).toFixed(4);
27556
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS`, "-an", outputPath]);
27557
+ } else if (ratio <= 1.4) {
27558
+ const slowFactor = (1 / ratio).toFixed(4);
27559
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
27560
+ } else {
27561
+ const slowRatio = 0.7;
27562
+ const slowedDuration = videoDuration / slowRatio;
27563
+ const freezeDuration = targetDuration - slowedDuration;
27564
+ if (freezeDuration <= 0) {
27565
+ const slowFactor = (1 / ratio).toFixed(4);
27566
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
27567
+ } else {
27568
+ const slowFactor = (1 / slowRatio).toFixed(4);
27569
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS,tpad=stop_mode=clone:stop_duration=${freezeDuration.toFixed(2)}`, "-an", outputPath]);
27570
+ }
27571
+ }
27572
+ }
27573
+ var init_audio = __esm({
27574
+ "src/utils/audio.ts"() {
27575
+ "use strict";
27576
+ init_exec_safe();
27577
+ }
27578
+ });
27579
+
27422
27580
  // src/commands/_shared/hf-skill-bundle/bundle-content.ts
27423
27581
  var SKILL_MD, HOUSE_STYLE_MD, MOTION_PRINCIPLES_MD, TYPOGRAPHY_MD, TRANSITIONS_MD;
27424
27582
  var init_bundle_content = __esm({
@@ -29020,7 +29178,7 @@ async function getComposePrompts(opts) {
29020
29178
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
29021
29179
  }
29022
29180
  if (!existsSync6(storyboardPath)) {
29023
- return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` first.`);
29181
+ return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`);
29024
29182
  }
29025
29183
  if (!existsSync6(skillPath)) {
29026
29184
  warnings.push(
@@ -29256,7 +29414,10 @@ async function executeSceneBuild(opts) {
29256
29414
  const mode = resolveSceneBuildMode(opts);
29257
29415
  const storyboardPath = join7(projectDir, "STORYBOARD.md");
29258
29416
  if (!existsSync8(storyboardPath)) {
29259
- return failBeforePrimitives(`STORYBOARD.md not found at ${storyboardPath}`, startedAt);
29417
+ return failBeforePrimitives(
29418
+ `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
29419
+ startedAt
29420
+ );
29260
29421
  }
29261
29422
  const storyboardMd = await readFile10(storyboardPath, "utf-8");
29262
29423
  const parsed = parseStoryboard(storyboardMd);
@@ -29336,6 +29497,13 @@ async function executeSceneBuild(opts) {
29336
29497
  }
29337
29498
  composeData = composeResult.data;
29338
29499
  }
29500
+ if (!existsSync8(join7(projectDir, "index.html"))) {
29501
+ await scaffoldSceneProject({
29502
+ dir: projectDir,
29503
+ name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
29504
+ profile: "full"
29505
+ });
29506
+ }
29339
29507
  await syncRootClipReferences(parsed.beats, projectDir, beatOutcomes);
29340
29508
  let outputPath;
29341
29509
  let renderResult;
@@ -29434,6 +29602,7 @@ async function dispatchBackdrop(beat, ctx) {
29434
29602
  ctx.onProgress({ type: "backdrop-cached", beatId: beat.id, path: rel });
29435
29603
  return { status: "cached", path: rel };
29436
29604
  }
29605
+ loadSceneBuildEnv(ctx.projectDir);
29437
29606
  const apiKey = process.env.OPENAI_API_KEY ?? "";
29438
29607
  if (!apiKey) {
29439
29608
  const error = "OPENAI_API_KEY not set \u2014 cannot dispatch backdrop";
@@ -29462,6 +29631,18 @@ async function dispatchBackdrop(beat, ctx) {
29462
29631
  });
29463
29632
  return { status: "generated", path: rel };
29464
29633
  }
29634
+ function loadSceneBuildEnv(projectDir) {
29635
+ (0, import_dotenv2.config)({ path: join7(projectDir, ".env"), quiet: true });
29636
+ (0, import_dotenv2.config)({ path: resolve12(process.cwd(), ".env"), quiet: true });
29637
+ let dir = process.cwd();
29638
+ while (dir !== dirname4(dir)) {
29639
+ if (existsSync8(join7(dir, "pnpm-workspace.yaml"))) {
29640
+ (0, import_dotenv2.config)({ path: join7(dir, ".env"), quiet: true });
29641
+ return;
29642
+ }
29643
+ dir = dirname4(dir);
29644
+ }
29645
+ }
29465
29646
  async function skipped(kind, beatId, reason, ctx) {
29466
29647
  ctx.onProgress({ type: `${kind}-skipped`, beatId, reason });
29467
29648
  return { status: "skipped" };
@@ -29492,20 +29673,24 @@ async function syncRootClipReferences(beats, projectDir, outcomes) {
29492
29673
  const clipLines = [];
29493
29674
  const audioLines = [];
29494
29675
  for (const beat of beats) {
29495
- const duration = beat.duration ?? 3;
29676
+ const outcome = outcomes.find((o) => o.beatId === beat.id);
29677
+ const duration = await resolveBeatDuration({
29678
+ beatDuration: beat.duration,
29679
+ narrationPath: outcome?.narrationPath,
29680
+ projectDir
29681
+ });
29496
29682
  const compositionId = `scene-${beat.id}`;
29497
29683
  clipLines.push(
29498
29684
  ` <div class="clip" data-composition-id="${compositionId}" data-composition-src="compositions/${compositionId}.html" data-start="${cursor}" data-duration="${duration}" data-track-index="0"></div>`
29499
29685
  );
29500
- const outcome = outcomes.find((o) => o.beatId === beat.id);
29501
29686
  if (outcome?.narrationPath) {
29502
29687
  audioLines.push(
29503
- ` <audio src="${outcome.narrationPath}" data-start="${cursor}" data-duration="${duration}" data-track-index="2"></audio>`
29688
+ ` <audio id="narration-${beat.id}" src="${outcome.narrationPath}" data-start="${cursor}" data-duration="${duration}" data-track-index="2"></audio>`
29504
29689
  );
29505
29690
  }
29506
29691
  cursor += duration;
29507
29692
  }
29508
- const totalDuration = cursor;
29693
+ const totalDuration = Number(cursor.toFixed(2));
29509
29694
  const block = " <!-- vibe-scene-build: clip refs (auto-generated; safe to re-run) -->\n" + clipLines.join("\n") + (audioLines.length > 0 ? "\n" + audioLines.join("\n") : "") + "\n <!-- /vibe-scene-build -->";
29510
29695
  let next;
29511
29696
  const markerRe = /\n? *<!-- vibe-scene-build: clip refs.*?<!-- \/vibe-scene-build -->/s;
@@ -29523,77 +29708,40 @@ ${block}
29523
29708
  }
29524
29709
  }
29525
29710
  next = next.replace(
29526
- /(id="root"[\s\S]*?data-duration=")(\d+(?:\.\d+)?)(")/,
29711
+ /(id="root"[\s\S]*?data-duration=")([^"]*)(")/,
29527
29712
  `$1${totalDuration}$3`
29528
29713
  );
29529
29714
  if (next !== html) {
29530
29715
  await writeFile7(rootPath, next, "utf-8");
29531
29716
  }
29532
29717
  }
29718
+ async function resolveBeatDuration(opts) {
29719
+ const storyboardMin = opts.beatDuration ?? 3;
29720
+ if (!opts.narrationPath) return Number(storyboardMin.toFixed(2));
29721
+ try {
29722
+ const audioDuration = await getAudioDuration(join7(opts.projectDir, opts.narrationPath));
29723
+ return Number(Math.max(storyboardMin, audioDuration + 0.5).toFixed(2));
29724
+ } catch {
29725
+ return Number(storyboardMin.toFixed(2));
29726
+ }
29727
+ }
29728
+ var import_dotenv2;
29533
29729
  var init_scene_build = __esm({
29534
29730
  "src/commands/_shared/scene-build.ts"() {
29535
29731
  "use strict";
29732
+ import_dotenv2 = __toESM(require_main(), 1);
29536
29733
  init_dist2();
29734
+ init_audio();
29537
29735
  init_compose_scenes_skills();
29538
29736
  init_compose_prompts();
29539
29737
  init_agent_host_detect();
29540
29738
  init_scene_render();
29541
29739
  init_storyboard_parse();
29740
+ init_scene_project();
29542
29741
  init_tts_resolve();
29543
29742
  }
29544
29743
  });
29545
29744
 
29546
- // src/utils/audio.ts
29547
- async function getAudioDuration(filePath) {
29548
- try {
29549
- return await ffprobeDuration(filePath);
29550
- } catch (error) {
29551
- const message = error instanceof Error ? error.message : String(error);
29552
- throw new Error(`Failed to get audio duration: ${message}`);
29553
- }
29554
- }
29555
- async function getVideoDuration(filePath) {
29556
- try {
29557
- return await ffprobeDuration(filePath);
29558
- } catch (error) {
29559
- const message = error instanceof Error ? error.message : String(error);
29560
- throw new Error(`Failed to get video duration: ${message}`);
29561
- }
29562
- }
29563
- async function extendVideoNaturally(videoPath, targetDuration, outputPath) {
29564
- const videoDuration = await getVideoDuration(videoPath);
29565
- const ratio = targetDuration / videoDuration;
29566
- if (ratio <= 1) {
29567
- const { copyFile: copyFile4 } = await import("node:fs/promises");
29568
- await copyFile4(videoPath, outputPath);
29569
- return;
29570
- }
29571
- if (ratio <= 1.15) {
29572
- const slowFactor = (1 / ratio).toFixed(4);
29573
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS`, "-an", outputPath]);
29574
- } else if (ratio <= 1.4) {
29575
- const slowFactor = (1 / ratio).toFixed(4);
29576
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
29577
- } else {
29578
- const slowRatio = 0.7;
29579
- const slowedDuration = videoDuration / slowRatio;
29580
- const freezeDuration = targetDuration - slowedDuration;
29581
- if (freezeDuration <= 0) {
29582
- const slowFactor = (1 / ratio).toFixed(4);
29583
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
29584
- } else {
29585
- const slowFactor = (1 / slowRatio).toFixed(4);
29586
- await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS,tpad=stop_mode=clone:stop_duration=${freezeDuration.toFixed(2)}`, "-an", outputPath]);
29587
- }
29588
- }
29589
- }
29590
- var init_audio = __esm({
29591
- "src/utils/audio.ts"() {
29592
- "use strict";
29593
- init_exec_safe();
29594
- }
29595
- });
29596
-
29597
29745
  // src/commands/_shared/install-skill.ts
29598
29746
  import { mkdir as mkdir6, writeFile as writeFile8 } from "node:fs/promises";
29599
29747
  import { existsSync as existsSync9 } from "node:fs";
@@ -29730,6 +29878,30 @@ function validateVisualStyle(value) {
29730
29878
  }
29731
29879
  return found;
29732
29880
  }
29881
+ function formatSceneInitProfile(profile) {
29882
+ if (profile === "minimal") return "authoring files only; build will add render scaffold when needed";
29883
+ if (profile === "agent") return "authoring files plus local composition rules for host agents";
29884
+ return "complete authoring, agent, and render scaffold";
29885
+ }
29886
+ function printSceneInitDryRun(opts) {
29887
+ console.log();
29888
+ console.log(source_default.bold.cyan("VibeFrame Scene Init - dry run"));
29889
+ console.log(source_default.dim("-".repeat(60)));
29890
+ console.log(` Project: ${source_default.bold(opts.dir)}`);
29891
+ console.log(` Name: ${source_default.bold(opts.name)}`);
29892
+ console.log(` Profile: ${source_default.bold(opts.profile)} ${source_default.dim(formatSceneInitProfile(opts.profile))}`);
29893
+ console.log(` Aspect: ${opts.aspect}`);
29894
+ console.log(` Duration: ${opts.duration}s`);
29895
+ console.log(` Visual style: ${opts.visualStyleName ?? "none"}`);
29896
+ console.log();
29897
+ console.log(source_default.bold.cyan("Files that would be prepared"));
29898
+ console.log(source_default.dim("-".repeat(60)));
29899
+ for (const file of opts.groups.authoring) console.log(` authoring ${file}`);
29900
+ for (const file of opts.groups.agent) console.log(` agent ${file}`);
29901
+ for (const file of opts.groups.render) console.log(` render ${file}`);
29902
+ console.log();
29903
+ console.log(source_default.dim("No files were written."));
29904
+ }
29733
29905
  async function pathExists2(p) {
29734
29906
  try {
29735
29907
  await access4(p);
@@ -30041,7 +30213,7 @@ function formatPrimitiveStatus(status, path2) {
30041
30213
  return status;
30042
30214
  }
30043
30215
  }
30044
- var import_yaml5, VALID_ASPECTS2, sceneCommand, VALID_INSTALL_SKILL_HOSTS, VALID_FPS, VALID_QUALITIES, VALID_FORMATS;
30216
+ var import_yaml5, VALID_ASPECTS2, VALID_SCENE_INIT_PROFILES, sceneCommand, VALID_INSTALL_SKILL_HOSTS, VALID_FPS, VALID_QUALITIES, VALID_FORMATS;
30045
30217
  var init_scene = __esm({
30046
30218
  "src/commands/scene.ts"() {
30047
30219
  "use strict";
@@ -30064,7 +30236,8 @@ var init_scene = __esm({
30064
30236
  init_install_skill();
30065
30237
  init_compose_prompts();
30066
30238
  VALID_ASPECTS2 = ["16:9", "9:16", "1:1", "4:5"];
30067
- sceneCommand = new Command("scene").description("Author and render per-scene HTML compositions (Hyperframes backend)").addHelpText("after", `
30239
+ VALID_SCENE_INIT_PROFILES = ["minimal", "agent", "full"];
30240
+ sceneCommand = new Command("scene").description("Advanced scene commands for VibeFrame video projects").addHelpText("after", `
30068
30241
  Examples:
30069
30242
  $ vibe scene init my-video # Scaffold a new project
30070
30243
  $ vibe scene init my-video -r 9:16 -d 30 # Vertical 30s project
@@ -30072,22 +30245,32 @@ Examples:
30072
30245
  --headline "Welcome to VibeFrame" # Headline-only scene
30073
30246
  $ vibe scene add overview --narration "VibeFrame turns scripts into video." \\
30074
30247
  --visuals "studio desk, soft lighting" # AI narration + image
30075
- $ vibe scene lint # Validate every scene against Hyperframes rules
30248
+ $ vibe scene lint # Validate every scene against composition rules
30076
30249
  $ vibe scene lint --fix # Auto-fix mechanical issues (e.g. missing class="clip")
30077
30250
  $ vibe scene lint --json # Structured output for agent loops
30078
30251
  $ vibe scene render # Render to renders/<name>-<timestamp>.mp4
30079
30252
  $ vibe scene render -o demo.mp4 --quality high # Custom output path + quality
30080
30253
  $ vibe scene render --fps 60 --format webm # 60fps WebM render
30081
30254
 
30082
- A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
30255
+ Most users can start with \`vibe init\`, \`vibe build\`, and \`vibe render\`.
30256
+ This namespace exposes lower-level scene authoring and rendering controls.
30083
30257
  Run 'vibe schema scene.<command>' for structured parameter info.`);
30084
- sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing Hyperframes project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
30258
+ sceneCommand.command("init").description("Scaffold a new scene project (or safely augment an existing project)").argument("<dir>", "Project directory (created if it doesn't exist)").option("-n, --name <name>", "Project name (defaults to directory basename)").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default root composition duration (seconds)", "10").option("--visual-style <name>", `Seed DESIGN.md from a named style (browse via \`vibe scene styles\`). E.g. "Swiss Pulse"`).option("--profile <profile>", "Scene profile: minimal (storyboard/design only), agent (recommended), full (render scaffold upfront)", "agent").option("--dry-run", "Preview parameters without writing files").action(async (dir, options) => {
30085
30259
  const startedAt = Date.now();
30086
30260
  const aspect = validateAspect(options.ratio);
30087
30261
  const duration = validateDuration(options.duration);
30088
30262
  const name = options.name ?? basename4(dir.replace(/\/+$/, ""));
30089
30263
  const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
30264
+ const profile = String(options.profile ?? "agent");
30265
+ if (!isSceneScaffoldProfile(profile)) {
30266
+ exitWithError(usageError(`Invalid --profile: ${profile}`, `Must be one of: ${VALID_SCENE_INIT_PROFILES.join(", ")}`));
30267
+ }
30268
+ const groups = describeSceneScaffold({ dir, profile });
30090
30269
  if (options.dryRun) {
30270
+ if (!isJsonMode() && !isQuietMode()) {
30271
+ printSceneInitDryRun({ dir, name, aspect, duration, visualStyleName: visualStyle?.name ?? null, profile, groups });
30272
+ return;
30273
+ }
30091
30274
  outputSuccess({
30092
30275
  command: "scene init",
30093
30276
  startedAt,
@@ -30098,23 +30281,25 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30098
30281
  name,
30099
30282
  aspect,
30100
30283
  duration,
30101
- visualStyle: visualStyle?.name ?? null
30102
- }
30284
+ visualStyle: visualStyle?.name ?? null,
30285
+ profile
30286
+ },
30287
+ groups
30103
30288
  }
30104
30289
  });
30105
30290
  return;
30106
30291
  }
30107
- const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
30292
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
30108
30293
  try {
30109
- const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
30294
+ const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle, profile });
30110
30295
  const detectedIds = detectedAgentHosts().map((h) => h.id);
30111
30296
  const skillHosts = deriveInstallHosts(detectedIds);
30112
30297
  const projectAbs = resolve14(dir);
30113
- const skillResult = await installHyperframesSkill({
30298
+ const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
30114
30299
  projectDir: projectAbs,
30115
30300
  hosts: skillHosts
30116
- });
30117
- if (isJsonMode()) {
30301
+ }) : { success: true, files: [], bundleVersion: "not-installed" };
30302
+ if (isJsonMode() || isQuietMode()) {
30118
30303
  outputSuccess({
30119
30304
  command: "scene init",
30120
30305
  startedAt,
@@ -30124,16 +30309,27 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30124
30309
  aspect,
30125
30310
  duration,
30126
30311
  visualStyle: visualStyle?.name ?? null,
30312
+ profile,
30127
30313
  created: result.created,
30128
30314
  merged: result.merged,
30129
30315
  skipped: result.skipped,
30316
+ groups: result.groups,
30130
30317
  skillFiles: skillResult.files,
30131
30318
  skillBundleVersion: skillResult.bundleVersion
30132
30319
  }
30133
30320
  });
30134
30321
  return;
30135
30322
  }
30136
- spinner2?.succeed(source_default.green(`Scene project ready: ${dir}`));
30323
+ spinner2?.succeed(source_default.green(`Video project ready: ${dir}`));
30324
+ console.log();
30325
+ console.log(source_default.bold.cyan("Edit first"));
30326
+ console.log(source_default.dim("\u2500".repeat(60)));
30327
+ console.log(` ${source_default.bold("STORYBOARD.md")} ${source_default.dim("# beats: narration, backdrop, minimum duration")}`);
30328
+ console.log(` ${source_default.bold("DESIGN.md")} ${source_default.dim("# palette, typography, motion rules")}`);
30329
+ console.log();
30330
+ console.log(source_default.bold.cyan("Profile"));
30331
+ console.log(source_default.dim("\u2500".repeat(60)));
30332
+ console.log(` ${source_default.bold(profile)} ${source_default.dim(formatSceneInitProfile(profile))}`);
30137
30333
  console.log();
30138
30334
  console.log(source_default.bold.cyan("Files"));
30139
30335
  console.log(source_default.dim("\u2500".repeat(60)));
@@ -30144,7 +30340,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30144
30340
  const skillSkipped = skillResult.files.filter((f) => f.status === "skipped-exists");
30145
30341
  if (skillWritten.length + skillSkipped.length > 0) {
30146
30342
  console.log();
30147
- console.log(source_default.bold.cyan("Hyperframes skill"));
30343
+ console.log(source_default.bold.cyan("Composition rules"));
30148
30344
  console.log(source_default.dim("\u2500".repeat(60)));
30149
30345
  for (const f of skillWritten) console.log(source_default.green(" +"), f.path);
30150
30346
  for (const f of skillSkipped) console.log(source_default.dim(" \xB7"), f.path, source_default.dim("(kept existing)"));
@@ -30158,10 +30354,14 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30158
30354
  } else {
30159
30355
  console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
30160
30356
  }
30161
- console.log(` ${source_default.dim("Your agent now has Hyperframes rules in")} ${source_default.cyan("SKILL.md")} ${source_default.dim("\u2014 ask it to author scene HTML directly.")}`);
30357
+ if (profile === "agent" || profile === "full") {
30358
+ console.log(` ${source_default.dim("Your agent now has composition rules in")} ${source_default.cyan("SKILL.md")} ${source_default.dim("\u2014 ask it to author scene HTML directly.")}`);
30359
+ } else {
30360
+ console.log(` ${source_default.cyan("vibe scene install-skill")} ${source_default.dim("# add agent authoring rules later")}`);
30361
+ }
30162
30362
  console.log(` ${source_default.cyan("vibe scene add")} <name> ${source_default.dim("# fallback: 5-preset emit (no agent)")}`);
30163
- console.log(` ${source_default.cyan("vibe scene lint")} ${source_default.dim("# validate HTML")}`);
30164
- console.log(` ${source_default.cyan("vibe scene render")} ${source_default.dim("# render to MP4")}`);
30363
+ console.log(` ${source_default.cyan("vibe build")} ${source_default.dim("# build STORYBOARD.md into scenes/assets")}`);
30364
+ console.log(` ${source_default.cyan("vibe render")} ${source_default.dim("# render to video")}`);
30165
30365
  } catch (error) {
30166
30366
  spinner2?.fail("Failed to scaffold scene project");
30167
30367
  const msg = error instanceof Error ? error.message : String(error);
@@ -30449,7 +30649,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30449
30649
  exitWithError(generalError(msg));
30450
30650
  }
30451
30651
  });
30452
- sceneCommand.command("lint").description("Validate scene HTML against Hyperframes rules (in-process, no Chrome required)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("--fix", 'Apply mechanical auto-fixes (currently: missing class="clip")').action(async (root, options) => {
30652
+ sceneCommand.command("lint").description("Validate scene HTML against composition rules (in-process, no Chrome required)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("--fix", 'Apply mechanical auto-fixes (currently: missing class="clip")').action(async (root, options) => {
30453
30653
  const startedAt = Date.now();
30454
30654
  const projectDir = resolve14(options.project);
30455
30655
  if (!await rootExists(projectDir, root)) {
@@ -30705,7 +30905,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30705
30905
  }
30706
30906
  }
30707
30907
  console.log();
30708
- console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe scene build` to lint + render."));
30908
+ console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe build` to lint + render."));
30709
30909
  console.log(source_default.dim("Or pass `--mode batch` to use the internal LLM compose path instead."));
30710
30910
  return;
30711
30911
  }
@@ -32463,16 +32663,16 @@ async function prompt2(question) {
32463
32663
  input,
32464
32664
  output: process.stdout
32465
32665
  });
32466
- return new Promise((resolve62) => {
32666
+ return new Promise((resolve64) => {
32467
32667
  rl.question(question, (answer) => {
32468
32668
  rl.close();
32469
- resolve62(answer);
32669
+ resolve64(answer);
32470
32670
  });
32471
32671
  });
32472
32672
  }
32473
32673
  async function promptHidden(question) {
32474
32674
  const input = getTTYInputStream();
32475
- return new Promise((resolve62) => {
32675
+ return new Promise((resolve64) => {
32476
32676
  process.stdout.write(question);
32477
32677
  let value = "";
32478
32678
  if (typeof input.setRawMode === "function") {
@@ -32484,7 +32684,7 @@ async function promptHidden(question) {
32484
32684
  input.setRawMode(false);
32485
32685
  input.removeListener("data", onData);
32486
32686
  process.stdout.write("\n");
32487
- resolve62(value);
32687
+ resolve64(value);
32488
32688
  } else if (char === "") {
32489
32689
  input.setRawMode(false);
32490
32690
  process.stdout.write("\n");
@@ -32504,7 +32704,7 @@ async function promptHidden(question) {
32504
32704
  const rl = createInterface2({ input, output: process.stdout });
32505
32705
  rl.question("", (answer) => {
32506
32706
  rl.close();
32507
- resolve62(answer);
32707
+ resolve64(answer);
32508
32708
  });
32509
32709
  }
32510
32710
  });
@@ -32512,7 +32712,7 @@ async function promptHidden(question) {
32512
32712
  async function promptSelect(_question, options, defaultIndex = 0) {
32513
32713
  const input = getTTYInputStream();
32514
32714
  if (typeof input.setRawMode === "function") {
32515
- return new Promise((resolve62) => {
32715
+ return new Promise((resolve64) => {
32516
32716
  let selected = defaultIndex;
32517
32717
  const render = () => {
32518
32718
  if (renderCount > 0) {
@@ -32536,7 +32736,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
32536
32736
  input.setRawMode(false);
32537
32737
  input.removeListener("data", onData);
32538
32738
  process.stdout.write("\n");
32539
- resolve62(selected);
32739
+ resolve64(selected);
32540
32740
  } else if (char === "") {
32541
32741
  input.setRawMode(false);
32542
32742
  process.stdout.write("\n");
@@ -32553,7 +32753,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
32553
32753
  input.removeListener("data", onData);
32554
32754
  render();
32555
32755
  process.stdout.write("\n");
32556
- resolve62(selected);
32756
+ resolve64(selected);
32557
32757
  }
32558
32758
  };
32559
32759
  input.on("data", onData);
@@ -35343,7 +35543,7 @@ var init_dist3 = __esm({
35343
35543
  import { writeFile as writeFile23, unlink as unlink2, rename as rename2 } from "node:fs/promises";
35344
35544
  import { resolve as resolve27, basename as basename7 } from "node:path";
35345
35545
  function sleep(ms) {
35346
- return new Promise((resolve62) => setTimeout(resolve62, ms));
35546
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
35347
35547
  }
35348
35548
  async function uploadToImgbb(imageBuffer, apiKey) {
35349
35549
  try {
@@ -36076,9 +36276,14 @@ var init_ai_script_pipeline = __esm({
36076
36276
  import { resolve as resolve29 } from "node:path";
36077
36277
  import { readFile as readFile17, writeFile as writeFile25 } from "node:fs/promises";
36078
36278
  function registerVideoCommand(parent) {
36079
- parent.command("video").alias("vid").description("Generate video using AI (Kling, Runway, Veo, or Grok)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: fal (Seedance 2.0, default when FAL_KEY set), grok, kling, runway, veo").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option("-d, --duration <sec>", "Duration: 5 or 10 seconds", "5").option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("-s, --seed <number>", "Random seed for reproducibility (Runway only)").option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("-n, --negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
36279
+ parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance.").option("-k, --api-key <key>", "API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
36280
+ "-d, --duration <sec>",
36281
+ "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
36282
+ "5"
36283
+ ).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("-s, --seed <number>", "Random seed for reproducibility (Runway only)").option("-m, --mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("-n, --negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
36080
36284
  Examples:
36081
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default)
36285
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
36286
+ $ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
36082
36287
  $ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
36083
36288
  $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
36084
36289
  $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
@@ -36104,12 +36309,13 @@ Examples:
36104
36309
  if (options.output) {
36105
36310
  validateOutputPath(options.output);
36106
36311
  }
36107
- const validProviders = ["runway", "kling", "veo", "grok", "fal"];
36312
+ const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
36108
36313
  const videoEnvMap = {
36109
36314
  grok: "XAI_API_KEY",
36110
36315
  veo: "GOOGLE_API_KEY",
36111
36316
  kling: "KLING_API_KEY",
36112
36317
  runway: "RUNWAY_API_SECRET",
36318
+ seedance: "FAL_KEY",
36113
36319
  fal: "FAL_KEY"
36114
36320
  };
36115
36321
  let provider;
@@ -36119,7 +36325,7 @@ Examples:
36119
36325
  exitWithError(
36120
36326
  usageError(
36121
36327
  `Invalid provider: ${provider}`,
36122
- `Available providers: ${validProviders.join(", ")}`
36328
+ "Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
36123
36329
  )
36124
36330
  );
36125
36331
  }
@@ -36182,14 +36388,15 @@ Examples:
36182
36388
  data: {
36183
36389
  params: {
36184
36390
  prompt: prompt3,
36185
- provider,
36391
+ provider: provider === "fal" ? "seedance" : provider,
36186
36392
  duration: options.duration,
36187
36393
  ratio: options.ratio,
36188
36394
  image: options.image,
36189
36395
  mode: options.mode,
36190
36396
  negative: options.negative,
36191
36397
  resolution: options.resolution,
36192
- veoModel: options.veoModel
36398
+ veoModel: options.veoModel,
36399
+ seedanceModel: options.seedanceModel
36193
36400
  }
36194
36401
  }
36195
36402
  });
@@ -36200,6 +36407,7 @@ Examples:
36200
36407
  kling: "KLING_API_KEY",
36201
36408
  veo: "GOOGLE_API_KEY",
36202
36409
  grok: "XAI_API_KEY",
36410
+ seedance: "FAL_KEY",
36203
36411
  fal: "FAL_KEY"
36204
36412
  };
36205
36413
  const providerNameMap = {
@@ -36207,7 +36415,8 @@ Examples:
36207
36415
  kling: "Kling",
36208
36416
  veo: "Veo",
36209
36417
  grok: "Grok",
36210
- fal: "fal.ai (Seedance 2.0)"
36418
+ seedance: "Seedance 2.0 via fal.ai",
36419
+ fal: "Seedance 2.0 via fal.ai"
36211
36420
  };
36212
36421
  const envKey = envKeyMap[provider];
36213
36422
  const providerName = providerNameMap[provider];
@@ -36426,15 +36635,15 @@ Examples:
36426
36635
  },
36427
36636
  3e5
36428
36637
  );
36429
- } else if (provider === "fal") {
36638
+ } else if (provider === "fal" || provider === "seedance") {
36430
36639
  const fal = new FalProvider();
36431
36640
  await fal.initialize({ apiKey });
36432
36641
  let falImage = referenceImage;
36433
36642
  if (falImage && falImage.startsWith("data:")) {
36434
- spinner2.text = "Uploading image to ImgBB for fal...";
36643
+ spinner2.text = "Uploading image to ImgBB for Seedance...";
36435
36644
  const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
36436
36645
  if (!imgbbKey) {
36437
- spinner2.fail("ImgBB API key required for fal image-to-video");
36646
+ spinner2.fail("ImgBB API key required for Seedance image-to-video");
36438
36647
  exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
36439
36648
  }
36440
36649
  const base64Data = falImage.split(",")[1];
@@ -36446,8 +36655,9 @@ Examples:
36446
36655
  }
36447
36656
  falImage = uploadResult.url;
36448
36657
  }
36449
- spinner2.text = "Generating video with Seedance 2.0 (this may take 1-3 minutes)...";
36450
- const falModel = options.model === "fast" ? "seedance-2.0-fast" : "seedance-2.0";
36658
+ spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
36659
+ const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
36660
+ const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
36451
36661
  result = await fal.generateVideo(prompt3, {
36452
36662
  prompt: prompt3,
36453
36663
  referenceImage: falImage,
@@ -36572,7 +36782,8 @@ var init_generate = __esm({
36572
36782
  Examples:
36573
36783
  $ vibe generate image "a sunset over the ocean" -o sunset.png
36574
36784
  $ vibe generate image "logo design" -o logo.png -p openai
36575
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default, native audio)
36785
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
36786
+ $ vibe generate video "city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
36576
36787
  $ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
36577
36788
  $ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
36578
36789
  $ vibe generate speech "Hello world" -o hello.mp3
@@ -36582,7 +36793,8 @@ Examples:
36582
36793
  API Keys (per provider):
36583
36794
  GOOGLE_API_KEY Image (default), Veo video
36584
36795
  OPENAI_API_KEY Image (-p openai)
36585
- XAI_API_KEY Grok image/video (default video)
36796
+ FAL_KEY Seedance video (-p seedance, default video)
36797
+ XAI_API_KEY Grok image/video
36586
36798
  KLING_API_KEY Kling video (-p kling)
36587
36799
  RUNWAY_API_SECRET Runway video (-p runway)
36588
36800
  ELEVENLABS_API_KEY Speech, sound effects, music
@@ -51922,7 +52134,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
51922
52134
  return args;
51923
52135
  }
51924
52136
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
51925
- return new Promise((resolve62, reject) => {
52137
+ return new Promise((resolve64, reject) => {
51926
52138
  const ffmpeg = spawn(ffmpegPath, args, {
51927
52139
  stdio: ["pipe", "pipe", "pipe"]
51928
52140
  });
@@ -51946,7 +52158,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
51946
52158
  });
51947
52159
  ffmpeg.on("close", (code) => {
51948
52160
  if (code === 0) {
51949
- resolve62();
52161
+ resolve64();
51950
52162
  } else {
51951
52163
  const errorMatch = stderr.match(/Error.*$/m);
51952
52164
  const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
@@ -52083,16 +52295,16 @@ function getPresetSettings(preset, aspectRatio) {
52083
52295
  async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
52084
52296
  spinner2.text = "Loading project...";
52085
52297
  const { readFile: readFile42 } = await import("node:fs/promises");
52086
- const { resolve: resolve62, basename: basename22 } = await import("node:path");
52298
+ const { resolve: resolve64, basename: basename22 } = await import("node:path");
52087
52299
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
52088
52300
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
52089
52301
  const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
52090
52302
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
52091
- const filePath = resolve62(process.cwd(), projectPath);
52303
+ const filePath = resolve64(process.cwd(), projectPath);
52092
52304
  const content = await readFile42(filePath, "utf-8");
52093
52305
  const project = Project2.fromJSON(JSON.parse(content));
52094
52306
  const state = project.getState();
52095
- const outputPath = options.output ? resolve62(process.cwd(), options.output) : resolve62(process.cwd(), `${basename22(projectPath, ".vibe.json")}.${options.format ?? "mp4"}`);
52307
+ const outputPath = options.output ? resolve64(process.cwd(), options.output) : resolve64(process.cwd(), `${basename22(projectPath, ".vibe.json")}.${options.format ?? "mp4"}`);
52096
52308
  const quality = ["draft", "standard", "high"].includes(options.preset ?? "") ? options.preset : "standard";
52097
52309
  const backend = createHyperframesBackend2();
52098
52310
  spinner2.text = "Rendering with Hyperframes...";
@@ -52263,6 +52475,25 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
52263
52475
  spinner2.text = `Encoding... ${progress}%`;
52264
52476
  });
52265
52477
  spinner2.succeed(source_default.green(`Exported: ${outputPath}`));
52478
+ if (isJsonMode()) {
52479
+ outputSuccess({
52480
+ command: "export",
52481
+ startedAt,
52482
+ data: {
52483
+ outputPath,
52484
+ backend: "ffmpeg",
52485
+ format: options.format,
52486
+ preset: options.preset,
52487
+ resolution: presetSettings.resolution,
52488
+ duration: summary.duration,
52489
+ clipCount: summary.clipCount,
52490
+ bitrate: options.bitrate ?? null,
52491
+ fps: options.fps ?? null,
52492
+ codec: options.codec ?? null
52493
+ }
52494
+ });
52495
+ return;
52496
+ }
52266
52497
  console.log();
52267
52498
  console.log(source_default.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
52268
52499
  console.log(source_default.dim(" Clips:"), summary.clipCount);
@@ -52811,7 +53042,7 @@ __export(ai_video_exports, {
52811
53042
  executeVideoStatus: () => executeVideoStatus
52812
53043
  });
52813
53044
  import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
52814
- import { resolve as resolve52 } from "node:path";
53045
+ import { resolve as resolve54 } from "node:path";
52815
53046
  async function executeVideoGenerate(options) {
52816
53047
  const {
52817
53048
  prompt: prompt3,
@@ -52824,24 +53055,61 @@ async function executeVideoGenerate(options) {
52824
53055
  negative,
52825
53056
  resolution,
52826
53057
  veoModel = "3.1-fast",
53058
+ seedanceModel = "quality",
52827
53059
  output,
52828
53060
  wait = true,
52829
53061
  apiKey
52830
53062
  } = options;
52831
53063
  try {
52832
- const envKeyMap = { grok: "XAI_API_KEY", runway: "RUNWAY_API_SECRET", kling: "KLING_API_KEY", veo: "GOOGLE_API_KEY" };
53064
+ const envKeyMap = {
53065
+ grok: "XAI_API_KEY",
53066
+ runway: "RUNWAY_API_SECRET",
53067
+ kling: "KLING_API_KEY",
53068
+ veo: "GOOGLE_API_KEY",
53069
+ seedance: "FAL_KEY",
53070
+ fal: "FAL_KEY"
53071
+ };
52833
53072
  const key = apiKey || process.env[envKeyMap[provider] || ""];
52834
53073
  if (!key) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
52835
53074
  let referenceImage;
52836
53075
  if (image) {
52837
- const imagePath = resolve52(process.cwd(), image);
53076
+ const imagePath = resolve54(process.cwd(), image);
52838
53077
  const imageBuffer = await readFile36(imagePath);
52839
53078
  const ext = image.toLowerCase().split(".").pop();
52840
53079
  const mimeTypes2 = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
52841
53080
  const mimeType = mimeTypes2[ext || "png"] || "image/png";
52842
53081
  referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
52843
53082
  }
52844
- if (provider === "runway") {
53083
+ if (provider === "seedance" || provider === "fal") {
53084
+ const fal = new FalProvider();
53085
+ await fal.initialize({ apiKey: key });
53086
+ let falImage = referenceImage;
53087
+ if (falImage && falImage.startsWith("data:")) {
53088
+ const imgbbKey = process.env.IMGBB_API_KEY;
53089
+ if (!imgbbKey) return { success: false, error: "IMGBB_API_KEY required for Seedance image-to-video" };
53090
+ const base64Data = falImage.split(",")[1];
53091
+ const uploadResult = await uploadToImgbb(Buffer.from(base64Data, "base64"), imgbbKey);
53092
+ if (!uploadResult.success || !uploadResult.url) return { success: false, error: `ImgBB upload failed: ${uploadResult.error}` };
53093
+ falImage = uploadResult.url;
53094
+ }
53095
+ const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
53096
+ const result = await fal.generateVideo(prompt3, {
53097
+ prompt: prompt3,
53098
+ referenceImage: falImage,
53099
+ duration,
53100
+ aspectRatio: ratio,
53101
+ negativePrompt: negative,
53102
+ model
53103
+ });
53104
+ if (result.status === "failed") return { success: false, error: result.error || "Seedance generation failed" };
53105
+ let outputPath;
53106
+ if (output && result.videoUrl) {
53107
+ const buffer = await downloadVideo(result.videoUrl, key);
53108
+ outputPath = resolve54(process.cwd(), output);
53109
+ await writeFile40(outputPath, buffer);
53110
+ }
53111
+ return { success: true, taskId: result.id, status: "completed", videoUrl: result.videoUrl, outputPath, provider: "seedance" };
53112
+ } else if (provider === "runway") {
52845
53113
  const runway = new RunwayProvider();
52846
53114
  await runway.initialize({ apiKey: key });
52847
53115
  const result = await runway.generateVideo(prompt3, {
@@ -52859,7 +53127,7 @@ async function executeVideoGenerate(options) {
52859
53127
  let outputPath;
52860
53128
  if (output && finalResult.videoUrl) {
52861
53129
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52862
- outputPath = resolve52(process.cwd(), output);
53130
+ outputPath = resolve54(process.cwd(), output);
52863
53131
  await writeFile40(outputPath, buffer);
52864
53132
  }
52865
53133
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "runway" };
@@ -52893,7 +53161,7 @@ async function executeVideoGenerate(options) {
52893
53161
  let outputPath;
52894
53162
  if (output && finalResult.videoUrl) {
52895
53163
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52896
- outputPath = resolve52(process.cwd(), output);
53164
+ outputPath = resolve54(process.cwd(), output);
52897
53165
  await writeFile40(outputPath, buffer);
52898
53166
  }
52899
53167
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "kling" };
@@ -52920,7 +53188,7 @@ async function executeVideoGenerate(options) {
52920
53188
  let outputPath;
52921
53189
  if (output && finalResult.videoUrl) {
52922
53190
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52923
- outputPath = resolve52(process.cwd(), output);
53191
+ outputPath = resolve54(process.cwd(), output);
52924
53192
  await writeFile40(outputPath, buffer);
52925
53193
  }
52926
53194
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, outputPath, provider: "veo" };
@@ -52941,7 +53209,7 @@ async function executeVideoGenerate(options) {
52941
53209
  let outputPath;
52942
53210
  if (output && finalResult.videoUrl) {
52943
53211
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52944
- outputPath = resolve52(process.cwd(), output);
53212
+ outputPath = resolve54(process.cwd(), output);
52945
53213
  await writeFile40(outputPath, buffer);
52946
53214
  }
52947
53215
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "grok" };
@@ -52968,7 +53236,7 @@ async function executeVideoStatus(options) {
52968
53236
  let outputPath;
52969
53237
  if (output && result.videoUrl) {
52970
53238
  const buffer = await downloadVideo(result.videoUrl, key);
52971
- outputPath = resolve52(process.cwd(), output);
53239
+ outputPath = resolve54(process.cwd(), output);
52972
53240
  await writeFile40(outputPath, buffer);
52973
53241
  }
52974
53242
  return { success: true, taskId, status: result.status, progress: result.progress, videoUrl: result.videoUrl, outputPath };
@@ -52983,7 +53251,7 @@ async function executeVideoStatus(options) {
52983
53251
  let outputPath;
52984
53252
  if (output && result.videoUrl) {
52985
53253
  const buffer = await downloadVideo(result.videoUrl, key);
52986
- outputPath = resolve52(process.cwd(), output);
53254
+ outputPath = resolve54(process.cwd(), output);
52987
53255
  await writeFile40(outputPath, buffer);
52988
53256
  }
52989
53257
  return { success: true, taskId, status: result.status, videoUrl: result.videoUrl, duration: result.duration, outputPath };
@@ -53028,7 +53296,7 @@ async function executeVideoExtend(options) {
53028
53296
  let outputPath;
53029
53297
  if (output && finalResult.videoUrl) {
53030
53298
  const buffer = await downloadVideo(finalResult.videoUrl, key);
53031
- outputPath = resolve52(process.cwd(), output);
53299
+ outputPath = resolve54(process.cwd(), output);
53032
53300
  await writeFile40(outputPath, buffer);
53033
53301
  }
53034
53302
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath };
@@ -53051,7 +53319,7 @@ async function executeVideoExtend(options) {
53051
53319
  let outputPath;
53052
53320
  if (output && finalResult.videoUrl) {
53053
53321
  const buffer = await downloadVideo(finalResult.videoUrl, key);
53054
- outputPath = resolve52(process.cwd(), output);
53322
+ outputPath = resolve54(process.cwd(), output);
53055
53323
  await writeFile40(outputPath, buffer);
53056
53324
  }
53057
53325
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, outputPath };
@@ -53079,7 +53347,7 @@ __export(ai_audio_exports, {
53079
53347
  executeTranscribe: () => executeTranscribe,
53080
53348
  executeVoiceClone: () => executeVoiceClone
53081
53349
  });
53082
- import { resolve as resolve53, dirname as dirname20, basename as basename20, extname as extname12 } from "node:path";
53350
+ import { resolve as resolve55, dirname as dirname20, basename as basename20, extname as extname12 } from "node:path";
53083
53351
  import { readFile as readFile37, writeFile as writeFile41 } from "node:fs/promises";
53084
53352
  import { existsSync as existsSync37 } from "node:fs";
53085
53353
  async function executeTranscribe(options) {
@@ -53087,7 +53355,7 @@ async function executeTranscribe(options) {
53087
53355
  try {
53088
53356
  const key = apiKey || process.env.OPENAI_API_KEY;
53089
53357
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
53090
- const absPath = resolve53(process.cwd(), audioPath);
53358
+ const absPath = resolve55(process.cwd(), audioPath);
53091
53359
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
53092
53360
  const whisper = new WhisperProvider();
53093
53361
  await whisper.initialize({ apiKey: key });
@@ -53099,7 +53367,7 @@ async function executeTranscribe(options) {
53099
53367
  }
53100
53368
  let outputPath;
53101
53369
  if (output) {
53102
- outputPath = resolve53(process.cwd(), output);
53370
+ outputPath = resolve55(process.cwd(), output);
53103
53371
  const fmt = detectFormat(output, format);
53104
53372
  const content = formatTranscript(result, fmt);
53105
53373
  await writeFile41(outputPath, content, "utf-8");
@@ -53120,7 +53388,7 @@ async function executeIsolate(options) {
53120
53388
  try {
53121
53389
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
53122
53390
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
53123
- const absPath = resolve53(process.cwd(), audioPath);
53391
+ const absPath = resolve55(process.cwd(), audioPath);
53124
53392
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
53125
53393
  const audioBuffer = await readFile37(absPath);
53126
53394
  const elevenlabs = new ElevenLabsProvider();
@@ -53129,7 +53397,7 @@ async function executeIsolate(options) {
53129
53397
  if (!result.success || !result.audioBuffer) {
53130
53398
  return { success: false, error: result.error || "Audio isolation failed" };
53131
53399
  }
53132
- const outputPath = resolve53(process.cwd(), output);
53400
+ const outputPath = resolve55(process.cwd(), output);
53133
53401
  await writeFile41(outputPath, result.audioBuffer);
53134
53402
  return { success: true, outputPath };
53135
53403
  } catch (error) {
@@ -53146,7 +53414,7 @@ async function executeVoiceClone(options) {
53146
53414
  }
53147
53415
  const audioBuffers = [];
53148
53416
  for (const samplePath of samplePaths) {
53149
- const absPath = resolve53(process.cwd(), samplePath);
53417
+ const absPath = resolve55(process.cwd(), samplePath);
53150
53418
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${samplePath}` };
53151
53419
  const buffer = await readFile37(absPath);
53152
53420
  audioBuffers.push(buffer);
@@ -53168,7 +53436,7 @@ async function executeVoiceClone(options) {
53168
53436
  async function executeDub(options) {
53169
53437
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
53170
53438
  try {
53171
- const absPath = resolve53(process.cwd(), mediaPath);
53439
+ const absPath = resolve55(process.cwd(), mediaPath);
53172
53440
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
53173
53441
  const openaiKey = process.env.OPENAI_API_KEY;
53174
53442
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
@@ -53180,7 +53448,7 @@ async function executeDub(options) {
53180
53448
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
53181
53449
  let audioPath = absPath;
53182
53450
  if (isVideo) {
53183
- const tempAudioPath = resolve53(dirname20(absPath), `temp-audio-${Date.now()}.mp3`);
53451
+ const tempAudioPath = resolve55(dirname20(absPath), `temp-audio-${Date.now()}.mp3`);
53184
53452
  execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
53185
53453
  audioPath = tempAudioPath;
53186
53454
  }
@@ -53229,7 +53497,7 @@ ${segmentTexts}`,
53229
53497
  }
53230
53498
  if (analyzeOnly) {
53231
53499
  if (output) {
53232
- const timingPath = resolve53(process.cwd(), output);
53500
+ const timingPath = resolve55(process.cwd(), output);
53233
53501
  const timingData = {
53234
53502
  sourcePath: absPath,
53235
53503
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
@@ -53248,7 +53516,7 @@ ${segmentTexts}`,
53248
53516
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
53249
53517
  targetLanguage: language,
53250
53518
  segmentCount: segments.length,
53251
- outputPath: output ? resolve53(process.cwd(), output) : void 0
53519
+ outputPath: output ? resolve55(process.cwd(), output) : void 0
53252
53520
  };
53253
53521
  }
53254
53522
  const elevenlabs = new ElevenLabsProvider();
@@ -53262,8 +53530,8 @@ ${segmentTexts}`,
53262
53530
  }
53263
53531
  const combinedBuffer = Buffer.concat(dubbedBuffers);
53264
53532
  const outputExt = isVideo ? ".mp3" : extname12(absPath);
53265
- const defaultOutputPath2 = resolve53(dirname20(absPath), `${basename20(absPath, extname12(absPath))}-${language}${outputExt}`);
53266
- const finalOutputPath = resolve53(process.cwd(), output || defaultOutputPath2);
53533
+ const defaultOutputPath2 = resolve55(dirname20(absPath), `${basename20(absPath, extname12(absPath))}-${language}${outputExt}`);
53534
+ const finalOutputPath = resolve55(process.cwd(), output || defaultOutputPath2);
53267
53535
  await writeFile41(finalOutputPath, combinedBuffer);
53268
53536
  if (isVideo && audioPath !== absPath) {
53269
53537
  try {
@@ -53295,12 +53563,12 @@ async function executeDuck(options) {
53295
53563
  } = options;
53296
53564
  try {
53297
53565
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
53298
- const absMusicPath = resolve53(process.cwd(), musicPath);
53299
- const absVoicePath = resolve53(process.cwd(), voicePath);
53566
+ const absMusicPath = resolve55(process.cwd(), musicPath);
53567
+ const absVoicePath = resolve55(process.cwd(), voicePath);
53300
53568
  if (!existsSync37(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
53301
53569
  if (!existsSync37(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
53302
- const defaultOutput = resolve53(dirname20(absMusicPath), `${basename20(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
53303
- const outputPath = resolve53(process.cwd(), output || defaultOutput);
53570
+ const defaultOutput = resolve55(dirname20(absMusicPath), `${basename20(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
53571
+ const outputPath = resolve55(process.cwd(), output || defaultOutput);
53304
53572
  const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
53305
53573
  await execSafe("ffmpeg", [
53306
53574
  "-i",
@@ -53328,11 +53596,11 @@ var init_ai_audio = __esm({
53328
53596
  });
53329
53597
 
53330
53598
  // src/pipeline/executor.ts
53331
- import { resolve as resolve54 } from "node:path";
53599
+ import { resolve as resolve56 } from "node:path";
53332
53600
  import { readFile as readFile38, writeFile as writeFile42, mkdir as mkdir22 } from "node:fs/promises";
53333
53601
  import { existsSync as existsSync38 } from "node:fs";
53334
53602
  function maxCostFor(action) {
53335
- const cmd = ACTION_TO_COMMAND[action];
53603
+ const cmd = ACTION_METADATA[action]?.command;
53336
53604
  if (!cmd) return 0;
53337
53605
  return COST_ESTIMATES[cmd]?.max ?? 0;
53338
53606
  }
@@ -53341,9 +53609,9 @@ function registerAction(action, handler) {
53341
53609
  }
53342
53610
  function getOutput(params, outputDir, defaultName) {
53343
53611
  if (params.output && typeof params.output === "string") {
53344
- return resolve54(outputDir, params.output);
53612
+ return resolve56(outputDir, params.output);
53345
53613
  }
53346
- return resolve54(outputDir, defaultName);
53614
+ return resolve56(outputDir, defaultName);
53347
53615
  }
53348
53616
  async function ensureActionsRegistered() {
53349
53617
  if (Object.keys(ACTION_HANDLERS).length > 0) return;
@@ -53356,7 +53624,15 @@ async function ensureActionsRegistered() {
53356
53624
  registerAction("generate-video", async (params, outputDir) => {
53357
53625
  const { executeVideoGenerate: executeVideoGenerate2 } = await Promise.resolve().then(() => (init_ai_video(), ai_video_exports));
53358
53626
  const output = getOutput(params, outputDir, "video.mp4");
53359
- const r = await executeVideoGenerate2({ prompt: params.prompt, provider: params.provider, image: params.image, duration: params.duration, ratio: params.ratio, output, wait: true });
53627
+ const r = await executeVideoGenerate2({
53628
+ prompt: params.prompt,
53629
+ provider: params.provider,
53630
+ image: params.image,
53631
+ duration: params.duration,
53632
+ ratio: params.ratio,
53633
+ output,
53634
+ wait: true
53635
+ });
53360
53636
  return { id: "", action: "generate-video", success: r.success, output: r.outputPath || r.videoUrl, data: { taskId: r.taskId, provider: r.provider, videoUrl: r.videoUrl }, error: r.error };
53361
53637
  });
53362
53638
  registerAction("generate-tts", async (params, outputDir) => {
@@ -53488,7 +53764,7 @@ async function ensureActionsRegistered() {
53488
53764
  const { executeSceneBuild: executeSceneBuild2 } = await Promise.resolve().then(() => (init_scene_build(), scene_build_exports));
53489
53765
  const projectRel = params.project ?? ".";
53490
53766
  const r = await executeSceneBuild2({
53491
- projectDir: resolve54(outputDir, projectRel),
53767
+ projectDir: resolve56(outputDir, projectRel),
53492
53768
  mode: params.mode,
53493
53769
  effort: params.effort,
53494
53770
  composer: params.composer,
@@ -53514,7 +53790,7 @@ async function ensureActionsRegistered() {
53514
53790
  const { executeSceneRender: executeSceneRender2 } = await Promise.resolve().then(() => (init_scene_render(), scene_render_exports));
53515
53791
  const projectRel = params.project ?? ".";
53516
53792
  const r = await executeSceneRender2({
53517
- projectDir: resolve54(outputDir, projectRel),
53793
+ projectDir: resolve56(outputDir, projectRel),
53518
53794
  root: params.root,
53519
53795
  output: params.output,
53520
53796
  fps: params.fps,
@@ -53538,7 +53814,7 @@ async function ensureActionsRegistered() {
53538
53814
  });
53539
53815
  }
53540
53816
  async function loadPipeline(filePath) {
53541
- const absPath = resolve54(process.cwd(), filePath);
53817
+ const absPath = resolve56(process.cwd(), filePath);
53542
53818
  if (!existsSync38(absPath)) {
53543
53819
  throw new Error(`Pipeline file not found: ${absPath}`);
53544
53820
  }
@@ -53557,13 +53833,13 @@ async function loadPipeline(filePath) {
53557
53833
  }
53558
53834
  async function executePipeline(manifest2, options = {}) {
53559
53835
  await ensureActionsRegistered();
53560
- const outputDir = resolve54(process.cwd(), options.outputDir || `${manifest2.name}-output`);
53836
+ const outputDir = resolve56(process.cwd(), options.outputDir || `${manifest2.name}-output`);
53561
53837
  await mkdir22(outputDir, { recursive: true });
53562
53838
  const completedSteps = /* @__PURE__ */ new Map();
53563
53839
  const results = [];
53564
53840
  const startTime = Date.now();
53565
53841
  if (options.resume) {
53566
- const checkpointPath = resolve54(outputDir, CHECKPOINT_FILE);
53842
+ const checkpointPath = resolve56(outputDir, CHECKPOINT_FILE);
53567
53843
  if (existsSync38(checkpointPath)) {
53568
53844
  const checkpointContent = await readFile38(checkpointPath, "utf-8");
53569
53845
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
@@ -53684,7 +53960,7 @@ async function executePipeline(manifest2, options = {}) {
53684
53960
  }))
53685
53961
  };
53686
53962
  await writeFile42(
53687
- resolve54(outputDir, CHECKPOINT_FILE),
53963
+ resolve56(outputDir, CHECKPOINT_FILE),
53688
53964
  (0, import_yaml8.stringify)(checkpoint, { indent: 2 }),
53689
53965
  "utf-8"
53690
53966
  );
@@ -53731,42 +54007,48 @@ async function executePipeline(manifest2, options = {}) {
53731
54007
  budget: effectiveBudget ? budgetUsage : void 0
53732
54008
  };
53733
54009
  }
53734
- var import_yaml8, ACTION_TO_COMMAND, ACTION_HANDLERS, CHECKPOINT_FILE;
54010
+ var import_yaml8, ACTION_METADATA, ACTION_HANDLERS, CHECKPOINT_FILE;
53735
54011
  var init_executor = __esm({
53736
54012
  "src/pipeline/executor.ts"() {
53737
54013
  "use strict";
53738
54014
  import_yaml8 = __toESM(require_dist(), 1);
53739
54015
  init_resolver();
53740
54016
  init_output();
53741
- ACTION_TO_COMMAND = {
53742
- "generate-image": "generate image",
53743
- "generate-video": "generate video",
53744
- "generate-tts": "generate speech",
53745
- "generate-sfx": "generate sound-effect",
53746
- "generate-music": "generate music",
53747
- "generate-storyboard": "generate storyboard",
53748
- "generate-motion": "generate motion",
53749
- "edit-silence-cut": "edit silence-cut",
53750
- "edit-jump-cut": "edit jump-cut",
53751
- "edit-caption": "edit caption",
53752
- "edit-noise-reduce": "edit noise-reduce",
53753
- "edit-fade": "edit fade",
53754
- "edit-translate-srt": "edit translate-srt",
53755
- "edit-text-overlay": "edit text-overlay",
53756
- "edit-grade": "edit grade",
53757
- "edit-speed-ramp": "edit speed-ramp",
53758
- "edit-reframe": "edit reframe",
53759
- "edit-interpolate": "edit interpolate",
53760
- "edit-upscale": "edit upscale-video",
53761
- "edit-image": "edit image",
53762
- "audio-transcribe": "audio transcribe",
53763
- "detect-scenes": "detect scenes",
53764
- "detect-silence": "detect silence",
53765
- "detect-beats": "detect beats",
53766
- "analyze-media": "analyze media",
53767
- "analyze-video": "analyze video",
53768
- "review-video": "analyze review",
53769
- "compose-scenes-with-skills": "compose scenes with skills"
54017
+ ACTION_METADATA = {
54018
+ "generate-image": { id: "generate-image", title: "Generate image", category: "generate", command: "generate image", outputs: ["image"] },
54019
+ "generate-video": { id: "generate-video", title: "Generate video", category: "generate", command: "generate video", outputs: ["video"], requiredKeys: ["provider-dependent"] },
54020
+ "generate-tts": { id: "generate-tts", title: "Generate speech", category: "generate", command: "generate speech", outputs: ["audio"] },
54021
+ "generate-sfx": { id: "generate-sfx", title: "Generate sound effect", category: "generate", command: "generate sound-effect", outputs: ["audio"] },
54022
+ "generate-music": { id: "generate-music", title: "Generate music", category: "generate", command: "generate music", outputs: ["audio"] },
54023
+ "generate-storyboard": { id: "generate-storyboard", title: "Generate storyboard", category: "generate", command: "generate storyboard", outputs: ["storyboard"] },
54024
+ "generate-motion": { id: "generate-motion", title: "Generate motion", category: "generate", command: "generate motion", outputs: ["code", "video"] },
54025
+ "edit-silence-cut": { id: "edit-silence-cut", title: "Cut silence", category: "edit", command: "edit silence-cut", outputs: ["video"] },
54026
+ "edit-jump-cut": { id: "edit-jump-cut", title: "Jump cut", category: "edit", command: "edit jump-cut", outputs: ["video"] },
54027
+ "edit-caption": { id: "edit-caption", title: "Caption video", category: "edit", command: "edit caption", outputs: ["video", "srt"] },
54028
+ "edit-noise-reduce": { id: "edit-noise-reduce", title: "Reduce noise", category: "edit", command: "edit noise-reduce", outputs: ["video"] },
54029
+ "edit-fade": { id: "edit-fade", title: "Add fade", category: "edit", command: "edit fade", outputs: ["video"] },
54030
+ "edit-translate-srt": { id: "edit-translate-srt", title: "Translate subtitles", category: "edit", command: "edit translate-srt", outputs: ["srt"] },
54031
+ "edit-text-overlay": { id: "edit-text-overlay", title: "Add text overlay", category: "edit", command: "edit text-overlay", outputs: ["video"] },
54032
+ "edit-grade": { id: "edit-grade", title: "Color grade", category: "edit", command: "edit grade", outputs: ["video"] },
54033
+ "edit-speed-ramp": { id: "edit-speed-ramp", title: "Speed ramp", category: "edit", command: "edit speed-ramp", outputs: ["video"] },
54034
+ "edit-reframe": { id: "edit-reframe", title: "Reframe video", category: "edit", command: "edit reframe", outputs: ["video"] },
54035
+ "edit-interpolate": { id: "edit-interpolate", title: "Interpolate frames", category: "edit", command: "edit interpolate", outputs: ["video"] },
54036
+ "edit-upscale": { id: "edit-upscale", title: "Upscale video", category: "edit", command: "edit upscale-video", outputs: ["video"] },
54037
+ "edit-image": { id: "edit-image", title: "Edit image", category: "edit", command: "edit image", outputs: ["image"] },
54038
+ "audio-transcribe": { id: "audio-transcribe", title: "Transcribe audio", category: "audio", command: "audio transcribe", outputs: ["transcript", "srt"] },
54039
+ "audio-isolate": { id: "audio-isolate", title: "Isolate audio", category: "audio", outputs: ["audio"] },
54040
+ "audio-dub": { id: "audio-dub", title: "Dub audio", category: "audio", outputs: ["audio", "video"] },
54041
+ "audio-duck": { id: "audio-duck", title: "Duck audio", category: "audio", outputs: ["video"] },
54042
+ "detect-scenes": { id: "detect-scenes", title: "Detect scenes", category: "detect", command: "detect scenes", outputs: ["json"] },
54043
+ "detect-silence": { id: "detect-silence", title: "Detect silence", category: "detect", command: "detect silence", outputs: ["json"] },
54044
+ "detect-beats": { id: "detect-beats", title: "Detect beats", category: "detect", command: "detect beats", outputs: ["json"] },
54045
+ "analyze-media": { id: "analyze-media", title: "Analyze media", category: "analyze", command: "analyze media", outputs: ["json"] },
54046
+ "analyze-video": { id: "analyze-video", title: "Analyze video", category: "analyze", command: "analyze video", outputs: ["json"] },
54047
+ "review-video": { id: "review-video", title: "Review video", category: "analyze", command: "analyze review", outputs: ["json"] },
54048
+ "compose-scenes-with-skills": { id: "compose-scenes-with-skills", title: "Compose scenes with skills", category: "scene", command: "compose scenes with skills", outputs: ["html"] },
54049
+ "scene-build": { id: "scene-build", title: "Build scene project", category: "scene", outputs: ["video", "html", "assets"] },
54050
+ "scene-render": { id: "scene-render", title: "Render scene project", category: "scene", outputs: ["video"] },
54051
+ export: { id: "export", title: "Export project", category: "export", outputs: ["video"] }
53770
54052
  };
53771
54053
  ACTION_HANDLERS = {};
53772
54054
  CHECKPOINT_FILE = ".pipeline-state.yaml";
@@ -58641,7 +58923,7 @@ var init_define_tool = __esm({
58641
58923
  });
58642
58924
 
58643
58925
  // src/tools/manifest/scene.ts
58644
- import { resolve as resolve55, relative as relative7 } from "node:path";
58926
+ import { resolve as resolve57, relative as relative7 } from "node:path";
58645
58927
  function summariseLint(result) {
58646
58928
  return {
58647
58929
  ok: result.ok,
@@ -58757,10 +59039,10 @@ var init_scene2 = __esm({
58757
59039
  name: "scene_init",
58758
59040
  category: "scene",
58759
59041
  cost: "free",
58760
- description: "Scaffold a new bilingual VibeFrame + Hyperframes scene project. Creates index.html, hyperframes.json, vibe.project.yaml, compositions/, assets/, .gitignore, and a project-local CLAUDE.md. Idempotent: re-running on an existing Hyperframes project merges hyperframes.json instead of overwriting. No API keys required.",
59042
+ description: "Scaffold a new VibeFrame video scene project. Supports minimal, agent, and full profiles; full includes the current HTML render backend metadata. Idempotent: re-running keeps user-authored files and merges backend config instead of overwriting. No API keys required.",
58761
59043
  schema: sceneInitSchema,
58762
59044
  async execute(args, ctx) {
58763
- const dir = resolve55(ctx.workingDirectory, args.dir);
59045
+ const dir = resolve57(ctx.workingDirectory, args.dir);
58764
59046
  const result = await scaffoldSceneProject({
58765
59047
  dir,
58766
59048
  name: args.name,
@@ -58808,7 +59090,7 @@ var init_scene2 = __esm({
58808
59090
  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.",
58809
59091
  schema: sceneAddSchema,
58810
59092
  async execute(args, ctx) {
58811
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59093
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58812
59094
  const result = await executeSceneAdd({
58813
59095
  name: args.name,
58814
59096
  preset: args.preset ?? "simple",
@@ -58865,7 +59147,7 @@ var init_scene2 = __esm({
58865
59147
  description: "Validate every scene file in a project against the public Hyperframes lint rules (in-process, no Chrome required). Returns errors, warnings, and info findings per file. Optional fix:true mechanically repairs `timed_element_missing_clip_class` only \u2014 other issues surface with fixHints.",
58866
59148
  schema: sceneLintSchema,
58867
59149
  async execute(args, ctx) {
58868
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59150
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58869
59151
  const result = await runProjectLint({
58870
59152
  projectDir,
58871
59153
  rootRel: args.root,
@@ -58906,7 +59188,7 @@ var init_scene2 = __esm({
58906
59188
  description: "Render a scene project to MP4/WebM/MOV via the Hyperframes producer. Requires Chrome installed locally. Output defaults to renders/<projectName>-<isoStamp>.<format>.",
58907
59189
  schema: sceneRenderSchema,
58908
59190
  async execute(args, ctx) {
58909
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59191
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58910
59192
  const result = await executeSceneRender({
58911
59193
  projectDir,
58912
59194
  root: args.root,
@@ -58961,7 +59243,7 @@ var init_scene2 = __esm({
58961
59243
  description: "v0.60 one-shot orchestrator: read STORYBOARD.md per-beat YAML cues (narration / backdrop / duration), dispatch TTS + image generation per beat, compose scene HTML via the compose-scenes-with-skills pipeline, then render to MP4. Use this instead of chaining scene_init + scene_add + scene_render manually. Caches by SHA256 of (DESIGN.md + cue body) so re-runs are idempotent and cheap.",
58962
59244
  schema: sceneBuildSchema,
58963
59245
  async execute(args, ctx) {
58964
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59246
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58965
59247
  const result = await executeSceneBuild({
58966
59248
  projectDir,
58967
59249
  mode: args.mode,
@@ -59022,7 +59304,7 @@ var init_scene2 = __esm({
59022
59304
  description: "Install the vendored Hyperframes skill bundle into a scene project so the host agent (Claude Code, Cursor, Codex, Aider) can read framework rules + house style directly. Writes a universal SKILL.md + references/ at the project root, plus per-host layouts (.claude/skills/hyperframes/ for Claude Code, .cursor/rules/hyperframes.mdc for Cursor) when those hosts are detected. Phase H1 of the agentic-native composer plan \u2014 once installed, the host agent itself can author scene HTML using the rules instead of relying on vibe's internal LLM call.",
59023
59305
  schema: sceneInstallSkillSchema,
59024
59306
  async execute(args, ctx) {
59025
- const projectDir = resolve55(ctx.workingDirectory, args.projectDir);
59307
+ const projectDir = resolve57(ctx.workingDirectory, args.projectDir);
59026
59308
  const hostFlag = args.host ?? "auto";
59027
59309
  const hosts = (() => {
59028
59310
  if (hostFlag === "all") return ["all"];
@@ -59064,7 +59346,7 @@ var init_scene2 = __esm({
59064
59346
  description: "Emit the per-beat compose plan for the host agent to author scene HTML itself. Reads STORYBOARD.md + DESIGN.md and returns each beat's outputPath + userPrompt + cues + body, plus references to the project's SKILL.md (Hyperframes rules) and DESIGN.md (visual identity). The host agent writes each compositions/scene-<id>.html file directly \u2014 VibeFrame makes NO LLM call here. Pairs with scene_install_skill (Phase H1). Phase H2 of the agentic-native composer plan; the internal-LLM batch path (scene_build) remains as a fallback for non-agent contexts.",
59065
59347
  schema: sceneComposePromptsSchema,
59066
59348
  async execute(args, ctx) {
59067
- const projectDir = resolve55(ctx.workingDirectory, args.projectDir);
59349
+ const projectDir = resolve57(ctx.workingDirectory, args.projectDir);
59068
59350
  const result = await getComposePrompts({
59069
59351
  projectDir,
59070
59352
  beatId: args.beat
@@ -59654,12 +59936,12 @@ var init_edit = __esm({
59654
59936
 
59655
59937
  // src/commands/ai-suggest-edit.ts
59656
59938
  import { readFile as readFile39, writeFile as writeFile43 } from "node:fs/promises";
59657
- import { resolve as resolve56 } from "node:path";
59939
+ import { resolve as resolve58 } from "node:path";
59658
59940
  async function executeSuggestEdit(options) {
59659
59941
  try {
59660
59942
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
59661
59943
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
59662
- const filePath = resolve56(process.cwd(), options.projectPath);
59944
+ const filePath = resolve58(process.cwd(), options.projectPath);
59663
59945
  const content = await readFile39(filePath, "utf-8");
59664
59946
  const data = JSON.parse(content);
59665
59947
  const project = Project.fromJSON(data);
@@ -60438,9 +60720,9 @@ var init_detect2 = __esm({
60438
60720
 
60439
60721
  // src/tools/manifest/_project-io.ts
60440
60722
  import { readFile as readFile40, writeFile as writeFile45 } from "node:fs/promises";
60441
- import { resolve as resolve57 } from "node:path";
60723
+ import { resolve as resolve59 } from "node:path";
60442
60724
  async function loadProject(projectPath, cwd) {
60443
- const absPath = resolve57(cwd, projectPath);
60725
+ const absPath = resolve59(cwd, projectPath);
60444
60726
  const content = await readFile40(absPath, "utf-8");
60445
60727
  const data = JSON.parse(content);
60446
60728
  return { project: Project.fromJSON(data), absPath };
@@ -60456,7 +60738,7 @@ var init_project_io = __esm({
60456
60738
  });
60457
60739
 
60458
60740
  // src/tools/manifest/timeline.ts
60459
- import { resolve as resolve58 } from "node:path";
60741
+ import { resolve as resolve60 } from "node:path";
60460
60742
  var MEDIA_TYPES, timelineAddSourceTool, timelineAddClipTool, timelineSplitClipTool, timelineTrimClipTool, timelineMoveClipTool, timelineDeleteClipTool, timelineDuplicateClipTool, timelineAddEffectTool, timelineAddTrackTool, timelineListTool, timelineTools;
60461
60743
  var init_timeline = __esm({
60462
60744
  "src/tools/manifest/timeline.ts"() {
@@ -60492,7 +60774,7 @@ var init_timeline = __esm({
60492
60774
  }),
60493
60775
  async execute(args, ctx) {
60494
60776
  const { project, absPath } = await loadProject(args.projectPath, ctx.workingDirectory);
60495
- const mediaPath = resolve58(ctx.workingDirectory, args.mediaPath);
60777
+ const mediaPath = resolve60(ctx.workingDirectory, args.mediaPath);
60496
60778
  const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
60497
60779
  const source = project.addSource({
60498
60780
  name: args.name ?? mediaPath.split("/").pop() ?? "media",
@@ -60718,7 +61000,7 @@ var init_timeline = __esm({
60718
61000
  });
60719
61001
 
60720
61002
  // src/tools/manifest/project.ts
60721
- import { resolve as resolve59 } from "node:path";
61003
+ import { resolve as resolve61 } from "node:path";
60722
61004
  import { writeFile as writeFile46 } from "node:fs/promises";
60723
61005
  var projectCreateTool, projectInfoTool, projectTools;
60724
61006
  var init_project2 = __esm({
@@ -60741,7 +61023,7 @@ var init_project2 = __esm({
60741
61023
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
60742
61024
  }),
60743
61025
  async execute(args, ctx) {
60744
- const outputPath = resolve59(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
61026
+ const outputPath = resolve61(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
60745
61027
  const project = new Project(args.name);
60746
61028
  if (args.fps) project.setFrameRate(args.fps);
60747
61029
  await writeFile46(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
@@ -60787,7 +61069,7 @@ var init_project2 = __esm({
60787
61069
  });
60788
61070
 
60789
61071
  // src/tools/manifest/export.ts
60790
- import { resolve as resolve60 } from "node:path";
61072
+ import { resolve as resolve62 } from "node:path";
60791
61073
  var exportVideoTool, exportTools;
60792
61074
  var init_export2 = __esm({
60793
61075
  "src/tools/manifest/export.ts"() {
@@ -60808,8 +61090,8 @@ var init_export2 = __esm({
60808
61090
  overwrite: external_exports.boolean().optional().describe("Overwrite existing output file (default: false)")
60809
61091
  }),
60810
61092
  async execute(args, ctx) {
60811
- const projectPath = resolve60(ctx.workingDirectory, args.projectPath);
60812
- const outputPath = resolve60(ctx.workingDirectory, args.outputPath);
61093
+ const projectPath = resolve62(ctx.workingDirectory, args.projectPath);
61094
+ const outputPath = resolve62(ctx.workingDirectory, args.outputPath);
60813
61095
  const result = await runExport(projectPath, outputPath, {
60814
61096
  preset: args.preset,
60815
61097
  format: args.format,
@@ -60829,7 +61111,7 @@ var init_export2 = __esm({
60829
61111
 
60830
61112
  // src/tools/manifest/agent-only.ts
60831
61113
  import { readFile as readFile41, writeFile as writeFile47, readdir as readdir4, stat as stat6, access as access8, unlink as unlink4 } from "node:fs/promises";
60832
- import { resolve as resolve61, join as join15, basename as basename21, extname as extname13 } from "node:path";
61114
+ import { resolve as resolve63, join as join15, basename as basename21, extname as extname13 } from "node:path";
60833
61115
  function matchPattern(filename, pattern) {
60834
61116
  const regex2 = new RegExp(
60835
61117
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -60885,11 +61167,11 @@ async function getMediaDuration2(filePath) {
60885
61167
  }
60886
61168
  }
60887
61169
  async function resolveProjectPath3(inputPath, cwd) {
60888
- const filePath = resolve61(cwd, inputPath);
61170
+ const filePath = resolve63(cwd, inputPath);
60889
61171
  try {
60890
61172
  const stats = await stat6(filePath);
60891
61173
  if (stats.isDirectory()) {
60892
- return resolve61(filePath, "project.vibe.json");
61174
+ return resolve63(filePath, "project.vibe.json");
60893
61175
  }
60894
61176
  } catch {
60895
61177
  }
@@ -60917,7 +61199,7 @@ var init_agent_only = __esm({
60917
61199
  async execute(args, ctx) {
60918
61200
  const dirPath = args.path ?? ".";
60919
61201
  try {
60920
- const absPath = resolve61(ctx.workingDirectory, dirPath);
61202
+ const absPath = resolve63(ctx.workingDirectory, dirPath);
60921
61203
  const entries = await readdir4(absPath, { withFileTypes: true });
60922
61204
  const results = [];
60923
61205
  for (const entry of entries) {
@@ -60948,7 +61230,7 @@ var init_agent_only = __esm({
60948
61230
  }),
60949
61231
  async execute(args, ctx) {
60950
61232
  try {
60951
- const absPath = resolve61(ctx.workingDirectory, args.path);
61233
+ const absPath = resolve63(ctx.workingDirectory, args.path);
60952
61234
  const content = await readFile41(absPath, "utf-8");
60953
61235
  const maxLength = 4e3;
60954
61236
  const truncated = content.length > maxLength;
@@ -60971,7 +61253,7 @@ var init_agent_only = __esm({
60971
61253
  }),
60972
61254
  async execute(args, ctx) {
60973
61255
  try {
60974
- const absPath = resolve61(ctx.workingDirectory, args.path);
61256
+ const absPath = resolve63(ctx.workingDirectory, args.path);
60975
61257
  await writeFile47(absPath, args.content, "utf-8");
60976
61258
  return { success: true, data: { bytes: args.content.length }, humanLines: [`File written: ${args.path} (${formatSize(args.content.length)})`] };
60977
61259
  } catch (error) {
@@ -60989,7 +61271,7 @@ var init_agent_only = __esm({
60989
61271
  path: external_exports.string().describe("Path to check")
60990
61272
  }),
60991
61273
  async execute(args, ctx) {
60992
- const absPath = resolve61(ctx.workingDirectory, args.path);
61274
+ const absPath = resolve63(ctx.workingDirectory, args.path);
60993
61275
  try {
60994
61276
  await access8(absPath);
60995
61277
  const stats = await stat6(absPath);
@@ -61015,11 +61297,11 @@ var init_agent_only = __esm({
61015
61297
  }),
61016
61298
  async execute(args, ctx) {
61017
61299
  try {
61018
- const filePath = resolve61(ctx.workingDirectory, args.project);
61300
+ const filePath = resolve63(ctx.workingDirectory, args.project);
61019
61301
  const content = await readFile41(filePath, "utf-8");
61020
61302
  const data = JSON.parse(content);
61021
61303
  const project = Project.fromJSON(data);
61022
- const dirPath = resolve61(ctx.workingDirectory, args.directory);
61304
+ const dirPath = resolve63(ctx.workingDirectory, args.directory);
61023
61305
  const filterExts = args.filter ? args.filter.split(",").map((e) => e.trim().toLowerCase()) : null;
61024
61306
  const imageDuration = args.imageDuration ?? 5;
61025
61307
  const recursive = args.recursive ?? false;
@@ -61085,7 +61367,7 @@ var init_agent_only = __esm({
61085
61367
  }),
61086
61368
  async execute(args, ctx) {
61087
61369
  try {
61088
- const filePath = resolve61(ctx.workingDirectory, args.project);
61370
+ const filePath = resolve63(ctx.workingDirectory, args.project);
61089
61371
  const content = await readFile41(filePath, "utf-8");
61090
61372
  const data = JSON.parse(content);
61091
61373
  const project = Project.fromJSON(data);
@@ -61154,7 +61436,7 @@ var init_agent_only = __esm({
61154
61436
  }),
61155
61437
  async execute(args, ctx) {
61156
61438
  try {
61157
- const filePath = resolve61(ctx.workingDirectory, args.project);
61439
+ const filePath = resolve63(ctx.workingDirectory, args.project);
61158
61440
  const content = await readFile41(filePath, "utf-8");
61159
61441
  const data = JSON.parse(content);
61160
61442
  const project = Project.fromJSON(data);
@@ -61432,7 +61714,7 @@ var init_agent_only = __esm({
61432
61714
  }),
61433
61715
  async execute(args, ctx) {
61434
61716
  try {
61435
- const absPath = resolve61(ctx.workingDirectory, args.path);
61717
+ const absPath = resolve63(ctx.workingDirectory, args.path);
61436
61718
  const { stdout } = await execSafe(
61437
61719
  "ffprobe",
61438
61720
  ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", absPath],
@@ -61519,10 +61801,10 @@ Audio:`);
61519
61801
  }),
61520
61802
  async execute(args, ctx) {
61521
61803
  try {
61522
- const absInput = resolve61(ctx.workingDirectory, args.input);
61804
+ const absInput = resolve63(ctx.workingDirectory, args.input);
61523
61805
  const ext = args.input.split(".").pop() || "mp4";
61524
61806
  const baseName = args.input.replace(/\.[^/.]+$/, "");
61525
- const outputPath = args.output ? resolve61(ctx.workingDirectory, args.output) : resolve61(ctx.workingDirectory, `${baseName}-compressed.${ext}`);
61807
+ const outputPath = args.output ? resolve63(ctx.workingDirectory, args.output) : resolve63(ctx.workingDirectory, `${baseName}-compressed.${ext}`);
61526
61808
  const crfValues = { low: 28, medium: 23, high: 18 };
61527
61809
  const crf = crfValues[args.quality ?? "medium"];
61528
61810
  await execSafe(
@@ -61580,8 +61862,8 @@ Audio:`);
61580
61862
  }),
61581
61863
  async execute(args, ctx) {
61582
61864
  try {
61583
- const absInput = resolve61(ctx.workingDirectory, args.input);
61584
- const absOutput = resolve61(ctx.workingDirectory, args.output);
61865
+ const absInput = resolve63(ctx.workingDirectory, args.input);
61866
+ const absOutput = resolve63(ctx.workingDirectory, args.output);
61585
61867
  const codecMap = {
61586
61868
  h264: "libx264",
61587
61869
  h265: "libx265",
@@ -61632,11 +61914,11 @@ Audio:`);
61632
61914
  };
61633
61915
  }
61634
61916
  try {
61635
- const absOutput = resolve61(ctx.workingDirectory, args.output);
61917
+ const absOutput = resolve63(ctx.workingDirectory, args.output);
61636
61918
  if (args.reencode) {
61637
61919
  const ffmpegArgs = [];
61638
61920
  for (const i of args.inputs) {
61639
- ffmpegArgs.push("-i", resolve61(ctx.workingDirectory, i));
61921
+ ffmpegArgs.push("-i", resolve63(ctx.workingDirectory, i));
61640
61922
  }
61641
61923
  const filterComplex = args.inputs.map((_, i) => `[${i}:v][${i}:a]`).join("");
61642
61924
  ffmpegArgs.push(
@@ -61651,8 +61933,8 @@ Audio:`);
61651
61933
  );
61652
61934
  await execSafe("ffmpeg", ffmpegArgs, { maxBuffer: 100 * 1024 * 1024 });
61653
61935
  } else {
61654
- const tempList = resolve61(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
61655
- const listContent = args.inputs.map((i) => `file '${resolve61(ctx.workingDirectory, i)}'`).join("\n");
61936
+ const tempList = resolve63(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
61937
+ const listContent = args.inputs.map((i) => `file '${resolve63(ctx.workingDirectory, i)}'`).join("\n");
61656
61938
  await writeFile47(tempList, listContent, "utf-8");
61657
61939
  try {
61658
61940
  await execSafe(
@@ -61737,7 +62019,7 @@ A scene project is a directory that is **bilingual**: it works with both
61737
62019
  and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
61738
62020
  at render.
61739
62021
 
61740
- \`vibe scene build\` (v0.60+) is the supported one-shot driver from a
62022
+ \`vibe build\` (v0.60+) is the supported one-shot driver from a
61741
62023
  written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
61742
62024
  host agent itself authors the per-beat HTML \u2014 no internal LLM call.
61743
62025
 
@@ -61745,19 +62027,19 @@ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
61745
62027
 
61746
62028
  | Path | Command | When to use |
61747
62029
  |---|---|---|
61748
- | **One-shot (default, v0.60+)** | \`vibe scene build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
61749
- | **High-craft (manual)** | \`DESIGN.md\` + Hyperframes skill in your agent | Maximum control: hand-author each scene |
62030
+ | **One-shot (default, v0.60+)** | \`vibe build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
62031
+ | **High-craft (manual)** | \`DESIGN.md\` + local composition rules in your agent | Maximum control: hand-author each scene |
61750
62032
  | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
61751
62033
 
61752
- Recommend \`vibe scene build\` whenever the user has a STORYBOARD with
62034
+ Recommend \`vibe build\` whenever the user has a STORYBOARD with
61753
62035
  narration / backdrop intent.
61754
62036
 
61755
62037
  ## High-craft path
61756
62038
 
61757
- 1. \`vibe scene init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
62039
+ 1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
61758
62040
  \`DESIGN.md\` (palette, typography, motion, transitions) plus the
61759
62041
  \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
61760
- In Plan H this **also installs the Hyperframes skill** at the
62042
+ In Plan H this **also installs local composition rules** at the
61761
62043
  right place for your host (\`.claude/skills/hyperframes/\` for Claude
61762
62044
  Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
61763
62045
  \`SKILL.md\` for everyone else).
@@ -61768,7 +62050,7 @@ narration / backdrop intent.
61768
62050
  4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
61769
62051
  using the rules from steps 2 and 3. The skill enforces the visual
61770
62052
  identity contract \u2014 scenes that contradict DESIGN.md fail lint.
61771
- 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe scene render\`
62053
+ 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
61772
62054
  to MP4.
61773
62055
 
61774
62056
  ## Quick-draft path
@@ -61778,7 +62060,7 @@ vibe scene init my-promo -r 16:9 -d 30
61778
62060
  vibe scene add intro --style announcement \\
61779
62061
  --headline "Ship videos, not clicks"
61780
62062
  vibe scene lint
61781
- vibe scene render
62063
+ vibe render my-promo
61782
62064
  \`\`\`
61783
62065
 
61784
62066
  \`vibe scene init\` is **idempotent** \u2014 running it on an existing
@@ -61790,12 +62072,12 @@ Safe to invoke on user-provided projects.
61790
62072
  \`\`\`bash
61791
62073
  vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61792
62074
  vibe scene styles [<name>] # list / show vendored visual identities
61793
- vibe scene install-skill [<dir>] [--host all] # retroactive Hyperframes-skill install
62075
+ vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
61794
62076
  vibe scene add <name> --style <preset> [...]
61795
62077
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
61796
62078
  vibe scene lint [<root>] [--json] [--fix]
61797
62079
  vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
61798
- vibe scene build [<dir>] [--mode agent|batch|auto] # H3 dispatch
62080
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
61799
62081
  \`\`\`
61800
62082
 
61801
62083
  ## Style presets (for \`vibe scene add --style\`)
@@ -61813,18 +62095,18 @@ from the generated TTS audio.
61813
62095
  ## STORYBOARD-to-MP4 (one command, v0.60+)
61814
62096
 
61815
62097
  \`\`\`bash
61816
- vibe scene init my-promo --visual-style "Swiss Pulse" -d 12
62098
+ vibe init my-promo --visual-style "Swiss Pulse" -d 12
61817
62099
  # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
61818
- vibe scene build my-promo
62100
+ vibe build my-promo
61819
62101
  \`\`\`
61820
62102
 
61821
- \`vibe scene build\` reads the STORYBOARD frontmatter + per-beat cues,
62103
+ \`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
61822
62104
  dispatches TTS + image-gen per beat, then either:
61823
62105
 
61824
62106
  - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
61825
62107
  \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
61826
62108
  authors each \`compositions/scene-<id>.html\` itself, then re-invoking
61827
- \`vibe scene build\` proceeds to lint + render.
62109
+ \`vibe build\` proceeds to lint + render.
61828
62110
  - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
61829
62111
  Gemini) to compose the HTML, then renders.
61830
62112
 
@@ -61847,9 +62129,9 @@ and surface the error to the user.
61847
62129
  | Task | Tool |
61848
62130
  |------|------|
61849
62131
  | Generate narration + image, then author scene | \`vibe scene add\` |
61850
- | Generate a full scenes project from a STORYBOARD | \`vibe scene build\` |
62132
+ | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
61851
62133
  | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
61852
- | Render the project | \`vibe scene render\` *or* \`npx hyperframes render\` (equivalent) |
62134
+ | Render the project | \`vibe render\` *or* \`vibe scene render\` for lower-level control |
61853
62135
  | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
61854
62136
 
61855
62137
  The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
@@ -61933,8 +62215,8 @@ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
61933
62215
 
61934
62216
  ## Authoring tips
61935
62217
 
61936
- 1. **Start from examples** \u2014 \`examples/demo-pipeline.yaml\` (FFmpeg-only,
61937
- no keys), \`examples/promo-video.yaml\` (AI providers).
62218
+ 1. **Start from a tiny YAML** \u2014 keep it in your project directory and run
62219
+ \`vibe run pipeline.yaml --dry-run\` before spending provider budget.
61938
62220
  2. **Dry-run first** \u2014 you see estimated cost and resolved variable
61939
62221
  graph before spending API credits.
61940
62222
  3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
@@ -61969,18 +62251,18 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
61969
62251
  title: "Scene authoring with vibe",
61970
62252
  summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
61971
62253
  steps: [
61972
- 'Run `vibe scene init <dir> --visual-style "<style name>"` to scaffold the project + install the Hyperframes skill (Plan H).',
62254
+ 'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
61973
62255
  "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
61974
62256
  "Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
61975
- "Run `vibe scene build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
61976
- "Run `vibe scene lint --fix` to validate, then `vibe scene render` to produce the MP4."
62257
+ "Run `vibe build <dir>`. With an agent host detected, the CLI emits a `needs-author` plan; the host agent authors each `compositions/scene-<id>.html` and re-invokes to render.",
62258
+ "Run `vibe scene lint --fix` to validate, then `vibe render <dir>` to produce the MP4."
61977
62259
  ],
61978
62260
  relatedCommands: [
61979
- "vibe scene init",
62261
+ "vibe init",
61980
62262
  "vibe scene styles",
61981
62263
  "vibe scene install-skill",
61982
62264
  "vibe scene compose-prompts",
61983
- "vibe scene build",
62265
+ "vibe build",
61984
62266
  "vibe scene lint",
61985
62267
  "vibe scene render",
61986
62268
  "vibe scene add"
@@ -62306,7 +62588,7 @@ init_source();
62306
62588
  // package.json
62307
62589
  var package_default = {
62308
62590
  name: "@vibeframe/cli",
62309
- version: "0.72.0",
62591
+ version: "0.73.0",
62310
62592
  description: "VibeFrame CLI - natural language video editing from the terminal",
62311
62593
  private: false,
62312
62594
  license: "MIT",
@@ -62578,6 +62860,19 @@ projectCommand.command("create").description("Create a new project").argument("<
62578
62860
  const data = JSON.stringify(project.toJSON(), null, 2);
62579
62861
  await writeFile(outputPath, data, "utf-8");
62580
62862
  spinner2.succeed(source_default.green(`Project created: ${outputPath}`));
62863
+ if (isJsonMode()) {
62864
+ outputSuccess({
62865
+ command: "project create",
62866
+ startedAt,
62867
+ data: {
62868
+ outputPath,
62869
+ name: projectName,
62870
+ aspectRatio: options.ratio,
62871
+ frameRate: parseInt(options.fps, 10)
62872
+ }
62873
+ });
62874
+ return;
62875
+ }
62581
62876
  console.log();
62582
62877
  console.log(source_default.dim(" Name:"), projectName);
62583
62878
  console.log(source_default.dim(" Aspect Ratio:"), options.ratio);
@@ -62589,6 +62884,7 @@ projectCommand.command("create").description("Create a new project").argument("<
62589
62884
  }
62590
62885
  });
62591
62886
  projectCommand.command("info").description("Show project information").argument("<file>", "Project file path").action(async (file) => {
62887
+ const startedAt = Date.now();
62592
62888
  const spinner2 = ora("Loading project...").start();
62593
62889
  try {
62594
62890
  const filePath = await resolveProjectPath(file);
@@ -62598,6 +62894,24 @@ projectCommand.command("info").description("Show project information").argument(
62598
62894
  spinner2.stop();
62599
62895
  const summary = project.getSummary();
62600
62896
  const meta = project.getMeta();
62897
+ if (isJsonMode()) {
62898
+ outputSuccess({
62899
+ command: "project info",
62900
+ startedAt,
62901
+ data: {
62902
+ name: summary.name,
62903
+ duration: summary.duration,
62904
+ aspectRatio: summary.aspectRatio,
62905
+ frameRate: summary.frameRate,
62906
+ trackCount: summary.trackCount,
62907
+ clipCount: summary.clipCount,
62908
+ sourceCount: summary.sourceCount,
62909
+ createdAt: meta.createdAt.toISOString(),
62910
+ updatedAt: meta.updatedAt.toISOString()
62911
+ }
62912
+ });
62913
+ return;
62914
+ }
62601
62915
  console.log();
62602
62916
  console.log(source_default.bold.cyan("Project Info"));
62603
62917
  console.log(source_default.dim("\u2500".repeat(40)));
@@ -62648,6 +62962,21 @@ projectCommand.command("set").description("Update project settings").argument("<
62648
62962
  if (options.fps) project.setFrameRate(parseInt(options.fps, 10));
62649
62963
  await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62650
62964
  spinner2.succeed(source_default.green("Project updated"));
62965
+ if (isJsonMode()) {
62966
+ outputSuccess({
62967
+ command: "project set",
62968
+ startedAt,
62969
+ data: {
62970
+ file: filePath,
62971
+ updates: {
62972
+ name: options.name ?? null,
62973
+ ratio: options.ratio ?? null,
62974
+ fps: options.fps ? parseInt(options.fps, 10) : null
62975
+ }
62976
+ }
62977
+ });
62978
+ return;
62979
+ }
62651
62980
  } catch (error) {
62652
62981
  spinner2.fail("Failed to update project");
62653
62982
  const msg = error instanceof Error ? error.message : String(error);
@@ -62721,6 +63050,20 @@ timelineCommand.command("add-source").description("Add a media source to the pro
62721
63050
  });
62722
63051
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62723
63052
  spinner2.succeed(source_default.green(`Source added: ${source.id}`));
63053
+ if (isJsonMode()) {
63054
+ outputSuccess({
63055
+ command: "timeline add-source",
63056
+ startedAt,
63057
+ data: {
63058
+ id: source.id,
63059
+ name: mediaName,
63060
+ type: mediaType,
63061
+ path: absMediaPath,
63062
+ duration
63063
+ }
63064
+ });
63065
+ return;
63066
+ }
62724
63067
  console.log();
62725
63068
  console.log(source_default.dim(" Name:"), mediaName);
62726
63069
  console.log(source_default.dim(" Type:"), mediaType);
@@ -62788,6 +63131,21 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
62788
63131
  });
62789
63132
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62790
63133
  spinner2.succeed(source_default.green(`Clip added: ${clip.id}`));
63134
+ if (isJsonMode()) {
63135
+ outputSuccess({
63136
+ command: "timeline add-clip",
63137
+ startedAt,
63138
+ data: {
63139
+ id: clip.id,
63140
+ sourceId,
63141
+ sourceName: source.name,
63142
+ trackId,
63143
+ startTime,
63144
+ duration
63145
+ }
63146
+ });
63147
+ return;
63148
+ }
62791
63149
  console.log();
62792
63150
  console.log(source_default.dim(" Source:"), source.name);
62793
63151
  console.log(source_default.dim(" Track:"), trackId);
@@ -62835,6 +63193,18 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
62835
63193
  });
62836
63194
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62837
63195
  spinner2.succeed(source_default.green(`Track added: ${track.id}`));
63196
+ if (isJsonMode()) {
63197
+ outputSuccess({
63198
+ command: "timeline add-track",
63199
+ startedAt,
63200
+ data: {
63201
+ id: track.id,
63202
+ name: track.name,
63203
+ type: track.type
63204
+ }
63205
+ });
63206
+ return;
63207
+ }
62838
63208
  console.log();
62839
63209
  console.log(source_default.dim(" Name:"), track.name);
62840
63210
  console.log(source_default.dim(" Type:"), track.type);
@@ -62891,6 +63261,21 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
62891
63261
  }
62892
63262
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62893
63263
  spinner2.succeed(source_default.green(`Effect added: ${effect.id}`));
63264
+ if (isJsonMode()) {
63265
+ outputSuccess({
63266
+ command: "timeline add-effect",
63267
+ startedAt,
63268
+ data: {
63269
+ id: effect.id,
63270
+ type: effectType,
63271
+ clipId,
63272
+ startTime,
63273
+ duration,
63274
+ params
63275
+ }
63276
+ });
63277
+ return;
63278
+ }
62894
63279
  console.log();
62895
63280
  console.log(source_default.dim(" Type:"), effectType);
62896
63281
  console.log(source_default.dim(" Start:"), startTime, "s");
@@ -62940,6 +63325,18 @@ timelineCommand.command("trim").description("Trim a clip").argument("<project>",
62940
63325
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62941
63326
  const updatedClip = project.getClip(clipId);
62942
63327
  spinner2.succeed(source_default.green("Clip trimmed"));
63328
+ if (isJsonMode()) {
63329
+ outputSuccess({
63330
+ command: "timeline trim",
63331
+ startedAt,
63332
+ data: {
63333
+ id: updatedClip.id,
63334
+ startTime: updatedClip.startTime,
63335
+ duration: updatedClip.duration
63336
+ }
63337
+ });
63338
+ return;
63339
+ }
62943
63340
  console.log();
62944
63341
  console.log(source_default.dim(" Start:"), updatedClip.startTime, "s");
62945
63342
  console.log(source_default.dim(" Duration:"), updatedClip.duration, "s");
@@ -62950,17 +63347,61 @@ timelineCommand.command("trim").description("Trim a clip").argument("<project>",
62950
63347
  }
62951
63348
  });
62952
63349
  timelineCommand.command("list").description("List timeline contents").argument("<project>", "Project file path").option("--sources", "List sources only").option("--tracks", "List tracks only").option("--clips", "List clips only").action(async (projectPath, options) => {
63350
+ const startedAt = Date.now();
62953
63351
  try {
62954
63352
  const filePath = resolve15(process.cwd(), projectPath);
62955
63353
  const content = await readFile12(filePath, "utf-8");
62956
63354
  const data = JSON.parse(content);
62957
63355
  const project = Project.fromJSON(data);
62958
63356
  const showAll = !options.sources && !options.tracks && !options.clips;
63357
+ const sources = project.getSources();
63358
+ const tracks = project.getTracks();
63359
+ const clips = project.getClips();
63360
+ if (isJsonMode()) {
63361
+ const result = {};
63362
+ if (showAll || options.sources) {
63363
+ result.sources = sources.map((s) => ({
63364
+ id: s.id,
63365
+ name: s.name,
63366
+ type: s.type,
63367
+ duration: s.duration
63368
+ }));
63369
+ }
63370
+ if (showAll || options.tracks) {
63371
+ result.tracks = tracks.map((t) => ({
63372
+ id: t.id,
63373
+ name: t.name,
63374
+ type: t.type,
63375
+ isMuted: t.isMuted,
63376
+ isLocked: t.isLocked,
63377
+ isVisible: t.isVisible
63378
+ }));
63379
+ }
63380
+ if (showAll || options.clips) {
63381
+ result.clips = clips.map((c) => {
63382
+ const source = project.getSource(c.sourceId);
63383
+ return {
63384
+ id: c.id,
63385
+ sourceId: c.sourceId,
63386
+ sourceName: source?.name ?? null,
63387
+ trackId: c.trackId,
63388
+ startTime: c.startTime,
63389
+ duration: c.duration,
63390
+ effects: c.effects.map((e) => ({ id: e.id, type: e.type }))
63391
+ };
63392
+ });
63393
+ }
63394
+ outputSuccess({
63395
+ command: "timeline list",
63396
+ startedAt,
63397
+ data: result
63398
+ });
63399
+ return;
63400
+ }
62959
63401
  if (showAll || options.sources) {
62960
63402
  console.log();
62961
63403
  console.log(source_default.bold.cyan("Sources"));
62962
63404
  console.log(source_default.dim("\u2500".repeat(60)));
62963
- const sources = project.getSources();
62964
63405
  if (sources.length === 0) {
62965
63406
  console.log(source_default.dim(" (none)"));
62966
63407
  } else {
@@ -62974,7 +63415,6 @@ timelineCommand.command("list").description("List timeline contents").argument("
62974
63415
  console.log();
62975
63416
  console.log(source_default.bold.cyan("Tracks"));
62976
63417
  console.log(source_default.dim("\u2500".repeat(60)));
62977
- const tracks = project.getTracks();
62978
63418
  for (const track of tracks) {
62979
63419
  const status = [
62980
63420
  track.isMuted ? "muted" : null,
@@ -62989,7 +63429,6 @@ timelineCommand.command("list").description("List timeline contents").argument("
62989
63429
  console.log();
62990
63430
  console.log(source_default.bold.cyan("Clips"));
62991
63431
  console.log(source_default.dim("\u2500".repeat(60)));
62992
- const clips = project.getClips();
62993
63432
  if (clips.length === 0) {
62994
63433
  console.log(source_default.dim(" (none)"));
62995
63434
  } else {
@@ -63051,6 +63490,17 @@ timelineCommand.command("split").description("Split a clip at a specific time").
63051
63490
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63052
63491
  const [first, second] = result;
63053
63492
  spinner2.succeed(source_default.green("Clip split successfully"));
63493
+ if (isJsonMode()) {
63494
+ outputSuccess({
63495
+ command: "timeline split",
63496
+ startedAt,
63497
+ data: {
63498
+ first: { id: first.id, duration: first.duration },
63499
+ second: { id: second.id, duration: second.duration }
63500
+ }
63501
+ });
63502
+ return;
63503
+ }
63054
63504
  console.log();
63055
63505
  console.log(source_default.dim(" First clip:"), first.id, `(${first.duration.toFixed(2)}s)`);
63056
63506
  console.log(source_default.dim(" Second clip:"), second.id, `(${second.duration.toFixed(2)}s)`);
@@ -63097,6 +63547,19 @@ timelineCommand.command("duplicate").description("Duplicate a clip").argument("<
63097
63547
  }
63098
63548
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63099
63549
  spinner2.succeed(source_default.green(`Clip duplicated: ${duplicated.id}`));
63550
+ if (isJsonMode()) {
63551
+ outputSuccess({
63552
+ command: "timeline duplicate",
63553
+ startedAt,
63554
+ data: {
63555
+ id: duplicated.id,
63556
+ sourceClipId: clipId,
63557
+ startTime: duplicated.startTime,
63558
+ duration: duplicated.duration
63559
+ }
63560
+ });
63561
+ return;
63562
+ }
63100
63563
  console.log();
63101
63564
  console.log(source_default.dim(" Start:"), duplicated.startTime, "s");
63102
63565
  console.log(source_default.dim(" Duration:"), duplicated.duration, "s");
@@ -63141,6 +63604,17 @@ timelineCommand.command("delete").description("Delete a clip from the timeline")
63141
63604
  }
63142
63605
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63143
63606
  spinner2.succeed(source_default.green("Clip deleted"));
63607
+ if (isJsonMode()) {
63608
+ outputSuccess({
63609
+ command: "timeline delete",
63610
+ startedAt,
63611
+ data: {
63612
+ id: clipId,
63613
+ deleted: true
63614
+ }
63615
+ });
63616
+ return;
63617
+ }
63144
63618
  } catch (error) {
63145
63619
  spinner2.fail("Failed to delete clip");
63146
63620
  const msg = error instanceof Error ? error.message : String(error);
@@ -63188,6 +63662,18 @@ timelineCommand.command("move").description("Move a clip to a new position").arg
63188
63662
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63189
63663
  const updated = project.getClip(clipId);
63190
63664
  spinner2.succeed(source_default.green("Clip moved"));
63665
+ if (isJsonMode()) {
63666
+ outputSuccess({
63667
+ command: "timeline move",
63668
+ startedAt,
63669
+ data: {
63670
+ id: updated.id,
63671
+ trackId: updated.trackId,
63672
+ startTime: updated.startTime
63673
+ }
63674
+ });
63675
+ return;
63676
+ }
63191
63677
  console.log();
63192
63678
  console.log(source_default.dim(" Track:"), updated.trackId);
63193
63679
  console.log(source_default.dim(" Start:"), updated.startTime, "s");
@@ -64124,13 +64610,13 @@ init_ai_highlights();
64124
64610
  init_ai_animated_caption();
64125
64611
  init_output();
64126
64612
  var pipelineCommand = new Command("pipeline").alias("pipe").description(
64127
- "AI media transformations on existing video / audio (highlights, auto-shorts, animated captions). For BUILD-from-text flows see `vibe scene build`."
64613
+ "AI media transformations on existing video / audio (highlights, auto-shorts, animated captions). For BUILD-from-text flows see `vibe build`."
64128
64614
  ).addHelpText(
64129
64615
  "after",
64130
64616
  `
64131
64617
  Two flows \u2014 pick by intent:
64132
64618
  BUILD \u2014 text \u2192 MP4 (intent \u2192 AI generation \u2192 new video)
64133
- Use \`vibe scene build\` with a STORYBOARD.md + DESIGN.md.
64619
+ Use \`vibe build\` with a STORYBOARD.md + DESIGN.md.
64134
64620
  Idempotent, agent-editable, skills-driven (v0.60+).
64135
64621
  PROCESS \u2014 existing video/audio \u2192 transformed media
64136
64622
  Use \`vibe pipeline\` (this group) or \`vibe edit\` / \`vibe audio\`.
@@ -64142,7 +64628,7 @@ Examples (PROCESS):
64142
64628
  $ vibe pipeline animated-caption video.mp4 -o captioned.mp4 -s highlight
64143
64629
  $ vibe pipeline animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64144
64630
 
64145
- Required API Keys:
64631
+ Provider API Keys:
64146
64632
  highlights: GOOGLE_API_KEY (Gemini analysis)
64147
64633
  auto-shorts: GOOGLE_API_KEY + OPENAI_API_KEY (optional captions)
64148
64634
  animated-caption: OPENAI_API_KEY (Whisper transcription)
@@ -64153,7 +64639,7 @@ Cost tiers:
64153
64639
  animated-caption: $ Low (~$0.01)
64154
64640
  regenerate-scene: $$$ High (per-scene re-run; depends on provider)
64155
64641
 
64156
- Note: \`pipeline script-to-video\` was removed \u2014 use \`vibe scene build\` for
64642
+ Note: \`pipeline script-to-video\` was removed \u2014 use \`vibe build\` for
64157
64643
  text \u2192 MP4. \`pipeline regenerate-scene\` still operates on the
64158
64644
  storyboard.{yaml,json} layout produced by older runs.
64159
64645
 
@@ -64343,18 +64829,36 @@ function listCommands(program3) {
64343
64829
  console.log(JSON.stringify(commands, null, 2));
64344
64830
  }
64345
64831
  function extractEnumFromDescription(description) {
64346
- const providerMatch = description.match(
64347
- /(?:Provider|Providers?):\s*([a-z0-9,\s-]+?)(?:\s*\(|$)/i
64832
+ const cleaned = description.replace(/\s*\([^()]*\)/g, "");
64833
+ const finalize = (raw) => {
64834
+ const values = raw.map((v) => v.trim().replace(/^or\s+/i, "")).filter(Boolean);
64835
+ if (values.some((v) => /\s/.test(v))) return void 0;
64836
+ if (values.length >= 2 && values.length <= 12) return values;
64837
+ return void 0;
64838
+ };
64839
+ const providerMatch = cleaned.match(
64840
+ /(?:Provider|Providers?):\s*([a-z0-9,\s-]+?)$/i
64348
64841
  );
64349
64842
  if (providerMatch) {
64350
- return providerMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
64843
+ const values = finalize(providerMatch[1].split(","));
64844
+ if (values) return values;
64351
64845
  }
64352
- const styleMatch = description.match(
64353
- /^[A-Z][a-z]+:\s*([a-z0-9,\s-]+?)(?:\s*\(|$)/
64846
+ const labeledMatch = cleaned.match(
64847
+ /^([A-Z][a-z]+(?:\s+[a-z]+){0,2}):\s*([a-z0-9:,\s/-]+?)$/
64354
64848
  );
64355
- if (styleMatch) {
64356
- const values = styleMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
64357
- if (values.length >= 2 && values.length <= 10) return values;
64849
+ if (labeledMatch) {
64850
+ return finalize(labeledMatch[2].split(","));
64851
+ }
64852
+ const parenMatch = description.match(/\(([^()]+)\)\s*$/);
64853
+ if (parenMatch) {
64854
+ const inner = parenMatch[1].trim();
64855
+ if (/^default:|^e\.g\.|^or\s|via\s|run\s/i.test(inner)) return void 0;
64856
+ const rawValues = inner.split(",").map((s) => s.trim()).filter(Boolean);
64857
+ const values = rawValues.map((v) => v.replace(/^or\s+/i, "").trim());
64858
+ const looksLikeEnum = values.every(
64859
+ (v) => /^[a-z0-9][a-z0-9:/_-]*$/i.test(v) && v.length <= 24
64860
+ );
64861
+ if (looksLikeEnum && values.length >= 2 && values.length <= 12) return values;
64358
64862
  }
64359
64863
  return void 0;
64360
64864
  }
@@ -68849,7 +69353,7 @@ init_detect();
68849
69353
  // src/commands/setup.ts
68850
69354
  init_esm();
68851
69355
  init_source();
68852
- var import_dotenv2 = __toESM(require_main(), 1);
69356
+ var import_dotenv3 = __toESM(require_main(), 1);
68853
69357
  init_config();
68854
69358
  init_output();
68855
69359
  init_tty();
@@ -68898,7 +69402,7 @@ var AI_FEATURES = [
68898
69402
  {
68899
69403
  label: "Videos",
68900
69404
  desc: "generate + extend",
68901
- defaultProvider: "fal.ai Seedance 2.0 (Artificial Analysis #2 t2v + i2v, since v0.57)",
69405
+ defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
68902
69406
  alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
68903
69407
  keys: [{ configKey: "fal", envVar: "FAL_KEY", name: "fal.ai", url: "https://fal.ai/dashboard/keys", what: "ByteDance Seedance 2.0 text-to-video and image-to-video" }],
68904
69408
  tryCommand: 'vibe generate video "ocean waves" -o waves.mp4'
@@ -68973,7 +69477,7 @@ async function runSetupWizard(fullSetup = false) {
68973
69477
  console.log();
68974
69478
  await collectKeys(config2, pipelineKeys);
68975
69479
  await saveConfig(config2);
68976
- showComplete(config2, "vibe scene build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
69480
+ showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
68977
69481
  return;
68978
69482
  }
68979
69483
  console.log(source_default.dim("Step 2 of 2"));
@@ -69138,7 +69642,7 @@ function showComplete(config2, defaultTryCommand, features = []) {
69138
69642
  console.log(source_default.bold(" Next steps:"));
69139
69643
  console.log(source_default.dim(" cd <project>; vibe init Scaffold AGENTS.md / CLAUDE.md / .env.example (project scope)"));
69140
69644
  console.log(source_default.dim(" vibe doctor Check system health + available commands"));
69141
- console.log(source_default.dim(" vibe schema --list Discover all 69 commands"));
69645
+ console.log(source_default.dim(" vibe schema --list Discover all 81 commands"));
69142
69646
  console.log(source_default.dim(" vibe setup Re-run user-scope setup anytime"));
69143
69647
  const hosts = detectedAgentHosts();
69144
69648
  const primary = hosts[0];
@@ -69151,14 +69655,14 @@ function showComplete(config2, defaultTryCommand, features = []) {
69151
69655
  );
69152
69656
  console.log(
69153
69657
  source_default.dim(
69154
- ` Scene composer will auto-dispatch to ${primary.label} (${source_default.bold("--mode agent")}). Run \`vibe scene init my-promo\` to scaffold a scene project + install the Hyperframes skill.`
69658
+ ` Scene composer will auto-dispatch to ${primary.label} (${source_default.bold("--mode agent")}). Run \`vibe init my-promo\` to scaffold a video project + install local composition rules.`
69155
69659
  )
69156
69660
  );
69157
69661
  } else {
69158
69662
  console.log();
69159
69663
  console.log(
69160
69664
  source_default.dim(
69161
- ` No agent host detected \u2014 \`vibe scene build\` will use the internal LLM composer (${source_default.bold("--mode batch")}).`
69665
+ ` No agent host detected \u2014 \`vibe build\` will use the internal LLM composer (${source_default.bold("--mode batch")}).`
69162
69666
  )
69163
69667
  );
69164
69668
  }
@@ -69207,7 +69711,7 @@ async function showConfig() {
69207
69711
  if (hasCwdEnv) {
69208
69712
  try {
69209
69713
  const envContent = await readFile33(cwdEnvPath, "utf-8");
69210
- dotenvKeys = (0, import_dotenv2.parse)(Buffer.from(envContent));
69714
+ dotenvKeys = (0, import_dotenv3.parse)(Buffer.from(envContent));
69211
69715
  } catch {
69212
69716
  }
69213
69717
  }
@@ -69269,6 +69773,9 @@ async function showConfig() {
69269
69773
  init_esm();
69270
69774
  init_source();
69271
69775
  init_agent_host_detect();
69776
+ init_scene_project();
69777
+ init_visual_styles();
69778
+ init_install_skill();
69272
69779
  import { existsSync as existsSync34 } from "node:fs";
69273
69780
  import { mkdir as mkdir20, readFile as readFile34, writeFile as writeFile39 } from "node:fs/promises";
69274
69781
  import { basename as basename19, dirname as dirname18, resolve as resolve48 } from "node:path";
@@ -69322,9 +69829,9 @@ VibeFrame splits cleanly into two flows. Routing the user's request
69322
69829
  correctly is the most important judgement call you'll make.
69323
69830
 
69324
69831
  **BUILD \u2014 create new video from text intent.**
69325
- Use \`vibe scene build\` with a STORYBOARD.md + DESIGN.md. The
69832
+ Use \`vibe build\` with a STORYBOARD.md + DESIGN.md. The
69326
69833
  skills-driven pipeline (v0.60+) dispatches narration TTS + backdrop
69327
- image-gen per beat, composes scene HTML via the Hyperframes skill
69834
+ image-gen per beat, composes scene HTML via the bundled composition rules
69328
69835
  bundle, then renders to MP4. Idempotent re-runs reuse cached assets.
69329
69836
 
69330
69837
  **PROCESS \u2014 transform existing video / audio.**
@@ -69341,8 +69848,8 @@ PROCESS.
69341
69848
 
69342
69849
  | Task | Command |
69343
69850
  |---|---|
69344
- | One-shot storyboard \u2192 MP4 | \`vibe scene build [project-dir]\` |
69345
- | Render an existing scene project | \`vibe scene render -o out.mp4\` |
69851
+ | One-shot storyboard \u2192 video | \`vibe build [project-dir]\` |
69852
+ | Render an existing scene project | \`vibe render [project-dir] -o out.mp4\` |
69346
69853
  | Lint scene HTML | \`vibe scene lint --json\` |
69347
69854
  | Generate a single image | \`vibe generate image "prompt" -o img.png --quality hd\` |
69348
69855
  | Generate a single video | \`vibe generate video "prompt" -i image.png -o clip.mp4\` |
@@ -69375,7 +69882,7 @@ PROCESS.
69375
69882
  | **Free** | \`detect *\`, \`edit silence-cut/fade/noise-reduce\`, \`schema\`, \`project\`, \`timeline\` | $0 |
69376
69883
  | **Low** | \`analyze *\`, \`audio transcribe\`, \`generate image\` | $0.01\u2013$0.10 |
69377
69884
  | **High** | \`generate video\`, \`edit image\` | $1\u2013$5 |
69378
- | **Very High** | \`pipeline *\` (highlights, auto-shorts, regenerate-scene), \`scene build\` | $5\u2013$50+ |
69885
+ | **Very High** | \`pipeline *\` (highlights, auto-shorts, regenerate-scene), \`build\` | $5\u2013$50+ |
69379
69886
 
69380
69887
  ## Agent invariants
69381
69888
 
@@ -69396,9 +69903,9 @@ API keys live in \`.env\` (gitignored). Copy \`.env.example\` to start. Run
69396
69903
  \`vibe doctor\` to see which keys are currently detected and which providers
69397
69904
  they unlock.
69398
69905
 
69399
- ### Hyperframes skill (scene HTML rules)
69906
+ ### Composition rules (scene HTML)
69400
69907
 
69401
- \`vibe scene init\` installs the Hyperframes skill into your project. The
69908
+ \`vibe init\` installs local composition rules into your project. The
69402
69909
  universal copy lives at \`SKILL.md\` (with \`references/*.md\`), and
69403
69910
  host-specific copies are placed where each agent expects them
69404
69911
  (\`.claude/skills/hyperframes/\` for Claude Code, \`.cursor/rules/hyperframes.mdc\`
@@ -69413,7 +69920,7 @@ To retro-install on a project scaffolded before this command existed, run
69413
69920
 
69414
69921
  ### Scene composer (batch / non-agent fallback)
69415
69922
 
69416
- When you don't want to author HTML yourself, \`vibe scene build\` runs an
69923
+ When you don't want to author HTML yourself, \`vibe build --mode batch\` runs an
69417
69924
  LLM internally with the same skill bundle. It auto-picks a provider based
69418
69925
  on available keys (\`claude > gemini > openai\`) \u2014 pass \`--composer <name>\`
69419
69926
  to force one:
@@ -69453,7 +69960,7 @@ pack is available as slash commands (consolidated to 2 in v0.62 \u2014 the
69453
69960
  overview content moved into AGENTS.md above):
69454
69961
 
69455
69962
  - \`/vibe-pipeline\` \u2014 YAML pipeline authoring helper (Video as Code)
69456
- - \`/vibe-scene\` \u2014 per-scene HTML authoring + \`vibe scene build\`
69963
+ - \`/vibe-scene\` \u2014 per-scene HTML authoring + \`vibe build\`
69457
69964
 
69458
69965
  To install / update them later:
69459
69966
 
@@ -69518,7 +70025,7 @@ renders/
69518
70025
  `;
69519
70026
  function renderProjectYaml(opts) {
69520
70027
  return `# VibeFrame project config. Used by \`vibe scene render\` to name
69521
- # outputs and by \`vibe scene build\` for default providers / budget.
70028
+ # outputs and by \`vibe build\` for default providers / budget.
69522
70029
  name: ${opts.name}
69523
70030
  aspect: "16:9"
69524
70031
  defaults:
@@ -69539,8 +70046,16 @@ defaults:
69539
70046
  // src/commands/init.ts
69540
70047
  init_output();
69541
70048
  var VALID_AGENTS = ["claude-code", "codex", "cursor", "aider", "gemini-cli", "opencode", "all", "auto"];
69542
- var initCommand = new Command("init").description("Scaffold project-scope agent files (AGENTS.md / CLAUDE.md / .env.example / .gitignore)").argument("[project-dir]", "Project directory (defaults to cwd)", ".").option("--agent <id>", `Agent target: ${VALID_AGENTS.join(" | ")}`, "auto").option("--force", "Overwrite existing files instead of skipping").option("--dry-run", "Print the file list without writing anything").action(async (projectDirArg, options) => {
70049
+ var initCommand = new Command("init").description("Scaffold a VibeFrame project (video scene project or project-scope agent files)").argument("[project-dir]", "Project directory (defaults to cwd)", ".").option("--type <type>", "Project type: scene (video project) | agent (agent files only)", "scene").option("--profile <profile>", "Scene profile: minimal (storyboard/design only), agent (recommended), full (render scaffold upfront)", "agent").option("-r, --ratio <ratio>", "Scene aspect ratio: 16:9, 9:16, 1:1, 4:5", "16:9").option("-d, --duration <sec>", "Default scene/root duration in seconds", "10").option("--visual-style <name>", "Seed scene DESIGN.md from a named style").option("--agent <id>", `Agent target: ${VALID_AGENTS.join(" | ")}`, "auto").option("--force", "Overwrite existing files instead of skipping").option("--dry-run", "Print the file list without writing anything").action(async (projectDirArg, options) => {
69543
70050
  const startedAt = Date.now();
70051
+ const initType = String(options.type ?? "scene");
70052
+ if (initType !== "scene" && initType !== "agent") {
70053
+ exitWithError(usageError(`Invalid --type: ${initType}`, "Must be one of: scene, agent"));
70054
+ }
70055
+ if (initType === "scene") {
70056
+ await runSceneInit(projectDirArg, options, startedAt);
70057
+ return;
70058
+ }
69544
70059
  const agent = options.agent;
69545
70060
  if (!VALID_AGENTS.includes(agent)) {
69546
70061
  exitWithError(usageError(`Invalid --agent: ${agent}`, `Must be one of: ${VALID_AGENTS.join(", ")}`));
@@ -69642,6 +70157,150 @@ var initCommand = new Command("init").description("Scaffold project-scope agent
69642
70157
  console.log(source_default.dim(" vibe scene init my-promo Scaffold a starter scene project"));
69643
70158
  console.log();
69644
70159
  });
70160
+ var VALID_SCENE_ASPECTS = ["16:9", "9:16", "1:1", "4:5"];
70161
+ async function runSceneInit(projectDirArg, options, startedAt) {
70162
+ const profile = String(options.profile ?? "agent");
70163
+ if (!isSceneScaffoldProfile(profile)) {
70164
+ exitWithError(usageError(`Invalid --profile: ${profile}`, "Must be one of: minimal, agent, full"));
70165
+ }
70166
+ const aspect = String(options.ratio ?? "16:9");
70167
+ if (!VALID_SCENE_ASPECTS.includes(aspect)) {
70168
+ exitWithError(usageError(`Invalid --ratio: ${aspect}`, `Must be one of: ${VALID_SCENE_ASPECTS.join(", ")}`));
70169
+ }
70170
+ const duration = Number.parseFloat(String(options.duration ?? "10"));
70171
+ if (!Number.isFinite(duration) || duration <= 0 || duration > 3600) {
70172
+ exitWithError(usageError(`Invalid --duration: ${String(options.duration)}`, "Duration must be a positive number of seconds (\u22643600)"));
70173
+ }
70174
+ const visualStyle = options.visualStyle ? getVisualStyle(String(options.visualStyle)) : void 0;
70175
+ if (options.visualStyle && !visualStyle) {
70176
+ exitWithError(usageError(`Unknown visual style: ${String(options.visualStyle)}`, `Valid: ${visualStyleNames()}. Browse with \`vibe scene styles\`.`));
70177
+ }
70178
+ const projectDir = resolve48(projectDirArg);
70179
+ const projectName = basename19(projectDir);
70180
+ const groups = describeSceneScaffold({ dir: projectDir, profile });
70181
+ if (options.dryRun) {
70182
+ if (!isJsonMode()) {
70183
+ printSceneInitPlan({
70184
+ projectDir,
70185
+ profile,
70186
+ aspect,
70187
+ duration,
70188
+ visualStyleName: visualStyle?.name ?? null,
70189
+ groups,
70190
+ dryRun: true
70191
+ });
70192
+ return;
70193
+ }
70194
+ outputSuccess({
70195
+ command: "init",
70196
+ startedAt,
70197
+ dryRun: true,
70198
+ data: {
70199
+ type: "scene",
70200
+ projectDir,
70201
+ name: projectName,
70202
+ profile,
70203
+ aspect,
70204
+ duration,
70205
+ visualStyle: visualStyle?.name ?? null,
70206
+ groups
70207
+ }
70208
+ });
70209
+ return;
70210
+ }
70211
+ const result = await scaffoldSceneProject({
70212
+ dir: projectDir,
70213
+ name: projectName,
70214
+ aspect,
70215
+ duration,
70216
+ visualStyle,
70217
+ profile
70218
+ });
70219
+ const detectedIds = detectedAgentHosts().map((h) => h.id);
70220
+ const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
70221
+ projectDir,
70222
+ hosts: deriveInstallHosts(detectedIds)
70223
+ }) : { success: true, files: [], bundleVersion: "not-installed" };
70224
+ if (isJsonMode()) {
70225
+ outputSuccess({
70226
+ command: "init",
70227
+ startedAt,
70228
+ data: {
70229
+ type: "scene",
70230
+ projectDir,
70231
+ name: projectName,
70232
+ profile,
70233
+ aspect,
70234
+ duration,
70235
+ visualStyle: visualStyle?.name ?? null,
70236
+ created: result.created,
70237
+ merged: result.merged,
70238
+ skipped: result.skipped,
70239
+ groups: result.groups,
70240
+ skillFiles: skillResult.files,
70241
+ skillBundleVersion: skillResult.bundleVersion
70242
+ }
70243
+ });
70244
+ return;
70245
+ }
70246
+ console.log();
70247
+ console.log(source_default.bold.magenta("VibeFrame Init") + source_default.dim(" \u2014 video project ready"));
70248
+ console.log(source_default.dim("\u2500".repeat(60)));
70249
+ console.log(`${source_default.dim("Project")} ${projectDir}`);
70250
+ console.log(`${source_default.dim("Profile")} ${profile} ${source_default.dim(sceneProfileSummary(profile))}`);
70251
+ console.log();
70252
+ console.log(source_default.bold("Edit first"));
70253
+ console.log(` ${source_default.cyan("STORYBOARD.md")} ${source_default.dim("beats: narration, backdrop, minimum duration")}`);
70254
+ console.log(` ${source_default.cyan("DESIGN.md")} ${source_default.dim("palette, typography, motion rules")}`);
70255
+ console.log();
70256
+ console.log(source_default.bold("Then run"));
70257
+ console.log(` ${source_default.cyan("vibe build")} ${source_default.dim("build storyboard assets/compositions")}`);
70258
+ console.log(` ${source_default.cyan("vibe render")} ${source_default.dim("render final MP4")}`);
70259
+ console.log();
70260
+ console.log(source_default.bold("Files"));
70261
+ for (const p of result.created) console.log(source_default.green(` + ${p.replace(projectDir + "/", "")}`));
70262
+ for (const p of result.merged) console.log(source_default.yellow(` ~ ${p.replace(projectDir + "/", "")} (merged)`));
70263
+ for (const p of result.skipped) console.log(source_default.dim(` \xB7 ${p.replace(projectDir + "/", "")} (kept existing)`));
70264
+ for (const f of skillResult.files.filter((f2) => f2.status === "wrote")) {
70265
+ console.log(source_default.green(` + ${f.path.replace(projectDir + "/", "")}`));
70266
+ }
70267
+ console.log();
70268
+ console.log(source_default.dim(`Tip: cd ${projectDirArg} before running the next commands.`));
70269
+ console.log();
70270
+ }
70271
+ function sceneProfileSummary(profile) {
70272
+ if (profile === "minimal") return "(storyboard/design only)";
70273
+ if (profile === "agent") return "(recommended: + local agent authoring rules)";
70274
+ return "(+ render scaffold upfront)";
70275
+ }
70276
+ function printSceneInitPlan(opts) {
70277
+ console.log();
70278
+ console.log(source_default.bold.magenta("VibeFrame Init") + source_default.dim(opts.dryRun ? " \u2014 dry run" : ""));
70279
+ console.log(source_default.dim("\u2500".repeat(60)));
70280
+ console.log(`${source_default.dim("Project")} ${opts.projectDir}`);
70281
+ console.log(`${source_default.dim("Profile")} ${opts.profile} ${source_default.dim(sceneProfileSummary(opts.profile))}`);
70282
+ console.log(`${source_default.dim("Canvas")} ${opts.aspect} \xB7 ${opts.duration}s default`);
70283
+ if (opts.visualStyleName) console.log(`${source_default.dim("Style")} ${opts.visualStyleName}`);
70284
+ console.log();
70285
+ console.log(source_default.bold("Edit first"));
70286
+ console.log(` ${source_default.cyan("STORYBOARD.md")} ${source_default.dim("beats: narration, backdrop, minimum duration")}`);
70287
+ console.log(` ${source_default.cyan("DESIGN.md")} ${source_default.dim("palette, typography, motion rules")}`);
70288
+ console.log();
70289
+ console.log(source_default.bold("Project contents"));
70290
+ printGroup("authoring", opts.groups.authoring, opts.projectDir);
70291
+ printGroup("agent", opts.groups.agent, opts.projectDir);
70292
+ printGroup("render", opts.groups.render, opts.projectDir);
70293
+ console.log();
70294
+ console.log(source_default.dim("Dry run \u2014 no files were written. Re-run without --dry-run to create the project."));
70295
+ console.log();
70296
+ }
70297
+ function printGroup(label, files, projectDir) {
70298
+ if (files.length === 0) return;
70299
+ console.log(` ${source_default.bold(label)}`);
70300
+ for (const file of files) {
70301
+ console.log(source_default.dim(` ${file.replace(projectDir + "/", "")}`));
70302
+ }
70303
+ }
69645
70304
  function resolveTargets(agent) {
69646
70305
  if (agent === "all") {
69647
70306
  return ["claude-code", "codex", "cursor", "aider", "gemini-cli", "opencode"];
@@ -69696,6 +70355,306 @@ function formatStatusIcon(status) {
69696
70355
  }
69697
70356
  }
69698
70357
 
70358
+ // src/commands/build.ts
70359
+ init_esm();
70360
+ init_source();
70361
+ init_ora();
70362
+ init_scene_build();
70363
+ init_output();
70364
+ import { resolve as resolve49 } from "node:path";
70365
+ var VALID_MODES = ["agent", "batch", "auto"];
70366
+ var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", "Image provider: openai").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText("after", `
70367
+ Examples:
70368
+ $ vibe init my-video --profile agent
70369
+ $ vibe build my-video --mode agent --tts kokoro
70370
+ $ vibe build my-video --skip-render
70371
+
70372
+ Advanced equivalent: \`vibe scene build\`.`).action(async (projectDirArg, options) => {
70373
+ const startedAt = Date.now();
70374
+ const projectDir = resolve49(projectDirArg);
70375
+ const mode = String(options.mode ?? "auto");
70376
+ if (!VALID_MODES.includes(mode)) {
70377
+ exitWithError(usageError(`Invalid --mode: ${mode}`, `Must be one of: ${VALID_MODES.join(", ")}`));
70378
+ }
70379
+ const params = {
70380
+ projectDir,
70381
+ mode,
70382
+ effort: options.effort,
70383
+ composer: options.composer,
70384
+ skipNarration: options.skipNarration ?? false,
70385
+ skipBackdrop: options.skipBackdrop ?? false,
70386
+ skipRender: options.skipRender ?? false,
70387
+ ttsProvider: options.tts,
70388
+ voice: options.voice,
70389
+ imageProvider: options.imageProvider,
70390
+ imageQuality: options.quality,
70391
+ imageSize: options.imageSize,
70392
+ force: options.force ?? false
70393
+ };
70394
+ if (options.dryRun) {
70395
+ if (!isJsonMode() && !isQuietMode()) {
70396
+ printBuildDryRun(projectDirArg, params);
70397
+ return;
70398
+ }
70399
+ outputSuccess({
70400
+ command: "build",
70401
+ startedAt,
70402
+ dryRun: true,
70403
+ data: { params }
70404
+ });
70405
+ return;
70406
+ }
70407
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora("Building video project...").start();
70408
+ const result = await executeSceneBuild({
70409
+ projectDir,
70410
+ mode,
70411
+ effort: options.effort,
70412
+ composer: options.composer,
70413
+ skipNarration: options.skipNarration,
70414
+ skipBackdrop: options.skipBackdrop,
70415
+ skipRender: options.skipRender,
70416
+ ttsProvider: options.tts,
70417
+ voice: options.voice,
70418
+ imageProvider: options.imageProvider,
70419
+ imageQuality: options.quality,
70420
+ imageSize: options.imageSize,
70421
+ force: options.force,
70422
+ onProgress: (e) => {
70423
+ if (!spinner2) return;
70424
+ if (e.type === "phase-start") spinner2.text = `Phase: ${e.phase}...`;
70425
+ else if (e.type === "render-done") spinner2.text = `Rendered: ${e.outputPath}`;
70426
+ }
70427
+ });
70428
+ if (!result.success) {
70429
+ spinner2?.fail(`Build failed: ${result.error}`);
70430
+ if (isJsonMode()) {
70431
+ outputSuccess({ command: "build", startedAt, data: { ...result } });
70432
+ process.exitCode = 1;
70433
+ return;
70434
+ }
70435
+ exitWithError(generalError(result.error ?? "Build failed"));
70436
+ }
70437
+ if (isJsonMode() || isQuietMode()) {
70438
+ outputSuccess({ command: "build", startedAt, data: { ...result } });
70439
+ return;
70440
+ }
70441
+ if (result.phase === "needs-author") {
70442
+ printNeedsAuthor(result);
70443
+ return;
70444
+ }
70445
+ printBuildResult(spinner2, result, projectDirArg);
70446
+ });
70447
+ function printBuildDryRun(projectDirArg, params) {
70448
+ console.log();
70449
+ console.log(source_default.bold.cyan("VibeFrame Build - dry run"));
70450
+ console.log(source_default.dim("-".repeat(60)));
70451
+ console.log(` Project: ${source_default.bold(projectDirArg)}`);
70452
+ console.log(` Mode: ${source_default.bold(params.mode)}`);
70453
+ console.log(` Composer: ${source_default.bold(String(params.composer ?? "auto"))}`);
70454
+ console.log(` TTS: ${source_default.bold(String(params.ttsProvider ?? "auto"))}${params.skipNarration ? source_default.dim(" (skipped)") : ""}`);
70455
+ console.log(` Image: ${source_default.bold(String(params.imageProvider ?? "openai"))} ${source_default.dim(`${params.imageQuality} ${params.imageSize}`)}${params.skipBackdrop ? source_default.dim(" (skipped)") : ""}`);
70456
+ console.log(` Render: ${source_default.bold(params.skipRender ? "skip" : "yes")}`);
70457
+ console.log(` Regenerate: ${source_default.bold(params.force ? "yes" : "cache when possible")}`);
70458
+ console.log();
70459
+ console.log(source_default.dim("No assets, provider calls, or video files were created."));
70460
+ }
70461
+ function printNeedsAuthor(result) {
70462
+ console.log(source_default.cyan("Agent authoring required"));
70463
+ console.log();
70464
+ console.log(source_default.bold.cyan("Missing compositions"));
70465
+ console.log(source_default.dim("-".repeat(60)));
70466
+ const missing = result.composePrompts?.beats.filter((beat) => !beat.exists) ?? [];
70467
+ if (missing.length === 0) {
70468
+ console.log(source_default.dim(" No missing files were reported. Re-run `vibe build` to continue."));
70469
+ } else {
70470
+ for (const beat of missing) {
70471
+ const dur = beat.duration !== void 0 ? source_default.dim(` ${beat.duration}s`) : "";
70472
+ console.log(` ${source_default.bold(beat.id)}${dur} -> ${beat.outputPath}`);
70473
+ }
70474
+ }
70475
+ console.log();
70476
+ console.log(source_default.bold.cyan("Next"));
70477
+ console.log(source_default.dim("-".repeat(60)));
70478
+ console.log(" Ask your coding agent to author the missing HTML files.");
70479
+ console.log(" Then run: vibe build");
70480
+ console.log(source_default.dim(" Use `vibe build --mode batch` when you want the CLI to call an LLM composer instead."));
70481
+ }
70482
+ function printBuildResult(spinner2, result, projectDirArg) {
70483
+ spinner2?.succeed(source_default.green(result.outputPath ? `Build complete: ${result.outputPath}` : "Build complete"));
70484
+ console.log();
70485
+ console.log(source_default.bold.cyan("Beats"));
70486
+ console.log(source_default.dim("-".repeat(60)));
70487
+ for (const beat of result.beats) {
70488
+ const narration = formatPrimitiveStatus2(beat.narrationStatus, beat.narrationPath);
70489
+ const backdrop = formatPrimitiveStatus2(beat.backdropStatus, beat.backdropPath);
70490
+ console.log(` ${source_default.bold(beat.beatId.padEnd(12))} narration: ${narration} backdrop: ${backdrop}`);
70491
+ if (beat.narrationError) console.log(source_default.red(` ! narration: ${beat.narrationError}`));
70492
+ if (beat.backdropError) console.log(source_default.red(` ! backdrop: ${beat.backdropError}`));
70493
+ }
70494
+ if (result.composeData) {
70495
+ console.log();
70496
+ console.log(source_default.bold.cyan("Compose"));
70497
+ console.log(source_default.dim("-".repeat(60)));
70498
+ console.log(` beats ${result.composeData.beats}`);
70499
+ console.log(` cache ${result.composeData.cacheHits} hit / ${result.composeData.beats - result.composeData.cacheHits} fresh`);
70500
+ console.log(` cost $${result.composeData.totalCostUsd.toFixed(4)}`);
70501
+ }
70502
+ console.log();
70503
+ console.log(source_default.bold.cyan("Next"));
70504
+ console.log(source_default.dim("-".repeat(60)));
70505
+ if (result.outputPath) {
70506
+ console.log(` Watch: ${result.outputPath}`);
70507
+ } else {
70508
+ console.log(` Render: vibe render ${projectDirArg}`);
70509
+ }
70510
+ console.log(source_default.dim(` Total: ${(result.totalLatencyMs / 1e3).toFixed(1)}s`));
70511
+ }
70512
+ function formatPrimitiveStatus2(status, path2) {
70513
+ switch (status) {
70514
+ case "generated":
70515
+ return source_default.green(path2 ?? "generated");
70516
+ case "cached":
70517
+ return source_default.dim(path2 ?? "cached");
70518
+ case "skipped":
70519
+ return source_default.dim("skipped");
70520
+ case "missing":
70521
+ return source_default.yellow("missing cue");
70522
+ case "failed":
70523
+ return source_default.red("failed");
70524
+ default:
70525
+ return status;
70526
+ }
70527
+ }
70528
+
70529
+ // src/commands/render.ts
70530
+ init_esm();
70531
+ init_source();
70532
+ init_ora();
70533
+ init_scene_render();
70534
+ init_output();
70535
+ import { resolve as resolve50 } from "node:path";
70536
+ var VALID_FPS2 = [24, 30, 60];
70537
+ var VALID_QUALITIES2 = ["draft", "standard", "high"];
70538
+ var VALID_FORMATS2 = ["mp4", "webm", "mov"];
70539
+ var renderCommand = new Command("render").description("Render a VibeFrame video project to MP4/WebM/MOV").argument("[project-dir]", "Video project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--root <file>", "Root composition file", "index.html").option("--fps <n>", `Frames per second: ${VALID_FPS2.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES2.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS2.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").addHelpText("after", `
70540
+ Examples:
70541
+ $ vibe render my-video
70542
+ $ vibe render my-video -o renders/final.mp4 --quality high
70543
+
70544
+ Alias note: this is the project-level entrypoint for \`vibe scene render\`.`).action(async (projectDirArg, options) => {
70545
+ const startedAt = Date.now();
70546
+ const projectDir = resolve50(projectDirArg);
70547
+ const fps = parseFps(String(options.fps));
70548
+ const quality = parseQuality(String(options.quality));
70549
+ const format = parseFormat(String(options.format));
70550
+ const workers = parseWorkers(String(options.workers));
70551
+ const params = {
70552
+ projectDir,
70553
+ root: options.root,
70554
+ output: options.out,
70555
+ fps,
70556
+ quality,
70557
+ format,
70558
+ workers
70559
+ };
70560
+ if (options.dryRun) {
70561
+ if (!isJsonMode() && !isQuietMode()) {
70562
+ printRenderDryRun(projectDirArg, params);
70563
+ return;
70564
+ }
70565
+ outputSuccess({
70566
+ command: "render",
70567
+ startedAt,
70568
+ dryRun: true,
70569
+ data: { params }
70570
+ });
70571
+ return;
70572
+ }
70573
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora("Rendering video project...").start();
70574
+ const result = await executeSceneRender({
70575
+ projectDir,
70576
+ root: options.root,
70577
+ output: options.out,
70578
+ fps,
70579
+ quality,
70580
+ format,
70581
+ workers,
70582
+ onProgress: (pct, stage) => {
70583
+ if (spinner2) spinner2.text = `Rendering [${Math.round(pct * 100)}%] ${stage}`;
70584
+ }
70585
+ });
70586
+ if (!result.success) {
70587
+ spinner2?.fail("Render failed");
70588
+ if (isJsonMode()) {
70589
+ outputSuccess({ command: "render", startedAt, data: { ...result } });
70590
+ process.exitCode = 1;
70591
+ return;
70592
+ }
70593
+ exitWithError(generalError(result.error ?? "Render failed"));
70594
+ }
70595
+ if (isJsonMode() || isQuietMode()) {
70596
+ outputSuccess({ command: "render", startedAt, data: { ...result } });
70597
+ return;
70598
+ }
70599
+ printRenderResult(spinner2, result);
70600
+ });
70601
+ function printRenderDryRun(projectDirArg, params) {
70602
+ console.log();
70603
+ console.log(source_default.bold.cyan("VibeFrame Render - dry run"));
70604
+ console.log(source_default.dim("-".repeat(60)));
70605
+ console.log(` Project: ${source_default.bold(projectDirArg)}`);
70606
+ console.log(` Root: ${source_default.bold(String(params.root))}`);
70607
+ console.log(` Output: ${source_default.bold(String(params.output ?? `renders/<name>-<timestamp>.${params.format}`))}`);
70608
+ console.log(` Format: ${source_default.bold(params.format)}`);
70609
+ console.log(` Quality/FPS: ${source_default.bold(`${params.quality} / ${params.fps}`)}`);
70610
+ console.log(` Workers: ${source_default.bold(String(params.workers))}`);
70611
+ console.log();
70612
+ console.log(source_default.dim("No browser capture, FFmpeg mux, or video files were created."));
70613
+ }
70614
+ function printRenderResult(spinner2, result) {
70615
+ spinner2?.succeed(source_default.green(`Render complete: ${result.outputPath}`));
70616
+ console.log();
70617
+ console.log(source_default.bold.cyan("Output"));
70618
+ console.log(source_default.dim("-".repeat(60)));
70619
+ console.log(` File: ${source_default.bold(result.outputPath)}`);
70620
+ console.log(` Format: ${result.format ?? "mp4"}`);
70621
+ console.log(` Quality: ${result.quality ?? "standard"}`);
70622
+ console.log(` FPS: ${result.fps ?? 30}`);
70623
+ if (result.totalFrames !== void 0) console.log(` Frames: ${result.framesRendered ?? result.totalFrames}/${result.totalFrames}`);
70624
+ if (result.durationMs !== void 0) console.log(` Duration: ${(result.durationMs / 1e3).toFixed(2)}s`);
70625
+ if (result.audioCount !== void 0) {
70626
+ const audio = result.audioCount > 0 ? `${result.audioCount} track${result.audioCount === 1 ? "" : "s"} muxed` : "silent";
70627
+ console.log(` Audio: ${audio}`);
70628
+ }
70629
+ if (result.audioMuxWarning) console.log(source_default.yellow(` Warning: ${result.audioMuxWarning}`));
70630
+ }
70631
+ function parseFps(value) {
70632
+ const n = Number.parseInt(value, 10);
70633
+ if (!VALID_FPS2.includes(n)) {
70634
+ exitWithError(usageError(`Invalid --fps: ${value}`, `Valid: ${VALID_FPS2.join(", ")}`));
70635
+ }
70636
+ return n;
70637
+ }
70638
+ function parseQuality(value) {
70639
+ if (!VALID_QUALITIES2.includes(value)) {
70640
+ exitWithError(usageError(`Invalid --quality: ${value}`, `Valid: ${VALID_QUALITIES2.join(", ")}`));
70641
+ }
70642
+ return value;
70643
+ }
70644
+ function parseFormat(value) {
70645
+ if (!VALID_FORMATS2.includes(value)) {
70646
+ exitWithError(usageError(`Invalid --format: ${value}`, `Valid: ${VALID_FORMATS2.join(", ")}`));
70647
+ }
70648
+ return value;
70649
+ }
70650
+ function parseWorkers(value) {
70651
+ const n = Number.parseInt(value, 10);
70652
+ if (!Number.isFinite(n) || n < 1 || n > 16) {
70653
+ exitWithError(usageError(`Invalid --workers: ${value}`, "Must be an integer between 1 and 16"));
70654
+ }
70655
+ return n;
70656
+ }
70657
+
69699
70658
  // src/commands/doctor.ts
69700
70659
  init_esm();
69701
70660
  init_source();
@@ -69711,7 +70670,7 @@ init_scene_build();
69711
70670
  init_output();
69712
70671
  import { access as access7 } from "node:fs/promises";
69713
70672
  import { existsSync as existsSync35 } from "node:fs";
69714
- import { resolve as resolve49 } from "node:path";
70673
+ import { resolve as resolve51 } from "node:path";
69715
70674
  var COMMAND_KEY_MAP = getCommandKeyMap();
69716
70675
  var FREE_COMMANDS = [
69717
70676
  "edit silence-cut",
@@ -69868,7 +70827,7 @@ async function runDiagnostics() {
69868
70827
  const cwd = process.cwd();
69869
70828
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
69870
70829
  path: rel,
69871
- exists: existsSync35(resolve49(cwd, rel))
70830
+ exists: existsSync35(resolve51(cwd, rel))
69872
70831
  }));
69873
70832
  const projectInitialized = projectFiles.some((f) => f.exists);
69874
70833
  const hosts = detectAgentHosts();
@@ -69883,8 +70842,8 @@ async function runDiagnostics() {
69883
70842
  } catch (err) {
69884
70843
  if (!(err instanceof ComposerResolveError)) throw err;
69885
70844
  }
69886
- const sceneProjectInCwd = existsSync35(resolve49(cwd, "STORYBOARD.md"));
69887
- const skillInstalled = existsSync35(resolve49(cwd, "SKILL.md"));
70845
+ const sceneProjectInCwd = existsSync35(resolve51(cwd, "STORYBOARD.md"));
70846
+ const skillInstalled = existsSync35(resolve51(cwd, "SKILL.md"));
69888
70847
  return {
69889
70848
  system: {
69890
70849
  node: { version: nodeVersion, ok: true },
@@ -70049,10 +71008,10 @@ init_exec_safe();
70049
71008
  init_detect();
70050
71009
  init_ai_edit();
70051
71010
  init_output();
70052
- import { resolve as resolve50 } from "node:path";
71011
+ import { resolve as resolve52 } from "node:path";
70053
71012
  import { existsSync as existsSync36 } from "node:fs";
70054
71013
  import { mkdir as mkdir21, rm as rm4 } from "node:fs/promises";
70055
- var DEMO_DIR = resolve50(process.cwd(), ".vibeframe-demo");
71014
+ var DEMO_DIR = resolve52(process.cwd(), ".vibeframe-demo");
70056
71015
  async function generateTestVideo(outputPath) {
70057
71016
  const filter = [
70058
71017
  // Video: color bars with text
@@ -70132,7 +71091,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70132
71091
  console.log();
70133
71092
  }
70134
71093
  await mkdir21(DEMO_DIR, { recursive: true });
70135
- const testVideo = resolve50(DEMO_DIR, "test-input.mp4");
71094
+ const testVideo = resolve52(DEMO_DIR, "test-input.mp4");
70136
71095
  const genSpinner = !isJson ? ora("Generating test video...").start() : null;
70137
71096
  const generated = await generateTestVideo(testVideo);
70138
71097
  if (!generated) {
@@ -70161,7 +71120,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70161
71120
  name: "Silence Cut",
70162
71121
  description: "Removing silent segments",
70163
71122
  run: async () => {
70164
- const output = resolve50(DEMO_DIR, "silence-cut.mp4");
71123
+ const output = resolve52(DEMO_DIR, "silence-cut.mp4");
70165
71124
  const r = await executeSilenceCut({ videoPath: testVideo, outputPath: output, padding: 0.1 });
70166
71125
  if (r.success && r.totalDuration && r.silentDuration) {
70167
71126
  return { success: true, detail: `Removed ${r.silentDuration.toFixed(1)}s silence (${r.totalDuration.toFixed(1)}s \u2192 ${(r.totalDuration - r.silentDuration).toFixed(1)}s)` };
@@ -70173,7 +71132,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70173
71132
  name: "Noise Reduce",
70174
71133
  description: "Reducing background noise",
70175
71134
  run: async () => {
70176
- const output = resolve50(DEMO_DIR, "noise-reduced.mp4");
71135
+ const output = resolve52(DEMO_DIR, "noise-reduced.mp4");
70177
71136
  const r = await executeNoiseReduce({ inputPath: testVideo, outputPath: output, strength: "medium" });
70178
71137
  return { success: r.success, detail: r.success ? `Cleaned audio \u2192 ${output}` : r.error };
70179
71138
  }
@@ -70182,7 +71141,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70182
71141
  name: "Fade Effects",
70183
71142
  description: "Adding fade in/out",
70184
71143
  run: async () => {
70185
- const output = resolve50(DEMO_DIR, "faded.mp4");
71144
+ const output = resolve52(DEMO_DIR, "faded.mp4");
70186
71145
  const r = await executeFade({ videoPath: testVideo, outputPath: output, fadeIn: 1, fadeOut: 1 });
70187
71146
  return { success: r.success, detail: r.success ? `1s fade in + 1s fade out \u2192 ${output}` : r.error };
70188
71147
  }
@@ -70250,13 +71209,13 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70250
71209
  // src/commands/context.ts
70251
71210
  init_esm();
70252
71211
  import { readFile as readFile35 } from "node:fs/promises";
70253
- import { resolve as resolve51, dirname as dirname19 } from "node:path";
71212
+ import { resolve as resolve53, dirname as dirname19 } from "node:path";
70254
71213
  import { fileURLToPath as fileURLToPath2 } from "node:url";
70255
71214
  var __filename = fileURLToPath2(import.meta.url);
70256
71215
  var __dirname = dirname19(__filename);
70257
71216
  var contextCommand = new Command("context").description("Print CLI context/guidelines for AI agent integration").action(async (_options, cmd) => {
70258
71217
  const options = { json: cmd.parent?.opts()?.json || cmd.opts()?.json };
70259
- const contextPath = resolve51(__dirname, "../../CONTEXT.md");
71218
+ const contextPath = resolve53(__dirname, "../../CONTEXT.md");
70260
71219
  try {
70261
71220
  const content = await readFile35(contextPath, "utf-8");
70262
71221
  if (options.json) {
@@ -70997,14 +71956,14 @@ async function promptConfirm2(toolName, args) {
70997
71956
  output: process.stdout,
70998
71957
  terminal: process.stdin.isTTY ?? false
70999
71958
  });
71000
- return new Promise((resolve62) => {
71959
+ return new Promise((resolve64) => {
71001
71960
  const argsStr = JSON.stringify(args, null, 2);
71002
71961
  console.log();
71003
71962
  console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?`));
71004
71963
  console.log(source_default.dim(argsStr));
71005
71964
  rl.question(source_default.cyan("(y/n): "), (answer) => {
71006
71965
  rl.close();
71007
- resolve62(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
71966
+ resolve64(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
71008
71967
  });
71009
71968
  });
71010
71969
  }
@@ -71093,7 +72052,7 @@ async function startAgent(options = {}) {
71093
72052
  console.log();
71094
72053
  console.log(source_default.dim(" Commands: exit \xB7 reset \xB7 tools \xB7 context"));
71095
72054
  console.log();
71096
- return new Promise((resolve62) => {
72055
+ return new Promise((resolve64) => {
71097
72056
  if (typeof process.stdin.ref === "function") {
71098
72057
  process.stdin.ref();
71099
72058
  }
@@ -71195,7 +72154,7 @@ async function startAgent(options = {}) {
71195
72154
  clearInterval(keepalive);
71196
72155
  console.log();
71197
72156
  console.log(source_default.dim("Goodbye!"));
71198
- resolve62();
72157
+ resolve64();
71199
72158
  });
71200
72159
  rl.prompt();
71201
72160
  });
@@ -71378,7 +72337,7 @@ async function readStdin() {
71378
72337
  return Buffer.concat(chunks).toString("utf-8").trim();
71379
72338
  }
71380
72339
  var program2 = new Command();
71381
- program2.name("vibe").showSuggestionAfterError(true).description("VibeFrame CLI - AI-First Video Editor").version(package_default.version).option("--json", "Output in JSON format").option("-q, --quiet", "Output only the primary result value (path, URL, or ID)").option("--fields <fields>", "Limit JSON output to specific fields (comma-separated)").option("--stdin", "Read options from stdin as JSON (for agent/script use)").option("--describe", "Show JSON schema for the command and exit (no execution)").exitOverride().configureOutput({
72340
+ program2.name("vibe").showSuggestionAfterError(true).description("VibeFrame CLI - video projects, AI media generation, and workflow automation").version(package_default.version).option("--json", "Output in JSON format").option("-q, --quiet", "Output only the primary result value (path, URL, or ID)").option("--fields <fields>", "Limit JSON output to specific fields (comma-separated)").option("--stdin", "Read options from stdin as JSON (for agent/script use)").option("--describe", "Show JSON schema for the command and exit (no execution)").exitOverride().configureOutput({
71382
72341
  outputError: (str, write2) => {
71383
72342
  if (process.env.VIBE_JSON_OUTPUT === "1" || process.argv.includes("--json")) {
71384
72343
  const err = { success: false, error: str.trim(), code: "USAGE_ERROR", exitCode: 2, retryable: false };
@@ -71403,13 +72362,25 @@ Cost tiers:
71403
72362
  Low analyze, audio transcribe, generate image ~$0.01-$0.10
71404
72363
  High generate video, edit image ~$1-$5
71405
72364
  V.High pipeline (regenerate-scene, highlights, auto-shorts) ~$5-$50+
72365
+ Tip Add --dry-run before paid generation to preview the work.
71406
72366
 
71407
- Quick start:
72367
+ Project flow:
71408
72368
  vibe doctor Check system health and API keys
71409
72369
  vibe setup Configure API keys interactively
71410
- vibe schema --list Discover all commands (69 commands)
72370
+ vibe init my-video Scaffold a video project
72371
+ vibe build my-video Build STORYBOARD.md into scene compositions/assets
72372
+ vibe render my-video Render the project to MP4
72373
+
72374
+ Asset tools:
72375
+ vibe generate image "..." Generate a still image
72376
+ vibe generate video "..." Generate a standalone AI video
72377
+
72378
+ Workflow automation:
72379
+ vibe run workflow.yaml Run a Video-as-YAML pipeline
72380
+ vibe schema --list Discover all commands
71411
72381
 
71412
72382
  Agent integration:
72383
+ vibe walkthrough scene Load the scene authoring guide
71413
72384
  echo '{"provider":"kling"}' | vibe generate video "prompt" --stdin --json
71414
72385
  vibe schema generate.video Get parameter schema for any command
71415
72386
  `
@@ -71478,6 +72449,8 @@ program2.addCommand(audioCommand);
71478
72449
  program2.addCommand(pipelineCommand);
71479
72450
  program2.addCommand(setupCommand);
71480
72451
  program2.addCommand(initCommand);
72452
+ program2.addCommand(buildCommand);
72453
+ program2.addCommand(renderCommand);
71481
72454
  program2.addCommand(doctorCommand);
71482
72455
  program2.addCommand(demoCommand);
71483
72456
  program2.addCommand(runCommand);