@vibeframe/mcp-server 0.70.0 → 0.72.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 (3) hide show
  1. package/README.md +37 -9
  2. package/dist/index.js +1230 -551
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -79585,19 +79585,19 @@ var require_range = __commonJS({
79585
79585
  var replaceCaret = (comp, options) => {
79586
79586
  debug7("caret", comp, options);
79587
79587
  const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
79588
- const z12 = options.includePrerelease ? "-0" : "";
79588
+ const z13 = options.includePrerelease ? "-0" : "";
79589
79589
  return comp.replace(r, (_, M, m, p, pr) => {
79590
79590
  debug7("caret", comp, _, M, m, p, pr);
79591
79591
  let ret;
79592
79592
  if (isX(M)) {
79593
79593
  ret = "";
79594
79594
  } else if (isX(m)) {
79595
- ret = `>=${M}.0.0${z12} <${+M + 1}.0.0-0`;
79595
+ ret = `>=${M}.0.0${z13} <${+M + 1}.0.0-0`;
79596
79596
  } else if (isX(p)) {
79597
79597
  if (M === "0") {
79598
- ret = `>=${M}.${m}.0${z12} <${M}.${+m + 1}.0-0`;
79598
+ ret = `>=${M}.${m}.0${z13} <${M}.${+m + 1}.0-0`;
79599
79599
  } else {
79600
- ret = `>=${M}.${m}.0${z12} <${+M + 1}.0.0-0`;
79600
+ ret = `>=${M}.${m}.0${z13} <${+M + 1}.0.0-0`;
79601
79601
  }
79602
79602
  } else if (pr) {
79603
79603
  debug7("replaceCaret pr", pr);
@@ -79614,9 +79614,9 @@ var require_range = __commonJS({
79614
79614
  debug7("no pr");
79615
79615
  if (M === "0") {
79616
79616
  if (m === "0") {
79617
- ret = `>=${M}.${m}.${p}${z12} <${M}.${m}.${+p + 1}-0`;
79617
+ ret = `>=${M}.${m}.${p}${z13} <${M}.${m}.${+p + 1}-0`;
79618
79618
  } else {
79619
- ret = `>=${M}.${m}.${p}${z12} <${M}.${+m + 1}.0-0`;
79619
+ ret = `>=${M}.${m}.${p}${z13} <${M}.${+m + 1}.0-0`;
79620
79620
  }
79621
79621
  } else {
79622
79622
  ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`;
@@ -106350,7 +106350,7 @@ var require_emscripten_module_WASM_RELEASE_SYNC = __commonJS({
106350
106350
  m = b;
106351
106351
  n = c;
106352
106352
  });
106353
- var p = Object.assign({}, a), t = "./this.program", u = "object" == typeof window, v = "function" == typeof importScripts, w = "object" == typeof process && "object" == typeof process.versions && "string" == typeof process.versions.node, x = "", y, z12, A;
106353
+ var p = Object.assign({}, a), t = "./this.program", u = "object" == typeof window, v = "function" == typeof importScripts, w = "object" == typeof process && "object" == typeof process.versions && "string" == typeof process.versions.node, x = "", y, z13, A;
106354
106354
  if (w) {
106355
106355
  var fs8 = __require("fs"), B = __require("path");
106356
106356
  x = v ? B.dirname(x) + "/" : __dirname + "/";
@@ -106366,7 +106366,7 @@ var require_emscripten_module_WASM_RELEASE_SYNC = __commonJS({
106366
106366
  b.buffer || (b = new Uint8Array(b));
106367
106367
  return b;
106368
106368
  };
106369
- z12 = (b, c, d) => {
106369
+ z13 = (b, c, d) => {
106370
106370
  var e = C(b);
106371
106371
  e && c(e);
106372
106372
  b = b.startsWith("file://") ? new URL(b) : B.normalize(b);
@@ -106410,7 +106410,7 @@ var require_emscripten_module_WASM_RELEASE_SYNC = __commonJS({
106410
106410
  return b;
106411
106411
  throw d;
106412
106412
  }
106413
- }), z12 = (b, c, d) => {
106413
+ }), z13 = (b, c, d) => {
106414
106414
  var e = new XMLHttpRequest();
106415
106415
  e.open("GET", b, true);
106416
106416
  e.responseType = "arraybuffer";
@@ -106491,9 +106491,9 @@ var require_emscripten_module_WASM_RELEASE_SYNC = __commonJS({
106491
106491
  }).catch(function() {
106492
106492
  return ha(b);
106493
106493
  });
106494
- if (z12)
106494
+ if (z13)
106495
106495
  return new Promise(function(c, d) {
106496
- z12(b, function(e) {
106496
+ z13(b, function(e) {
106497
106497
  c(new Uint8Array(e));
106498
106498
  }, d);
106499
106499
  });
@@ -347714,8 +347714,8 @@ async function queryElementStacking(page, nativeHdrVideoIds) {
347714
347714
  while (current) {
347715
347715
  const cs = window.getComputedStyle(current);
347716
347716
  const pos = cs.position;
347717
- const z12 = parseInt(cs.zIndex);
347718
- if (!Number.isNaN(z12) && pos !== "static") return z12;
347717
+ const z13 = parseInt(cs.zIndex);
347718
+ if (!Number.isNaN(z13) && pos !== "static") return z13;
347719
347719
  current = current.parentElement;
347720
347720
  }
347721
347721
  return 0;
@@ -396287,19 +396287,19 @@ ${sourceUrlComment}
396287
396287
  var replaceCaret = (comp, options) => {
396288
396288
  debug62("caret", comp, options);
396289
396289
  const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET];
396290
- const z12 = options.includePrerelease ? "-0" : "";
396290
+ const z13 = options.includePrerelease ? "-0" : "";
396291
396291
  return comp.replace(r, (_, M, m, p, pr) => {
396292
396292
  debug62("caret", comp, _, M, m, p, pr);
396293
396293
  let ret;
396294
396294
  if (isX(M)) {
396295
396295
  ret = "";
396296
396296
  } else if (isX(m)) {
396297
- ret = `>=${M}.0.0${z12} <${+M + 1}.0.0-0`;
396297
+ ret = `>=${M}.0.0${z13} <${+M + 1}.0.0-0`;
396298
396298
  } else if (isX(p)) {
396299
396299
  if (M === "0") {
396300
- ret = `>=${M}.${m}.0${z12} <${M}.${+m + 1}.0-0`;
396300
+ ret = `>=${M}.${m}.0${z13} <${M}.${+m + 1}.0-0`;
396301
396301
  } else {
396302
- ret = `>=${M}.${m}.0${z12} <${+M + 1}.0.0-0`;
396302
+ ret = `>=${M}.${m}.0${z13} <${+M + 1}.0.0-0`;
396303
396303
  }
396304
396304
  } else if (pr) {
396305
396305
  debug62("replaceCaret pr", pr);
@@ -396316,9 +396316,9 @@ ${sourceUrlComment}
396316
396316
  debug62("no pr");
396317
396317
  if (M === "0") {
396318
396318
  if (m === "0") {
396319
- ret = `>=${M}.${m}.${p}${z12} <${M}.${m}.${+p + 1}-0`;
396319
+ ret = `>=${M}.${m}.${p}${z13} <${M}.${m}.${+p + 1}-0`;
396320
396320
  } else {
396321
- ret = `>=${M}.${m}.${p}${z12} <${M}.${+m + 1}.0-0`;
396321
+ ret = `>=${M}.${m}.${p}${z13} <${M}.${+m + 1}.0-0`;
396322
396322
  }
396323
396323
  } else {
396324
396324
  ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`;
@@ -422114,7 +422114,7 @@ Attempted to suspend at:`);
422114
422114
  m = b;
422115
422115
  n = c;
422116
422116
  });
422117
- var p = Object.assign({}, a), t = "./this.program", u = "object" == typeof window, v = "function" == typeof importScripts, w = "object" == typeof process && "object" == typeof process.versions && "string" == typeof process.versions.node, x = "", y, z12, A;
422117
+ var p = Object.assign({}, a), t = "./this.program", u = "object" == typeof window, v = "function" == typeof importScripts, w = "object" == typeof process && "object" == typeof process.versions && "string" == typeof process.versions.node, x = "", y, z13, A;
422118
422118
  if (w) {
422119
422119
  var fs8 = __require2("fs"), B = __require2("path");
422120
422120
  x = v ? B.dirname(x) + "/" : __dirname + "/";
@@ -422130,7 +422130,7 @@ Attempted to suspend at:`);
422130
422130
  b.buffer || (b = new Uint8Array(b));
422131
422131
  return b;
422132
422132
  };
422133
- z12 = (b, c, d) => {
422133
+ z13 = (b, c, d) => {
422134
422134
  var e = C(b);
422135
422135
  e && c(e);
422136
422136
  b = b.startsWith("file://") ? new URL(b) : B.normalize(b);
@@ -422174,7 +422174,7 @@ Attempted to suspend at:`);
422174
422174
  return b;
422175
422175
  throw d;
422176
422176
  }
422177
- }), z12 = (b, c, d) => {
422177
+ }), z13 = (b, c, d) => {
422178
422178
  var e = new XMLHttpRequest();
422179
422179
  e.open("GET", b, true);
422180
422180
  e.responseType = "arraybuffer";
@@ -422255,9 +422255,9 @@ Attempted to suspend at:`);
422255
422255
  }).catch(function() {
422256
422256
  return ha(b);
422257
422257
  });
422258
- if (z12)
422258
+ if (z13)
422259
422259
  return new Promise(function(c, d) {
422260
- z12(b, function(e) {
422260
+ z13(b, function(e) {
422261
422261
  c(new Uint8Array(e));
422262
422262
  }, d);
422263
422263
  });
@@ -448826,6 +448826,26 @@ function detectAgentHosts(env4 = process.env) {
448826
448826
  detected: false,
448827
448827
  signals: [],
448828
448828
  projectFiles: ["AGENTS.md", ".aider.conf.yml"]
448829
+ },
448830
+ {
448831
+ id: "gemini-cli",
448832
+ label: "Gemini CLI",
448833
+ detected: false,
448834
+ signals: [],
448835
+ // Per https://geminicli.com/docs/cli/gemini-md/ Gemini CLI's primary
448836
+ // context file is GEMINI.md; AGENTS.md is the cross-tool fallback
448837
+ // VibeFrame writes by default. Both are honoured.
448838
+ projectFiles: ["GEMINI.md", "AGENTS.md", ".gemini/"]
448839
+ },
448840
+ {
448841
+ id: "opencode",
448842
+ label: "OpenCode",
448843
+ detected: false,
448844
+ signals: [],
448845
+ // sst/opencode officially supports the agents.md spec — AGENTS.md at
448846
+ // project root is the standard place. Local config also under
448847
+ // `.opencode/` per https://opencode.ai/docs/config/.
448848
+ projectFiles: ["AGENTS.md", ".opencode/"]
448829
448849
  }
448830
448850
  ].map((host) => {
448831
448851
  const signals2 = [];
@@ -448873,14 +448893,21 @@ var init_agent_host_detect = __esm({
448873
448893
  "claude-code": "claude",
448874
448894
  codex: "codex",
448875
448895
  cursor: "cursor",
448876
- aider: "aider"
448896
+ aider: "aider",
448897
+ "gemini-cli": "gemini",
448898
+ opencode: "opencode"
448877
448899
  };
448878
448900
  HOST_CONFIG_DIRS = {
448879
448901
  "claude-code": ".claude",
448880
448902
  codex: ".codex",
448881
448903
  cursor: ".cursor",
448882
448904
  // some installs; macOS app stores prefs elsewhere
448883
- aider: null
448905
+ aider: null,
448906
+ "gemini-cli": ".gemini",
448907
+ // sst/opencode uses XDG-style `~/.config/opencode/` per
448908
+ // https://opencode.ai/docs/config/. The path is relative to $HOME so
448909
+ // the join in detectAgentHosts() resolves correctly.
448910
+ opencode: ".config/opencode"
448884
448911
  };
448885
448912
  }
448886
448913
  });
@@ -449205,6 +449232,7 @@ __export(output_exports, {
449205
449232
  notFoundError: () => notFoundError,
449206
449233
  outputError: () => outputError,
449207
449234
  outputResult: () => outputResult,
449235
+ outputSuccess: () => outputSuccess,
449208
449236
  spinner: () => spinner,
449209
449237
  suggestNext: () => suggestNext,
449210
449238
  usageError: () => usageError
@@ -449263,6 +449291,40 @@ function formatCost(min, max, unit) {
449263
449291
  if (min === max) return `~$${min.toFixed(2)} ${unit}`;
449264
449292
  return `~$${min.toFixed(2)}-$${max.toFixed(2)} ${unit}`;
449265
449293
  }
449294
+ function lookupCostEstimateUpperBound(command3) {
449295
+ return COST_ESTIMATES[command3]?.max ?? 0;
449296
+ }
449297
+ function outputSuccess(opts) {
449298
+ const elapsedMs = Math.max(0, Date.now() - opts.startedAt);
449299
+ const costUsd = opts.costUsd ?? (opts.dryRun ? lookupCostEstimateUpperBound(opts.command) : 0);
449300
+ const envelope = {
449301
+ command: opts.command,
449302
+ ...opts.dryRun ? { dryRun: true } : {},
449303
+ elapsedMs,
449304
+ costUsd,
449305
+ warnings: opts.warnings ?? [],
449306
+ data: opts.data
449307
+ };
449308
+ if (isJsonMode()) {
449309
+ const fields = process.env.VIBE_OUTPUT_FIELDS;
449310
+ if (fields) {
449311
+ const keys2 = fields.split(",").map((k) => k.trim());
449312
+ const data = opts.data;
449313
+ const filteredData = {};
449314
+ for (const key2 of keys2) {
449315
+ if (key2 in data) filteredData[key2] = data[key2];
449316
+ }
449317
+ envelope.data = filteredData;
449318
+ }
449319
+ console.log(JSON.stringify(envelope, null, 2));
449320
+ return;
449321
+ }
449322
+ if (isQuietMode()) {
449323
+ const data = opts.data;
449324
+ const primary = data.outputPath ?? data.output ?? data.path ?? data.url ?? data.id;
449325
+ if (primary !== void 0) console.log(String(primary));
449326
+ }
449327
+ }
449266
449328
  function outputResult(result) {
449267
449329
  if (result.dryRun && result.command && typeof result.command === "string") {
449268
449330
  const cost = COST_ESTIMATES[result.command];
@@ -451854,6 +451916,7 @@ Examples:
451854
451916
  $ vibe ed sc video.mp4 --dry-run --json
451855
451917
 
451856
451918
  No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires GOOGLE_API_KEY).`).action(async (videoPath, options) => {
451919
+ const startedAt = Date.now();
451857
451920
  try {
451858
451921
  if (options.output) {
451859
451922
  validateOutputPath(options.output);
@@ -451870,16 +451933,19 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
451870
451933
  const outputPath = options.output || `${name}-cut${ext}`;
451871
451934
  const useGemini = options.useGemini || false;
451872
451935
  if (options.dryRun) {
451873
- outputResult({
451874
- dryRun: true,
451936
+ outputSuccess({
451875
451937
  command: "edit silence-cut",
451876
- params: {
451877
- videoPath: absVideoPath,
451878
- noiseThreshold: parseFloat(options.noise),
451879
- minDuration: parseFloat(options.minDuration),
451880
- padding: parseFloat(options.padding),
451881
- useGemini,
451882
- analyzeOnly: options.analyzeOnly || false
451938
+ startedAt,
451939
+ dryRun: true,
451940
+ data: {
451941
+ params: {
451942
+ videoPath: absVideoPath,
451943
+ noiseThreshold: parseFloat(options.noise),
451944
+ minDuration: parseFloat(options.minDuration),
451945
+ padding: parseFloat(options.padding),
451946
+ useGemini,
451947
+ analyzeOnly: options.analyzeOnly || false
451948
+ }
451883
451949
  }
451884
451950
  });
451885
451951
  return;
@@ -451904,13 +451970,16 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
451904
451970
  }
451905
451971
  spinner2.succeed(source_default.green("Silence detection complete"));
451906
451972
  if (isJsonMode()) {
451907
- outputResult({
451908
- success: true,
451909
- method: result.method,
451910
- totalDuration: result.totalDuration,
451911
- silentPeriods: result.silentPeriods,
451912
- silentDuration: result.silentDuration,
451913
- outputPath: result.outputPath
451973
+ outputSuccess({
451974
+ command: "edit silence-cut",
451975
+ startedAt,
451976
+ data: {
451977
+ method: result.method,
451978
+ totalDuration: result.totalDuration,
451979
+ silentPeriods: result.silentPeriods,
451980
+ silentDuration: result.silentDuration,
451981
+ outputPath: result.outputPath
451982
+ }
451914
451983
  });
451915
451984
  return;
451916
451985
  }
@@ -451948,6 +452017,7 @@ Examples:
451948
452017
  $ vibe ed cap video.mp4 --dry-run --json
451949
452018
 
451950
452019
  Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoPath, options) => {
452020
+ const startedAt = Date.now();
451951
452021
  try {
451952
452022
  if (options.output) {
451953
452023
  validateOutputPath(options.output);
@@ -451960,16 +452030,19 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
451960
452030
  exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
451961
452031
  }
451962
452032
  if (options.dryRun) {
451963
- outputResult({
451964
- dryRun: true,
452033
+ outputSuccess({
451965
452034
  command: "edit caption",
451966
- params: {
451967
- videoPath: absVideoPath,
451968
- style: options.style,
451969
- fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
451970
- fontColor: options.color,
451971
- language: options.language,
451972
- position: options.position
452035
+ startedAt,
452036
+ dryRun: true,
452037
+ data: {
452038
+ params: {
452039
+ videoPath: absVideoPath,
452040
+ style: options.style,
452041
+ fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
452042
+ fontColor: options.color,
452043
+ language: options.language,
452044
+ position: options.position
452045
+ }
451973
452046
  }
451974
452047
  });
451975
452048
  return;
@@ -451998,12 +452071,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
451998
452071
  }
451999
452072
  spinner2.succeed(source_default.green("Captions applied"));
452000
452073
  if (isJsonMode()) {
452001
- outputResult({
452002
- success: true,
452003
- segmentCount: result.segmentCount,
452004
- style: options.style || "bold",
452005
- outputPath: result.outputPath,
452006
- srtPath: result.srtPath
452074
+ outputSuccess({
452075
+ command: "edit caption",
452076
+ startedAt,
452077
+ data: {
452078
+ segmentCount: result.segmentCount,
452079
+ style: options.style || "bold",
452080
+ outputPath: result.outputPath,
452081
+ srtPath: result.srtPath
452082
+ }
452007
452083
  });
452008
452084
  return;
452009
452085
  }
@@ -452022,6 +452098,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452022
452098
  }
452023
452099
  });
452024
452100
  aiCommand.command("noise-reduce").description("Remove background noise from audio/video using FFmpeg (no API key needed)").argument("<input>", "Audio or video file path").option("-o, --output <path>", "Output file path (default: <name>-denoised.<ext>)").option("-s, --strength <level>", "Noise reduction strength: low, medium, high (default: medium)", "medium").option("-n, --noise-floor <dB>", "Custom noise floor in dB (overrides strength preset)").option("--dry-run", "Preview parameters without executing").action(async (inputPath, options) => {
452101
+ const startedAt = Date.now();
452025
452102
  try {
452026
452103
  if (options.output) {
452027
452104
  validateOutputPath(options.output);
@@ -452034,13 +452111,16 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452034
452111
  exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
452035
452112
  }
452036
452113
  if (options.dryRun) {
452037
- outputResult({
452038
- dryRun: true,
452114
+ outputSuccess({
452039
452115
  command: "edit noise-reduce",
452040
- params: {
452041
- inputPath: absInputPath,
452042
- strength: options.strength,
452043
- noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
452116
+ startedAt,
452117
+ dryRun: true,
452118
+ data: {
452119
+ params: {
452120
+ inputPath: absInputPath,
452121
+ strength: options.strength,
452122
+ noiseFloor: options.noiseFloor ? parseFloat(options.noiseFloor) : void 0
452123
+ }
452044
452124
  }
452045
452125
  });
452046
452126
  return;
@@ -452061,11 +452141,14 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452061
452141
  }
452062
452142
  spinner2.succeed(source_default.green("Noise reduction complete"));
452063
452143
  if (isJsonMode()) {
452064
- outputResult({
452065
- success: true,
452066
- inputDuration: result.inputDuration,
452067
- strength: options.strength || "medium",
452068
- outputPath: result.outputPath
452144
+ outputSuccess({
452145
+ command: "edit noise-reduce",
452146
+ startedAt,
452147
+ data: {
452148
+ inputDuration: result.inputDuration,
452149
+ strength: options.strength || "medium",
452150
+ outputPath: result.outputPath
452151
+ }
452069
452152
  });
452070
452153
  return;
452071
452154
  }
@@ -452081,6 +452164,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452081
452164
  }
452082
452165
  });
452083
452166
  aiCommand.command("fade").description("Apply fade in/out effects to video (FFmpeg only, no API key needed)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-faded.<ext>)").option("--fade-in <seconds>", "Fade-in duration in seconds (default: 1)", "1").option("--fade-out <seconds>", "Fade-out duration in seconds (default: 1)", "1").option("--audio-only", "Apply fade to audio only (video stream copied)").option("--video-only", "Apply fade to video only (audio stream copied)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
452167
+ const startedAt = Date.now();
452084
452168
  try {
452085
452169
  if (options.output) {
452086
452170
  validateOutputPath(options.output);
@@ -452093,15 +452177,18 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452093
452177
  exitWithError(generalError("FFmpeg not found. Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux). Run `vibe doctor` for details."));
452094
452178
  }
452095
452179
  if (options.dryRun) {
452096
- outputResult({
452097
- dryRun: true,
452180
+ outputSuccess({
452098
452181
  command: "edit fade",
452099
- params: {
452100
- videoPath: absVideoPath,
452101
- fadeIn: parseFloat(options.fadeIn),
452102
- fadeOut: parseFloat(options.fadeOut),
452103
- audioOnly: options.audioOnly || false,
452104
- videoOnly: options.videoOnly || false
452182
+ startedAt,
452183
+ dryRun: true,
452184
+ data: {
452185
+ params: {
452186
+ videoPath: absVideoPath,
452187
+ fadeIn: parseFloat(options.fadeIn),
452188
+ fadeOut: parseFloat(options.fadeOut),
452189
+ audioOnly: options.audioOnly || false,
452190
+ videoOnly: options.videoOnly || false
452191
+ }
452105
452192
  }
452106
452193
  });
452107
452194
  return;
@@ -452124,12 +452211,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452124
452211
  }
452125
452212
  spinner2.succeed(source_default.green("Fade effects applied"));
452126
452213
  if (isJsonMode()) {
452127
- outputResult({
452128
- success: true,
452129
- totalDuration: result.totalDuration,
452130
- fadeInApplied: result.fadeInApplied,
452131
- fadeOutApplied: result.fadeOutApplied,
452132
- outputPath: result.outputPath
452214
+ outputSuccess({
452215
+ command: "edit fade",
452216
+ startedAt,
452217
+ data: {
452218
+ totalDuration: result.totalDuration,
452219
+ fadeInApplied: result.fadeInApplied,
452220
+ fadeOutApplied: result.fadeOutApplied,
452221
+ outputPath: result.outputPath
452222
+ }
452133
452223
  });
452134
452224
  return;
452135
452225
  }
@@ -452146,6 +452236,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452146
452236
  }
452147
452237
  });
452148
452238
  aiCommand.command("translate-srt").description("Translate SRT subtitle file to another language (Claude/OpenAI)").argument("<srt>", "SRT file path").option("-t, --target <language>", "Target language (e.g., ko, es, fr, ja, zh)").option("-o, --output <path>", "Output file path (default: <name>-<target>.srt)").option("-p, --provider <provider>", "Translation provider: claude, openai (default: claude)", "claude").option("--source <language>", "Source language (auto-detected if omitted)").option("-k, --api-key <key>", "API key (or set ANTHROPIC_API_KEY / OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (srtPath, options) => {
452239
+ const startedAt = Date.now();
452149
452240
  try {
452150
452241
  if (options.output) {
452151
452242
  validateOutputPath(options.output);
@@ -452158,14 +452249,17 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452158
452249
  exitWithError(notFoundError(absSrtPath));
452159
452250
  }
452160
452251
  if (options.dryRun) {
452161
- outputResult({
452162
- dryRun: true,
452252
+ outputSuccess({
452163
452253
  command: "edit translate-srt",
452164
- params: {
452165
- srtPath: absSrtPath,
452166
- targetLanguage: options.target,
452167
- provider: options.provider || "claude",
452168
- sourceLanguage: options.source
452254
+ startedAt,
452255
+ dryRun: true,
452256
+ data: {
452257
+ params: {
452258
+ srtPath: absSrtPath,
452259
+ targetLanguage: options.target,
452260
+ provider: options.provider || "claude",
452261
+ sourceLanguage: options.source
452262
+ }
452169
452263
  }
452170
452264
  });
452171
452265
  return;
@@ -452195,12 +452289,15 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452195
452289
  }
452196
452290
  spinner2.succeed(source_default.green("Translation complete"));
452197
452291
  if (isJsonMode()) {
452198
- outputResult({
452199
- success: true,
452200
- segmentCount: result.segmentCount,
452201
- sourceLanguage: result.sourceLanguage,
452202
- targetLanguage: result.targetLanguage,
452203
- outputPath: result.outputPath
452292
+ outputSuccess({
452293
+ command: "edit translate-srt",
452294
+ startedAt,
452295
+ data: {
452296
+ segmentCount: result.segmentCount,
452297
+ sourceLanguage: result.sourceLanguage,
452298
+ targetLanguage: result.targetLanguage,
452299
+ outputPath: result.outputPath
452300
+ }
452204
452301
  });
452205
452302
  return;
452206
452303
  }
@@ -452217,6 +452314,7 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452217
452314
  }
452218
452315
  });
452219
452316
  aiCommand.command("jump-cut").description("Remove filler words (um, uh, like, etc.) from video using Whisper word-level timestamps").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-jumpcut.<ext>)").option("--fillers <words>", "Comma-separated filler words to detect").option("--padding <seconds>", "Padding around cuts in seconds (default: 0.05)", "0.05").option("-l, --language <lang>", "Language code for transcription (e.g., en, ko)").option("--analyze-only", "Only detect fillers, don't cut").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
452317
+ const startedAt = Date.now();
452220
452318
  try {
452221
452319
  if (options.output) {
452222
452320
  validateOutputPath(options.output);
@@ -452231,15 +452329,18 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452231
452329
  }
452232
452330
  if (options.dryRun) {
452233
452331
  const fillers2 = options.fillers ? options.fillers.split(",").map((f) => f.trim()) : void 0;
452234
- outputResult({
452235
- dryRun: true,
452332
+ outputSuccess({
452236
452333
  command: "edit jump-cut",
452237
- params: {
452238
- videoPath: absVideoPath,
452239
- fillers: fillers2,
452240
- padding: parseFloat(options.padding),
452241
- language: options.language,
452242
- analyzeOnly: options.analyzeOnly || false
452334
+ startedAt,
452335
+ dryRun: true,
452336
+ data: {
452337
+ params: {
452338
+ videoPath: absVideoPath,
452339
+ fillers: fillers2,
452340
+ padding: parseFloat(options.padding),
452341
+ language: options.language,
452342
+ analyzeOnly: options.analyzeOnly || false
452343
+ }
452243
452344
  }
452244
452345
  });
452245
452346
  return;
@@ -452268,13 +452369,16 @@ Requires: OPENAI_API_KEY (Whisper transcription) + FFmpeg`).action(async (videoP
452268
452369
  }
452269
452370
  spinner2.succeed(source_default.green("Filler detection complete"));
452270
452371
  if (isJsonMode()) {
452271
- outputResult({
452272
- success: true,
452273
- totalDuration: result.totalDuration,
452274
- fillerCount: result.fillerCount,
452275
- fillerDuration: result.fillerDuration,
452276
- fillers: result.fillers,
452277
- outputPath: result.outputPath
452372
+ outputSuccess({
452373
+ command: "edit jump-cut",
452374
+ startedAt,
452375
+ data: {
452376
+ totalDuration: result.totalDuration,
452377
+ fillerCount: result.fillerCount,
452378
+ fillerDuration: result.fillerDuration,
452379
+ fillers: result.fillers,
452380
+ outputPath: result.outputPath
452381
+ }
452278
452382
  });
452279
452383
  return;
452280
452384
  }
@@ -453419,6 +453523,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453419
453523
  registerEditCommands(editCommand);
453420
453524
  registerFillGapsCommand(editCommand);
453421
453525
  editCommand.command("grade").description("Apply AI-generated color grading (Claude + FFmpeg)").argument("<video>", "Video file path").option("-s, --style <prompt>", "Style description (e.g., 'cinematic warm')").option("--preset <name>", "Built-in preset: film-noir, vintage, cinematic-warm, cool-tones, high-contrast, pastel, cyberpunk, horror").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show filter without applying").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
453526
+ const startedAt = Date.now();
453422
453527
  try {
453423
453528
  if (options.style) rejectControlChars(options.style);
453424
453529
  if (options.output) {
@@ -453434,13 +453539,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453434
453539
  exitWithError(notFoundError("FFmpeg not found. Install with: brew install ffmpeg"));
453435
453540
  }
453436
453541
  if (options.dryRun) {
453437
- outputResult({
453438
- dryRun: true,
453542
+ outputSuccess({
453439
453543
  command: "edit grade",
453440
- params: {
453441
- videoPath: resolve29(process.cwd(), videoPath),
453442
- style: options.style || options.preset,
453443
- analyzeOnly: options.analyzeOnly || false
453544
+ startedAt,
453545
+ dryRun: true,
453546
+ data: {
453547
+ params: {
453548
+ videoPath: resolve29(process.cwd(), videoPath),
453549
+ style: options.style || options.preset,
453550
+ analyzeOnly: options.analyzeOnly || false
453551
+ }
453444
453552
  }
453445
453553
  });
453446
453554
  return;
@@ -453466,12 +453574,15 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453466
453574
  if (isJsonMode()) {
453467
453575
  const absPath2 = resolve29(process.cwd(), videoPath);
453468
453576
  const gradeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath2.replace(/(\.[^.]+)$/, "-graded$1");
453469
- outputResult({
453470
- success: true,
453471
- style: options.preset || options.style,
453472
- description: gradeResult.description,
453473
- ffmpegFilter: gradeResult.ffmpegFilter,
453474
- outputPath: options.analyzeOnly ? void 0 : gradeOutputPath
453577
+ outputSuccess({
453578
+ command: "edit grade",
453579
+ startedAt,
453580
+ data: {
453581
+ style: options.preset || options.style,
453582
+ description: gradeResult.description,
453583
+ ffmpegFilter: gradeResult.ffmpegFilter,
453584
+ outputPath: options.analyzeOnly ? void 0 : gradeOutputPath
453585
+ }
453475
453586
  });
453476
453587
  return;
453477
453588
  }
@@ -453500,6 +453611,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453500
453611
  }
453501
453612
  });
453502
453613
  editCommand.command("text-overlay").description("Apply text overlays to video (FFmpeg drawtext)").argument("<video>", "Video file path").option("-t, --text <texts...>", "Text lines to overlay (repeat for multiple)").option("-s, --style <style>", "Overlay style: lower-third, center-bold, subtitle, minimal", "lower-third").option("--font-size <size>", "Font size in pixels (auto-calculated if omitted)").option("--font-color <color>", "Font color (default: white)", "white").option("--fade <seconds>", "Fade in/out duration in seconds", "0.3").option("--start <seconds>", "Start time in seconds", "0").option("--end <seconds>", "End time in seconds (default: video duration)").option("-o, --output <path>", "Output video file path").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
453614
+ const startedAt = Date.now();
453503
453615
  try {
453504
453616
  if (!options.text || options.text.length === 0) {
453505
453617
  exitWithError(usageError("At least one --text option is required", 'Example: vibe edit text-overlay video.mp4 -t "NEXUS AI" --style center-bold'));
@@ -453512,18 +453624,21 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453512
453624
  exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
453513
453625
  }
453514
453626
  if (options.dryRun) {
453515
- outputResult({
453516
- dryRun: true,
453627
+ outputSuccess({
453517
453628
  command: "edit text-overlay",
453518
- params: {
453519
- videoPath: resolve29(process.cwd(), videoPath),
453520
- texts: options.text,
453521
- style: options.style,
453522
- fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
453523
- fontColor: options.fontColor,
453524
- fade: parseFloat(options.fade),
453525
- start: parseFloat(options.start),
453526
- end: options.end ? parseFloat(options.end) : void 0
453629
+ startedAt,
453630
+ dryRun: true,
453631
+ data: {
453632
+ params: {
453633
+ videoPath: resolve29(process.cwd(), videoPath),
453634
+ texts: options.text,
453635
+ style: options.style,
453636
+ fontSize: options.fontSize ? parseInt(options.fontSize) : void 0,
453637
+ fontColor: options.fontColor,
453638
+ fade: parseFloat(options.fade),
453639
+ start: parseFloat(options.start),
453640
+ end: options.end ? parseFloat(options.end) : void 0
453641
+ }
453527
453642
  }
453528
453643
  });
453529
453644
  return;
@@ -453548,11 +453663,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453548
453663
  }
453549
453664
  spinner2.succeed(source_default.green("Text overlays applied"));
453550
453665
  if (isJsonMode()) {
453551
- outputResult({
453552
- success: true,
453553
- style: options.style,
453554
- texts: options.text,
453555
- outputPath: result.outputPath
453666
+ outputSuccess({
453667
+ command: "edit text-overlay",
453668
+ startedAt,
453669
+ data: {
453670
+ style: options.style,
453671
+ texts: options.text,
453672
+ outputPath: result.outputPath
453673
+ }
453556
453674
  });
453557
453675
  return;
453558
453676
  }
@@ -453569,6 +453687,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453569
453687
  }
453570
453688
  });
453571
453689
  editCommand.command("speed-ramp").description("Apply content-aware speed ramping (Whisper + Claude + FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output video file path").option("-s, --style <style>", "Style: dramatic, smooth, action", "dramatic").option("--min-speed <factor>", "Minimum speed factor", "0.25").option("--max-speed <factor>", "Maximum speed factor", "4.0").option("--analyze-only", "Show keyframes without applying").option("-l, --language <lang>", "Language code for transcription").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
453690
+ const startedAt = Date.now();
453572
453691
  try {
453573
453692
  if (options.output) {
453574
453693
  validateOutputPath(options.output);
@@ -453577,15 +453696,18 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453577
453696
  exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
453578
453697
  }
453579
453698
  if (options.dryRun) {
453580
- outputResult({
453581
- dryRun: true,
453699
+ outputSuccess({
453582
453700
  command: "edit speed-ramp",
453583
- params: {
453584
- videoPath: resolve29(process.cwd(), videoPath),
453585
- style: options.style,
453586
- minSpeed: parseFloat(options.minSpeed),
453587
- maxSpeed: parseFloat(options.maxSpeed),
453588
- analyzeOnly: options.analyzeOnly || false
453701
+ startedAt,
453702
+ dryRun: true,
453703
+ data: {
453704
+ params: {
453705
+ videoPath: resolve29(process.cwd(), videoPath),
453706
+ style: options.style,
453707
+ minSpeed: parseFloat(options.minSpeed),
453708
+ maxSpeed: parseFloat(options.maxSpeed),
453709
+ analyzeOnly: options.analyzeOnly || false
453710
+ }
453589
453711
  }
453590
453712
  });
453591
453713
  return;
@@ -453638,11 +453760,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453638
453760
  if (isJsonMode()) {
453639
453761
  const avgSpeed2 = speedResult.keyframes.reduce((sum, kf) => sum + kf.speed, 0) / speedResult.keyframes.length;
453640
453762
  const speedRampOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, "-ramped$1");
453641
- outputResult({
453642
- success: true,
453643
- keyframes: speedResult.keyframes,
453644
- avgSpeed: avgSpeed2,
453645
- outputPath: options.analyzeOnly ? void 0 : speedRampOutputPath
453763
+ outputSuccess({
453764
+ command: "edit speed-ramp",
453765
+ startedAt,
453766
+ data: {
453767
+ keyframes: speedResult.keyframes,
453768
+ avgSpeed: avgSpeed2,
453769
+ outputPath: options.analyzeOnly ? void 0 : speedRampOutputPath
453770
+ }
453646
453771
  });
453647
453772
  return;
453648
453773
  }
@@ -453682,6 +453807,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453682
453807
  }
453683
453808
  });
453684
453809
  editCommand.command("reframe").description("Auto-reframe video to different aspect ratio (Claude Vision + FFmpeg)").argument("<video>", "Video file path").option("-a, --aspect <ratio>", "Target aspect ratio: 9:16, 1:1, 4:5", "9:16").option("-f, --focus <mode>", "Focus mode: auto, face, center, action", "auto").option("-o, --output <path>", "Output video file path").option("--analyze-only", "Show crop regions without applying").option("--keyframes <path>", "Export keyframes to JSON file").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
453810
+ const startedAt = Date.now();
453685
453811
  try {
453686
453812
  if (options.output) {
453687
453813
  validateOutputPath(options.output);
@@ -453690,14 +453816,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453690
453816
  exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS) or apt install ffmpeg (Linux)"));
453691
453817
  }
453692
453818
  if (options.dryRun) {
453693
- outputResult({
453694
- dryRun: true,
453819
+ outputSuccess({
453695
453820
  command: "edit reframe",
453696
- params: {
453697
- videoPath: resolve29(process.cwd(), videoPath),
453698
- aspect: options.aspect,
453699
- focus: options.focus,
453700
- analyzeOnly: options.analyzeOnly || false
453821
+ startedAt,
453822
+ dryRun: true,
453823
+ data: {
453824
+ params: {
453825
+ videoPath: resolve29(process.cwd(), videoPath),
453826
+ aspect: options.aspect,
453827
+ focus: options.focus,
453828
+ analyzeOnly: options.analyzeOnly || false
453829
+ }
453701
453830
  }
453702
453831
  });
453703
453832
  return;
@@ -453765,13 +453894,16 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453765
453894
  spinner2.succeed(source_default.green(`Analyzed ${cropKeyframes.length} keyframes`));
453766
453895
  if (isJsonMode()) {
453767
453896
  const reframeOutputPath = options.output ? resolve29(process.cwd(), options.output) : absPath.replace(/(\.[^.]+)$/, `-${options.aspect.replace(":", "x")}$1`);
453768
- outputResult({
453769
- success: true,
453770
- sourceWidth,
453771
- sourceHeight,
453772
- aspect: options.aspect,
453773
- cropKeyframes,
453774
- outputPath: options.analyzeOnly ? void 0 : reframeOutputPath
453897
+ outputSuccess({
453898
+ command: "edit reframe",
453899
+ startedAt,
453900
+ data: {
453901
+ sourceWidth,
453902
+ sourceHeight,
453903
+ aspect: options.aspect,
453904
+ cropKeyframes,
453905
+ outputPath: options.analyzeOnly ? void 0 : reframeOutputPath
453906
+ }
453775
453907
  });
453776
453908
  return;
453777
453909
  }
@@ -453822,6 +453954,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453822
453954
  }
453823
453955
  });
453824
453956
  editCommand.command("image").description("Edit image(s) using AI (Gemini/OpenAI/Grok)").argument("<images...>", "Input image file(s) followed by edit prompt").option("-p, --provider <provider>", "Provider: gemini (default), openai, grok", "gemini").option("-k, --api-key <key>", "API key (or set env variable)").option("-o, --output <path>", "Output file path", "edited.png").option("-m, --model <model>", "Model: flash/3.1-flash/latest/pro (Gemini only)", "flash").option("-r, --ratio <ratio>", "Output aspect ratio").option("-s, --size <resolution>", "Resolution: 1K, 2K, 4K (Gemini Pro only)").option("--dry-run", "Preview parameters without executing").action(async (args, options) => {
453957
+ const startedAt = Date.now();
453825
453958
  try {
453826
453959
  if (args.length < 2) {
453827
453960
  exitWithError(usageError("Need at least one image and a prompt"));
@@ -453837,16 +453970,19 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453837
453970
  exitWithError(usageError("Grok supports only 1 input image for editing.", "Use -p gemini (up to 14 images) or -p openai (up to 16 images) for multi-image editing."));
453838
453971
  }
453839
453972
  if (options.dryRun) {
453840
- outputResult({
453841
- dryRun: true,
453973
+ outputSuccess({
453842
453974
  command: "edit image",
453843
- params: {
453844
- imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
453845
- prompt: prompt3,
453846
- provider,
453847
- model: options.model,
453848
- ratio: options.ratio,
453849
- size: options.size
453975
+ startedAt,
453976
+ dryRun: true,
453977
+ data: {
453978
+ params: {
453979
+ imagePaths: imagePaths.map((p) => resolve29(process.cwd(), p)),
453980
+ prompt: prompt3,
453981
+ provider,
453982
+ model: options.model,
453983
+ ratio: options.ratio,
453984
+ size: options.size
453985
+ }
453850
453986
  }
453851
453987
  });
453852
453988
  return;
@@ -453924,11 +454060,14 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453924
454060
  };
453925
454061
  const resultModel = result.model;
453926
454062
  if (isJsonMode()) {
453927
- outputResult({
453928
- success: true,
453929
- provider,
453930
- model: resultModel || options.model,
453931
- outputPath
454063
+ outputSuccess({
454064
+ command: "edit image",
454065
+ startedAt,
454066
+ data: {
454067
+ provider,
454068
+ model: resultModel || options.model,
454069
+ outputPath
454070
+ }
453932
454071
  });
453933
454072
  await saveImage();
453934
454073
  return;
@@ -453944,6 +454083,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453944
454083
  }
453945
454084
  });
453946
454085
  editCommand.command("interpolate").description("Create slow motion with frame interpolation (FFmpeg)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-f, --factor <number>", "Slow motion factor: 2, 4, or 8", "2").option("--fps <number>", "Target output FPS").option("--quality <mode>", "Quality: fast or quality", "quality").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
454086
+ const startedAt = Date.now();
453947
454087
  try {
453948
454088
  if (options.output) {
453949
454089
  validateOutputPath(options.output);
@@ -453954,14 +454094,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453954
454094
  exitWithError(usageError("Factor must be 2, 4, or 8"));
453955
454095
  }
453956
454096
  if (options.dryRun) {
453957
- outputResult({
453958
- dryRun: true,
454097
+ outputSuccess({
453959
454098
  command: "edit interpolate",
453960
- params: {
453961
- videoPath: absPath,
453962
- factor,
453963
- fps: options.fps ? parseInt(options.fps) : void 0,
453964
- quality: options.quality
454099
+ startedAt,
454100
+ dryRun: true,
454101
+ data: {
454102
+ params: {
454103
+ videoPath: absPath,
454104
+ factor,
454105
+ fps: options.fps ? parseInt(options.fps) : void 0,
454106
+ quality: options.quality
454107
+ }
453965
454108
  }
453966
454109
  });
453967
454110
  return;
@@ -453988,12 +454131,15 @@ Run 'vibe schema edit.<command>' for structured parameter info.
453988
454131
  await execSafe("ffmpeg", ["-i", absPath, "-filter:v", `minterpolate='${mi}:fps=${targetFps}',setpts=${factor}*PTS`, "-an", outputPath, "-y"], { timeout: 6e5 });
453989
454132
  spinner2.succeed(source_default.green(`Created ${factor}x slow motion`));
453990
454133
  if (isJsonMode()) {
453991
- outputResult({
453992
- success: true,
453993
- originalFps,
453994
- targetFps,
453995
- factor,
453996
- outputPath
454134
+ outputSuccess({
454135
+ command: "edit interpolate",
454136
+ startedAt,
454137
+ data: {
454138
+ originalFps,
454139
+ targetFps,
454140
+ factor,
454141
+ outputPath
454142
+ }
453997
454143
  });
453998
454144
  return;
453999
454145
  }
@@ -454019,6 +454165,7 @@ Run 'vibe schema edit.<command>' for structured parameter info.
454019
454165
  }
454020
454166
  });
454021
454167
  editCommand.command("upscale-video").description("Upscale video resolution using AI or FFmpeg").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path").option("-s, --scale <factor>", "Scale factor: 2 or 4", "2").option("-m, --model <model>", "Model: real-esrgan, topaz", "real-esrgan").option("--ffmpeg", "Use FFmpeg lanczos (free, no API)").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").option("--no-wait", "Start processing and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
454168
+ const startedAt = Date.now();
454022
454169
  try {
454023
454170
  if (options.output) {
454024
454171
  validateOutputPath(options.output);
@@ -454029,14 +454176,17 @@ Run 'vibe schema edit.<command>' for structured parameter info.
454029
454176
  exitWithError(usageError("Scale must be 2 or 4"));
454030
454177
  }
454031
454178
  if (options.dryRun) {
454032
- outputResult({
454033
- dryRun: true,
454179
+ outputSuccess({
454034
454180
  command: "edit upscale-video",
454035
- params: {
454036
- videoPath: absPath,
454037
- scale,
454038
- model: options.model,
454039
- ffmpeg: options.ffmpeg || false
454181
+ startedAt,
454182
+ dryRun: true,
454183
+ data: {
454184
+ params: {
454185
+ videoPath: absPath,
454186
+ scale,
454187
+ model: options.model,
454188
+ ffmpeg: options.ffmpeg || false
454189
+ }
454040
454190
  }
454041
454191
  });
454042
454192
  return;
@@ -454062,10 +454212,13 @@ Run 'vibe schema edit.<command>' for structured parameter info.
454062
454212
  await execSafe("ffmpeg", ["-i", absPath, "-vf", `scale=${newWidth}:${newHeight}:flags=lanczos`, "-c:a", "copy", outputPath, "-y"]);
454063
454213
  spinner3.succeed(source_default.green(`Upscaled to ${newWidth}x${newHeight}`));
454064
454214
  if (isJsonMode()) {
454065
- outputResult({
454066
- success: true,
454067
- dimensions: `${newWidth}x${newHeight}`,
454068
- outputPath
454215
+ outputSuccess({
454216
+ command: "edit upscale-video",
454217
+ startedAt,
454218
+ data: {
454219
+ dimensions: `${newWidth}x${newHeight}`,
454220
+ outputPath
454221
+ }
454069
454222
  });
454070
454223
  return;
454071
454224
  }
@@ -454770,21 +454923,25 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
454770
454923
  }
454771
454924
  function registerReviewCommand(aiCommand) {
454772
454925
  aiCommand.command("review").description("Review video quality using Gemini AI and optionally auto-fix issues").argument("<video>", "Video file path").option("-s, --storyboard <path>", "Storyboard JSON file for context").option("--auto-apply", "Automatically apply fixable corrections").option("--verify", "Run verification pass after applying fixes").option("-m, --model <model>", "Gemini model: flash (default), flash-2.5, pro", "flash").option("-o, --output <path>", "Output video file path (for auto-apply)").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
454926
+ const startedAt = Date.now();
454773
454927
  try {
454774
454928
  if (options.output) {
454775
454929
  validateOutputPath(options.output);
454776
454930
  }
454777
454931
  if (options.dryRun) {
454778
- outputResult({
454779
- dryRun: true,
454932
+ outputSuccess({
454780
454933
  command: "ai review",
454781
- params: {
454782
- videoPath,
454783
- storyboard: options.storyboard,
454784
- autoApply: options.autoApply ?? false,
454785
- verify: options.verify ?? false,
454786
- model: options.model,
454787
- output: options.output
454934
+ startedAt,
454935
+ dryRun: true,
454936
+ data: {
454937
+ params: {
454938
+ videoPath,
454939
+ storyboard: options.storyboard,
454940
+ autoApply: options.autoApply ?? false,
454941
+ verify: options.verify ?? false,
454942
+ model: options.model,
454943
+ output: options.output
454944
+ }
454788
454945
  }
454789
454946
  });
454790
454947
  return;
@@ -455052,27 +455209,31 @@ Use this image analysis to inform the color palette, typography placement, and o
455052
455209
  }
455053
455210
  function registerMotionCommand(aiCommand) {
455054
455211
  aiCommand.command("motion").description("Generate motion graphics using Claude + Remotion (render & composite)").argument("<description>", "Natural language description of the motion graphic").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output file path", "motion.tsx").option("-d, --duration <sec>", "Duration in seconds", "5").option("-w, --width <px>", "Width in pixels", "1920").option("-h, --height <px>", "Height in pixels", "1080").option("--fps <fps>", "Frame rate", "30").option("-s, --style <style>", "Style preset: minimal, corporate, playful, cinematic").option("--render", "Render the generated code with Remotion (output .webm)").option("--video <path>", "Base video to composite the motion graphic onto").option("--image <path>", "Image to analyze with Gemini \u2014 color/mood fed into Claude prompt").option("--from-tsx <path>", "Refine an existing TSX file instead of generating from scratch").option("-m, --model <alias>", "LLM model: sonnet (default), opus, gemini, gemini-3.1-pro", "sonnet").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
455212
+ const startedAt = Date.now();
455055
455213
  try {
455056
455214
  if (options.output) {
455057
455215
  validateOutputPath(options.output);
455058
455216
  }
455059
455217
  if (options.dryRun) {
455060
- outputResult({
455218
+ outputSuccess({
455219
+ command: "generate motion",
455220
+ startedAt,
455061
455221
  dryRun: true,
455062
- command: "ai motion",
455063
- params: {
455064
- description: description.slice(0, 200),
455065
- duration: options.duration,
455066
- width: options.width,
455067
- height: options.height,
455068
- fps: options.fps,
455069
- style: options.style,
455070
- render: options.render ?? false,
455071
- video: options.video,
455072
- image: options.image,
455073
- fromTsx: options.fromTsx,
455074
- model: options.model,
455075
- output: options.output
455222
+ data: {
455223
+ params: {
455224
+ description: description.slice(0, 200),
455225
+ duration: options.duration,
455226
+ width: options.width,
455227
+ height: options.height,
455228
+ fps: options.fps,
455229
+ style: options.style,
455230
+ render: options.render ?? false,
455231
+ video: options.video,
455232
+ image: options.image,
455233
+ fromTsx: options.fromTsx,
455234
+ model: options.model,
455235
+ output: options.output
455236
+ }
455076
455237
  }
455077
455238
  });
455078
455239
  return;
@@ -455186,20 +455347,24 @@ async function executeSoundEffect(options) {
455186
455347
  }
455187
455348
  function registerSoundEffectCommand(parent) {
455188
455349
  parent.command("sound-effect").description("Generate sound effect using ElevenLabs").argument("<prompt>", "Description of the sound effect").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "sound-effect.mp3").option("-d, --duration <seconds>", "Duration in seconds (0.5-22, default: auto)").option("--prompt-influence <value>", "Prompt influence (0-1, default: 0.3)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
455350
+ const startedAt = Date.now();
455189
455351
  try {
455190
455352
  rejectControlChars(prompt3);
455191
455353
  if (options.output) {
455192
455354
  validateOutputPath(options.output);
455193
455355
  }
455194
455356
  if (options.dryRun) {
455195
- outputResult({
455196
- dryRun: true,
455357
+ outputSuccess({
455197
455358
  command: "generate sound-effect",
455198
- params: {
455199
- prompt: prompt3,
455200
- duration: options.duration,
455201
- promptInfluence: options.promptInfluence,
455202
- output: options.output
455359
+ startedAt,
455360
+ dryRun: true,
455361
+ data: {
455362
+ params: {
455363
+ prompt: prompt3,
455364
+ duration: options.duration,
455365
+ promptInfluence: options.promptInfluence,
455366
+ output: options.output
455367
+ }
455203
455368
  }
455204
455369
  });
455205
455370
  return;
@@ -455226,7 +455391,11 @@ function registerSoundEffectCommand(parent) {
455226
455391
  await writeFile22(outputPath, result.audioBuffer);
455227
455392
  spinner2.succeed(source_default.green("Sound effect generated"));
455228
455393
  if (isJsonMode()) {
455229
- outputResult({ success: true, outputPath });
455394
+ outputSuccess({
455395
+ command: "generate sound-effect",
455396
+ startedAt,
455397
+ data: { outputPath }
455398
+ });
455230
455399
  return;
455231
455400
  }
455232
455401
  console.log(source_default.green(`Saved to: ${outputPath}`));
@@ -455276,6 +455445,7 @@ async function executeMusicStatus(options) {
455276
455445
  }
455277
455446
  function registerMusicStatusCommand(parent) {
455278
455447
  parent.command("music-status", { hidden: true }).description("Check music generation status").argument("<task-id>", "Task ID from music generation").option("-k, --api-key <key>", "Replicate API token (or set REPLICATE_API_TOKEN env)").action(async (taskId, options) => {
455448
+ const startedAt = Date.now();
455279
455449
  try {
455280
455450
  const apiKey = await requireApiKey(
455281
455451
  "REPLICATE_API_TOKEN",
@@ -455287,12 +455457,15 @@ function registerMusicStatusCommand(parent) {
455287
455457
  const result = await replicate.getMusicStatus(taskId);
455288
455458
  if (isJsonMode()) {
455289
455459
  const status = result.audioUrl ? "completed" : result.error ? "failed" : "processing";
455290
- outputResult({
455291
- success: true,
455292
- taskId,
455293
- status,
455294
- audioUrl: result.audioUrl,
455295
- error: result.error
455460
+ outputSuccess({
455461
+ command: "generate music-status",
455462
+ startedAt,
455463
+ data: {
455464
+ taskId,
455465
+ status,
455466
+ audioUrl: result.audioUrl,
455467
+ error: result.error
455468
+ }
455296
455469
  });
455297
455470
  return;
455298
455471
  }
@@ -455330,6 +455503,7 @@ var init_music_status = __esm({
455330
455503
  // ../cli/src/commands/generate/video-cancel.ts
455331
455504
  function registerVideoCancelCommand(parent) {
455332
455505
  parent.command("video-cancel", { hidden: true }).description("Cancel video generation (Grok or Runway)").argument("<task-id>", "Task ID to cancel").option("-p, --provider <provider>", "Provider: grok, runway", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET env)").action(async (taskId, options) => {
455506
+ const startedAt = Date.now();
455333
455507
  try {
455334
455508
  const provider = (options.provider || "grok").toLowerCase();
455335
455509
  let success = false;
@@ -455342,7 +455516,11 @@ function registerVideoCancelCommand(parent) {
455342
455516
  if (success) {
455343
455517
  spinner2.succeed(source_default.green("Generation cancelled"));
455344
455518
  if (isJsonMode()) {
455345
- outputResult({ success: true, taskId, provider: "grok", cancelled: true });
455519
+ outputSuccess({
455520
+ command: "generate video-cancel",
455521
+ startedAt,
455522
+ data: { taskId, provider: "grok", cancelled: true }
455523
+ });
455346
455524
  return;
455347
455525
  }
455348
455526
  } else {
@@ -455362,7 +455540,11 @@ function registerVideoCancelCommand(parent) {
455362
455540
  if (success) {
455363
455541
  spinner2.succeed(source_default.green("Generation cancelled"));
455364
455542
  if (isJsonMode()) {
455365
- outputResult({ success: true, taskId, provider: "runway", cancelled: true });
455543
+ outputSuccess({
455544
+ command: "generate video-cancel",
455545
+ startedAt,
455546
+ data: { taskId, provider: "runway", cancelled: true }
455547
+ });
455366
455548
  return;
455367
455549
  }
455368
455550
  } else {
@@ -455435,16 +455617,18 @@ async function executeBackground(options) {
455435
455617
  }
455436
455618
  function registerBackgroundCommand(parent) {
455437
455619
  parent.command("background").description("Generate video background using DALL-E").argument("<description>", "Background description").option("-k, --api-key <key>", "OpenAI API key (or set OPENAI_API_KEY env)").option("-o, --output <path>", "Output file path (downloads image)").option("-a, --aspect <ratio>", "Aspect ratio: 16:9, 9:16, 1:1", "16:9").option("--dry-run", "Preview parameters without executing").action(async (description, options) => {
455620
+ const startedAt = Date.now();
455438
455621
  try {
455439
455622
  rejectControlChars(description);
455440
455623
  if (options.output) {
455441
455624
  validateOutputPath(options.output);
455442
455625
  }
455443
455626
  if (options.dryRun) {
455444
- outputResult({
455445
- dryRun: true,
455627
+ outputSuccess({
455446
455628
  command: "generate background",
455447
- params: { description, aspect: options.aspect, output: options.output }
455629
+ startedAt,
455630
+ dryRun: true,
455631
+ data: { params: { description, aspect: options.aspect, output: options.output } }
455448
455632
  });
455449
455633
  return;
455450
455634
  }
@@ -455479,7 +455663,11 @@ function registerBackgroundCommand(parent) {
455479
455663
  await mkdir18(dirname25(outputPath), { recursive: true });
455480
455664
  await writeFile23(outputPath, buffer);
455481
455665
  }
455482
- outputResult({ success: true, imageUrl: img.url, outputPath });
455666
+ outputSuccess({
455667
+ command: "generate background",
455668
+ startedAt,
455669
+ data: { imageUrl: img.url, outputPath }
455670
+ });
455483
455671
  return;
455484
455672
  }
455485
455673
  console.log();
@@ -455576,6 +455764,7 @@ async function executeStoryboard(options) {
455576
455764
  }
455577
455765
  function registerStoryboardCommand(parent) {
455578
455766
  parent.command("storyboard").description("Generate video storyboard from content using Claude").argument("<content>", "Content to analyze (text or file path)").option("-k, --api-key <key>", "Anthropic API key (or set ANTHROPIC_API_KEY env)").option("-o, --output <path>", "Output JSON file path").option("-d, --duration <sec>", "Target total duration in seconds").option("-f, --file", "Treat content argument as file path").option("-c, --creativity <level>", "Creativity level: low (default, consistent) or high (varied, unexpected)", "low").option("--dry-run", "Preview parameters without executing").action(async (content, options) => {
455767
+ const startedAt = Date.now();
455579
455768
  try {
455580
455769
  rejectControlChars(content);
455581
455770
  if (options.output) {
@@ -455591,13 +455780,16 @@ function registerStoryboardCommand(parent) {
455591
455780
  textContent2 = await readFile23(filePath, "utf-8");
455592
455781
  }
455593
455782
  if (options.dryRun) {
455594
- outputResult({
455595
- dryRun: true,
455783
+ outputSuccess({
455596
455784
  command: "generate storyboard",
455597
- params: {
455598
- content: textContent2.substring(0, 200),
455599
- duration: options.duration,
455600
- creativity
455785
+ startedAt,
455786
+ dryRun: true,
455787
+ data: {
455788
+ params: {
455789
+ content: textContent2.substring(0, 200),
455790
+ duration: options.duration,
455791
+ creativity
455792
+ }
455601
455793
  }
455602
455794
  });
455603
455795
  return;
@@ -455629,16 +455821,23 @@ function registerStoryboardCommand(parent) {
455629
455821
  const outputPath = resolve39(process.cwd(), options.output);
455630
455822
  await writeFile24(outputPath, JSON.stringify(segments, null, 2), "utf-8");
455631
455823
  if (isJsonMode()) {
455632
- outputResult({
455633
- success: true,
455634
- segmentCount: segments.length,
455635
- segments,
455636
- outputPath
455824
+ outputSuccess({
455825
+ command: "generate storyboard",
455826
+ startedAt,
455827
+ data: {
455828
+ segmentCount: segments.length,
455829
+ segments,
455830
+ outputPath
455831
+ }
455637
455832
  });
455638
455833
  return;
455639
455834
  }
455640
455835
  } else if (isJsonMode()) {
455641
- outputResult({ success: true, segmentCount: segments.length, segments });
455836
+ outputSuccess({
455837
+ command: "generate storyboard",
455838
+ startedAt,
455839
+ data: { segmentCount: segments.length, segments }
455840
+ });
455642
455841
  return;
455643
455842
  }
455644
455843
  console.log();
@@ -455770,6 +455969,7 @@ async function executeSpeech(options) {
455770
455969
  }
455771
455970
  function registerSpeechCommand(parent) {
455772
455971
  parent.command("speech").alias("tts").description("Generate speech from text using ElevenLabs").argument("[text]", "Text to convert to speech (interactive if omitted)").option("-k, --api-key <key>", "ElevenLabs API key (or set ELEVENLABS_API_KEY env)").option("-o, --output <path>", "Output audio file path", "output.mp3").option("-v, --voice <id>", "Voice ID (default: Rachel)", "21m00Tcm4TlvDq8ikWAM").option("--list-voices", "List available voices").option("--fit-duration <seconds>", "Speed up audio to fit target duration (via FFmpeg atempo)", parseFloat).option("--dry-run", "Preview parameters without executing").action(async (text, options) => {
455972
+ const startedAt = Date.now();
455773
455973
  try {
455774
455974
  if (!text) {
455775
455975
  if (hasTTY()) {
@@ -455791,10 +455991,11 @@ function registerSpeechCommand(parent) {
455791
455991
  validateOutputPath(options.output);
455792
455992
  }
455793
455993
  if (options.dryRun) {
455794
- outputResult({
455795
- dryRun: true,
455994
+ outputSuccess({
455796
455995
  command: "generate speech",
455797
- params: { text, voice: options.voice, output: options.output }
455996
+ startedAt,
455997
+ dryRun: true,
455998
+ data: { params: { text, voice: options.voice, output: options.output } }
455798
455999
  });
455799
456000
  return;
455800
456001
  }
@@ -455883,10 +456084,13 @@ function registerSpeechCommand(parent) {
455883
456084
  }
455884
456085
  }
455885
456086
  if (isJsonMode()) {
455886
- outputResult({
455887
- success: true,
455888
- characterCount: result.characterCount,
455889
- outputPath
456087
+ outputSuccess({
456088
+ command: "generate speech",
456089
+ startedAt,
456090
+ data: {
456091
+ characterCount: result.characterCount,
456092
+ outputPath
456093
+ }
455890
456094
  });
455891
456095
  return;
455892
456096
  }
@@ -455975,6 +456179,7 @@ async function executeMusic(options) {
455975
456179
  }
455976
456180
  function registerMusicCommand(parent) {
455977
456181
  parent.command("music").description("Generate background music from a text prompt (ElevenLabs or Replicate MusicGen)").argument("<prompt>", "Description of the music to generate").option("-p, --provider <provider>", "Provider: elevenlabs (default, up to 10min), replicate (MusicGen, max 30s)", "elevenlabs").option("-k, --api-key <key>", "API key (or set ELEVENLABS_API_KEY / REPLICATE_API_TOKEN env)").option("-d, --duration <seconds>", "Duration in seconds (elevenlabs: 3-600, replicate: 1-30)", "8").option("--instrumental", "Force instrumental music, no vocals (ElevenLabs only)").option("-m, --melody <file>", "Reference melody audio file for conditioning (Replicate only)").option("--model <model>", "Model variant (Replicate only): large, stereo-large, melody-large, stereo-melody-large", "stereo-large").option("-o, --output <path>", "Output audio file path", "music.mp3").option("--no-wait", "Don't wait for generation to complete (Replicate async mode)").option("--dry-run", "Preview parameters without executing").action(async (prompt3, options) => {
456182
+ const startedAt = Date.now();
455978
456183
  try {
455979
456184
  rejectControlChars(prompt3);
455980
456185
  if (options.output) {
@@ -455982,16 +456187,19 @@ function registerMusicCommand(parent) {
455982
456187
  }
455983
456188
  const provider = (options.provider || "elevenlabs").toLowerCase();
455984
456189
  if (options.dryRun) {
455985
- outputResult({
455986
- dryRun: true,
456190
+ outputSuccess({
455987
456191
  command: "generate music",
455988
- params: {
455989
- prompt: prompt3,
455990
- provider,
455991
- duration: options.duration,
455992
- model: options.model,
455993
- output: options.output,
455994
- instrumental: options.instrumental
456192
+ startedAt,
456193
+ dryRun: true,
456194
+ data: {
456195
+ params: {
456196
+ prompt: prompt3,
456197
+ provider,
456198
+ duration: options.duration,
456199
+ model: options.model,
456200
+ output: options.output,
456201
+ instrumental: options.instrumental
456202
+ }
455995
456203
  }
455996
456204
  });
455997
456205
  return;
@@ -456018,11 +456226,14 @@ function registerMusicCommand(parent) {
456018
456226
  await writeFile26(outputPath, result.audioBuffer);
456019
456227
  spinner2.succeed(source_default.green("Music generated successfully"));
456020
456228
  if (isJsonMode()) {
456021
- outputResult({
456022
- success: true,
456023
- provider: "elevenlabs",
456024
- outputPath,
456025
- duration
456229
+ outputSuccess({
456230
+ command: "generate music",
456231
+ startedAt,
456232
+ data: {
456233
+ provider: "elevenlabs",
456234
+ outputPath,
456235
+ duration
456236
+ }
456026
456237
  });
456027
456238
  return;
456028
456239
  }
@@ -456090,12 +456301,15 @@ function registerMusicCommand(parent) {
456090
456301
  await writeFile26(outputPath, audioBuffer);
456091
456302
  spinner2.succeed(source_default.green("Music generated successfully"));
456092
456303
  if (isJsonMode()) {
456093
- outputResult({
456094
- success: true,
456095
- provider: "replicate",
456096
- taskId: result.taskId,
456097
- audioUrl: finalResult.audioUrl,
456098
- outputPath
456304
+ outputSuccess({
456305
+ command: "generate music",
456306
+ startedAt,
456307
+ data: {
456308
+ provider: "replicate",
456309
+ taskId: result.taskId,
456310
+ audioUrl: finalResult.audioUrl,
456311
+ outputPath
456312
+ }
456099
456313
  });
456100
456314
  return;
456101
456315
  }
@@ -456130,6 +456344,7 @@ import { existsSync as existsSync47 } from "node:fs";
456130
456344
  import { writeFile as writeFile27, mkdir as mkdir19 } from "node:fs/promises";
456131
456345
  function registerThumbnailCommand(parent) {
456132
456346
  parent.command("thumbnail").description("Generate video thumbnail (DALL-E) or extract best frame from video (Gemini)").argument("[description]", "Thumbnail description (for DALL-E generation)").option("-k, --api-key <key>", "API key (OpenAI for generation, Google for best-frame)").option("-o, --output <path>", "Output file path").option("-s, --style <style>", "Platform style: youtube, instagram, tiktok, twitter").option("--best-frame <video>", "Extract best thumbnail frame from video using Gemini AI").option("--prompt <prompt>", "Custom prompt for best-frame analysis").option("--model <model>", "Gemini model: flash, latest, pro (default: flash)", "flash").action(async (description, options) => {
456347
+ const startedAt = Date.now();
456133
456348
  try {
456134
456349
  if (description) rejectControlChars(description);
456135
456350
  if (options.output) {
@@ -456165,11 +456380,14 @@ function registerThumbnailCommand(parent) {
456165
456380
  }
456166
456381
  spinner3.succeed(source_default.green("Best frame extracted"));
456167
456382
  if (isJsonMode()) {
456168
- outputResult({
456169
- success: true,
456170
- timestamp: result2.timestamp,
456171
- reason: result2.reason,
456172
- outputPath: result2.outputPath
456383
+ outputSuccess({
456384
+ command: "generate thumbnail",
456385
+ startedAt,
456386
+ data: {
456387
+ timestamp: result2.timestamp,
456388
+ reason: result2.reason,
456389
+ outputPath: result2.outputPath
456390
+ }
456173
456391
  });
456174
456392
  return;
456175
456393
  }
@@ -456217,7 +456435,11 @@ function registerThumbnailCommand(parent) {
456217
456435
  await mkdir19(dirname26(outputPath), { recursive: true });
456218
456436
  await writeFile27(outputPath, buffer);
456219
456437
  }
456220
- outputResult({ success: true, imageUrl: img.url, outputPath });
456438
+ outputSuccess({
456439
+ command: "generate thumbnail",
456440
+ startedAt,
456441
+ data: { imageUrl: img.url, outputPath }
456442
+ });
456221
456443
  return;
456222
456444
  }
456223
456445
  console.log();
@@ -456288,6 +456510,7 @@ function getStatusColor(status) {
456288
456510
  }
456289
456511
  function registerVideoStatusCommand(parent) {
456290
456512
  parent.command("video-status", { hidden: true }).description("Check video generation status (Grok, Runway, or Kling)").argument("<task-id>", "Task ID from video generation").option("-p, --provider <provider>", "Provider: grok, runway, kling", "grok").option("-k, --api-key <key>", "API key (or set XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY env)").option("-t, --type <type>", "Task type: text2video or image2video (Kling only)", "text2video").option("-w, --wait", "Wait for completion").option("-o, --output <path>", "Download video when complete").action(async (taskId, options) => {
456513
+ const startedAt = Date.now();
456291
456514
  try {
456292
456515
  const provider = (options.provider || "grok").toLowerCase();
456293
456516
  if (provider === "grok") {
@@ -456310,14 +456533,17 @@ function registerVideoStatusCommand(parent) {
456310
456533
  outputPath = resolve44(process.cwd(), options.output);
456311
456534
  await writeFile28(outputPath, buffer);
456312
456535
  }
456313
- outputResult({
456314
- success: true,
456315
- taskId,
456316
- provider: "grok",
456317
- status: result.status,
456318
- videoUrl: result.videoUrl,
456319
- error: result.error,
456320
- outputPath
456536
+ outputSuccess({
456537
+ command: "generate video-status",
456538
+ startedAt,
456539
+ data: {
456540
+ taskId,
456541
+ provider: "grok",
456542
+ status: result.status,
456543
+ videoUrl: result.videoUrl,
456544
+ error: result.error,
456545
+ outputPath
456546
+ }
456321
456547
  });
456322
456548
  return;
456323
456549
  }
@@ -456375,15 +456601,18 @@ function registerVideoStatusCommand(parent) {
456375
456601
  outputPath = resolve44(process.cwd(), options.output);
456376
456602
  await writeFile28(outputPath, buffer);
456377
456603
  }
456378
- outputResult({
456379
- success: true,
456380
- taskId,
456381
- provider: "runway",
456382
- status: result.status,
456383
- videoUrl: result.videoUrl,
456384
- progress: result.progress,
456385
- error: result.error,
456386
- outputPath
456604
+ outputSuccess({
456605
+ command: "generate video-status",
456606
+ startedAt,
456607
+ data: {
456608
+ taskId,
456609
+ provider: "runway",
456610
+ status: result.status,
456611
+ videoUrl: result.videoUrl,
456612
+ progress: result.progress,
456613
+ error: result.error,
456614
+ outputPath
456615
+ }
456387
456616
  });
456388
456617
  return;
456389
456618
  }
@@ -456439,15 +456668,18 @@ function registerVideoStatusCommand(parent) {
456439
456668
  outputPath = resolve44(process.cwd(), options.output);
456440
456669
  await writeFile28(outputPath, buffer);
456441
456670
  }
456442
- outputResult({
456443
- success: true,
456444
- taskId,
456445
- provider: "kling",
456446
- status: result.status,
456447
- videoUrl: result.videoUrl,
456448
- duration: result.duration,
456449
- error: result.error,
456450
- outputPath
456671
+ outputSuccess({
456672
+ command: "generate video-status",
456673
+ startedAt,
456674
+ data: {
456675
+ taskId,
456676
+ provider: "kling",
456677
+ status: result.status,
456678
+ videoUrl: result.videoUrl,
456679
+ duration: result.duration,
456680
+ error: result.error,
456681
+ outputPath
456682
+ }
456451
456683
  });
456452
456684
  return;
456453
456685
  }
@@ -456511,22 +456743,26 @@ import { resolve as resolve45 } from "node:path";
456511
456743
  import { writeFile as writeFile29 } from "node:fs/promises";
456512
456744
  function registerVideoExtendCommand(parent) {
456513
456745
  parent.command("video-extend", { hidden: true }).description("Extend video duration (Kling by video ID, Veo by operation name)").argument("<id>", "Kling video ID or Veo operation name").option("-p, --provider <provider>", "Provider: kling, veo", "kling").option("-k, --api-key <key>", "API key (KLING_API_KEY or GOOGLE_API_KEY)").option("-o, --output <path>", "Output file path").option("--prompt <text>", "Continuation prompt").option("-d, --duration <sec>", "Duration: 5 or 10 (Kling), 4/6/8 (Veo)", "5").option("-n, --negative <prompt>", "Negative prompt (what to avoid, Kling only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast", "3.1").option("--no-wait", "Start extension and return task ID without waiting").option("--dry-run", "Preview parameters without executing").action(async (id, options) => {
456746
+ const startedAt = Date.now();
456514
456747
  try {
456515
456748
  const provider = (options.provider || "kling").toLowerCase();
456516
456749
  if (options.output) {
456517
456750
  validateOutputPath(options.output);
456518
456751
  }
456519
456752
  if (options.dryRun) {
456520
- outputResult({
456521
- dryRun: true,
456753
+ outputSuccess({
456522
456754
  command: "generate video-extend",
456523
- params: {
456524
- id,
456525
- provider,
456526
- prompt: options.prompt,
456527
- duration: options.duration,
456528
- negative: options.negative,
456529
- veoModel: options.veoModel
456755
+ startedAt,
456756
+ dryRun: true,
456757
+ data: {
456758
+ params: {
456759
+ id,
456760
+ provider,
456761
+ prompt: options.prompt,
456762
+ duration: options.duration,
456763
+ negative: options.negative,
456764
+ veoModel: options.veoModel
456765
+ }
456530
456766
  }
456531
456767
  });
456532
456768
  return;
@@ -456583,13 +456819,16 @@ function registerVideoExtendCommand(parent) {
456583
456819
  outputPath = resolve45(process.cwd(), options.output);
456584
456820
  await writeFile29(outputPath, buffer);
456585
456821
  }
456586
- outputResult({
456587
- success: true,
456588
- provider: "kling",
456589
- taskId: result.id,
456590
- videoUrl: finalResult.videoUrl,
456591
- duration: finalResult.duration,
456592
- outputPath
456822
+ outputSuccess({
456823
+ command: "generate video-extend",
456824
+ startedAt,
456825
+ data: {
456826
+ provider: "kling",
456827
+ taskId: result.id,
456828
+ videoUrl: finalResult.videoUrl,
456829
+ duration: finalResult.duration,
456830
+ outputPath
456831
+ }
456593
456832
  });
456594
456833
  return;
456595
456834
  }
@@ -456669,13 +456908,16 @@ function registerVideoExtendCommand(parent) {
456669
456908
  outputPath = resolve45(process.cwd(), options.output);
456670
456909
  await writeFile29(outputPath, buffer);
456671
456910
  }
456672
- outputResult({
456673
- success: true,
456674
- provider: "veo",
456675
- taskId: result.id,
456676
- videoUrl: finalResult.videoUrl,
456677
- duration: finalResult.duration,
456678
- outputPath
456911
+ outputSuccess({
456912
+ command: "generate video-extend",
456913
+ startedAt,
456914
+ data: {
456915
+ provider: "veo",
456916
+ taskId: result.id,
456917
+ videoUrl: finalResult.videoUrl,
456918
+ duration: finalResult.duration,
456919
+ outputPath
456920
+ }
456679
456921
  });
456680
456922
  return;
456681
456923
  }
@@ -456790,6 +457032,7 @@ Examples:
456790
457032
  $ vibe gen img "landscape photo" -o wide.png -r 16:9
456791
457033
  $ vibe gen img "portrait" -o portrait.png -p gemini -m pro
456792
457034
  $ vibe gen img "product shot" --dry-run --json`).action(async (prompt3, options) => {
457035
+ const startedAt = Date.now();
456793
457036
  try {
456794
457037
  if (!prompt3) {
456795
457038
  if (hasTTY()) {
@@ -456848,18 +457091,21 @@ Examples:
456848
457091
  provider = resolved?.name ?? "gemini";
456849
457092
  }
456850
457093
  if (options.dryRun) {
456851
- outputResult({
456852
- dryRun: true,
457094
+ outputSuccess({
456853
457095
  command: "generate image",
456854
- params: {
456855
- prompt: prompt3,
456856
- provider,
456857
- model: options.model,
456858
- ratio: options.ratio,
456859
- size: options.size,
456860
- quality: options.quality,
456861
- count: options.count,
456862
- output: options.output
457096
+ startedAt,
457097
+ dryRun: true,
457098
+ data: {
457099
+ params: {
457100
+ prompt: prompt3,
457101
+ provider,
457102
+ model: options.model,
457103
+ ratio: options.ratio,
457104
+ size: options.size,
457105
+ quality: options.quality,
457106
+ count: options.count,
457107
+ output: options.output
457108
+ }
456863
457109
  }
456864
457110
  });
456865
457111
  return;
@@ -456897,14 +457143,17 @@ Examples:
456897
457143
  await mkdir20(dirname27(outputPath), { recursive: true });
456898
457144
  await writeFile30(outputPath, buffer);
456899
457145
  }
456900
- outputResult({
456901
- success: true,
456902
- provider: "openai",
456903
- images: result.images.map((img) => ({
456904
- url: img.url,
456905
- revisedPrompt: img.revisedPrompt
456906
- })),
456907
- outputPath
457146
+ outputSuccess({
457147
+ command: "generate image",
457148
+ startedAt,
457149
+ data: {
457150
+ provider: "openai",
457151
+ images: result.images.map((img) => ({
457152
+ url: img.url,
457153
+ revisedPrompt: img.revisedPrompt
457154
+ })),
457155
+ outputPath
457156
+ }
456908
457157
  });
456909
457158
  return;
456910
457159
  }
@@ -457016,13 +457265,18 @@ Examples:
457016
457265
  await mkdir20(dirname27(outputPath), { recursive: true });
457017
457266
  await writeFile30(outputPath, buffer);
457018
457267
  }
457019
- outputResult({
457020
- success: true,
457021
- provider: "gemini",
457022
- images: result.images.map((img) => ({
457023
- mimeType: img.mimeType
457024
- })),
457025
- outputPath
457268
+ outputSuccess({
457269
+ command: "generate image",
457270
+ startedAt,
457271
+ warnings: usedLabel.includes("fallback") ? [`Model "${options.model}" failed; fell back to flash`] : [],
457272
+ data: {
457273
+ provider: "gemini",
457274
+ model: usedLabel,
457275
+ images: result.images.map((img) => ({
457276
+ mimeType: img.mimeType
457277
+ })),
457278
+ outputPath
457279
+ }
457026
457280
  });
457027
457281
  return;
457028
457282
  }
@@ -457106,11 +457360,14 @@ Examples:
457106
457360
  await mkdir20(dirname27(outputPath), { recursive: true });
457107
457361
  await writeFile30(outputPath, buffer);
457108
457362
  }
457109
- outputResult({
457110
- success: true,
457111
- provider: "grok",
457112
- images: result.images.map((img) => ({ url: img.url })),
457113
- outputPath
457363
+ outputSuccess({
457364
+ command: "generate image",
457365
+ startedAt,
457366
+ data: {
457367
+ provider: "grok",
457368
+ images: result.images.map((img) => ({ url: img.url })),
457369
+ outputPath
457370
+ }
457114
457371
  });
457115
457372
  return;
457116
457373
  }
@@ -457181,11 +457438,14 @@ Examples:
457181
457438
  proc.on("close", (code) => {
457182
457439
  if (code === 0) {
457183
457440
  if (isJsonMode()) {
457184
- outputResult({
457185
- success: true,
457186
- provider: "runway",
457187
- images: [{ format: "file" }],
457188
- outputPath
457441
+ outputSuccess({
457442
+ command: "generate image",
457443
+ startedAt,
457444
+ data: {
457445
+ provider: "runway",
457446
+ images: [{ format: "file" }],
457447
+ outputPath
457448
+ }
457189
457449
  });
457190
457450
  } else {
457191
457451
  spinner2.succeed(source_default.green("Generated image with Runway"));
@@ -458911,6 +459171,7 @@ Examples:
458911
459171
  $ vibe gen vid "epic scene" -i frame.png -o out.mp4 -p runway # Image-to-video
458912
459172
  $ vibe gen vid "ocean waves" -o waves.mp4 -p veo --resolution 1080p # Veo
458913
459173
  $ vibe gen vid "sunset" -o sun.mp4 -d 10 --dry-run --json`).action(async (prompt3, options) => {
459174
+ const startedAt = Date.now();
458914
459175
  try {
458915
459176
  if (!prompt3) {
458916
459177
  if (hasTTY()) {
@@ -459002,19 +459263,22 @@ Examples:
459002
459263
  options.ratio = "16:9";
459003
459264
  }
459004
459265
  if (options.dryRun) {
459005
- outputResult({
459006
- dryRun: true,
459266
+ outputSuccess({
459007
459267
  command: "generate video",
459008
- params: {
459009
- prompt: prompt3,
459010
- provider,
459011
- duration: options.duration,
459012
- ratio: options.ratio,
459013
- image: options.image,
459014
- mode: options.mode,
459015
- negative: options.negative,
459016
- resolution: options.resolution,
459017
- veoModel: options.veoModel
459268
+ startedAt,
459269
+ dryRun: true,
459270
+ data: {
459271
+ params: {
459272
+ prompt: prompt3,
459273
+ provider,
459274
+ duration: options.duration,
459275
+ ratio: options.ratio,
459276
+ image: options.image,
459277
+ mode: options.mode,
459278
+ negative: options.negative,
459279
+ resolution: options.resolution,
459280
+ veoModel: options.veoModel
459281
+ }
459018
459282
  }
459019
459283
  });
459020
459284
  return;
@@ -459294,13 +459558,16 @@ Examples:
459294
459558
  outputPath = resolve49(process.cwd(), options.output);
459295
459559
  await writeFile33(outputPath, buffer);
459296
459560
  }
459297
- outputResult({
459298
- success: true,
459299
- provider,
459300
- taskId: result?.id,
459301
- videoUrl: finalResult.videoUrl,
459302
- duration: finalResult.duration,
459303
- outputPath
459561
+ outputSuccess({
459562
+ command: "generate video",
459563
+ startedAt,
459564
+ data: {
459565
+ provider,
459566
+ taskId: result?.id,
459567
+ videoUrl: finalResult.videoUrl,
459568
+ duration: finalResult.duration,
459569
+ outputPath
459570
+ }
459304
459571
  });
459305
459572
  return;
459306
459573
  }
@@ -459865,20 +460132,24 @@ var init_detect = __esm({
459865
460132
  init_validate();
459866
460133
  detectCommand = new Command("detect").description("Auto-detect scenes, beats, and silences in media");
459867
460134
  detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("-t, --threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("-p, --project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
460135
+ const startedAt = Date.now();
459868
460136
  const spinner2 = ora("Detecting scenes...").start();
459869
460137
  try {
459870
460138
  if (options.output) {
459871
460139
  validateOutputPath(options.output);
459872
460140
  }
459873
460141
  if (options.dryRun) {
459874
- outputResult({
459875
- dryRun: true,
460142
+ outputSuccess({
459876
460143
  command: "detect scenes",
459877
- params: {
459878
- video: videoPath,
459879
- threshold: options.threshold,
459880
- output: options.output || null,
459881
- project: options.project || null
460144
+ startedAt,
460145
+ dryRun: true,
460146
+ data: {
460147
+ params: {
460148
+ video: videoPath,
460149
+ threshold: options.threshold,
460150
+ output: options.output || null,
460151
+ project: options.project || null
460152
+ }
459882
460153
  }
459883
460154
  });
459884
460155
  return;
@@ -459980,20 +460251,24 @@ var init_detect = __esm({
459980
460251
  }
459981
460252
  });
459982
460253
  detectCommand.command("silence").description("Detect silence in audio/video").argument("<media>", "Media file path").option("-n, --noise <dB>", "Noise threshold in dB", "-30").option("-d, --duration <sec>", "Minimum silence duration", "0.5").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (mediaPath, options) => {
460254
+ const startedAt = Date.now();
459983
460255
  const spinner2 = ora("Detecting silence...").start();
459984
460256
  try {
459985
460257
  if (options.output) {
459986
460258
  validateOutputPath(options.output);
459987
460259
  }
459988
460260
  if (options.dryRun) {
459989
- outputResult({
459990
- dryRun: true,
460261
+ outputSuccess({
459991
460262
  command: "detect silence",
459992
- params: {
459993
- media: mediaPath,
459994
- noise: options.noise,
459995
- duration: options.duration,
459996
- output: options.output || null
460263
+ startedAt,
460264
+ dryRun: true,
460265
+ data: {
460266
+ params: {
460267
+ media: mediaPath,
460268
+ noise: options.noise,
460269
+ duration: options.duration,
460270
+ output: options.output || null
460271
+ }
459997
460272
  }
459998
460273
  });
459999
460274
  return;
@@ -460062,18 +460337,22 @@ var init_detect = __esm({
460062
460337
  }
460063
460338
  });
460064
460339
  detectCommand.command("beats").description("Detect beats in audio (for music sync)").argument("<audio>", "Audio file path").option("-o, --output <path>", "Output JSON file with timestamps").option("--dry-run", "Preview parameters without executing").action(async (audioPath, options) => {
460340
+ const startedAt = Date.now();
460065
460341
  const spinner2 = ora("Detecting beats...").start();
460066
460342
  try {
460067
460343
  if (options.output) {
460068
460344
  validateOutputPath(options.output);
460069
460345
  }
460070
460346
  if (options.dryRun) {
460071
- outputResult({
460072
- dryRun: true,
460347
+ outputSuccess({
460073
460348
  command: "detect beats",
460074
- params: {
460075
- audio: audioPath,
460076
- output: options.output || null
460349
+ startedAt,
460350
+ dryRun: true,
460351
+ data: {
460352
+ params: {
460353
+ audio: audioPath,
460354
+ output: options.output || null
460355
+ }
460077
460356
  }
460078
460357
  });
460079
460358
  return;
@@ -461547,20 +461826,24 @@ Examples:
461547
461826
  A scene project is bilingual: it works with both \`vibe\` and \`npx hyperframes\`.
461548
461827
  Run 'vibe schema scene.<command>' for structured parameter info.`);
461549
461828
  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) => {
461829
+ const startedAt = Date.now();
461550
461830
  const aspect = validateAspect(options.ratio);
461551
461831
  const duration = validateDuration(options.duration);
461552
461832
  const name = options.name ?? basename6(dir.replace(/\/+$/, ""));
461553
461833
  const visualStyle = options.visualStyle ? validateVisualStyle(options.visualStyle) : void 0;
461554
461834
  if (options.dryRun) {
461555
- outputResult({
461556
- dryRun: true,
461835
+ outputSuccess({
461557
461836
  command: "scene init",
461558
- params: {
461559
- dir,
461560
- name,
461561
- aspect,
461562
- duration,
461563
- visualStyle: visualStyle?.name ?? null
461837
+ startedAt,
461838
+ dryRun: true,
461839
+ data: {
461840
+ params: {
461841
+ dir,
461842
+ name,
461843
+ aspect,
461844
+ duration,
461845
+ visualStyle: visualStyle?.name ?? null
461846
+ }
461564
461847
  }
461565
461848
  });
461566
461849
  return;
@@ -461576,19 +461859,21 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461576
461859
  hosts: skillHosts
461577
461860
  });
461578
461861
  if (isJsonMode()) {
461579
- outputResult({
461580
- success: true,
461862
+ outputSuccess({
461581
461863
  command: "scene init",
461582
- dir,
461583
- name,
461584
- aspect,
461585
- duration,
461586
- visualStyle: visualStyle?.name ?? null,
461587
- created: result.created,
461588
- merged: result.merged,
461589
- skipped: result.skipped,
461590
- skillFiles: skillResult.files,
461591
- skillBundleVersion: skillResult.bundleVersion
461864
+ startedAt,
461865
+ data: {
461866
+ dir,
461867
+ name,
461868
+ aspect,
461869
+ duration,
461870
+ visualStyle: visualStyle?.name ?? null,
461871
+ created: result.created,
461872
+ merged: result.merged,
461873
+ skipped: result.skipped,
461874
+ skillFiles: skillResult.files,
461875
+ skillBundleVersion: skillResult.bundleVersion
461876
+ }
461592
461877
  });
461593
461878
  return;
461594
461879
  }
@@ -461629,6 +461914,7 @@ sceneCommand.command("init").description("Scaffold a new scene project (or safel
461629
461914
  });
461630
461915
  var VALID_INSTALL_SKILL_HOSTS = ["claude-code", "cursor", "auto", "all"];
461631
461916
  sceneCommand.command("install-skill").description("Install the Hyperframes skill into a scene project so the host agent can read it (Phase H1)").argument("[project-dir]", "Project directory containing STORYBOARD.md / DESIGN.md", ".").option("--host <id>", `Host layout target: ${VALID_INSTALL_SKILL_HOSTS.join(" | ")}`, "auto").option("--force", "Overwrite existing skill files (default: skip-on-exist)").option("--dry-run", "Preview which files would be written without changing anything").action(async (projectDirArg, options) => {
461917
+ const startedAt = Date.now();
461632
461918
  const hostFlag = options.host ?? "auto";
461633
461919
  if (!VALID_INSTALL_SKILL_HOSTS.includes(hostFlag)) {
461634
461920
  exitWithError(usageError(`Invalid --host: ${hostFlag}`, `Valid: ${VALID_INSTALL_SKILL_HOSTS.join(", ")}`));
@@ -461648,15 +461934,17 @@ sceneCommand.command("install-skill").description("Install the Hyperframes skill
461648
461934
  dryRun: options.dryRun ?? false
461649
461935
  });
461650
461936
  if (isJsonMode()) {
461651
- outputResult({
461652
- success: true,
461937
+ outputSuccess({
461653
461938
  command: "scene install-skill",
461654
- projectDir,
461655
- host: hostFlag,
461656
- resolvedHosts: hosts,
461657
- bundleVersion: result.bundleVersion,
461658
- files: result.files,
461659
- dryRun: options.dryRun ?? false
461939
+ startedAt,
461940
+ dryRun: options.dryRun ?? false,
461941
+ data: {
461942
+ projectDir,
461943
+ host: hostFlag,
461944
+ resolvedHosts: hosts,
461945
+ bundleVersion: result.bundleVersion,
461946
+ files: result.files
461947
+ }
461660
461948
  });
461661
461949
  return;
461662
461950
  }
@@ -461678,6 +461966,7 @@ sceneCommand.command("install-skill").description("Install the Hyperframes skill
461678
461966
  }
461679
461967
  });
461680
461968
  sceneCommand.command("compose-prompts").description("Emit the per-beat compose plan for the host agent to author HTML itself (Phase H2 \u2014 no LLM call)").argument("[project-dir]", "Project directory containing STORYBOARD.md / DESIGN.md", ".").option("--beat <id>", "Restrict the plan to a single beat by id (e.g. 'hook', '1')").action(async (projectDirArg, options) => {
461969
+ const startedAt = Date.now();
461681
461970
  const projectDir = resolve21(projectDirArg);
461682
461971
  const result = await getComposePrompts({
461683
461972
  projectDir,
@@ -461685,18 +461974,21 @@ sceneCommand.command("compose-prompts").description("Emit the per-beat compose p
461685
461974
  });
461686
461975
  if (!result.success) {
461687
461976
  if (isJsonMode()) {
461688
- outputResult({
461977
+ outputSuccess({
461689
461978
  command: "scene compose-prompts",
461690
- ...result
461979
+ startedAt,
461980
+ data: { ...result }
461691
461981
  });
461692
- process.exit(1);
461982
+ process.exitCode = 1;
461983
+ return;
461693
461984
  }
461694
461985
  exitWithError(generalError(result.error ?? "compose-prompts failed"));
461695
461986
  }
461696
461987
  if (isJsonMode()) {
461697
- outputResult({
461988
+ outputSuccess({
461698
461989
  command: "scene compose-prompts",
461699
- ...result
461990
+ startedAt,
461991
+ data: { ...result }
461700
461992
  });
461701
461993
  return;
461702
461994
  }
@@ -461730,20 +462022,23 @@ sceneCommand.command("compose-prompts").description("Emit the per-beat compose p
461730
462022
  console.log(source_default.dim("Re-run with --json to get the full per-beat userPrompt + cues for direct consumption."));
461731
462023
  });
461732
462024
  sceneCommand.command("styles").description("List vendored visual styles (or show one) for DESIGN.md seeding").argument("[name]", "Style name to inspect (omit to list all)").action((name) => {
462025
+ const startedAt = Date.now();
461733
462026
  if (!name) {
461734
462027
  const all = listVisualStyles();
461735
462028
  if (isJsonMode()) {
461736
- outputResult({
461737
- success: true,
462029
+ outputSuccess({
461738
462030
  command: "scene styles",
461739
- count: all.length,
461740
- styles: all.map((s) => ({
461741
- name: s.name,
461742
- slug: s.slug,
461743
- designer: s.designer,
461744
- mood: s.mood,
461745
- bestFor: s.bestFor
461746
- }))
462031
+ startedAt,
462032
+ data: {
462033
+ count: all.length,
462034
+ styles: all.map((s) => ({
462035
+ name: s.name,
462036
+ slug: s.slug,
462037
+ designer: s.designer,
462038
+ mood: s.mood,
462039
+ bestFor: s.bestFor
462040
+ }))
462041
+ }
461747
462042
  });
461748
462043
  return;
461749
462044
  }
@@ -461771,7 +462066,11 @@ sceneCommand.command("styles").description("List vendored visual styles (or show
461771
462066
  return;
461772
462067
  }
461773
462068
  if (isJsonMode()) {
461774
- outputResult({ success: true, command: "scene styles", style });
462069
+ outputSuccess({
462070
+ command: "scene styles",
462071
+ startedAt,
462072
+ data: { style }
462073
+ });
461775
462074
  return;
461776
462075
  }
461777
462076
  console.log();
@@ -461793,6 +462092,7 @@ sceneCommand.command("styles").description("List vendored visual styles (or show
461793
462092
  console.log(source_default.dim("Seed DESIGN.md:"), source_default.cyan(`vibe scene init <dir> --visual-style "${style.name}"`));
461794
462093
  });
461795
462094
  sceneCommand.command("add").description("Add a new scene to a project: AI narration + image + per-scene HTML").argument("<name>", "Scene name (slugified into the composition id)").option("--style <preset>", `Style preset: ${SCENE_PRESETS.join(", ")}`, "simple").option("--narration <text>", "Narration text (or path to a .txt file). Drives TTS + scene duration.").option("--narration-file <path>", "Existing narration audio file (.wav/.mp3). Skips TTS \u2014 useful with hyperframes tts, Mac say, or other external tools.").option("-d, --duration <sec>", "Explicit scene duration in seconds (overrides narration audio)").option("--visuals <prompt>", "Image prompt \u2014 generates assets/scene-<id>.png via the configured image provider").option("--headline <text>", "Visible headline (defaults to the humanised scene name)").option("--kicker <text>", "Small label above the headline (explainer / product-shot)").option("--insert-into <path>", "Root composition file to update", "index.html").option("--project <dir>", "Project directory", ".").option("--image-provider <name>", "Image provider: gemini, openai", "gemini").option("--tts <provider>", "TTS provider: auto, elevenlabs, kokoro (default auto \u2014 picks ElevenLabs when key set, else Kokoro local)", "auto").option("--voice <id>", "Voice id (ElevenLabs name/id, or Kokoro id like af_heart, am_michael)").option("--no-audio", "Skip TTS even when --narration is provided (useful for tests/agent dry runs)").option("--no-image", "Skip image generation even when --visuals is provided").option("--no-transcribe", "Skip Whisper word-level transcribe step (no transcript-<id>.json emitted)").option("--transcribe-language <code>", "BCP-47 language code passed to Whisper (e.g. en, ko)").option("--force", "Overwrite an existing compositions/scene-<id>.html").option("--dry-run", "Preview parameters without writing files or calling APIs").action(async (name, options) => {
462095
+ const startedAt = Date.now();
461796
462096
  if (options.style) options.style = validatePreset(options.style);
461797
462097
  if (options.duration !== void 0) options.duration = validateDuration(options.duration);
461798
462098
  let tts;
@@ -461803,25 +462103,28 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
461803
462103
  }
461804
462104
  if (options.dryRun) {
461805
462105
  const id = slugifySceneName(name);
461806
- outputResult({
461807
- dryRun: true,
462106
+ outputSuccess({
461808
462107
  command: "scene add",
461809
- params: {
461810
- name,
461811
- id,
461812
- preset: options.style,
461813
- narration: !!options.narration,
461814
- visuals: !!options.visuals,
461815
- duration: options.duration,
461816
- headline: options.headline,
461817
- kicker: options.kicker,
461818
- project: options.project,
461819
- insertInto: options.insertInto,
461820
- imageProvider: options.imageProvider,
461821
- tts,
461822
- audio: options.audio,
461823
- // commander sets `audio: false` when --no-audio is passed
461824
- image: options.image
462108
+ startedAt,
462109
+ dryRun: true,
462110
+ data: {
462111
+ params: {
462112
+ name,
462113
+ id,
462114
+ preset: options.style,
462115
+ narration: !!options.narration,
462116
+ visuals: !!options.visuals,
462117
+ duration: options.duration,
462118
+ headline: options.headline,
462119
+ kicker: options.kicker,
462120
+ project: options.project,
462121
+ insertInto: options.insertInto,
462122
+ imageProvider: options.imageProvider,
462123
+ tts,
462124
+ audio: options.audio,
462125
+ // commander sets `audio: false` when --no-audio is passed
462126
+ image: options.image
462127
+ }
461825
462128
  }
461826
462129
  });
461827
462130
  return;
@@ -461856,9 +462159,10 @@ sceneCommand.command("add").description("Add a new scene to a project: AI narrat
461856
462159
  exitWithError(generalError(result.error ?? "Scene add failed"));
461857
462160
  }
461858
462161
  if (isJsonMode()) {
461859
- outputResult({
462162
+ outputSuccess({
461860
462163
  command: "scene add",
461861
- ...result
462164
+ startedAt,
462165
+ data: { ...result }
461862
462166
  });
461863
462167
  return;
461864
462168
  }
@@ -462154,6 +462458,7 @@ async function executeSceneAdd(opts) {
462154
462458
  };
462155
462459
  }
462156
462460
  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 (root2, options) => {
462461
+ const startedAt = Date.now();
462157
462462
  const projectDir = resolve21(options.project);
462158
462463
  if (!await rootExists(projectDir, root2)) {
462159
462464
  exitWithError(generalError(
@@ -462171,11 +462476,12 @@ sceneCommand.command("lint").description("Validate scene HTML against Hyperframe
462171
462476
  exitWithError(generalError(`Lint failed: ${msg}`));
462172
462477
  }
462173
462478
  if (isJsonMode()) {
462174
- outputResult({
462479
+ outputSuccess({
462175
462480
  command: "scene lint",
462176
- ...result
462481
+ startedAt,
462482
+ data: { ...result }
462177
462483
  });
462178
- if (!result.ok) process.exit(1);
462484
+ if (!result.ok) process.exitCode = 1;
462179
462485
  return;
462180
462486
  }
462181
462487
  if (result.ok && result.warningCount === 0 && result.infoCount === 0) {
@@ -462209,7 +462515,7 @@ sceneCommand.command("lint").description("Validate scene HTML against Hyperframe
462209
462515
  console.log(` ${source_default.green("\u2714")} ${fx.file} ${source_default.dim(fx.codes.join(", "))}`);
462210
462516
  }
462211
462517
  }
462212
- if (!result.ok) process.exit(1);
462518
+ if (!result.ok) process.exitCode = 1;
462213
462519
  });
462214
462520
  function severityTag(severity) {
462215
462521
  if (severity === "error") return source_default.red("\u2718 error ");
@@ -462246,23 +462552,27 @@ function validateWorkers(value) {
462246
462552
  return n;
462247
462553
  }
462248
462554
  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 (root2, options) => {
462555
+ const startedAt = Date.now();
462249
462556
  const fps = validateFps(options.fps);
462250
462557
  const quality = validateQuality(options.quality);
462251
462558
  const format4 = validateFormat(options.format);
462252
462559
  const workers = validateWorkers(options.workers);
462253
462560
  const projectDir = resolve21(options.project);
462254
462561
  if (options.dryRun) {
462255
- outputResult({
462256
- dryRun: true,
462562
+ outputSuccess({
462257
462563
  command: "scene render",
462258
- params: {
462259
- projectDir,
462260
- root: root2,
462261
- output: options.out,
462262
- fps,
462263
- quality,
462264
- format: format4,
462265
- workers
462564
+ startedAt,
462565
+ dryRun: true,
462566
+ data: {
462567
+ params: {
462568
+ projectDir,
462569
+ root: root2,
462570
+ output: options.out,
462571
+ fps,
462572
+ quality,
462573
+ format: format4,
462574
+ workers
462575
+ }
462266
462576
  }
462267
462577
  });
462268
462578
  return;
@@ -462283,13 +462593,22 @@ sceneCommand.command("render").description("Render a scene project to MP4/WebM/M
462283
462593
  if (!result.success) {
462284
462594
  spinner2?.fail("Render failed");
462285
462595
  if (isJsonMode()) {
462286
- outputResult({ command: "scene render", ...result });
462287
- process.exit(1);
462596
+ outputSuccess({
462597
+ command: "scene render",
462598
+ startedAt,
462599
+ data: { ...result }
462600
+ });
462601
+ process.exitCode = 1;
462602
+ return;
462288
462603
  }
462289
462604
  exitWithError(generalError(result.error ?? "Render failed"));
462290
462605
  }
462291
462606
  if (isJsonMode()) {
462292
- outputResult({ command: "scene render", ...result });
462607
+ outputSuccess({
462608
+ command: "scene render",
462609
+ startedAt,
462610
+ data: { ...result }
462611
+ });
462293
462612
  return;
462294
462613
  }
462295
462614
  spinner2?.succeed(source_default.green(`Render complete: ${result.outputPath}`));
@@ -462309,25 +462628,29 @@ sceneCommand.command("render").description("Render a scene project to MP4/WebM/M
462309
462628
  }
462310
462629
  });
462311
462630
  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) => {
462631
+ const startedAt = Date.now();
462312
462632
  const projectDir = resolve21(projectDirArg);
462313
462633
  if (options.dryRun) {
462314
- outputResult({
462315
- dryRun: true,
462634
+ outputSuccess({
462316
462635
  command: "scene build",
462317
- params: {
462318
- projectDir,
462319
- mode: options.mode,
462320
- effort: options.effort,
462321
- composer: options.composer,
462322
- skipNarration: options.skipNarration ?? false,
462323
- skipBackdrop: options.skipBackdrop ?? false,
462324
- skipRender: options.skipRender ?? false,
462325
- ttsProvider: options.tts,
462326
- voice: options.voice,
462327
- imageProvider: options.imageProvider,
462328
- imageQuality: options.quality,
462329
- imageSize: options.imageSize,
462330
- force: options.force ?? false
462636
+ startedAt,
462637
+ dryRun: true,
462638
+ data: {
462639
+ params: {
462640
+ projectDir,
462641
+ mode: options.mode,
462642
+ effort: options.effort,
462643
+ composer: options.composer,
462644
+ skipNarration: options.skipNarration ?? false,
462645
+ skipBackdrop: options.skipBackdrop ?? false,
462646
+ skipRender: options.skipRender ?? false,
462647
+ ttsProvider: options.tts,
462648
+ voice: options.voice,
462649
+ imageProvider: options.imageProvider,
462650
+ imageQuality: options.quality,
462651
+ imageSize: options.imageSize,
462652
+ force: options.force ?? false
462653
+ }
462331
462654
  }
462332
462655
  });
462333
462656
  return;
@@ -462381,13 +462704,22 @@ sceneCommand.command("build").description("One-shot: read STORYBOARD.md cues, di
462381
462704
  if (!result.success) {
462382
462705
  spinner2?.fail(`Build failed: ${result.error}`);
462383
462706
  if (isJsonMode()) {
462384
- outputResult({ command: "scene build", ...result });
462385
- process.exit(1);
462707
+ outputSuccess({
462708
+ command: "scene build",
462709
+ startedAt,
462710
+ data: { ...result }
462711
+ });
462712
+ process.exitCode = 1;
462713
+ return;
462386
462714
  }
462387
462715
  exitWithError(generalError(result.error ?? "Build failed"));
462388
462716
  }
462389
462717
  if (isJsonMode()) {
462390
- outputResult({ command: "scene build", ...result });
462718
+ outputSuccess({
462719
+ command: "scene build",
462720
+ startedAt,
462721
+ data: { ...result }
462722
+ });
462391
462723
  return;
462392
462724
  }
462393
462725
  if (result.phase === "needs-author") {
@@ -465897,6 +466229,7 @@ Cost: Free (no API keys needed). Requires FFmpeg.
465897
466229
  GIF format: 15fps, no audio, looping. Good for previews and sharing.
465898
466230
  Custom flags (--bitrate, --fps, --resolution, --codec) override preset values.
465899
466231
  Run 'vibe schema export' for structured parameter info.`).action(async (projectPath, options) => {
466232
+ const startedAt = Date.now();
465900
466233
  const spinner2 = ora("Checking FFmpeg...").start();
465901
466234
  try {
465902
466235
  if (options.output) {
@@ -465914,27 +466247,30 @@ Run 'vibe schema export' for structured parameter info.`).action(async (projectP
465914
466247
  exitWithError(usageError(overrideError));
465915
466248
  }
465916
466249
  if (options.dryRun) {
465917
- outputResult({
465918
- dryRun: true,
466250
+ outputSuccess({
465919
466251
  command: "export",
465920
- params: {
465921
- project: projectPath,
465922
- output: options.output || null,
465923
- format: options.format,
465924
- preset: options.preset,
465925
- overwrite: options.overwrite,
465926
- gapFill: options.gapFill,
465927
- backend: options.backend,
465928
- bitrate: options.bitrate ?? null,
465929
- fps: customOverrides.fps ?? null,
465930
- resolution: options.resolution ?? null,
465931
- codec: options.codec ?? null
466252
+ startedAt,
466253
+ dryRun: true,
466254
+ data: {
466255
+ params: {
466256
+ project: projectPath,
466257
+ output: options.output || null,
466258
+ format: options.format,
466259
+ preset: options.preset,
466260
+ overwrite: options.overwrite,
466261
+ gapFill: options.gapFill,
466262
+ backend: options.backend,
466263
+ bitrate: options.bitrate ?? null,
466264
+ fps: customOverrides.fps ?? null,
466265
+ resolution: options.resolution ?? null,
466266
+ codec: options.codec ?? null
466267
+ }
465932
466268
  }
465933
466269
  });
465934
466270
  return;
465935
466271
  }
465936
466272
  if (options.backend === "hyperframes") {
465937
- await runHyperframesExport(projectPath, options, spinner2);
466273
+ await runHyperframesExport(projectPath, options, spinner2, startedAt);
465938
466274
  return;
465939
466275
  }
465940
466276
  const ffmpegPath = await findFFmpeg();
@@ -466521,13 +466857,13 @@ function getPresetSettings(preset, aspectRatio) {
466521
466857
  }
466522
466858
  return settings;
466523
466859
  }
466524
- async function runHyperframesExport(projectPath, options, spinner2) {
466860
+ async function runHyperframesExport(projectPath, options, spinner2, startedAt) {
466525
466861
  spinner2.text = "Loading project...";
466526
466862
  const { readFile: readFile34 } = await import("node:fs/promises");
466527
466863
  const { resolve: resolve64, basename: basename18 } = await import("node:path");
466528
466864
  const { Project: Project2 } = await Promise.resolve().then(() => (init_engine(), engine_exports));
466529
466865
  const { createHyperframesBackend: createHyperframesBackend2 } = await Promise.resolve().then(() => (init_hyperframes(), hyperframes_exports));
466530
- const { exitWithError: exitWithError2, generalError: generalError2, outputResult: outputResult2 } = await Promise.resolve().then(() => (init_output(), output_exports));
466866
+ const { exitWithError: exitWithError2, generalError: generalError2, outputSuccess: outputSuccess2 } = await Promise.resolve().then(() => (init_output(), output_exports));
466531
466867
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
466532
466868
  const filePath = resolve64(process.cwd(), projectPath);
466533
466869
  const content = await readFile34(filePath, "utf-8");
@@ -466553,10 +466889,10 @@ async function runHyperframesExport(projectPath, options, spinner2) {
466553
466889
  return;
466554
466890
  }
466555
466891
  spinner2.succeed(chalk2.green(`Exported: ${result.outputPath}`));
466556
- outputResult2({
466557
- success: true,
466892
+ outputSuccess2({
466558
466893
  command: "export",
466559
- result: {
466894
+ startedAt,
466895
+ data: {
466560
466896
  outputPath: result.outputPath,
466561
466897
  backend: "hyperframes",
466562
466898
  durationMs: result.durationMs,
@@ -467461,6 +467797,348 @@ var agentOnlyTools = [
467461
467797
  mediaConcatTool
467462
467798
  ];
467463
467799
 
467800
+ // ../cli/src/tools/manifest/walkthrough.ts
467801
+ import { z as z12 } from "zod";
467802
+
467803
+ // ../cli/src/commands/_shared/walkthroughs/walkthroughs.ts
467804
+ var SCENE_WALKTHROUGH = `# Scene authoring with vibe
467805
+
467806
+ A scene project is a directory that is **bilingual**: it works with both
467807
+ \`vibe\` and \`npx hyperframes\`. Each scene is one HTML file with scoped CSS
467808
+ and a paused GSAP timeline. Cheap to edit, cheap to lint, expensive only
467809
+ at render.
467810
+
467811
+ \`vibe scene build\` (v0.60+) is the supported one-shot driver from a
467812
+ written storyboard to an MP4. Plan H (v0.70) added \`--mode agent\` so the
467813
+ host agent itself authors the per-beat HTML \u2014 no internal LLM call.
467814
+
467815
+ ## Three authoring paths
467816
+
467817
+ | Path | Command | When to use |
467818
+ |---|---|---|
467819
+ | **One-shot (default, v0.60+)** | \`vibe scene build [project-dir]\` | STORYBOARD.md has YAML frontmatter + per-beat cues |
467820
+ | **High-craft (manual)** | \`DESIGN.md\` + Hyperframes skill in your agent | Maximum control: hand-author each scene |
467821
+ | **Quick draft** | \`vibe scene add --style <preset>\` | No agent or no API keys; fast iteration |
467822
+
467823
+ Recommend \`vibe scene build\` whenever the user has a STORYBOARD with
467824
+ narration / backdrop intent.
467825
+
467826
+ ## High-craft path
467827
+
467828
+ 1. \`vibe scene init my-promo --visual-style "Swiss Pulse"\` \u2014 seeds
467829
+ \`DESIGN.md\` (palette, typography, motion, transitions) plus the
467830
+ \`vibe.project.yaml\` / \`hyperframes.json\` / \`index.html\` scaffold.
467831
+ In Plan H this **also installs the Hyperframes skill** at the
467832
+ right place for your host (\`.claude/skills/hyperframes/\` for Claude
467833
+ Code, \`.cursor/rules/hyperframes.mdc\` for Cursor, universal
467834
+ \`SKILL.md\` for everyone else).
467835
+ 2. Read \`SKILL.md\` (or the host-specific copy) \u2014 Hyperframes
467836
+ framework rules, motion principles, type system, transition recipes.
467837
+ 3. Read \`DESIGN.md\` \u2014 project-specific palette / typography / motion
467838
+ signature (visual identity hard-gate).
467839
+ 4. Author each scene HTML directly under \`compositions/scene-<id>.html\`
467840
+ using the rules from steps 2 and 3. The skill enforces the visual
467841
+ identity contract \u2014 scenes that contradict DESIGN.md fail lint.
467842
+ 5. \`vibe scene lint --fix\` for mechanical issues, \`vibe scene render\`
467843
+ to MP4.
467844
+
467845
+ ## Quick-draft path
467846
+
467847
+ \`\`\`bash
467848
+ vibe scene init my-promo -r 16:9 -d 30
467849
+ vibe scene add intro --style announcement \\
467850
+ --headline "Ship videos, not clicks"
467851
+ vibe scene lint
467852
+ vibe scene render
467853
+ \`\`\`
467854
+
467855
+ \`vibe scene init\` is **idempotent** \u2014 running it on an existing
467856
+ Hyperframes directory merges \`hyperframes.json\` instead of clobbering it.
467857
+ Safe to invoke on user-provided projects.
467858
+
467859
+ ## Subcommands
467860
+
467861
+ \`\`\`bash
467862
+ vibe scene init <dir> [-r 16:9|9:16|1:1|4:5] [-d <sec>] [--visual-style "<name>"]
467863
+ vibe scene styles [<name>] # list / show vendored visual identities
467864
+ vibe scene install-skill [<dir>] [--host all] # retroactive Hyperframes-skill install
467865
+ vibe scene add <name> --style <preset> [...]
467866
+ vibe scene compose-prompts [<dir>] [--beat <id>] # H2: emit plan, no LLM call
467867
+ vibe scene lint [<root>] [--json] [--fix]
467868
+ vibe scene render [<root>] [--fps 30] [--quality standard] [--format mp4]
467869
+ vibe scene build [<dir>] [--mode agent|batch|auto] # H3 dispatch
467870
+ \`\`\`
467871
+
467872
+ ## Style presets (for \`vibe scene add --style\`)
467873
+
467874
+ - **simple** \u2014 backdrop + bottom caption (default)
467875
+ - **announcement** \u2014 single huge headline, gradient text
467876
+ - **explainer** \u2014 kicker + title + subtitle stack
467877
+ - **kinetic-type** \u2014 words animate in word-by-word
467878
+ - **product-shot** \u2014 corner label + bottom headline + slow zoom
467879
+
467880
+ All presets accept \`--narration <text|file>\`, \`--visuals <prompt>\`,
467881
+ \`--headline\`, \`--kicker\`. With \`--narration\`, scene duration auto-derives
467882
+ from the generated TTS audio.
467883
+
467884
+ ## STORYBOARD-to-MP4 (one command, v0.60+)
467885
+
467886
+ \`\`\`bash
467887
+ vibe scene init my-promo --visual-style "Swiss Pulse" -d 12
467888
+ # (edit STORYBOARD.md with per-beat YAML cues \u2014 narration, backdrop, duration)
467889
+ vibe scene build my-promo
467890
+ \`\`\`
467891
+
467892
+ \`vibe scene build\` reads the STORYBOARD frontmatter + per-beat cues,
467893
+ dispatches TTS + image-gen per beat, then either:
467894
+
467895
+ - **\`--mode agent\`** (default when an agent host is detected) \u2014 emits a
467896
+ \`needs-author\` plan via \`vibe scene compose-prompts\`. The host agent
467897
+ authors each \`compositions/scene-<id>.html\` itself, then re-invoking
467898
+ \`vibe scene build\` proceeds to lint + render.
467899
+ - **\`--mode batch\`** \u2014 VibeFrame runs an internal LLM (Claude / OpenAI /
467900
+ Gemini) to compose the HTML, then renders.
467901
+
467902
+ \`VIBE_BUILD_MODE\` env var overrides the auto-resolve.
467903
+
467904
+ ## Lint feedback loop
467905
+
467906
+ \`\`\`bash
467907
+ vibe scene lint --json --fix
467908
+ \`\`\`
467909
+
467910
+ Returns structured findings. The recommended loop: 1) run lint with
467911
+ \`--fix\` (mechanical fixes applied), 2) if \`errorCount > 0\`, edit the
467912
+ scene HTML, 3) re-lint. Cap retries at 3 \u2014 if errors persist, fall back
467913
+ to a template preset (\`vibe scene add <id> --style simple --force\`)
467914
+ and surface the error to the user.
467915
+
467916
+ ## When to use VibeFrame vs raw Hyperframes
467917
+
467918
+ | Task | Tool |
467919
+ |------|------|
467920
+ | Generate narration + image, then author scene | \`vibe scene add\` |
467921
+ | Generate a full scenes project from a STORYBOARD | \`vibe scene build\` |
467922
+ | Hand-tweak a single scene's animation | edit \`compositions/<file>.html\` directly |
467923
+ | Render the project | \`vibe scene render\` *or* \`npx hyperframes render\` (equivalent) |
467924
+ | Lint | \`vibe scene lint\` *or* \`npx hyperframes lint\` (equivalent) |
467925
+
467926
+ The \`vibe\` CLI adds asset generation, AI orchestration, and pipeline
467927
+ integration on top of Hyperframes' rendering primitives.
467928
+
467929
+ ## Quality checklist before render
467930
+
467931
+ - [ ] \`vibe scene lint\` exits 0 (or only warnings)
467932
+ - [ ] \`vibe doctor\` confirms a usable Chrome (required for render)
467933
+ - [ ] Root \`data-duration\` matches the sum of clip durations
467934
+ - [ ] Aspect ratio in \`vibe.project.yaml\` matches the destination platform
467935
+ `;
467936
+ var PIPELINE_WALKTHROUGH = `# YAML pipelines (Video as Code)
467937
+
467938
+ A pipeline is a YAML manifest with steps that reference each other's
467939
+ outputs. \`vibe run pipeline.yaml\` executes them with checkpointing and
467940
+ cost estimation.
467941
+
467942
+ ## Minimal skeleton
467943
+
467944
+ \`\`\`yaml
467945
+ name: promo-video
467946
+ description: 15s product teaser
467947
+ steps:
467948
+ - id: backdrop
467949
+ action: generate-image
467950
+ prompt: "sleek product shot on white background"
467951
+ output: backdrop.png
467952
+ - id: scene
467953
+ action: generate-video
467954
+ image: $backdrop.output # reference previous step output
467955
+ prompt: "slow camera pan"
467956
+ duration: 5
467957
+ output: scene.mp4
467958
+ - id: voice
467959
+ action: generate-tts
467960
+ text: "Meet the new standard."
467961
+ output: voice.mp3
467962
+ - id: final
467963
+ action: compose
467964
+ video: $scene.output
467965
+ audio: $voice.output
467966
+ output: final.mp4
467967
+ \`\`\`
467968
+
467969
+ ## Supported actions
467970
+
467971
+ - \`generate-image\`, \`generate-video\`, \`generate-tts\`, \`generate-music\`,
467972
+ \`generate-sound-effect\`, \`generate-storyboard\`, \`generate-motion\`
467973
+ - \`edit-silence-cut\`, \`edit-jump-cut\`, \`edit-caption\`, \`edit-grade\`,
467974
+ \`edit-reframe\`, \`edit-speed-ramp\`, \`edit-fade\`, \`edit-noise-reduce\`,
467975
+ \`edit-text-overlay\`, \`edit-fill-gaps\`
467976
+ - \`analyze-media\`, \`analyze-video\`, \`analyze-review\`, \`analyze-suggest\`
467977
+ - \`audio-transcribe\`, \`audio-isolate\`, \`audio-voice-clone\`, \`audio-dub\`,
467978
+ \`audio-duck\`
467979
+ - \`detect-scenes\`, \`detect-silence\`, \`detect-beats\`
467980
+ - \`compose\`, \`export\`
467981
+ - \`scene-build\` (Plan H one-shot driver) and \`scene-render\`
467982
+ - \`compose-scenes-with-skills\` (internal-LLM compose pass)
467983
+
467984
+ The full set lives in \`packages/cli/src/pipeline/executor.ts\`.
467985
+
467986
+ ## Variable references
467987
+
467988
+ - \`$<step-id>.output\` \u2014 previous step's output path
467989
+ - \`$<step-id>.result.<field>\` \u2014 structured field from JSON result
467990
+ - \`\${ENV_VAR}\` \u2014 environment variable
467991
+ - Values can be templated: \`"\${SCRIPT_TITLE} - Episode \${EPISODE}"\`
467992
+
467993
+ ## Running
467994
+
467995
+ \`\`\`bash
467996
+ vibe run pipeline.yaml --dry-run # plan + cost estimate, no execution
467997
+ vibe run pipeline.yaml # execute
467998
+ vibe run pipeline.yaml --resume # retry from last successful step
467999
+ vibe run pipeline.yaml --from scene # start at specific step
468000
+ vibe run pipeline.yaml --provider-video kling # override provider
468001
+ \`\`\`
468002
+
468003
+ Checkpoints land next to the YAML: \`pipeline.yaml.checkpoint.json\`.
468004
+
468005
+ ## Authoring tips
468006
+
468007
+ 1. **Start from examples** \u2014 \`examples/demo-pipeline.yaml\` (FFmpeg-only,
468008
+ no keys), \`examples/promo-video.yaml\` (AI providers).
468009
+ 2. **Dry-run first** \u2014 you see estimated cost and resolved variable
468010
+ graph before spending API credits.
468011
+ 3. **Keep step ids short and descriptive** (\`intro\`, \`scene1\`, \`voice\`,
468012
+ \`bgm\`) \u2014 they appear in logs and variable refs.
468013
+ 4. **Name outputs** with extensions matching the action (\`.mp4\`, \`.mp3\`,
468014
+ \`.png\`, \`.json\`).
468015
+ 5. **Declare \`budget:\`** on expensive pipelines:
468016
+ \`\`\`yaml
468017
+ budget:
468018
+ tokens: 500_000
468019
+ max_tool_errors: 3
468020
+ cost_usd: 5.00
468021
+ \`\`\`
468022
+ 6. **Split large pipelines** into smaller YAML files and compose via
468023
+ \`action: run-pipeline\` (nested).
468024
+
468025
+ ## Converting ad-hoc shell sessions to pipelines
468026
+
468027
+ When the user has a working shell sequence, extract steps:
468028
+
468029
+ - Each \`vibe ...\` command becomes one step
468030
+ - File outputs become step outputs; downstream \`-i <file>\` references
468031
+ become \`$<id>.output\`
468032
+ - Shared parameters move to a top-level \`defaults:\` section
468033
+ - Wrap the entire chain in a \`name:\` + \`steps:\` skeleton
468034
+
468035
+ The \`compose\` action is the catch-all assembly step (audio mux, video
468036
+ overlay, etc.) \u2014 useful at the tail of a pipeline.
468037
+ `;
468038
+ var META = {
468039
+ scene: {
468040
+ title: "Scene authoring with vibe",
468041
+ summary: "Author per-scene HTML compositions and render to MP4 (BUILD flow)",
468042
+ steps: [
468043
+ 'Run `vibe scene init <dir> --visual-style "<style name>"` to scaffold the project + install the Hyperframes skill (Plan H).',
468044
+ "Edit `STORYBOARD.md` with per-beat YAML cues (narration / backdrop / duration).",
468045
+ "Read `SKILL.md` for the framework rules and `DESIGN.md` for the visual-identity hard-gate.",
468046
+ "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.",
468047
+ "Run `vibe scene lint --fix` to validate, then `vibe scene render` to produce the MP4."
468048
+ ],
468049
+ relatedCommands: [
468050
+ "vibe scene init",
468051
+ "vibe scene styles",
468052
+ "vibe scene install-skill",
468053
+ "vibe scene compose-prompts",
468054
+ "vibe scene build",
468055
+ "vibe scene lint",
468056
+ "vibe scene render",
468057
+ "vibe scene add"
468058
+ ]
468059
+ },
468060
+ pipeline: {
468061
+ title: "YAML pipelines (Video as Code)",
468062
+ summary: "Author and run reproducible multi-step video workflows",
468063
+ steps: [
468064
+ "Sketch the workflow as YAML \u2014 `name`, `description`, then `steps:` with `id` + `action` + inputs/outputs.",
468065
+ "Reference previous step outputs via `$<step-id>.output` (or `$<step-id>.result.<field>` for structured returns).",
468066
+ "Run `vibe run pipeline.yaml --dry-run` to see the resolved graph + cost estimate before spending API budget.",
468067
+ "Add a `budget:` block (tokens / cost_usd / max_tool_errors) to cap expensive runs.",
468068
+ "Run `vibe run pipeline.yaml` to execute. Failed steps checkpoint to `pipeline.yaml.checkpoint.json`; resume with `--resume`."
468069
+ ],
468070
+ relatedCommands: [
468071
+ "vibe run",
468072
+ "vibe schema --list",
468073
+ "vibe doctor"
468074
+ ]
468075
+ }
468076
+ };
468077
+ var CONTENT2 = {
468078
+ scene: SCENE_WALKTHROUGH,
468079
+ pipeline: PIPELINE_WALKTHROUGH
468080
+ };
468081
+ var WALKTHROUGH_TOPICS = ["scene", "pipeline"];
468082
+ function loadWalkthrough(topic) {
468083
+ const meta = META[topic];
468084
+ const content = CONTENT2[topic];
468085
+ if (!meta || !content) {
468086
+ throw new Error(`Unknown walkthrough topic: ${topic}`);
468087
+ }
468088
+ return {
468089
+ topic,
468090
+ title: meta.title,
468091
+ summary: meta.summary,
468092
+ steps: meta.steps,
468093
+ relatedCommands: meta.relatedCommands,
468094
+ content
468095
+ };
468096
+ }
468097
+ function listWalkthroughs() {
468098
+ return WALKTHROUGH_TOPICS.map((topic) => ({
468099
+ topic,
468100
+ title: META[topic].title,
468101
+ summary: META[topic].summary
468102
+ }));
468103
+ }
468104
+
468105
+ // ../cli/src/tools/manifest/walkthrough.ts
468106
+ var walkthroughSchema = z12.object({
468107
+ topic: z12.enum(WALKTHROUGH_TOPICS).optional().describe(
468108
+ "Walkthrough topic to load. Omit to list every available walkthrough \u2014 useful for discovery on first contact."
468109
+ )
468110
+ });
468111
+ var walkthroughTool = defineTool({
468112
+ name: "walkthrough",
468113
+ category: "agent",
468114
+ cost: "free",
468115
+ description: "Load the step-by-step authoring guide for a vibe workflow (BUILD scene authoring, YAML pipeline authoring). Universal CLI-equivalent of Claude Code's /vibe-* slash commands \u2014 any host agent that calls this tool gets the same content the slash menu delivers in Claude Code, with no Claude Code dependency. Without a topic, returns the catalog of walkthroughs for discovery.",
468116
+ schema: walkthroughSchema,
468117
+ async execute(args) {
468118
+ if (!args.topic) {
468119
+ const topics = listWalkthroughs();
468120
+ return {
468121
+ success: true,
468122
+ data: { action: "list", topics },
468123
+ humanLines: [
468124
+ `Available walkthroughs: ${topics.map((t) => t.topic).join(", ")}.`,
468125
+ `Call again with topic to load full content.`
468126
+ ]
468127
+ };
468128
+ }
468129
+ const result = loadWalkthrough(args.topic);
468130
+ return {
468131
+ success: true,
468132
+ data: { action: "show", ...result },
468133
+ humanLines: [
468134
+ `Loaded walkthrough: ${result.title}.`,
468135
+ `${result.steps.length} steps, ${result.relatedCommands.length} related commands, ${result.content.length} chars of guide content.`
468136
+ ]
468137
+ };
468138
+ }
468139
+ });
468140
+ var walkthroughTools = [walkthroughTool];
468141
+
467464
468142
  // ../cli/src/tools/manifest/index.ts
467465
468143
  var manifest = [
467466
468144
  ...sceneTools,
@@ -467473,15 +468151,16 @@ var manifest = [
467473
468151
  ...timelineTools,
467474
468152
  ...projectTools,
467475
468153
  ...exportTools,
467476
- ...agentOnlyTools
468154
+ ...agentOnlyTools,
468155
+ ...walkthroughTools
467477
468156
  ];
467478
468157
 
467479
468158
  // ../cli/src/tools/zod-to-json-schema.ts
467480
- function getDef(z12) {
467481
- return z12._def;
468159
+ function getDef(z13) {
468160
+ return z13._def;
467482
468161
  }
467483
- function getDescription(z12) {
467484
- let current = z12;
468162
+ function getDescription(z13) {
468163
+ let current = z13;
467485
468164
  while (current) {
467486
468165
  const def = getDef(current);
467487
468166
  if (def.description) return def.description;
@@ -467489,24 +468168,24 @@ function getDescription(z12) {
467489
468168
  }
467490
468169
  return void 0;
467491
468170
  }
467492
- function unwrapOptional(z12) {
467493
- const def = getDef(z12);
468171
+ function unwrapOptional(z13) {
468172
+ const def = getDef(z13);
467494
468173
  if (def.typeName === "ZodOptional" && def.innerType) {
467495
468174
  return { inner: def.innerType, optional: true };
467496
468175
  }
467497
- return { inner: z12, optional: false };
468176
+ return { inner: z13, optional: false };
467498
468177
  }
467499
- function unwrapDefault(z12) {
467500
- const def = getDef(z12);
468178
+ function unwrapDefault(z13) {
468179
+ const def = getDef(z13);
467501
468180
  if (def.typeName === "ZodDefault" && def.innerType && def.defaultValue) {
467502
468181
  return { inner: def.innerType, defaultValue: def.defaultValue() };
467503
468182
  }
467504
- return { inner: z12 };
468183
+ return { inner: z13 };
467505
468184
  }
467506
- function convertLeaf(z12, description) {
467507
- const { inner: afterDefault, defaultValue } = unwrapDefault(z12);
468185
+ function convertLeaf(z13, description) {
468186
+ const { inner: afterDefault, defaultValue } = unwrapDefault(z13);
467508
468187
  const def = getDef(afterDefault);
467509
- const desc = description ?? getDescription(z12);
468188
+ const desc = description ?? getDescription(z13);
467510
468189
  const base = {};
467511
468190
  if (desc) base.description = desc;
467512
468191
  if (defaultValue !== void 0) base.default = defaultValue;
@@ -467553,8 +468232,8 @@ function convertLeaf(z12, description) {
467553
468232
  );
467554
468233
  }
467555
468234
  }
467556
- function convertObject(z12, description) {
467557
- const def = getDef(z12);
468235
+ function convertObject(z13, description) {
468236
+ const def = getDef(z13);
467558
468237
  if (def.typeName !== "ZodObject" || !def.shape) {
467559
468238
  throw new Error(`zod-to-json-schema: convertObject called on non-ZodObject (${def.typeName})`);
467560
468239
  }