@vibeframe/cli 0.91.7 → 0.91.8

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 +381 -179
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -62962,7 +62962,7 @@ init_source();
62962
62962
  // package.json
62963
62963
  var package_default = {
62964
62964
  name: "@vibeframe/cli",
62965
- version: "0.91.7",
62965
+ version: "0.91.8",
62966
62966
  description: "VibeFrame CLI - natural language video editing from the terminal",
62967
62967
  private: false,
62968
62968
  license: "MIT",
@@ -69963,14 +69963,19 @@ var VALID_SCOPES = ["user", "project"];
69963
69963
  function parseScope(raw) {
69964
69964
  const v = String(raw ?? "user");
69965
69965
  if (!VALID_SCOPES.includes(v)) {
69966
- exitWithError(generalError(
69967
- `Invalid --scope: ${v}`,
69968
- `Must be one of: ${VALID_SCOPES.join(", ")}`
69969
- ));
69966
+ exitWithError(
69967
+ generalError(`Invalid --scope: ${v}`, `Must be one of: ${VALID_SCOPES.join(", ")}`)
69968
+ );
69970
69969
  }
69971
69970
  return v;
69972
69971
  }
69973
- var setupCommand = new Command("setup").description("Configure VibeFrame (LLM provider, API keys)").option("--reset", "Reset configuration to defaults").option("--full", "Run full setup with all optional providers").option("--show", "Show current configuration (for debugging)").option("-v, --verbose", "With --show: include unset providers + Resolution order + Defaults block").option("--claude-code", "Show Claude Code integration guide").option("-y, --yes", "Non-interactive: write config without prompting (CI / devcontainer)").option("--provider <id>", "Set the Agent LLM provider (claude | openai | gemini | xai | openrouter | ollama)").option("--import-env", "Promote API keys from .env / shell env into config.yaml").option("--test", "After save, live-test each configured key (exits 7 if any FAIL)").option(
69972
+ var setupCommand = new Command("setup").description("Configure VibeFrame (LLM provider, API keys)").option("--reset", "Reset configuration to defaults").option("--full", "Run full setup with all optional providers").option("--show", "Show current configuration (for debugging)").option(
69973
+ "-v, --verbose",
69974
+ "With --show: include unset providers + Resolution order + Defaults block"
69975
+ ).option("--claude-code", "Show Claude Code integration guide").option("-y, --yes", "Non-interactive: write config without prompting (CI / devcontainer)").option(
69976
+ "--provider <id>",
69977
+ "Set the Agent LLM provider (claude | openai | gemini | xai | openrouter | ollama)"
69978
+ ).option("--import-env", "Promote API keys from .env / shell env into config.yaml").option("--test", "After save, live-test each configured key (exits 7 if any FAIL)").option(
69974
69979
  "--scope <scope>",
69975
69980
  "Where to save: 'user' (~/.vibeframe/config.yaml, shared) or 'project' (./.vibeframe/config.yaml, gitignored, this project only)",
69976
69981
  "user"
@@ -70020,10 +70025,12 @@ Exit codes (non-interactive):
70020
70025
  return;
70021
70026
  }
70022
70027
  if (!hasTTY()) {
70023
- exitWithError(generalError(
70024
- "Interactive setup requires a terminal.",
70025
- "Run 'vibe setup' directly from your terminal, or use 'vibe setup --yes --provider <id> --import-env' for CI."
70026
- ));
70028
+ exitWithError(
70029
+ generalError(
70030
+ "Interactive setup requires a terminal.",
70031
+ "Run 'vibe setup' directly from your terminal, or use 'vibe setup --yes --provider <id> --import-env' for CI."
70032
+ )
70033
+ );
70027
70034
  }
70028
70035
  try {
70029
70036
  await runSetupWizard(options.full, scope);
@@ -70040,24 +70047,50 @@ var AI_FEATURES = [
70040
70047
  desc: "generate + edit",
70041
70048
  defaultProvider: "OpenAI gpt-image-2 (Artificial Analysis #1, since v0.56)",
70042
70049
  alsoAvailable: "Gemini Nano Banana, Grok Imagine",
70043
- 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)" }],
70050
+ keys: [
70051
+ {
70052
+ configKey: "openai",
70053
+ envVar: "OPENAI_API_KEY",
70054
+ name: "OpenAI",
70055
+ url: "https://platform.openai.com/api-keys",
70056
+ what: "gpt-image-2 image generation + editing (also Whisper, Agent)"
70057
+ }
70058
+ ],
70044
70059
  providerChoices: [
70045
70060
  {
70046
70061
  label: "OpenAI gpt-image-2",
70047
70062
  desc: "recommended default, image generation + editing",
70048
- 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)" },
70063
+ key: {
70064
+ configKey: "openai",
70065
+ envVar: "OPENAI_API_KEY",
70066
+ name: "OpenAI",
70067
+ url: "https://platform.openai.com/api-keys",
70068
+ what: "gpt-image-2 image generation + editing (also Whisper, Agent)"
70069
+ },
70049
70070
  defaultFor: { kind: "image", value: "openai" }
70050
70071
  },
70051
70072
  {
70052
70073
  label: "Gemini",
70053
70074
  desc: "Google image generation + multimodal analysis",
70054
- key: { configKey: "google", envVar: "GOOGLE_API_KEY", name: "Google", url: "https://aistudio.google.com/apikey", what: "Gemini image generation and multimodal analysis" },
70075
+ key: {
70076
+ configKey: "google",
70077
+ envVar: "GOOGLE_API_KEY",
70078
+ name: "Google",
70079
+ url: "https://aistudio.google.com/apikey",
70080
+ what: "Gemini image generation and multimodal analysis"
70081
+ },
70055
70082
  defaultFor: { kind: "image", value: "gemini" }
70056
70083
  },
70057
70084
  {
70058
70085
  label: "Grok",
70059
70086
  desc: "xAI image generation",
70060
- key: { configKey: "xai", envVar: "XAI_API_KEY", name: "xAI", url: "https://console.x.ai/", what: "Grok image generation and Agent" },
70087
+ key: {
70088
+ configKey: "xai",
70089
+ envVar: "XAI_API_KEY",
70090
+ name: "xAI",
70091
+ url: "https://console.x.ai/",
70092
+ what: "Grok image generation and Agent"
70093
+ },
70061
70094
  defaultFor: { kind: "image", value: "grok" }
70062
70095
  }
70063
70096
  ],
@@ -70068,36 +70101,74 @@ var AI_FEATURES = [
70068
70101
  desc: "generate + extend",
70069
70102
  defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
70070
70103
  alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
70071
- 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" }],
70104
+ keys: [
70105
+ {
70106
+ configKey: "fal",
70107
+ envVar: "FAL_KEY",
70108
+ name: "Seedance 2.0 (via fal.ai)",
70109
+ url: "https://fal.ai/dashboard/keys",
70110
+ what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
70111
+ }
70112
+ ],
70072
70113
  providerChoices: [
70073
70114
  {
70074
70115
  label: "Seedance 2.0 via fal.ai",
70075
70116
  desc: "recommended default, text-to-video + image-to-video",
70076
- 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" },
70117
+ key: {
70118
+ configKey: "fal",
70119
+ envVar: "FAL_KEY",
70120
+ name: "Seedance 2.0 (via fal.ai)",
70121
+ url: "https://fal.ai/dashboard/keys",
70122
+ what: "ByteDance Seedance 2.0 text-to-video and image-to-video"
70123
+ },
70077
70124
  defaultFor: { kind: "video", value: "seedance" }
70078
70125
  },
70079
70126
  {
70080
70127
  label: "Grok",
70081
70128
  desc: "xAI video generation with audio",
70082
- key: { configKey: "xai", envVar: "XAI_API_KEY", name: "xAI", url: "https://console.x.ai/", what: "Grok video generation and Agent" },
70129
+ key: {
70130
+ configKey: "xai",
70131
+ envVar: "XAI_API_KEY",
70132
+ name: "xAI",
70133
+ url: "https://console.x.ai/",
70134
+ what: "Grok video generation and Agent"
70135
+ },
70083
70136
  defaultFor: { kind: "video", value: "grok" }
70084
70137
  },
70085
70138
  {
70086
70139
  label: "Kling",
70087
70140
  desc: "Kling v2.5/v3 video generation",
70088
- key: { configKey: "kling", envVar: "KLING_API_KEY", name: "Kling", url: "https://platform.klingai.com/", what: "Kling video generation" },
70141
+ key: {
70142
+ configKey: "kling",
70143
+ envVar: "KLING_API_KEY",
70144
+ name: "Kling",
70145
+ url: "https://platform.klingai.com/",
70146
+ what: "Kling video generation"
70147
+ },
70089
70148
  defaultFor: { kind: "video", value: "kling" }
70090
70149
  },
70091
70150
  {
70092
70151
  label: "Runway",
70093
70152
  desc: "Runway Gen-4.5 video generation",
70094
- key: { configKey: "runway", envVar: "RUNWAY_API_SECRET", name: "Runway", url: "https://app.runwayml.com/settings/api-keys", what: "Runway Gen-4.5 video generation" },
70153
+ key: {
70154
+ configKey: "runway",
70155
+ envVar: "RUNWAY_API_SECRET",
70156
+ name: "Runway",
70157
+ url: "https://app.runwayml.com/settings/api-keys",
70158
+ what: "Runway Gen-4.5 video generation"
70159
+ },
70095
70160
  defaultFor: { kind: "video", value: "runway" }
70096
70161
  },
70097
70162
  {
70098
70163
  label: "Veo",
70099
70164
  desc: "Google Veo video generation",
70100
- key: { configKey: "google", envVar: "GOOGLE_API_KEY", name: "Google", url: "https://aistudio.google.com/apikey", what: "Google Veo video generation" },
70165
+ key: {
70166
+ configKey: "google",
70167
+ envVar: "GOOGLE_API_KEY",
70168
+ name: "Google",
70169
+ url: "https://aistudio.google.com/apikey",
70170
+ what: "Google Veo video generation"
70171
+ },
70101
70172
  defaultFor: { kind: "video", value: "veo" }
70102
70173
  }
70103
70174
  ],
@@ -70108,12 +70179,26 @@ var AI_FEATURES = [
70108
70179
  desc: "TTS, SFX, music, voice clone",
70109
70180
  defaultProvider: "ElevenLabs (paid, premium quality) \u2014 falls back to local Kokoro when no key (free, since v0.54)",
70110
70181
  alsoAvailable: "Replicate MusicGen (music only)",
70111
- 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)" }],
70182
+ keys: [
70183
+ {
70184
+ configKey: "elevenlabs",
70185
+ envVar: "ELEVENLABS_API_KEY",
70186
+ name: "ElevenLabs",
70187
+ url: "https://elevenlabs.io/app/settings/api-keys",
70188
+ what: "Text-to-speech, sound effects, music, voice cloning (skip to use local Kokoro)"
70189
+ }
70190
+ ],
70112
70191
  providerChoices: [
70113
70192
  {
70114
70193
  label: "ElevenLabs",
70115
70194
  desc: "premium TTS, SFX, music, voice clone",
70116
- 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" }
70195
+ key: {
70196
+ configKey: "elevenlabs",
70197
+ envVar: "ELEVENLABS_API_KEY",
70198
+ name: "ElevenLabs",
70199
+ url: "https://elevenlabs.io/app/settings/api-keys",
70200
+ what: "Text-to-speech, sound effects, music, voice cloning"
70201
+ }
70117
70202
  },
70118
70203
  {
70119
70204
  label: "Kokoro local",
@@ -70123,7 +70208,13 @@ var AI_FEATURES = [
70123
70208
  {
70124
70209
  label: "Replicate MusicGen",
70125
70210
  desc: "background music generation",
70126
- key: { configKey: "replicate", envVar: "REPLICATE_API_TOKEN", name: "Replicate", url: "https://replicate.com/account/api-tokens", what: "MusicGen background music" }
70211
+ key: {
70212
+ configKey: "replicate",
70213
+ envVar: "REPLICATE_API_TOKEN",
70214
+ name: "Replicate",
70215
+ url: "https://replicate.com/account/api-tokens",
70216
+ what: "MusicGen background music"
70217
+ }
70127
70218
  }
70128
70219
  ],
70129
70220
  tryCommand: 'vibe generate speech "Hello world" -o hello.mp3'
@@ -70134,8 +70225,20 @@ var AI_FEATURES = [
70134
70225
  defaultProvider: "Whisper (transcription) + Claude (reasoning)",
70135
70226
  alsoAvailable: "",
70136
70227
  keys: [
70137
- { configKey: "openai", envVar: "OPENAI_API_KEY", name: "OpenAI", url: "https://platform.openai.com/api-keys", what: "Whisper transcription (captions, jump-cut)" },
70138
- { configKey: "anthropic", envVar: "ANTHROPIC_API_KEY", name: "Anthropic", url: "https://console.anthropic.com/settings/keys", what: "Claude (color grade, reframe, motion graphics)" }
70228
+ {
70229
+ configKey: "openai",
70230
+ envVar: "OPENAI_API_KEY",
70231
+ name: "OpenAI",
70232
+ url: "https://platform.openai.com/api-keys",
70233
+ what: "Whisper transcription (captions, jump-cut)"
70234
+ },
70235
+ {
70236
+ configKey: "anthropic",
70237
+ envVar: "ANTHROPIC_API_KEY",
70238
+ name: "Anthropic",
70239
+ url: "https://console.anthropic.com/settings/keys",
70240
+ what: "Claude (color grade, reframe, motion graphics)"
70241
+ }
70139
70242
  ],
70140
70243
  tryCommand: "vibe edit caption video.mp4 -o captioned.mp4"
70141
70244
  }
@@ -70166,6 +70269,14 @@ function isProviderChoiceConfigured(config2, choice) {
70166
70269
  }
70167
70270
  return false;
70168
70271
  }
70272
+ function isFeatureConfigured(config2, feature) {
70273
+ if (feature.providerChoices?.some((choice) => isProviderChoiceConfigured(config2, choice))) {
70274
+ return true;
70275
+ }
70276
+ return feature.keys.some(
70277
+ (keyDef) => Boolean(config2.providers[keyDef.configKey])
70278
+ );
70279
+ }
70169
70280
  function mergeSelectedFeatures(current, next) {
70170
70281
  const merged = [...current];
70171
70282
  for (const feature of next) {
@@ -70184,10 +70295,12 @@ async function runNonInteractiveSetup(opts) {
70184
70295
  const changes = [];
70185
70296
  if (opts.provider) {
70186
70297
  if (!VALID_LLM_PROVIDERS.includes(opts.provider)) {
70187
- exitWithError(generalError(
70188
- `Invalid --provider: ${opts.provider}`,
70189
- `Must be one of: ${VALID_LLM_PROVIDERS.join(", ")}`
70190
- ));
70298
+ exitWithError(
70299
+ generalError(
70300
+ `Invalid --provider: ${opts.provider}`,
70301
+ `Must be one of: ${VALID_LLM_PROVIDERS.join(", ")}`
70302
+ )
70303
+ );
70191
70304
  }
70192
70305
  if (config2.llm.provider !== opts.provider) {
70193
70306
  config2.llm.provider = opts.provider;
@@ -70254,7 +70367,11 @@ async function runNonInteractiveSetup(opts) {
70254
70367
  }
70255
70368
  if (failures > 0) {
70256
70369
  console.error();
70257
- console.error(source_default.red(`Verification failed: ${failures} key${failures === 1 ? "" : "s"} returned non-2xx.`));
70370
+ console.error(
70371
+ source_default.red(
70372
+ `Verification failed: ${failures} key${failures === 1 ? "" : "s"} returned non-2xx.`
70373
+ )
70374
+ );
70258
70375
  process.exit(7);
70259
70376
  }
70260
70377
  }
@@ -70263,15 +70380,11 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70263
70380
  console.log();
70264
70381
  const cfgPath = getConfigPath(scope).replace(homedir5(), "~");
70265
70382
  const scopeNote = scope === "project" ? "applies to this project only \xB7 add .vibeframe/ to .gitignore" : "applies to every project for this user";
70266
- console.log(
70267
- source_default.bold.magenta("VibeFrame Setup") + source_default.bold(` \u2014 ${scope} scope`)
70268
- );
70383
+ console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.bold(` \u2014 ${scope} scope`));
70269
70384
  console.log(source_default.dim(`${cfgPath} \xB7 ${scopeNote}`));
70270
70385
  if (scope === "user") {
70271
70386
  console.log(
70272
- source_default.dim(
70273
- "Tip: for per-project keys, cd into a project and run: vibe setup --scope project"
70274
- )
70387
+ source_default.dim("Tip: for per-project keys, cd into a project and run: vibe setup --scope project")
70275
70388
  );
70276
70389
  }
70277
70390
  console.log(source_default.dim("\u2500".repeat(40)));
@@ -70312,10 +70425,34 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70312
70425
  if (topIndex === 2) {
70313
70426
  config2.llm.provider = "claude";
70314
70427
  const pipelineKeys = [
70315
- { configKey: "anthropic", envVar: "ANTHROPIC_API_KEY", name: "Anthropic", url: "https://console.anthropic.com/settings/keys", what: "Claude \u2014 storyboard generation + reasoning" },
70316
- { configKey: "openai", envVar: "OPENAI_API_KEY", name: "OpenAI", url: "https://platform.openai.com/api-keys", what: "gpt-image-2 image generation (default since v0.56) + Whisper word-level transcribe" },
70317
- { configKey: "fal", envVar: "FAL_KEY", name: "fal.ai", url: "https://fal.ai/dashboard/keys", what: "Seedance 2.0 \u2014 video generation, default since v0.57" },
70318
- { configKey: "elevenlabs", envVar: "ELEVENLABS_API_KEY", name: "ElevenLabs", url: "https://elevenlabs.io/app/settings/api-keys", what: "Text-to-speech narration + music (skip to use local Kokoro)" }
70428
+ {
70429
+ configKey: "anthropic",
70430
+ envVar: "ANTHROPIC_API_KEY",
70431
+ name: "Anthropic",
70432
+ url: "https://console.anthropic.com/settings/keys",
70433
+ what: "Claude \u2014 storyboard generation + reasoning"
70434
+ },
70435
+ {
70436
+ configKey: "openai",
70437
+ envVar: "OPENAI_API_KEY",
70438
+ name: "OpenAI",
70439
+ url: "https://platform.openai.com/api-keys",
70440
+ what: "gpt-image-2 image generation (default since v0.56) + Whisper word-level transcribe"
70441
+ },
70442
+ {
70443
+ configKey: "fal",
70444
+ envVar: "FAL_KEY",
70445
+ name: "fal.ai",
70446
+ url: "https://fal.ai/dashboard/keys",
70447
+ what: "Seedance 2.0 \u2014 video generation, default since v0.57"
70448
+ },
70449
+ {
70450
+ configKey: "elevenlabs",
70451
+ envVar: "ELEVENLABS_API_KEY",
70452
+ name: "ElevenLabs",
70453
+ url: "https://elevenlabs.io/app/settings/api-keys",
70454
+ what: "Text-to-speech narration + music (skip to use local Kokoro)"
70455
+ }
70319
70456
  ];
70320
70457
  console.log(source_default.dim("2. API keys"));
70321
70458
  console.log(source_default.bold("Pipeline requires these API keys:"));
@@ -70323,10 +70460,19 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70323
70460
  console.log();
70324
70461
  await collectKeys(config2, pipelineKeys);
70325
70462
  await saveConfig(config2, { scope });
70326
- await completeOrContinue(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format", [], scope);
70463
+ await completeOrContinue(
70464
+ config2,
70465
+ "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format",
70466
+ [],
70467
+ scope
70468
+ );
70327
70469
  return;
70328
70470
  }
70329
70471
  const result = await runAIFeaturesSetup(config2, scope);
70472
+ if (result.back) {
70473
+ await completeOrContinue(config2, "vibe --help", [], scope);
70474
+ return;
70475
+ }
70330
70476
  await saveConfig(config2, { scope });
70331
70477
  await completeOrContinue(config2, result.tryCommand, result.selectedFeatures, scope);
70332
70478
  }
@@ -70346,6 +70492,10 @@ async function completeOrContinue(config2, tryCommand, selectedFeatures, scope)
70346
70492
  console.log();
70347
70493
  if (action === 0) {
70348
70494
  const result = await runAIFeaturesSetup(config2, scope);
70495
+ if (result.back) {
70496
+ await completeOrContinue(config2, tryCommand, selectedFeatures, scope);
70497
+ return;
70498
+ }
70349
70499
  const mergedFeatures = mergeSelectedFeatures(selectedFeatures, result.selectedFeatures);
70350
70500
  const mergedTryCommand = selectedFeatures.length > 0 ? tryCommand : result.tryCommand;
70351
70501
  await saveConfig(config2, { scope });
@@ -70361,142 +70511,168 @@ async function completeOrContinue(config2, tryCommand, selectedFeatures, scope)
70361
70511
  await showComplete(config2, tryCommand, selectedFeatures, scope);
70362
70512
  }
70363
70513
  async function runAIFeaturesSetup(config2, _scope) {
70364
- console.log(source_default.dim("2. Features"));
70365
- console.log(source_default.bold("Which AI features do you need?"));
70366
- console.log(source_default.dim(` ${source_default.bold("Most users pick more than one.")} Press ${source_default.bold("space")} on each feature you want, then ${source_default.bold("enter")} to confirm.`));
70367
- console.log(source_default.dim(" Shortcut: enter alone picks only the highlighted feature."));
70368
- console.log();
70369
- const featureLabels = AI_FEATURES.map((f) => {
70370
- const keyCount = f.keys.length;
70371
- const tag = source_default.dim(`${keyCount} key${keyCount > 1 ? "s" : ""}`);
70372
- return `${f.label} ${source_default.dim(`(${f.desc})`)} ${tag}`;
70373
- });
70374
- const picked = await promptMultiSelect(
70375
- source_default.cyan(" Pick (e.g. 1,3 or 'all'): "),
70376
- featureLabels,
70377
- [],
70378
- { pickFocusedOnEnter: true }
70379
- );
70380
- const selectedFeatures = picked.map((i) => AI_FEATURES[i]);
70381
- console.log();
70382
- if (selectedFeatures.length === 0) {
70383
- console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
70514
+ featureSelection: for (; ; ) {
70515
+ console.log(source_default.dim("2. Features"));
70516
+ console.log(source_default.bold("Which AI features do you need?"));
70517
+ console.log(
70518
+ source_default.dim(
70519
+ ` ${source_default.bold("Most users pick more than one.")} Press ${source_default.bold("space")} on each feature you want, then ${source_default.bold("enter")} to confirm.`
70520
+ )
70521
+ );
70522
+ console.log(source_default.dim(" Shortcut: enter alone picks only the highlighted feature."));
70523
+ console.log(source_default.dim(` Select ${source_default.bold("Back")} to return to the setup checkpoint.`));
70384
70524
  console.log();
70385
- return { tryCommand: "vibe --help", selectedFeatures: [] };
70386
- }
70387
- const plannedKeys = /* @__PURE__ */ new Map();
70388
- console.log(source_default.dim("3. Providers"));
70389
- console.log(source_default.bold("Choose providers for each selected feature"));
70390
- console.log(source_default.dim(` Recommended default is highlighted. Press ${source_default.bold("enter")} to pick the highlighted provider, or ${source_default.bold("space")} to choose multiple.`));
70391
- console.log();
70392
- for (const feature of selectedFeatures) {
70393
- const keysForFeature = [];
70394
- if (feature.providerChoices && feature.providerChoices.length > 0) {
70395
- console.log(source_default.bold.cyan(` ${feature.label}`));
70396
- console.log(source_default.dim(` Default path: ${feature.defaultProvider}`));
70397
- if (feature.alsoAvailable) {
70398
- console.log(source_default.dim(` Alternatives: ${feature.alsoAvailable}`));
70399
- }
70525
+ const backIndex = AI_FEATURES.length;
70526
+ const featureLabels = [
70527
+ ...AI_FEATURES.map((f) => {
70528
+ const keyCount = f.keys.length;
70529
+ const tag = source_default.dim(`${keyCount} key${keyCount > 1 ? "s" : ""}`);
70530
+ return `${f.label} ${source_default.dim(`(${f.desc})`)} ${tag}`;
70531
+ }),
70532
+ `Back ${source_default.dim("(return to setup checkpoint)")}`
70533
+ ];
70534
+ const configuredFeatures = AI_FEATURES.map((feature) => isFeatureConfigured(config2, feature));
70535
+ const picked = await promptMultiSelect(
70536
+ source_default.cyan(" Pick (e.g. 1,3 or 'all'): "),
70537
+ featureLabels,
70538
+ [...configuredFeatures, false],
70539
+ { pickFocusedOnEnter: true, preserveDefaultSelectionOnEnter: true }
70540
+ );
70541
+ if (picked.includes(backIndex)) {
70400
70542
  console.log();
70401
- const defaultSelected = feature.providerChoices.map(
70402
- (choice) => isProviderChoiceConfigured(config2, choice)
70403
- );
70404
- const providerLabels = feature.providerChoices.map((choice) => {
70405
- const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(choice.key.envVar) : source_default.dim("no key");
70406
- const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
70407
- return [
70408
- choice.label,
70409
- defaultHint,
70410
- source_default.dim(`- ${choice.desc}`),
70411
- keyHint
70412
- ].filter(Boolean).join(" ");
70413
- });
70414
- const pickedProviders = await promptMultiSelect(
70415
- source_default.cyan(" Providers (arrow + enter picks one, space adds more): "),
70416
- providerLabels,
70417
- defaultSelected,
70418
- { pickFocusedOnEnter: true, preserveDefaultSelectionOnEnter: true }
70419
- );
70420
- const selectedChoices = pickedProviders.map((i) => feature.providerChoices[i]);
70421
- const defaultChoice = selectedChoices.find((choice) => choice.defaultFor);
70422
- if (defaultChoice) {
70423
- applyProviderDefault(config2, defaultChoice);
70424
- }
70425
- for (const choice of selectedChoices) {
70426
- if (choice.key) addUniqueKey(keysForFeature, choice.key);
70427
- }
70428
- if (selectedChoices.some((choice) => choice.keyless)) {
70429
- console.log(source_default.dim(" No API key needed for selected local provider(s)."));
70430
- }
70543
+ return { tryCommand: "vibe --help", selectedFeatures: [], back: true };
70544
+ }
70545
+ const selectedFeatures = picked.filter((i) => i < AI_FEATURES.length).map((i) => AI_FEATURES[i]);
70546
+ console.log();
70547
+ if (selectedFeatures.length === 0) {
70548
+ console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
70431
70549
  console.log();
70432
- } else {
70433
- for (const keyDef of feature.keys) {
70434
- addUniqueKey(keysForFeature, keyDef);
70435
- }
70550
+ return { tryCommand: "vibe --help", selectedFeatures: [] };
70436
70551
  }
70437
- plannedKeys.set(feature.label, keysForFeature);
70438
- }
70439
- const needsKeys = [...plannedKeys.values()].some((keys) => keys.length > 0);
70440
- if (needsKeys) {
70441
- console.log(source_default.dim("4. API keys"));
70442
- console.log(source_default.bold("API Keys"));
70443
- console.log(source_default.dim(" Saved locally, never shared. Press Enter to skip."));
70552
+ const plannedKeys = /* @__PURE__ */ new Map();
70553
+ console.log(source_default.dim("3. Providers"));
70554
+ console.log(source_default.bold("Choose providers for each selected feature"));
70555
+ console.log(
70556
+ source_default.dim(
70557
+ ` Recommended default is highlighted. Press ${source_default.bold("enter")} to pick the highlighted provider, or ${source_default.bold("space")} to choose multiple.`
70558
+ )
70559
+ );
70444
70560
  console.log();
70445
- const collectedKeys = /* @__PURE__ */ new Set();
70446
70561
  for (const feature of selectedFeatures) {
70447
- const keysForFeature = plannedKeys.get(feature.label) ?? [];
70448
- if (keysForFeature.length === 0) {
70449
- continue;
70450
- }
70451
- console.log(source_default.bold.cyan(` ${feature.label}`));
70452
- console.log(source_default.dim(` Default: ${feature.defaultProvider}`));
70453
- if (feature.alsoAvailable) {
70454
- console.log(source_default.dim(` Also available: ${feature.alsoAvailable}`));
70562
+ const keysForFeature = [];
70563
+ if (feature.providerChoices && feature.providerChoices.length > 0) {
70564
+ console.log(source_default.bold.cyan(` ${feature.label}`));
70565
+ console.log(source_default.dim(` Default path: ${feature.defaultProvider}`));
70566
+ if (feature.alsoAvailable) {
70567
+ console.log(source_default.dim(` Alternatives: ${feature.alsoAvailable}`));
70568
+ }
70569
+ console.log();
70570
+ const defaultSelected = feature.providerChoices.map(
70571
+ (choice) => isProviderChoiceConfigured(config2, choice)
70572
+ );
70573
+ const providerLabels = feature.providerChoices.map((choice) => {
70574
+ const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(choice.key.envVar) : source_default.dim("no key");
70575
+ const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
70576
+ return [choice.label, defaultHint, source_default.dim(`- ${choice.desc}`), keyHint].filter(Boolean).join(" ");
70577
+ });
70578
+ const backProviderIndex = providerLabels.length;
70579
+ const pickedProviders = await promptMultiSelect(
70580
+ source_default.cyan(" Providers (arrow + enter picks one, space adds more): "),
70581
+ [...providerLabels, `Back ${source_default.dim("(choose different features)")}`],
70582
+ [...defaultSelected, false],
70583
+ { pickFocusedOnEnter: true, preserveDefaultSelectionOnEnter: true }
70584
+ );
70585
+ if (pickedProviders.includes(backProviderIndex)) {
70586
+ console.log();
70587
+ continue featureSelection;
70588
+ }
70589
+ const selectedChoices = pickedProviders.filter((i) => i < feature.providerChoices.length).map((i) => feature.providerChoices[i]);
70590
+ const defaultChoice = selectedChoices.find((choice) => choice.defaultFor);
70591
+ if (defaultChoice) {
70592
+ applyProviderDefault(config2, defaultChoice);
70593
+ }
70594
+ for (const choice of selectedChoices) {
70595
+ if (choice.key) addUniqueKey(keysForFeature, choice.key);
70596
+ }
70597
+ if (selectedChoices.some((choice) => choice.keyless)) {
70598
+ console.log(source_default.dim(" No API key needed for selected local provider(s)."));
70599
+ }
70600
+ console.log();
70601
+ } else {
70602
+ for (const keyDef of feature.keys) {
70603
+ addUniqueKey(keysForFeature, keyDef);
70604
+ }
70455
70605
  }
70606
+ plannedKeys.set(feature.label, keysForFeature);
70607
+ }
70608
+ const needsKeys = [...plannedKeys.values()].some((keys) => keys.length > 0);
70609
+ if (needsKeys) {
70610
+ console.log(source_default.dim("4. API keys"));
70611
+ console.log(source_default.bold("API Keys"));
70612
+ console.log(source_default.dim(" Saved locally, never shared. Press Enter to skip."));
70456
70613
  console.log();
70457
- for (const keyDef of keysForFeature) {
70458
- if (collectedKeys.has(keyDef.configKey)) {
70459
- console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} (already set above)`);
70614
+ const collectedKeys = /* @__PURE__ */ new Set();
70615
+ for (const feature of selectedFeatures) {
70616
+ const keysForFeature = plannedKeys.get(feature.label) ?? [];
70617
+ if (keysForFeature.length === 0) {
70460
70618
  continue;
70461
70619
  }
70462
- loadEnv();
70463
- const configValue = config2.providers[keyDef.configKey];
70464
- const envValue = process.env[keyDef.envVar];
70465
- if (configValue || envValue) {
70466
- const value = configValue || envValue;
70467
- const source = configValue ? "config" : ".env";
70468
- console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(value)} ${source_default.dim(`(${source})`)}`);
70469
- collectedKeys.add(keyDef.configKey);
70470
- continue;
70620
+ console.log(source_default.bold.cyan(` ${feature.label}`));
70621
+ console.log(source_default.dim(` Default: ${feature.defaultProvider}`));
70622
+ if (feature.alsoAvailable) {
70623
+ console.log(source_default.dim(` Also available: ${feature.alsoAvailable}`));
70471
70624
  }
70472
- console.log(source_default.dim(` ${keyDef.what}`));
70473
- console.log(source_default.dim(` Get key: ${keyDef.url} [o] open in browser`));
70474
- const newKey = await promptHidden(
70475
- source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `),
70476
- { openHotkeyUrl: keyDef.url }
70477
- );
70478
- if (newKey.trim()) {
70479
- const trimmed = newKey.trim();
70480
- config2.providers[keyDef.configKey] = trimmed;
70481
- const fmt = validateKeyFormat(keyDef.configKey, trimmed);
70482
- if (!fmt.ok && fmt.expected) {
70483
- console.log(` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`);
70625
+ console.log();
70626
+ for (const keyDef of keysForFeature) {
70627
+ if (collectedKeys.has(keyDef.configKey)) {
70628
+ console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} (already set above)`);
70629
+ continue;
70630
+ }
70631
+ loadEnv();
70632
+ const configValue = config2.providers[keyDef.configKey];
70633
+ const envValue = process.env[keyDef.envVar];
70634
+ if (configValue || envValue) {
70635
+ const value = configValue || envValue;
70636
+ const source = configValue ? "config" : ".env";
70637
+ console.log(
70638
+ ` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(value)} ${source_default.dim(`(${source})`)}`
70639
+ );
70640
+ collectedKeys.add(keyDef.configKey);
70641
+ continue;
70642
+ }
70643
+ console.log(source_default.dim(` ${keyDef.what}`));
70644
+ console.log(source_default.dim(` Get key: ${keyDef.url} [o] open in browser`));
70645
+ const newKey = await promptHidden(
70646
+ source_default.cyan(` ${keyDef.name.padEnd(14)} ${source_default.dim(keyDef.envVar)}: `),
70647
+ { openHotkeyUrl: keyDef.url }
70648
+ );
70649
+ if (newKey.trim()) {
70650
+ const trimmed = newKey.trim();
70651
+ config2.providers[keyDef.configKey] = trimmed;
70652
+ const fmt = validateKeyFormat(keyDef.configKey, trimmed);
70653
+ if (!fmt.ok && fmt.expected) {
70654
+ console.log(
70655
+ ` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`
70656
+ );
70657
+ } else {
70658
+ console.log(` ${source_default.green("\u2713")} Saved`);
70659
+ }
70660
+ collectedKeys.add(keyDef.configKey);
70484
70661
  } else {
70485
- console.log(` ${source_default.green("\u2713")} Saved`);
70662
+ console.log(
70663
+ ` ${source_default.yellow("\u26A0")} Skipped ${source_default.dim(`(set ${keyDef.envVar} in .env later)`)}`
70664
+ );
70486
70665
  }
70487
- collectedKeys.add(keyDef.configKey);
70488
- } else {
70489
- console.log(` ${source_default.yellow("\u26A0")} Skipped ${source_default.dim(`(set ${keyDef.envVar} in .env later)`)}`);
70490
70666
  }
70667
+ console.log();
70491
70668
  }
70669
+ } else {
70670
+ console.log(source_default.dim("4. API keys"));
70671
+ console.log(source_default.dim(" No API keys needed for the selected provider(s)."));
70492
70672
  console.log();
70493
70673
  }
70494
- } else {
70495
- console.log(source_default.dim("4. API keys"));
70496
- console.log(source_default.dim(" No API keys needed for the selected provider(s)."));
70497
- console.log();
70674
+ return { tryCommand: selectedFeatures[0].tryCommand, selectedFeatures };
70498
70675
  }
70499
- return { tryCommand: selectedFeatures[0].tryCommand, selectedFeatures };
70500
70676
  }
70501
70677
  async function collectKeys(config2, keys) {
70502
70678
  loadEnv();
@@ -70504,11 +70680,15 @@ async function collectKeys(config2, keys) {
70504
70680
  const configValue = config2.providers[keyDef.configKey];
70505
70681
  const envValue = process.env[keyDef.envVar];
70506
70682
  if (configValue) {
70507
- console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(configValue)} ${source_default.dim("(config)")}`);
70683
+ console.log(
70684
+ ` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(configValue)} ${source_default.dim("(config)")}`
70685
+ );
70508
70686
  continue;
70509
70687
  }
70510
70688
  if (envValue) {
70511
- console.log(` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(envValue)} ${source_default.dim("(.env)")}`);
70689
+ console.log(
70690
+ ` ${source_default.green("\u2713")} ${keyDef.name.padEnd(14)} ${maskApiKey(envValue)} ${source_default.dim("(.env)")}`
70691
+ );
70512
70692
  continue;
70513
70693
  }
70514
70694
  console.log(source_default.dim(` ${keyDef.what}`));
@@ -70522,12 +70702,16 @@ async function collectKeys(config2, keys) {
70522
70702
  config2.providers[keyDef.configKey] = trimmed;
70523
70703
  const fmt = validateKeyFormat(keyDef.configKey, trimmed);
70524
70704
  if (!fmt.ok && fmt.expected) {
70525
- console.log(` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`);
70705
+ console.log(
70706
+ ` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`
70707
+ );
70526
70708
  } else {
70527
70709
  console.log(` ${source_default.green("\u2713")} Saved`);
70528
70710
  }
70529
70711
  } else {
70530
- console.log(` ${source_default.yellow("\u26A0")} Skipped ${source_default.dim(`(set ${keyDef.envVar} in .env later)`)}`);
70712
+ console.log(
70713
+ ` ${source_default.yellow("\u26A0")} Skipped ${source_default.dim(`(set ${keyDef.envVar} in .env later)`)}`
70714
+ );
70531
70715
  }
70532
70716
  }
70533
70717
  console.log();
@@ -70564,37 +70748,41 @@ async function runCustomSetup(config2, scope = "user", opts = {}) {
70564
70748
  for (const p of allProviders) {
70565
70749
  const existing = config2.providers[p.key];
70566
70750
  if (existing) {
70567
- console.log(` ${source_default.green("\u2713")} ${p.name.padEnd(12)} ${maskApiKey(existing)} ${source_default.dim(p.desc)}`);
70751
+ console.log(
70752
+ ` ${source_default.green("\u2713")} ${p.name.padEnd(12)} ${maskApiKey(existing)} ${source_default.dim(p.desc)}`
70753
+ );
70568
70754
  const change = await promptConfirm(source_default.cyan(" Update?"), false);
70569
70755
  if (change) {
70570
70756
  console.log(source_default.dim(` Get key: ${p.url} [o] open in browser`));
70571
- const newKey = await promptHidden(
70572
- source_default.cyan(" New key: "),
70573
- { openHotkeyUrl: p.url }
70574
- );
70757
+ const newKey = await promptHidden(source_default.cyan(" New key: "), { openHotkeyUrl: p.url });
70575
70758
  if (newKey.trim()) {
70576
70759
  const trimmed = newKey.trim();
70577
70760
  config2.providers[p.key] = trimmed;
70578
70761
  const fmt = validateKeyFormat(p.key, trimmed);
70579
70762
  if (!fmt.ok && fmt.expected) {
70580
- console.log(source_default.yellow(` \u26A0 Updated, but format looks unusual (expected ${fmt.expected})`));
70763
+ console.log(
70764
+ source_default.yellow(` \u26A0 Updated, but format looks unusual (expected ${fmt.expected})`)
70765
+ );
70581
70766
  } else {
70582
70767
  console.log(source_default.green(" \u2713 Updated"));
70583
70768
  }
70584
70769
  }
70585
70770
  }
70586
70771
  } else {
70587
- console.log(source_default.dim(` ${source_default.dim("\u25CB")} ${p.name.padEnd(12)} ${source_default.dim(p.desc)} Get key: ${p.url} [o] open`));
70588
- const newKey = await promptHidden(
70589
- source_default.cyan(` ${p.env}: `),
70590
- { openHotkeyUrl: p.url }
70772
+ console.log(
70773
+ source_default.dim(
70774
+ ` ${source_default.dim("\u25CB")} ${p.name.padEnd(12)} ${source_default.dim(p.desc)} Get key: ${p.url} [o] open`
70775
+ )
70591
70776
  );
70777
+ const newKey = await promptHidden(source_default.cyan(` ${p.env}: `), { openHotkeyUrl: p.url });
70592
70778
  if (newKey.trim()) {
70593
70779
  const trimmed = newKey.trim();
70594
70780
  config2.providers[p.key] = trimmed;
70595
70781
  const fmt = validateKeyFormat(p.key, trimmed);
70596
70782
  if (!fmt.ok && fmt.expected) {
70597
- console.log(` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`);
70783
+ console.log(
70784
+ ` ${source_default.yellow("\u26A0")} Saved, but format looks unusual ${source_default.dim(`(expected ${fmt.expected})`)}`
70785
+ );
70598
70786
  } else {
70599
70787
  console.log(` ${source_default.green("\u2713")} Saved`);
70600
70788
  }
@@ -70631,14 +70819,20 @@ async function showComplete(config2, defaultTryCommand, features = [], scope = "
70631
70819
  }
70632
70820
  console.log();
70633
70821
  console.log(source_default.bold(" Next steps:"));
70634
- console.log(source_default.dim(" cd <project>; vibe init Scaffold AGENTS.md / CLAUDE.md / .env.example (project scope)"));
70822
+ console.log(
70823
+ source_default.dim(
70824
+ " cd <project>; vibe init Scaffold AGENTS.md / CLAUDE.md / .env.example (project scope)"
70825
+ )
70826
+ );
70635
70827
  const hostDetected = detectedAgentHosts().length > 0;
70636
70828
  for (const w of listWalkthroughs()) {
70637
70829
  const cmd = `vibe walkthrough ${w.topic}`.padEnd(26);
70638
70830
  const tag = hostDetected && w.topic === "scene" ? source_default.cyan(" (recommended)") : "";
70639
70831
  console.log(source_default.dim(` ${cmd}${w.summary}${tag}`));
70640
70832
  }
70641
- console.log(source_default.dim(" vibe doctor --test-keys Live-check each provider key against its API"));
70833
+ console.log(
70834
+ source_default.dim(" vibe doctor --test-keys Live-check each provider key against its API")
70835
+ );
70642
70836
  console.log(source_default.dim(" vibe doctor Check system health + available commands"));
70643
70837
  console.log(source_default.dim(" vibe schema --list Discover every command"));
70644
70838
  console.log(source_default.dim(" vibe setup Re-run user-scope setup anytime"));
@@ -70773,7 +70967,11 @@ async function showConfig(opts = { verbose: false }) {
70773
70967
  }
70774
70968
  }
70775
70969
  if (!verbose && unsetCount > 0) {
70776
- console.log(source_default.dim(` (${unsetCount} provider${unsetCount === 1 ? "" : "s"} unset \u2014 run with --verbose to list)`));
70970
+ console.log(
70971
+ source_default.dim(
70972
+ ` (${unsetCount} provider${unsetCount === 1 ? "" : "s"} unset \u2014 run with --verbose to list)`
70973
+ )
70974
+ );
70777
70975
  }
70778
70976
  console.log();
70779
70977
  if (verbose) {
@@ -70785,8 +70983,12 @@ async function showConfig(opts = { verbose: false }) {
70785
70983
  }
70786
70984
  console.log(source_default.bold("Resolution order:"));
70787
70985
  console.log(source_default.dim(" 1. --api-key CLI option"));
70788
- console.log(source_default.dim(` 2. Active config scope: ${activeScope === "project" ? projectPath : userPath}`));
70789
- console.log(source_default.dim(` (project takes priority when present at cwd; user is ignored in that case)`));
70986
+ console.log(
70987
+ source_default.dim(` 2. Active config scope: ${activeScope === "project" ? projectPath : userPath}`)
70988
+ );
70989
+ console.log(
70990
+ source_default.dim(` (project takes priority when present at cwd; user is ignored in that case)`)
70991
+ );
70790
70992
  console.log(source_default.dim(" 3. .env / shell environment variables (process.env)"));
70791
70993
  console.log();
70792
70994
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.91.7",
3
+ "version": "0.91.8",
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.91.7",
187
- "@vibeframe/core": "0.91.7"
186
+ "@vibeframe/ai-providers": "0.91.8",
187
+ "@vibeframe/core": "0.91.8"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"