@vibeframe/cli 0.82.0 → 0.83.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 +131 -13
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -35862,6 +35862,37 @@ var init_video = __esm({
35862
35862
  }
35863
35863
  });
35864
35864
 
35865
+ // src/commands/_shared/cost-tier.ts
35866
+ function applyTier(cmd, tier) {
35867
+ cmd[COST_TIER_KEY] = tier;
35868
+ cmd.addHelpText("after", `
35869
+ Cost: ${tier} (${TIER_DESCRIPTION[tier]})
35870
+ `);
35871
+ return cmd;
35872
+ }
35873
+ function getCostTier(cmd) {
35874
+ return cmd[COST_TIER_KEY];
35875
+ }
35876
+ function applyTiers(parent, tiers) {
35877
+ for (const cmd of parent.commands) {
35878
+ const tier = tiers[cmd.name()];
35879
+ if (tier) applyTier(cmd, tier);
35880
+ }
35881
+ }
35882
+ var TIER_DESCRIPTION, COST_TIER_KEY;
35883
+ var init_cost_tier = __esm({
35884
+ "src/commands/_shared/cost-tier.ts"() {
35885
+ "use strict";
35886
+ TIER_DESCRIPTION = {
35887
+ "free": "FFmpeg only, no API call",
35888
+ "low": "$0.01\u2013$0.10 per call",
35889
+ "high": "$1\u2013$5 per call",
35890
+ "very-high": "$5\u2013$50+ per call"
35891
+ };
35892
+ COST_TIER_KEY = Symbol.for("@vibeframe/cli.costTier");
35893
+ }
35894
+ });
35895
+
35865
35896
  // src/commands/generate.ts
35866
35897
  var generate_exports = {};
