@vibeframe/mcp-server 0.31.1 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +369 -171
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1464,17 +1464,93 @@ async function handleTimelineToolCall(name, args) {
1464
1464
  // ../cli/src/commands/export.ts
1465
1465
  import { Command } from "commander";
1466
1466
  import { readFile as readFile2, access, stat } from "node:fs/promises";
1467
- import { resolve as resolve3, basename } from "node:path";
1467
+ import { resolve as resolve4, basename } from "node:path";
1468
1468
  import { spawn } from "node:child_process";
1469
+ import chalk2 from "chalk";
1470
+ import ora2 from "ora";
1471
+ init_exec_safe();
1472
+
1473
+ // ../cli/src/commands/output.ts
1469
1474
  import chalk from "chalk";
1470
1475
  import ora from "ora";
1471
- init_exec_safe();
1476
+ function usageError(msg, suggestion) {
1477
+ return { success: false, error: msg, code: "USAGE_ERROR", exitCode: 2 /* USAGE */, suggestion, retryable: false };
1478
+ }
1479
+ function notFoundError(path) {
1480
+ return { success: false, error: `File not found: ${path}`, code: "NOT_FOUND", exitCode: 3 /* NOT_FOUND */, retryable: false };
1481
+ }
1482
+ function generalError(msg, suggestion) {
1483
+ return { success: false, error: msg, code: "ERROR", exitCode: 1 /* GENERAL */, suggestion, retryable: false };
1484
+ }
1485
+ function exitWithError(err) {
1486
+ if (isJsonMode()) {
1487
+ console.error(JSON.stringify(err, null, 2));
1488
+ } else {
1489
+ console.error(chalk.red(`
1490
+ ${err.error}`));
1491
+ if (err.suggestion) {
1492
+ console.error(chalk.dim(` ${err.suggestion}`));
1493
+ }
1494
+ console.error();
1495
+ }
1496
+ process.exit(err.exitCode);
1497
+ }
1498
+ function isJsonMode() {
1499
+ return process.env.VIBE_JSON_OUTPUT === "1";
1500
+ }
1501
+ function isQuietMode() {
1502
+ return process.env.VIBE_QUIET_OUTPUT === "1";
1503
+ }
1504
+ function outputResult(result) {
1505
+ if (isJsonMode()) {
1506
+ const fields = process.env.VIBE_OUTPUT_FIELDS;
1507
+ if (fields) {
1508
+ const keys = fields.split(",").map((k) => k.trim());
1509
+ const filtered = {};
1510
+ for (const key of keys) {
1511
+ if (key in result) filtered[key] = result[key];
1512
+ }
1513
+ if ("success" in result) filtered.success = result.success;
1514
+ console.log(JSON.stringify(filtered, null, 2));
1515
+ } else {
1516
+ console.log(JSON.stringify(result, null, 2));
1517
+ }
1518
+ } else if (isQuietMode()) {
1519
+ const primary = result.output ?? result.path ?? result.url ?? result.id ?? result.result;
1520
+ if (primary !== void 0) console.log(String(primary));
1521
+ }
1522
+ }
1523
+
1524
+ // ../cli/src/commands/validate.ts
1525
+ import { resolve as resolve3, relative, isAbsolute } from "node:path";
1526
+ function validateOutputPath(path, cwd = process.cwd()) {
1527
+ rejectControlChars(path);
1528
+ if (isAbsolute(path)) {
1529
+ return resolve3(path);
1530
+ }
1531
+ const resolved = resolve3(cwd, path);
1532
+ const rel = relative(cwd, resolved);
1533
+ if (rel.startsWith("..")) {
1534
+ throw new Error(
1535
+ `Output path "${path}" escapes the working directory. Use a path within "${cwd}".`
1536
+ );
1537
+ }
1538
+ return resolved;
1539
+ }
1540
+ function rejectControlChars(input) {
1541
+ if (/[\x00-\x1f\x7f-\x9f]/.test(input)) {
1542
+ throw new Error("Input contains invalid control characters.");
1543
+ }
1544
+ return input;
1545
+ }
1546
+
1547
+ // ../cli/src/commands/export.ts
1472
1548
  async function resolveProjectPath(inputPath) {
1473
- const filePath = resolve3(process.cwd(), inputPath);
1549
+ const filePath = resolve4(process.cwd(), inputPath);
1474
1550
  try {
1475
1551
  const stats = await stat(filePath);
1476
1552
  if (stats.isDirectory()) {
1477
- return resolve3(filePath, "project.vibe.json");
1553
+ return resolve4(filePath, "project.vibe.json");
1478
1554
  }
1479
1555
  } catch {
1480
1556
  }
@@ -1519,7 +1595,7 @@ async function runExport(projectPath, outputPath, options = {}) {
1519
1595
  message: "Project has no clips to export"
1520
1596
  };
1521
1597
  }
1522
- const finalOutputPath = resolve3(process.cwd(), outputPath);
1598
+ const finalOutputPath = resolve4(process.cwd(), outputPath);
1523
1599
  const presetSettings = getPresetSettings(preset, summary.aspectRatio);
1524
1600
  const clips = project.getClips().sort((a, b) => a.startTime - b.startTime);
1525
1601
  const sources = project.getSources();
@@ -1560,24 +1636,37 @@ var exportCommand = new Command("export").description("Export project to video f
1560
1636
  "-p, --preset <preset>",
1561
1637
  "Quality preset (draft, standard, high, ultra)",
1562
1638
  "standard"
1563
- ).option("-y, --overwrite", "Overwrite output file if exists", false).option("-g, --gap-fill <strategy>", "Gap filling strategy (black, extend)", "extend").addHelpText("after", `
1639
+ ).option("-y, --overwrite", "Overwrite output file if exists", false).option("-g, --gap-fill <strategy>", "Gap filling strategy (black, extend)", "extend").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
1564
1640
  Examples:
1565
1641
  $ vibe export project.vibe.json -o output.mp4
1566
1642
  $ vibe export project.vibe.json -o output.mp4 -p high -y
1567
1643
  $ vibe export project.vibe.json -o output.webm -f webm
1568
1644
 
1569
1645
  No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
1570
- const spinner = ora("Checking FFmpeg...").start();
1646
+ const spinner = ora2("Checking FFmpeg...").start();
1571
1647
  try {
1648
+ if (options.output) {
1649
+ validateOutputPath(options.output);
1650
+ }
1651
+ if (options.dryRun) {
1652
+ outputResult({
1653
+ dryRun: true,
1654
+ command: "export",
1655
+ params: {
1656
+ project: projectPath,
1657
+ output: options.output || null,
1658
+ format: options.format,
1659
+ preset: options.preset,
1660
+ overwrite: options.overwrite,
1661
+ gapFill: options.gapFill
1662
+ }
1663
+ });
1664
+ return;
1665
+ }
1572
1666
  const ffmpegPath = await findFFmpeg();
1573
1667
  if (!ffmpegPath) {
1574
- spinner.fail(chalk.red("FFmpeg not found"));
1575
- console.error();
1576
- console.error(chalk.yellow("Please install FFmpeg:"));
1577
- console.error(chalk.dim(" macOS: brew install ffmpeg"));
1578
- console.error(chalk.dim(" Ubuntu: sudo apt install ffmpeg"));
1579
- console.error(chalk.dim(" Windows: winget install ffmpeg"));
1580
- process.exit(1);
1668
+ spinner.fail("FFmpeg not found");
1669
+ exitWithError(generalError("FFmpeg not found", "Install with: brew install ffmpeg (macOS), apt install ffmpeg (Linux), or winget install ffmpeg (Windows)"));
1581
1670
  }
1582
1671
  spinner.text = "Loading project...";
1583
1672
  const filePath = await resolveProjectPath(projectPath);
@@ -1586,10 +1675,10 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
1586
1675
  const project = Project.fromJSON(data);
1587
1676
  const summary = project.getSummary();
1588
1677
  if (summary.clipCount === 0) {
1589
- spinner.fail(chalk.red("Project has no clips to export"));
1590
- process.exit(1);
1678
+ spinner.fail("Project has no clips to export");
1679
+ exitWithError(usageError("Project has no clips to export"));
1591
1680
  }
1592
- const outputPath = options.output ? resolve3(process.cwd(), options.output) : resolve3(
1681
+ const outputPath = options.output ? resolve4(process.cwd(), options.output) : resolve4(
1593
1682
  process.cwd(),
1594
1683
  `${basename(projectPath, ".vibe.json")}.${options.format}`
1595
1684
  );
@@ -1607,8 +1696,8 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
1607
1696
  sourceAudioMap.set(source.id, await checkHasAudio(source.url));
1608
1697
  }
1609
1698
  } catch {
1610
- spinner.fail(chalk.red(`Source file not found: ${source.url}`));
1611
- process.exit(1);
1699
+ spinner.fail(`Source file not found: ${source.url}`);
1700
+ exitWithError(notFoundError(source.url));
1612
1701
  }
1613
1702
  }
1614
1703
  }
@@ -1624,23 +1713,18 @@ No API keys needed. Requires FFmpeg.`).action(async (projectPath, options) => {
1624
1713
  await runFFmpegProcess(ffmpegPath, ffmpegArgs, (progress) => {
1625
1714
  spinner.text = `Encoding... ${progress}%`;
1626
1715
  });
1627
- spinner.succeed(chalk.green(`Exported: ${outputPath}`));
1716
+ spinner.succeed(chalk2.green(`Exported: ${outputPath}`));
1628
1717
  console.log();
1629
- console.log(chalk.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
1630
- console.log(chalk.dim(" Clips:"), summary.clipCount);
1631
- console.log(chalk.dim(" Format:"), options.format);
1632
- console.log(chalk.dim(" Preset:"), options.preset);
1633
- console.log(chalk.dim(" Resolution:"), presetSettings.resolution);
1718
+ console.log(chalk2.dim(" Duration:"), `${summary.duration.toFixed(1)}s`);
1719
+ console.log(chalk2.dim(" Clips:"), summary.clipCount);
1720
+ console.log(chalk2.dim(" Format:"), options.format);
1721
+ console.log(chalk2.dim(" Preset:"), options.preset);
1722
+ console.log(chalk2.dim(" Resolution:"), presetSettings.resolution);
1634
1723
  console.log();
1635
1724
  } catch (error) {
1636
- spinner.fail(chalk.red("Export failed"));
1637
- if (error instanceof Error) {
1638
- console.error(chalk.red(error.message));
1639
- if (process.env.DEBUG) {
1640
- console.error(error.stack);
1641
- }
1642
- }
1643
- process.exit(1);
1725
+ spinner.fail("Export failed");
1726
+ const msg = error instanceof Error ? error.message : String(error);
1727
+ exitWithError(generalError(`Export failed: ${msg}`));
1644
1728
  }
1645
1729
  });
1646
1730
  async function findFFmpeg() {
@@ -1862,62 +1946,95 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
1862
1946
  videoStreamIdx++;
1863
1947
  }
1864
1948
  }
