@vibeframe/cli 0.90.0 → 0.91.1

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 +224 -58
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -14296,7 +14296,7 @@ function getCommandKeyMap() {
14296
14296
  function getSetupProviders() {
14297
14297
  return [...apiKeyRegistry.values()].filter((k) => k.showInSetup).map((k) => ({
14298
14298
  key: k.configKey,
14299
- name: k.label,
14299
+ name: getDisplayLabelForApiKey(k.configKey),
14300
14300
  env: k.envVar,
14301
14301
  desc: k.setupDescription ?? "",
14302
14302
  url: k.envExampleUrl
@@ -14308,6 +14308,16 @@ function getAllApiKeys() {
14308
14308
  function getKeyFormat(configKey) {
14309
14309
  return apiKeyRegistry.get(configKey)?.keyFormat;
14310
14310
  }
14311
+ function getDisplayLabelForApiKey(configKey) {
14312
+ const apiKey = apiKeyRegistry.get(configKey);
14313
+ if (!apiKey)
14314
+ return configKey;
14315
+ const provider = [...providerRegistry2.values()].find((p) => p.apiKey === configKey && p.displayName && p.gateway);
14316
+ if (provider?.displayName && provider.gateway) {
14317
+ return `${provider.displayName} (via ${provider.gateway})`;
14318
+ }
14319
+ return apiKey.label;
14320
+ }
14311
14321
  function getAllProviders() {
14312
14322
  return [...providerRegistry2.values()];
14313
14323
  }
@@ -24412,7 +24422,7 @@ var init_FalProvider = __esm({
24412
24422
  VALID_ASPECTS = ["21:9", "16:9", "4:3", "1:1", "3:4", "9:16", "auto"];
24413
24423
  FalProvider = class {
24414
24424
  constructor() {
24415
- this.id = "fal";
24425
+ this.id = "seedance";
24416
24426
  this.name = "fal.ai (Seedance 2.0)";
24417
24427
  this.description = "fal.ai hosting ByteDance Seedance 2.0 \u2014 Artificial Analysis #2 on both text-to-video and image-to-video leaderboards";
24418
24428
  this.capabilities = ["text-to-video", "image-to-video"];
@@ -24511,11 +24521,13 @@ var init_fal = __esm({
24511
24521
  init_FalProvider();
24512
24522
  init_define_provider();
24513
24523
  defineProvider({
24514
- id: "fal",
24515
- label: "fal.ai (Seedance 2.0)",
24524
+ id: "seedance",
24525
+ label: "Seedance 2.0",
24516
24526
  displayName: "Seedance 2.0",
24517
24527
  gateway: "fal.ai",
24518
- aliases: ["seedance"],
24528
+ // `fal` is a deprecated v0.x alias kept so existing scripts keep working.
24529
+ // Will be removed at the 1.0 cut (see docs/1.0-readiness.md).
24530
+ aliases: ["fal"],
24519
24531
  models: ["seedance-2.0", "seedance-2.0-fast"],
24520
24532
  capabilities: ["text-to-video", "image-to-video", "native-audio"],
24521
24533
  apiKey: "fal",
@@ -25216,6 +25228,7 @@ __export(dist_exports2, {
25216
25228
  getAllProviders: () => getAllProviders,
25217
25229
  getBestProviderForCapability: () => getBestProviderForCapability,
25218
25230
  getCommandKeyMap: () => getCommandKeyMap,
25231
+ getDisplayLabelForApiKey: () => getDisplayLabelForApiKey,
25219
25232
  getKeyFormat: () => getKeyFormat,
25220
25233
  getProviderEnvVars: () => getProviderEnvVars,
25221
25234
  getProvidersFor: () => getProvidersFor,
@@ -31882,7 +31895,7 @@ async function promptConfirm(question, defaultYes = true) {
31882
31895
  if (answer === "") return defaultYes;
31883
31896
  return answer.toLowerCase().startsWith("y");
31884
31897
  }
31885
- async function promptMultiSelect(_question, options, defaultSelected = []) {
31898
+ async function promptMultiSelect(_question, options, defaultSelected = [], opts = {}) {
31886
31899
  const input = getTTYInputStream();
31887
31900
  const selected = options.map((_, i) => Boolean(defaultSelected[i]));
31888
31901
  if (typeof input.setRawMode === "function") {
@@ -31918,6 +31931,9 @@ async function promptMultiSelect(_question, options, defaultSelected = []) {
31918
31931
  };
31919
31932
  const onData = (char) => {
31920
31933
  if (char === "\r" || char === "\n") {
31934
+ if (opts.enterSelectsFocusedWhenEmpty && selected.every((v) => !v)) {
31935
+ selected[cursor] = true;
31936
+ }
31921
31937
  finish();
31922
31938
  } else if (char === "") {
31923
31939
  input.setRawMode(false);
@@ -35484,9 +35500,9 @@ var init_ai_script_pipeline = __esm({
35484
35500
  import { resolve as resolve26 } from "node:path";
35485
35501
  import { readFile as readFile14, writeFile as writeFile24 } from "node:fs/promises";
35486
35502
  function registerVideoCommand(parent) {
35487
- parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance.").option("-k, --api-key <key>", "API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
35503
+ parent.command("video").alias("vid").description("Generate video using AI (Seedance, Grok, Kling, Runway, or Veo)").argument("[prompt]", "Text prompt describing the video (interactive if omitted)").option("-p, --provider <provider>", "Provider: seedance (ByteDance Seedance 2.0 via fal.ai), grok, kling, runway, veo. `fal` is a deprecated v0.x alias for seedance and will be removed in 1.0.").option("-k, --api-key <key>", "API key (or set FAL_KEY / XAI_API_KEY / RUNWAY_API_SECRET / KLING_API_KEY / GOOGLE_API_KEY env)").option("-o, --output <path>", "Output file path (downloads video)").option("-i, --image <path>", "Reference image for image-to-video").option(
35488
35504
  "-d, --duration <sec>",
35489
- "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
35505
+ "Duration in seconds. Seedance accepts 4-15; Kling accepts 5 or 10; Veo maps to 6 or 8.",
35490
35506
  "5"
35491
35507
  ).option("-r, --ratio <ratio>", "Aspect ratio: 16:9, 9:16, or 1:1 (auto-detected from image if omitted)").option("--seed <number>", "Random seed for reproducibility (Runway only)").option("--mode <mode>", "Generation mode: std or pro (Kling only)", "std").option("--seedance-model <model>", "Seedance variant: quality or fast (fal.ai only)", "quality").option("--negative <prompt>", "Negative prompt - what to avoid (Kling/Veo)").option("--resolution <res>", "Video resolution: 720p, 1080p, 4k (Veo only)").option("--last-frame <path>", "Last frame image for frame interpolation (Veo only)").option("--ref-images <paths...>", "Reference images for character consistency (Veo 3.1 only, max 3)").option("--person <mode>", "Person generation: allow_all, allow_adult (Veo only)").option("--veo-model <model>", "Veo model: 3.0, 3.1, 3.1-fast (default: 3.1-fast)", "3.1-fast").option("--runway-model <model>", "Runway model: gen4.5 (default, text+image-to-video), gen4_turbo (image-to-video only)", "gen4.5").option("--no-wait", "Start generation and return task ID without waiting").option("--dry-run", "Preview parameters without executing").addHelpText("after", `
35492
35508
  Examples:
@@ -35532,8 +35548,7 @@ Examples:
35532
35548
  veo: "GOOGLE_API_KEY",
35533
35549
  kling: "KLING_API_KEY",
35534
35550
  runway: "RUNWAY_API_SECRET",
35535
- seedance: "FAL_KEY",
35536
- fal: "FAL_KEY"
35551
+ seedance: "FAL_KEY"
35537
35552
  };
35538
35553
  let provider;
35539
35554
  if (options.provider) {
@@ -35542,9 +35557,17 @@ Examples:
35542
35557
  exitWithError(
35543
35558
  usageError(
35544
35559
  `Invalid provider: ${provider}`,
35545
- "Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
35560
+ "Available providers: seedance, grok, kling, runway, veo. `fal` is a deprecated alias for seedance."
35561
+ )
35562
+ );
35563
+ }
35564
+ if (provider === "fal") {
35565
+ process.stderr.write(
35566
+ source_default.yellow(
35567
+ "Note: `-p fal` is a deprecated alias for `-p seedance` and will be removed in 1.0.\n"
35546
35568
  )
35547
35569
  );
35570
+ provider = "seedance";
35548
35571
  }
35549
35572
  if (videoEnvMap[provider] && !hasApiKey(videoEnvMap[provider]) && !options.apiKey) {
35550
35573
  const resolved = resolveProvider("video");
@@ -35605,7 +35628,7 @@ Examples:
35605
35628
  data: {
35606
35629
  params: {
35607
35630
  prompt: prompt3,
35608
- provider: provider === "fal" ? "seedance" : provider,
35631
+ provider,
35609
35632
  duration: options.duration,
35610
35633
  ratio: options.ratio,
35611
35634
  image: options.image,
@@ -35624,16 +35647,14 @@ Examples:
35624
35647
  kling: "KLING_API_KEY",
35625
35648
  veo: "GOOGLE_API_KEY",
35626
35649
  grok: "XAI_API_KEY",
35627
- seedance: "FAL_KEY",
35628
- fal: "FAL_KEY"
35650
+ seedance: "FAL_KEY"
35629
35651
  };
35630
35652
  const providerNameMap = {
35631
35653
  runway: "Runway",
35632
35654
  kling: "Kling",
35633
35655
  veo: "Veo",
35634
35656
  grok: "Grok",
35635
- seedance: "Seedance 2.0 via fal.ai",
35636
- fal: "Seedance 2.0 via fal.ai"
35657
+ seedance: "Seedance 2.0 via fal.ai"
35637
35658
  };
35638
35659
  const envKey = envKeyMap[provider];
35639
35660
  const providerName = providerNameMap[provider];
@@ -35852,7 +35873,7 @@ Examples:
35852
35873
  },
35853
35874
  3e5
35854
35875
  );
35855
- } else if (provider === "fal" || provider === "seedance") {
35876
+ } else if (provider === "seedance") {
35856
35877
  const fal = new FalProvider();
35857
35878
  await fal.initialize({ apiKey });
35858
35879
  let falImage = referenceImage;
@@ -62892,7 +62913,7 @@ init_source();
62892
62913
  // package.json
62893
62914
  var package_default = {
62894
62915
  name: "@vibeframe/cli",
62895
- version: "0.90.0",
62916
+ version: "0.91.1",
62896
62917
  description: "VibeFrame CLI - natural language video editing from the terminal",
62897
62918
  private: false,
62898
62919
  license: "MIT",
@@ -69969,6 +69990,26 @@ var AI_FEATURES = [
69969
69990
  defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
69970
69991
  alsoAvailable: "Gemini Nano Banana, Grok Imagine",
69971
69992
  keys: [{ configKey: "openai", envVar: "OPENAI_API_KEY", name: "OpenAI", url: "https://platform.openai.com/api-keys", what: "gpt-image-2 image generation + editing (also Whisper, Agent)" }],
69993
+ providerChoices: [
69994
+ {
69995
+ label: "OpenAI gpt-image-2",
69996
+ desc: "recommended default, image generation + editing",
69997
+ key: { configKey: "openai", envVar: "OPENAI_API_KEY", name: "OpenAI", url: "https://platform.openai.com/api-keys", what: "gpt-image-2 image generation + editing (also Whisper, Agent)" },
69998
+ defaultFor: { kind: "image", value: "openai" }
69999
+ },
70000
+ {
70001
+ label: "Gemini",
70002
+ desc: "Google image generation + multimodal analysis",
70003
+ key: { configKey: "google", envVar: "GOOGLE_API_KEY", name: "Google", url: "https://aistudio.google.com/apikey", what: "Gemini image generation and multimodal analysis" },
70004
+ defaultFor: { kind: "image", value: "gemini" }
70005
+ },
70006
+ {
70007
+ label: "Grok",
70008
+ desc: "xAI image generation",
70009
+ key: { configKey: "xai", envVar: "XAI_API_KEY", name: "xAI", url: "https://console.x.ai/", what: "Grok image generation and Agent" },
70010
+ defaultFor: { kind: "image", value: "grok" }
70011
+ }
70012
+ ],
69972
70013
  tryCommand: 'vibe generate image "a sunset over mountains" -o test.png'
69973
70014
  },
69974
70015
  {
@@ -69976,7 +70017,39 @@ var AI_FEATURES = [
69976
70017
  desc: "generate + extend",
69977
70018
  defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
69978
70019
  alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
69979
- keys: [{ configKey: "fal", envVar: "FAL_KEY", name: "fal.ai", url: "https://fal.ai/dashboard/keys", what: "ByteDance Seedance 2.0 text-to-video and image-to-video" }],
70020
+ keys: [{ configKey: "fal", envVar: "FAL_KEY", name: "Seedance 2.0 (via fal.ai)", url: "https://fal.ai/dashboard/keys", what: "ByteDance Seedance 2.0 text-to-video and image-to-video" }],
70021
+ providerChoices: [
70022
+ {
70023
+ label: "Seedance 2.0 via fal.ai",
70024
+ desc: "recommended default, text-to-video + image-to-video",
70025
+ key: { configKey: "fal", envVar: "FAL_KEY", name: "Seedance 2.0 (via fal.ai)", url: "https://fal.ai/dashboard/keys", what: "ByteDance Seedance 2.0 text-to-video and image-to-video" },
70026
+ defaultFor: { kind: "video", value: "seedance" }
70027
+ },
70028
+ {
70029
+ label: "Grok",
70030
+ desc: "xAI video generation with audio",
70031
+ key: { configKey: "xai", envVar: "XAI_API_KEY", name: "xAI", url: "https://console.x.ai/", what: "Grok video generation and Agent" },
70032
+ defaultFor: { kind: "video", value: "grok" }
70033
+ },
70034
+ {
70035
+ label: "Kling",
70036
+ desc: "Kling v2.5/v3 video generation",
70037
+ key: { configKey: "kling", envVar: "KLING_API_KEY", name: "Kling", url: "https://platform.klingai.com/", what: "Kling video generation" },
70038
+ defaultFor: { kind: "video", value: "kling" }
70039
+ },
70040
+ {
70041
+ label: "Runway",
70042
+ desc: "Runway Gen-4.5 video generation",
70043
+ key: { configKey: "runway", envVar: "RUNWAY_API_SECRET", name: "Runway", url: "https://app.runwayml.com/settings/api-keys", what: "Runway Gen-4.5 video generation" },
70044
+ defaultFor: { kind: "video", value: "runway" }
70045
+ },
70046
+ {
70047
+ label: "Veo",
70048
+ desc: "Google Veo video generation",
70049
+ key: { configKey: "google", envVar: "GOOGLE_API_KEY", name: "Google", url: "https://aistudio.google.com/apikey", what: "Google Veo video generation" },
70050
+ defaultFor: { kind: "video", value: "veo" }
70051
+ }
70052
+ ],
69980
70053
  tryCommand: 'vibe generate video "ocean waves" -o waves.mp4'
69981
70054
  },
69982
70055
  {
@@ -69985,6 +70058,23 @@ var AI_FEATURES = [
69985
70058
  defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
69986
70059
  alsoAvailable: "Replicate MusicGen (music only)",
69987
70060
  keys: [{ configKey: "elevenlabs", envVar: "ELEVENLABS_API_KEY", name: "ElevenLabs", url: "https://elevenlabs.io/app/settings/api-keys", what: "Text-to-speech, sound effects, music, voice cloning (skip to use local Kokoro)" }],
70061
+ providerChoices: [
70062
+ {
70063
+ label: "ElevenLabs",
70064
+ desc: "premium TTS, SFX, music, voice clone",
70065
+ key: { configKey: "elevenlabs", envVar: "ELEVENLABS_API_KEY", name: "ElevenLabs", url: "https://elevenlabs.io/app/settings/api-keys", what: "Text-to-speech, sound effects, music, voice cloning" }
70066
+ },
70067
+ {
70068
+ label: "Kokoro local",
70069
+ desc: "free local TTS fallback, no API key",
70070
+ keyless: true
70071
+ },
70072
+ {
70073
+ label: "Replicate MusicGen",
70074
+ desc: "background music generation",
70075
+ key: { configKey: "replicate", envVar: "REPLICATE_API_TOKEN", name: "Replicate", url: "https://replicate.com/account/api-tokens", what: "MusicGen background music" }
70076
+ }
70077
+ ],
69988
70078
  tryCommand: 'vibe generate speech "Hello world" -o hello.mp3'
69989
70079
  },
69990
70080
  {
@@ -69999,6 +70089,19 @@ var AI_FEATURES = [
69999
70089
  tryCommand: "vibe edit caption video.mp4 -o captioned.mp4"
70000
70090
  }
70001
70091
  ];
70092
+ function addUniqueKey(keys, keyDef) {
70093
+ if (!keys.some((k) => k.configKey === keyDef.configKey)) {
70094
+ keys.push(keyDef);
70095
+ }
70096
+ }
70097
+ function applyProviderDefault(config2, choice) {
70098
+ if (!choice.defaultFor) return;
70099
+ if (choice.defaultFor.kind === "image") {
70100
+ config2.defaults.imageProvider = choice.defaultFor.value;
70101
+ } else {
70102
+ config2.defaults.videoProvider = choice.defaultFor.value;
70103
+ }
70104
+ }
70002
70105
  async function runNonInteractiveSetup(opts) {
70003
70106
  const scope = opts.scope ?? "user";
70004
70107
  let config2 = await loadConfig({ scope });
@@ -70140,6 +70243,7 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70140
70243
  console.log(source_default.dim("2. Features"));
70141
70244
  console.log(source_default.bold("Which AI features do you need?"));
70142
70245
  console.log(source_default.dim(" \u2191\u2193 navigate \xB7 space to toggle \xB7 enter to confirm"));
70246
+ console.log(source_default.dim(" Tip: pressing enter on the highlighted item selects it if nothing is checked yet."));
70143
70247
  console.log();
70144
70248
  const featureLabels = AI_FEATURES.map((f) => {
70145
70249
  const keyCount = f.keys.length;
@@ -70148,7 +70252,9 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70148
70252
  });
70149
70253
  const picked = await promptMultiSelect(
70150
70254
  source_default.cyan(" Pick (e.g. 1,3 or 'all'): "),
70151
- featureLabels
70255
+ featureLabels,
70256
+ [],
70257
+ { enterSelectsFocusedWhenEmpty: true }
70152
70258
  );
70153
70259
  const selectedFeatures = picked.map((i) => AI_FEATURES[i]);
70154
70260
  console.log();
@@ -70159,53 +70265,107 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70159
70265
  await showComplete(config2, "vibe --help", [], scope);
70160
70266
  return;
70161
70267
  }
70162
- console.log(source_default.dim("3. API keys"));
70163
- console.log(source_default.bold("API Keys"));
70164
- console.log(source_default.dim(" Saved locally, never shared. Press Enter to skip."));
70268
+ const plannedKeys = /* @__PURE__ */ new Map();
70269
+ console.log(source_default.dim("3. Providers"));
70270
+ console.log(source_default.bold("Choose providers for each selected feature"));
70271
+ console.log(source_default.dim(" Recommended defaults are pre-selected. You can toggle more than one."));
70165
70272
  console.log();
70166
- const collectedKeys = /* @__PURE__ */ new Set();
70167
70273
  for (const feature of selectedFeatures) {
70168
- console.log(source_default.bold.cyan(` ${feature.label}`));
70169
- console.log(source_default.dim(` Default: ${feature.defaultProvider}`));
70170
- if (feature.alsoAvailable) {
70171
- console.log(source_default.dim(` Also available: ${feature.alsoAvailable}`));
70274
+ const keysForFeature = [];
70275
+ if (feature.providerChoices && feature.providerChoices.length > 0) {
70276
+ console.log(source_default.bold.cyan(` ${feature.label}`));
70277
+ console.log(source_default.dim(` Default path: ${feature.defaultProvider}`));
70278
+ if (feature.alsoAvailable) {
70279
+ console.log(source_default.dim(` Alternatives: ${feature.alsoAvailable}`));
70280
+ }
70281
+ console.log();
70282
+ const defaultSelected = feature.providerChoices.map((_, i) => i === 0);
70283
+ const providerLabels = feature.providerChoices.map((choice) => {
70284
+ const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(choice.key.envVar) : source_default.dim("no key");
70285
+ return `${choice.label} ${source_default.dim(`- ${choice.desc}`)} ${keyHint}`;
70286
+ });
70287
+ const pickedProviders = await promptMultiSelect(
70288
+ source_default.cyan(" Providers (space toggles, enter confirms): "),
70289
+ providerLabels,
70290
+ defaultSelected
70291
+ );
70292
+ const selectedChoices = pickedProviders.map((i) => feature.providerChoices[i]);
70293
+ const defaultChoice = selectedChoices.find((choice) => choice.defaultFor);
70294
+ if (defaultChoice) {
70295
+ applyProviderDefault(config2, defaultChoice);
70296
+ }
70297
+ for (const choice of selectedChoices) {
70298
+ if (choice.key) addUniqueKey(keysForFeature, choice.key);
70299
+ }
70300
+ if (selectedChoices.some((choice) => choice.keyless)) {
70301
+ console.log(source_default.dim(" No API key needed for selected local provider(s)."));
70302
+ }
70303
+ console.log();
70304
+ } else {
70305
+ for (const keyDef of feature.keys) {
70306
+ addUniqueKey(keysForFeature, keyDef);
70307
+ }
70172
70308
  }
70309
+ plannedKeys.set(feature.label, keysForFeature);
70310
+ }
70311
+ const needsKeys = [...plannedKeys.values()].some((keys) => keys.length > 0);
70312
+ if (needsKeys) {
70313
+ console.log(source_default.dim("4. API keys"));
70314
+ console.log(source_default.bold("API Keys"));
70315
+ console.log(source_default.dim(" Saved locally, never shared. Press Enter to skip."));
70173
70316
  console.log();
70174
- for (const keyDef of feature.keys) {
70175
- if (collectedKeys.has(keyDef.configKey)) {
70176
- console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} (already set above)`);
70317
+ const collectedKeys = /* @__PURE__ */ new Set();
70318
+ for (const feature of selectedFeatures) {
70319
+ const keysForFeature = plannedKeys.get(feature.label) ?? [];
70320
+ if (keysForFeature.length === 0) {
70177
70321
  continue;
70178
70322
  }
70179
- loadEnv();
70180
- const configValue = config2.providers[keyDef.configKey];
70181
- const envValue = process.env[keyDef.envVar];
70182
- if (configValue || envValue) {
70183
- const value = configValue || envValue;
70184
- const source = configValue ? "config" : ".env";
70185
- console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(value)} ${source_default.dim(`(${source})`)}`);
70186
- collectedKeys.add(keyDef.configKey);
70187
- continue;
70323
+ console.log(source_default.bold.cyan(` ${feature.label}`));
70324
+ console.log(source_default.dim(` Default: ${feature.defaultProvider}`));
70325
+ if (feature.alsoAvailable) {
70326
+ console.log(source_default.dim(` Also available: ${feature.alsoAvailable}`));
70188
70327
  }
70189
- console.log(source_default.dim(` ${keyDef.what}`));
70190
- console.log(source_default.dim(` Get key: ${keyDef.url} [o] open in browser`));
70191
- const newKey = await promptHidden(
70192
- source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `),
70193
- { openHotkeyUrl: keyDef.url }
70194
- );
70195
- if (newKey.trim()) {
70196
- const trimmed = newKey.trim();
70197
- config2.providers[keyDef.configKey] = trimmed;
70198
- const fmt = validateKeyFormat(keyDef.configKey, trimmed);
70199
- if (!fmt.ok && fmt.expected) {
70200
- console.log(` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`);
70328
+ console.log();
70329
+ for (const keyDef of keysForFeature) {
70330
+ if (collectedKeys.has(keyDef.configKey)) {
70331
+ console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} (already set above)`);
70332
+ continue;
70333
+ }
70334
+ loadEnv();
70335
+ const configValue = config2.providers[keyDef.configKey];
70336
+ const envValue = process.env[keyDef.envVar];
70337
+ if (configValue || envValue) {
70338
+ const value = configValue || envValue;
70339
+ const source = configValue ? "config" : ".env";
70340
+ console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(value)} ${source_default.dim(`(${source})`)}`);
70341
+ collectedKeys.add(keyDef.configKey);
70342
+ continue;
70343
+ }
70344
+ console.log(source_default.dim(` ${keyDef.what}`));
70345
+ console.log(source_default.dim(` Get key: ${keyDef.url} [o] open in browser`));
70346
+ const newKey = await promptHidden(
70347
+ source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `),
70348
+ { openHotkeyUrl: keyDef.url }
70349
+ );
70350
+ if (newKey.trim()) {
70351
+ const trimmed = newKey.trim();
70352
+ config2.providers[keyDef.configKey] = trimmed;
70353
+ const fmt = validateKeyFormat(keyDef.configKey, trimmed);
70354
+ if (!fmt.ok && fmt.expected) {
70355
+ console.log(` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`);
70356
+ } else {
70357
+ console.log(` ${source_default.green("\u2713")} Saved`);
70358
+ }
70359
+ collectedKeys.add(keyDef.configKey);
70201
70360
  } else {
70202
- console.log(` ${source_default.green("\u2713")} Saved`);
70361
+ console.log(` ${source_default.yellow("\u26A0")} Skipped ${source_default.dim(`(set ${keyDef.envVar} in .env later)`)}`);
70203
70362
  }
70204
- collectedKeys.add(keyDef.configKey);
70205
- } else {
70206
- console.log(` ${source_default.yellow("\u26A0")} Skipped ${source_default.dim(`(set ${keyDef.envVar} in .env later)`)}`);
70207
70363
  }
70364
+ console.log();
70208
70365
  }
70366
+ } else {
70367
+ console.log(source_default.dim("4. API keys"));
70368
+ console.log(source_default.dim(" No API keys needed for the selected provider(s)."));
70209
70369
  console.log();
70210
70370
  }
70211
70371
  await saveConfig(config2, { scope });
@@ -71795,11 +71955,16 @@ function printReport(results, opts = { verbose: false }) {
71795
71955
  console.log(source_default.bold(" API Keys"));
71796
71956
  const configured = [];
71797
71957
  const missing = [];
71958
+ const labelFor = (configKey, fallbackEnv) => {
71959
+ const label = getDisplayLabelForApiKey(configKey);
71960
+ return label === configKey ? fallbackEnv : label;
71961
+ };
71798
71962
  for (const [name, info] of Object.entries(results.providers)) {
71799
71963
  if (info.configured) {
71800
71964
  configured.push(name);
71965
+ const label = labelFor(name, info.envVar);
71801
71966
  console.log(
71802
- ` ${source_default.green("OK")} ${info.envVar.padEnd(24)} ${source_default.dim(info.commands.join(", "))}`
71967
+ ` ${source_default.green("OK")} ${label.padEnd(28)} ${source_default.dim(info.envVar.padEnd(20))} ${source_default.dim(info.commands.join(", "))}`
71803
71968
  );
71804
71969
  } else {
71805
71970
  missing.push(name);
@@ -71809,8 +71974,9 @@ function printReport(results, opts = { verbose: false }) {
71809
71974
  if (verbose) {
71810
71975
  for (const name of missing) {
71811
71976
  const info = results.providers[name];
71977
+ const label = labelFor(name, info.envVar);
71812
71978
  console.log(
71813
- ` ${source_default.red("--")} ${info.envVar.padEnd(24)} ${source_default.dim(info.commands.join(", "))}`
71979
+ ` ${source_default.red("--")} ${label.padEnd(28)} ${source_default.dim(info.envVar.padEnd(20))} ${source_default.dim(info.commands.join(", "))}`
71814
71980
  );
71815
71981
  }
71816
71982
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.90.0",
3
+ "version": "0.91.1",
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.90.0",
187
- "@vibeframe/core": "0.90.0"
186
+ "@vibeframe/ai-providers": "0.91.1",
187
+ "@vibeframe/core": "0.91.1"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"