@vibeframe/cli 0.72.0 → 0.74.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 +1458 -453
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -6546,8 +6546,10 @@ var output_exports = {};
6546
6546
  __export(output_exports, {
6547
6547
  COST_ESTIMATES: () => COST_ESTIMATES,
6548
6548
  ExitCode: () => ExitCode,
6549
+ _resetDeprecationMemoryForTesting: () => _resetDeprecationMemoryForTesting,
6549
6550
  apiError: () => apiError,
6550
6551
  authError: () => authError,
6552
+ emitDeprecationWarning: () => emitDeprecationWarning,
6551
6553
  exitWithError: () => exitWithError,
6552
6554
  generalError: () => generalError,
6553
6555
  isJsonMode: () => isJsonMode,
@@ -6692,6 +6694,19 @@ function suggestNext(tip) {
6692
6694
  Tip: ${tip}`));
6693
6695
  }
6694
6696
  }
6697
+ function emitDeprecationWarning(oldName, newName, removeIn) {
6698
+ if (isJsonMode() || isQuietMode()) return;
6699
+ if (!process.stderr.isTTY) return;
6700
+ const key = `${oldName}\u2192${newName}`;
6701
+ if (_seenDeprecations.has(key)) return;
6702
+ _seenDeprecations.add(key);
6703
+ process.stderr.write(
6704
+ source_default.yellow(`[deprecated] '${oldName}' is deprecated; use '${newName}' instead. Alias will be removed in ${removeIn}.`) + "\n"
6705
+ );
6706
+ }
6707
+ function _resetDeprecationMemoryForTesting() {
6708
+ _seenDeprecations.clear();
6709
+ }
6695
6710
  function outputError(error, details) {
6696
6711
  if (isJsonMode()) {
6697
6712
  console.error(JSON.stringify({ success: false, error, ...details }, null, 2));
@@ -6699,7 +6714,7 @@ function outputError(error, details) {
6699
6714
  console.error(error);
6700
6715
  }
6701
6716
  }
6702
- var ExitCode, PROVIDER_ERROR_HINTS, COST_ESTIMATES;
6717
+ var ExitCode, PROVIDER_ERROR_HINTS, COST_ESTIMATES, _seenDeprecations;
6703
6718
  var init_output = __esm({
6704
6719
  "src/commands/output.ts"() {
6705
6720
  "use strict";
@@ -6734,7 +6749,7 @@ var init_output = __esm({
6734
6749
  { pattern: /context_length_exceeded|maximum.*context.*length|token.*limit.*exceeded|prompt.*too.*long/i, suggestion: "Input exceeds the model's context window. Shorten the prompt, or use a model with larger context (run 'vibe schema <command>' for options).", retryable: false },
6735
6750
  { pattern: /model.*not.*found|invalid.*model|unknown.*model|model_not_found/i, suggestion: "The specified model is unavailable. Check 'vibe schema <command>' for valid model options.", retryable: false },
6736
6751
  // Provider-specific
6737
- { pattern: /voice.*not.*found|voice_not_found|invalid.*voice.?id/i, suggestion: "Voice ID not found. Run 'vibe audio voices' to list available voices, then pass --voice <id>.", retryable: false },
6752
+ { pattern: /voice.*not.*found|voice_not_found|invalid.*voice.?id/i, suggestion: "Voice ID not found. Run 'vibe audio list-voices' to list available voices, then pass --voice <id>.", retryable: false },
6738
6753
  { pattern: /character.*(count|limit).*exceeded|invalid_character_count/i, suggestion: "Text exceeds the TTS provider's character limit. Shorten the text or split into chunks.", retryable: false },
6739
6754
  { pattern: /invalid.*aspect.*ratio|unsupported.*aspect.*ratio|unsupported.*resolution/i, suggestion: "This aspect ratio or resolution isn't supported by the chosen model. Check 'vibe schema <command>' for supported values.", retryable: false },
6740
6755
  { pattern: /invalid.*file.*format|unsupported.*(format|codec)|unsupported.*media.?type/i, suggestion: "Input file format not supported. Convert to MP4/MP3/PNG first with 'vibe export' or 'ffmpeg'.", retryable: false },
@@ -6785,6 +6800,7 @@ var init_output = __esm({
6785
6800
  // pre-flight). Range covers a 1-beat preview to a 10-beat long-form.
6786
6801
  "compose scenes with skills": { min: 0.05, max: 1.5, unit: "per pipeline" }
6787
6802
  };
6803
+ _seenDeprecations = /* @__PURE__ */ new Set();
6788
6804
  }
6789
6805
  });
6790
6806
 
@@ -14343,12 +14359,12 @@ var init_api_keys = __esm({
14343
14359
  label: "ImgBB",
14344
14360
  showInSetup: false,
14345
14361
  // 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)",
14362
+ envExampleComment: "ImgBB API Key (image hosting \u2014 used by Kling and Seedance for image-to-video uploads)",
14347
14363
  envExampleUrl: "https://api.imgbb.com/",
14348
14364
  // ImgBB has no provider class (envvar-only); doctor still shows what it
14349
14365
  // unlocks at the apiKey level.
14350
14366
  commandsUnlocked: [
14351
- "generate video -p kling/fal (image-to-video upload host)"
14367
+ "generate video -p kling/seedance (image-to-video upload host)"
14352
14368
  ]
14353
14369
  });
14354
14370
  defineProvider({
@@ -15194,7 +15210,7 @@ var init_GeminiProvider = __esm({
15194
15210
  * Sleep helper
15195
15211
  */
15196
15212
  sleep(ms) {
15197
- return new Promise((resolve62) => setTimeout(resolve62, ms));
15213
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
15198
15214
  }
15199
15215
  /**
15200
15216
  * Extend a previously generated Veo video
@@ -19366,7 +19382,7 @@ var init_RunwayProvider = __esm({
19366
19382
  * Sleep helper
19367
19383
  */
19368
19384
  sleep(ms) {
19369
- return new Promise((resolve62) => setTimeout(resolve62, ms));
19385
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
19370
19386
  }
19371
19387
  };
19372
19388
  RunwayProvider.API_VERSION = "2024-11-06";
@@ -19807,7 +19823,7 @@ var init_KlingProvider = __esm({
19807
19823
  * Sleep helper
19808
19824
  */
19809
19825
  sleep(ms) {
19810
- return new Promise((resolve62) => setTimeout(resolve62, ms));
19826
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
19811
19827
  }
19812
19828
  };
19813
19829
  klingProvider = new KlingProvider();
@@ -20125,7 +20141,7 @@ var init_GrokProvider = __esm({
20125
20141
  }
20126
20142
  }
20127
20143
  sleep(ms) {
20128
- return new Promise((resolve62) => setTimeout(resolve62, ms));
20144
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
20129
20145
  }
20130
20146
  };
20131
20147
  grokProvider = new GrokProvider();
@@ -20160,11 +20176,11 @@ var require_middleware = __commonJS({
20160
20176
  "use strict";
20161
20177
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20162
20178
  function adopt(value) {
20163
- return value instanceof P ? value : new P(function(resolve62) {
20164
- resolve62(value);
20179
+ return value instanceof P ? value : new P(function(resolve64) {
20180
+ resolve64(value);
20165
20181
  });
20166
20182
  }
20167
- return new (P || (P = Promise))(function(resolve62, reject) {
20183
+ return new (P || (P = Promise))(function(resolve64, reject) {
20168
20184
  function fulfilled(value) {
20169
20185
  try {
20170
20186
  step(generator.next(value));
@@ -20180,7 +20196,7 @@ var require_middleware = __commonJS({
20180
20196
  }
20181
20197
  }
20182
20198
  function step(result) {
20183
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20199
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20184
20200
  }
20185
20201
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20186
20202
  });
@@ -20265,11 +20281,11 @@ var require_response = __commonJS({
20265
20281
  "use strict";
20266
20282
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20267
20283
  function adopt(value) {
20268
- return value instanceof P ? value : new P(function(resolve62) {
20269
- resolve62(value);
20284
+ return value instanceof P ? value : new P(function(resolve64) {
20285
+ resolve64(value);
20270
20286
  });
20271
20287
  }
20272
- return new (P || (P = Promise))(function(resolve62, reject) {
20288
+ return new (P || (P = Promise))(function(resolve64, reject) {
20273
20289
  function fulfilled(value) {
20274
20290
  try {
20275
20291
  step(generator.next(value));
@@ -20285,7 +20301,7 @@ var require_response = __commonJS({
20285
20301
  }
20286
20302
  }
20287
20303
  function step(result) {
20288
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20304
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20289
20305
  }
20290
20306
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20291
20307
  });
@@ -20392,11 +20408,11 @@ var require_utils = __commonJS({
20392
20408
  "use strict";
20393
20409
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20394
20410
  function adopt(value) {
20395
- return value instanceof P ? value : new P(function(resolve62) {
20396
- resolve62(value);
20411
+ return value instanceof P ? value : new P(function(resolve64) {
20412
+ resolve64(value);
20397
20413
  });
20398
20414
  }
20399
- return new (P || (P = Promise))(function(resolve62, reject) {
20415
+ return new (P || (P = Promise))(function(resolve64, reject) {
20400
20416
  function fulfilled(value) {
20401
20417
  try {
20402
20418
  step(generator.next(value));
@@ -20412,7 +20428,7 @@ var require_utils = __commonJS({
20412
20428
  }
20413
20429
  }
20414
20430
  function step(result) {
20415
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20431
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20416
20432
  }
20417
20433
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20418
20434
  });
@@ -20505,7 +20521,7 @@ var require_utils = __commonJS({
20505
20521
  }
20506
20522
  function sleep2(ms) {
20507
20523
  return __awaiter(this, void 0, void 0, function* () {
20508
- return new Promise((resolve62) => setTimeout(resolve62, ms));
20524
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
20509
20525
  });
20510
20526
  }
20511
20527
  }
@@ -20517,11 +20533,11 @@ var require_retry = __commonJS({
20517
20533
  "use strict";
20518
20534
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20519
20535
  function adopt(value) {
20520
- return value instanceof P ? value : new P(function(resolve62) {
20521
- resolve62(value);
20536
+ return value instanceof P ? value : new P(function(resolve64) {
20537
+ resolve64(value);
20522
20538
  });
20523
20539
  }
20524
- return new (P || (P = Promise))(function(resolve62, reject) {
20540
+ return new (P || (P = Promise))(function(resolve64, reject) {
20525
20541
  function fulfilled(value) {
20526
20542
  try {
20527
20543
  step(generator.next(value));
@@ -20537,7 +20553,7 @@ var require_retry = __commonJS({
20537
20553
  }
20538
20554
  }
20539
20555
  function step(result) {
20540
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20556
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20541
20557
  }
20542
20558
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20543
20559
  });
@@ -20744,11 +20760,11 @@ var require_request = __commonJS({
20744
20760
  "use strict";
20745
20761
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20746
20762
  function adopt(value) {
20747
- return value instanceof P ? value : new P(function(resolve62) {
20748
- resolve62(value);
20763
+ return value instanceof P ? value : new P(function(resolve64) {
20764
+ resolve64(value);
20749
20765
  });
20750
20766
  }
20751
- return new (P || (P = Promise))(function(resolve62, reject) {
20767
+ return new (P || (P = Promise))(function(resolve64, reject) {
20752
20768
  function fulfilled(value) {
20753
20769
  try {
20754
20770
  step(generator.next(value));
@@ -20764,7 +20780,7 @@ var require_request = __commonJS({
20764
20780
  }
20765
20781
  }
20766
20782
  function step(result) {
20767
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20783
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20768
20784
  }
20769
20785
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20770
20786
  });
@@ -20851,11 +20867,11 @@ var require_storage = __commonJS({
20851
20867
  "use strict";
20852
20868
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
20853
20869
  function adopt(value) {
20854
- return value instanceof P ? value : new P(function(resolve62) {
20855
- resolve62(value);
20870
+ return value instanceof P ? value : new P(function(resolve64) {
20871
+ resolve64(value);
20856
20872
  });
20857
20873
  }
20858
- return new (P || (P = Promise))(function(resolve62, reject) {
20874
+ return new (P || (P = Promise))(function(resolve64, reject) {
20859
20875
  function fulfilled(value) {
20860
20876
  try {
20861
20877
  step(generator.next(value));
@@ -20871,7 +20887,7 @@ var require_storage = __commonJS({
20871
20887
  }
20872
20888
  }
20873
20889
  function step(result) {
20874
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
20890
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
20875
20891
  }
20876
20892
  step((generator = generator.apply(thisArg, _arguments || [])).next());
20877
20893
  });
@@ -21199,11 +21215,11 @@ var require_auth = __commonJS({
21199
21215
  "use strict";
21200
21216
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
21201
21217
  function adopt(value) {
21202
- return value instanceof P ? value : new P(function(resolve62) {
21203
- resolve62(value);
21218
+ return value instanceof P ? value : new P(function(resolve64) {
21219
+ resolve64(value);
21204
21220
  });
21205
21221
  }
21206
- return new (P || (P = Promise))(function(resolve62, reject) {
21222
+ return new (P || (P = Promise))(function(resolve64, reject) {
21207
21223
  function fulfilled(value) {
21208
21224
  try {
21209
21225
  step(generator.next(value));
@@ -21219,7 +21235,7 @@ var require_auth = __commonJS({
21219
21235
  }
21220
21236
  }
21221
21237
  function step(result) {
21222
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
21238
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
21223
21239
  }
21224
21240
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21225
21241
  });
@@ -21258,11 +21274,11 @@ var require_streaming = __commonJS({
21258
21274
  "use strict";
21259
21275
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
21260
21276
  function adopt(value) {
21261
- return value instanceof P ? value : new P(function(resolve62) {
21262
- resolve62(value);
21277
+ return value instanceof P ? value : new P(function(resolve64) {
21278
+ resolve64(value);
21263
21279
  });
21264
21280
  }
21265
- return new (P || (P = Promise))(function(resolve62, reject) {
21281
+ return new (P || (P = Promise))(function(resolve64, reject) {
21266
21282
  function fulfilled(value) {
21267
21283
  try {
21268
21284
  step(generator.next(value));
@@ -21278,7 +21294,7 @@ var require_streaming = __commonJS({
21278
21294
  }
21279
21295
  }
21280
21296
  function step(result) {
21281
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
21297
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
21282
21298
  }
21283
21299
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21284
21300
  });
@@ -21511,7 +21527,7 @@ var require_streaming = __commonJS({
21511
21527
  query: options.queryParams
21512
21528
  });
21513
21529
  this.options = options;
21514
- this.donePromise = new Promise((resolve62, reject) => {
21530
+ this.donePromise = new Promise((resolve64, reject) => {
21515
21531
  if (this.streamClosed) {
21516
21532
  reject(new response_1.ApiError({
21517
21533
  message: "Streaming connection is already closed.",
@@ -21522,11 +21538,11 @@ var require_streaming = __commonJS({
21522
21538
  }
21523
21539
  this.signal.addEventListener("abort", () => {
21524
21540
  var _a2;
21525
- resolve62((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
21541
+ resolve64((_a2 = this.currentData) !== null && _a2 !== void 0 ? _a2 : {});
21526
21542
  });
21527
21543
  this.on("done", (data) => {
21528
21544
  this.streamClosed = true;
21529
- resolve62(data);
21545
+ resolve64(data);
21530
21546
  });
21531
21547
  this.on("error", (error) => {
21532
21548
  this.streamClosed = true;
@@ -21551,7 +21567,7 @@ var require_streaming = __commonJS({
21551
21567
  if (data) {
21552
21568
  yield yield __await(data);
21553
21569
  }
21554
- yield __await(new Promise((resolve62) => setTimeout(resolve62, 16)));
21570
+ yield __await(new Promise((resolve64) => setTimeout(resolve64, 16)));
21555
21571
  }
21556
21572
  });
21557
21573
  }
@@ -21596,11 +21612,11 @@ var require_queue = __commonJS({
21596
21612
  "use strict";
21597
21613
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
21598
21614
  function adopt(value) {
21599
- return value instanceof P ? value : new P(function(resolve62) {
21600
- resolve62(value);
21615
+ return value instanceof P ? value : new P(function(resolve64) {
21616
+ resolve64(value);
21601
21617
  });
21602
21618
  }
21603
- return new (P || (P = Promise))(function(resolve62, reject) {
21619
+ return new (P || (P = Promise))(function(resolve64, reject) {
21604
21620
  function fulfilled(value) {
21605
21621
  try {
21606
21622
  step(generator.next(value));
@@ -21616,7 +21632,7 @@ var require_queue = __commonJS({
21616
21632
  }
21617
21633
  }
21618
21634
  function step(result) {
21619
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
21635
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
21620
21636
  }
21621
21637
  step((generator = generator.apply(thisArg, _arguments || [])).next());
21622
21638
  });
@@ -21751,7 +21767,7 @@ var require_queue = __commonJS({
21751
21767
  }
21752
21768
  return doneStatus;
21753
21769
  }
21754
- return new Promise((resolve62, reject) => {
21770
+ return new Promise((resolve64, reject) => {
21755
21771
  var _a;
21756
21772
  let pollingTimeoutId;
21757
21773
  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 +21799,7 @@ var require_queue = __commonJS({
21783
21799
  }
21784
21800
  if (requestStatus.status === "COMPLETED") {
21785
21801
  clearScheduledTasks();
21786
- resolve62(requestStatus);
21802
+ resolve64(requestStatus);
21787
21803
  return;
21788
21804
  }
21789
21805
  pollingTimeoutId = setTimeout(poll, pollInterval);
@@ -23744,11 +23760,11 @@ var require_realtime = __commonJS({
23744
23760
  "use strict";
23745
23761
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
23746
23762
  function adopt(value) {
23747
- return value instanceof P ? value : new P(function(resolve62) {
23748
- resolve62(value);
23763
+ return value instanceof P ? value : new P(function(resolve64) {
23764
+ resolve64(value);
23749
23765
  });
23750
23766
  }
23751
- return new (P || (P = Promise))(function(resolve62, reject) {
23767
+ return new (P || (P = Promise))(function(resolve64, reject) {
23752
23768
  function fulfilled(value) {
23753
23769
  try {
23754
23770
  step(generator.next(value));
@@ -23764,7 +23780,7 @@ var require_realtime = __commonJS({
23764
23780
  }
23765
23781
  }
23766
23782
  function step(result) {
23767
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
23783
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
23768
23784
  }
23769
23785
  step((generator = generator.apply(thisArg, _arguments || [])).next());
23770
23786
  });
@@ -24089,11 +24105,11 @@ var require_client = __commonJS({
24089
24105
  "use strict";
24090
24106
  var __awaiter = exports && exports.__awaiter || function(thisArg, _arguments, P, generator) {
24091
24107
  function adopt(value) {
24092
- return value instanceof P ? value : new P(function(resolve62) {
24093
- resolve62(value);
24108
+ return value instanceof P ? value : new P(function(resolve64) {
24109
+ resolve64(value);
24094
24110
  });
24095
24111
  }
24096
- return new (P || (P = Promise))(function(resolve62, reject) {
24112
+ return new (P || (P = Promise))(function(resolve64, reject) {
24097
24113
  function fulfilled(value) {
24098
24114
  try {
24099
24115
  step(generator.next(value));
@@ -24109,7 +24125,7 @@ var require_client = __commonJS({
24109
24125
  }
24110
24126
  }
24111
24127
  function step(result) {
24112
- result.done ? resolve62(result.value) : adopt(result.value).then(fulfilled, rejected);
24128
+ result.done ? resolve64(result.value) : adopt(result.value).then(fulfilled, rejected);
24113
24129
  }
24114
24130
  step((generator = generator.apply(thisArg, _arguments || [])).next());
24115
24131
  });
@@ -24417,13 +24433,18 @@ var init_fal = __esm({
24417
24433
  defineProvider({
24418
24434
  id: "fal",
24419
24435
  label: "fal.ai (Seedance 2.0)",
24436
+ displayName: "Seedance 2.0",
24437
+ gateway: "fal.ai",
24438
+ aliases: ["seedance"],
24439
+ models: ["seedance-2.0", "seedance-2.0-fast"],
24440
+ capabilities: ["text-to-video", "image-to-video", "native-audio"],
24420
24441
  apiKey: "fal",
24421
24442
  kinds: ["video"],
24422
24443
  resolverPriority: { video: 1 },
24423
24444
  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)"
24445
+ "generate video -p seedance (Seedance 2.0 via fal.ai \u2014 default since v0.57)",
24446
+ "generate video -p seedance --seedance-model fast (lower-latency variant)",
24447
+ "generate video -p seedance -i <image> (image-to-video)"
24427
24448
  ]
24428
24449
  });
24429
24450
  }
@@ -24687,7 +24708,7 @@ var init_ReplicateProvider = __esm({
24687
24708
  * Sleep helper
24688
24709
  */
24689
24710
  sleep(ms) {
24690
- return new Promise((resolve62) => setTimeout(resolve62, ms));
24711
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
24691
24712
  }
24692
24713
  /**
24693
24714
  * Generate music from text prompt using MusicGen
@@ -25680,7 +25701,7 @@ async function prompt(question, hidden = false) {
25680
25701
  input: process.stdin,
25681
25702
  output: process.stdout
25682
25703
  });
25683
- return new Promise((resolve62) => {
25704
+ return new Promise((resolve64) => {
25684
25705
  if (hidden && process.stdin.isTTY) {
25685
25706
  process.stdout.write(question);
25686
25707
  let input = "";
@@ -25694,7 +25715,7 @@ async function prompt(question, hidden = false) {
25694
25715
  process.stdin.removeListener("data", onData);
25695
25716
  process.stdout.write("\n");
25696
25717
  rl.close();
25697
- resolve62(input);
25718
+ resolve64(input);
25698
25719
  } else if (char === "") {
25699
25720
  process.exit(1);
25700
25721
  } else if (char === "\x7F" || char === "\b") {
@@ -25709,7 +25730,7 @@ async function prompt(question, hidden = false) {
25709
25730
  } else {
25710
25731
  rl.question(question, (answer) => {
25711
25732
  rl.close();
25712
- resolve62(answer);
25733
+ resolve64(answer);
25713
25734
  });
25714
25735
  }
25715
25736
  });
@@ -26040,6 +26061,54 @@ _Browse other named styles: \`vibe scene styles\`_
26040
26061
  ${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
26062
  `;
26042
26063
  }
26064
+ function buildStoryboardMd(name, duration = 12) {
26065
+ return `---
26066
+ title: ${name}
26067
+ duration: ${duration}
26068
+ aspect: 16:9
26069
+ tts: auto
26070
+ imageProvider: openai
26071
+ ---
26072
+
26073
+ # ${name} \u2014 Storyboard
26074
+
26075
+ Edit these beats before running \`vibe build\`. Each beat starts with
26076
+ YAML cues that drive narration, backdrop generation, and timing.
26077
+
26078
+ ## Beat hook \u2014 Hook
26079
+
26080
+ \`\`\`yaml
26081
+ narration: "Introduce the promise in one crisp sentence."
26082
+ backdrop: "Cinematic abstract technology backdrop, precise light, premium editorial feel"
26083
+ duration: 4
26084
+ \`\`\`
26085
+
26086
+ Show the core visual identity immediately. Keep copy short enough for one
26087
+ screen and one spoken breath.
26088
+
26089
+ ## Beat proof \u2014 Proof
26090
+
26091
+ \`\`\`yaml
26092
+ narration: "Show the mechanism or proof point that makes the promise believable."
26093
+ backdrop: "Layered interface details, subtle motion trails, high-contrast product storytelling"
26094
+ duration: 4
26095
+ \`\`\`
26096
+
26097
+ Use this beat for the concrete differentiator: command, workflow, metric, or
26098
+ before/after.
26099
+
26100
+ ## Beat close \u2014 Close
26101
+
26102
+ \`\`\`yaml
26103
+ narration: "Close with the action the viewer should remember."
26104
+ backdrop: "Resolved hero frame, confident final composition, clean negative space"
26105
+ duration: 4
26106
+ \`\`\`
26107
+
26108
+ End on the product name, offer, or command. Avoid adding a new idea in the
26109
+ final beat.
26110
+ `;
26111
+ }
26043
26112
  function buildProjectClaudeMd(name) {
26044
26113
  return `# ${name} \u2014 Scene Authoring Project
26045
26114
 
@@ -26069,7 +26138,7 @@ semantics, VibeFrame pipeline conventions) that are NOT in generic web docs.
26069
26138
  | **vibe-scene** | \`/vibe-scene\` | VibeFrame's authoring loop, AI assets, lint feedback, pipeline integration |
26070
26139
  | **gsap** | \`/gsap\` | GSAP tweens, timelines, easing |
26071
26140
 
26072
- Install the Hyperframes skills once per machine:
26141
+ Optional: install the upstream Hyperframes skills once per machine when your agent supports skill commands:
26073
26142
 
26074
26143
  \`\`\`bash
26075
26144
  npx skills add heygen-com/hyperframes
@@ -26082,6 +26151,7 @@ the framework-level minimum, not the cinematic craft layer.
26082
26151
  ## Project structure
26083
26152
 
26084
26153
  - \`DESIGN.md\` \u2014 visual identity contract (palette, type, motion, transitions)
26154
+ - \`STORYBOARD.md\` \u2014 per-beat narration/backdrop/duration cues for \`vibe build\`
26085
26155
  - \`index.html\` \u2014 root composition (timeline)
26086
26156
  - \`compositions/scene-*.html\` \u2014 per-scene HTML authored by you or the agent
26087
26157
  - \`assets/\` \u2014 shared media (narration audio, images, video)
@@ -26094,6 +26164,7 @@ the framework-level minimum, not the cinematic craft layer.
26094
26164
 
26095
26165
  \`\`\`bash
26096
26166
  vibe scene add <name> --narration "..." --visuals "..." # Author a new scene via AI
26167
+ vibe build # STORYBOARD.md \u2192 narrated MP4
26097
26168
  vibe scene lint # Validate scenes (in-process HF linter)
26098
26169
  vibe scene render # Render to MP4
26099
26170
 
@@ -26138,6 +26209,41 @@ tmp/
26138
26209
  *.log
26139
26210
  `;
26140
26211
  }
26212
+ function isSceneScaffoldProfile(value) {
26213
+ return value === "minimal" || value === "agent" || value === "full";
26214
+ }
26215
+ function describeSceneScaffold(opts) {
26216
+ const dir = resolve5(opts.dir);
26217
+ const profile = opts.profile ?? "full";
26218
+ const groups = {
26219
+ authoring: [
26220
+ resolve5(dir, "STORYBOARD.md"),
26221
+ resolve5(dir, "DESIGN.md"),
26222
+ resolve5(dir, "vibe.project.yaml"),
26223
+ resolve5(dir, ".gitignore")
26224
+ ],
26225
+ render: [],
26226
+ agent: []
26227
+ };
26228
+ if (profile === "full") {
26229
+ groups.render = [
26230
+ resolve5(dir, "index.html"),
26231
+ resolve5(dir, "compositions"),
26232
+ resolve5(dir, "assets"),
26233
+ resolve5(dir, "renders"),
26234
+ resolve5(dir, "hyperframes.json"),
26235
+ resolve5(dir, "meta.json")
26236
+ ];
26237
+ }
26238
+ if (profile === "agent" || profile === "full") {
26239
+ groups.agent = [
26240
+ resolve5(dir, "SKILL.md"),
26241
+ resolve5(dir, "references"),
26242
+ resolve5(dir, "CLAUDE.md")
26243
+ ];
26244
+ }
26245
+ return groups;
26246
+ }
26141
26247
  async function pathExists(p) {
26142
26248
  try {
26143
26249
  await access3(p);
@@ -26152,37 +26258,43 @@ async function scaffoldSceneProject(opts) {
26152
26258
  const aspect = opts.aspect ?? "16:9";
26153
26259
  const duration = opts.duration ?? 10;
26154
26260
  const now = opts.now ?? /* @__PURE__ */ new Date();
26261
+ const profile = opts.profile ?? "full";
26155
26262
  await mkdir2(dir, { recursive: true });
26156
- await mkdir2(resolve5(dir, "compositions"), { recursive: true });
26157
- await mkdir2(resolve5(dir, "assets"), { recursive: true });
26263
+ if (profile === "full") {
26264
+ await mkdir2(resolve5(dir, "compositions"), { recursive: true });
26265
+ await mkdir2(resolve5(dir, "assets"), { recursive: true });
26266
+ await mkdir2(resolve5(dir, "renders"), { recursive: true });
26267
+ }
26158
26268
  const created = [];
26159
26269
  const skipped2 = [];
26160
26270
  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);
26271
+ if (profile === "full") {
26272
+ const hfPath = resolve5(dir, "hyperframes.json");
26273
+ const hfDefaults = buildHyperframesConfig();
26274
+ if (await pathExists(hfPath)) {
26275
+ const existingRaw = await readFile4(hfPath, "utf-8");
26276
+ const existing = JSON.parse(existingRaw);
26277
+ const mergedConfig = mergeHyperframesConfig(existing, hfDefaults);
26278
+ await writeFile4(hfPath, JSON.stringify(mergedConfig, null, 2) + "\n", "utf-8");
26279
+ merged.push(hfPath);
26280
+ } else {
26281
+ await writeFile4(hfPath, JSON.stringify(hfDefaults, null, 2) + "\n", "utf-8");
26282
+ created.push(hfPath);
26283
+ }
26284
+ const metaPath = resolve5(dir, "meta.json");
26285
+ if (await pathExists(metaPath)) {
26286
+ skipped2.push(metaPath);
26287
+ } else {
26288
+ await writeFile4(metaPath, JSON.stringify(buildHyperframesMeta(name, now), null, 2) + "\n", "utf-8");
26289
+ created.push(metaPath);
26290
+ }
26291
+ const rootPath = resolve5(dir, "index.html");
26292
+ if (await pathExists(rootPath)) {
26293
+ skipped2.push(rootPath);
26294
+ } else {
26295
+ await writeFile4(rootPath, buildEmptyRootHtml({ aspect, duration }), "utf-8");
26296
+ created.push(rootPath);
26297
+ }
26186
26298
  }
26187
26299
  const vibePath = resolve5(dir, "vibe.project.yaml");
26188
26300
  if (await pathExists(vibePath)) {
@@ -26192,12 +26304,14 @@ async function scaffoldSceneProject(opts) {
26192
26304
  await writeFile4(vibePath, (0, import_yaml2.stringify)(cfg), "utf-8");
26193
26305
  created.push(vibePath);
26194
26306
  }
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);
26307
+ if (profile === "agent" || profile === "full") {
26308
+ const claudePath = resolve5(dir, "CLAUDE.md");
26309
+ if (await pathExists(claudePath)) {
26310
+ skipped2.push(claudePath);
26311
+ } else {
26312
+ await writeFile4(claudePath, buildProjectClaudeMd(name), "utf-8");
26313
+ created.push(claudePath);
26314
+ }
26201
26315
  }
26202
26316
  const designPath = resolve5(dir, "DESIGN.md");
26203
26317
  if (await pathExists(designPath)) {
@@ -26210,6 +26324,13 @@ async function scaffoldSceneProject(opts) {
26210
26324
  );
26211
26325
  created.push(designPath);
26212
26326
  }
26327
+ const storyboardPath = resolve5(dir, "STORYBOARD.md");
26328
+ if (await pathExists(storyboardPath)) {
26329
+ skipped2.push(storyboardPath);
26330
+ } else {
26331
+ await writeFile4(storyboardPath, buildStoryboardMd(name, duration), "utf-8");
26332
+ created.push(storyboardPath);
26333
+ }
26213
26334
  const gitignorePath = resolve5(dir, ".gitignore");
26214
26335
  if (await pathExists(gitignorePath)) {
26215
26336
  skipped2.push(gitignorePath);
@@ -26217,7 +26338,7 @@ async function scaffoldSceneProject(opts) {
26217
26338
  await writeFile4(gitignorePath, buildSceneGitignore(), "utf-8");
26218
26339
  created.push(gitignorePath);
26219
26340
  }
26220
- return { created, skipped: skipped2, merged };
26341
+ return { created, skipped: skipped2, merged, groups: describeSceneScaffold({ dir, profile }) };
26221
26342
  }
26222
26343
  var import_yaml2, ASPECT_DIMS;
26223
26344
  var init_scene_project = __esm({
@@ -27157,7 +27278,9 @@ function buildAudioMuxFilter(audios) {
27157
27278
  const inputIdx = i + 1;
27158
27279
  const delayMs = Math.max(0, Math.round(a.absoluteStart * 1e3));
27159
27280
  const volume = Number.isFinite(a.volume) ? a.volume : 1;
27160
- const trimSec = Math.max(0, a.clipDurationCap);
27281
+ const durationHint = typeof a.durationHint === "number" && Number.isFinite(a.durationHint) ? Math.max(0, a.durationHint) : null;
27282
+ const clipCap = Math.max(0, a.clipDurationCap);
27283
+ const trimSec = durationHint === null ? clipCap : Math.min(durationHint, clipCap);
27161
27284
  const label = `a${i}`;
27162
27285
  const stage = [
27163
27286
  `[${inputIdx}:a]`,
@@ -27419,6 +27542,57 @@ var init_scene_render = __esm({
27419
27542
  }
27420
27543
  });
27421
27544
 
27545
+ // src/utils/audio.ts
27546
+ async function getAudioDuration(filePath) {
27547
+ try {
27548
+ return await ffprobeDuration(filePath);
27549
+ } catch (error) {
27550
+ const message = error instanceof Error ? error.message : String(error);
27551
+ throw new Error(`Failed to get audio duration: ${message}`);
27552
+ }
27553
+ }
27554
+ async function getVideoDuration(filePath) {
27555
+ try {
27556
+ return await ffprobeDuration(filePath);
27557
+ } catch (error) {
27558
+ const message = error instanceof Error ? error.message : String(error);
27559
+ throw new Error(`Failed to get video duration: ${message}`);
27560
+ }
27561
+ }
27562
+ async function extendVideoNaturally(videoPath, targetDuration, outputPath) {
27563
+ const videoDuration = await getVideoDuration(videoPath);
27564
+ const ratio = targetDuration / videoDuration;
27565
+ if (ratio <= 1) {
27566
+ const { copyFile: copyFile4 } = await import("node:fs/promises");
27567
+ await copyFile4(videoPath, outputPath);
27568
+ return;
27569
+ }
27570
+ if (ratio <= 1.15) {
27571
+ const slowFactor = (1 / ratio).toFixed(4);
27572
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS`, "-an", outputPath]);
27573
+ } else if (ratio <= 1.4) {
27574
+ const slowFactor = (1 / ratio).toFixed(4);
27575
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
27576
+ } else {
27577
+ const slowRatio = 0.7;
27578
+ const slowedDuration = videoDuration / slowRatio;
27579
+ const freezeDuration = targetDuration - slowedDuration;
27580
+ if (freezeDuration <= 0) {
27581
+ const slowFactor = (1 / ratio).toFixed(4);
27582
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `minterpolate=fps=60:mi_mode=mci,setpts=${slowFactor}*PTS`, "-an", outputPath]);
27583
+ } else {
27584
+ const slowFactor = (1 / slowRatio).toFixed(4);
27585
+ await execSafe("ffmpeg", ["-y", "-i", videoPath, "-filter:v", `setpts=${slowFactor}*PTS,tpad=stop_mode=clone:stop_duration=${freezeDuration.toFixed(2)}`, "-an", outputPath]);
27586
+ }
27587
+ }
27588
+ }
27589
+ var init_audio = __esm({
27590
+ "src/utils/audio.ts"() {
27591
+ "use strict";
27592
+ init_exec_safe();
27593
+ }
27594
+ });
27595
+
27422
27596
  // src/commands/_shared/hf-skill-bundle/bundle-content.ts
27423
27597
  var SKILL_MD, HOUSE_STYLE_MD, MOTION_PRINCIPLES_MD, TYPOGRAPHY_MD, TRANSITIONS_MD;
27424
27598
  var init_bundle_content = __esm({
@@ -29020,7 +29194,7 @@ async function getComposePrompts(opts) {
29020
29194
  return baseError(`DESIGN.md not found at ${designPath}. Run \`vibe scene init <dir>\` first.`);
29021
29195
  }
29022
29196
  if (!existsSync6(storyboardPath)) {
29023
- return baseError(`STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` first.`);
29197
+ 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
29198
  }
29025
29199
  if (!existsSync6(skillPath)) {
29026
29200
  warnings.push(
@@ -29256,7 +29430,10 @@ async function executeSceneBuild(opts) {
29256
29430
  const mode = resolveSceneBuildMode(opts);
29257
29431
  const storyboardPath = join7(projectDir, "STORYBOARD.md");
29258
29432
  if (!existsSync8(storyboardPath)) {
29259
- return failBeforePrimitives(`STORYBOARD.md not found at ${storyboardPath}`, startedAt);
29433
+ return failBeforePrimitives(
29434
+ `STORYBOARD.md not found at ${storyboardPath}. Run \`vibe scene init <dir>\` to create a starter, or add STORYBOARD.md with per-beat cues.`,
29435
+ startedAt
29436
+ );
29260
29437
  }
29261
29438
  const storyboardMd = await readFile10(storyboardPath, "utf-8");
29262
29439
  const parsed = parseStoryboard(storyboardMd);
@@ -29336,6 +29513,13 @@ async function executeSceneBuild(opts) {
29336
29513
  }
29337
29514
  composeData = composeResult.data;
29338
29515
  }
29516
+ if (!existsSync8(join7(projectDir, "index.html"))) {
29517
+ await scaffoldSceneProject({
29518
+ dir: projectDir,
29519
+ name: projectDir.split(/[\\/]/).filter(Boolean).pop(),
29520
+ profile: "full"
29521
+ });
29522
+ }
29339
29523
  await syncRootClipReferences(parsed.beats, projectDir, beatOutcomes);
29340
29524
  let outputPath;
29341
29525
  let renderResult;
@@ -29434,6 +29618,7 @@ async function dispatchBackdrop(beat, ctx) {
29434
29618
  ctx.onProgress({ type: "backdrop-cached", beatId: beat.id, path: rel });
29435
29619
  return { status: "cached", path: rel };
29436
29620
  }
29621
+ loadSceneBuildEnv(ctx.projectDir);
29437
29622
  const apiKey = process.env.OPENAI_API_KEY ?? "";
29438
29623
  if (!apiKey) {
29439
29624
  const error = "OPENAI_API_KEY not set \u2014 cannot dispatch backdrop";
@@ -29462,6 +29647,18 @@ async function dispatchBackdrop(beat, ctx) {
29462
29647
  });
29463
29648
  return { status: "generated", path: rel };
29464
29649
  }
29650
+ function loadSceneBuildEnv(projectDir) {
29651
+ (0, import_dotenv2.config)({ path: join7(projectDir, ".env"), quiet: true });
29652
+ (0, import_dotenv2.config)({ path: resolve12(process.cwd(), ".env"), quiet: true });
29653
+ let dir = process.cwd();
29654
+ while (dir !== dirname4(dir)) {
29655
+ if (existsSync8(join7(dir, "pnpm-workspace.yaml"))) {
29656
+ (0, import_dotenv2.config)({ path: join7(dir, ".env"), quiet: true });
29657
+ return;
29658
+ }
29659
+ dir = dirname4(dir);
29660
+ }
29661
+ }
29465
29662
  async function skipped(kind, beatId, reason, ctx) {
29466
29663
  ctx.onProgress({ type: `${kind}-skipped`, beatId, reason });
29467
29664
  return { status: "skipped" };
@@ -29492,20 +29689,24 @@ async function syncRootClipReferences(beats, projectDir, outcomes) {
29492
29689
  const clipLines = [];
29493
29690
  const audioLines = [];
29494
29691
  for (const beat of beats) {
29495
- const duration = beat.duration ?? 3;
29692
+ const outcome = outcomes.find((o) => o.beatId === beat.id);
29693
+ const duration = await resolveBeatDuration({
29694
+ beatDuration: beat.duration,
29695
+ narrationPath: outcome?.narrationPath,
29696
+ projectDir
29697
+ });
29496
29698
  const compositionId = `scene-${beat.id}`;
29497
29699
  clipLines.push(
29498
29700
  ` <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
29701
  );
29500
- const outcome = outcomes.find((o) => o.beatId === beat.id);
29501
29702
  if (outcome?.narrationPath) {
29502
29703
  audioLines.push(
29503
- ` <audio src="${outcome.narrationPath}" data-start="${cursor}" data-duration="${duration}" data-track-index="2"></audio>`
29704
+ ` <audio id="narration-${beat.id}" src="${outcome.narrationPath}" data-start="${cursor}" data-duration="${duration}" data-track-index="2"></audio>`
29504
29705
  );
29505
29706
  }
29506
29707
  cursor += duration;
29507
29708
  }
29508
- const totalDuration = cursor;
29709
+ const totalDuration = Number(cursor.toFixed(2));
29509
29710
  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
29711
  let next;
29511
29712
  const markerRe = /\n? *<!-- vibe-scene-build: clip refs.*?<!-- \/vibe-scene-build -->/s;
@@ -29523,77 +29724,40 @@ ${block}
29523
29724
  }
29524
29725
  }
29525
29726
  next = next.replace(
29526
- /(id="root"[\s\S]*?data-duration=")(\d+(?:\.\d+)?)(")/,
29727
+ /(id="root"[\s\S]*?data-duration=")([^"]*)(")/,
29527
29728
  `$1${totalDuration}$3`
29528
29729
  );
29529
29730
  if (next !== html) {
29530
29731
  await writeFile7(rootPath, next, "utf-8");
29531
29732
  }
29532
29733
  }
29734
+ async function resolveBeatDuration(opts) {
29735
+ const storyboardMin = opts.beatDuration ?? 3;
29736
+ if (!opts.narrationPath) return Number(storyboardMin.toFixed(2));
29737
+ try {
29738
+ const audioDuration = await getAudioDuration(join7(opts.projectDir, opts.narrationPath));
29739
+ return Number(Math.max(storyboardMin, audioDuration + 0.5).toFixed(2));
29740
+ } catch {
29741
+ return Number(storyboardMin.toFixed(2));
29742
+ }
29743
+ }
29744
+ var import_dotenv2;
29533
29745
  var init_scene_build = __esm({
29534
29746
  "src/commands/_shared/scene-build.ts"() {
29535
29747
  "use strict";
29748
+ import_dotenv2 = __toESM(require_main(), 1);
29536
29749
  init_dist2();
29750
+ init_audio();
29537
29751
  init_compose_scenes_skills();
29538
29752
  init_compose_prompts();
29539
29753
  init_agent_host_detect();
29540
29754
  init_scene_render();
29541
29755
  init_storyboard_parse();
29756
+ init_scene_project();
29542
29757
  init_tts_resolve();
29543
29758
  }
29544
29759
  });
29545
29760
 
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
29761
  // src/commands/_shared/install-skill.ts
29598
29762
  import { mkdir as mkdir6, writeFile as writeFile8 } from "node:fs/promises";
29599
29763
  import { existsSync as existsSync9 } from "node:fs";
@@ -29730,6 +29894,30 @@ function validateVisualStyle(value) {
29730
29894
  }
29731
29895
  return found;
29732
29896
  }
29897
+ function formatSceneInitProfile(profile) {
29898
+ if (profile === "minimal") return "authoring files only; build will add render scaffold when needed";
29899
+ if (profile === "agent") return "authoring files plus local composition rules for host agents";
29900
+ return "complete authoring, agent, and render scaffold";
29901
+ }
29902
+ function printSceneInitDryRun(opts) {
29903
+ console.log();
29904
+ console.log(source_default.bold.cyan("VibeFrame Scene Init - dry run"));
29905
+ console.log(source_default.dim("-".repeat(60)));
29906
+ console.log(` Project: ${source_default.bold(opts.dir)}`);
29907
+ console.log(` Name: ${source_default.bold(opts.name)}`);
29908
+ console.log(` Profile: ${source_default.bold(opts.profile)} ${source_default.dim(formatSceneInitProfile(opts.profile))}`);
29909
+ console.log(` Aspect: ${opts.aspect}`);
29910
+ console.log(` Duration: ${opts.duration}s`);
29911
+ console.log(` Visual style: ${opts.visualStyleName ?? "none"}`);
29912
+ console.log();
29913
+ console.log(source_default.bold.cyan("Files that would be prepared"));
29914
+ console.log(source_default.dim("-".repeat(60)));
29915
+ for (const file of opts.groups.authoring) console.log(` authoring ${file}`);
29916
+ for (const file of opts.groups.agent) console.log(` agent ${file}`);
29917
+ for (const file of opts.groups.render) console.log(` render ${file}`);
29918
+ console.log();
29919
+ console.log(source_default.dim("No files were written."));
29920
+ }
29733
29921
  async function pathExists2(p) {
29734
29922
  try {
29735
29923
  await access4(p);
@@ -30041,7 +30229,7 @@ function formatPrimitiveStatus(status, path2) {
30041
30229
  return status;
30042
30230
  }
30043
30231
  }
30044
- var import_yaml5, VALID_ASPECTS2, sceneCommand, VALID_INSTALL_SKILL_HOSTS, VALID_FPS, VALID_QUALITIES, VALID_FORMATS;
30232
+ var import_yaml5, VALID_ASPECTS2, VALID_SCENE_INIT_PROFILES, sceneCommand, VALID_INSTALL_SKILL_HOSTS, VALID_FPS, VALID_QUALITIES, VALID_FORMATS;
30045
30233
  var init_scene = __esm({
30046
30234
  "src/commands/scene.ts"() {
30047
30235
  "use strict";
@@ -30064,30 +30252,38 @@ var init_scene = __esm({
30064
30252
  init_install_skill();
30065
30253
  init_compose_prompts();
30066
30254
  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", `
30255
+ VALID_SCENE_INIT_PROFILES = ["minimal", "agent", "full"];
30256
+ sceneCommand = new Command("scene").description("Lower-level scene authoring (add, lint, styles). For project flow use `vibe init` / `vibe build` / `vibe render`.").addHelpText("after", `
30068
30257
  Examples:
30069
- $ vibe scene init my-video # Scaffold a new project
30070
- $ vibe scene init my-video -r 9:16 -d 30 # Vertical 30s project
30071
30258
  $ vibe scene add intro --style announcement \\
30072
30259
  --headline "Welcome to VibeFrame" # Headline-only scene
30073
30260
  $ vibe scene add overview --narration "VibeFrame turns scripts into video." \\
30074
30261
  --visuals "studio desk, soft lighting" # AI narration + image
30075
- $ vibe scene lint # Validate every scene against Hyperframes rules
30262
+ $ vibe scene lint # Validate every scene against composition rules
30076
30263
  $ vibe scene lint --fix # Auto-fix mechanical issues (e.g. missing class="clip")
30077
30264
  $ vibe scene lint --json # Structured output for agent loops
30078
- $ vibe scene render # Render to renders/<name>-<timestamp>.mp4
30079
- $ vibe scene render -o demo.mp4 --quality high # Custom output path + quality
30080
- $ vibe scene render --fps 60 --format webm # 60fps WebM render
30265
+ $ vibe scene styles # Browse seed visual styles for DESIGN.md
30081
30266
 
30082
- A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
30267
+ For the project flow (init / build / render), use the top-level commands.
30268
+ The \`scene init\`, \`scene build\`, and \`scene render\` legacy aliases
30269
+ are still callable but hidden from this help \u2014 they will be removed in v1.0.
30083
30270
  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) => {
30271
+ sceneCommand.command("init", { hidden: true }).description("Scaffold a new scene project (or safely augment an existing project) [legacy \u2014 prefer `vibe init`]").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
30272
  const startedAt = Date.now();
30086
30273
  const aspect = validateAspect(options.ratio);
30087
30274
  const duration = validateDuration(options.duration);
30088
30275
  const name = options.name ?? basename4(dir.replace(/\/+$/, ""));
30089
30276
  const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
30277
+ const profile = String(options.profile ?? "agent");
30278
+ if (!isSceneScaffoldProfile(profile)) {
30279
+ exitWithError(usageError(`Invalid --profile: ${profile}`, `Must be one of: ${VALID_SCENE_INIT_PROFILES.join(", ")}`));
30280
+ }
30281
+ const groups = describeSceneScaffold({ dir, profile });
30090
30282
  if (options.dryRun) {
30283
+ if (!isJsonMode() && !isQuietMode()) {
30284
+ printSceneInitDryRun({ dir, name, aspect, duration, visualStyleName: visualStyle?.name ?? null, profile, groups });
30285
+ return;
30286
+ }
30091
30287
  outputSuccess({
30092
30288
  command: "scene init",
30093
30289
  startedAt,
@@ -30098,23 +30294,25 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30098
30294
  name,
30099
30295
  aspect,
30100
30296
  duration,
30101
- visualStyle: visualStyle?.name ?? null
30102
- }
30297
+ visualStyle: visualStyle?.name ?? null,
30298
+ profile
30299
+ },
30300
+ groups
30103
30301
  }
30104
30302
  });
30105
30303
  return;
30106
30304
  }
30107
- const spinner2 = isJsonMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
30305
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora(`Scaffolding scene project at ${dir}...`).start();
30108
30306
  try {
30109
- const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle });
30307
+ const result = await scaffoldSceneProject({ dir, name, aspect, duration, visualStyle, profile });
30110
30308
  const detectedIds = detectedAgentHosts().map((h) => h.id);
30111
30309
  const skillHosts = deriveInstallHosts(detectedIds);
30112
30310
  const projectAbs = resolve14(dir);
30113
- const skillResult = await installHyperframesSkill({
30311
+ const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
30114
30312
  projectDir: projectAbs,
30115
30313
  hosts: skillHosts
30116
- });
30117
- if (isJsonMode()) {
30314
+ }) : { success: true, files: [], bundleVersion: "not-installed" };
30315
+ if (isJsonMode() || isQuietMode()) {
30118
30316
  outputSuccess({
30119
30317
  command: "scene init",
30120
30318
  startedAt,
@@ -30124,16 +30322,27 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30124
30322
  aspect,
30125
30323
  duration,
30126
30324
  visualStyle: visualStyle?.name ?? null,
30325
+ profile,
30127
30326
  created: result.created,
30128
30327
  merged: result.merged,
30129
30328
  skipped: result.skipped,
30329
+ groups: result.groups,
30130
30330
  skillFiles: skillResult.files,
30131
30331
  skillBundleVersion: skillResult.bundleVersion
30132
30332
  }
30133
30333
  });
30134
30334
  return;
30135
30335
  }
30136
- spinner2?.succeed(source_default.green(`Scene project ready: ${dir}`));
30336
+ spinner2?.succeed(source_default.green(`Video project ready: ${dir}`));
30337
+ console.log();
30338
+ console.log(source_default.bold.cyan("Edit first"));
30339
+ console.log(source_default.dim("\u2500".repeat(60)));
30340
+ console.log(` ${source_default.bold("STORYBOARD.md")} ${source_default.dim("# beats: narration, backdrop, minimum duration")}`);
30341
+ console.log(` ${source_default.bold("DESIGN.md")} ${source_default.dim("# palette, typography, motion rules")}`);
30342
+ console.log();
30343
+ console.log(source_default.bold.cyan("Profile"));
30344
+ console.log(source_default.dim("\u2500".repeat(60)));
30345
+ console.log(` ${source_default.bold(profile)} ${source_default.dim(formatSceneInitProfile(profile))}`);
30137
30346
  console.log();
30138
30347
  console.log(source_default.bold.cyan("Files"));
30139
30348
  console.log(source_default.dim("\u2500".repeat(60)));
@@ -30144,7 +30353,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30144
30353
  const skillSkipped = skillResult.files.filter((f) => f.status === "skipped-exists");
30145
30354
  if (skillWritten.length + skillSkipped.length > 0) {
30146
30355
  console.log();
30147
- console.log(source_default.bold.cyan("Hyperframes skill"));
30356
+ console.log(source_default.bold.cyan("Composition rules"));
30148
30357
  console.log(source_default.dim("\u2500".repeat(60)));
30149
30358
  for (const f of skillWritten) console.log(source_default.green(" +"), f.path);
30150
30359
  for (const f of skillSkipped) console.log(source_default.dim(" \xB7"), f.path, source_default.dim("(kept existing)"));
@@ -30158,10 +30367,14 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30158
30367
  } else {
30159
30368
  console.log(` ${source_default.cyan("vibe scene styles")} ${source_default.dim("# pick a named style for DESIGN.md")}`);
30160
30369
  }
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.")}`);
30370
+ if (profile === "agent" || profile === "full") {
30371
+ 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.")}`);
30372
+ } else {
30373
+ console.log(` ${source_default.cyan("vibe scene install-skill")} ${source_default.dim("# add agent authoring rules later")}`);
30374
+ }
30162
30375
  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")}`);
30376
+ console.log(` ${source_default.cyan("vibe build")} ${source_default.dim("# build STORYBOARD.md into scenes/assets")}`);
30377
+ console.log(` ${source_default.cyan("vibe render")} ${source_default.dim("# render to video")}`);
30165
30378
  } catch (error) {
30166
30379
  spinner2?.fail("Failed to scaffold scene project");
30167
30380
  const msg = error instanceof Error ? error.message : String(error);
@@ -30449,7 +30662,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30449
30662
  exitWithError(generalError(msg));
30450
30663
  }
30451
30664
  });
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) => {
30665
+ 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
30666
  const startedAt = Date.now();
30454
30667
  const projectDir = resolve14(options.project);
30455
30668
  if (!await rootExists(projectDir, root)) {
@@ -30512,7 +30725,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30512
30725
  VALID_FPS = [24, 30, 60];
30513
30726
  VALID_QUALITIES = ["draft", "standard", "high"];
30514
30727
  VALID_FORMATS = ["mp4", "webm", "mov"];
30515
- sceneCommand.command("render").description("Render a scene project to MP4/WebM/MOV via the Hyperframes producer (requires Chrome)").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--fps <n>", `Frames per second: ${VALID_FPS.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").action(async (root, options) => {
30728
+ sceneCommand.command("render", { hidden: true }).description("Render a scene project to MP4/WebM/MOV via the Hyperframes producer (requires Chrome) [legacy \u2014 prefer `vibe render`]").argument("[root]", "Root composition file relative to --project", "index.html").option("--project <dir>", "Project directory", ".").option("-o, --out <path>", "Output file (default: renders/<name>-<timestamp>.<format>)").option("--fps <n>", `Frames per second: ${VALID_FPS.join("|")}`, "30").option("--quality <q>", `Quality preset: ${VALID_QUALITIES.join("|")}`, "standard").option("--format <f>", `Output container: ${VALID_FORMATS.join("|")}`, "mp4").option("--workers <n>", "Capture workers (1-16, default 1)", "1").option("--dry-run", "Preview parameters without rendering").action(async (root, options) => {
30516
30729
  const startedAt = Date.now();
30517
30730
  const fps = validateFps(options.fps);
30518
30731
  const quality = validateQuality(options.quality);
@@ -30588,7 +30801,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30588
30801
  }
30589
30802
  }
30590
30803
  });
30591
- sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, dispatch TTS + image-gen per beat, compose, render to MP4 (v0.60)").argument("[project-dir]", "Project directory containing STORYBOARD.md", ".").option("--mode <mode>", "Build mode: agent (host-agent authors HTML) | batch (CLI's internal LLM authors HTML) | auto (agent if any host detected) [Plan H \u2014 Phase 3]", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "LLM that composes scene HTML in batch mode: claude|openai|gemini (default: auto-resolve from available API keys, claude > gemini > openai)").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 (overrides frontmatter)").option("--voice <id>", "Voice id (provider-specific \u2014 overrides frontmatter)").option("--image-provider <name>", "Image provider: openai (only one supported in v0.60)").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").action(async (projectDirArg, options) => {
30804
+ sceneCommand.command("build", { hidden: true }).description("One-shot: read STORYBOARD.md cues, dispatch TTS + image-gen per beat, compose, render to MP4 [legacy \u2014 prefer `vibe build`]").argument("[project-dir]", "Project directory containing STORYBOARD.md", ".").option("--mode <mode>", "Build mode: agent (host-agent authors HTML) | batch (CLI's internal LLM authors HTML) | auto (agent if any host detected) [Plan H \u2014 Phase 3]", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "LLM that composes scene HTML in batch mode: claude|openai|gemini (default: auto-resolve from available API keys, claude > gemini > openai)").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 (overrides frontmatter)").option("--voice <id>", "Voice id (provider-specific \u2014 overrides frontmatter)").option("--image-provider <name>", "Image provider: openai (only one supported in v0.60)").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").action(async (projectDirArg, options) => {
30592
30805
  const startedAt = Date.now();
30593
30806
  const projectDir = resolve14(projectDirArg);
30594
30807
  if (options.dryRun) {
@@ -30705,7 +30918,7 @@ Run 'vibe schema scene.<command>' for structured parameter info.`);
30705
30918
  }
30706
30919
  }
30707
30920
  console.log();
30708
- console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe scene build` to lint + render."));
30921
+ console.log(source_default.dim("Once you've authored each beat's HTML, re-run `vibe build` to lint + render."));
30709
30922
  console.log(source_default.dim("Or pass `--mode batch` to use the internal LLM compose path instead."));
30710
30923
  return;
30711
30924
  }
@@ -32463,16 +32676,16 @@ async function prompt2(question) {
32463
32676
  input,
32464
32677
  output: process.stdout
32465
32678
  });
32466
- return new Promise((resolve62) => {
32679
+ return new Promise((resolve64) => {
32467
32680
  rl.question(question, (answer) => {
32468
32681
  rl.close();
32469
- resolve62(answer);
32682
+ resolve64(answer);
32470
32683
  });
32471
32684
  });
32472
32685
  }
32473
32686
  async function promptHidden(question) {
32474
32687
  const input = getTTYInputStream();
32475
- return new Promise((resolve62) => {
32688
+ return new Promise((resolve64) => {
32476
32689
  process.stdout.write(question);
32477
32690
  let value = "";
32478
32691
  if (typeof input.setRawMode === "function") {
@@ -32484,7 +32697,7 @@ async function promptHidden(question) {
32484
32697
  input.setRawMode(false);
32485
32698
  input.removeListener("data", onData);
32486
32699
  process.stdout.write("\n");
32487
- resolve62(value);
32700
+ resolve64(value);
32488
32701
  } else if (char === "") {
32489
32702
  input.setRawMode(false);
32490
32703
  process.stdout.write("\n");
@@ -32504,7 +32717,7 @@ async function promptHidden(question) {
32504
32717
  const rl = createInterface2({ input, output: process.stdout });
32505
32718
  rl.question("", (answer) => {
32506
32719
  rl.close();
32507
- resolve62(answer);
32720
+ resolve64(answer);
32508
32721
  });
32509
32722
  }
32510
32723
  });
@@ -32512,7 +32725,7 @@ async function promptHidden(question) {
32512
32725
  async function promptSelect(_question, options, defaultIndex = 0) {
32513
32726
  const input = getTTYInputStream();
32514
32727
  if (typeof input.setRawMode === "function") {
32515
- return new Promise((resolve62) => {
32728
+ return new Promise((resolve64) => {
32516
32729
  let selected = defaultIndex;
32517
32730
  const render = () => {
32518
32731
  if (renderCount > 0) {
@@ -32536,7 +32749,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
32536
32749
  input.setRawMode(false);
32537
32750
  input.removeListener("data", onData);
32538
32751
  process.stdout.write("\n");
32539
- resolve62(selected);
32752
+ resolve64(selected);
32540
32753
  } else if (char === "") {
32541
32754
  input.setRawMode(false);
32542
32755
  process.stdout.write("\n");
@@ -32553,7 +32766,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
32553
32766
  input.removeListener("data", onData);
32554
32767
  render();
32555
32768
  process.stdout.write("\n");
32556
- resolve62(selected);
32769
+ resolve64(selected);
32557
32770
  }
32558
32771
  };
32559
32772
  input.on("data", onData);
@@ -35343,7 +35556,7 @@ var init_dist3 = __esm({
35343
35556
  import { writeFile as writeFile23, unlink as unlink2, rename as rename2 } from "node:fs/promises";
35344
35557
  import { resolve as resolve27, basename as basename7 } from "node:path";
35345
35558
  function sleep(ms) {
35346
- return new Promise((resolve62) => setTimeout(resolve62, ms));
35559
+ return new Promise((resolve64) => setTimeout(resolve64, ms));
35347
35560
  }
35348
35561
  async function uploadToImgbb(imageBuffer, apiKey) {
35349
35562
  try {
@@ -36076,9 +36289,14 @@ var init_ai_script_pipeline = __esm({
36076
36289
  import { resolve as resolve29 } from "node:path";
36077
36290
  import { readFile as readFile17, writeFile as writeFile25 } from "node:fs/promises";
36078
36291
  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", `
36292
+ 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(
36293
+ "-d, --duration <sec>",
36294
+ "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
36295
+ "5"
36296
+ ).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
36297
  Examples:
36081
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default)
36298
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
36299
+ $ vibe gen vid "cinematic city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
36082
36300
  $ vibe gen vid "city timelapse" -o city.mp4 -p kling # Kling
36083
36301
  $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
36084
36302
  $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
@@ -36104,12 +36322,13 @@ Examples:
36104
36322
  if (options.output) {
36105
36323
  validateOutputPath(options.output);
36106
36324
  }
36107
- const validProviders = ["runway", "kling", "veo", "grok", "fal"];
36325
+ const validProviders = ["runway", "kling", "veo", "grok", "seedance", "fal"];
36108
36326
  const videoEnvMap = {
36109
36327
  grok: "XAI_API_KEY",
36110
36328
  veo: "GOOGLE_API_KEY",
36111
36329
  kling: "KLING_API_KEY",
36112
36330
  runway: "RUNWAY_API_SECRET",
36331
+ seedance: "FAL_KEY",
36113
36332
  fal: "FAL_KEY"
36114
36333
  };
36115
36334
  let provider;
@@ -36119,7 +36338,7 @@ Examples:
36119
36338
  exitWithError(
36120
36339
  usageError(
36121
36340
  `Invalid provider: ${provider}`,
36122
- `Available providers: ${validProviders.join(", ")}`
36341
+ "Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
36123
36342
  )
36124
36343
  );
36125
36344
  }
@@ -36182,14 +36401,15 @@ Examples:
36182
36401
  data: {
36183
36402
  params: {
36184
36403
  prompt: prompt3,
36185
- provider,
36404
+ provider: provider === "fal" ? "seedance" : provider,
36186
36405
  duration: options.duration,
36187
36406
  ratio: options.ratio,
36188
36407
  image: options.image,
36189
36408
  mode: options.mode,
36190
36409
  negative: options.negative,
36191
36410
  resolution: options.resolution,
36192
- veoModel: options.veoModel
36411
+ veoModel: options.veoModel,
36412
+ seedanceModel: options.seedanceModel
36193
36413
  }
36194
36414
  }
36195
36415
  });
@@ -36200,6 +36420,7 @@ Examples:
36200
36420
  kling: "KLING_API_KEY",
36201
36421
  veo: "GOOGLE_API_KEY",
36202
36422
  grok: "XAI_API_KEY",
36423
+ seedance: "FAL_KEY",
36203
36424
  fal: "FAL_KEY"
36204
36425
  };
36205
36426
  const providerNameMap = {
@@ -36207,7 +36428,8 @@ Examples:
36207
36428
  kling: "Kling",
36208
36429
  veo: "Veo",
36209
36430
  grok: "Grok",
36210
- fal: "fal.ai (Seedance 2.0)"
36431
+ seedance: "Seedance 2.0 via fal.ai",
36432
+ fal: "Seedance 2.0 via fal.ai"
36211
36433
  };
36212
36434
  const envKey = envKeyMap[provider];
36213
36435
  const providerName = providerNameMap[provider];
@@ -36426,15 +36648,15 @@ Examples:
36426
36648
  },
36427
36649
  3e5
36428
36650
  );
36429
- } else if (provider === "fal") {
36651
+ } else if (provider === "fal" || provider === "seedance") {
36430
36652
  const fal = new FalProvider();
36431
36653
  await fal.initialize({ apiKey });
36432
36654
  let falImage = referenceImage;
36433
36655
  if (falImage && falImage.startsWith("data:")) {
36434
- spinner2.text = "Uploading image to ImgBB for fal...";
36656
+ spinner2.text = "Uploading image to ImgBB for Seedance...";
36435
36657
  const imgbbKey = await getApiKeyFromConfig("imgbb") || process.env.IMGBB_API_KEY;
36436
36658
  if (!imgbbKey) {
36437
- spinner2.fail("ImgBB API key required for fal image-to-video");
36659
+ spinner2.fail("ImgBB API key required for Seedance image-to-video");
36438
36660
  exitWithError(authError("IMGBB_API_KEY", "ImgBB"));
36439
36661
  }
36440
36662
  const base64Data = falImage.split(",")[1];
@@ -36446,8 +36668,9 @@ Examples:
36446
36668
  }
36447
36669
  falImage = uploadResult.url;
36448
36670
  }
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";
36671
+ spinner2.text = "Generating video with fal.ai Seedance 2.0 (this may take 1-3 minutes)...";
36672
+ const seedanceModel = String(options.seedanceModel ?? "quality").toLowerCase();
36673
+ const falModel = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
36451
36674
  result = await fal.generateVideo(prompt3, {
36452
36675
  prompt: prompt3,
36453
36676
  referenceImage: falImage,
@@ -36572,7 +36795,8 @@ var init_generate = __esm({
36572
36795
  Examples:
36573
36796
  $ vibe generate image "a sunset over the ocean" -o sunset.png
36574
36797
  $ vibe generate image "logo design" -o logo.png -p openai
36575
- $ vibe generate video "dancing cat" -o cat.mp4 # Grok (default, native audio)
36798
+ $ vibe generate video "dancing cat" -o cat.mp4 # Seedance when FAL_KEY is set
36799
+ $ vibe generate video "city timelapse" -o city.mp4 -p seedance # Seedance via fal.ai
36576
36800
  $ vibe generate video "city timelapse" -o city.mp4 -p kling # Kling
36577
36801
  $ vibe generate video "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
36578
36802
  $ vibe generate speech "Hello world" -o hello.mp3
@@ -36582,7 +36806,8 @@ Examples:
36582
36806
  API Keys (per provider):
36583
36807
  GOOGLE_API_KEY Image (default), Veo video
36584
36808
  OPENAI_API_KEY Image (-p openai)
36585
- XAI_API_KEY Grok image/video (default video)
36809
+ FAL_KEY Seedance video (-p seedance, default video)
36810
+ XAI_API_KEY Grok image/video
36586
36811
  KLING_API_KEY Kling video (-p kling)
36587
36812
  RUNWAY_API_SECRET Runway video (-p runway)
36588
36813
  ELEVENLABS_API_KEY Speech, sound effects, music
@@ -51922,7 +52147,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
51922
52147
  return args;
51923
52148
  }
51924
52149
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
51925
- return new Promise((resolve62, reject) => {
52150
+ return new Promise((resolve64, reject) => {
51926
52151
  const ffmpeg = spawn(ffmpegPath, args, {
51927
52152
  stdio: ["pipe", "pipe", "pipe"]
51928
52153
  });
@@ -51946,7 +52171,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
51946
52171
  });
51947
52172
  ffmpeg.on("close", (code) => {
51948
52173
  if (code === 0) {
51949
- resolve62();
52174
+ resolve64();
51950
52175
  } else {
51951
52176
  const errorMatch = stderr.match(/Error.*$/m);
51952
52177
  const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
@@ -52083,16 +52308,16 @@ function getPresetSettings(preset, aspectRatio) {
52083
52308
  async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
52084
52309
  spinner2.text = "Loading project...";
52085
52310
  const { readFile: readFile42 } = await import("node:fs/promises");
52086
- const { resolve: resolve62, basename: basename22 } = await import("node:path");
52311
+ const { resolve: resolve64, basename: basename22 } = await import("node:path");
52087
52312
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
52088
52313
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
52089
52314
  const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
52090
52315
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
52091
- const filePath = resolve62(process.cwd(), projectPath);
52316
+ const filePath = resolve64(process.cwd(), projectPath);
52092
52317
  const content = await readFile42(filePath, "utf-8");
52093
52318
  const project = Project2.fromJSON(JSON.parse(content));
52094
52319
  const state = project.getState();
52095
- const outputPath = options.output ? resolve62(process.cwd(), options.output) : resolve62(process.cwd(), `${basename22(projectPath, ".vibe.json")}.${options.format ?? "mp4"}`);
52320
+ const outputPath = options.output ? resolve64(process.cwd(), options.output) : resolve64(process.cwd(), `${basename22(projectPath, ".vibe.json")}.${options.format ?? "mp4"}`);
52096
52321
  const quality = ["draft", "standard", "high"].includes(options.preset ?? "") ? options.preset : "standard";
52097
52322
  const backend = createHyperframesBackend2();
52098
52323
  spinner2.text = "Rendering with Hyperframes...";
@@ -52263,6 +52488,25 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
52263
52488
  spinner2.text = `Encoding... ${progress}%`;
52264
52489
  });
52265
52490
  spinner2.succeed(source_default.green(`Exported: ${outputPath}`));
52491
+ if (isJsonMode()) {
52492
+ outputSuccess({
52493
+ command: "export",
52494
+ startedAt,
52495
+ data: {
52496
+ outputPath,
52497
+ backend: "ffmpeg",
52498
+ format: options.format,
52499
+ preset: options.preset,
52500
+ resolution: presetSettings.resolution,
52501
+ duration: summary.duration,
52502
+ clipCount: summary.clipCount,
52503
+ bitrate: options.bitrate ?? null,
52504
+ fps: options.fps ?? null,
52505
+ codec: options.codec ?? null
52506
+ }
52507
+ });
52508
+ return;
52509
+ }
52266
52510
  console.log();
52267
52511
  console.log(source_default.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
52268
52512
  console.log(source_default.dim(" Clips:"), summary.clipCount);
@@ -52811,7 +53055,7 @@ __export(ai_video_exports, {
52811
53055
  executeVideoStatus: () => executeVideoStatus
52812
53056
  });
52813
53057
  import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
52814
- import { resolve as resolve52 } from "node:path";
53058
+ import { resolve as resolve54 } from "node:path";
52815
53059
  async function executeVideoGenerate(options) {
52816
53060
  const {
52817
53061
  prompt: prompt3,
@@ -52824,24 +53068,61 @@ async function executeVideoGenerate(options) {
52824
53068
  negative,
52825
53069
  resolution,
52826
53070
  veoModel = "3.1-fast",
53071
+ seedanceModel = "quality",
52827
53072
  output,
52828
53073
  wait = true,
52829
53074
  apiKey
52830
53075
  } = options;
52831
53076
  try {
52832
- const envKeyMap = { grok: "XAI_API_KEY", runway: "RUNWAY_API_SECRET", kling: "KLING_API_KEY", veo: "GOOGLE_API_KEY" };
53077
+ const envKeyMap = {
53078
+ grok: "XAI_API_KEY",
53079
+ runway: "RUNWAY_API_SECRET",
53080
+ kling: "KLING_API_KEY",
53081
+ veo: "GOOGLE_API_KEY",
53082
+ seedance: "FAL_KEY",
53083
+ fal: "FAL_KEY"
53084
+ };
52833
53085
  const key = apiKey || process.env[envKeyMap[provider] || ""];
52834
53086
  if (!key) return { success: false, error: `${envKeyMap[provider]} required for ${provider}` };
52835
53087
  let referenceImage;
52836
53088
  if (image) {
52837
- const imagePath = resolve52(process.cwd(), image);
53089
+ const imagePath = resolve54(process.cwd(), image);
52838
53090
  const imageBuffer = await readFile36(imagePath);
52839
53091
  const ext = image.toLowerCase().split(".").pop();
52840
53092
  const mimeTypes2 = { jpg: "image/jpeg", jpeg: "image/jpeg", png: "image/png", gif: "image/gif", webp: "image/webp" };
52841
53093
  const mimeType = mimeTypes2[ext || "png"] || "image/png";
52842
53094
  referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
52843
53095
  }
52844
- if (provider === "runway") {
53096
+ if (provider === "seedance" || provider === "fal") {
53097
+ const fal = new FalProvider();
53098
+ await fal.initialize({ apiKey: key });
53099
+ let falImage = referenceImage;
53100
+ if (falImage && falImage.startsWith("data:")) {
53101
+ const imgbbKey = process.env.IMGBB_API_KEY;
53102
+ if (!imgbbKey) return { success: false, error: "IMGBB_API_KEY required for Seedance image-to-video" };
53103
+ const base64Data = falImage.split(",")[1];
53104
+ const uploadResult = await uploadToImgbb(Buffer.from(base64Data, "base64"), imgbbKey);
53105
+ if (!uploadResult.success || !uploadResult.url) return { success: false, error: `ImgBB upload failed: ${uploadResult.error}` };
53106
+ falImage = uploadResult.url;
53107
+ }
53108
+ const model = seedanceModel === "fast" || seedanceModel === "seedance-2.0-fast" ? "seedance-2.0-fast" : "seedance-2.0";
53109
+ const result = await fal.generateVideo(prompt3, {
53110
+ prompt: prompt3,
53111
+ referenceImage: falImage,
53112
+ duration,
53113
+ aspectRatio: ratio,
53114
+ negativePrompt: negative,
53115
+ model
53116
+ });
53117
+ if (result.status === "failed") return { success: false, error: result.error || "Seedance generation failed" };
53118
+ let outputPath;
53119
+ if (output && result.videoUrl) {
53120
+ const buffer = await downloadVideo(result.videoUrl, key);
53121
+ outputPath = resolve54(process.cwd(), output);
53122
+ await writeFile40(outputPath, buffer);
53123
+ }
53124
+ return { success: true, taskId: result.id, status: "completed", videoUrl: result.videoUrl, outputPath, provider: "seedance" };
53125
+ } else if (provider === "runway") {
52845
53126
  const runway = new RunwayProvider();
52846
53127
  await runway.initialize({ apiKey: key });
52847
53128
  const result = await runway.generateVideo(prompt3, {
@@ -52859,7 +53140,7 @@ async function executeVideoGenerate(options) {
52859
53140
  let outputPath;
52860
53141
  if (output && finalResult.videoUrl) {
52861
53142
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52862
- outputPath = resolve52(process.cwd(), output);
53143
+ outputPath = resolve54(process.cwd(), output);
52863
53144
  await writeFile40(outputPath, buffer);
52864
53145
  }
52865
53146
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "runway" };
@@ -52893,7 +53174,7 @@ async function executeVideoGenerate(options) {
52893
53174
  let outputPath;
52894
53175
  if (output && finalResult.videoUrl) {
52895
53176
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52896
- outputPath = resolve52(process.cwd(), output);
53177
+ outputPath = resolve54(process.cwd(), output);
52897
53178
  await writeFile40(outputPath, buffer);
52898
53179
  }
52899
53180
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "kling" };
@@ -52920,7 +53201,7 @@ async function executeVideoGenerate(options) {
52920
53201
  let outputPath;
52921
53202
  if (output && finalResult.videoUrl) {
52922
53203
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52923
- outputPath = resolve52(process.cwd(), output);
53204
+ outputPath = resolve54(process.cwd(), output);
52924
53205
  await writeFile40(outputPath, buffer);
52925
53206
  }
52926
53207
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, outputPath, provider: "veo" };
@@ -52941,7 +53222,7 @@ async function executeVideoGenerate(options) {
52941
53222
  let outputPath;
52942
53223
  if (output && finalResult.videoUrl) {
52943
53224
  const buffer = await downloadVideo(finalResult.videoUrl, key);
52944
- outputPath = resolve52(process.cwd(), output);
53225
+ outputPath = resolve54(process.cwd(), output);
52945
53226
  await writeFile40(outputPath, buffer);
52946
53227
  }
52947
53228
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath, provider: "grok" };
@@ -52968,7 +53249,7 @@ async function executeVideoStatus(options) {
52968
53249
  let outputPath;
52969
53250
  if (output && result.videoUrl) {
52970
53251
  const buffer = await downloadVideo(result.videoUrl, key);
52971
- outputPath = resolve52(process.cwd(), output);
53252
+ outputPath = resolve54(process.cwd(), output);
52972
53253
  await writeFile40(outputPath, buffer);
52973
53254
  }
52974
53255
  return { success: true, taskId, status: result.status, progress: result.progress, videoUrl: result.videoUrl, outputPath };
@@ -52983,7 +53264,7 @@ async function executeVideoStatus(options) {
52983
53264
  let outputPath;
52984
53265
  if (output && result.videoUrl) {
52985
53266
  const buffer = await downloadVideo(result.videoUrl, key);
52986
- outputPath = resolve52(process.cwd(), output);
53267
+ outputPath = resolve54(process.cwd(), output);
52987
53268
  await writeFile40(outputPath, buffer);
52988
53269
  }
52989
53270
  return { success: true, taskId, status: result.status, videoUrl: result.videoUrl, duration: result.duration, outputPath };
@@ -53028,7 +53309,7 @@ async function executeVideoExtend(options) {
53028
53309
  let outputPath;
53029
53310
  if (output && finalResult.videoUrl) {
53030
53311
  const buffer = await downloadVideo(finalResult.videoUrl, key);
53031
- outputPath = resolve52(process.cwd(), output);
53312
+ outputPath = resolve54(process.cwd(), output);
53032
53313
  await writeFile40(outputPath, buffer);
53033
53314
  }
53034
53315
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, duration: finalResult.duration, outputPath };
@@ -53051,7 +53332,7 @@ async function executeVideoExtend(options) {
53051
53332
  let outputPath;
53052
53333
  if (output && finalResult.videoUrl) {
53053
53334
  const buffer = await downloadVideo(finalResult.videoUrl, key);
53054
- outputPath = resolve52(process.cwd(), output);
53335
+ outputPath = resolve54(process.cwd(), output);
53055
53336
  await writeFile40(outputPath, buffer);
53056
53337
  }
53057
53338
  return { success: true, taskId: result.id, status: "completed", videoUrl: finalResult.videoUrl, outputPath };
@@ -53079,7 +53360,7 @@ __export(ai_audio_exports, {
53079
53360
  executeTranscribe: () => executeTranscribe,
53080
53361
  executeVoiceClone: () => executeVoiceClone
53081
53362
  });
53082
- import { resolve as resolve53, dirname as dirname20, basename as basename20, extname as extname12 } from "node:path";
53363
+ import { resolve as resolve55, dirname as dirname20, basename as basename20, extname as extname12 } from "node:path";
53083
53364
  import { readFile as readFile37, writeFile as writeFile41 } from "node:fs/promises";
53084
53365
  import { existsSync as existsSync37 } from "node:fs";
53085
53366
  async function executeTranscribe(options) {
@@ -53087,7 +53368,7 @@ async function executeTranscribe(options) {
53087
53368
  try {
53088
53369
  const key = apiKey || process.env.OPENAI_API_KEY;
53089
53370
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
53090
- const absPath = resolve53(process.cwd(), audioPath);
53371
+ const absPath = resolve55(process.cwd(), audioPath);
53091
53372
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
53092
53373
  const whisper = new WhisperProvider();
53093
53374
  await whisper.initialize({ apiKey: key });
@@ -53099,7 +53380,7 @@ async function executeTranscribe(options) {
53099
53380
  }
53100
53381
  let outputPath;
53101
53382
  if (output) {
53102
- outputPath = resolve53(process.cwd(), output);
53383
+ outputPath = resolve55(process.cwd(), output);
53103
53384
  const fmt = detectFormat(output, format);
53104
53385
  const content = formatTranscript(result, fmt);
53105
53386
  await writeFile41(outputPath, content, "utf-8");
@@ -53120,7 +53401,7 @@ async function executeIsolate(options) {
53120
53401
  try {
53121
53402
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
53122
53403
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
53123
- const absPath = resolve53(process.cwd(), audioPath);
53404
+ const absPath = resolve55(process.cwd(), audioPath);
53124
53405
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
53125
53406
  const audioBuffer = await readFile37(absPath);
53126
53407
  const elevenlabs = new ElevenLabsProvider();
@@ -53129,7 +53410,7 @@ async function executeIsolate(options) {
53129
53410
  if (!result.success || !result.audioBuffer) {
53130
53411
  return { success: false, error: result.error || "Audio isolation failed" };
53131
53412
  }
53132
- const outputPath = resolve53(process.cwd(), output);
53413
+ const outputPath = resolve55(process.cwd(), output);
53133
53414
  await writeFile41(outputPath, result.audioBuffer);
53134
53415
  return { success: true, outputPath };
53135
53416
  } catch (error) {
@@ -53146,7 +53427,7 @@ async function executeVoiceClone(options) {
53146
53427
  }
53147
53428
  const audioBuffers = [];
53148
53429
  for (const samplePath of samplePaths) {
53149
- const absPath = resolve53(process.cwd(), samplePath);
53430
+ const absPath = resolve55(process.cwd(), samplePath);
53150
53431
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${samplePath}` };
53151
53432
  const buffer = await readFile37(absPath);
53152
53433
  audioBuffers.push(buffer);
@@ -53168,7 +53449,7 @@ async function executeVoiceClone(options) {
53168
53449
  async function executeDub(options) {
53169
53450
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
53170
53451
  try {
53171
- const absPath = resolve53(process.cwd(), mediaPath);
53452
+ const absPath = resolve55(process.cwd(), mediaPath);
53172
53453
  if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
53173
53454
  const openaiKey = process.env.OPENAI_API_KEY;
53174
53455
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
@@ -53180,7 +53461,7 @@ async function executeDub(options) {
53180
53461
  const isVideo = [".mp4", ".mov", ".avi", ".mkv", ".webm"].includes(ext);
53181
53462
  let audioPath = absPath;
53182
53463
  if (isVideo) {
53183
- const tempAudioPath = resolve53(dirname20(absPath), `temp-audio-${Date.now()}.mp3`);
53464
+ const tempAudioPath = resolve55(dirname20(absPath), `temp-audio-${Date.now()}.mp3`);
53184
53465
  execSafeSync("ffmpeg", ["-i", absPath, "-vn", "-acodec", "mp3", "-y", tempAudioPath]);
53185
53466
  audioPath = tempAudioPath;
53186
53467
  }
@@ -53229,7 +53510,7 @@ ${segmentTexts}`,
53229
53510
  }
53230
53511
  if (analyzeOnly) {
53231
53512
  if (output) {
53232
- const timingPath = resolve53(process.cwd(), output);
53513
+ const timingPath = resolve55(process.cwd(), output);
53233
53514
  const timingData = {
53234
53515
  sourcePath: absPath,
53235
53516
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
@@ -53248,7 +53529,7 @@ ${segmentTexts}`,
53248
53529
  sourceLanguage: transcriptResult.detectedLanguage || source || "auto",
53249
53530
  targetLanguage: language,
53250
53531
  segmentCount: segments.length,
53251
- outputPath: output ? resolve53(process.cwd(), output) : void 0
53532
+ outputPath: output ? resolve55(process.cwd(), output) : void 0
53252
53533
  };
53253
53534
  }
53254
53535
  const elevenlabs = new ElevenLabsProvider();
@@ -53262,8 +53543,8 @@ ${segmentTexts}`,
53262
53543
  }
53263
53544
  const combinedBuffer = Buffer.concat(dubbedBuffers);
53264
53545
  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);
53546
+ const defaultOutputPath2 = resolve55(dirname20(absPath), `${basename20(absPath, extname12(absPath))}-${language}${outputExt}`);
53547
+ const finalOutputPath = resolve55(process.cwd(), output || defaultOutputPath2);
53267
53548
  await writeFile41(finalOutputPath, combinedBuffer);
53268
53549
  if (isVideo && audioPath !== absPath) {
53269
53550
  try {
@@ -53295,12 +53576,12 @@ async function executeDuck(options) {
53295
53576
  } = options;
53296
53577
  try {
53297
53578
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
53298
- const absMusicPath = resolve53(process.cwd(), musicPath);
53299
- const absVoicePath = resolve53(process.cwd(), voicePath);
53579
+ const absMusicPath = resolve55(process.cwd(), musicPath);
53580
+ const absVoicePath = resolve55(process.cwd(), voicePath);
53300
53581
  if (!existsSync37(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
53301
53582
  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);
53583
+ const defaultOutput = resolve55(dirname20(absMusicPath), `${basename20(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
53584
+ const outputPath = resolve55(process.cwd(), output || defaultOutput);
53304
53585
  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
53586
  await execSafe("ffmpeg", [
53306
53587
  "-i",
@@ -53328,11 +53609,11 @@ var init_ai_audio = __esm({
53328
53609
  });
53329
53610
 
53330
53611
  // src/pipeline/executor.ts
53331
- import { resolve as resolve54 } from "node:path";
53612
+ import { resolve as resolve56 } from "node:path";
53332
53613
  import { readFile as readFile38, writeFile as writeFile42, mkdir as mkdir22 } from "node:fs/promises";
53333
53614
  import { existsSync as existsSync38 } from "node:fs";
53334
53615
  function maxCostFor(action) {
53335
- const cmd = ACTION_TO_COMMAND[action];
53616
+ const cmd = ACTION_METADATA[action]?.command;
53336
53617
  if (!cmd) return 0;
53337
53618
  return COST_ESTIMATES[cmd]?.max ?? 0;
53338
53619
  }
@@ -53341,9 +53622,9 @@ function registerAction(action, handler) {
53341
53622
  }
53342
53623
  function getOutput(params, outputDir, defaultName) {
53343
53624
  if (params.output && typeof params.output === "string") {
53344
- return resolve54(outputDir, params.output);
53625
+ return resolve56(outputDir, params.output);
53345
53626
  }
53346
- return resolve54(outputDir, defaultName);
53627
+ return resolve56(outputDir, defaultName);
53347
53628
  }
53348
53629
  async function ensureActionsRegistered() {
53349
53630
  if (Object.keys(ACTION_HANDLERS).length > 0) return;
@@ -53356,7 +53637,15 @@ async function ensureActionsRegistered() {
53356
53637
  registerAction("generate-video", async (params, outputDir) => {
53357
53638
  const { executeVideoGenerate: executeVideoGenerate2 } = await Promise.resolve().then(() => (init_ai_video(), ai_video_exports));
53358
53639
  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 });
53640
+ const r = await executeVideoGenerate2({
53641
+ prompt: params.prompt,
53642
+ provider: params.provider,
53643
+ image: params.image,
53644
+ duration: params.duration,
53645
+ ratio: params.ratio,
53646
+ output,
53647
+ wait: true
53648
+ });
53360
53649
  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
53650
  });
53362
53651
  registerAction("generate-tts", async (params, outputDir) => {
@@ -53488,7 +53777,7 @@ async function ensureActionsRegistered() {
53488
53777
  const { executeSceneBuild: executeSceneBuild2 } = await Promise.resolve().then(() => (init_scene_build(), scene_build_exports));
53489
53778
  const projectRel = params.project ?? ".";
53490
53779
  const r = await executeSceneBuild2({
53491
- projectDir: resolve54(outputDir, projectRel),
53780
+ projectDir: resolve56(outputDir, projectRel),
53492
53781
  mode: params.mode,
53493
53782
  effort: params.effort,
53494
53783
  composer: params.composer,
@@ -53514,7 +53803,7 @@ async function ensureActionsRegistered() {
53514
53803
  const { executeSceneRender: executeSceneRender2 } = await Promise.resolve().then(() => (init_scene_render(), scene_render_exports));
53515
53804
  const projectRel = params.project ?? ".";
53516
53805
  const r = await executeSceneRender2({
53517
- projectDir: resolve54(outputDir, projectRel),
53806
+ projectDir: resolve56(outputDir, projectRel),
53518
53807
  root: params.root,
53519
53808
  output: params.output,
53520
53809
  fps: params.fps,
@@ -53538,7 +53827,7 @@ async function ensureActionsRegistered() {
53538
53827
  });
53539
53828
  }
53540
53829
  async function loadPipeline(filePath) {
53541
- const absPath = resolve54(process.cwd(), filePath);
53830
+ const absPath = resolve56(process.cwd(), filePath);
53542
53831
  if (!existsSync38(absPath)) {
53543
53832
  throw new Error(`Pipeline file not found: ${absPath}`);
53544
53833
  }
@@ -53557,13 +53846,13 @@ async function loadPipeline(filePath) {
53557
53846
  }
53558
53847
  async function executePipeline(manifest2, options = {}) {
53559
53848
  await ensureActionsRegistered();
53560
- const outputDir = resolve54(process.cwd(), options.outputDir || `${manifest2.name}-output`);
53849
+ const outputDir = resolve56(process.cwd(), options.outputDir || `${manifest2.name}-output`);
53561
53850
  await mkdir22(outputDir, { recursive: true });
53562
53851
  const completedSteps = /* @__PURE__ */ new Map();
53563
53852
  const results = [];
53564
53853
  const startTime = Date.now();
53565
53854
  if (options.resume) {
53566
- const checkpointPath = resolve54(outputDir, CHECKPOINT_FILE);
53855
+ const checkpointPath = resolve56(outputDir, CHECKPOINT_FILE);
53567
53856
  if (existsSync38(checkpointPath)) {
53568
53857
  const checkpointContent = await readFile38(checkpointPath, "utf-8");
53569
53858
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
@@ -53684,7 +53973,7 @@ async function executePipeline(manifest2, options = {}) {
53684
53973
  }))
53685
53974
  };
53686
53975
  await writeFile42(
53687
- resolve54(outputDir, CHECKPOINT_FILE),
53976
+ resolve56(outputDir, CHECKPOINT_FILE),
53688
53977
  (0, import_yaml8.stringify)(checkpoint, { indent: 2 }),
53689
53978
  "utf-8"
53690
53979
  );
@@ -53731,42 +54020,48 @@ async function executePipeline(manifest2, options = {}) {
53731
54020
  budget: effectiveBudget ? budgetUsage : void 0
53732
54021
  };
53733
54022
  }
53734
- var import_yaml8, ACTION_TO_COMMAND, ACTION_HANDLERS, CHECKPOINT_FILE;
54023
+ var import_yaml8, ACTION_METADATA, ACTION_HANDLERS, CHECKPOINT_FILE;
53735
54024
  var init_executor = __esm({
53736
54025
  "src/pipeline/executor.ts"() {
53737
54026
  "use strict";
53738
54027
  import_yaml8 = __toESM(require_dist(), 1);
53739
54028
  init_resolver();
53740
54029
  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"
54030
+ ACTION_METADATA = {
54031
+ "generate-image": { id: "generate-image", title: "Generate image", category: "generate", command: "generate image", outputs: ["image"] },
54032
+ "generate-video": { id: "generate-video", title: "Generate video", category: "generate", command: "generate video", outputs: ["video"], requiredKeys: ["provider-dependent"] },
54033
+ "generate-tts": { id: "generate-tts", title: "Generate speech", category: "generate", command: "generate speech", outputs: ["audio"] },
54034
+ "generate-sfx": { id: "generate-sfx", title: "Generate sound effect", category: "generate", command: "generate sound-effect", outputs: ["audio"] },
54035
+ "generate-music": { id: "generate-music", title: "Generate music", category: "generate", command: "generate music", outputs: ["audio"] },
54036
+ "generate-storyboard": { id: "generate-storyboard", title: "Generate storyboard", category: "generate", command: "generate storyboard", outputs: ["storyboard"] },
54037
+ "generate-motion": { id: "generate-motion", title: "Generate motion", category: "generate", command: "generate motion", outputs: ["code", "video"] },
54038
+ "edit-silence-cut": { id: "edit-silence-cut", title: "Cut silence", category: "edit", command: "edit silence-cut", outputs: ["video"] },
54039
+ "edit-jump-cut": { id: "edit-jump-cut", title: "Jump cut", category: "edit", command: "edit jump-cut", outputs: ["video"] },
54040
+ "edit-caption": { id: "edit-caption", title: "Caption video", category: "edit", command: "edit caption", outputs: ["video", "srt"] },
54041
+ "edit-noise-reduce": { id: "edit-noise-reduce", title: "Reduce noise", category: "edit", command: "edit noise-reduce", outputs: ["video"] },
54042
+ "edit-fade": { id: "edit-fade", title: "Add fade", category: "edit", command: "edit fade", outputs: ["video"] },
54043
+ "edit-translate-srt": { id: "edit-translate-srt", title: "Translate subtitles", category: "edit", command: "edit translate-srt", outputs: ["srt"] },
54044
+ "edit-text-overlay": { id: "edit-text-overlay", title: "Add text overlay", category: "edit", command: "edit text-overlay", outputs: ["video"] },
54045
+ "edit-grade": { id: "edit-grade", title: "Color grade", category: "edit", command: "edit grade", outputs: ["video"] },
54046
+ "edit-speed-ramp": { id: "edit-speed-ramp", title: "Speed ramp", category: "edit", command: "edit speed-ramp", outputs: ["video"] },
54047
+ "edit-reframe": { id: "edit-reframe", title: "Reframe video", category: "edit", command: "edit reframe", outputs: ["video"] },
54048
+ "edit-interpolate": { id: "edit-interpolate", title: "Interpolate frames", category: "edit", command: "edit interpolate", outputs: ["video"] },
54049
+ "edit-upscale": { id: "edit-upscale", title: "Upscale video", category: "edit", command: "edit upscale-video", outputs: ["video"] },
54050
+ "edit-image": { id: "edit-image", title: "Edit image", category: "edit", command: "edit image", outputs: ["image"] },
54051
+ "audio-transcribe": { id: "audio-transcribe", title: "Transcribe audio", category: "audio", command: "audio transcribe", outputs: ["transcript", "srt"] },
54052
+ "audio-isolate": { id: "audio-isolate", title: "Isolate audio", category: "audio", outputs: ["audio"] },
54053
+ "audio-dub": { id: "audio-dub", title: "Dub audio", category: "audio", outputs: ["audio", "video"] },
54054
+ "audio-duck": { id: "audio-duck", title: "Duck audio", category: "audio", outputs: ["video"] },
54055
+ "detect-scenes": { id: "detect-scenes", title: "Detect scenes", category: "detect", command: "detect scenes", outputs: ["json"] },
54056
+ "detect-silence": { id: "detect-silence", title: "Detect silence", category: "detect", command: "detect silence", outputs: ["json"] },
54057
+ "detect-beats": { id: "detect-beats", title: "Detect beats", category: "detect", command: "detect beats", outputs: ["json"] },
54058
+ "analyze-media": { id: "analyze-media", title: "Analyze media", category: "analyze", command: "analyze media", outputs: ["json"] },
54059
+ "analyze-video": { id: "analyze-video", title: "Analyze video", category: "analyze", command: "analyze video", outputs: ["json"] },
54060
+ "review-video": { id: "review-video", title: "Review video", category: "analyze", command: "analyze review", outputs: ["json"] },
54061
+ "compose-scenes-with-skills": { id: "compose-scenes-with-skills", title: "Compose scenes with skills", category: "scene", command: "compose scenes with skills", outputs: ["html"] },
54062
+ "scene-build": { id: "scene-build", title: "Build scene project", category: "scene", outputs: ["video", "html", "assets"] },
54063
+ "scene-render": { id: "scene-render", title: "Render scene project", category: "scene", outputs: ["video"] },
54064
+ export: { id: "export", title: "Export project", category: "export", outputs: ["video"] }
53770
54065
  };
53771
54066
  ACTION_HANDLERS = {};
53772
54067
  CHECKPOINT_FILE = ".pipeline-state.yaml";
@@ -58641,7 +58936,7 @@ var init_define_tool = __esm({
58641
58936
  });
58642
58937
 
58643
58938
  // src/tools/manifest/scene.ts
58644
- import { resolve as resolve55, relative as relative7 } from "node:path";
58939
+ import { resolve as resolve57, relative as relative7 } from "node:path";
58645
58940
  function summariseLint(result) {
58646
58941
  return {
58647
58942
  ok: result.ok,
@@ -58757,10 +59052,10 @@ var init_scene2 = __esm({
58757
59052
  name: "scene_init",
58758
59053
  category: "scene",
58759
59054
  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.",
59055
+ 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
59056
  schema: sceneInitSchema,
58762
59057
  async execute(args, ctx) {
58763
- const dir = resolve55(ctx.workingDirectory, args.dir);
59058
+ const dir = resolve57(ctx.workingDirectory, args.dir);
58764
59059
  const result = await scaffoldSceneProject({
58765
59060
  dir,
58766
59061
  name: args.name,
@@ -58808,7 +59103,7 @@ var init_scene2 = __esm({
58808
59103
  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
59104
  schema: sceneAddSchema,
58810
59105
  async execute(args, ctx) {
58811
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59106
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58812
59107
  const result = await executeSceneAdd({
58813
59108
  name: args.name,
58814
59109
  preset: args.preset ?? "simple",
@@ -58865,7 +59160,7 @@ var init_scene2 = __esm({
58865
59160
  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
59161
  schema: sceneLintSchema,
58867
59162
  async execute(args, ctx) {
58868
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59163
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58869
59164
  const result = await runProjectLint({
58870
59165
  projectDir,
58871
59166
  rootRel: args.root,
@@ -58906,7 +59201,7 @@ var init_scene2 = __esm({
58906
59201
  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
59202
  schema: sceneRenderSchema,
58908
59203
  async execute(args, ctx) {
58909
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59204
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58910
59205
  const result = await executeSceneRender({
58911
59206
  projectDir,
58912
59207
  root: args.root,
@@ -58961,7 +59256,7 @@ var init_scene2 = __esm({
58961
59256
  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
59257
  schema: sceneBuildSchema,
58963
59258
  async execute(args, ctx) {
58964
- const projectDir = args.projectDir ? resolve55(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
59259
+ const projectDir = args.projectDir ? resolve57(ctx.workingDirectory, args.projectDir) : ctx.workingDirectory;
58965
59260
  const result = await executeSceneBuild({
58966
59261
  projectDir,
58967
59262
  mode: args.mode,
@@ -59022,7 +59317,7 @@ var init_scene2 = __esm({
59022
59317
  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
59318
  schema: sceneInstallSkillSchema,
59024
59319
  async execute(args, ctx) {
59025
- const projectDir = resolve55(ctx.workingDirectory, args.projectDir);
59320
+ const projectDir = resolve57(ctx.workingDirectory, args.projectDir);
59026
59321
  const hostFlag = args.host ?? "auto";
59027
59322
  const hosts = (() => {
59028
59323
  if (hostFlag === "all") return ["all"];
@@ -59064,7 +59359,7 @@ var init_scene2 = __esm({
59064
59359
  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
59360
  schema: sceneComposePromptsSchema,
59066
59361
  async execute(args, ctx) {
59067
- const projectDir = resolve55(ctx.workingDirectory, args.projectDir);
59362
+ const projectDir = resolve57(ctx.workingDirectory, args.projectDir);
59068
59363
  const result = await getComposePrompts({
59069
59364
  projectDir,
59070
59365
  beatId: args.beat
@@ -59654,12 +59949,12 @@ var init_edit = __esm({
59654
59949
 
59655
59950
  // src/commands/ai-suggest-edit.ts
59656
59951
  import { readFile as readFile39, writeFile as writeFile43 } from "node:fs/promises";
59657
- import { resolve as resolve56 } from "node:path";
59952
+ import { resolve as resolve58 } from "node:path";
59658
59953
  async function executeSuggestEdit(options) {
59659
59954
  try {
59660
59955
  const apiKey = options.apiKey ?? process.env.GOOGLE_API_KEY;
59661
59956
  if (!apiKey) return { success: false, error: "GOOGLE_API_KEY required for suggest" };
59662
- const filePath = resolve56(process.cwd(), options.projectPath);
59957
+ const filePath = resolve58(process.cwd(), options.projectPath);
59663
59958
  const content = await readFile39(filePath, "utf-8");
59664
59959
  const data = JSON.parse(content);
59665
59960
  const project = Project.fromJSON(data);
@@ -60438,9 +60733,9 @@ var init_detect2 = __esm({
60438
60733
 
60439
60734
  // src/tools/manifest/_project-io.ts
60440
60735
  import { readFile as readFile40, writeFile as writeFile45 } from "node:fs/promises";
60441
- import { resolve as resolve57 } from "node:path";
60736
+ import { resolve as resolve59 } from "node:path";
60442
60737
  async function loadProject(projectPath, cwd) {
60443
- const absPath = resolve57(cwd, projectPath);
60738
+ const absPath = resolve59(cwd, projectPath);
60444
60739
  const content = await readFile40(absPath, "utf-8");
60445
60740
  const data = JSON.parse(content);
60446
60741
  return { project: Project.fromJSON(data), absPath };
@@ -60456,7 +60751,7 @@ var init_project_io = __esm({
60456
60751
  });
60457
60752
 
60458
60753
  // src/tools/manifest/timeline.ts
60459
- import { resolve as resolve58 } from "node:path";
60754
+ import { resolve as resolve60 } from "node:path";
60460
60755
  var MEDIA_TYPES, timelineAddSourceTool, timelineAddClipTool, timelineSplitClipTool, timelineTrimClipTool, timelineMoveClipTool, timelineDeleteClipTool, timelineDuplicateClipTool, timelineAddEffectTool, timelineAddTrackTool, timelineListTool, timelineTools;
60461
60756
  var init_timeline = __esm({
60462
60757
  "src/tools/manifest/timeline.ts"() {
@@ -60492,7 +60787,7 @@ var init_timeline = __esm({
60492
60787
  }),
60493
60788
  async execute(args, ctx) {
60494
60789
  const { project, absPath } = await loadProject(args.projectPath, ctx.workingDirectory);
60495
- const mediaPath = resolve58(ctx.workingDirectory, args.mediaPath);
60790
+ const mediaPath = resolve60(ctx.workingDirectory, args.mediaPath);
60496
60791
  const ext = mediaPath.split(".").pop()?.toLowerCase() ?? "";
60497
60792
  const source = project.addSource({
60498
60793
  name: args.name ?? mediaPath.split("/").pop() ?? "media",
@@ -60718,7 +61013,7 @@ var init_timeline = __esm({
60718
61013
  });
60719
61014
 
60720
61015
  // src/tools/manifest/project.ts
60721
- import { resolve as resolve59 } from "node:path";
61016
+ import { resolve as resolve61 } from "node:path";
60722
61017
  import { writeFile as writeFile46 } from "node:fs/promises";
60723
61018
  var projectCreateTool, projectInfoTool, projectTools;
60724
61019
  var init_project2 = __esm({
@@ -60741,7 +61036,7 @@ var init_project2 = __esm({
60741
61036
  fps: external_exports.number().optional().describe("Frames per second (default: 30)")
60742
61037
  }),
60743
61038
  async execute(args, ctx) {
60744
- const outputPath = resolve59(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
61039
+ const outputPath = resolve61(ctx.workingDirectory, args.outputPath ?? `${args.name}.vibe.json`);
60745
61040
  const project = new Project(args.name);
60746
61041
  if (args.fps) project.setFrameRate(args.fps);
60747
61042
  await writeFile46(outputPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
@@ -60787,7 +61082,7 @@ var init_project2 = __esm({
60787
61082
  });
60788
61083
 
60789
61084
  // src/tools/manifest/export.ts
60790
- import { resolve as resolve60 } from "node:path";
61085
+ import { resolve as resolve62 } from "node:path";
60791
61086
  var exportVideoTool, exportTools;
60792
61087
  var init_export2 = __esm({
60793
61088
  "src/tools/manifest/export.ts"() {
@@ -60808,8 +61103,8 @@ var init_export2 = __esm({
60808
61103
  overwrite: external_exports.boolean().optional().describe("Overwrite existing output file (default: false)")
60809
61104
  }),
60810
61105
  async execute(args, ctx) {
60811
- const projectPath = resolve60(ctx.workingDirectory, args.projectPath);
60812
- const outputPath = resolve60(ctx.workingDirectory, args.outputPath);
61106
+ const projectPath = resolve62(ctx.workingDirectory, args.projectPath);
61107
+ const outputPath = resolve62(ctx.workingDirectory, args.outputPath);
60813
61108
  const result = await runExport(projectPath, outputPath, {
60814
61109
  preset: args.preset,
60815
61110
  format: args.format,
@@ -60829,7 +61124,7 @@ var init_export2 = __esm({
60829
61124
 
60830
61125
  // src/tools/manifest/agent-only.ts
60831
61126
  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";
61127
+ import { resolve as resolve63, join as join15, basename as basename21, extname as extname13 } from "node:path";
60833
61128
  function matchPattern(filename, pattern) {
60834
61129
  const regex2 = new RegExp(
60835
61130
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -60885,11 +61180,11 @@ async function getMediaDuration2(filePath) {
60885
61180
  }
60886
61181
  }
60887
61182
  async function resolveProjectPath3(inputPath, cwd) {
60888
- const filePath = resolve61(cwd, inputPath);
61183
+ const filePath = resolve63(cwd, inputPath);
60889
61184
  try {
60890
61185
  const stats = await stat6(filePath);
60891
61186
  if (stats.isDirectory()) {
60892
- return resolve61(filePath, "project.vibe.json");
61187
+ return resolve63(filePath, "project.vibe.json");
60893
61188
  }
60894
61189
  } catch {
60895
61190
  }
@@ -60917,7 +61212,7 @@ var init_agent_only = __esm({
60917
61212
  async execute(args, ctx) {
60918
61213
  const dirPath = args.path ?? ".";
60919
61214
  try {
60920
- const absPath = resolve61(ctx.workingDirectory, dirPath);
61215
+ const absPath = resolve63(ctx.workingDirectory, dirPath);
60921
61216
  const entries = await readdir4(absPath, { withFileTypes: true });
60922
61217
  const results = [];
60923
61218
  for (const entry of entries) {
@@ -60948,7 +61243,7 @@ var init_agent_only = __esm({
60948
61243
  }),
60949
61244
  async execute(args, ctx) {
60950
61245
  try {
60951
- const absPath = resolve61(ctx.workingDirectory, args.path);
61246
+ const absPath = resolve63(ctx.workingDirectory, args.path);
60952
61247
  const content = await readFile41(absPath, "utf-8");
60953
61248
  const maxLength = 4e3;
60954
61249
  const truncated = content.length > maxLength;
@@ -60971,7 +61266,7 @@ var init_agent_only = __esm({
60971
61266
  }),
60972
61267
  async execute(args, ctx) {
60973
61268
  try {
60974
- const absPath = resolve61(ctx.workingDirectory, args.path);
61269
+ const absPath = resolve63(ctx.workingDirectory, args.path);
60975
61270
  await writeFile47(absPath, args.content, "utf-8");
60976
61271
  return { success: true, data: { bytes: args.content.length }, humanLines: [`File written: ${args.path} (${formatSize(args.content.length)})`] };
60977
61272
  } catch (error) {
@@ -60989,7 +61284,7 @@ var init_agent_only = __esm({
60989
61284
  path: external_exports.string().describe("Path to check")
60990
61285
  }),
60991
61286
  async execute(args, ctx) {
60992
- const absPath = resolve61(ctx.workingDirectory, args.path);
61287
+ const absPath = resolve63(ctx.workingDirectory, args.path);
60993
61288
  try {
60994
61289
  await access8(absPath);
60995
61290
  const stats = await stat6(absPath);
@@ -61015,11 +61310,11 @@ var init_agent_only = __esm({
61015
61310
  }),
61016
61311
  async execute(args, ctx) {
61017
61312
  try {
61018
- const filePath = resolve61(ctx.workingDirectory, args.project);
61313
+ const filePath = resolve63(ctx.workingDirectory, args.project);
61019
61314
  const content = await readFile41(filePath, "utf-8");
61020
61315
  const data = JSON.parse(content);
61021
61316
  const project = Project.fromJSON(data);
61022
- const dirPath = resolve61(ctx.workingDirectory, args.directory);
61317
+ const dirPath = resolve63(ctx.workingDirectory, args.directory);
61023
61318
  const filterExts = args.filter ? args.filter.split(",").map((e) => e.trim().toLowerCase()) : null;
61024
61319
  const imageDuration = args.imageDuration ?? 5;
61025
61320
  const recursive = args.recursive ?? false;
@@ -61085,7 +61380,7 @@ var init_agent_only = __esm({
61085
61380
  }),
61086
61381
  async execute(args, ctx) {
61087
61382
  try {
61088
- const filePath = resolve61(ctx.workingDirectory, args.project);
61383
+ const filePath = resolve63(ctx.workingDirectory, args.project);
61089
61384
  const content = await readFile41(filePath, "utf-8");
61090
61385
  const data = JSON.parse(content);
61091
61386
  const project = Project.fromJSON(data);
@@ -61154,7 +61449,7 @@ var init_agent_only = __esm({
61154
61449
  }),
61155
61450
  async execute(args, ctx) {
61156
61451
  try {
61157
- const filePath = resolve61(ctx.workingDirectory, args.project);
61452
+ const filePath = resolve63(ctx.workingDirectory, args.project);
61158
61453
  const content = await readFile41(filePath, "utf-8");
61159
61454
  const data = JSON.parse(content);
61160
61455
  const project = Project.fromJSON(data);
@@ -61432,7 +61727,7 @@ var init_agent_only = __esm({
61432
61727
  }),
61433
61728
  async execute(args, ctx) {
61434
61729
  try {
61435
- const absPath = resolve61(ctx.workingDirectory, args.path);
61730
+ const absPath = resolve63(ctx.workingDirectory, args.path);
61436
61731
  const { stdout } = await execSafe(
61437
61732
  "ffprobe",
61438
61733
  ["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", absPath],
@@ -61519,10 +61814,10 @@ Audio:`);
61519
61814
  }),
61520
61815
  async execute(args, ctx) {
61521
61816
  try {
61522
- const absInput = resolve61(ctx.workingDirectory, args.input);
61817
+ const absInput = resolve63(ctx.workingDirectory, args.input);
61523
61818
  const ext = args.input.split(".").pop() || "mp4";
61524
61819
  const baseName = args.input.replace(/\.[^/.]+$/, "");
61525
- const outputPath = args.output ? resolve61(ctx.workingDirectory, args.output) : resolve61(ctx.workingDirectory, `${baseName}-compressed.${ext}`);
61820
+ const outputPath = args.output ? resolve63(ctx.workingDirectory, args.output) : resolve63(ctx.workingDirectory, `${baseName}-compressed.${ext}`);
61526
61821
  const crfValues = { low: 28, medium: 23, high: 18 };
61527
61822
  const crf = crfValues[args.quality ?? "medium"];
61528
61823
  await execSafe(
@@ -61580,8 +61875,8 @@ Audio:`);
61580
61875
  }),
61581
61876
  async execute(args, ctx) {
61582
61877
  try {
61583
- const absInput = resolve61(ctx.workingDirectory, args.input);
61584
- const absOutput = resolve61(ctx.workingDirectory, args.output);
61878
+ const absInput = resolve63(ctx.workingDirectory, args.input);
61879
+ const absOutput = resolve63(ctx.workingDirectory, args.output);
61585
61880
  const codecMap = {
61586
61881
  h264: "libx264",
61587
61882
  h265: "libx265",
@@ -61632,11 +61927,11 @@ Audio:`);
61632
61927
  };
61633
61928
  }
61634
61929
  try {
61635
- const absOutput = resolve61(ctx.workingDirectory, args.output);
61930
+ const absOutput = resolve63(ctx.workingDirectory, args.output);
61636
61931
  if (args.reencode) {
61637
61932
  const ffmpegArgs = [];
61638
61933
  for (const i of args.inputs) {
61639
- ffmpegArgs.push("-i", resolve61(ctx.workingDirectory, i));
61934
+ ffmpegArgs.push("-i", resolve63(ctx.workingDirectory, i));
61640
61935
  }
61641
61936
  const filterComplex = args.inputs.map((_, i) => `[${i}:v][${i}:a]`).join("");
61642
61937
  ffmpegArgs.push(
@@ -61651,8 +61946,8 @@ Audio:`);
61651
61946
  );
61652
61947
  await execSafe("ffmpeg", ffmpegArgs, { maxBuffer: 100 * 1024 * 1024 });
61653
61948
  } 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");
61949
+ const tempList = resolve63(ctx.workingDirectory, `concat-list-${Date.now()}.txt`);
61950
+ const listContent = args.inputs.map((i) => `file '${resolve63(ctx.workingDirectory, i)}'`).join("\n");
61656
61951
  await writeFile47(tempList, listContent, "utf-8");
61657
61952
  try {
61658
61953
  await execSafe(
@@ -61737,7 +62032,7 @@ A scene project is a directory that is **bilingual**: it works with both
61737
62032
  and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
61738
62033
  at render.
61739
62034
 
61740
- \`vibe scene build\` (v0.60+) is the supported one-shot driver from a
62035
+ \`vibe build\` (v0.60+) is the supported one-shot driver from a
61741
62036
  written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
61742
62037
  host agent itself authors the per-beat HTML \u2014 no internal LLM call.
61743
62038
 
@@ -61745,19 +62040,19 @@ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
61745
62040
 
61746
62041
  | Path | Command | When to use |
61747
62042
  |---|---|---|
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 |
62043
+ | **One-shot (default, v0.60+)** | \`vibe build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
62044
+ | **High-craft (manual)** | \`DESIGN.md\` + local composition rules in your agent | Maximum control: hand-author each scene |
61750
62045
  | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
61751
62046
 
61752
- Recommend \`vibe scene build\` whenever the user has a STORYBOARD with
62047
+ Recommend \`vibe build\` whenever the user has a STORYBOARD with
61753
62048
  narration / backdrop intent.
61754
62049
 
61755
62050
  ## High-craft path
61756
62051
 
61757
- 1. \`vibe scene init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
62052
+ 1. \`vibe init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
61758
62053
  \`DESIGN.md\` (palette, typography, motion, transitions) plus the
61759
62054
  \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
61760
- In Plan H this **also installs the Hyperframes skill** at the
62055
+ In Plan H this **also installs local composition rules** at the
61761
62056
  right place for your host (\`.claude/skills/hyperframes/\` for Claude
61762
62057
  Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
61763
62058
  \`SKILL.md\` for everyone else).
@@ -61768,7 +62063,7 @@ narration / backdrop intent.
61768
62063
  4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
61769
62064
  using the rules from steps 2 and 3. The skill enforces the visual
61770
62065
  identity contract \u2014 scenes that contradict DESIGN.md fail lint.
61771
- 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe scene render\`
62066
+ 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe render my-promo\`
61772
62067
  to MP4.
61773
62068
 
61774
62069
  ## Quick-draft path
@@ -61778,7 +62073,7 @@ vibe scene init my-promo -r 16:9 -d 30
61778
62073
  vibe scene add intro --style announcement \\
61779
62074
  --headline "Ship videos, not clicks"
61780
62075
  vibe scene lint
61781
- vibe scene render
62076
+ vibe render my-promo
61782
62077
  \`\`\`
61783
62078
 
61784
62079
  \`vibe scene init\` is **idempotent** \u2014 running it on an existing
@@ -61790,12 +62085,12 @@ Safe to invoke on user-provided projects.
61790
62085
  \`\`\`bash
61791
62086
  vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
61792
62087
  vibe scene styles [<name>] # list / show vendored visual identities
61793
- vibe scene install-skill [<dir>] [--host all] # retroactive Hyperframes-skill install
62088
+ vibe scene install-skill [<dir>] [--host all] # retroactive composition-rules install
61794
62089
  vibe scene add <name> --style <preset> [...]
61795
62090
  vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
61796
62091
  vibe scene lint [<root>] [--json] [--fix]
61797
62092
  vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
61798
- vibe scene build [<dir>] [--mode agent|batch|auto] # H3 dispatch
62093
+ vibe build [<dir>] [--mode agent|batch|auto] # H3 dispatch
61799
62094
  \`\`\`
61800
62095
 
61801
62096
  ## Style presets (for \`vibe scene add --style\`)
@@ -61813,18 +62108,18 @@ from the generated TTS audio.
61813
62108
  ## STORYBOARD-to-MP4 (one command, v0.60+)
61814
62109
 
61815
62110
  \`\`\`bash
61816
- vibe scene init my-promo --visual-style "Swiss Pulse" -d 12
62111
+ vibe init my-promo --visual-style "Swiss Pulse" -d 12
61817
62112
  # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
61818
- vibe scene build my-promo
62113
+ vibe build my-promo
61819
62114
  \`\`\`
61820
62115
 
61821
- \`vibe scene build\` reads the STORYBOARD frontmatter + per-beat cues,
62116
+ \`vibe build\` reads the STORYBOARD frontmatter + per-beat cues,
61822
62117
  dispatches TTS + image-gen per beat, then either:
61823
62118
 
61824
62119
  - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
61825
62120
  \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
61826
62121
  authors each \`compositions/scene-<id>.html\` itself, then re-invoking
61827
- \`vibe scene build\` proceeds to lint + render.
62122
+ \`vibe build\` proceeds to lint + render.
61828
62123
  - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
61829
62124
  Gemini) to compose the HTML, then renders.
61830
62125
 
@@ -61847,9 +62142,9 @@ and surface the error to the user.
61847
62142
  | Task | Tool |
61848
62143
  |------|------|
61849
62144
  | Generate narration + image, then author scene | \`vibe scene add\` |
61850
- | Generate a full scenes project from a STORYBOARD | \`vibe scene build\` |
62145
+ | Generate a full scenes project from a STORYBOARD | \`vibe build\` |
61851
62146
  | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
61852
- | Render the project | \`vibe scene render\` *or* \`npx hyperframes render\` (equivalent) |
62147
+ | Render the project | \`vibe render\` *or* \`vibe scene render\` for lower-level control |
61853
62148
  | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
61854
62149
 
61855
62150
  The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
@@ -61933,8 +62228,8 @@ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
61933
62228
 
61934
62229
  ## Authoring tips
61935
62230
 
61936
- 1. **Start from examples** \u2014 \`examples/demo-pipeline.yaml\` (FFmpeg-only,
61937
- no keys), \`examples/promo-video.yaml\` (AI providers).
62231
+ 1. **Start from a tiny YAML** \u2014 keep it in your project directory and run
62232
+ \`vibe run pipeline.yaml --dry-run\` before spending provider budget.
61938
62233
  2. **Dry-run first** \u2014 you see estimated cost and resolved variable
61939
62234
  graph before spending API credits.
61940
62235
  3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
@@ -61969,18 +62264,18 @@ overlay, etc.) \u2014 useful at the tail of a pipeline.
61969
62264
  title: "Scene authoring with vibe",
61970
62265
  summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
61971
62266
  steps: [
61972
- 'Run `vibe scene init <dir> --visual-style "<style name>"` to scaffold the project + install the Hyperframes skill (Plan H).',
62267
+ 'Run `vibe init <dir> --visual-style "<style name>"` to scaffold the project + install local composition rules.',
61973
62268
  "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
61974
62269
  "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."
62270
+ "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.",
62271
+ "Run `vibe scene lint --fix` to validate, then `vibe render <dir>` to produce the MP4."
61977
62272
  ],
61978
62273
  relatedCommands: [
61979
- "vibe scene init",
62274
+ "vibe init",
61980
62275
  "vibe scene styles",
61981
62276
  "vibe scene install-skill",
61982
62277
  "vibe scene compose-prompts",
61983
- "vibe scene build",
62278
+ "vibe build",
61984
62279
  "vibe scene lint",
61985
62280
  "vibe scene render",
61986
62281
  "vibe scene add"
@@ -62306,7 +62601,7 @@ init_source();
62306
62601
  // package.json
62307
62602
  var package_default = {
62308
62603
  name: "@vibeframe/cli",
62309
- version: "0.72.0",
62604
+ version: "0.74.0",
62310
62605
  description: "VibeFrame CLI - natural language video editing from the terminal",
62311
62606
  private: false,
62312
62607
  license: "MIT",
@@ -62578,6 +62873,19 @@ projectCommand.command("create").description("Create a new project").argument("<
62578
62873
  const data = JSON.stringify(project.toJSON(), null, 2);
62579
62874
  await writeFile(outputPath, data, "utf-8");
62580
62875
  spinner2.succeed(source_default.green(`Project created: ${outputPath}`));
62876
+ if (isJsonMode()) {
62877
+ outputSuccess({
62878
+ command: "project create",
62879
+ startedAt,
62880
+ data: {
62881
+ outputPath,
62882
+ name: projectName,
62883
+ aspectRatio: options.ratio,
62884
+ frameRate: parseInt(options.fps, 10)
62885
+ }
62886
+ });
62887
+ return;
62888
+ }
62581
62889
  console.log();
62582
62890
  console.log(source_default.dim(" Name:"), projectName);
62583
62891
  console.log(source_default.dim(" Aspect Ratio:"), options.ratio);
@@ -62589,6 +62897,7 @@ projectCommand.command("create").description("Create a new project").argument("<
62589
62897
  }
62590
62898
  });
62591
62899
  projectCommand.command("info").description("Show project information").argument("<file>", "Project file path").action(async (file) => {
62900
+ const startedAt = Date.now();
62592
62901
  const spinner2 = ora("Loading project...").start();
62593
62902
  try {
62594
62903
  const filePath = await resolveProjectPath(file);
@@ -62598,6 +62907,24 @@ projectCommand.command("info").description("Show project information").argument(
62598
62907
  spinner2.stop();
62599
62908
  const summary = project.getSummary();
62600
62909
  const meta = project.getMeta();
62910
+ if (isJsonMode()) {
62911
+ outputSuccess({
62912
+ command: "project info",
62913
+ startedAt,
62914
+ data: {
62915
+ name: summary.name,
62916
+ duration: summary.duration,
62917
+ aspectRatio: summary.aspectRatio,
62918
+ frameRate: summary.frameRate,
62919
+ trackCount: summary.trackCount,
62920
+ clipCount: summary.clipCount,
62921
+ sourceCount: summary.sourceCount,
62922
+ createdAt: meta.createdAt.toISOString(),
62923
+ updatedAt: meta.updatedAt.toISOString()
62924
+ }
62925
+ });
62926
+ return;
62927
+ }
62601
62928
  console.log();
62602
62929
  console.log(source_default.bold.cyan("Project Info"));
62603
62930
  console.log(source_default.dim("\u2500".repeat(40)));
@@ -62648,6 +62975,21 @@ projectCommand.command("set").description("Update project settings").argument("<
62648
62975
  if (options.fps) project.setFrameRate(parseInt(options.fps, 10));
62649
62976
  await writeFile(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62650
62977
  spinner2.succeed(source_default.green("Project updated"));
62978
+ if (isJsonMode()) {
62979
+ outputSuccess({
62980
+ command: "project set",
62981
+ startedAt,
62982
+ data: {
62983
+ file: filePath,
62984
+ updates: {
62985
+ name: options.name ?? null,
62986
+ ratio: options.ratio ?? null,
62987
+ fps: options.fps ? parseInt(options.fps, 10) : null
62988
+ }
62989
+ }
62990
+ });
62991
+ return;
62992
+ }
62651
62993
  } catch (error) {
62652
62994
  spinner2.fail("Failed to update project");
62653
62995
  const msg = error instanceof Error ? error.message : String(error);
@@ -62721,6 +63063,20 @@ timelineCommand.command("add-source").description("Add a media source to the pro
62721
63063
  });
62722
63064
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62723
63065
  spinner2.succeed(source_default.green(`Source added: ${source.id}`));
63066
+ if (isJsonMode()) {
63067
+ outputSuccess({
63068
+ command: "timeline add-source",
63069
+ startedAt,
63070
+ data: {
63071
+ id: source.id,
63072
+ name: mediaName,
63073
+ type: mediaType,
63074
+ path: absMediaPath,
63075
+ duration
63076
+ }
63077
+ });
63078
+ return;
63079
+ }
62724
63080
  console.log();
62725
63081
  console.log(source_default.dim(" Name:"), mediaName);
62726
63082
  console.log(source_default.dim(" Type:"), mediaType);
@@ -62788,6 +63144,21 @@ timelineCommand.command("add-clip").description("Add a clip to the timeline").ar
62788
63144
  });
62789
63145
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62790
63146
  spinner2.succeed(source_default.green(`Clip added: ${clip.id}`));
63147
+ if (isJsonMode()) {
63148
+ outputSuccess({
63149
+ command: "timeline add-clip",
63150
+ startedAt,
63151
+ data: {
63152
+ id: clip.id,
63153
+ sourceId,
63154
+ sourceName: source.name,
63155
+ trackId,
63156
+ startTime,
63157
+ duration
63158
+ }
63159
+ });
63160
+ return;
63161
+ }
62791
63162
  console.log();
62792
63163
  console.log(source_default.dim(" Source:"), source.name);
62793
63164
  console.log(source_default.dim(" Track:"), trackId);
@@ -62835,6 +63206,18 @@ timelineCommand.command("add-track").description("Add a new track").argument("<p
62835
63206
  });
62836
63207
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62837
63208
  spinner2.succeed(source_default.green(`Track added: ${track.id}`));
63209
+ if (isJsonMode()) {
63210
+ outputSuccess({
63211
+ command: "timeline add-track",
63212
+ startedAt,
63213
+ data: {
63214
+ id: track.id,
63215
+ name: track.name,
63216
+ type: track.type
63217
+ }
63218
+ });
63219
+ return;
63220
+ }
62838
63221
  console.log();
62839
63222
  console.log(source_default.dim(" Name:"), track.name);
62840
63223
  console.log(source_default.dim(" Type:"), track.type);
@@ -62891,6 +63274,21 @@ timelineCommand.command("add-effect").description("Add an effect to a clip").arg
62891
63274
  }
62892
63275
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62893
63276
  spinner2.succeed(source_default.green(`Effect added: ${effect.id}`));
63277
+ if (isJsonMode()) {
63278
+ outputSuccess({
63279
+ command: "timeline add-effect",
63280
+ startedAt,
63281
+ data: {
63282
+ id: effect.id,
63283
+ type: effectType,
63284
+ clipId,
63285
+ startTime,
63286
+ duration,
63287
+ params
63288
+ }
63289
+ });
63290
+ return;
63291
+ }
62894
63292
  console.log();
62895
63293
  console.log(source_default.dim(" Type:"), effectType);
62896
63294
  console.log(source_default.dim(" Start:"), startTime, "s");
@@ -62940,6 +63338,18 @@ timelineCommand.command("trim").description("Trim a clip").argument("<project>",
62940
63338
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
62941
63339
  const updatedClip = project.getClip(clipId);
62942
63340
  spinner2.succeed(source_default.green("Clip trimmed"));
63341
+ if (isJsonMode()) {
63342
+ outputSuccess({
63343
+ command: "timeline trim",
63344
+ startedAt,
63345
+ data: {
63346
+ id: updatedClip.id,
63347
+ startTime: updatedClip.startTime,
63348
+ duration: updatedClip.duration
63349
+ }
63350
+ });
63351
+ return;
63352
+ }
62943
63353
  console.log();
62944
63354
  console.log(source_default.dim(" Start:"), updatedClip.startTime, "s");
62945
63355
  console.log(source_default.dim(" Duration:"), updatedClip.duration, "s");
@@ -62950,17 +63360,61 @@ timelineCommand.command("trim").description("Trim a clip").argument("<project>",
62950
63360
  }
62951
63361
  });
62952
63362
  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) => {
63363
+ const startedAt = Date.now();
62953
63364
  try {
62954
63365
  const filePath = resolve15(process.cwd(), projectPath);
62955
63366
  const content = await readFile12(filePath, "utf-8");
62956
63367
  const data = JSON.parse(content);
62957
63368
  const project = Project.fromJSON(data);
62958
63369
  const showAll = !options.sources && !options.tracks && !options.clips;
63370
+ const sources = project.getSources();
63371
+ const tracks = project.getTracks();
63372
+ const clips = project.getClips();
63373
+ if (isJsonMode()) {
63374
+ const result = {};
63375
+ if (showAll || options.sources) {
63376
+ result.sources = sources.map((s) => ({
63377
+ id: s.id,
63378
+ name: s.name,
63379
+ type: s.type,
63380
+ duration: s.duration
63381
+ }));
63382
+ }
63383
+ if (showAll || options.tracks) {
63384
+ result.tracks = tracks.map((t) => ({
63385
+ id: t.id,
63386
+ name: t.name,
63387
+ type: t.type,
63388
+ isMuted: t.isMuted,
63389
+ isLocked: t.isLocked,
63390
+ isVisible: t.isVisible
63391
+ }));
63392
+ }
63393
+ if (showAll || options.clips) {
63394
+ result.clips = clips.map((c) => {
63395
+ const source = project.getSource(c.sourceId);
63396
+ return {
63397
+ id: c.id,
63398
+ sourceId: c.sourceId,
63399
+ sourceName: source?.name ?? null,
63400
+ trackId: c.trackId,
63401
+ startTime: c.startTime,
63402
+ duration: c.duration,
63403
+ effects: c.effects.map((e) => ({ id: e.id, type: e.type }))
63404
+ };
63405
+ });
63406
+ }
63407
+ outputSuccess({
63408
+ command: "timeline list",
63409
+ startedAt,
63410
+ data: result
63411
+ });
63412
+ return;
63413
+ }
62959
63414
  if (showAll || options.sources) {
62960
63415
  console.log();
62961
63416
  console.log(source_default.bold.cyan("Sources"));
62962
63417
  console.log(source_default.dim("\u2500".repeat(60)));
62963
- const sources = project.getSources();
62964
63418
  if (sources.length === 0) {
62965
63419
  console.log(source_default.dim(" (none)"));
62966
63420
  } else {
@@ -62974,7 +63428,6 @@ timelineCommand.command("list").description("List timeline contents").argument("
62974
63428
  console.log();
62975
63429
  console.log(source_default.bold.cyan("Tracks"));
62976
63430
  console.log(source_default.dim("\u2500".repeat(60)));
62977
- const tracks = project.getTracks();
62978
63431
  for (const track of tracks) {
62979
63432
  const status = [
62980
63433
  track.isMuted ? "muted" : null,
@@ -62989,7 +63442,6 @@ timelineCommand.command("list").description("List timeline contents").argument("
62989
63442
  console.log();
62990
63443
  console.log(source_default.bold.cyan("Clips"));
62991
63444
  console.log(source_default.dim("\u2500".repeat(60)));
62992
- const clips = project.getClips();
62993
63445
  if (clips.length === 0) {
62994
63446
  console.log(source_default.dim(" (none)"));
62995
63447
  } else {
@@ -63051,6 +63503,17 @@ timelineCommand.command("split").description("Split a clip at a specific time").
63051
63503
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63052
63504
  const [first, second] = result;
63053
63505
  spinner2.succeed(source_default.green("Clip split successfully"));
63506
+ if (isJsonMode()) {
63507
+ outputSuccess({
63508
+ command: "timeline split",
63509
+ startedAt,
63510
+ data: {
63511
+ first: { id: first.id, duration: first.duration },
63512
+ second: { id: second.id, duration: second.duration }
63513
+ }
63514
+ });
63515
+ return;
63516
+ }
63054
63517
  console.log();
63055
63518
  console.log(source_default.dim(" First clip:"), first.id, `(${first.duration.toFixed(2)}s)`);
63056
63519
  console.log(source_default.dim(" Second clip:"), second.id, `(${second.duration.toFixed(2)}s)`);
@@ -63097,6 +63560,19 @@ timelineCommand.command("duplicate").description("Duplicate a clip").argument("<
63097
63560
  }
63098
63561
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63099
63562
  spinner2.succeed(source_default.green(`Clip duplicated: ${duplicated.id}`));
63563
+ if (isJsonMode()) {
63564
+ outputSuccess({
63565
+ command: "timeline duplicate",
63566
+ startedAt,
63567
+ data: {
63568
+ id: duplicated.id,
63569
+ sourceClipId: clipId,
63570
+ startTime: duplicated.startTime,
63571
+ duration: duplicated.duration
63572
+ }
63573
+ });
63574
+ return;
63575
+ }
63100
63576
  console.log();
63101
63577
  console.log(source_default.dim(" Start:"), duplicated.startTime, "s");
63102
63578
  console.log(source_default.dim(" Duration:"), duplicated.duration, "s");
@@ -63141,6 +63617,17 @@ timelineCommand.command("delete").description("Delete a clip from the timeline")
63141
63617
  }
63142
63618
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63143
63619
  spinner2.succeed(source_default.green("Clip deleted"));
63620
+ if (isJsonMode()) {
63621
+ outputSuccess({
63622
+ command: "timeline delete",
63623
+ startedAt,
63624
+ data: {
63625
+ id: clipId,
63626
+ deleted: true
63627
+ }
63628
+ });
63629
+ return;
63630
+ }
63144
63631
  } catch (error) {
63145
63632
  spinner2.fail("Failed to delete clip");
63146
63633
  const msg = error instanceof Error ? error.message : String(error);
@@ -63188,6 +63675,18 @@ timelineCommand.command("move").description("Move a clip to a new position").arg
63188
63675
  await writeFile10(filePath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
63189
63676
  const updated = project.getClip(clipId);
63190
63677
  spinner2.succeed(source_default.green("Clip moved"));
63678
+ if (isJsonMode()) {
63679
+ outputSuccess({
63680
+ command: "timeline move",
63681
+ startedAt,
63682
+ data: {
63683
+ id: updated.id,
63684
+ trackId: updated.trackId,
63685
+ startTime: updated.startTime
63686
+ }
63687
+ });
63688
+ return;
63689
+ }
63191
63690
  console.log();
63192
63691
  console.log(source_default.dim(" Track:"), updated.trackId);
63193
63692
  console.log(source_default.dim(" Start:"), updated.startTime, "s");
@@ -63228,22 +63727,28 @@ init_sanitize();
63228
63727
  init_validate();
63229
63728
  import { readFile as readFile26, writeFile as writeFile31 } from "node:fs/promises";
63230
63729
  import { resolve as resolve36 } from "node:path";
63231
- var analyzeCommand = new Command("analyze").alias("az").description("Analyze media using AI (images, videos, YouTube URLs)").addHelpText(
63730
+ var analyzeCommand = new Command("inspect").alias("analyze").alias("az").description("Inspect media using AI (images, videos, YouTube URLs)").hook("preAction", () => {
63731
+ const invoked = process.argv[2];
63732
+ if (invoked === "analyze" || invoked === "az") {
63733
+ emitDeprecationWarning(invoked, "inspect", "v1.0");
63734
+ }
63735
+ }).addHelpText(
63232
63736
  "after",
63233
63737
  `
63234
63738
  Examples:
63235
- $ vibe analyze media image.png "Describe this image"
63236
- $ vibe analyze media video.mp4 "Summarize this video"
63237
- $ vibe analyze media "https://youtube.com/watch?v=..." "Key takeaways"
63238
- $ vibe analyze video video.mp4 "List all scene changes" --low-res
63239
- $ vibe analyze review video.mp4 --auto-apply -o fixed.mp4
63240
- $ vibe analyze suggest project.vibe.json "make it more dramatic"
63739
+ $ vibe inspect media image.png "Describe this image"
63740
+ $ vibe inspect media video.mp4 "Summarize this video"
63741
+ $ vibe inspect media "https://youtube.com/watch?v=..." "Key takeaways"
63742
+ $ vibe inspect video video.mp4 "List all scene changes" --low-res
63743
+ $ vibe inspect review video.mp4 --auto-apply -o fixed.mp4
63744
+ $ vibe inspect suggest project.vibe.json "make it more dramatic"
63241
63745
 
63242
63746
  API Keys:
63243
- GOOGLE_API_KEY Required for all analyze commands (Gemini)
63747
+ GOOGLE_API_KEY Required for all inspect commands (Gemini)
63244
63748
 
63749
+ Aliases: \`analyze\`, \`az\` (deprecated \u2014 removed in v1.0).
63245
63750
  Use '--fields response,model' to limit output size.
63246
- Run 'vibe schema analyze.<command>' for structured parameter info.
63751
+ Run 'vibe schema inspect.<command>' for structured parameter info.
63247
63752
  `
63248
63753
  );
63249
63754
  analyzeCommand.command("media").description("Analyze any media: images, videos, or YouTube URLs using Gemini").argument("<source>", "Image/video file path, image URL, or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Describe this image', 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second for video (default: 1)").option("--start <seconds>", "Start offset in seconds (video only)").option("--end <seconds>", "End offset in seconds (video only)").option("--low-res", "Use low resolution mode (fewer tokens)").option("-v, --verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").action(async (source, prompt3, options) => {
@@ -63438,18 +63943,19 @@ var audioCommand = new Command("audio").alias("au").description("Audio operation
63438
63943
  Examples:
63439
63944
  $ vibe audio transcribe interview.mp3 -o transcript.srt -f srt
63440
63945
  $ vibe audio transcribe video.mp4 -l ko # Specify language
63441
- $ vibe audio voices # List available voices
63946
+ $ vibe audio list-voices # List available voices
63442
63947
  $ vibe audio isolate song.mp3 -o vocals.mp3
63443
- $ vibe audio voice-clone sample.mp3 -n "my-voice"
63948
+ $ vibe audio clone-voice sample.mp3 -n "my-voice"
63444
63949
  $ vibe audio dub video.mp4 -l ko -o dubbed.mp4
63445
63950
  $ vibe audio duck music.mp3 --voice narration.mp3 -o ducked.mp3
63446
63951
 
63447
63952
  API Keys:
63448
63953
  OPENAI_API_KEY transcribe (Whisper)
63449
- ELEVENLABS_API_KEY voices, isolate, voice-clone
63954
+ ELEVENLABS_API_KEY list-voices, isolate, clone-voice
63450
63955
  OPENAI_API_KEY + ANTHROPIC_API_KEY + ELEVENLABS_API_KEY dub (full pipeline)
63451
63956
  No key needed duck (FFmpeg only)
63452
63957
 
63958
+ Aliases (deprecated, removed in v1.0): voices \u2192 list-voices, voice-clone \u2192 clone-voice
63453
63959
  Run 'vibe schema audio.<command>' for structured parameter info.
63454
63960
  `
63455
63961
  );
@@ -63513,7 +64019,9 @@ audioCommand.command("transcribe").description("Transcribe audio using Whisper")
63513
64019
  exitWithError(apiError(`Transcription failed: ${msg}`, true));
63514
64020
  }
63515
64021
  });
63516
- audioCommand.command("voices").description("List available ElevenLabs voices").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").action(async (options) => {
64022
+ audioCommand.command("list-voices").alias("voices").description("List available ElevenLabs voices").hook("preAction", () => {
64023
+ if (process.argv[3] === "voices") emitDeprecationWarning("audio voices", "audio list-voices", "v1.0");
64024
+ }).option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").action(async (options) => {
63517
64025
  const startedAt = Date.now();
63518
64026
  try {
63519
64027
  const apiKey = await requireApiKey("ELEVENLABS_API_KEY", "ElevenLabs", options.apiKey);
@@ -63524,7 +64032,7 @@ audioCommand.command("voices").description("List available ElevenLabs voices").o
63524
64032
  spinner2.succeed(source_default.green(`Found ${voices.length} voices`));
63525
64033
  if (isJsonMode()) {
63526
64034
  outputSuccess({
63527
- command: "audio voices",
64035
+ command: "audio list-voices",
63528
64036
  startedAt,
63529
64037
  data: {
63530
64038
  voices: voices.map((v) => ({ name: v.name, voiceId: v.voice_id, category: v.category, labels: v.labels }))
@@ -63591,12 +64099,14 @@ audioCommand.command("isolate").description("Isolate vocals from audio using Ele
63591
64099
  exitWithError(apiError(`Audio isolation failed: ${msg}`, true));
63592
64100
  }
63593
64101
  });
63594
- audioCommand.command("voice-clone").description("Clone a voice from audio samples using ElevenLabs").argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-n, --name <name>", "Voice name (required)").option("-d, --description <desc>", "Voice description").option("--labels <json>", `Labels as JSON (e.g., '{"accent": "american"}')`).option("--remove-noise", "Remove background noise from samples").option("--list", "List all available voices").option("--dry-run", "Preview parameters without executing").action(async (samples, options) => {
64102
+ audioCommand.command("clone-voice").alias("voice-clone").description("Clone a voice from audio samples using ElevenLabs").hook("preAction", () => {
64103
+ if (process.argv[3] === "voice-clone") emitDeprecationWarning("audio voice-clone", "audio clone-voice", "v1.0");
64104
+ }).argument("[samples...]", "Audio sample files (1-25 files)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-n, --name <name>", "Voice name (required)").option("-d, --description <desc>", "Voice description").option("--labels <json>", `Labels as JSON (e.g., '{"accent": "american"}')`).option("--remove-noise", "Remove background noise from samples").option("--list", "List all available voices").option("--dry-run", "Preview parameters without executing").action(async (samples, options) => {
63595
64105
  const startedAt = Date.now();
63596
64106
  try {
63597
64107
  if (options.dryRun) {
63598
64108
  outputSuccess({
63599
- command: "audio voice-clone",
64109
+ command: "audio clone-voice",
63600
64110
  startedAt,
63601
64111
  dryRun: true,
63602
64112
  data: { params: { samples: samples?.length, name: options.name } }
@@ -63657,7 +64167,7 @@ audioCommand.command("voice-clone").description("Clone a voice from audio sample
63657
64167
  spinner2.succeed(source_default.green("Voice cloned successfully"));
63658
64168
  if (isJsonMode()) {
63659
64169
  outputSuccess({
63660
- command: "audio voice-clone",
64170
+ command: "audio clone-voice",
63661
64171
  startedAt,
63662
64172
  data: { name: options.name, voiceId: result.voiceId }
63663
64173
  });
@@ -64123,26 +64633,31 @@ function registerScriptPipelineCommands(aiCommand) {
64123
64633
  init_ai_highlights();
64124
64634
  init_ai_animated_caption();
64125
64635
  init_output();
64126
- 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`."
64128
- ).addHelpText(
64636
+ var pipelineCommand = new Command("remix").alias("pipeline").alias("pipe").description(
64637
+ "AI media transformations on existing video / audio (highlights, auto-shorts, animated captions). For BUILD-from-text flows see `vibe build`."
64638
+ ).hook("preAction", () => {
64639
+ const invoked = process.argv[2];
64640
+ if (invoked === "pipeline" || invoked === "pipe") {
64641
+ emitDeprecationWarning(invoked, "remix", "v1.0");
64642
+ }
64643
+ }).addHelpText(
64129
64644
  "after",
64130
64645
  `
64131
64646
  Two flows \u2014 pick by intent:
64132
64647
  BUILD \u2014 text \u2192 MP4 (intent \u2192 AI generation \u2192 new video)
64133
- Use \`vibe scene build\` with a STORYBOARD.md + DESIGN.md.
64648
+ Use \`vibe build\` with a STORYBOARD.md + DESIGN.md.
64134
64649
  Idempotent, agent-editable, skills-driven (v0.60+).
64135
- PROCESS \u2014 existing video/audio \u2192 transformed media
64136
- Use \`vibe pipeline\` (this group) or \`vibe edit\` / \`vibe audio\`.
64650
+ REMIX \u2014 existing video/audio \u2192 transformed media
64651
+ Use \`vibe remix\` (this group) or \`vibe edit\` / \`vibe audio\`.
64137
64652
  One-shot, batch-oriented, no storyboard required.
64138
64653
 
64139
- Examples (PROCESS):
64140
- $ vibe pipeline highlights long-video.mp4 -o highlights.json -d 60
64141
- $ vibe pipeline auto-shorts long-video.mp4 -o shorts/ -n 3 --add-captions
64142
- $ vibe pipeline animated-caption video.mp4 -o captioned.mp4 -s highlight
64143
- $ vibe pipeline animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64654
+ Examples (REMIX):
64655
+ $ vibe remix highlights long-video.mp4 -o highlights.json -d 60
64656
+ $ vibe remix auto-shorts long-video.mp4 -o shorts/ -n 3 --add-captions
64657
+ $ vibe remix animated-caption video.mp4 -o captioned.mp4 -s highlight
64658
+ $ vibe remix animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64144
64659
 
64145
- Required API Keys:
64660
+ Provider API Keys:
64146
64661
  highlights: GOOGLE_API_KEY (Gemini analysis)
64147
64662
  auto-shorts: GOOGLE_API_KEY + OPENAI_API_KEY (optional captions)
64148
64663
  animated-caption: OPENAI_API_KEY (Whisper transcription)
@@ -64153,12 +64668,9 @@ Cost tiers:
64153
64668
  animated-caption: $ Low (~$0.01)
64154
64669
  regenerate-scene: $$$ High (per-scene re-run; depends on provider)
64155
64670
 
64156
- Note: \`pipeline script-to-video\` was removed \u2014 use \`vibe scene build\` for
64157
- text \u2192 MP4. \`pipeline regenerate-scene\` still operates on the
64158
- storyboard.{yaml,json} layout produced by older runs.
64159
-
64671
+ Aliases: \`pipeline\`, \`pipe\` (deprecated \u2014 removed in v1.0).
64160
64672
  Use '--dry-run' to preview parameters before execution.
64161
- Run 'vibe schema pipeline.<command>' for structured parameter info.
64673
+ Run 'vibe schema remix.<command>' for structured parameter info.
64162
64674
  `
64163
64675
  );
64164
64676
  registerScriptPipelineCommands(pipelineCommand);
@@ -64168,9 +64680,9 @@ pipelineCommand.command("animated-caption").description("Add animated captions w
64168
64680
  "after",
64169
64681
  `
64170
64682
  Examples:
64171
- $ vibe pipeline animated-caption video.mp4 -o captioned.mp4
64172
- $ vibe pipeline animated-caption video.mp4 -o out.mp4 -s bounce
64173
- $ vibe pipeline animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64683
+ $ vibe remix animated-caption video.mp4 -o captioned.mp4
64684
+ $ vibe remix animated-caption video.mp4 -o out.mp4 -s bounce
64685
+ $ vibe remix animated-caption video.mp4 -o out.mp4 -s karaoke-sweep --fast
64174
64686
 
64175
64687
  Styles:
64176
64688
  highlight (default) TikTok-style background highlight on active word (Remotion)
@@ -64343,18 +64855,36 @@ function listCommands(program3) {
64343
64855
  console.log(JSON.stringify(commands, null, 2));
64344
64856
  }
64345
64857
  function extractEnumFromDescription(description) {
64346
- const providerMatch = description.match(
64347
- /(?:Provider|Providers?):\s*([a-z0-9,\s-]+?)(?:\s*\(|$)/i
64858
+ const cleaned = description.replace(/\s*\([^()]*\)/g, "");
64859
+ const finalize = (raw) => {
64860
+ const values = raw.map((v) => v.trim().replace(/^or\s+/i, "")).filter(Boolean);
64861
+ if (values.some((v) => /\s/.test(v))) return void 0;
64862
+ if (values.length >= 2 && values.length <= 12) return values;
64863
+ return void 0;
64864
+ };
64865
+ const providerMatch = cleaned.match(
64866
+ /(?:Provider|Providers?):\s*([a-z0-9,\s-]+?)$/i
64348
64867
  );
64349
64868
  if (providerMatch) {
64350
- return providerMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
64869
+ const values = finalize(providerMatch[1].split(","));
64870
+ if (values) return values;
64351
64871
  }
64352
- const styleMatch = description.match(
64353
- /^[A-Z][a-z]+:\s*([a-z0-9,\s-]+?)(?:\s*\(|$)/
64872
+ const labeledMatch = cleaned.match(
64873
+ /^([A-Z][a-z]+(?:\s+[a-z]+){0,2}):\s*([a-z0-9:,\s/-]+?)$/
64354
64874
  );
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;
64875
+ if (labeledMatch) {
64876
+ return finalize(labeledMatch[2].split(","));
64877
+ }
64878
+ const parenMatch = description.match(/\(([^()]+)\)\s*$/);
64879
+ if (parenMatch) {
64880
+ const inner = parenMatch[1].trim();
64881
+ if (/^default:|^e\.g\.|^or\s|via\s|run\s/i.test(inner)) return void 0;
64882
+ const rawValues = inner.split(",").map((s) => s.trim()).filter(Boolean);
64883
+ const values = rawValues.map((v) => v.replace(/^or\s+/i, "").trim());
64884
+ const looksLikeEnum = values.every(
64885
+ (v) => /^[a-z0-9][a-z0-9:/_-]*$/i.test(v) && v.length <= 24
64886
+ );
64887
+ if (looksLikeEnum && values.length >= 2 && values.length <= 12) return values;
64358
64888
  }
64359
64889
  return void 0;
64360
64890
  }
@@ -68849,7 +69379,7 @@ init_detect();
68849
69379
  // src/commands/setup.ts
68850
69380
  init_esm();
68851
69381
  init_source();
68852
- var import_dotenv2 = __toESM(require_main(), 1);
69382
+ var import_dotenv3 = __toESM(require_main(), 1);
68853
69383
  init_config();
68854
69384
  init_output();
68855
69385
  init_tty();
@@ -68898,7 +69428,7 @@ var AI_FEATURES = [
68898
69428
  {
68899
69429
  label: "Videos",
68900
69430
  desc: "generate + extend",
68901
- defaultProvider: "fal.ai Seedance 2.0 (Artificial Analysis #2 t2v + i2v, since v0.57)",
69431
+ defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
68902
69432
  alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
68903
69433
  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
69434
  tryCommand: 'vibe generate video "ocean waves" -o waves.mp4'
@@ -68973,7 +69503,7 @@ async function runSetupWizard(fullSetup = false) {
68973
69503
  console.log();
68974
69504
  await collectKeys(config2, pipelineKeys);
68975
69505
  await saveConfig(config2);
68976
- showComplete(config2, "vibe scene build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
69506
+ showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
68977
69507
  return;
68978
69508
  }
68979
69509
  console.log(source_default.dim("Step 2 of 2"));
@@ -69138,7 +69668,7 @@ function showComplete(config2, defaultTryCommand, features = []) {
69138
69668
  console.log(source_default.bold(" Next steps:"));
69139
69669
  console.log(source_default.dim(" cd <project>; vibe init Scaffold AGENTS.md / CLAUDE.md / .env.example (project scope)"));
69140
69670
  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"));
69671
+ console.log(source_default.dim(" vibe schema --list Discover all 81 commands"));
69142
69672
  console.log(source_default.dim(" vibe setup Re-run user-scope setup anytime"));
69143
69673
  const hosts = detectedAgentHosts();
69144
69674
  const primary = hosts[0];
@@ -69151,14 +69681,14 @@ function showComplete(config2, defaultTryCommand, features = []) {
69151
69681
  );
69152
69682
  console.log(
69153
69683
  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.`
69684
+ ` 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
69685
  )
69156
69686
  );
69157
69687
  } else {
69158
69688
  console.log();
69159
69689
  console.log(
69160
69690
  source_default.dim(
69161
- ` No agent host detected \u2014 \`vibe scene build\` will use the internal LLM composer (${source_default.bold("--mode batch")}).`
69691
+ ` No agent host detected \u2014 \`vibe build\` will use the internal LLM composer (${source_default.bold("--mode batch")}).`
69162
69692
  )
69163
69693
  );
69164
69694
  }
@@ -69207,7 +69737,7 @@ async function showConfig() {
69207
69737
  if (hasCwdEnv) {
69208
69738
  try {
69209
69739
  const envContent = await readFile33(cwdEnvPath, "utf-8");
69210
- dotenvKeys = (0, import_dotenv2.parse)(Buffer.from(envContent));
69740
+ dotenvKeys = (0, import_dotenv3.parse)(Buffer.from(envContent));
69211
69741
  } catch {
69212
69742
  }
69213
69743
  }
@@ -69269,6 +69799,9 @@ async function showConfig() {
69269
69799
  init_esm();
69270
69800
  init_source();
69271
69801
  init_agent_host_detect();
69802
+ init_scene_project();
69803
+ init_visual_styles();
69804
+ init_install_skill();
69272
69805
  import { existsSync as existsSync34 } from "node:fs";
69273
69806
  import { mkdir as mkdir20, readFile as readFile34, writeFile as writeFile39 } from "node:fs/promises";
69274
69807
  import { basename as basename19, dirname as dirname18, resolve as resolve48 } from "node:path";
@@ -69322,18 +69855,18 @@ VibeFrame splits cleanly into two flows. Routing the user's request
69322
69855
  correctly is the most important judgement call you'll make.
69323
69856
 
69324
69857
  **BUILD \u2014 create new video from text intent.**
69325
- Use \`vibe scene build\` with a STORYBOARD.md + DESIGN.md. The
69858
+ Use \`vibe build\` with a STORYBOARD.md + DESIGN.md. The
69326
69859
  skills-driven pipeline (v0.60+) dispatches narration TTS + backdrop
69327
- image-gen per beat, composes scene HTML via the Hyperframes skill
69860
+ image-gen per beat, composes scene HTML via the bundled composition rules
69328
69861
  bundle, then renders to MP4. Idempotent re-runs reuse cached assets.
69329
69862
 
69330
- **PROCESS \u2014 transform existing video / audio.**
69331
- Use \`vibe pipeline\`, \`vibe edit\`, or \`vibe audio\`. One-shot,
69863
+ **REMIX \u2014 transform existing video / audio.**
69864
+ Use \`vibe remix\`, \`vibe edit\`, or \`vibe audio\`. One-shot,
69332
69865
  batch-oriented operations on a file the user already has on disk.
69333
69866
 
69334
69867
  Decision rule: if the user is starting from intent (a script, a brand,
69335
69868
  an idea), it's BUILD. If the user is starting from a media file, it's
69336
- PROCESS.
69869
+ REMIX.
69337
69870
 
69338
69871
  ## Common workflows
69339
69872
 
@@ -69341,25 +69874,25 @@ PROCESS.
69341
69874
 
69342
69875
  | Task | Command |
69343
69876
  |---|---|
69344
- | One-shot storyboard \u2192 MP4 | \`vibe scene build [project-dir]\` |
69345
- | Render an existing scene project | \`vibe scene render -o out.mp4\` |
69877
+ | One-shot storyboard \u2192 video | \`vibe build [project-dir]\` |
69878
+ | Render an existing scene project | \`vibe render [project-dir] -o out.mp4\` |
69346
69879
  | Lint scene HTML | \`vibe scene lint --json\` |
69347
69880
  | Generate a single image | \`vibe generate image "prompt" -o img.png --quality hd\` |
69348
69881
  | Generate a single video | \`vibe generate video "prompt" -i image.png -o clip.mp4\` |
69349
69882
  | Generate narration | \`vibe generate speech "text" -o voice.mp3\` |
69350
69883
 
69351
- ### PROCESS (transform existing media)
69884
+ ### REMIX (transform existing media)
69352
69885
 
69353
69886
  | Task | Command |
69354
69887
  |---|---|
69355
- | Extract highlights from a long video | \`vibe pipeline highlights file.mp4 -d 60\` |
69356
- | Long video \u2192 vertical shorts | \`vibe pipeline auto-shorts file.mp4 -n 3 --add-captions\` |
69357
- | Add animated word-by-word captions | \`vibe pipeline animated-caption file.mp4 -s karaoke-sweep\` |
69888
+ | Extract highlights from a long video | \`vibe remix highlights file.mp4 -d 60\` |
69889
+ | Long video \u2192 vertical shorts | \`vibe remix auto-shorts file.mp4 -n 3 --add-captions\` |
69890
+ | Add animated word-by-word captions | \`vibe remix animated-caption file.mp4 -s karaoke-sweep\` |
69358
69891
  | Remove silence | \`vibe edit silence-cut in.mp4 -o out.mp4\` |
69359
69892
  | Add static captions | \`vibe edit caption in.mp4 -o out.mp4\` |
69360
69893
  | Translate audio (transcribe \u2192 TTS) | \`vibe audio dub file.mp4 -t ko\` |
69361
69894
  | Transcribe (Whisper, word-level) | \`vibe audio transcribe file.mp4 --granularity word\` |
69362
- | Analyze a video | \`vibe analyze video file.mp4 "summarise"\` |
69895
+ | Inspect a video | \`vibe inspect video file.mp4 "summarise"\` |
69363
69896
 
69364
69897
  ### Compose pipelines (Video as Code)
69365
69898
 
@@ -69373,9 +69906,9 @@ PROCESS.
69373
69906
  | Tier | Examples | Approx. per call |
69374
69907
  |---|---|---|
69375
69908
  | **Free** | \`detect *\`, \`edit silence-cut/fade/noise-reduce\`, \`schema\`, \`project\`, \`timeline\` | $0 |
69376
- | **Low** | \`analyze *\`, \`audio transcribe\`, \`generate image\` | $0.01\u2013$0.10 |
69909
+ | **Low** | \`inspect *\`, \`audio transcribe\`, \`generate image\` | $0.01\u2013$0.10 |
69377
69910
  | **High** | \`generate video\`, \`edit image\` | $1\u2013$5 |
69378
- | **Very High** | \`pipeline *\` (highlights, auto-shorts, regenerate-scene), \`scene build\` | $5\u2013$50+ |
69911
+ | **Very High** | \`remix *\` (highlights, auto-shorts, regenerate-scene), \`build\` | $5\u2013$50+ |
69379
69912
 
69380
69913
  ## Agent invariants
69381
69914
 
@@ -69387,7 +69920,7 @@ When you run vibe commands programmatically:
69387
69920
  3. **Use \`vibe schema <command>\`** to discover parameters; do not guess flags
69388
69921
  4. **Pass complex options via \`--stdin\`**:
69389
69922
  \`\`\`bash
69390
- echo '{"video":"long.mp4","count":3,"aspect":"9:16"}' | vibe pipeline auto-shorts --stdin --json
69923
+ echo '{"video":"long.mp4","count":3,"aspect":"9:16"}' | vibe remix auto-shorts --stdin --json
69391
69924
  \`\`\`
69392
69925
 
69393
69926
  ## Provider keys
@@ -69396,9 +69929,9 @@ API keys live in \`.env\` (gitignored). Copy \`.env.example\` to start. Run
69396
69929
  \`vibe doctor\` to see which keys are currently detected and which providers
69397
69930
  they unlock.
69398
69931
 
69399
- ### Hyperframes skill (scene HTML rules)
69932
+ ### Composition rules (scene HTML)
69400
69933
 
69401
- \`vibe scene init\` installs the Hyperframes skill into your project. The
69934
+ \`vibe init\` installs local composition rules into your project. The
69402
69935
  universal copy lives at \`SKILL.md\` (with \`references/*.md\`), and
69403
69936
  host-specific copies are placed where each agent expects them
69404
69937
  (\`.claude/skills/hyperframes/\` for Claude Code, \`.cursor/rules/hyperframes.mdc\`
@@ -69413,7 +69946,7 @@ To retro-install on a project scaffolded before this command existed, run
69413
69946
 
69414
69947
  ### Scene composer (batch / non-agent fallback)
69415
69948
 
69416
- When you don't want to author HTML yourself, \`vibe scene build\` runs an
69949
+ When you don't want to author HTML yourself, \`vibe build --mode batch\` runs an
69417
69950
  LLM internally with the same skill bundle. It auto-picks a provider based
69418
69951
  on available keys (\`claude > gemini > openai\`) \u2014 pass \`--composer <name>\`
69419
69952
  to force one:
@@ -69453,7 +69986,7 @@ pack is available as slash commands (consolidated to 2 in v0.62 \u2014 the
69453
69986
  overview content moved into AGENTS.md above):
69454
69987
 
69455
69988
  - \`/vibe-pipeline\` \u2014 YAML pipeline authoring helper (Video as Code)
69456
- - \`/vibe-scene\` \u2014 per-scene HTML authoring + \`vibe scene build\`
69989
+ - \`/vibe-scene\` \u2014 per-scene HTML authoring + \`vibe build\`
69457
69990
 
69458
69991
  To install / update them later:
69459
69992
 
@@ -69518,7 +70051,7 @@ renders/
69518
70051
  `;
69519
70052
  function renderProjectYaml(opts) {
69520
70053
  return `# VibeFrame project config. Used by \`vibe scene render\` to name
69521
- # outputs and by \`vibe scene build\` for default providers / budget.
70054
+ # outputs and by \`vibe build\` for default providers / budget.
69522
70055
  name: ${opts.name}
69523
70056
  aspect: "16:9"
69524
70057
  defaults:
@@ -69539,8 +70072,16 @@ defaults:
69539
70072
  // src/commands/init.ts
69540
70073
  init_output();
69541
70074
  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) => {
70075
+ 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
70076
  const startedAt = Date.now();
70077
+ const initType = String(options.type ?? "scene");
70078
+ if (initType !== "scene" && initType !== "agent") {
70079
+ exitWithError(usageError(`Invalid --type: ${initType}`, "Must be one of: scene, agent"));
70080
+ }
70081
+ if (initType === "scene") {
70082
+ await runSceneInit(projectDirArg, options, startedAt);
70083
+ return;
70084
+ }
69544
70085
  const agent = options.agent;
69545
70086
  if (!VALID_AGENTS.includes(agent)) {
69546
70087
  exitWithError(usageError(`Invalid --agent: ${agent}`, `Must be one of: ${VALID_AGENTS.join(", ")}`));
@@ -69642,6 +70183,150 @@ var initCommand = new Command("init").description("Scaffold project-scope agent
69642
70183
  console.log(source_default.dim(" vibe scene init my-promo Scaffold a starter scene project"));
69643
70184
  console.log();
69644
70185
  });
70186
+ var VALID_SCENE_ASPECTS = ["16:9", "9:16", "1:1", "4:5"];
70187
+ async function runSceneInit(projectDirArg, options, startedAt) {
70188
+ const profile = String(options.profile ?? "agent");
70189
+ if (!isSceneScaffoldProfile(profile)) {
70190
+ exitWithError(usageError(`Invalid --profile: ${profile}`, "Must be one of: minimal, agent, full"));
70191
+ }
70192
+ const aspect = String(options.ratio ?? "16:9");
70193
+ if (!VALID_SCENE_ASPECTS.includes(aspect)) {
70194
+ exitWithError(usageError(`Invalid --ratio: ${aspect}`, `Must be one of: ${VALID_SCENE_ASPECTS.join(", ")}`));
70195
+ }
70196
+ const duration = Number.parseFloat(String(options.duration ?? "10"));
70197
+ if (!Number.isFinite(duration) || duration <= 0 || duration > 3600) {
70198
+ exitWithError(usageError(`Invalid --duration: ${String(options.duration)}`, "Duration must be a positive number of seconds (\u22643600)"));
70199
+ }
70200
+ const visualStyle = options.visualStyle ? getVisualStyle(String(options.visualStyle)) : void 0;
70201
+ if (options.visualStyle && !visualStyle) {
70202
+ exitWithError(usageError(`Unknown visual style: ${String(options.visualStyle)}`, `Valid: ${visualStyleNames()}. Browse with \`vibe scene styles\`.`));
70203
+ }
70204
+ const projectDir = resolve48(projectDirArg);
70205
+ const projectName = basename19(projectDir);
70206
+ const groups = describeSceneScaffold({ dir: projectDir, profile });
70207
+ if (options.dryRun) {
70208
+ if (!isJsonMode()) {
70209
+ printSceneInitPlan({
70210
+ projectDir,
70211
+ profile,
70212
+ aspect,
70213
+ duration,
70214
+ visualStyleName: visualStyle?.name ?? null,
70215
+ groups,
70216
+ dryRun: true
70217
+ });
70218
+ return;
70219
+ }
70220
+ outputSuccess({
70221
+ command: "init",
70222
+ startedAt,
70223
+ dryRun: true,
70224
+ data: {
70225
+ type: "scene",
70226
+ projectDir,
70227
+ name: projectName,
70228
+ profile,
70229
+ aspect,
70230
+ duration,
70231
+ visualStyle: visualStyle?.name ?? null,
70232
+ groups
70233
+ }
70234
+ });
70235
+ return;
70236
+ }
70237
+ const result = await scaffoldSceneProject({
70238
+ dir: projectDir,
70239
+ name: projectName,
70240
+ aspect,
70241
+ duration,
70242
+ visualStyle,
70243
+ profile
70244
+ });
70245
+ const detectedIds = detectedAgentHosts().map((h) => h.id);
70246
+ const skillResult = profile === "agent" || profile === "full" ? await installHyperframesSkill({
70247
+ projectDir,
70248
+ hosts: deriveInstallHosts(detectedIds)
70249
+ }) : { success: true, files: [], bundleVersion: "not-installed" };
70250
+ if (isJsonMode()) {
70251
+ outputSuccess({
70252
+ command: "init",
70253
+ startedAt,
70254
+ data: {
70255
+ type: "scene",
70256
+ projectDir,
70257
+ name: projectName,
70258
+ profile,
70259
+ aspect,
70260
+ duration,
70261
+ visualStyle: visualStyle?.name ?? null,
70262
+ created: result.created,
70263
+ merged: result.merged,
70264
+ skipped: result.skipped,
70265
+ groups: result.groups,
70266
+ skillFiles: skillResult.files,
70267
+ skillBundleVersion: skillResult.bundleVersion
70268
+ }
70269
+ });
70270
+ return;
70271
+ }
70272
+ console.log();
70273
+ console.log(source_default.bold.magenta("VibeFrame Init") + source_default.dim(" \u2014 video project ready"));
70274
+ console.log(source_default.dim("\u2500".repeat(60)));
70275
+ console.log(`${source_default.dim("Project")} ${projectDir}`);
70276
+ console.log(`${source_default.dim("Profile")} ${profile} ${source_default.dim(sceneProfileSummary(profile))}`);
70277
+ console.log();
70278
+ console.log(source_default.bold("Edit first"));
70279
+ console.log(` ${source_default.cyan("STORYBOARD.md")} ${source_default.dim("beats: narration, backdrop, minimum duration")}`);
70280
+ console.log(` ${source_default.cyan("DESIGN.md")} ${source_default.dim("palette, typography, motion rules")}`);
70281
+ console.log();
70282
+ console.log(source_default.bold("Then run"));
70283
+ console.log(` ${source_default.cyan("vibe build")} ${source_default.dim("build storyboard assets/compositions")}`);
70284
+ console.log(` ${source_default.cyan("vibe render")} ${source_default.dim("render final MP4")}`);
70285
+ console.log();
70286
+ console.log(source_default.bold("Files"));
70287
+ for (const p of result.created) console.log(source_default.green(` + ${p.replace(projectDir + "/", "")}`));
70288
+ for (const p of result.merged) console.log(source_default.yellow(` ~ ${p.replace(projectDir + "/", "")} (merged)`));
70289
+ for (const p of result.skipped) console.log(source_default.dim(` \xB7 ${p.replace(projectDir + "/", "")} (kept existing)`));
70290
+ for (const f of skillResult.files.filter((f2) => f2.status === "wrote")) {
70291
+ console.log(source_default.green(` + ${f.path.replace(projectDir + "/", "")}`));
70292
+ }
70293
+ console.log();
70294
+ console.log(source_default.dim(`Tip: cd ${projectDirArg} before running the next commands.`));
70295
+ console.log();
70296
+ }
70297
+ function sceneProfileSummary(profile) {
70298
+ if (profile === "minimal") return "(storyboard/design only)";
70299
+ if (profile === "agent") return "(recommended: + local agent authoring rules)";
70300
+ return "(+ render scaffold upfront)";
70301
+ }
70302
+ function printSceneInitPlan(opts) {
70303
+ console.log();
70304
+ console.log(source_default.bold.magenta("VibeFrame Init") + source_default.dim(opts.dryRun ? " \u2014 dry run" : ""));
70305
+ console.log(source_default.dim("\u2500".repeat(60)));
70306
+ console.log(`${source_default.dim("Project")} ${opts.projectDir}`);
70307
+ console.log(`${source_default.dim("Profile")} ${opts.profile} ${source_default.dim(sceneProfileSummary(opts.profile))}`);
70308
+ console.log(`${source_default.dim("Canvas")} ${opts.aspect} \xB7 ${opts.duration}s default`);
70309
+ if (opts.visualStyleName) console.log(`${source_default.dim("Style")} ${opts.visualStyleName}`);
70310
+ console.log();
70311
+ console.log(source_default.bold("Edit first"));
70312
+ console.log(` ${source_default.cyan("STORYBOARD.md")} ${source_default.dim("beats: narration, backdrop, minimum duration")}`);
70313
+ console.log(` ${source_default.cyan("DESIGN.md")} ${source_default.dim("palette, typography, motion rules")}`);
70314
+ console.log();
70315
+ console.log(source_default.bold("Project contents"));
70316
+ printGroup("authoring", opts.groups.authoring, opts.projectDir);
70317
+ printGroup("agent", opts.groups.agent, opts.projectDir);
70318
+ printGroup("render", opts.groups.render, opts.projectDir);
70319
+ console.log();
70320
+ console.log(source_default.dim("Dry run \u2014 no files were written. Re-run without --dry-run to create the project."));
70321
+ console.log();
70322
+ }
70323
+ function printGroup(label, files, projectDir) {
70324
+ if (files.length === 0) return;
70325
+ console.log(` ${source_default.bold(label)}`);
70326
+ for (const file of files) {
70327
+ console.log(source_default.dim(` ${file.replace(projectDir + "/", "")}`));
70328
+ }
70329
+ }
69645
70330
  function resolveTargets(agent) {
69646
70331
  if (agent === "all") {
69647
70332
  return ["claude-code", "codex", "cursor", "aider", "gemini-cli", "opencode"];
@@ -69696,6 +70381,306 @@ function formatStatusIcon(status) {
69696
70381
  }
69697
70382
  }
69698
70383
 
70384
+ // src/commands/build.ts
70385
+ init_esm();
70386
+ init_source();
70387
+ init_ora();
70388
+ init_scene_build();
70389
+ init_output();
70390
+ import { resolve as resolve49 } from "node:path";
70391
+ var VALID_MODES = ["agent", "batch", "auto"];
70392
+ 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", `
70393
+ Examples:
70394
+ $ vibe init my-video --profile agent
70395
+ $ vibe build my-video --mode agent --tts kokoro
70396
+ $ vibe build my-video --skip-render
70397
+
70398
+ Advanced equivalent: \`vibe scene build\`.`).action(async (projectDirArg, options) => {
70399
+ const startedAt = Date.now();
70400
+ const projectDir = resolve49(projectDirArg);
70401
+ const mode = String(options.mode ?? "auto");
70402
+ if (!VALID_MODES.includes(mode)) {
70403
+ exitWithError(usageError(`Invalid --mode: ${mode}`, `Must be one of: ${VALID_MODES.join(", ")}`));
70404
+ }
70405
+ const params = {
70406
+ projectDir,
70407
+ mode,
70408
+ effort: options.effort,
70409
+ composer: options.composer,
70410
+ skipNarration: options.skipNarration ?? false,
70411
+ skipBackdrop: options.skipBackdrop ?? false,
70412
+ skipRender: options.skipRender ?? false,
70413
+ ttsProvider: options.tts,
70414
+ voice: options.voice,
70415
+ imageProvider: options.imageProvider,
70416
+ imageQuality: options.quality,
70417
+ imageSize: options.imageSize,
70418
+ force: options.force ?? false
70419
+ };
70420
+ if (options.dryRun) {
70421
+ if (!isJsonMode() && !isQuietMode()) {
70422
+ printBuildDryRun(projectDirArg, params);
70423
+ return;
70424
+ }
70425
+ outputSuccess({
70426
+ command: "build",
70427
+ startedAt,
70428
+ dryRun: true,
70429
+ data: { params }
70430
+ });
70431
+ return;
70432
+ }
70433
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora("Building video project...").start();
70434
+ const result = await executeSceneBuild({
70435
+ projectDir,
70436
+ mode,
70437
+ effort: options.effort,
70438
+ composer: options.composer,
70439
+ skipNarration: options.skipNarration,
70440
+ skipBackdrop: options.skipBackdrop,
70441
+ skipRender: options.skipRender,
70442
+ ttsProvider: options.tts,
70443
+ voice: options.voice,
70444
+ imageProvider: options.imageProvider,
70445
+ imageQuality: options.quality,
70446
+ imageSize: options.imageSize,
70447
+ force: options.force,
70448
+ onProgress: (e) => {
70449
+ if (!spinner2) return;
70450
+ if (e.type === "phase-start") spinner2.text = `Phase: ${e.phase}...`;
70451
+ else if (e.type === "render-done") spinner2.text = `Rendered: ${e.outputPath}`;
70452
+ }
70453
+ });
70454
+ if (!result.success) {
70455
+ spinner2?.fail(`Build failed: ${result.error}`);
70456
+ if (isJsonMode()) {
70457
+ outputSuccess({ command: "build", startedAt, data: { ...result } });
70458
+ process.exitCode = 1;
70459
+ return;
70460
+ }
70461
+ exitWithError(generalError(result.error ?? "Build failed"));
70462
+ }
70463
+ if (isJsonMode() || isQuietMode()) {
70464
+ outputSuccess({ command: "build", startedAt, data: { ...result } });
70465
+ return;
70466
+ }
70467
+ if (result.phase === "needs-author") {
70468
+ printNeedsAuthor(result);
70469
+ return;
70470
+ }
70471
+ printBuildResult(spinner2, result, projectDirArg);
70472
+ });
70473
+ function printBuildDryRun(projectDirArg, params) {
70474
+ console.log();
70475
+ console.log(source_default.bold.cyan("VibeFrame Build - dry run"));
70476
+ console.log(source_default.dim("-".repeat(60)));
70477
+ console.log(` Project: ${source_default.bold(projectDirArg)}`);
70478
+ console.log(` Mode: ${source_default.bold(params.mode)}`);
70479
+ console.log(` Composer: ${source_default.bold(String(params.composer ?? "auto"))}`);
70480
+ console.log(` TTS: ${source_default.bold(String(params.ttsProvider ?? "auto"))}${params.skipNarration ? source_default.dim(" (skipped)") : ""}`);
70481
+ console.log(` Image: ${source_default.bold(String(params.imageProvider ?? "openai"))} ${source_default.dim(`${params.imageQuality} ${params.imageSize}`)}${params.skipBackdrop ? source_default.dim(" (skipped)") : ""}`);
70482
+ console.log(` Render: ${source_default.bold(params.skipRender ? "skip" : "yes")}`);
70483
+ console.log(` Regenerate: ${source_default.bold(params.force ? "yes" : "cache when possible")}`);
70484
+ console.log();
70485
+ console.log(source_default.dim("No assets, provider calls, or video files were created."));
70486
+ }
70487
+ function printNeedsAuthor(result) {
70488
+ console.log(source_default.cyan("Agent authoring required"));
70489
+ console.log();
70490
+ console.log(source_default.bold.cyan("Missing compositions"));
70491
+ console.log(source_default.dim("-".repeat(60)));
70492
+ const missing = result.composePrompts?.beats.filter((beat) => !beat.exists) ?? [];
70493
+ if (missing.length === 0) {
70494
+ console.log(source_default.dim(" No missing files were reported. Re-run `vibe build` to continue."));
70495
+ } else {
70496
+ for (const beat of missing) {
70497
+ const dur = beat.duration !== void 0 ? source_default.dim(` ${beat.duration}s`) : "";
70498
+ console.log(` ${source_default.bold(beat.id)}${dur} -> ${beat.outputPath}`);
70499
+ }
70500
+ }
70501
+ console.log();
70502
+ console.log(source_default.bold.cyan("Next"));
70503
+ console.log(source_default.dim("-".repeat(60)));
70504
+ console.log(" Ask your coding agent to author the missing HTML files.");
70505
+ console.log(" Then run: vibe build");
70506
+ console.log(source_default.dim(" Use `vibe build --mode batch` when you want the CLI to call an LLM composer instead."));
70507
+ }
70508
+ function printBuildResult(spinner2, result, projectDirArg) {
70509
+ spinner2?.succeed(source_default.green(result.outputPath ? `Build complete: ${result.outputPath}` : "Build complete"));
70510
+ console.log();
70511
+ console.log(source_default.bold.cyan("Beats"));
70512
+ console.log(source_default.dim("-".repeat(60)));
70513
+ for (const beat of result.beats) {
70514
+ const narration = formatPrimitiveStatus2(beat.narrationStatus, beat.narrationPath);
70515
+ const backdrop = formatPrimitiveStatus2(beat.backdropStatus, beat.backdropPath);
70516
+ console.log(` ${source_default.bold(beat.beatId.padEnd(12))} narration: ${narration} backdrop: ${backdrop}`);
70517
+ if (beat.narrationError) console.log(source_default.red(` ! narration: ${beat.narrationError}`));
70518
+ if (beat.backdropError) console.log(source_default.red(` ! backdrop: ${beat.backdropError}`));
70519
+ }
70520
+ if (result.composeData) {
70521
+ console.log();
70522
+ console.log(source_default.bold.cyan("Compose"));
70523
+ console.log(source_default.dim("-".repeat(60)));
70524
+ console.log(` beats ${result.composeData.beats}`);
70525
+ console.log(` cache ${result.composeData.cacheHits} hit / ${result.composeData.beats - result.composeData.cacheHits} fresh`);
70526
+ console.log(` cost $${result.composeData.totalCostUsd.toFixed(4)}`);
70527
+ }
70528
+ console.log();
70529
+ console.log(source_default.bold.cyan("Next"));
70530
+ console.log(source_default.dim("-".repeat(60)));
70531
+ if (result.outputPath) {
70532
+ console.log(` Watch: ${result.outputPath}`);
70533
+ } else {
70534
+ console.log(` Render: vibe render ${projectDirArg}`);
70535
+ }
70536
+ console.log(source_default.dim(` Total: ${(result.totalLatencyMs / 1e3).toFixed(1)}s`));
70537
+ }
70538
+ function formatPrimitiveStatus2(status, path2) {
70539
+ switch (status) {
70540
+ case "generated":
70541
+ return source_default.green(path2 ?? "generated");
70542
+ case "cached":
70543
+ return source_default.dim(path2 ?? "cached");
70544
+ case "skipped":
70545
+ return source_default.dim("skipped");
70546
+ case "missing":
70547
+ return source_default.yellow("missing cue");
70548
+ case "failed":
70549
+ return source_default.red("failed");
70550
+ default:
70551
+ return status;
70552
+ }
70553
+ }
70554
+
70555
+ // src/commands/render.ts
70556
+ init_esm();
70557
+ init_source();
70558
+ init_ora();
70559
+ init_scene_render();
70560
+ init_output();
70561
+ import { resolve as resolve50 } from "node:path";
70562
+ var VALID_FPS2 = [24, 30, 60];
70563
+ var VALID_QUALITIES2 = ["draft", "standard", "high"];
70564
+ var VALID_FORMATS2 = ["mp4", "webm", "mov"];
70565
+ 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", `
70566
+ Examples:
70567
+ $ vibe render my-video
70568
+ $ vibe render my-video -o renders/final.mp4 --quality high
70569
+
70570
+ Alias note: this is the project-level entrypoint for \`vibe scene render\`.`).action(async (projectDirArg, options) => {
70571
+ const startedAt = Date.now();
70572
+ const projectDir = resolve50(projectDirArg);
70573
+ const fps = parseFps(String(options.fps));
70574
+ const quality = parseQuality(String(options.quality));
70575
+ const format = parseFormat(String(options.format));
70576
+ const workers = parseWorkers(String(options.workers));
70577
+ const params = {
70578
+ projectDir,
70579
+ root: options.root,
70580
+ output: options.out,
70581
+ fps,
70582
+ quality,
70583
+ format,
70584
+ workers
70585
+ };
70586
+ if (options.dryRun) {
70587
+ if (!isJsonMode() && !isQuietMode()) {
70588
+ printRenderDryRun(projectDirArg, params);
70589
+ return;
70590
+ }
70591
+ outputSuccess({
70592
+ command: "render",
70593
+ startedAt,
70594
+ dryRun: true,
70595
+ data: { params }
70596
+ });
70597
+ return;
70598
+ }
70599
+ const spinner2 = isJsonMode() || isQuietMode() ? null : ora("Rendering video project...").start();
70600
+ const result = await executeSceneRender({
70601
+ projectDir,
70602
+ root: options.root,
70603
+ output: options.out,
70604
+ fps,
70605
+ quality,
70606
+ format,
70607
+ workers,
70608
+ onProgress: (pct, stage) => {
70609
+ if (spinner2) spinner2.text = `Rendering [${Math.round(pct * 100)}%] ${stage}`;
70610
+ }
70611
+ });
70612
+ if (!result.success) {
70613
+ spinner2?.fail("Render failed");
70614
+ if (isJsonMode()) {
70615
+ outputSuccess({ command: "render", startedAt, data: { ...result } });
70616
+ process.exitCode = 1;
70617
+ return;
70618
+ }
70619
+ exitWithError(generalError(result.error ?? "Render failed"));
70620
+ }
70621
+ if (isJsonMode() || isQuietMode()) {
70622
+ outputSuccess({ command: "render", startedAt, data: { ...result } });
70623
+ return;
70624
+ }
70625
+ printRenderResult(spinner2, result);
70626
+ });
70627
+ function printRenderDryRun(projectDirArg, params) {
70628
+ console.log();
70629
+ console.log(source_default.bold.cyan("VibeFrame Render - dry run"));
70630
+ console.log(source_default.dim("-".repeat(60)));
70631
+ console.log(` Project: ${source_default.bold(projectDirArg)}`);
70632
+ console.log(` Root: ${source_default.bold(String(params.root))}`);
70633
+ console.log(` Output: ${source_default.bold(String(params.output ?? `renders/<name>-<timestamp>.${params.format}`))}`);
70634
+ console.log(` Format: ${source_default.bold(params.format)}`);
70635
+ console.log(` Quality/FPS: ${source_default.bold(`${params.quality} / ${params.fps}`)}`);
70636
+ console.log(` Workers: ${source_default.bold(String(params.workers))}`);
70637
+ console.log();
70638
+ console.log(source_default.dim("No browser capture, FFmpeg mux, or video files were created."));
70639
+ }
70640
+ function printRenderResult(spinner2, result) {
70641
+ spinner2?.succeed(source_default.green(`Render complete: ${result.outputPath}`));
70642
+ console.log();
70643
+ console.log(source_default.bold.cyan("Output"));
70644
+ console.log(source_default.dim("-".repeat(60)));
70645
+ console.log(` File: ${source_default.bold(result.outputPath)}`);
70646
+ console.log(` Format: ${result.format ?? "mp4"}`);
70647
+ console.log(` Quality: ${result.quality ?? "standard"}`);
70648
+ console.log(` FPS: ${result.fps ?? 30}`);
70649
+ if (result.totalFrames !== void 0) console.log(` Frames: ${result.framesRendered ?? result.totalFrames}/${result.totalFrames}`);
70650
+ if (result.durationMs !== void 0) console.log(` Duration: ${(result.durationMs / 1e3).toFixed(2)}s`);
70651
+ if (result.audioCount !== void 0) {
70652
+ const audio = result.audioCount > 0 ? `${result.audioCount} track${result.audioCount === 1 ? "" : "s"} muxed` : "silent";
70653
+ console.log(` Audio: ${audio}`);
70654
+ }
70655
+ if (result.audioMuxWarning) console.log(source_default.yellow(` Warning: ${result.audioMuxWarning}`));
70656
+ }
70657
+ function parseFps(value) {
70658
+ const n = Number.parseInt(value, 10);
70659
+ if (!VALID_FPS2.includes(n)) {
70660
+ exitWithError(usageError(`Invalid --fps: ${value}`, `Valid: ${VALID_FPS2.join(", ")}`));
70661
+ }
70662
+ return n;
70663
+ }
70664
+ function parseQuality(value) {
70665
+ if (!VALID_QUALITIES2.includes(value)) {
70666
+ exitWithError(usageError(`Invalid --quality: ${value}`, `Valid: ${VALID_QUALITIES2.join(", ")}`));
70667
+ }
70668
+ return value;
70669
+ }
70670
+ function parseFormat(value) {
70671
+ if (!VALID_FORMATS2.includes(value)) {
70672
+ exitWithError(usageError(`Invalid --format: ${value}`, `Valid: ${VALID_FORMATS2.join(", ")}`));
70673
+ }
70674
+ return value;
70675
+ }
70676
+ function parseWorkers(value) {
70677
+ const n = Number.parseInt(value, 10);
70678
+ if (!Number.isFinite(n) || n < 1 || n > 16) {
70679
+ exitWithError(usageError(`Invalid --workers: ${value}`, "Must be an integer between 1 and 16"));
70680
+ }
70681
+ return n;
70682
+ }
70683
+
69699
70684
  // src/commands/doctor.ts
69700
70685
  init_esm();
69701
70686
  init_source();
@@ -69711,7 +70696,7 @@ init_scene_build();
69711
70696
  init_output();
69712
70697
  import { access as access7 } from "node:fs/promises";
69713
70698
  import { existsSync as existsSync35 } from "node:fs";
69714
- import { resolve as resolve49 } from "node:path";
70699
+ import { resolve as resolve51 } from "node:path";
69715
70700
  var COMMAND_KEY_MAP = getCommandKeyMap();
69716
70701
  var FREE_COMMANDS = [
69717
70702
  "edit silence-cut",
@@ -69868,7 +70853,7 @@ async function runDiagnostics() {
69868
70853
  const cwd = process.cwd();
69869
70854
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
69870
70855
  path: rel,
69871
- exists: existsSync35(resolve49(cwd, rel))
70856
+ exists: existsSync35(resolve51(cwd, rel))
69872
70857
  }));
69873
70858
  const projectInitialized = projectFiles.some((f) => f.exists);
69874
70859
  const hosts = detectAgentHosts();
@@ -69883,8 +70868,8 @@ async function runDiagnostics() {
69883
70868
  } catch (err) {
69884
70869
  if (!(err instanceof ComposerResolveError)) throw err;
69885
70870
  }
69886
- const sceneProjectInCwd = existsSync35(resolve49(cwd, "STORYBOARD.md"));
69887
- const skillInstalled = existsSync35(resolve49(cwd, "SKILL.md"));
70871
+ const sceneProjectInCwd = existsSync35(resolve51(cwd, "STORYBOARD.md"));
70872
+ const skillInstalled = existsSync35(resolve51(cwd, "SKILL.md"));
69888
70873
  return {
69889
70874
  system: {
69890
70875
  node: { version: nodeVersion, ok: true },
@@ -70049,10 +71034,10 @@ init_exec_safe();
70049
71034
  init_detect();
70050
71035
  init_ai_edit();
70051
71036
  init_output();
70052
- import { resolve as resolve50 } from "node:path";
71037
+ import { resolve as resolve52 } from "node:path";
70053
71038
  import { existsSync as existsSync36 } from "node:fs";
70054
71039
  import { mkdir as mkdir21, rm as rm4 } from "node:fs/promises";
70055
- var DEMO_DIR = resolve50(process.cwd(), ".vibeframe-demo");
71040
+ var DEMO_DIR = resolve52(process.cwd(), ".vibeframe-demo");
70056
71041
  async function generateTestVideo(outputPath) {
70057
71042
  const filter = [
70058
71043
  // Video: color bars with text
@@ -70132,7 +71117,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70132
71117
  console.log();
70133
71118
  }
70134
71119
  await mkdir21(DEMO_DIR, { recursive: true });
70135
- const testVideo = resolve50(DEMO_DIR, "test-input.mp4");
71120
+ const testVideo = resolve52(DEMO_DIR, "test-input.mp4");
70136
71121
  const genSpinner = !isJson ? ora("Generating test video...").start() : null;
70137
71122
  const generated = await generateTestVideo(testVideo);
70138
71123
  if (!generated) {
@@ -70161,7 +71146,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70161
71146
  name: "Silence Cut",
70162
71147
  description: "Removing silent segments",
70163
71148
  run: async () => {
70164
- const output = resolve50(DEMO_DIR, "silence-cut.mp4");
71149
+ const output = resolve52(DEMO_DIR, "silence-cut.mp4");
70165
71150
  const r = await executeSilenceCut({ videoPath: testVideo, outputPath: output, padding: 0.1 });
70166
71151
  if (r.success && r.totalDuration && r.silentDuration) {
70167
71152
  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 +71158,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70173
71158
  name: "Noise Reduce",
70174
71159
  description: "Reducing background noise",
70175
71160
  run: async () => {
70176
- const output = resolve50(DEMO_DIR, "noise-reduced.mp4");
71161
+ const output = resolve52(DEMO_DIR, "noise-reduced.mp4");
70177
71162
  const r = await executeNoiseReduce({ inputPath: testVideo, outputPath: output, strength: "medium" });
70178
71163
  return { success: r.success, detail: r.success ? `Cleaned audio \u2192 ${output}` : r.error };
70179
71164
  }
@@ -70182,7 +71167,7 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70182
71167
  name: "Fade Effects",
70183
71168
  description: "Adding fade in/out",
70184
71169
  run: async () => {
70185
- const output = resolve50(DEMO_DIR, "faded.mp4");
71170
+ const output = resolve52(DEMO_DIR, "faded.mp4");
70186
71171
  const r = await executeFade({ videoPath: testVideo, outputPath: output, fadeIn: 1, fadeOut: 1 });
70187
71172
  return { success: r.success, detail: r.success ? `1s fade in + 1s fade out \u2192 ${output}` : r.error };
70188
71173
  }
@@ -70250,13 +71235,13 @@ var demoCommand = new Command("demo").description("Run sample edits on a test vi
70250
71235
  // src/commands/context.ts
70251
71236
  init_esm();
70252
71237
  import { readFile as readFile35 } from "node:fs/promises";
70253
- import { resolve as resolve51, dirname as dirname19 } from "node:path";
71238
+ import { resolve as resolve53, dirname as dirname19 } from "node:path";
70254
71239
  import { fileURLToPath as fileURLToPath2 } from "node:url";
70255
71240
  var __filename = fileURLToPath2(import.meta.url);
70256
71241
  var __dirname = dirname19(__filename);
70257
71242
  var contextCommand = new Command("context").description("Print CLI context/guidelines for AI agent integration").action(async (_options, cmd) => {
70258
71243
  const options = { json: cmd.parent?.opts()?.json || cmd.opts()?.json };
70259
- const contextPath = resolve51(__dirname, "../../CONTEXT.md");
71244
+ const contextPath = resolve53(__dirname, "../../CONTEXT.md");
70260
71245
  try {
70261
71246
  const content = await readFile35(contextPath, "utf-8");
70262
71247
  if (options.json) {
@@ -70997,14 +71982,14 @@ async function promptConfirm2(toolName, args) {
70997
71982
  output: process.stdout,
70998
71983
  terminal: process.stdin.isTTY ?? false
70999
71984
  });
71000
- return new Promise((resolve62) => {
71985
+ return new Promise((resolve64) => {
71001
71986
  const argsStr = JSON.stringify(args, null, 2);
71002
71987
  console.log();
71003
71988
  console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?`));
71004
71989
  console.log(source_default.dim(argsStr));
71005
71990
  rl.question(source_default.cyan("(y/n): "), (answer) => {
71006
71991
  rl.close();
71007
- resolve62(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
71992
+ resolve64(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
71008
71993
  });
71009
71994
  });
71010
71995
  }
@@ -71093,7 +72078,7 @@ async function startAgent(options = {}) {
71093
72078
  console.log();
71094
72079
  console.log(source_default.dim(" Commands: exit \xB7 reset \xB7 tools \xB7 context"));
71095
72080
  console.log();
71096
- return new Promise((resolve62) => {
72081
+ return new Promise((resolve64) => {
71097
72082
  if (typeof process.stdin.ref === "function") {
71098
72083
  process.stdin.ref();
71099
72084
  }
@@ -71195,7 +72180,7 @@ async function startAgent(options = {}) {
71195
72180
  clearInterval(keepalive);
71196
72181
  console.log();
71197
72182
  console.log(source_default.dim("Goodbye!"));
71198
- resolve62();
72183
+ resolve64();
71199
72184
  });
71200
72185
  rl.prompt();
71201
72186
  });
@@ -71378,7 +72363,7 @@ async function readStdin() {
71378
72363
  return Buffer.concat(chunks).toString("utf-8").trim();
71379
72364
  }
71380
72365
  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({
72366
+ 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
72367
  outputError: (str, write2) => {
71383
72368
  if (process.env.VIBE_JSON_OUTPUT === "1" || process.argv.includes("--json")) {
71384
72369
  const err = { success: false, error: str.trim(), code: "USAGE_ERROR", exitCode: 2, retryable: false };
@@ -71398,20 +72383,38 @@ Global flags (work with any command):
71398
72383
  --stdin Read options from stdin as JSON (for agent/script use)
71399
72384
  --dry-run Preview without executing (most commands)
71400
72385
 
71401
- Cost tiers:
71402
- Free detect, edit (silence-cut/fade/noise-reduce), project, timeline, export
71403
- Low analyze, audio transcribe, generate image ~$0.01-$0.10
71404
- High generate video, edit image ~$1-$5
71405
- V.High pipeline (regenerate-scene, highlights, auto-shorts) ~$5-$50+
71406
-
71407
- Quick start:
72386
+ Get started (90% of users start here):
71408
72387
  vibe doctor Check system health and API keys
71409
72388
  vibe setup Configure API keys interactively
71410
- vibe schema --list Discover all commands (69 commands)
71411
-
71412
- Agent integration:
71413
- echo '{"provider":"kling"}' | vibe generate video "prompt" --stdin --json
71414
- vibe schema generate.video Get parameter schema for any command
72389
+ vibe init my-video Scaffold a video project
72390
+ vibe build my-video Build STORYBOARD.md into scene compositions/assets
72391
+ vibe render my-video Render the project to MP4
72392
+ vibe demo Try VibeFrame without API keys
72393
+
72394
+ One-shot media tools:
72395
+ vibe generate image "..." Generate a still image
72396
+ vibe generate video "..." Generate a standalone AI video
72397
+ vibe edit caption clip.mp4 -o out.mp4
72398
+ vibe inspect media clip.mp4 "Summarize this"
72399
+ vibe remix highlights long.mp4 -d 60
72400
+
72401
+ Advanced authoring:
72402
+ vibe project create my-video CRUD on .vibe.json
72403
+ vibe scene add intro --headline "Welcome"
72404
+ vibe timeline add-clip project.vibe.json <source-id>
72405
+
72406
+ Automation & agents:
72407
+ vibe run workflow.yaml Run a Video-as-YAML pipeline
72408
+ vibe agent Natural-language interface
72409
+ vibe schema generate.video Tool schema for any command
72410
+ vibe walkthrough scene Step-by-step authoring guide
72411
+
72412
+ Cost tiers (per call):
72413
+ Free detect, edit (silence-cut/fade/noise-reduce), project, timeline, export
72414
+ Low inspect, audio transcribe, generate image ~$0.01-$0.10
72415
+ High generate video, edit image ~$1-$5
72416
+ V.High remix (regenerate-scene, highlights, auto-shorts) ~$5-$50+
72417
+ Tip Add --dry-run before paid generation to preview the work.
71415
72418
  `
71416
72419
  );
71417
72420
  program2.hook("preAction", async (thisCommand, actionCommand) => {
@@ -71478,6 +72481,8 @@ program2.addCommand(audioCommand);
71478
72481
  program2.addCommand(pipelineCommand);
71479
72482
  program2.addCommand(setupCommand);
71480
72483
  program2.addCommand(initCommand);
72484
+ program2.addCommand(buildCommand);
72485
+ program2.addCommand(renderCommand);
71481
72486
  program2.addCommand(doctorCommand);
71482
72487
  program2.addCommand(demoCommand);
71483
72488
  program2.addCommand(runCommand);
@@ -71485,7 +72490,7 @@ program2.addCommand(agentCommand);
71485
72490
  program2.addCommand(projectCommand);
71486
72491
  program2.addCommand(sceneCommand);
71487
72492
  program2.addCommand(timelineCommand);
71488
- program2.addCommand(exportCommand);
72493
+ program2.addCommand(exportCommand, { hidden: true });
71489
72494
  program2.addCommand(detectCommand);
71490
72495
  program2.addCommand(batchCommand);
71491
72496
  program2.addCommand(schemaCommand);