1865
- const audioGaps = detectTimelineGaps(audioClips, totalDuration);
1866
- const audioSegments = [];
1949
+ const audioTrackMap = /* @__PURE__ */ new Map();
1867
1950
  for (const clip of audioClips) {
1868
- audioSegments.push({ type: "clip", clip, startTime: clip.startTime });
1869
- }
1870
- for (const gap of audioGaps) {
1871
- audioSegments.push({ type: "gap", gap, startTime: gap.start });
1951
+ const trackId = clip.trackId || "audio-track-1";
1952
+ if (!audioTrackMap.has(trackId)) {
1953
+ audioTrackMap.set(trackId, []);
1954
+ }
1955
+ audioTrackMap.get(trackId).push(clip);
1872
1956
  }
1873
- audioSegments.sort((a, b) => a.startTime - b.startTime);
1874
- const audioStreams = [];
1957
+ const videoDuration = videoClips.length > 0 ? Math.max(...videoClips.map((c) => c.startTime + c.duration)) : 0;
1958
+ const audioDuration = audioClips.length > 0 ? Math.max(...audioClips.map((c) => c.startTime + c.duration)) : 0;
1959
+ const timelineDuration = totalDuration || Math.max(videoDuration, audioDuration);
1960
+ const trackOutputLabels = [];
1875
1961
  let audioStreamIdx = 0;
