@vibeframe/cli 0.85.0 → 0.87.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 +153 -62
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -52677,14 +52677,14 @@ __export(ai_audio_exports, {
52677
52677
  });
52678
52678
  import { resolve as resolve54, dirname as dirname19, basename as basename18, extname as extname12 } from "node:path";
52679
52679
  import { readFile as readFile36, writeFile as writeFile40 } from "node:fs/promises";
52680
- import { existsSync as existsSync37 } from "node:fs";
52680
+ import { existsSync as existsSync38 } from "node:fs";
52681
52681
  async function executeTranscribe(options) {
52682
52682
  const { audioPath, language, output, format, apiKey } = options;
52683
52683
  try {
52684
52684
  const key = apiKey || process.env.OPENAI_API_KEY;
52685
52685
  if (!key) return { success: false, error: "OPENAI_API_KEY required" };
52686
52686
  const absPath = resolve54(process.cwd(), audioPath);
52687
- if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
52687
+ if (!existsSync38(absPath)) return { success: false, error: `File not found: ${absPath}` };
52688
52688
  const whisper = new WhisperProvider();
52689
52689
  await whisper.initialize({ apiKey: key });
52690
52690
  const audioBuffer = await readFile36(absPath);
@@ -52717,7 +52717,7 @@ async function executeIsolate(options) {
52717
52717
  const key = apiKey || process.env.ELEVENLABS_API_KEY;
52718
52718
  if (!key) return { success: false, error: "ELEVENLABS_API_KEY required" };
52719
52719
  const absPath = resolve54(process.cwd(), audioPath);
52720
- if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
52720
+ if (!existsSync38(absPath)) return { success: false, error: `File not found: ${absPath}` };
52721
52721
  const audioBuffer = await readFile36(absPath);
52722
52722
  const elevenlabs = new ElevenLabsProvider();
52723
52723
  await elevenlabs.initialize({ apiKey: key });
@@ -52743,7 +52743,7 @@ async function executeVoiceClone(options) {
52743
52743
  const audioBuffers = [];
52744
52744
  for (const samplePath of samplePaths) {
52745
52745
  const absPath = resolve54(process.cwd(), samplePath);
52746
- if (!existsSync37(absPath)) return { success: false, error: `File not found: ${samplePath}` };
52746
+ if (!existsSync38(absPath)) return { success: false, error: `File not found: ${samplePath}` };
52747
52747
  const buffer = await readFile36(absPath);
52748
52748
  audioBuffers.push(buffer);
52749
52749
  }
@@ -52765,7 +52765,7 @@ async function executeDub(options) {
52765
52765
  const { mediaPath, language, source, voice, analyzeOnly, output } = options;
52766
52766
  try {
52767
52767
  const absPath = resolve54(process.cwd(), mediaPath);
52768
- if (!existsSync37(absPath)) return { success: false, error: `File not found: ${absPath}` };
52768
+ if (!existsSync38(absPath)) return { success: false, error: `File not found: ${absPath}` };
52769
52769
  const openaiKey = process.env.OPENAI_API_KEY;
52770
52770
  const anthropicKey = process.env.ANTHROPIC_API_KEY;
52771
52771
  const elevenlabsKey = process.env.ELEVENLABS_API_KEY;
@@ -52893,8 +52893,8 @@ async function executeDuck(options) {
52893
52893
  if (!commandExists("ffmpeg")) return { success: false, error: "FFmpeg not found" };
52894
52894
  const absMusicPath = resolve54(process.cwd(), musicPath);
52895
52895
  const absVoicePath = resolve54(process.cwd(), voicePath);
52896
- if (!existsSync37(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
52897
- if (!existsSync37(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
52896
+ if (!existsSync38(absMusicPath)) return { success: false, error: `Music file not found: ${absMusicPath}` };
52897
+ if (!existsSync38(absVoicePath)) return { success: false, error: `Voice file not found: ${absVoicePath}` };
52898
52898
  const defaultOutput = resolve54(dirname19(absMusicPath), `${basename18(absMusicPath, extname12(absMusicPath))}-ducked${extname12(absMusicPath)}`);
52899
52899
  const outputPath = resolve54(process.cwd(), output || defaultOutput);
52900
52900
  const filter = `[1:a]asplit=2[sc][mix];[0:a][sc]sidechaincompress=threshold=${threshold}dB:ratio=${ratio}:attack=${attack}:release=${release}[ducked];[ducked][mix]amix=inputs=2:duration=longest`;
@@ -52908,7 +52908,7 @@ async function executeDuck(options) {
52908
52908
  "-y",
52909
52909
  outputPath
52910
52910
  ], { timeout: 12e4, maxBuffer: 50 * 1024 * 1024 });
52911
- if (!existsSync37(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
52911
+ if (!existsSync38(outputPath)) return { success: false, error: "FFmpeg failed to create output" };
52912
52912
  return { success: true, outputPath };
52913
52913
  } catch (error) {
52914
52914
  return { success: false, error: `Audio ducking failed: ${error instanceof Error ? error.message : String(error)}` };
@@ -52926,7 +52926,7 @@ var init_ai_audio = __esm({
52926
52926
  // src/pipeline/executor.ts
52927
52927
  import { resolve as resolve55 } from "node:path";
52928
52928
  import { readFile as readFile37, writeFile as writeFile41, mkdir as mkdir22 } from "node:fs/promises";
52929
- import { existsSync as existsSync38 } from "node:fs";
52929
+ import { existsSync as existsSync39 } from "node:fs";
52930
52930
  function maxCostFor(action) {
52931
52931
  const cmd = ACTION_METADATA[action]?.command;
52932
52932
  if (!cmd) return 0;
@@ -53143,7 +53143,7 @@ async function ensureActionsRegistered() {
53143
53143
  }
53144
53144
  async function loadPipeline(filePath) {
53145
53145
  const absPath = resolve55(process.cwd(), filePath);
53146
- if (!existsSync38(absPath)) {
53146
+ if (!existsSync39(absPath)) {
53147
53147
  throw new Error(`Pipeline file not found: ${absPath}`);
53148
53148
  }
53149
53149
  const content = await readFile37(absPath, "utf-8");
@@ -53168,7 +53168,7 @@ async function executePipeline(manifest2, options = {}) {
53168
53168
  const startTime = Date.now();
53169
53169
  if (options.resume) {
53170
53170
  const checkpointPath = resolve55(outputDir, CHECKPOINT_FILE);
53171
- if (existsSync38(checkpointPath)) {
53171
+ if (existsSync39(checkpointPath)) {
53172
53172
  const checkpointContent = await readFile37(checkpointPath, "utf-8");
53173
53173
  const checkpoint = (0, import_yaml8.parse)(checkpointContent);
53174
53174
  for (const cs of checkpoint.completedSteps) {
@@ -59771,7 +59771,7 @@ var init_generate2 = __esm({
59771
59771
  // src/tools/manifest/remix.ts
59772
59772
  import { writeFile as writeFile43 } from "node:fs/promises";
59773
59773
  import { tmpdir as tmpdir3 } from "node:os";
59774
- import { join as join13 } from "node:path";
59774
+ import { join as join14 } from "node:path";
59775
59775
  var pipelineHighlightsTool, pipelineAutoShortsTool, pipelineRunTool, pipelineRegenerateSceneTool, remixTools;
59776
59776
  var init_remix = __esm({
59777
59777
  "src/tools/manifest/remix.ts"() {
@@ -59879,7 +59879,7 @@ var init_remix = __esm({
59879
59879
  let resolvedPath = args.pipelinePath;
59880
59880
  let tempPath;
59881
59881
  if (args.pipelineYaml) {
59882
- tempPath = join13(tmpdir3(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
59882
+ tempPath = join14(tmpdir3(), `vibe-mcp-pipeline-${Date.now()}.yaml`);
59883
59883
  await writeFile43(tempPath, args.pipelineYaml, "utf-8");
59884
59884
  resolvedPath = tempPath;
59885
59885
  }
@@ -60636,7 +60636,7 @@ __export(project_builder_exports, {
60636
60636
  resolveSourceUrl: () => resolveSourceUrl
60637
60637
  });
60638
60638
  import { mkdtemp, mkdir as mkdir24, copyFile as copyFile3, writeFile as writeFile47, rm as rm4, readdir as readdir3 } from "node:fs/promises";
60639
- import { existsSync as existsSync39 } from "node:fs";
60639
+ import { existsSync as existsSync40 } from "node:fs";
60640
60640
  import { tmpdir as tmpdir4 } from "node:os";
60641
60641
  import { createRequire } from "node:module";
60642
60642
  import * as path from "node:path";
@@ -60648,7 +60648,7 @@ async function buildTempProject(state, projectFileDir) {
60648
60648
  for (const source of state.sources) {
60649
60649
  if (copied.has(source.id)) continue;
60650
60650
  const resolved = resolveSourceUrl(source.url, projectFileDir);
60651
- if (!existsSync39(resolved)) {
60651
+ if (!existsSync40(resolved)) {
60652
60652
  throw new Error(`Source file not found: ${resolved} (from source id=${source.id})`);
60653
60653
  }
60654
60654
  const dest = path.join(assetsDir, path.basename(resolved));
@@ -61677,7 +61677,7 @@ var init_export2 = __esm({
61677
61677
 
61678
61678
  // src/tools/manifest/agent-only.ts
61679
61679
  import { readFile as readFile41, writeFile as writeFile48, readdir as readdir4, stat as stat6, access as access9, unlink as unlink4 } from "node:fs/promises";
61680
- import { resolve as resolve63, join as join15, basename as basename22, extname as extname13 } from "node:path";
61680
+ import { resolve as resolve63, join as join16, basename as basename22, extname as extname13 } from "node:path";
61681
61681
  function matchPattern(filename, pattern) {
61682
61682
  const regex2 = new RegExp(
61683
61683
  "^" + pattern.replace(/\./g, "\\.").replace(/\*/g, ".*").replace(/\?/g, ".") + "$",
@@ -61760,7 +61760,7 @@ var init_agent_only = __esm({
61760
61760
  const results = [];
61761
61761
  for (const entry of entries) {
61762
61762
  if (args.pattern && !matchPattern(entry.name, args.pattern)) continue;
61763
- const fullPath = join15(absPath, entry.name);
61763
+ const fullPath = join16(absPath, entry.name);
61764
61764
  const stats = await stat6(fullPath);
61765
61765
  if (entry.isDirectory()) {
61766
61766
  results.push(`[DIR] ${entry.name}/`);
@@ -61865,7 +61865,7 @@ var init_agent_only = __esm({
61865
61865
  const scanDir = async (dir) => {
61866
61866
  const entries = await readdir4(dir, { withFileTypes: true });
61867
61867
  for (const entry of entries) {
61868
- const entryPath = join15(dir, entry.name);
61868
+ const entryPath = join16(dir, entry.name);
61869
61869
  if (entry.isDirectory() && recursive) {
61870
61870
  await scanDir(entryPath);
61871
61871
  } else if (entry.isFile()) {
@@ -62837,7 +62837,7 @@ init_source();
62837
62837
  // package.json
62838
62838
  var package_default = {
62839
62839
  name: "@vibeframe/cli",
62840
- version: "0.85.0",
62840
+ version: "0.87.0",
62841
62841
  description: "VibeFrame CLI - natural language video editing from the terminal",
62842
62842
  private: false,
62843
62843
  license: "MIT",
@@ -65122,13 +65122,20 @@ applyTiers(remixCommand, {
65122
65122
  init_esm();
65123
65123
  init_output();
65124
65124
  init_cost_tier();
65125
- var schemaCommand = new Command("schema").description("Show JSON schema for a CLI command").argument("[command]", "Command path (e.g., generate.image, edit.silence-cut)").option("--list", "List all available command paths").action((commandPath, options) => {
65125
+ var schemaCommand = new Command("schema").description("Show JSON schema for a CLI command").argument("[command]", "Command path (e.g., generate.image, edit.silence-cut)").option("--list", "List all available command paths").option("--filter <tier>", "With --list, only show commands at the given cost tier (free | low | high | very-high)").action((commandPath, options) => {
65126
65126
  const program3 = schemaCommand.parent;
65127
65127
  if (!program3) {
65128
65128
  exitWithError(generalError("Schema command must be registered on a program"));
65129
65129
  }
65130
65130
  if (options.list || !commandPath) {
65131
- listCommands(program3);
65131
+ const validTiers = /* @__PURE__ */ new Set(["free", "low", "high", "very-high"]);
65132
+ if (options.filter && !validTiers.has(options.filter)) {
65133
+ exitWithError(usageError(
65134
+ `Invalid --filter: ${options.filter}`,
65135
+ `Must be one of: free, low, high, very-high (matches the cost field on each subcommand).`
65136
+ ));
65137
+ }
65138
+ listCommands(program3, options.filter);
65132
65139
  return;
65133
65140
  }
65134
65141
  const parts = commandPath.split(".");
@@ -65168,7 +65175,7 @@ var schemaCommand = new Command("schema").description("Show JSON schema for a CL
65168
65175
  const schema = buildSchema(actionCmd, toolName);
65169
65176
  console.log(JSON.stringify(schema, null, 2));
65170
65177
  });
65171
- function listCommands(program3) {
65178
+ function listCommands(program3, filter) {
65172
65179
  const commands = [];
65173
65180
  const skipTopLevel = /* @__PURE__ */ new Set(["help", "schema"]);
65174
65181
  for (const group of program3.commands) {
@@ -65179,6 +65186,7 @@ function listCommands(program3) {
65179
65186
  const desc = group.description() || "";
65180
65187
  if (desc.toLowerCase().includes("deprecated")) continue;
65181
65188
  const cost = getCostTier(group);
65189
+ if (filter && cost !== filter) continue;
65182
65190
  commands.push({ path: name, description: desc, ...cost ? { cost } : {} });
65183
65191
  continue;
65184
65192
  }
@@ -65186,6 +65194,7 @@ function listCommands(program3) {
65186
65194
  const desc = sub.description() || "";
65187
65195
  if (desc.toLowerCase().includes("deprecated")) continue;
65188
65196
  const cost = getCostTier(sub);
65197
+ if (filter && cost !== filter) continue;
65189
65198
  commands.push({
65190
65199
  path: `${name}.${sub.name()}`,
65191
65200
  description: desc,
@@ -70960,8 +70969,10 @@ init_esm();
70960
70969
  init_source();
70961
70970
  init_ora();
70962
70971
  init_scene_build();
70972
+ init_storyboard_parse();
70963
70973
  init_output();
70964
- import { resolve as resolve48 } from "node:path";
70974
+ import { resolve as resolve48, join as join13 } from "node:path";
70975
+ import { readFileSync as readFileSync2, existsSync as existsSync35 } from "node:fs";
70965
70976
  var VALID_MODES = ["agent", "batch", "auto"];
70966
70977
  var buildCommand = new Command("build").description("Build a VibeFrame video project from STORYBOARD.md").argument("[project-dir]", "Video project directory", ".").option("--mode <mode>", "Build mode: agent|batch|auto", "auto").option("--effort <level>", "Compose effort tier (batch mode only): low|medium|high", "medium").option("--composer <provider>", "Batch composer: claude|openai|gemini").option("--skip-narration", "Don't dispatch TTS even when beats declare narration cues").option("--skip-backdrop", "Don't dispatch image-gen even when beats declare backdrop cues").option("--skip-render", "Compose only \u2014 don't render to MP4").option("--tts <provider>", "TTS provider: auto|elevenlabs|kokoro").option("--voice <id>", "Voice id").option("--image-provider <name>", "Image provider: openai").option("--quality <q>", "Image quality: standard|hd", "hd").option("--image-size <s>", "Image size: 1024x1024|1536x1024|1024x1536", "1536x1024").option("--force", "Re-dispatch primitives even when assets already exist").option("--dry-run", "Preview parameters without dispatching").addHelpText("after", `
70967
70978
  Examples:
@@ -71055,9 +71066,53 @@ function printBuildDryRun(projectDirArg, params) {
71055
71066
  console.log(` Image: ${source_default.bold(String(params.imageProvider ?? "openai"))} ${source_default.dim(`${params.imageQuality} ${params.imageSize}`)}${params.skipBackdrop ? source_default.dim(" (skipped)") : ""}`);
71056
71067
  console.log(` Render: ${source_default.bold(params.skipRender ? "skip" : "yes")}`);
71057
71068
  console.log(` Regenerate: ${source_default.bold(params.force ? "yes" : "cache when possible")}`);
71069
+ const estimate = estimateBuildCost(params);
71070
+ if (estimate) {
71071
+ console.log();
71072
+ console.log(source_default.bold.cyan("Estimated cost (tier-derived, conservative)"));
71073
+ console.log(source_default.dim("-".repeat(60)));
71074
+ console.log(` Beats: ${source_default.bold(estimate.beats)}`);
71075
+ if (!params.skipNarration) {
71076
+ console.log(` Narration: ${source_default.cyan(`$${estimate.narrationUsd.toFixed(2)}`)} ${source_default.dim(`(${estimate.beats} \xD7 low)`)}`);
71077
+ }
71078
+ if (!params.skipBackdrop) {
71079
+ console.log(` Backdrops: ${source_default.yellow(`$${estimate.backdropUsd.toFixed(2)}`)} ${source_default.dim(`(${estimate.beats} \xD7 high)`)}`);
71080
+ }
71081
+ if (params.mode !== "agent") {
71082
+ console.log(` Compose (LLM): ${source_default.cyan(`$${estimate.composeUsd.toFixed(2)}`)} ${source_default.dim(`(${estimate.beats} \xD7 ~$0.06 batch mode)`)}`);
71083
+ } else {
71084
+ console.log(` Compose: ${source_default.dim(`$0.00 (agent mode \u2014 host LLM does composition)`)}`);
71085
+ }
71086
+ console.log(` ${source_default.bold("Total:")} ${source_default.bold(`$${estimate.totalUsd.toFixed(2)}`)}`);
71087
+ }
71058
71088
  console.log();
71059
71089
  console.log(source_default.dim("No assets, provider calls, or video files were created."));
71060
71090
  }
71091
+ function estimateBuildCost(params) {
71092
+ const storyboardPath = join13(params.projectDir, "STORYBOARD.md");
71093
+ if (!existsSync35(storyboardPath)) return null;
71094
+ let beats = 0;
71095
+ try {
71096
+ const md = readFileSync2(storyboardPath, "utf-8");
71097
+ beats = parseStoryboard(md).beats.length;
71098
+ } catch {
71099
+ return null;
71100
+ }
71101
+ if (beats === 0) return null;
71102
+ const TTS_PER_BEAT = 0.05;
71103
+ const IMAGE_PER_BEAT = 3;
71104
+ const COMPOSE_PER_BEAT_BATCH = 0.06;
71105
+ const narrationUsd = params.skipNarration ? 0 : beats * TTS_PER_BEAT;
71106
+ const backdropUsd = params.skipBackdrop ? 0 : beats * IMAGE_PER_BEAT;
71107
+ const composeUsd = params.mode === "agent" ? 0 : beats * COMPOSE_PER_BEAT_BATCH;
71108
+ return {
71109
+ beats,
71110
+ narrationUsd,
71111
+ backdropUsd,
71112
+ composeUsd,
71113
+ totalUsd: narrationUsd + backdropUsd + composeUsd
71114
+ };
71115
+ }
71061
71116
  function printNeedsAuthor(result) {
71062
71117
  console.log(source_default.cyan("Agent authoring required"));
71063
71118
  console.log();
@@ -71266,10 +71321,11 @@ init_exec_safe();
71266
71321
  init_api_key();
71267
71322
  init_agent_host_detect();
71268
71323
  init_composer_resolve();
71324
+ init_cost_tier();
71269
71325
  init_scene_build();
71270
71326
  init_output();
71271
71327
  import { access as access7 } from "node:fs/promises";
71272
- import { existsSync as existsSync35 } from "node:fs";
71328
+ import { existsSync as existsSync36 } from "node:fs";
71273
71329
  import { resolve as resolve50 } from "node:path";
71274
71330
  var COMMAND_KEY_MAP = getCommandKeyMap();
71275
71331
  var FREE_COMMANDS = [
@@ -71307,7 +71363,10 @@ Examples:
71307
71363
  });
71308
71364
  return;
71309
71365
  }
71310
- printReport(results, { verbose: Boolean(options.verbose) });
71366
+ printReport(results, {
71367
+ verbose: Boolean(options.verbose),
71368
+ program: doctorCommand.parent
71369
+ });
71311
71370
  if (options.testKeys) {
71312
71371
  await runLiveKeyTests(results);
71313
71372
  }
@@ -71388,20 +71447,20 @@ async function runDiagnostics() {
71388
71447
  installed = false;
71389
71448
  }
71390
71449
  } else if (toolName === "chrome") {
71391
- const { existsSync: existsSync40 } = await import("node:fs");
71450
+ const { existsSync: existsSync41 } = await import("node:fs");
71392
71451
  const { homedir: homedir6 } = await import("node:os");
71393
- const { join: join16 } = await import("node:path");
71452
+ const { join: join17 } = await import("node:path");
71394
71453
  const chromePaths = [
71395
71454
  process.env.HYPERFRAMES_CHROME_PATH,
71396
71455
  process.env.CHROME_PATH,
71397
- join16(homedir6(), ".cache", "puppeteer", "chrome-headless-shell", "mac_arm-147.0.7727.56", "chrome-headless-shell-mac_arm", "chrome-headless-shell"),
71456
+ join17(homedir6(), ".cache", "puppeteer", "chrome-headless-shell", "mac_arm-147.0.7727.56", "chrome-headless-shell-mac_arm", "chrome-headless-shell"),
71398
71457
  "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
71399
71458
  "/Applications/Chromium.app/Contents/MacOS/Chromium",
71400
71459
  "/usr/bin/google-chrome",
71401
71460
  "/usr/bin/chromium",
71402
71461
  "/usr/bin/chromium-browser"
71403
71462
  ];
71404
- installed = chromePaths.some((p) => p && existsSync40(p));
71463
+ installed = chromePaths.some((p) => p && existsSync41(p));
71405
71464
  } else {
71406
71465
  installed = commandExists(toolName);
71407
71466
  }
@@ -71432,7 +71491,7 @@ async function runDiagnostics() {
71432
71491
  const cwd = process.cwd();
71433
71492
  const projectFiles = ["AGENTS.md", "CLAUDE.md", "vibe.project.yaml"].map((rel) => ({
71434
71493
  path: rel,
71435
- exists: existsSync35(resolve50(cwd, rel))
71494
+ exists: existsSync36(resolve50(cwd, rel))
71436
71495
  }));
71437
71496
  const projectInitialized = projectFiles.some((f) => f.exists);
71438
71497
  const hosts = detectAgentHosts();
@@ -71447,8 +71506,8 @@ async function runDiagnostics() {
71447
71506
  } catch (err) {
71448
71507
  if (!(err instanceof ComposerResolveError)) throw err;
71449
71508
  }
71450
- const sceneProjectInCwd = existsSync35(resolve50(cwd, "STORYBOARD.md"));
71451
- const skillInstalled = existsSync35(resolve50(cwd, "SKILL.md"));
71509
+ const sceneProjectInCwd = existsSync36(resolve50(cwd, "STORYBOARD.md"));
71510
+ const skillInstalled = existsSync36(resolve50(cwd, "SKILL.md"));
71452
71511
  return {
71453
71512
  system: {
71454
71513
  node: { version: nodeVersion, ok: true },
@@ -71474,7 +71533,7 @@ async function runDiagnostics() {
71474
71533
  };
71475
71534
  }
71476
71535
  function printReport(results, opts = { verbose: false }) {
71477
- const { verbose } = opts;
71536
+ const { verbose, program: program3 } = opts;
71478
71537
  console.log();
71479
71538
  console.log(source_default.bold(" System"));
71480
71539
  const nodeIcon = results.system.node.ok ? source_default.green("OK") : source_default.red("MISSING");
@@ -71611,12 +71670,40 @@ function printReport(results, opts = { verbose: false }) {
71611
71670
  console.log(
71612
71671
  source_default.dim(` Full command catalog: vibe schema --list`)
71613
71672
  );
71673
+ if (program3) {
71674
+ const tierCounts = countCostTiers(program3);
71675
+ const total = tierCounts.free + tierCounts.low + tierCounts.high + tierCounts["very-high"];
71676
+ if (total > 0) {
71677
+ const parts = [
71678
+ `${TIER_COLOR.free(`${tierCounts.free} free`)}`,
71679
+ `${TIER_COLOR.low(`${tierCounts.low} low`)}`,
71680
+ `${TIER_COLOR.high(`${tierCounts.high} high`)}`,
71681
+ `${TIER_COLOR["very-high"](`${tierCounts["very-high"]} very-high`)}`
71682
+ ];
71683
+ console.log(source_default.dim(` Cost mix:`) + ` ${parts.join(source_default.dim(", "))}`);
71684
+ }
71685
+ }
71614
71686
  const nextStep = pickNextStep(results, missing.length > 0);
71615
71687
  if (nextStep) {
71616
71688
  console.log(source_default.dim(` ${nextStep}`));
71617
71689
  }
71618
71690
  console.log();
71619
71691
  }
71692
+ function countCostTiers(program3) {
71693
+ const counts = {
71694
+ "free": 0,
71695
+ "low": 0,
71696
+ "high": 0,
71697
+ "very-high": 0
71698
+ };
71699
+ const walk = (cmd) => {
71700
+ const tier = getCostTier(cmd);
71701
+ if (tier) counts[tier]++;
71702
+ for (const child of cmd.commands) walk(child);
71703
+ };
71704
+ for (const top of program3.commands) walk(top);
71705
+ return counts;
71706
+ }
71620
71707
  async function runLiveKeyTests(results) {
71621
71708
  console.log();
71622
71709
  console.log(source_default.bold(" Live key tests"));
@@ -71669,7 +71756,7 @@ init_detect();
71669
71756
  init_ai_edit();
71670
71757
  init_output();
71671
71758
  import { resolve as resolve51 } from "node:path";
71672
- import { existsSync as existsSync36 } from "node:fs";
71759
+ import { existsSync as existsSync37 } from "node:fs";
71673
71760
  import { mkdir as mkdir21, rm as rm3 } from "node:fs/promises";
71674
71761
  var DEMO_DIR = resolve51(process.cwd(), ".vibeframe-demo");
71675
71762
  async function generateTestVideo(outputPath) {
@@ -71700,7 +71787,7 @@ async function generateTestVideo(outputPath) {
71700
71787
  "-y",
71701
71788
  outputPath
71702
71789
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
71703
- return existsSync36(outputPath);
71790
+ return existsSync37(outputPath);
71704
71791
  } catch {
71705
71792
  try {
71706
71793
  await execSafe("ffmpeg", [
@@ -71726,7 +71813,7 @@ async function generateTestVideo(outputPath) {
71726
71813
  "-y",
71727
71814
  outputPath
71728
71815
  ], { timeout: 3e4, maxBuffer: 50 * 1024 * 1024 });
71729
- return existsSync36(outputPath);
71816
+ return existsSync37(outputPath);
71730
71817
  } catch {
71731
71818
  return false;
71732
71819
  }
@@ -72006,18 +72093,27 @@ Run 'vibe schema run' for structured parameter info.
72006
72093
  } else {
72007
72094
  console.log(source_default.bold(" Execution Plan:"));
72008
72095
  console.log();
72096
+ let totalMax = 0;
72009
72097
  for (let i = 0; i < result2.steps.length; i++) {
72010
72098
  const step = result2.steps[i];
72011
72099
  const est = step.data?.estimatedCost;
72100
+ if (typeof est === "string") {
72101
+ const m = est.match(/\$([\d.]+)/);
72102
+ if (m) totalMax += parseFloat(m[1]);
72103
+ }
72012
72104
  const costTag = est ? source_default.dim(` ~${est}`) : "";
72013
72105
  console.log(` ${source_default.dim(`${i + 1}.`)} ${source_default.bold(step.id)} ${source_default.dim(`\u2192 ${step.action}`)}${costTag}`);
72014
72106
  }
72015
- if (result2.budget) {
72107
+ if (totalMax > 0) {
72016
72108
  console.log();
72017
- console.log(source_default.dim(` Total upper-bound: $${result2.budget.estimatedCostUsd.toFixed(2)}`));
72109
+ console.log(source_default.dim(` Total upper-bound: $${totalMax.toFixed(2)}`) + source_default.dim(` (sum of per-step ceilings)`));
72110
+ }
72111
+ if (result2.budget) {
72018
72112
  if (result2.budget.abortedBy) {
72019
72113
  console.log(source_default.yellow(` \u26A0 Budget ceiling exceeded (${result2.budget.abortedBy}) \u2014 execution will abort.`));
72020
72114
  }
72115
+ } else if (totalMax > 0) {
72116
+ console.log(source_default.dim(` Tip: cap with --budget-usd ${Math.ceil(totalMax)} to abort if cost exceeds the estimate.`));
72021
72117
  }
72022
72118
  console.log();
72023
72119
  console.log(source_default.dim(" Run without --dry-run to execute."));
@@ -72667,30 +72763,20 @@ init_tty();
72667
72763
  init_api_key();
72668
72764
  init_output();
72669
72765
  async function promptConfirm2(toolName, args, cost) {
72670
- const { createInterface: createInterface4 } = await import("node:readline");
72671
- const rl = createInterface4({
72672
- input: process.stdin,
72673
- output: process.stdout,
72674
- terminal: process.stdin.isTTY ?? false
72675
- });
72676
- return new Promise((resolve64) => {
72677
- const argsStr = JSON.stringify(args, null, 2);
72678
- console.log();
72679
- const tierColor = {
72680
- "free": source_default.green,
72681
- "low": source_default.cyan,
72682
- "medium": source_default.yellow,
72683
- "high": source_default.yellow,
72684
- "very-high": source_default.red
72685
- };
72686
- const costBadge = cost ? ` ${tierColor[cost](`[${cost.toUpperCase()}]`)}` : "";
72687
- console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?${costBadge}`));
72688
- console.log(source_default.dim(argsStr));
72689
- rl.question(source_default.cyan("(y/n): "), (answer) => {
72690
- rl.close();
72691
- resolve64(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
72692
- });
72693
- });
72766
+ const argsStr = JSON.stringify(args, null, 2);
72767
+ const tierColor = {
72768
+ "free": source_default.green,
72769
+ "low": source_default.cyan,
72770
+ "medium": source_default.yellow,
72771
+ "high": source_default.yellow,
72772
+ "very-high": source_default.red
72773
+ };
72774
+ const costBadge = cost ? ` ${tierColor[cost](`[${cost.toUpperCase()}]`)}` : "";
72775
+ console.log();
72776
+ console.log(source_default.yellow(`Execute ${source_default.bold(toolName)}?${costBadge}`));
72777
+ console.log(source_default.dim(argsStr));
72778
+ const defaultYes = cost !== "very-high";
72779
+ return promptConfirm("", defaultYes);
72694
72780
  }
72695
72781
  async function startAgent(options = {}) {
72696
72782
  const isNonInteractive = !!options.input;
@@ -72836,6 +72922,11 @@ async function startAgent(options = {}) {
72836
72922
  }
72837
72923
  console.log();
72838
72924
  console.log(source_default.dim(`Total: ${tools.length} tools`));
72925
+ const spent = agent.getCumulativeUsd();
72926
+ if (spent > 0 || budgetUsd !== void 0) {
72927
+ const budgetStr = budgetUsd !== void 0 ? ` / $${budgetUsd.toFixed(2)} cap` : "";
72928
+ console.log(source_default.dim(`Spent (tier-estimated): $${spent.toFixed(2)}${budgetStr}`));
72929
+ }
72839
72930
  console.log();
72840
72931
  rl.prompt();
72841
72932
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.85.0",
3
+ "version": "0.87.0",
4
4
  "description": "VibeFrame CLI - natural language video editing from the terminal",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -183,8 +183,8 @@
183
183
  "tsx": "^4.21.0",
184
184
  "typescript": "^5.3.3",
185
185
  "vitest": "^1.2.2",
186
- "@vibeframe/ai-providers": "0.85.0",
187
- "@vibeframe/core": "0.85.0"
186
+ "@vibeframe/ai-providers": "0.87.0",
187
+ "@vibeframe/core": "0.87.0"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"