@vibeframe/cli 0.89.0 → 0.91.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 +220 -89
  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,
@@ -25690,32 +25703,81 @@ var init_schema = __esm({
25690
25703
  import { resolve as resolve4 } from "node:path";
25691
25704
  import { homedir } from "node:os";
25692
25705
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "node:fs/promises";
25693
- async function loadConfig() {
25706
+ function getProjectConfigDir(cwd = process.cwd()) {
25707
+ return resolve4(cwd, ".vibeframe");
25708
+ }
25709
+ function getProjectConfigPath(cwd = process.cwd()) {
25710
+ return resolve4(getProjectConfigDir(cwd), "config.yaml");
25711
+ }
25712
+ function getConfigPath(scope, cwd) {
25713
+ return scope === "project" ? getProjectConfigPath(cwd) : USER_CONFIG_PATH;
25714
+ }
25715
+ function getConfigDir(scope, cwd) {
25716
+ return scope === "project" ? getProjectConfigDir(cwd) : USER_CONFIG_DIR;
25717
+ }
25718
+ async function fileExists(path2) {
25694
25719
  try {
25695
- await access2(CONFIG_PATH);
25696
- const content = await readFile2(CONFIG_PATH, "utf-8");
25697
- const config2 = (0, import_yaml.parse)(content);
25698
- const defaults = createDefaultConfig();
25699
- return {
25700
- ...defaults,
25701
- ...config2,
25702
- llm: { ...defaults.llm, ...config2.llm },
25703
- providers: { ...defaults.providers, ...config2.providers },
25704
- defaults: { ...defaults.defaults, ...config2.defaults },
25705
- repl: { ...defaults.repl, ...config2.repl }
25706
- };
25720
+ await access2(path2);
25721
+ return true;
25722
+ } catch {
25723
+ return false;
25724
+ }
25725
+ }
25726
+ async function getActiveScope(cwd) {
25727
+ return await fileExists(getProjectConfigPath(cwd)) ? "project" : "user";
25728
+ }
25729
+ function applyDefaults(parsed) {
25730
+ const defaults = createDefaultConfig();
25731
+ return {
25732
+ ...defaults,
25733
+ ...parsed,
25734
+ llm: { ...defaults.llm, ...parsed.llm },
25735
+ providers: { ...defaults.providers, ...parsed.providers },
25736
+ defaults: { ...defaults.defaults, ...parsed.defaults },
25737
+ repl: { ...defaults.repl, ...parsed.repl }
25738
+ };
25739
+ }
25740
+ async function readConfigFile(path2) {
25741
+ if (!await fileExists(path2)) return null;
25742
+ try {
25743
+ const content = await readFile2(path2, "utf-8");
25744
+ const parsed = (0, import_yaml.parse)(content);
25745
+ return applyDefaults(parsed);
25707
25746
  } catch {
25708
25747
  return null;
25709
25748
  }
25710
25749
  }
25711
- async function saveConfig(config2) {
25712
- await mkdir2(CONFIG_DIR, { recursive: true });
25713
- const content = (0, import_yaml.stringify)(config2, {
25714
- indent: 2,
25715
- lineWidth: 0
25716
- // Don't wrap lines
25717
- });
25718
- await writeFile2(CONFIG_PATH, content, "utf-8");
25750
+ async function loadConfig(options = {}) {
25751
+ const { scope, cwd, merge } = options;
25752
+ if (merge) {
25753
+ const user = await readConfigFile(USER_CONFIG_PATH);
25754
+ const project2 = await readConfigFile(getProjectConfigPath(cwd));
25755
+ if (!user && !project2) return null;
25756
+ if (!user) return project2;
25757
+ if (!project2) return user;
25758
+ return {
25759
+ ...user,
25760
+ ...project2,
25761
+ llm: { ...user.llm, ...project2.llm },
25762
+ providers: { ...user.providers, ...project2.providers },
25763
+ defaults: { ...user.defaults, ...project2.defaults },
25764
+ repl: { ...user.repl, ...project2.repl }
25765
+ };
25766
+ }
25767
+ if (scope) {
25768
+ return readConfigFile(getConfigPath(scope, cwd));
25769
+ }
25770
+ const project = await readConfigFile(getProjectConfigPath(cwd));
25771
+ if (project) return project;
25772
+ return readConfigFile(USER_CONFIG_PATH);
25773
+ }
25774
+ async function saveConfig(config2, options = {}) {
25775
+ const scope = options.scope ?? "user";
25776
+ const dir = getConfigDir(scope, options.cwd);
25777
+ const path2 = getConfigPath(scope, options.cwd);
25778
+ await mkdir2(dir, { recursive: true });
25779
+ const content = (0, import_yaml.stringify)(config2, { indent: 2, lineWidth: 0 });
25780
+ await writeFile2(path2, content, "utf-8");
25719
25781
  }
25720
25782
  async function isConfigured() {
25721
25783
  const config2 = await loadConfig();
@@ -25737,20 +25799,19 @@ async function getApiKeyFromConfig(providerKey) {
25737
25799
  return config2.providers[providerKey];
25738
25800
  }
25739
25801
  const envVar = PROVIDER_ENV_VARS[providerKey];
25740
- if (envVar) {
25741
- return process.env[envVar];
25742
- }
25802
+ if (envVar) return process.env[envVar];
25743
25803
  return void 0;
25744
25804
  }
25745
- var import_yaml, CONFIG_DIR, CONFIG_PATH;
25805
+ var import_yaml, USER_CONFIG_DIR, USER_CONFIG_PATH, CONFIG_PATH;
25746
25806
  var init_config = __esm({
25747
25807
  "src/config/index.ts"() {
25748
25808
  "use strict";
25749
25809
  import_yaml = __toESM(require_dist(), 1);
25750
25810
  init_schema();
25751
25811
  init_schema();
25752
- CONFIG_DIR = resolve4(homedir(), ".vibeframe");
25753
- CONFIG_PATH = resolve4(CONFIG_DIR, "config.yaml");
25812
+ USER_CONFIG_DIR = resolve4(homedir(), ".vibeframe");
25813
+ USER_CONFIG_PATH = resolve4(USER_CONFIG_DIR, "config.yaml");
25814
+ CONFIG_PATH = USER_CONFIG_PATH;
25754
25815
  }
25755
25816
  });
25756
25817
 
@@ -26277,9 +26338,8 @@ vibe scene lint --json # structured output for agent loops
26277
26338
  `;
26278
26339
  }
26279
26340
  function buildSceneGitignore() {
26280
- return `# VibeFrame caches
26281
- .vibeframe/cache/
26282
- .vibeframe/checkpoints/
26341
+ return `# VibeFrame \u2014 caches, checkpoints, and project-scope config.yaml (may contain API keys)
26342
+ .vibeframe/
26283
26343
 
26284
26344
  # Render outputs
26285
26345
  renders/*.mp4
@@ -35437,9 +35497,9 @@ var init_ai_script_pipeline = __esm({
35437
35497
  import { resolve as resolve26 } from "node:path";
35438
35498
  import { readFile as readFile14, writeFile as writeFile24 } from "node:fs/promises";
35439
35499
  function registerVideoCommand(parent) {
35440
- 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(
35500
+ 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(
35441
35501
  "-d, --duration <sec>",
35442
- "Duration in seconds. Seedance accepts 4-15 (`fal` alias supported); Kling accepts 5 or 10; Veo maps to 6 or 8.",
35502
+ "Duration in seconds. Seedance accepts 4-15; Kling accepts 5 or 10; Veo maps to 6 or 8.",
35443
35503
  "5"
35444
35504
  ).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", `
35445
35505
  Examples:
@@ -35485,8 +35545,7 @@ Examples:
35485
35545
  veo: "GOOGLE_API_KEY",
35486
35546
  kling: "KLING_API_KEY",
35487
35547
  runway: "RUNWAY_API_SECRET",
35488
- seedance: "FAL_KEY",
35489
- fal: "FAL_KEY"
35548
+ seedance: "FAL_KEY"
35490
35549
  };
35491
35550
  let provider;
35492
35551
  if (options.provider) {
@@ -35495,9 +35554,17 @@ Examples:
35495
35554
  exitWithError(
35496
35555
  usageError(
35497
35556
  `Invalid provider: ${provider}`,
35498
- "Available providers: seedance, grok, kling, runway, veo. `fal` is a backwards-compatible alias for seedance."
35557
+ "Available providers: seedance, grok, kling, runway, veo. `fal` is a deprecated alias for seedance."
35558
+ )
35559
+ );
35560
+ }
35561
+ if (provider === "fal") {
35562
+ process.stderr.write(
35563
+ source_default.yellow(
35564
+ "Note: `-p fal` is a deprecated alias for `-p seedance` and will be removed in 1.0.\n"
35499
35565
  )
35500
35566
  );
35567
+ provider = "seedance";
35501
35568
  }
35502
35569
  if (videoEnvMap[provider] && !hasApiKey(videoEnvMap[provider]) && !options.apiKey) {
35503
35570
  const resolved = resolveProvider("video");
@@ -35558,7 +35625,7 @@ Examples:
35558
35625
  data: {
35559
35626
  params: {
35560
35627
  prompt: prompt3,
35561
- provider: provider === "fal" ? "seedance" : provider,
35628
+ provider,
35562
35629
  duration: options.duration,
35563
35630
  ratio: options.ratio,
35564
35631
  image: options.image,
@@ -35577,16 +35644,14 @@ Examples:
35577
35644
  kling: "KLING_API_KEY",
35578
35645
  veo: "GOOGLE_API_KEY",
35579
35646
  grok: "XAI_API_KEY",
35580
- seedance: "FAL_KEY",
35581
- fal: "FAL_KEY"
35647
+ seedance: "FAL_KEY"
35582
35648
  };
35583
35649
  const providerNameMap = {
35584
35650
  runway: "Runway",
35585
35651
  kling: "Kling",
35586
35652
  veo: "Veo",
35587
35653
  grok: "Grok",
35588
- seedance: "Seedance 2.0 via fal.ai",
35589
- fal: "Seedance 2.0 via fal.ai"
35654
+ seedance: "Seedance 2.0 via fal.ai"
35590
35655
  };
35591
35656
  const envKey = envKeyMap[provider];
35592
35657
  const providerName = providerNameMap[provider];
@@ -35805,7 +35870,7 @@ Examples:
35805
35870
  },
35806
35871
  3e5
35807
35872
  );
35808
- } else if (provider === "fal" || provider === "seedance") {
35873
+ } else if (provider === "seedance") {
35809
35874
  const fal = new FalProvider();
35810
35875
  await fal.initialize({ apiKey });
35811
35876
  let falImage = referenceImage;
@@ -62845,7 +62910,7 @@ init_source();
62845
62910
  // package.json
62846
62911
  var package_default = {
62847
62912
  name: "@vibeframe/cli",
62848
- version: "0.89.0",
62913
+ version: "0.91.0",
62849
62914
  description: "VibeFrame CLI - natural language video editing from the terminal",
62850
62915
  private: false,
62851
62916
  license: "MIT",
@@ -69840,13 +69905,34 @@ var VALID_LLM_PROVIDERS = [
69840
69905
  "xai",
69841
69906
  "openrouter"
69842
69907
  ];
69843
- 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)").addHelpText(
69908
+ var VALID_SCOPES = ["user", "project"];
69909
+ function parseScope(raw) {
69910
+ const v = String(raw ?? "user");
69911
+ if (!VALID_SCOPES.includes(v)) {
69912
+ exitWithError(generalError(
69913
+ `Invalid --scope: ${v}`,
69914
+ `Must be one of: ${VALID_SCOPES.join(", ")}`
69915
+ ));
69916
+ }
69917
+ return v;
69918
+ }
69919
+ 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(
69920
+ "--scope <scope>",
69921
+ "Where to save: 'user' (~/.vibeframe/config.yaml, shared) or 'project' (./.vibeframe/config.yaml, gitignored, this project only)",
69922
+ "user"
69923
+ ).addHelpText(
69844
69924
  "after",
69845
69925
  `
69846
69926
  Non-interactive examples (no TTY required):
69847
69927
  vibe setup --yes --provider claude --import-env Bootstrap CI / devcontainer
69848
69928
  vibe setup --yes --import-env --test Persist .env keys + verify each one
69849
69929
  vibe setup --yes --provider openai Just switch the agent provider
69930
+ vibe setup --scope project --yes --import-env Project-only setup (./.vibeframe/config.yaml)
69931
+
69932
+ Scopes:
69933
+ user (default) ~/.vibeframe/config.yaml \u2014 shared across every project
69934
+ project ./.vibeframe/config.yaml \u2014 gitignored, isolates keys/defaults to this directory
69935
+ When a project config exists at the cwd, it is used and the user file is ignored.
69850
69936
 
69851
69937
  Exit codes (non-interactive):
69852
69938
  0 success 7 --test verification failed (one or more keys returned non-2xx)
@@ -69861,11 +69947,12 @@ Exit codes (non-interactive):
69861
69947
  await showConfig({ verbose: Boolean(options.verbose) });
69862
69948
  return;
69863
69949
  }
69950
+ const scope = parseScope(options.scope);
69864
69951
  if (options.reset) {
69865
69952
  const config2 = createDefaultConfig();
69866
- await saveConfig(config2);
69953
+ await saveConfig(config2, { scope });
69867
69954
  console.log(source_default.green("\u2713 Configuration reset to defaults"));
69868
- console.log(source_default.dim(` Saved to: ${CONFIG_PATH}`));
69955
+ console.log(source_default.dim(` Saved to: ${getConfigPath(scope)}`));
69869
69956
  return;
69870
69957
  }
69871
69958
  const wantsNonInteractive = Boolean(options.yes) || !hasTTY() && (options.provider || options.importEnv);
@@ -69873,7 +69960,8 @@ Exit codes (non-interactive):
69873
69960
  await runNonInteractiveSetup({
69874
69961
  provider: options.provider,
69875
69962
  importEnv: Boolean(options.importEnv),
69876
- test: Boolean(options.test)
69963
+ test: Boolean(options.test),
69964
+ scope
69877
69965
  });
69878
69966
  return;
69879
69967
  }
@@ -69884,7 +69972,7 @@ Exit codes (non-interactive):
69884
69972
  ));
69885
69973
  }
69886
69974
  try {
69887
- await runSetupWizard(options.full);
69975
+ await runSetupWizard(options.full, scope);
69888
69976
  closeTTYStream();
69889
69977
  process.exit(0);
69890
69978
  } catch (err) {
@@ -69906,7 +69994,7 @@ var AI_FEATURES = [
69906
69994
  desc: "generate + extend",
69907
69995
  defaultProvider: "Seedance 2.0 via fal.ai (Artificial Analysis #2 t2v + i2v, since v0.57)",
69908
69996
  alsoAvailable: "Grok Imagine, Kling, Runway Gen-4.5, Google Veo",
69909
- 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" }],
69997
+ 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" }],
69910
69998
  tryCommand: 'vibe generate video "ocean waves" -o waves.mp4'
69911
69999
  },
69912
70000
  {
@@ -69930,7 +70018,8 @@ var AI_FEATURES = [
69930
70018
  }
69931
70019
  ];
69932
70020
  async function runNonInteractiveSetup(opts) {
69933
- let config2 = await loadConfig();
70021
+ const scope = opts.scope ?? "user";
70022
+ let config2 = await loadConfig({ scope });
69934
70023
  if (!config2) {
69935
70024
  config2 = createDefaultConfig();
69936
70025
  }
@@ -69967,9 +70056,9 @@ async function runNonInteractiveSetup(opts) {
69967
70056
  changes.push(`imported ${importedCount} key${importedCount === 1 ? "" : "s"} from env`);
69968
70057
  }
69969
70058
  }
69970
- await saveConfig(config2);
69971
- console.log(source_default.green("\u2713 Setup complete (non-interactive)"));
69972
- console.log(source_default.dim(` Config: ${CONFIG_PATH}`));
70059
+ await saveConfig(config2, { scope });
70060
+ console.log(source_default.green(`\u2713 Setup complete (non-interactive, ${scope} scope)`));
70061
+ console.log(source_default.dim(` Config: ${getConfigPath(scope)}`));
69973
70062
  if (changes.length > 0) {
69974
70063
  for (const c of changes) {
69975
70064
  console.log(source_default.dim(` ${c}`));
@@ -70012,20 +70101,21 @@ async function runNonInteractiveSetup(opts) {
70012
70101
  }
70013
70102
  }
70014
70103
  }
70015
- async function runSetupWizard(fullSetup = false) {
70104
+ async function runSetupWizard(fullSetup = false, scope = "user") {
70016
70105
  console.log();
70017
- console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.dim(" \u2014 user scope"));
70106
+ const scopeLabel = scope === "project" ? source_default.dim(` \u2014 project scope (${getConfigPath("project")})`) : source_default.dim(" \u2014 user scope");
70107
+ console.log(source_default.bold.magenta("VibeFrame Setup") + scopeLabel);
70018
70108
  console.log(source_default.dim("\u2500".repeat(40)));
70019
70109
  console.log();
70020
70110
  const hosts = detectedAgentHosts();
70021
70111
  console.log(source_default.dim(`Agent hosts: ${summariseAgentHosts(hosts)}`));
70022
70112
  console.log();
70023
- let config2 = await loadConfig();
70113
+ let config2 = await loadConfig({ scope });
70024
70114
  if (!config2) {
70025
70115
  config2 = createDefaultConfig();
70026
70116
  }
70027
70117
  if (fullSetup) {
70028
- await runCustomSetup(config2);
70118
+ await runCustomSetup(config2, scope);
70029
70119
  return;
70030
70120
  }
70031
70121
  console.log(source_default.dim("1. Goal"));
@@ -70040,12 +70130,12 @@ async function runSetupWizard(fullSetup = false) {
70040
70130
  const topIndex = await promptSelect(source_default.cyan(" Select [1-4]: "), topLabels, 0);
70041
70131
  console.log();
70042
70132
  if (topIndex === 0) {
70043
- await saveConfig(config2);
70044
- await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4");
70133
+ await saveConfig(config2, { scope });
70134
+ await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4", [], scope);
70045
70135
  return;
70046
70136
  }
70047
70137
  if (topIndex === 3) {
70048
- await runCustomSetup(config2);
70138
+ await runCustomSetup(config2, scope);
70049
70139
  return;
70050
70140
  }
70051
70141
  if (topIndex === 2) {
@@ -70061,8 +70151,8 @@ async function runSetupWizard(fullSetup = false) {
70061
70151
  console.log(source_default.dim(" Saved locally, never shared. Press Enter to skip."));
70062
70152
  console.log();
70063
70153
  await collectKeys(config2, pipelineKeys);
70064
- await saveConfig(config2);
70065
- await showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
70154
+ await saveConfig(config2, { scope });
70155
+ await showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format", [], scope);
70066
70156
  return;
70067
70157
  }
70068
70158
  console.log(source_default.dim("2. Features"));
@@ -70083,8 +70173,8 @@ async function runSetupWizard(fullSetup = false) {
70083
70173
  if (selectedFeatures.length === 0) {
70084
70174
  console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
70085
70175
  console.log();
70086
- await saveConfig(config2);
70087
- await showComplete(config2, "vibe --help", []);
70176
+ await saveConfig(config2, { scope });
70177
+ await showComplete(config2, "vibe --help", [], scope);
70088
70178
  return;
70089
70179
  }
70090
70180
  console.log(source_default.dim("3. API keys"));
@@ -70136,8 +70226,8 @@ async function runSetupWizard(fullSetup = false) {
70136
70226
  }
70137
70227
  console.log();
70138
70228
  }
70139
- await saveConfig(config2);
70140
- await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures);
70229
+ await saveConfig(config2, { scope });
70230
+ await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures, scope);
70141
70231
  }
70142
70232
  async function collectKeys(config2, keys) {
70143
70233
  loadEnv();
@@ -70173,7 +70263,7 @@ async function collectKeys(config2, keys) {
70173
70263
  }
70174
70264
  console.log();
70175
70265
  }
70176
- async function runCustomSetup(config2) {
70266
+ async function runCustomSetup(config2, scope = "user") {
70177
70267
  console.log(source_default.bold("1. Agent LLM Provider") + source_default.dim(" (for vibe agent)"));
70178
70268
  console.log(source_default.dim(" Only needed if you use the interactive Agent mode."));
70179
70269
  console.log();
@@ -70243,14 +70333,15 @@ async function runCustomSetup(config2) {
70243
70333
  }
70244
70334
  }
70245
70335
  console.log();
70246
- await saveConfig(config2);
70247
- await showComplete(config2, "vibe doctor");
70336
+ await saveConfig(config2, { scope });
70337
+ await showComplete(config2, "vibe doctor", [], scope);
70248
70338
  }
70249
- async function showComplete(config2, defaultTryCommand, features = []) {
70339
+ async function showComplete(config2, defaultTryCommand, features = [], scope = "user") {
70250
70340
  console.log(source_default.dim("\u2500".repeat(40)));
70251
70341
  console.log(source_default.green.bold("\u2713 Setup complete!"));
70252
70342
  console.log();
70253
- console.log(source_default.dim(` Config: ${CONFIG_PATH}`));
70343
+ const scopeNote = scope === "project" ? source_default.dim(" (project scope)") : source_default.dim(" (user scope)");
70344
+ console.log(source_default.dim(` Config: ${getConfigPath(scope)}${scopeNote}`));
70254
70345
  console.log();
70255
70346
  const primaryTry = features.length > 1 ? features[0].tryCommand : defaultTryCommand;
70256
70347
  if (features.length > 1) {
@@ -70340,13 +70431,22 @@ async function showConfig(opts = { verbose: false }) {
70340
70431
  hasCwdEnv = true;
70341
70432
  } catch {
70342
70433
  }
70434
+ const activeScope = await getActiveScope();
70435
+ const userPath = USER_CONFIG_PATH;
70436
+ const projectPath = getConfigPath("project");
70437
+ const userExists = await loadConfig({ scope: "user" }) !== null;
70438
+ const projectExists = await loadConfig({ scope: "project" }) !== null;
70343
70439
  console.log();
70344
70440
  console.log(source_default.bold.magenta("VibeFrame Configuration"));
70345
70441
  console.log(source_default.dim("\u2500".repeat(40)));
70346
70442
  console.log();
70347
- console.log(source_default.dim(`Config file: ${CONFIG_PATH}`));
70443
+ const userMark = activeScope === "user" && userExists ? source_default.cyan("\u2190 active") : userExists ? source_default.dim("(present)") : source_default.dim("(missing)");
70444
+ const projectMark = activeScope === "project" ? source_default.cyan("\u2190 active") : projectExists ? source_default.dim("(present)") : source_default.dim("(missing)");
70445
+ console.log(source_default.dim(`Scopes:`));
70446
+ console.log(source_default.dim(` user ${userPath} ${userMark}`));
70447
+ console.log(source_default.dim(` project ${projectPath} ${projectMark}`));
70348
70448
  if (hasCwdEnv) {
70349
- console.log(source_default.dim(`Project .env: ${cwdEnvPath}`));
70449
+ console.log(source_default.dim(` .env ${cwdEnvPath} ${source_default.dim("(env vars override config)")}`));
70350
70450
  }
70351
70451
  console.log();
70352
70452
  const config2 = await loadConfig();
@@ -70414,9 +70514,9 @@ async function showConfig(opts = { verbose: false }) {
70414
70514
  }
70415
70515
  console.log(source_default.bold("Resolution order:"));
70416
70516
  console.log(source_default.dim(" 1. --api-key CLI option"));
70417
- console.log(source_default.dim(` 2. ${CONFIG_PATH}`));
70418
- console.log(source_default.dim(" 3. .env in current directory"));
70419
- console.log(source_default.dim(" 4. Shell environment variables"));
70517
+ console.log(source_default.dim(` 2. Active config scope: ${activeScope === "project" ? projectPath : userPath}`));
70518
+ console.log(source_default.dim(` (project takes priority when present at cwd; user is ignored in that case)`));
70519
+ console.log(source_default.dim(" 3. .env / shell environment variables (process.env)"));
70420
70520
  console.log();
70421
70521
  }
70422
70522
  }
@@ -71551,6 +71651,9 @@ async function runDiagnostics() {
71551
71651
  exists: existsSync36(resolve50(cwd, rel))
71552
71652
  }));
71553
71653
  const projectInitialized = projectFiles.some((f) => f.exists);
71654
+ const projectConfigPath = getProjectConfigPath(cwd);
71655
+ const projectConfigExists = existsSync36(projectConfigPath);
71656
+ const activeScope = await getActiveScope(cwd);
71554
71657
  const hosts = detectAgentHosts();
71555
71658
  const detectedNames = hosts.filter((h) => h.detected).map((h) => h.label);
71556
71659
  const recommendedMode = resolveSceneBuildMode({});
@@ -71573,8 +71676,15 @@ async function runDiagnostics() {
71573
71676
  ...Object.keys(optionalTools).length > 0 ? { optionalTools } : {}
71574
71677
  },
71575
71678
  scope: {
71679
+ activeScope,
71576
71680
  user: { configured: configExists, configPath: CONFIG_PATH },
71577
- project: { cwd, initialized: projectInitialized, files: projectFiles },
71681
+ project: {
71682
+ cwd,
71683
+ initialized: projectInitialized,
71684
+ files: projectFiles,
71685
+ configPath: projectConfigPath,
71686
+ configFileExists: projectConfigExists
71687
+ },
71578
71688
  agentHosts: { detected: detectedNames, summary: summariseAgentHosts(hosts) },
71579
71689
  sceneComposer: {
71580
71690
  recommendedMode,
@@ -71647,16 +71757,30 @@ function printReport(results, opts = { verbose: false }) {
71647
71757
  }
71648
71758
  console.log();
71649
71759
  console.log(source_default.bold(" Scope"));
71760
+ const userActive = results.scope.activeScope === "user" && results.scope.user.configured;
71761
+ const projectActive = results.scope.activeScope === "project";
71762
+ const activeMark = source_default.cyan(" \u2190 active");
71650
71763
  if (results.scope.user.configured) {
71651
- console.log(` User ${source_default.green("OK")} ${source_default.dim(results.scope.user.configPath)}`);
71764
+ console.log(
71765
+ ` Cfg(user) ${source_default.green("OK")} ${source_default.dim(results.scope.user.configPath)}${userActive ? activeMark : ""}`
71766
+ );
71652
71767
  } else {
71653
- console.log(` User ${source_default.yellow("NOT SET")} ${source_default.dim("Run: vibe setup")}`);
71768
+ console.log(` Cfg(user) ${source_default.yellow("NOT SET")} ${source_default.dim("Run: vibe setup")}`);
71769
+ }
71770
+ if (results.scope.project.configFileExists) {
71771
+ console.log(
71772
+ ` Cfg(proj) ${source_default.green("OK")} ${source_default.dim(results.scope.project.configPath)}${projectActive ? activeMark : ""}`
71773
+ );
71774
+ } else {
71775
+ console.log(
71776
+ ` Cfg(proj) ${source_default.dim("none")} ${source_default.dim(`Run: vibe setup --scope project (cwd: ${results.scope.project.cwd})`)}`
71777
+ );
71654
71778
  }
71655
71779
  if (results.scope.project.initialized) {
71656
71780
  const present = results.scope.project.files.filter((f) => f.exists).map((f) => f.path);
71657
- console.log(` Project ${source_default.green("OK")} ${source_default.dim(present.join(", "))}`);
71781
+ console.log(` Init ${source_default.green("OK")} ${source_default.dim(present.join(", "))}`);
71658
71782
  } else {
71659
- console.log(` Project ${source_default.yellow("NOT INIT")} ${source_default.dim(`Run: vibe init (cwd: ${results.scope.project.cwd})`)}`);
71783
+ console.log(` Init ${source_default.yellow("NOT INIT")} ${source_default.dim(`Run: vibe init (cwd: ${results.scope.project.cwd})`)}`);
71660
71784
  }
71661
71785
  console.log(` Agents ${source_default.dim(results.scope.agentHosts.summary)}`);
71662
71786
  if (verbose) {
@@ -71689,11 +71813,16 @@ function printReport(results, opts = { verbose: false }) {
71689
71813
  console.log(source_default.bold(" API Keys"));
71690
71814
  const configured = [];
71691
71815
  const missing = [];
71816
+ const labelFor = (configKey, fallbackEnv) => {
71817
+ const label = getDisplayLabelForApiKey(configKey);
71818
+ return label === configKey ? fallbackEnv : label;
71819
+ };
71692
71820
  for (const [name, info] of Object.entries(results.providers)) {
71693
71821
  if (info.configured) {
71694
71822
  configured.push(name);
71823
+ const label = labelFor(name, info.envVar);
71695
71824
  console.log(
71696
- ` ${source_default.green("OK")} ${info.envVar.padEnd(24)} ${source_default.dim(info.commands.join(", "))}`
71825
+ ` ${source_default.green("OK")} ${label.padEnd(28)} ${source_default.dim(info.envVar.padEnd(20))} ${source_default.dim(info.commands.join(", "))}`
71697
71826
  );
71698
71827
  } else {
71699
71828
  missing.push(name);
@@ -71703,8 +71832,9 @@ function printReport(results, opts = { verbose: false }) {
71703
71832
  if (verbose) {
71704
71833
  for (const name of missing) {
71705
71834
  const info = results.providers[name];
71835
+ const label = labelFor(name, info.envVar);
71706
71836
  console.log(
71707
- ` ${source_default.red("--")} ${info.envVar.padEnd(24)} ${source_default.dim(info.commands.join(", "))}`
71837
+ ` ${source_default.red("--")} ${label.padEnd(28)} ${source_default.dim(info.envVar.padEnd(20))} ${source_default.dim(info.commands.join(", "))}`
71708
71838
  );
71709
71839
  }
71710
71840
  } else {
@@ -71812,8 +71942,9 @@ async function runLiveKeyTests(results) {
71812
71942
  console.log();
71813
71943
  }
71814
71944
  function pickNextStep(results, hasMissingProviders) {
71815
- if (!results.scope.user.configured) {
71816
- return "Next: run 'vibe setup' to configure your user scope (API keys + LLM provider).";
71945
+ const anyConfigured = results.scope.user.configured || results.scope.project.configFileExists;
71946
+ if (!anyConfigured) {
71947
+ return "Next: run 'vibe setup' (or 'vibe setup --scope project' to keep config inside the project) to configure API keys + LLM provider.";
71817
71948
  }
71818
71949
  if (!results.scope.project.initialized) {
71819
71950
  return "Next: run 'vibe init' in your project directory to scaffold AGENTS.md / CLAUDE.md / .env.example.";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.89.0",
3
+ "version": "0.91.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.89.0",
187
- "@vibeframe/core": "0.89.0"
186
+ "@vibeframe/ai-providers": "0.91.0",
187
+ "@vibeframe/core": "0.91.0"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"