1876
- for (const segment of audioSegments) {
1877
- if (segment.type === "clip" && segment.clip) {
1878
- const clip = segment.clip;
1879
- const source = sources.find((s) => s.id === clip.sourceId);
1880
- if (!source) continue;
1881
- const srcIdx = sourceMap.get(source.id);
1882
- if (srcIdx === void 0) continue;
1883
- const hasAudio = source.type === "audio" || sourceAudioMap.get(source.id) === true;
1884
- let audioFilter;
1885
- if (hasAudio) {
1886
- const audioTrimStart = clip.sourceStartOffset;
1887
- const audioTrimEnd = clip.sourceStartOffset + clip.duration;
1888
- const sourceDuration = source.duration || 0;
1889
- const clipDuration = clip.duration;
1890
- if (source.type === "audio" && sourceDuration > clipDuration && audioTrimStart === 0) {
1891
- const tempo = sourceDuration / clipDuration;
1892
- if (tempo <= 2) {
1893
- audioFilter = `[${srcIdx}:a]atempo=${tempo.toFixed(4)},asetpts=PTS-STARTPTS`;
1962
+ for (const [, trackClips] of audioTrackMap) {
1963
+ const sorted = [...trackClips].sort((a, b) => a.startTime - b.startTime);
1964
+ const trackGaps = detectTimelineGaps(sorted, timelineDuration);
1965
+ const segments = [];
1966
+ for (const clip of sorted) {
1967
+ segments.push({ type: "clip", clip, startTime: clip.startTime });
1968
+ }
1969
+ for (const gap of trackGaps) {
1970
+ segments.push({ type: "gap", gap, startTime: gap.start });
1971
+ }
1972
+ segments.sort((a, b) => a.startTime - b.startTime);
1973
+ const segmentLabels = [];
1974
+ for (const segment of segments) {
1975
+ if (segment.type === "clip" && segment.clip) {
1976
+ const clip = segment.clip;
1977
+ const source = sources.find((s) => s.id === clip.sourceId);
1978
+ if (!source) continue;
1979
+ const srcIdx = sourceMap.get(source.id);
1980
+ if (srcIdx === void 0) continue;
1981
+ const hasAudio = source.type === "audio" || sourceAudioMap.get(source.id) === true;
1982
+ let audioFilter;
1983
+ if (hasAudio) {
1984
+ const audioTrimStart = clip.sourceStartOffset;
1985
+ const audioTrimEnd = clip.sourceStartOffset + clip.duration;
1986
+ const sourceDuration = source.duration || 0;
1987
+ const clipDuration = clip.duration;
1988
+ if (source.type === "audio" && sourceDuration > clipDuration && audioTrimStart === 0) {
1989
+ const tempo = sourceDuration / clipDuration;
1990
+ if (tempo <= 2) {
1991
+ audioFilter = `[${srcIdx}:a]atempo=${tempo.toFixed(4)},asetpts=PTS-STARTPTS`;
1992
+ } else {
1993
+ audioFilter = `[${srcIdx}:a]atrim=start=${audioTrimStart}:end=${audioTrimEnd},asetpts=PTS-STARTPTS`;
1994
+ }
1894
1995
  } else {
1895
1996
  audioFilter = `[${srcIdx}:a]atrim=start=${audioTrimStart}:end=${audioTrimEnd},asetpts=PTS-STARTPTS`;
1896
1997
  }
1897
1998
  } else {
1898
- audioFilter = `[${srcIdx}:a]atrim=start=${audioTrimStart}:end=${audioTrimEnd},asetpts=PTS-STARTPTS`;
1999
+ audioFilter = `anullsrc=r=48000:cl=stereo,atrim=0:${clip.duration},asetpts=PTS-STARTPTS`;
1899
2000
  }
1900
- } else {
1901
- audioFilter = `anullsrc=r=48000:cl=stereo,atrim=0:${clip.duration},asetpts=PTS-STARTPTS`;
1902
- }
1903
- for (const effect of clip.effects || []) {
1904
- if (effect.type === "fadeIn") {
1905
- audioFilter += `,afade=t=in:st=0:d=${effect.duration}`;
1906
- } else if (effect.type === "fadeOut") {
1907
- const fadeStart = clip.duration - effect.duration;
1908
- audioFilter += `,afade=t=out:st=${fadeStart}:d=${effect.duration}`;
2001
+ const clipVolume = clip.volume;
2002
+ if (clipVolume !== void 0 && clipVolume !== 1) {
2003
+ audioFilter += `,volume=${clipVolume.toFixed(2)}`;
2004
+ }
2005
+ for (const effect of clip.effects || []) {
2006
+ if (effect.type === "fadeIn") {
2007
+ audioFilter += `,afade=t=in:st=0:d=${effect.duration}`;
2008
+ } else if (effect.type === "fadeOut") {
2009
+ const fadeStart = clip.duration - effect.duration;
2010
+ audioFilter += `,afade=t=out:st=${fadeStart}:d=${effect.duration}`;
2011
+ } else if (effect.type === "volume" && effect.params?.level !== void 0) {
2012
+ audioFilter += `,volume=${effect.params.level}`;
2013
+ }
2014
+ }
2015
+ const label = `a${audioStreamIdx}`;
2016
+ audioFilter += `[${label}]`;
2017
+ filterParts.push(audioFilter);
2018
+ segmentLabels.push(`[${label}]`);
2019
+ audioStreamIdx++;
2020
+ } else if (segment.type === "gap" && segment.gap) {
2021
+ const gapDuration = segment.gap.end - segment.gap.start;
2022
+ if (gapDuration > 1e-3) {
2023
+ const label = `a${audioStreamIdx}`;
2024
+ filterParts.push(`anullsrc=r=48000:cl=stereo,atrim=0:${gapDuration.toFixed(4)},asetpts=PTS-STARTPTS[${label}]`);
2025
+ segmentLabels.push(`[${label}]`);
2026
+ audioStreamIdx++;
1909
2027
  }
1910
2028
  }
1911
- audioFilter += `[a${audioStreamIdx}]`;
1912
- filterParts.push(audioFilter);
1913
- audioStreams.push(`[a${audioStreamIdx}]`);
1914
- audioStreamIdx++;
1915
- } else if (segment.type === "gap" && segment.gap) {
1916
- const gapDuration = segment.gap.end - segment.gap.start;
1917
- const audioGapFilter = `anullsrc=r=48000:cl=stereo,atrim=0:${gapDuration},asetpts=PTS-STARTPTS[a${audioStreamIdx}]`;
1918
- filterParts.push(audioGapFilter);
1919
- audioStreams.push(`[a${audioStreamIdx}]`);
1920
- audioStreamIdx++;
2029
+ }
2030
+ if (segmentLabels.length > 1) {
2031
+ const trackLabel = `atrack${trackOutputLabels.length}`;
2032
+ filterParts.push(`${segmentLabels.join("")}concat=n=${segmentLabels.length}:v=0:a=1[${trackLabel}]`);
2033
+ trackOutputLabels.push(`[${trackLabel}]`);
2034
+ } else if (segmentLabels.length === 1) {
2035
+ const trackLabel = `atrack${trackOutputLabels.length}`;
2036
+ filterParts.push(`${segmentLabels[0]}acopy[${trackLabel}]`);
2037
+ trackOutputLabels.push(`[${trackLabel}]`);
1921
2038
  }
1922
2039
  }
1923
2040
  if (videoStreams.length > 1) {
@@ -1927,16 +2044,16 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
1927
2044
  } else if (videoStreams.length === 1) {
1928
2045
  filterParts.push(`${videoStreams[0]}copy[outv]`);
1929
2046
  }
1930
- if (audioStreams.length > 1) {
2047
+ if (trackOutputLabels.length > 1) {
1931
2048
  filterParts.push(
1932
- `${audioStreams.join("")}concat=n=${audioStreams.length}:v=0:a=1[outa]`
2049
+ `${trackOutputLabels.join("")}amix=inputs=${trackOutputLabels.length}:duration=longest:normalize=0[outa]`
1933
2050
  );
1934
- } else if (audioStreams.length === 1) {
1935
- filterParts.push(`${audioStreams[0]}acopy[outa]`);
2051
+ } else if (trackOutputLabels.length === 1) {
2052
+ filterParts.push(`${trackOutputLabels[0]}acopy[outa]`);
1936
2053
  }
1937
2054
  args.push("-filter_complex", filterParts.join(";"));
1938
2055
  args.push("-map", "[outv]");
1939
- if (audioStreams.length > 0) {
2056
+ if (trackOutputLabels.length > 0) {
1940
2057
  args.push("-map", "[outa]");
1941
2058
  }
1942
2059
  args.push(...presetSettings.ffmpegArgs);
@@ -1944,7 +2061,7 @@ function buildFFmpegArgs(clips, sources, presetSettings, outputPath, options, so
1944
2061
  return args;
1945
2062
  }
1946
2063
  function runFFmpegProcess(ffmpegPath, args, onProgress) {
1947
- return new Promise((resolve12, reject) => {
2064
+ return new Promise((resolve13, reject) => {
1948
2065
  const ffmpeg = spawn(ffmpegPath, args, {
1949
2066
  stdio: ["pipe", "pipe", "pipe"]
1950
2067
  });
@@ -1968,7 +2085,7 @@ function runFFmpegProcess(ffmpegPath, args, onProgress) {
1968
2085
  });
1969
2086
  ffmpeg.on("close", (code) => {
1970
2087
  if (code === 0) {
1971
- resolve12();
2088
+ resolve13();
1972
2089
  } else {
1973
2090
  const errorMatch = stderr.match(/Error.*$/m);
1974
2091
  const errorMsg = errorMatch ? errorMatch[0] : `FFmpeg exited with code ${code}`;
@@ -2108,7 +2225,7 @@ async function handleExportToolCall(name, args) {
2108
2225
  }
2109
2226
 
2110
2227
  // ../cli/src/commands/ai-edit.ts
2111
- import { resolve as resolve6, dirname, basename as basename2, extname, join as join2 } from "node:path";
2228
+ import { resolve as resolve7, dirname, basename as basename2, extname, join as join2 } from "node:path";
2112
2229
  import { readFile as readFile5, writeFile as writeFile5, mkdir as mkdir3 } from "node:fs/promises";
2113
2230
  import { existsSync as existsSync2 } from "node:fs";
2114
2231
 
@@ -2913,7 +3030,7 @@ var GeminiProvider = class {
2913
3030
  * Sleep helper
2914
3031
  */
2915
3032
  sleep(ms) {
2916
- return new Promise((resolve12) => setTimeout(resolve12, ms));
3033
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
2917
3034
  }
2918
3035
  /**
2919
3036
  * Extend a previously generated Veo video
@@ -6756,7 +6873,7 @@ var RunwayProvider = class {
6756
6873
  * Sleep helper
6757
6874
  */
6758
6875
  sleep(ms) {
6759
- return new Promise((resolve12) => setTimeout(resolve12, ms));
6876
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
6760
6877
  }
6761
6878
  };
6762
6879
  var runwayProvider = new RunwayProvider();
@@ -7172,7 +7289,7 @@ var KlingProvider = class {
7172
7289
  * Sleep helper
7173
7290
  */
7174
7291
  sleep(ms) {
7175
- return new Promise((resolve12) => setTimeout(resolve12, ms));
7292
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
7176
7293
  }
7177
7294
  };
7178
7295
  var klingProvider = new KlingProvider();
@@ -7467,7 +7584,7 @@ var GrokProvider = class {
7467
7584
  }
7468
7585
  }
7469
7586
  sleep(ms) {
7470
- return new Promise((resolve12) => setTimeout(resolve12, ms));
7587
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
7471
7588
  }
7472
7589
  };
7473
7590
  var grokProvider = new GrokProvider();
@@ -7726,7 +7843,7 @@ var ReplicateProvider = class {
7726
7843
  * Sleep helper
7727
7844
  */
7728
7845
  sleep(ms) {
7729
- return new Promise((resolve12) => setTimeout(resolve12, ms));
7846
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
7730
7847
  }
7731
7848
  /**
7732
7849
  * Generate music from text prompt using MusicGen
@@ -8110,12 +8227,12 @@ var replicateProvider = new ReplicateProvider();
8110
8227
  // ../cli/src/utils/api-key.ts
8111
8228
  import { createInterface } from "node:readline";
8112
8229
  import { readFile as readFile4, writeFile as writeFile3, access as access3 } from "node:fs/promises";
8113
- import { resolve as resolve5 } from "node:path";
8230
+ import { resolve as resolve6 } from "node:path";
8114
8231
  import { config } from "dotenv";
8115
- import chalk2 from "chalk";
8232
+ import chalk3 from "chalk";
8116
8233
 
8117
8234
  // ../cli/src/config/index.ts
8118
- import { resolve as resolve4 } from "node:path";
8235
+ import { resolve as resolve5 } from "node:path";
8119
8236
  import { homedir } from "node:os";
8120
8237
  import { readFile as readFile3, writeFile as writeFile2, mkdir, access as access2 } from "node:fs/promises";
8121
8238
  import { parse, stringify } from "yaml";
@@ -8151,8 +8268,8 @@ function createDefaultConfig() {
8151
8268
  }
8152
8269
 
8153
8270
  // ../cli/src/config/index.ts
8154
- var CONFIG_DIR = resolve4(homedir(), ".vibeframe");
8155
- var CONFIG_PATH = resolve4(CONFIG_DIR, "config.yaml");
8271
+ var CONFIG_DIR = resolve5(homedir(), ".vibeframe");
8272
+ var CONFIG_PATH = resolve5(CONFIG_DIR, "config.yaml");
8156
8273
  async function loadConfig() {
8157
8274
  try {
8158
8275
  await access2(CONFIG_PATH);
@@ -8185,20 +8302,20 @@ async function getApiKeyFromConfig(providerKey) {
8185
8302
 
8186
8303
  // ../cli/src/utils/api-key.ts
8187
8304
  function loadEnv() {
8188
- config({ path: resolve5(process.cwd(), ".env"), debug: false });
8305
+ config({ path: resolve6(process.cwd(), ".env"), debug: false, quiet: true });
8189
8306
  const monorepoRoot = findMonorepoRoot();
8190
8307
  if (monorepoRoot && monorepoRoot !== process.cwd()) {
8191
- config({ path: resolve5(monorepoRoot, ".env"), debug: false });
8308
+ config({ path: resolve6(monorepoRoot, ".env"), debug: false, quiet: true });
8192
8309
  }
8193
8310
  }
8194
8311
  function findMonorepoRoot() {
8195
8312
  let dir = process.cwd();
8196
8313
  while (dir !== "/") {
8197
8314
  try {
8198
- __require.resolve(resolve5(dir, "pnpm-workspace.yaml"));
8315
+ __require.resolve(resolve6(dir, "pnpm-workspace.yaml"));
8199
8316
  return dir;
8200
8317
  } catch {
8201
- dir = resolve5(dir, "..");
8318
+ dir = resolve6(dir, "..");
8202
8319
  }
8203
8320
  }
8204
8321
  return null;
@@ -8208,7 +8325,7 @@ async function prompt(question, hidden = false) {
8208
8325
  input: process.stdin,
8209
8326
  output: process.stdout
8210
8327
  });
8211
- return new Promise((resolve12) => {
8328
+ return new Promise((resolve13) => {
8212
8329
  if (hidden && process.stdin.isTTY) {
8213
8330
  process.stdout.write(question);
8214
8331
  let input = "";
@@ -8222,7 +8339,7 @@ async function prompt(question, hidden = false) {
8222
8339
  process.stdin.removeListener("data", onData);
8223
8340
  process.stdout.write("\n");
8224
8341
  rl.close();
8225
- resolve12(input);
8342
+ resolve13(input);
8226
8343
  } else if (char === "") {
8227
8344
  process.exit(1);
8228
8345
  } else if (char === "\x7F" || char === "\b") {
@@ -8237,7 +8354,7 @@ async function prompt(question, hidden = false) {
8237
8354
  } else {
8238
8355
  rl.question(question, (answer) => {
8239
8356
  rl.close();
8240
- resolve12(answer);
8357
+ resolve13(answer);
8241
8358
  });
8242
8359
  }
8243
8360
  });
@@ -8250,9 +8367,11 @@ async function getApiKey(envVar, providerName, optionValue) {
8250
8367
  ANTHROPIC_API_KEY: "anthropic",
8251
8368
  OPENAI_API_KEY: "openai",
8252
8369
  GOOGLE_API_KEY: "google",
8370
+ XAI_API_KEY: "xai",
8253
8371
  ELEVENLABS_API_KEY: "elevenlabs",
8254
8372
  RUNWAY_API_SECRET: "runway",
8255
8373
  KLING_API_KEY: "kling",
8374
+ OPENROUTER_API_KEY: "openrouter",
8256
8375
  IMGBB_API_KEY: "imgbb",
8257
8376
  REPLICATE_API_TOKEN: "replicate"
8258
8377
  };
@@ -8272,22 +8391,22 @@ async function getApiKey(envVar, providerName, optionValue) {
8272
8391
  return null;
8273
8392
  }
8274
8393
  console.log();
8275
- console.log(chalk2.yellow(`${providerName} API key not found.`));
8276
- console.log(chalk2.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`));
8394
+ console.log(chalk3.yellow(`${providerName} API key not found.`));
8395
+ console.log(chalk3.dim(`Set ${envVar} in .env (current directory), run 'vibe setup', or enter below.`));
8277
8396
  console.log();
8278
- const apiKey = await prompt(chalk2.cyan(`Enter ${providerName} API key: `), true);
8397
+ const apiKey = await prompt(chalk3.cyan(`Enter ${providerName} API key: `), true);
8279
8398
  if (!apiKey || apiKey.trim() === "") {
8280
8399
  return null;
8281
8400
  }
8282
- const save = await prompt(chalk2.cyan("Save to .env for future use? (y/N): "));
8401
+ const save = await prompt(chalk3.cyan("Save to .env for future use? (y/N): "));
8283
8402
  if (save.toLowerCase() === "y" || save.toLowerCase() === "yes") {
8284
8403
  await saveApiKeyToEnv(envVar, apiKey.trim());
8285
- console.log(chalk2.green("API key saved to .env"));
8404
+ console.log(chalk3.green("API key saved to .env"));
8286
8405
  }
8287
8406
  return apiKey.trim();
8288
8407
  }
8289
8408
  async function saveApiKeyToEnv(envVar, apiKey) {
8290
- const envPath = resolve5(process.cwd(), ".env");
8409
+ const envPath = resolve6(process.cwd(), ".env");
8291
8410
  let content = "";
8292
8411
  try {
8293
8412
  await access3(envPath);
@@ -9256,8 +9375,8 @@ async function applyTextOverlays(options) {
9256
9375
  if (!texts || texts.length === 0) {
9257
9376
  return { success: false, error: "No texts provided" };
9258
9377
  }
9259
- const absVideoPath = resolve6(process.cwd(), videoPath);
9260
- const absOutputPath = resolve6(process.cwd(), outputPath);
9378
+ const absVideoPath = resolve7(process.cwd(), videoPath);
9379
+ const absOutputPath = resolve7(process.cwd(), outputPath);
9261
9380
  if (!existsSync2(absVideoPath)) {
9262
9381
  return { success: false, error: `Video not found: ${absVideoPath}` };
9263
9382
  }
@@ -9628,10 +9747,10 @@ async function handleAiEditingToolCall(name, args) {
9628
9747
 
9629
9748
  // ../cli/src/commands/ai-analyze.ts
9630
9749
  import { readFile as readFile6 } from "node:fs/promises";
9631
- import { extname as extname2, resolve as resolve7 } from "node:path";
9750
+ import { extname as extname2, resolve as resolve8 } from "node:path";
9632
9751
  import { existsSync as existsSync3 } from "node:fs";
9633
- import chalk3 from "chalk";
9634
- import ora2 from "ora";
9752
+ import chalk4 from "chalk";
9753
+ import ora3 from "ora";
9635
9754
  async function executeGeminiVideo(options) {
9636
9755
  try {
9637
9756
  const apiKey = await getApiKey("GOOGLE_API_KEY", "Google");
@@ -9649,7 +9768,7 @@ async function executeGeminiVideo(options) {
9649
9768
  if (isYouTube) {
9650
9769
  videoData = options.source;
9651
9770
  } else {
9652
- const absPath = resolve7(process.cwd(), options.source);
9771
+ const absPath = resolve8(process.cwd(), options.source);
9653
9772
  if (!existsSync3(absPath)) {
9654
9773
  return { success: false, error: `File not found: ${absPath}` };
9655
9774
  }
@@ -9722,7 +9841,7 @@ async function executeAnalyze(options) {
9722
9841
  }
9723
9842
  imageBuffer = Buffer.from(await response.arrayBuffer());
9724
9843
  } else {
9725
- const absPath = resolve7(process.cwd(), source);
9844
+ const absPath = resolve8(process.cwd(), source);
9726
9845
  if (!existsSync3(absPath)) {
9727
9846
  return { success: false, error: `File not found: ${absPath}` };
9728
9847
  }
@@ -9757,7 +9876,7 @@ async function executeAnalyze(options) {
9757
9876
  }
9758
9877
  videoData = Buffer.from(await response.arrayBuffer());
9759
9878
  } else {
9760
- const absPath = resolve7(process.cwd(), source);
9879
+ const absPath = resolve8(process.cwd(), source);
9761
9880
  if (!existsSync3(absPath)) {
9762
9881
  return { success: false, error: `File not found: ${absPath}` };
9763
9882
  }
@@ -9792,10 +9911,10 @@ async function executeAnalyze(options) {
9792
9911
 
9793
9912
  // ../cli/src/commands/ai-review.ts
9794
9913
  import { readFile as readFile7, rename } from "node:fs/promises";
9795
- import { resolve as resolve8 } from "node:path";
9914
+ import { resolve as resolve9 } from "node:path";
9796
9915
  import { existsSync as existsSync4 } from "node:fs";
9797
- import chalk4 from "chalk";
9798
- import ora3 from "ora";
9916
+ import chalk5 from "chalk";
9917
+ import ora4 from "ora";
9799
9918
  init_exec_safe();
9800
9919
  function parseReviewFeedback(response) {
9801
9920
  let cleaned = response.trim();
@@ -9820,7 +9939,7 @@ function parseReviewFeedback(response) {
9820
9939
  }
9821
9940
  async function executeReview(options) {
9822
9941
  const { videoPath, storyboardPath, autoApply = false, verify = false, model = "flash" } = options;
9823
- const absVideoPath = resolve8(process.cwd(), videoPath);
9942
+ const absVideoPath = resolve9(process.cwd(), videoPath);
9824
9943
  if (!existsSync4(absVideoPath)) {
9825
9944
  return { success: false, error: `Video not found: ${absVideoPath}` };
9826
9945
  }
@@ -9830,7 +9949,7 @@ async function executeReview(options) {
9830
9949
  }
9831
9950
  let storyboardContext = "";
9832
9951
  if (storyboardPath) {
9833
- const absStoryboardPath = resolve8(process.cwd(), storyboardPath);
9952
+ const absStoryboardPath = resolve9(process.cwd(), storyboardPath);
9834
9953
  if (existsSync4(absStoryboardPath)) {
9835
9954
  const content = await readFile7(absStoryboardPath, "utf-8");
9836
9955
  storyboardContext = `
@@ -9886,7 +10005,7 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
9886
10005
  };
9887
10006
  if (autoApply && feedback.autoFixable.length > 0) {
9888
10007
  let currentInput = absVideoPath;
9889
- const outputBase = options.outputPath ? resolve8(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
10008
+ const outputBase = options.outputPath ? resolve9(process.cwd(), options.outputPath) : absVideoPath.replace(/(\.[^.]+)$/, "-reviewed$1");
9890
10009
  for (const fix of feedback.autoFixable) {
9891
10010
  if (fix.type === "color_grade" && fix.ffmpegFilter) {
9892
10011
  try {
@@ -9935,8 +10054,8 @@ Score each category 1-10. For fixable issues, provide an FFmpeg filter in autoFi
9935
10054
  // ../cli/src/commands/ai-image.ts
9936
10055
  import { readFile as readFile8, writeFile as writeFile6, mkdir as mkdir4 } from "node:fs/promises";
9937
10056
  import { existsSync as existsSync5 } from "node:fs";
9938
- import chalk5 from "chalk";
9939
- import ora4 from "ora";
10057
+ import chalk6 from "chalk";
10058
+ import ora5 from "ora";
9940
10059
  init_exec_safe();
9941
10060
  async function executeThumbnailBestFrame(options) {
9942
10061
  const {
@@ -10158,9 +10277,9 @@ async function handleAiAnalysisToolCall(name, args) {
10158
10277
 
10159
10278
  // ../cli/src/commands/ai-script-pipeline.ts
10160
10279
  import { readFile as readFile9, writeFile as writeFile7, mkdir as mkdir5, unlink, rename as rename2 } from "node:fs/promises";
10161
- import { resolve as resolve9, basename as basename3, extname as extname3 } from "node:path";
10280
+ import { resolve as resolve10, basename as basename3, extname as extname3 } from "node:path";
10162
10281
  import { existsSync as existsSync6 } from "node:fs";
10163
- import chalk6 from "chalk";
10282
+ import chalk7 from "chalk";
10164
10283
  init_exec_safe();
10165
10284
 
10166
10285
  // ../cli/src/commands/ai-helpers.ts
@@ -10180,7 +10299,37 @@ async function downloadVideo(url, apiKey) {
10180
10299
  var DEFAULT_VIDEO_RETRIES = 2;
10181
10300
  var RETRY_DELAY_MS = 5e3;
10182
10301
  function sleep(ms) {
10183
- return new Promise((resolve12) => setTimeout(resolve12, ms));
10302
+ return new Promise((resolve13) => setTimeout(resolve13, ms));
10303
+ }
10304
+ async function generateVideoWithRetryGrok(grok, segment, options, maxRetries, onProgress) {
10305
+ const prompt2 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
10306
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
10307
+ try {
10308
+ const result = await grok.generateVideo(prompt2, {
10309
+ prompt: prompt2,
10310
+ duration: options.duration,
10311
+ aspectRatio: options.aspectRatio,
10312
+ referenceImage: options.referenceImage
10313
+ });
10314
+ if (result.status !== "failed" && result.id) {
10315
+ return { requestId: result.id };
10316
+ }
10317
+ if (attempt < maxRetries) {
10318
+ onProgress?.(`\u26A0 Retry ${attempt + 1}/${maxRetries}...`);
10319
+ await sleep(RETRY_DELAY_MS);
10320
+ }
10321
+ } catch (err) {
10322
+ const errMsg = err instanceof Error ? err.message : String(err);
10323
+ if (attempt < maxRetries) {
10324
+ onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
10325
+ await sleep(RETRY_DELAY_MS);
10326
+ } else {
10327
+ console.error(chalk7.dim(`
10328
+ [Grok error: ${errMsg}]`));
10329
+ }
10330
+ }
10331
+ }
10332
+ return null;
10184
10333
  }
10185
10334
  async function generateVideoWithRetryKling(kling, segment, options, maxRetries, onProgress) {
10186
10335
  const prompt2 = segment.visualStyle ? `${segment.visuals}. Style: ${segment.visualStyle}` : segment.visuals;
@@ -10211,7 +10360,7 @@ async function generateVideoWithRetryKling(kling, segment, options, maxRetries,
10211
10360
  onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
10212
10361
  await sleep(RETRY_DELAY_MS);
10213
10362
  } else {
10214
- console.error(chalk6.dim(`
10363
+ console.error(chalk7.dim(`
10215
10364
  [Kling error: ${errMsg}]`));
10216
10365
  }
10217
10366
  }
@@ -10240,7 +10389,7 @@ async function generateVideoWithRetryRunway(runway, segment, referenceImage, opt
10240
10389
  onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
10241
10390
  await sleep(RETRY_DELAY_MS);
10242
10391
  } else {
10243
- console.error(chalk6.dim(`
10392
+ console.error(chalk7.dim(`
10244
10393
  [Runway error: ${errMsg}]`));
10245
10394
  }
10246
10395
  }
@@ -10271,7 +10420,7 @@ async function generateVideoWithRetryVeo(gemini, segment, options, maxRetries, o
10271
10420
  onProgress?.(`\u26A0 Error: ${errMsg.slice(0, 50)}... retry ${attempt + 1}/${maxRetries}`);
10272
10421
  await sleep(RETRY_DELAY_MS);
10273
10422
  } else {
10274
- console.error(chalk6.dim(`
10423
+ console.error(chalk7.dim(`
10275
10424
  [Veo error: ${errMsg}]`));
10276
10425
  }
10277
10426
  }
@@ -10326,24 +10475,23 @@ async function executeScriptToVideo(options) {
10326
10475
  }
10327
10476
  let videoApiKey;
10328
10477
  if (!options.imagesOnly) {
10329
- if (options.generator === "kling") {
10330
- videoApiKey = await getApiKey("KLING_API_KEY", "Kling") ?? void 0;
10331
- if (!videoApiKey) {
10332
- return { success: false, outputDir, scenes: 0, error: "Kling API key required (or use imagesOnly option). Run 'vibe setup' or set KLING_API_KEY in .env" };
10333
- }
10334
- } else if (options.generator === "veo") {
10335
- videoApiKey = await getApiKey("GOOGLE_API_KEY", "Google") ?? void 0;
10336
- if (!videoApiKey) {
10337
- return { success: false, outputDir, scenes: 0, error: "Google API key required for Veo video generation (or use imagesOnly option). Run 'vibe setup' or set GOOGLE_API_KEY in .env" };
10338
- }
10339
- } else {
10340
- videoApiKey = await getApiKey("RUNWAY_API_SECRET", "Runway") ?? void 0;
10341
- if (!videoApiKey) {
10342
- return { success: false, outputDir, scenes: 0, error: "Runway API key required (or use imagesOnly option). Run 'vibe setup' or set RUNWAY_API_SECRET in .env" };
10343
- }
10478
+ const generatorKeyMap = {
10479
+ grok: { envVar: "XAI_API_KEY", name: "xAI (Grok)" },
10480
+ kling: { envVar: "KLING_API_KEY", name: "Kling" },
10481
+ runway: { envVar: "RUNWAY_API_SECRET", name: "Runway" },
10482
+ veo: { envVar: "GOOGLE_API_KEY", name: "Google (Veo)" }
10483
+ };
10484
+ const generator = options.generator || "grok";
10485
+ const generatorInfo = generatorKeyMap[generator];
10486
+ if (!generatorInfo) {
10487
+ return { success: false, outputDir, scenes: 0, error: `Invalid generator: ${options.generator}. Available: ${Object.keys(generatorKeyMap).join(", ")}` };
10488
+ }
10489
+ videoApiKey = await getApiKey(generatorInfo.envVar, generatorInfo.name) ?? void 0;
10490
+ if (!videoApiKey) {
10491
+ return { success: false, outputDir, scenes: 0, error: `${generatorInfo.name} API key required (or use imagesOnly option). Run 'vibe setup' or set ${generatorInfo.envVar} in .env` };
10344
10492
  }
10345
10493
  }
10346
- const absOutputDir = resolve9(process.cwd(), outputDir);
10494
+ const absOutputDir = resolve10(process.cwd(), outputDir);
10347
10495
  if (!existsSync6(absOutputDir)) {
10348
10496
  await mkdir5(absOutputDir, { recursive: true });
10349
10497
  }
@@ -10365,7 +10513,7 @@ async function executeScriptToVideo(options) {
10365
10513
  if (segments.length === 0) {
10366
10514
  return { success: false, outputDir, scenes: 0, error: "Failed to generate storyboard" };
10367
10515
  }
10368
- const storyboardPath = resolve9(absOutputDir, "storyboard.json");
10516
+ const storyboardPath = resolve10(absOutputDir, "storyboard.json");
10369
10517
  await writeFile7(storyboardPath, JSON.stringify(segments, null, 2), "utf-8");
10370
10518
  const result = {
10371
10519
  success: true,
@@ -10399,7 +10547,7 @@ async function executeScriptToVideo(options) {
10399
10547
  voiceId: options.voice
10400
10548
  });
10401
10549
  if (ttsResult.success && ttsResult.audioBuffer) {
10402
- const audioPath = resolve9(absOutputDir, `narration-${i + 1}.mp3`);
10550
+ const audioPath = resolve10(absOutputDir, `narration-${i + 1}.mp3`);
10403
10551
  await writeFile7(audioPath, ttsResult.audioBuffer);
10404
10552
  const actualDuration = await getAudioDuration(audioPath);
10405
10553
  segment.duration = actualDuration;
@@ -10487,7 +10635,7 @@ async function executeScriptToVideo(options) {
10487
10635
  }
10488
10636
  }
10489
10637
  }
10490
- const imagePath = resolve9(absOutputDir, `scene-${i + 1}.png`);
10638
+ const imagePath = resolve10(absOutputDir, `scene-${i + 1}.png`);
10491
10639
  if (imageBuffer) {
10492
10640
  await writeFile7(imagePath, imageBuffer);
10493
10641
  imagePaths.push(imagePath);
@@ -10511,7 +10659,57 @@ async function executeScriptToVideo(options) {
10511
10659
  const videoPaths = [];
10512
10660
  const maxRetries = options.retries ?? DEFAULT_VIDEO_RETRIES;
10513
10661
  if (!options.imagesOnly && videoApiKey) {
10514
- if (options.generator === "kling") {
10662
+ if (options.generator === "grok") {
10663
+ const grok = new GrokProvider();
10664
+ await grok.initialize({ apiKey: videoApiKey });
10665
+ for (let i = 0; i < segments.length; i++) {
10666
+ if (!imagePaths[i]) {
10667
+ videoPaths.push("");
10668
+ continue;
10669
+ }
10670
+ const segment = segments[i];
10671
+ const videoDuration = Math.min(15, Math.max(1, segment.duration));
10672
+ const imageBuffer = await readFile9(imagePaths[i]);
10673
+ const ext = extname3(imagePaths[i]).toLowerCase().slice(1);
10674
+ const mimeType = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : "image/png";
10675
+ const referenceImage = `data:${mimeType};base64,${imageBuffer.toString("base64")}`;
10676
+ const taskResult = await generateVideoWithRetryGrok(
10677
+ grok,
10678
+ segment,
10679
+ { duration: videoDuration, aspectRatio: options.aspectRatio || "16:9", referenceImage },
10680
+ maxRetries
10681
+ );
10682
+ if (taskResult) {
10683
+ try {
10684
+ const waitResult = await grok.waitForCompletion(taskResult.requestId, void 0, 3e5);
10685
+ if (waitResult.status === "completed" && waitResult.videoUrl) {
10686
+ const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
10687
+ const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
10688
+ await writeFile7(videoPath, buffer);
10689
+ const targetDuration = segment.duration;
10690
+ const actualVideoDuration = await getVideoDuration(videoPath);
10691
+ if (actualVideoDuration < targetDuration - 0.1) {
10692
+ const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
10693
+ await extendVideoNaturally(videoPath, targetDuration, extendedPath);
10694
+ await unlink(videoPath);
10695
+ await rename2(extendedPath, videoPath);
10696
+ }
10697
+ videoPaths.push(videoPath);
10698
+ result.videos.push(videoPath);
10699
+ } else {
10700
+ videoPaths.push("");
10701
+ result.failedScenes.push(i + 1);
10702
+ }
10703
+ } catch {
10704
+ videoPaths.push("");
10705
+ result.failedScenes.push(i + 1);
10706
+ }
10707
+ } else {
10708
+ videoPaths.push("");
10709
+ result.failedScenes.push(i + 1);
10710
+ }
10711
+ }
10712
+ } else if (options.generator === "kling") {
10515
10713
  const kling = new KlingProvider();
10516
10714
  await kling.initialize({ apiKey: videoApiKey });
10517
10715
  if (!kling.isConfigured()) {
@@ -10534,13 +10732,13 @@ async function executeScriptToVideo(options) {
10534
10732
  try {
10535
10733
  const waitResult = await kling.waitForCompletion(taskResult.taskId, taskResult.type, void 0, 6e5);
10536
10734
  if (waitResult.status === "completed" && waitResult.videoUrl) {
10537
- const videoPath = resolve9(absOutputDir, `scene-${i + 1}.mp4`);
10735
+ const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
10538
10736
  const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
10539
10737
  await writeFile7(videoPath, buffer);
10540
10738
  const targetDuration = segment.duration;
10541
10739
  const actualVideoDuration = await getVideoDuration(videoPath);
10542
10740
  if (actualVideoDuration < targetDuration - 0.1) {
10543
- const extendedPath = resolve9(absOutputDir, `scene-${i + 1}-extended.mp4`);
10741
+ const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
10544
10742
  await extendVideoNaturally(videoPath, targetDuration, extendedPath);
10545
10743
  await unlink(videoPath);
10546
10744
  await rename2(extendedPath, videoPath);
@@ -10580,13 +10778,13 @@ async function executeScriptToVideo(options) {
10580
10778
  try {
10581
10779
  const waitResult = await veo.waitForVideoCompletion(taskResult.operationName, void 0, 3e5);
10582
10780
  if (waitResult.status === "completed" && waitResult.videoUrl) {
10583
- const videoPath = resolve9(absOutputDir, `scene-${i + 1}.mp4`);
10781
+ const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
10584
10782
  const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
10585
10783
  await writeFile7(videoPath, buffer);
10586
10784
  const targetDuration = segment.duration;
10587
10785
  const actualVideoDuration = await getVideoDuration(videoPath);
10588
10786
  if (actualVideoDuration < targetDuration - 0.1) {
10589
- const extendedPath = resolve9(absOutputDir, `scene-${i + 1}-extended.mp4`);
10787
+ const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
10590
10788
  await extendVideoNaturally(videoPath, targetDuration, extendedPath);
10591
10789
  await unlink(videoPath);
10592
10790
  await rename2(extendedPath, videoPath);
@@ -10632,13 +10830,13 @@ async function executeScriptToVideo(options) {
10632
10830
  try {
10633
10831
  const waitResult = await runway.waitForCompletion(taskResult.taskId, void 0, 3e5);
10634
10832
  if (waitResult.status === "completed" && waitResult.videoUrl) {
10635
- const videoPath = resolve9(absOutputDir, `scene-${i + 1}.mp4`);
10833
+ const videoPath = resolve10(absOutputDir, `scene-${i + 1}.mp4`);
10636
10834
  const buffer = await downloadVideo(waitResult.videoUrl, videoApiKey);
10637
10835
  await writeFile7(videoPath, buffer);
10638
10836
  const targetDuration = segment.duration;
10639
10837
  const actualVideoDuration = await getVideoDuration(videoPath);
10640
10838
  if (actualVideoDuration < targetDuration - 0.1) {
10641
- const extendedPath = resolve9(absOutputDir, `scene-${i + 1}-extended.mp4`);
10839
+ const extendedPath = resolve10(absOutputDir, `scene-${i + 1}-extended.mp4`);
10642
10840
  await extendVideoNaturally(videoPath, targetDuration, extendedPath);
10643
10841
  await unlink(videoPath);
10644
10842
  await rename2(extendedPath, videoPath);
@@ -10751,13 +10949,13 @@ async function executeScriptToVideo(options) {
10751
10949
  });
10752
10950
  currentTime += actualDuration;
10753
10951
  }
10754
- const projectPath = resolve9(absOutputDir, "project.vibe.json");
10952
+ const projectPath = resolve10(absOutputDir, "project.vibe.json");
10755
10953
  await writeFile7(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
10756
10954
  result.projectPath = projectPath;
10757
10955
  result.totalDuration = currentTime;
10758
10956
  if (options.review) {
10759
10957
  try {
10760
- const storyboardFile = resolve9(absOutputDir, "storyboard.json");
10958
+ const storyboardFile = resolve10(absOutputDir, "storyboard.json");
10761
10959
  const reviewTarget = videoPaths.find((p) => p && p !== "") || imagePaths.find((p) => p && p !== "");
10762
10960
  if (reviewTarget) {
10763
10961
  const reviewResult = await executeReview({
@@ -10788,10 +10986,10 @@ async function executeScriptToVideo(options) {
10788
10986
 
10789
10987
  // ../cli/src/commands/ai-highlights.ts
10790
10988
  import { readFile as readFile10, writeFile as writeFile8, mkdir as mkdir6 } from "node:fs/promises";
10791
- import { resolve as resolve10, dirname as dirname2, basename as basename4, extname as extname4 } from "node:path";
10989
+ import { resolve as resolve11, dirname as dirname2, basename as basename4, extname as extname4 } from "node:path";
10792
10990
  import { existsSync as existsSync7 } from "node:fs";
10793
- import chalk7 from "chalk";
10794
- import ora5 from "ora";
10991
+ import chalk8 from "chalk";
10992
+ import ora6 from "ora";
10795
10993
  init_exec_safe();
10796
10994
  function filterHighlights(highlights, options) {
10797
10995
  let filtered = highlights.filter((h) => h.confidence >= options.threshold);
@@ -10815,7 +11013,7 @@ function filterHighlights(highlights, options) {
10815
11013
  }
10816
11014
  async function executeHighlights(options) {
10817
11015
  try {
10818
- const absPath = resolve10(process.cwd(), options.media);
11016
+ const absPath = resolve11(process.cwd(), options.media);
10819
11017
  if (!existsSync7(absPath)) {
10820
11018
  return { success: false, highlights: [], totalDuration: 0, totalHighlightDuration: 0, error: `File not found: ${absPath}` };
10821
11019
  }
@@ -10963,7 +11161,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
10963
11161
  totalHighlightDuration
10964
11162
  };
10965
11163
  if (options.output) {
10966
- const outputPath = resolve10(process.cwd(), options.output);
11164
+ const outputPath = resolve11(process.cwd(), options.output);
10967
11165
  await writeFile8(outputPath, JSON.stringify({
10968
11166
  sourceFile: absPath,
10969
11167
  totalDuration: sourceDuration,
@@ -10998,7 +11196,7 @@ Analyze both what is SHOWN (visual cues, actions, expressions) and what is SAID
10998
11196
  currentTime += highlight.duration;
10999
11197
  }
11000
11198
  }
11001
- const projectPath = resolve10(process.cwd(), options.project);
11199
+ const projectPath = resolve11(process.cwd(), options.project);
11002
11200
  await writeFile8(projectPath, JSON.stringify(project.toJSON(), null, 2), "utf-8");
11003
11201
  extractResult.projectPath = projectPath;
11004
11202
  }
@@ -11018,7 +11216,7 @@ async function executeAutoShorts(options) {
11018
11216
  if (!commandExists("ffmpeg")) {
11019
11217
  return { success: false, shorts: [], error: "FFmpeg not found" };
11020
11218
  }
11021
- const absPath = resolve10(process.cwd(), options.video);
11219
+ const absPath = resolve11(process.cwd(), options.video);
11022
11220
  if (!existsSync7(absPath)) {
11023
11221
  return { success: false, shorts: [], error: `File not found: ${absPath}` };
11024
11222
  }
@@ -11146,7 +11344,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
11146
11344
  }))
11147
11345
  };
11148
11346
  }
11149
- const outputDir = options.outputDir ? resolve10(process.cwd(), options.outputDir) : dirname2(absPath);
11347
+ const outputDir = options.outputDir ? resolve11(process.cwd(), options.outputDir) : dirname2(absPath);
11150
11348
  if (options.outputDir && !existsSync7(outputDir)) {
11151
11349
  await mkdir6(outputDir, { recursive: true });
11152
11350
  }
@@ -11157,7 +11355,7 @@ Analyze both VISUALS (expressions, actions, scene changes) and AUDIO (speech, re
11157
11355
  for (let i = 0; i < selectedHighlights.length; i++) {
11158
11356
  const h = selectedHighlights[i];
11159
11357
  const baseName = basename4(absPath, extname4(absPath));
11160
- const outputPath = resolve10(outputDir, `${baseName}-short-${i + 1}.mp4`);
11358
+ const outputPath = resolve11(outputDir, `${baseName}-short-${i + 1}.mp4`);
11161
11359
  const { stdout: probeOut } = await execSafe("ffprobe", [
11162
11360
  "-v",
11163
11361
  "error",
@@ -11449,7 +11647,7 @@ async function handleToolCall(name, args) {
11449
11647
 
11450
11648
  // src/resources/index.ts
11451
11649
  import { readFile as readFile11 } from "node:fs/promises";
11452
- import { resolve as resolve11 } from "node:path";
11650
+ import { resolve as resolve12 } from "node:path";
11453
11651
  var resources = [
11454
11652
  {
11455
11653
  uri: "vibe://project/current",
@@ -11484,7 +11682,7 @@ var resources = [
11484
11682
  ];
11485
11683
  var currentProjectPath = process.env.VIBE_PROJECT_PATH || null;
11486
11684
  async function loadProject2(projectPath) {
11487
- const absPath = resolve11(process.cwd(), projectPath);
11685
+ const absPath = resolve12(process.cwd(), projectPath);
11488
11686
  const content = await readFile11(absPath, "utf-8");
11489
11687
  const data = JSON.parse(content);
11490
11688
  return Project.fromJSON(data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/mcp-server",
3
- "version": "0.31.1",
3
+ "version": "0.33.0",
4
4
  "description": "VibeFrame MCP Server - AI-native video editing via Model Context Protocol",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,8 +57,8 @@
57
57
  "tsx": "^4.21.0",
58
58
  "typescript": "^5.3.3",
59
59
  "vitest": "^1.2.2",
60
- "@vibeframe/cli": "0.31.1",
61
- "@vibeframe/core": "0.31.1"
60
+ "@vibeframe/cli": "0.33.0",
61
+ "@vibeframe/core": "0.33.0"
62
62
  },
63
63
  "engines": {
64
64
  "node": ">=20"