35867
35898
  __export(generate_exports, {
@@ -35873,6 +35904,10 @@ __export(generate_exports, {
35873
35904
  executeStoryboard: () => executeStoryboard,
35874
35905
  generateCommand: () => generateCommand
35875
35906
  });
35907
+ function tierLast(parent, tier) {
35908
+ const newest = parent.commands[parent.commands.length - 1];
35909
+ if (newest) applyTier(newest, tier);
35910
+ }
35876
35911
  var generateCommand;
35877
35912
  var init_generate = __esm({
35878
35913
  "src/commands/generate.ts"() {
@@ -35891,6 +35926,7 @@ var init_generate = __esm({
35891
35926
  init_video_extend();
35892
35927
  init_image();
35893
35928
  init_video();
35929
+ init_cost_tier();
35894
35930
  init_sound_effect();
35895
35931
  init_music_status();
35896
35932
  init_background();
@@ -35928,18 +35964,31 @@ Run 'vibe schema generate.<command>' for structured parameter info.
35928
35964
  `
35929
35965
  );
35930
35966
  registerImageCommand(generateCommand);
35967
+ tierLast(generateCommand, "high");
35931
35968
  registerVideoCommand(generateCommand);
35969
+ tierLast(generateCommand, "very-high");
35932
35970
  registerSpeechCommand(generateCommand);
35971
+ tierLast(generateCommand, "low");
35933
35972
  registerSoundEffectCommand(generateCommand);
35973
+ tierLast(generateCommand, "low");
35934
35974
  registerMusicCommand(generateCommand);
35975
+ tierLast(generateCommand, "low");
35935
35976
  registerMusicStatusCommand(generateCommand);
35977
+ tierLast(generateCommand, "free");
35936
35978
  registerStoryboardCommand(generateCommand);
35979
+ tierLast(generateCommand, "high");
35937
35980
  registerMotionCommand(generateCommand);
35981
+ tierLast(generateCommand, "high");
35938
35982
  registerThumbnailCommand(generateCommand);
35983
+ tierLast(generateCommand, "free");
35939
35984
  registerBackgroundCommand(generateCommand);
35985
+ tierLast(generateCommand, "high");
35940
35986
  registerVideoStatusCommand(generateCommand);
35987
+ tierLast(generateCommand, "free");
35941
35988
  registerVideoCancelCommand(generateCommand);
35989
+ tierLast(generateCommand, "free");
35942
35990
  registerVideoExtendCommand(generateCommand);
35991
+ tierLast(generateCommand, "very-high");
35943
35992
  }
35944
35993
  });
35945
35994
 
@@ -37238,7 +37287,7 @@ var init_ai_edit = __esm({
37238
37287
  import { resolve as resolve28, extname as extname5, basename as basename7 } from "node:path";
37239
37288
  import { existsSync as existsSync22 } from "node:fs";
37240
37289
  function registerEditCommands(aiCommand) {
37241
- aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
37290
+ aiCommand.command("silence-cut").alias("sc").description("Remove silent segments from video (FFmpeg default, or Gemini for smart detection)").argument("<video>", "Video file path").option("-o, --output <path>", "Output file path (default: <name>-cut.<ext>)").option("--noise <dB>", "Silence threshold in dB (default: -30)", "-30").option("-d, --min-duration <seconds>", "Minimum silence duration to cut (default: 0.5)", "0.5").option("--padding <seconds>", "Padding around non-silent segments (default: 0.1)", "0.1").option("--analyze-only", "(deprecated \u2014 use `vibe detect silence`) Only detect silence, don't cut").option("--use-gemini", "Use Gemini Video Understanding for context-aware silence detection").option("-m, --model <model>", "Gemini model (default: flash)").option("--low-res", "Low resolution mode for longer videos (Gemini only)").option("-k, --api-key <key>", "Google API key override (or set GOOGLE_API_KEY env)").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
37242
37291
  Examples:
37243
37292
  $ vibe edit silence-cut interview.mp4 -o clean.mp4
37244
37293
  $ vibe ed sc podcast.mp4 -o trimmed.mp4 --noise -25 --min-duration 1.0
@@ -37263,6 +37312,13 @@ No API key needed (FFmpeg only). Use --use-gemini for smart detection (requires
37263
37312
  const name = basename7(videoPath, ext);
37264
37313
  const outputPath = options.output || `${name}-cut${ext}`;
37265
37314
  const useGemini = options.useGemini || false;
37315
+ if (options.analyzeOnly) {
37316
+ console.error(
37317
+ source_default.yellow(
37318
+ "\u26A0 --analyze-only is deprecated. Use `vibe detect silence` for read-only detection. Will be removed in v1.0."
37319
+ )
37320
+ );
37321
+ }
37266
37322
  if (options.dryRun) {
37267
37323
  outputSuccess({
37268
37324
  command: "edit silence-cut",
@@ -38485,6 +38541,7 @@ var init_edit_cmd = __esm({
38485
38541
  init_ai_fill_gaps();
38486
38542
  init_output();
38487
38543
  init_validate();
38544
+ init_cost_tier();
38488
38545
  editCommand = new Command("edit").alias("ed").description(
38489
38546
  "Edit and post-process media (silence-cut, caption, grade, reframe, upscale...)"
38490
38547
  ).addHelpText(
@@ -39232,6 +39289,26 @@ Run 'vibe schema edit.<command>' for structured parameter info.
39232
39289
  exitWithError(generalError(`Video upscaling failed: ${msg}`));
39233
39290
  }
39234
39291
  });
39292
+ applyTiers(editCommand, {
39293
+ // free — FFmpeg only
39294
+ "noise-reduce": "free",
39295
+ "fade": "free",
39296
+ "text-overlay": "free",
39297
+ "interpolate": "free",
39298
+ // low — Whisper / single LLM call
39299
+ "silence-cut": "low",
39300
+ "caption": "low",
39301
+ "translate-srt": "low",
39302
+ "jump-cut": "low",
39303
+ "grade": "low",
39304
+ "speed-ramp": "low",
39305
+ // high — vision LLM or image gen
39306
+ "reframe": "high",
39307
+ "image": "high",
39308
+ "upscale": "high",
39309
+ // very-high — video gen per gap
39310
+ "fill-gaps": "very-high"
39311
+ });
39235
39312
  }
39236
39313
  });
39237
39314
 
@@ -50622,6 +50699,7 @@ var init_detect = __esm({
50622
50699
  init_exec_safe();
50623
50700
  init_output();
50624
50701
  init_validate();
50702
+ init_cost_tier();
50625
50703
  detectCommand = new Command("detect").description("Auto-detect scenes, beats, and silences in media");
50626
50704
  detectCommand.command("scenes").description("Detect scene changes in video").argument("<video>", "Video file path").option("--threshold <value>", "Scene change threshold (0-1)", "0.3").option("-o, --output <path>", "Output JSON file with timestamps").option("--project <path>", "Add scenes as clips to project").option("--dry-run", "Preview parameters without executing").action(async (videoPath, options) => {
50627
50705
  const startedAt = Date.now();
@@ -50913,6 +50991,11 @@ var init_detect = __esm({
50913
50991
  exitWithError(generalError(`Beat detection failed: ${msg}`));
50914
50992
  }
50915
50993
  });
50994
+ applyTiers(detectCommand, {
50995
+ "scenes": "free",
50996
+ "silence": "free",
50997
+ "beats": "free"
50998
+ });
50916
50999
  }
50917
51000
  });
50918
51001
 
@@ -51326,25 +51409,36 @@ var init_key_live_test = __esm({
51326
51409
  signal
51327
51410
  )
51328
51411
  },
51329
- // The four below have no cheap authenticated GET — providing a tester
51330
- // would either consume a paid API call (fal, Runway), require HMAC
51331
- // signing per request (Kling), or burn an image upload (ImgBB). We
51332
- // surface them as `skipped` so the user knows we didn't blank-check.
51412
+ // Runway: /v1/organization is a read-only GET that returns tier info
51413
+ // (free, doesn't consume credits). Requires the X-Runway-Version
51414
+ // header alongside the Bearer token — without it the request 401s.
51415
+ runway: {
51416
+ name: "Runway",
51417
+ test: (key, signal) => getOk(
51418
+ "https://api.dev.runwayml.com/v1/organization",
51419
+ {
51420
+ Authorization: `Bearer ${key}`,
51421
+ "X-Runway-Version": "2024-11-06"
51422
+ },
51423
+ signal
51424
+ )
51425
+ },
51426
+ // The three below still have no cheap authenticated GET as of v0.83:
51427
+ // fal lacks a documented validation endpoint, Kling auth is per-
51428
+ // request HMAC-signed, ImgBB's only authenticated route is image
51429
+ // upload. Surface them as `skipped` so the user knows we didn't
51430
+ // blank-check.
51333
51431
  fal: {
51334
51432
  name: "fal.ai",
51335
- test: async () => ({ ok: false, skipped: true, message: "no list endpoint" })
51433
+ test: async () => ({ ok: false, skipped: true, message: "no auth-only endpoint published" })
51336
51434
  },
51337
51435
  kling: {
51338
51436
  name: "Kling",
51339
- test: async () => ({ ok: false, skipped: true, message: "HMAC-signed; can't test cheaply" })
51340
- },
51341
- runway: {
51342
- name: "Runway",
51343
- test: async () => ({ ok: false, skipped: true, message: "POST-only API" })
51437
+ test: async () => ({ ok: false, skipped: true, message: "per-request HMAC; can't test cheaply" })
51344
51438
  },
51345
51439
  imgbb: {
51346
51440
  name: "ImgBB",
51347
- test: async () => ({ ok: false, skipped: true, message: "would consume an upload" })
51441
+ test: async () => ({ ok: false, skipped: true, message: "only auth route is image upload" })
51348
51442
  }
51349
51443
  };
51350
51444
  }
@@ -62651,7 +62745,7 @@ init_source();
62651
62745
  // package.json
62652
62746
  var package_default = {
62653
62747
  name: "@vibeframe/cli",
62654
- version: "0.82.0",
62748
+ version: "0.83.0",
62655
62749
  description: "VibeFrame CLI - natural language video editing from the terminal",
62656
62750
  private: false,
62657
62751
  license: "MIT",
@@ -63819,6 +63913,7 @@ init_ai_review();
63819
63913
  init_output();
63820
63914
  init_sanitize();
63821
63915
  init_validate();
63916
+ init_cost_tier();
63822
63917
  import { readFile as readFile23, writeFile as writeFile30 } from "node:fs/promises";
63823
63918
  import { resolve as resolve33 } from "node:path";
63824
63919
  var inspectCommand = new Command("inspect").description("Inspect media using AI (images, videos, YouTube URLs)").addHelpText(
@@ -63916,6 +64011,7 @@ inspectCommand.command("media").description("Analyze any media: images, videos,
63916
64011
  exitWithError(apiError(`Analysis failed: ${error.message}`));
63917
64012
  }
63918
64013
  });
64014
+ applyTier(inspectCommand.commands[inspectCommand.commands.length - 1], "low");
63919
64015
  inspectCommand.command("video").description("Analyze video using Gemini (summarize, Q&A, extract info)").argument("<source>", "Video file path or YouTube URL").argument("<prompt>", "Analysis prompt (e.g., 'Summarize this video')").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("-m, --model <model>", "Model: flash (default), flash-2.5, pro", "flash").option("--fps <number>", "Frames per second (default: 1, higher for action)").option("--start <seconds>", "Start offset in seconds (for clipping)").option("--end <seconds>", "End offset in seconds (for clipping)").option("--low-res", "Use low resolution mode (fewer tokens, longer videos)").option("--verbose", "Show token usage").option("--fields <fields>", "Comma-separated fields to include in output (e.g., response,model)").option("--dry-run", "Preview parameters without executing").action(async (source, prompt3, options) => {
63920
64016
  const startedAt = Date.now();
63921
64017
  try {
@@ -63992,7 +64088,9 @@ inspectCommand.command("video").description("Analyze video using Gemini (summari
63992
64088
  exitWithError(apiError(`Video analysis failed: ${error.message}`));
63993
64089
  }
63994
64090
  });
64091
+ applyTier(inspectCommand.commands[inspectCommand.commands.length - 1], "low");
63995
64092
  registerReviewCommand(inspectCommand);
64093
+ applyTier(inspectCommand.commands[inspectCommand.commands.length - 1], "low");
63996
64094
  inspectCommand.command("suggest").description("Get AI edit suggestions using Gemini").argument("<project>", "Timeline file or directory").argument("<instruction>", "Natural language instruction").option("-k, --api-key <key>", "Google API key (or set GOOGLE_API_KEY env)").option("--apply", "Apply the first suggestion automatically").option("--dry-run", "Preview parameters without executing").action(async (projectPath, instruction, options) => {
63997
64095
  const startedAt = Date.now();
63998
64096
  try {
@@ -64063,6 +64161,7 @@ inspectCommand.command("suggest").description("Get AI edit suggestions using Gem
64063
64161
  exitWithError(apiError(`AI suggestion failed: ${error.message}`));
64064
64162
  }
64065
64163
  });
64164
+ applyTier(inspectCommand.commands[inspectCommand.commands.length - 1], "low");
64066
64165
 
64067
64166
  // src/commands/audio.ts
64068
64167
  init_esm();
@@ -64072,6 +64171,7 @@ init_dist2();
64072
64171
  init_api_key();
64073
64172
  init_exec_safe();
64074
64173
  init_subtitle();
64174
+ init_cost_tier();
64075
64175
  init_ai_helpers();
64076
64176
  init_output();
64077
64177
  init_validate();
@@ -64579,6 +64679,14 @@ audioCommand.command("duck").description("Auto-duck background music when voice
64579
64679
  exitWithError(generalError(`Audio ducking failed: ${msg}`));
64580
64680
  }
64581
64681
  });
64682
+ applyTiers(audioCommand, {
64683
+ "transcribe": "low",
64684
+ "list-voices": "low",
64685
+ "isolate": "low",
64686
+ "clone-voice": "low",
64687
+ "dub": "high",
64688
+ "duck": "free"
64689
+ });
64582
64690
 
64583
64691
  // src/commands/remix.ts
64584
64692
  init_esm();
@@ -64769,6 +64877,7 @@ function registerScriptPipelineCommands(aiCommand) {
64769
64877
  init_ai_highlights();
64770
64878
  init_ai_animated_caption();
64771
64879
  init_output();
64880
+ init_cost_tier();
64772
64881
  var remixCommand = new Command("remix").description(
64773
64882
  "Transform existing media (highlights, auto-shorts, animated captions, regenerate-scene)"
64774
64883
  ).addHelpText(
@@ -64910,10 +65019,17 @@ Required API Key: OPENAI_API_KEY (Whisper transcription)
64910
65019
  exitWithError(generalError(`Animated caption failed: ${msg}`));
64911
65020
  }
64912
65021
  });
65022
+ applyTiers(remixCommand, {
65023
+ "regenerate-scene": "very-high",
65024
+ "highlights": "high",
65025
+ "auto-shorts": "high",
65026
+ "animated-caption": "low"
65027
+ });
64913
65028
 
64914
65029
  // src/commands/schema.ts
64915
65030
  init_esm();
64916
65031
  init_output();
65032
+ init_cost_tier();
64917
65033
  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) => {
64918
65034
  const program3 = schemaCommand.parent;
64919
65035
  if (!program3) {
@@ -65106,9 +65222,11 @@ function buildSchema(cmd, toolName) {
65106
65222
  }
65107
65223
  properties[name] = prop;
65108
65224
  }
65225
+ const cost = getCostTier(cmd);
65109
65226
  return {
65110
65227
  name: toolName,
65111
65228
  description: cmd.description(),
65229
+ ...cost ? { cost } : {},
65112
65230
  parameters: {
65113
65231
  type: "object",
65114
65232
  properties,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.82.0",
3
+ "version": "0.83.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.82.0",
187
- "@vibeframe/core": "0.82.0"
186
+ "@vibeframe/ai-providers": "0.83.0",
187
+ "@vibeframe/core": "0.83.0"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"