@vibeframe/cli 0.88.0 → 0.90.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 +354 -70
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -25690,32 +25690,81 @@ var init_schema = __esm({
25690
25690
  import { resolve as resolve4 } from "node:path";
25691
25691
  import { homedir } from "node:os";
25692
25692
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, access as access2 } from "node:fs/promises";
25693
- async function loadConfig() {
25693
+ function getProjectConfigDir(cwd = process.cwd()) {
25694
+ return resolve4(cwd, ".vibeframe");
25695
+ }
25696
+ function getProjectConfigPath(cwd = process.cwd()) {
25697
+ return resolve4(getProjectConfigDir(cwd), "config.yaml");
25698
+ }
25699
+ function getConfigPath(scope, cwd) {
25700
+ return scope === "project" ? getProjectConfigPath(cwd) : USER_CONFIG_PATH;
25701
+ }
25702
+ function getConfigDir(scope, cwd) {
25703
+ return scope === "project" ? getProjectConfigDir(cwd) : USER_CONFIG_DIR;
25704
+ }
25705
+ async function fileExists(path2) {
25694
25706
  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
- };
25707
+ await access2(path2);
25708
+ return true;
25709
+ } catch {
25710
+ return false;
25711
+ }
25712
+ }
25713
+ async function getActiveScope(cwd) {
25714
+ return await fileExists(getProjectConfigPath(cwd)) ? "project" : "user";
25715
+ }
25716
+ function applyDefaults(parsed) {
25717
+ const defaults = createDefaultConfig();
25718
+ return {
25719
+ ...defaults,
25720
+ ...parsed,
25721
+ llm: { ...defaults.llm, ...parsed.llm },
25722
+ providers: { ...defaults.providers, ...parsed.providers },
25723
+ defaults: { ...defaults.defaults, ...parsed.defaults },
25724
+ repl: { ...defaults.repl, ...parsed.repl }
25725
+ };
25726
+ }
25727
+ async function readConfigFile(path2) {
25728
+ if (!await fileExists(path2)) return null;
25729
+ try {
25730
+ const content = await readFile2(path2, "utf-8");
25731
+ const parsed = (0, import_yaml.parse)(content);
25732
+ return applyDefaults(parsed);
25707
25733
  } catch {
25708
25734
  return null;
25709
25735
  }
25710
25736
  }
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");
25737
+ async function loadConfig(options = {}) {
25738
+ const { scope, cwd, merge } = options;
25739
+ if (merge) {
25740
+ const user = await readConfigFile(USER_CONFIG_PATH);
25741
+ const project2 = await readConfigFile(getProjectConfigPath(cwd));
25742
+ if (!user && !project2) return null;
25743
+ if (!user) return project2;
25744
+ if (!project2) return user;
25745
+ return {
25746
+ ...user,
25747
+ ...project2,
25748
+ llm: { ...user.llm, ...project2.llm },
25749
+ providers: { ...user.providers, ...project2.providers },
25750
+ defaults: { ...user.defaults, ...project2.defaults },
25751
+ repl: { ...user.repl, ...project2.repl }
25752
+ };
25753
+ }
25754
+ if (scope) {
25755
+ return readConfigFile(getConfigPath(scope, cwd));
25756
+ }
25757
+ const project = await readConfigFile(getProjectConfigPath(cwd));
25758
+ if (project) return project;
25759
+ return readConfigFile(USER_CONFIG_PATH);
25760
+ }
25761
+ async function saveConfig(config2, options = {}) {
25762
+ const scope = options.scope ?? "user";
25763
+ const dir = getConfigDir(scope, options.cwd);
25764
+ const path2 = getConfigPath(scope, options.cwd);
25765
+ await mkdir2(dir, { recursive: true });
25766
+ const content = (0, import_yaml.stringify)(config2, { indent: 2, lineWidth: 0 });
25767
+ await writeFile2(path2, content, "utf-8");
25719
25768
  }
25720
25769
  async function isConfigured() {
25721
25770
  const config2 = await loadConfig();
@@ -25737,20 +25786,19 @@ async function getApiKeyFromConfig(providerKey) {
25737
25786
  return config2.providers[providerKey];
25738
25787
  }
25739
25788
  const envVar = PROVIDER_ENV_VARS[providerKey];
25740
- if (envVar) {
25741
- return process.env[envVar];
25742
- }
25789
+ if (envVar) return process.env[envVar];
25743
25790
  return void 0;
25744
25791
  }
25745
- var import_yaml, CONFIG_DIR, CONFIG_PATH;
25792
+ var import_yaml, USER_CONFIG_DIR, USER_CONFIG_PATH, CONFIG_PATH;
25746
25793
  var init_config = __esm({
25747
25794
  "src/config/index.ts"() {
25748
25795
  "use strict";
25749
25796
  import_yaml = __toESM(require_dist(), 1);
25750
25797
  init_schema();
25751
25798
  init_schema();
25752
- CONFIG_DIR = resolve4(homedir(), ".vibeframe");
25753
- CONFIG_PATH = resolve4(CONFIG_DIR, "config.yaml");
25799
+ USER_CONFIG_DIR = resolve4(homedir(), ".vibeframe");
25800
+ USER_CONFIG_PATH = resolve4(USER_CONFIG_DIR, "config.yaml");
25801
+ CONFIG_PATH = USER_CONFIG_PATH;
25754
25802
  }
25755
25803
  });
25756
25804
 
@@ -26277,9 +26325,8 @@ vibe scene lint --json # structured output for agent loops
26277
26325
  `;
26278
26326
  }
26279
26327
  function buildSceneGitignore() {
26280
- return `# VibeFrame caches
26281
- .vibeframe/cache/
26282
- .vibeframe/checkpoints/
26328
+ return `# VibeFrame \u2014 caches, checkpoints, and project-scope config.yaml (may contain API keys)
26329
+ .vibeframe/
26283
26330
 
26284
26331
  # Render outputs
26285
26332
  renders/*.mp4
@@ -62845,7 +62892,7 @@ init_source();
62845
62892
  // package.json
62846
62893
  var package_default = {
62847
62894
  name: "@vibeframe/cli",
62848
- version: "0.88.0",
62895
+ version: "0.90.0",
62849
62896
  description: "VibeFrame CLI - natural language video editing from the terminal",
62850
62897
  private: false,
62851
62898
  license: "MIT",
@@ -69840,13 +69887,34 @@ var VALID_LLM_PROVIDERS = [
69840
69887
  "xai",
69841
69888
  "openrouter"
69842
69889
  ];
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(
69890
+ var VALID_SCOPES = ["user", "project"];
69891
+ function parseScope(raw) {
69892
+ const v = String(raw ?? "user");
69893
+ if (!VALID_SCOPES.includes(v)) {
69894
+ exitWithError(generalError(
69895
+ `Invalid --scope: ${v}`,
69896
+ `Must be one of: ${VALID_SCOPES.join(", ")}`
69897
+ ));
69898
+ }
69899
+ return v;
69900
+ }
69901
+ 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(
69902
+ "--scope <scope>",
69903
+ "Where to save: 'user' (~/.vibeframe/config.yaml, shared) or 'project' (./.vibeframe/config.yaml, gitignored, this project only)",
69904
+ "user"
69905
+ ).addHelpText(
69844
69906
  "after",
69845
69907
  `
69846
69908
  Non-interactive examples (no TTY required):
69847
69909
  vibe setup --yes --provider claude --import-env Bootstrap CI / devcontainer
69848
69910
  vibe setup --yes --import-env --test Persist .env keys + verify each one
69849
69911
  vibe setup --yes --provider openai Just switch the agent provider
69912
+ vibe setup --scope project --yes --import-env Project-only setup (./.vibeframe/config.yaml)
69913
+
69914
+ Scopes:
69915
+ user (default) ~/.vibeframe/config.yaml \u2014 shared across every project
69916
+ project ./.vibeframe/config.yaml \u2014 gitignored, isolates keys/defaults to this directory
69917
+ When a project config exists at the cwd, it is used and the user file is ignored.
69850
69918
 
69851
69919
  Exit codes (non-interactive):
69852
69920
  0 success 7 --test verification failed (one or more keys returned non-2xx)
@@ -69861,11 +69929,12 @@ Exit codes (non-interactive):
69861
69929
  await showConfig({ verbose: Boolean(options.verbose) });
69862
69930
  return;
69863
69931
  }
69932
+ const scope = parseScope(options.scope);
69864
69933
  if (options.reset) {
69865
69934
  const config2 = createDefaultConfig();
69866
- await saveConfig(config2);
69935
+ await saveConfig(config2, { scope });
69867
69936
  console.log(source_default.green("\u2713 Configuration reset to defaults"));
69868
- console.log(source_default.dim(` Saved to: ${CONFIG_PATH}`));
69937
+ console.log(source_default.dim(` Saved to: ${getConfigPath(scope)}`));
69869
69938
  return;
69870
69939
  }
69871
69940
  const wantsNonInteractive = Boolean(options.yes) || !hasTTY() && (options.provider || options.importEnv);
@@ -69873,7 +69942,8 @@ Exit codes (non-interactive):
69873
69942
  await runNonInteractiveSetup({
69874
69943
  provider: options.provider,
69875
69944
  importEnv: Boolean(options.importEnv),
69876
- test: Boolean(options.test)
69945
+ test: Boolean(options.test),
69946
+ scope
69877
69947
  });
69878
69948
  return;
69879
69949
  }
@@ -69884,7 +69954,7 @@ Exit codes (non-interactive):
69884
69954
  ));
69885
69955
  }
69886
69956
  try {
69887
- await runSetupWizard(options.full);
69957
+ await runSetupWizard(options.full, scope);
69888
69958
  closeTTYStream();
69889
69959
  process.exit(0);
69890
69960
  } catch (err) {
@@ -69930,7 +70000,8 @@ var AI_FEATURES = [
69930
70000
  }
69931
70001
  ];
69932
70002
  async function runNonInteractiveSetup(opts) {
69933
- let config2 = await loadConfig();
70003
+ const scope = opts.scope ?? "user";
70004
+ let config2 = await loadConfig({ scope });
69934
70005
  if (!config2) {
69935
70006
  config2 = createDefaultConfig();
69936
70007
  }
@@ -69967,9 +70038,9 @@ async function runNonInteractiveSetup(opts) {
69967
70038
  changes.push(`imported ${importedCount} key${importedCount === 1 ? "" : "s"} from env`);
69968
70039
  }
69969
70040
  }
69970
- await saveConfig(config2);
69971
- console.log(source_default.green("\u2713 Setup complete (non-interactive)"));
69972
- console.log(source_default.dim(` Config: ${CONFIG_PATH}`));
70041
+ await saveConfig(config2, { scope });
70042
+ console.log(source_default.green(`\u2713 Setup complete (non-interactive, ${scope} scope)`));
70043
+ console.log(source_default.dim(` Config: ${getConfigPath(scope)}`));
69973
70044
  if (changes.length > 0) {
69974
70045
  for (const c of changes) {
69975
70046
  console.log(source_default.dim(` ${c}`));
@@ -70012,20 +70083,21 @@ async function runNonInteractiveSetup(opts) {
70012
70083
  }
70013
70084
  }
70014
70085
  }
70015
- async function runSetupWizard(fullSetup = false) {
70086
+ async function runSetupWizard(fullSetup = false, scope = "user") {
70016
70087
  console.log();
70017
- console.log(source_default.bold.magenta("VibeFrame Setup") + source_default.dim(" \u2014 user scope"));
70088
+ const scopeLabel = scope === "project" ? source_default.dim(` \u2014 project scope (${getConfigPath("project")})`) : source_default.dim(" \u2014 user scope");
70089
+ console.log(source_default.bold.magenta("VibeFrame Setup") + scopeLabel);
70018
70090
  console.log(source_default.dim("\u2500".repeat(40)));
70019
70091
  console.log();
70020
70092
  const hosts = detectedAgentHosts();
70021
70093
  console.log(source_default.dim(`Agent hosts: ${summariseAgentHosts(hosts)}`));
70022
70094
  console.log();
70023
- let config2 = await loadConfig();
70095
+ let config2 = await loadConfig({ scope });
70024
70096
  if (!config2) {
70025
70097
  config2 = createDefaultConfig();
70026
70098
  }
70027
70099
  if (fullSetup) {
70028
- await runCustomSetup(config2);
70100
+ await runCustomSetup(config2, scope);
70029
70101
  return;
70030
70102
  }
70031
70103
  console.log(source_default.dim("1. Goal"));
@@ -70040,12 +70112,12 @@ async function runSetupWizard(fullSetup = false) {
70040
70112
  const topIndex = await promptSelect(source_default.cyan(" Select [1-4]: "), topLabels, 0);
70041
70113
  console.log();
70042
70114
  if (topIndex === 0) {
70043
- await saveConfig(config2);
70044
- await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4");
70115
+ await saveConfig(config2, { scope });
70116
+ await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4", [], scope);
70045
70117
  return;
70046
70118
  }
70047
70119
  if (topIndex === 3) {
70048
- await runCustomSetup(config2);
70120
+ await runCustomSetup(config2, scope);
70049
70121
  return;
70050
70122
  }
70051
70123
  if (topIndex === 2) {
@@ -70061,8 +70133,8 @@ async function runSetupWizard(fullSetup = false) {
70061
70133
  console.log(source_default.dim(" Saved locally, never shared. Press Enter to skip."));
70062
70134
  console.log();
70063
70135
  await collectKeys(config2, pipelineKeys);
70064
- await saveConfig(config2);
70065
- await showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format");
70136
+ await saveConfig(config2, { scope });
70137
+ await showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format", [], scope);
70066
70138
  return;
70067
70139
  }
70068
70140
  console.log(source_default.dim("2. Features"));
@@ -70083,8 +70155,8 @@ async function runSetupWizard(fullSetup = false) {
70083
70155
  if (selectedFeatures.length === 0) {
70084
70156
  console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
70085
70157
  console.log();
70086
- await saveConfig(config2);
70087
- await showComplete(config2, "vibe --help", []);
70158
+ await saveConfig(config2, { scope });
70159
+ await showComplete(config2, "vibe --help", [], scope);
70088
70160
  return;
70089
70161
  }
70090
70162
  console.log(source_default.dim("3. API keys"));
@@ -70136,8 +70208,8 @@ async function runSetupWizard(fullSetup = false) {
70136
70208
  }
70137
70209
  console.log();
70138
70210
  }
70139
- await saveConfig(config2);
70140
- await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures);
70211
+ await saveConfig(config2, { scope });
70212
+ await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures, scope);
70141
70213
  }
70142
70214
  async function collectKeys(config2, keys) {
70143
70215
  loadEnv();
@@ -70173,7 +70245,7 @@ async function collectKeys(config2, keys) {
70173
70245
  }
70174
70246
  console.log();
70175
70247
  }
70176
- async function runCustomSetup(config2) {
70248
+ async function runCustomSetup(config2, scope = "user") {
70177
70249
  console.log(source_default.bold("1. Agent LLM Provider") + source_default.dim(" (for vibe agent)"));
70178
70250
  console.log(source_default.dim(" Only needed if you use the interactive Agent mode."));
70179
70251
  console.log();
@@ -70243,14 +70315,15 @@ async function runCustomSetup(config2) {
70243
70315
  }
70244
70316
  }
70245
70317
  console.log();
70246
- await saveConfig(config2);
70247
- await showComplete(config2, "vibe doctor");
70318
+ await saveConfig(config2, { scope });
70319
+ await showComplete(config2, "vibe doctor", [], scope);
70248
70320
  }
70249
- async function showComplete(config2, defaultTryCommand, features = []) {
70321
+ async function showComplete(config2, defaultTryCommand, features = [], scope = "user") {
70250
70322
  console.log(source_default.dim("\u2500".repeat(40)));
70251
70323
  console.log(source_default.green.bold("\u2713 Setup complete!"));
70252
70324
  console.log();
70253
- console.log(source_default.dim(` Config: ${CONFIG_PATH}`));
70325
+ const scopeNote = scope === "project" ? source_default.dim(" (project scope)") : source_default.dim(" (user scope)");
70326
+ console.log(source_default.dim(` Config: ${getConfigPath(scope)}${scopeNote}`));
70254
70327
  console.log();
70255
70328
  const primaryTry = features.length > 1 ? features[0].tryCommand : defaultTryCommand;
70256
70329
  if (features.length > 1) {
@@ -70280,6 +70353,12 @@ async function showComplete(config2, defaultTryCommand, features = []) {
70280
70353
  console.log(source_default.dim(" vibe doctor Check system health + available commands"));
70281
70354
  console.log(source_default.dim(" vibe schema --list Discover every command"));
70282
70355
  console.log(source_default.dim(" vibe setup Re-run user-scope setup anytime"));
70356
+ const shellHint = pickCompletionHint(process.env.SHELL);
70357
+ if (shellHint) {
70358
+ console.log();
70359
+ console.log(source_default.bold(" Tab completion:"));
70360
+ console.log(source_default.dim(` ${shellHint}`));
70361
+ }
70283
70362
  const hosts = detectedAgentHosts();
70284
70363
  const primary = hosts[0];
70285
70364
  if (primary) {
@@ -70334,13 +70413,22 @@ async function showConfig(opts = { verbose: false }) {
70334
70413
  hasCwdEnv = true;
70335
70414
  } catch {
70336
70415
  }
70416
+ const activeScope = await getActiveScope();
70417
+ const userPath = USER_CONFIG_PATH;
70418
+ const projectPath = getConfigPath("project");
70419
+ const userExists = await loadConfig({ scope: "user" }) !== null;
70420
+ const projectExists = await loadConfig({ scope: "project" }) !== null;
70337
70421
  console.log();
70338
70422
  console.log(source_default.bold.magenta("VibeFrame Configuration"));
70339
70423
  console.log(source_default.dim("\u2500".repeat(40)));
70340
70424
  console.log();
70341
- console.log(source_default.dim(`Config file: ${CONFIG_PATH}`));
70425
+ const userMark = activeScope === "user" && userExists ? source_default.cyan("\u2190 active") : userExists ? source_default.dim("(present)") : source_default.dim("(missing)");
70426
+ const projectMark = activeScope === "project" ? source_default.cyan("\u2190 active") : projectExists ? source_default.dim("(present)") : source_default.dim("(missing)");
70427
+ console.log(source_default.dim(`Scopes:`));
70428
+ console.log(source_default.dim(` user ${userPath} ${userMark}`));
70429
+ console.log(source_default.dim(` project ${projectPath} ${projectMark}`));
70342
70430
  if (hasCwdEnv) {
70343
- console.log(source_default.dim(`Project .env: ${cwdEnvPath}`));
70431
+ console.log(source_default.dim(` .env ${cwdEnvPath} ${source_default.dim("(env vars override config)")}`));
70344
70432
  }
70345
70433
  console.log();
70346
70434
  const config2 = await loadConfig();
@@ -70408,12 +70496,26 @@ async function showConfig(opts = { verbose: false }) {
70408
70496
  }
70409
70497
  console.log(source_default.bold("Resolution order:"));
70410
70498
  console.log(source_default.dim(" 1. --api-key CLI option"));
70411
- console.log(source_default.dim(` 2. ${CONFIG_PATH}`));
70412
- console.log(source_default.dim(" 3. .env in current directory"));
70413
- console.log(source_default.dim(" 4. Shell environment variables"));
70499
+ console.log(source_default.dim(` 2. Active config scope: ${activeScope === "project" ? projectPath : userPath}`));
70500
+ console.log(source_default.dim(` (project takes priority when present at cwd; user is ignored in that case)`));
70501
+ console.log(source_default.dim(" 3. .env / shell environment variables (process.env)"));
70414
70502
  console.log();
70415
70503
  }
70416
70504
  }
70505
+ function pickCompletionHint(shellPath) {
70506
+ if (!shellPath) return void 0;
70507
+ const shell = shellPath.split("/").pop() ?? "";
70508
+ if (shell.includes("zsh")) {
70509
+ return `echo 'eval "$(vibe completion zsh)"' >> ~/.zshrc`;
70510
+ }
70511
+ if (shell.includes("bash")) {
70512
+ return `echo 'eval "$(vibe completion bash)"' >> ~/.bashrc`;
70513
+ }
70514
+ if (shell.includes("fish")) {
70515
+ return `vibe completion fish > ~/.config/fish/completions/vibe.fish`;
70516
+ }
70517
+ return void 0;
70518
+ }
70417
70519
 
70418
70520
  // src/commands/init.ts
70419
70521
  init_esm();
@@ -71531,6 +71633,9 @@ async function runDiagnostics() {
71531
71633
  exists: existsSync36(resolve50(cwd, rel))
71532
71634
  }));
71533
71635
  const projectInitialized = projectFiles.some((f) => f.exists);
71636
+ const projectConfigPath = getProjectConfigPath(cwd);
71637
+ const projectConfigExists = existsSync36(projectConfigPath);
71638
+ const activeScope = await getActiveScope(cwd);
71534
71639
  const hosts = detectAgentHosts();
71535
71640
  const detectedNames = hosts.filter((h) => h.detected).map((h) => h.label);
71536
71641
  const recommendedMode = resolveSceneBuildMode({});
@@ -71553,8 +71658,15 @@ async function runDiagnostics() {
71553
71658
  ...Object.keys(optionalTools).length > 0 ? { optionalTools } : {}
71554
71659
  },
71555
71660
  scope: {
71661
+ activeScope,
71556
71662
  user: { configured: configExists, configPath: CONFIG_PATH },
71557
- project: { cwd, initialized: projectInitialized, files: projectFiles },
71663
+ project: {
71664
+ cwd,
71665
+ initialized: projectInitialized,
71666
+ files: projectFiles,
71667
+ configPath: projectConfigPath,
71668
+ configFileExists: projectConfigExists
71669
+ },
71558
71670
  agentHosts: { detected: detectedNames, summary: summariseAgentHosts(hosts) },
71559
71671
  sceneComposer: {
71560
71672
  recommendedMode,
@@ -71627,16 +71739,30 @@ function printReport(results, opts = { verbose: false }) {
71627
71739
  }
71628
71740
  console.log();
71629
71741
  console.log(source_default.bold(" Scope"));
71742
+ const userActive = results.scope.activeScope === "user" && results.scope.user.configured;
71743
+ const projectActive = results.scope.activeScope === "project";
71744
+ const activeMark = source_default.cyan(" \u2190 active");
71630
71745
  if (results.scope.user.configured) {
71631
- console.log(` User ${source_default.green("OK")} ${source_default.dim(results.scope.user.configPath)}`);
71746
+ console.log(
71747
+ ` Cfg(user) ${source_default.green("OK")} ${source_default.dim(results.scope.user.configPath)}${userActive ? activeMark : ""}`
71748
+ );
71749
+ } else {
71750
+ console.log(` Cfg(user) ${source_default.yellow("NOT SET")} ${source_default.dim("Run: vibe setup")}`);
71751
+ }
71752
+ if (results.scope.project.configFileExists) {
71753
+ console.log(
71754
+ ` Cfg(proj) ${source_default.green("OK")} ${source_default.dim(results.scope.project.configPath)}${projectActive ? activeMark : ""}`
71755
+ );
71632
71756
  } else {
71633
- console.log(` User ${source_default.yellow("NOT SET")} ${source_default.dim("Run: vibe setup")}`);
71757
+ console.log(
71758
+ ` Cfg(proj) ${source_default.dim("none")} ${source_default.dim(`Run: vibe setup --scope project (cwd: ${results.scope.project.cwd})`)}`
71759
+ );
71634
71760
  }
71635
71761
  if (results.scope.project.initialized) {
71636
71762
  const present = results.scope.project.files.filter((f) => f.exists).map((f) => f.path);
71637
- console.log(` Project ${source_default.green("OK")} ${source_default.dim(present.join(", "))}`);
71763
+ console.log(` Init ${source_default.green("OK")} ${source_default.dim(present.join(", "))}`);
71638
71764
  } else {
71639
- console.log(` Project ${source_default.yellow("NOT INIT")} ${source_default.dim(`Run: vibe init (cwd: ${results.scope.project.cwd})`)}`);
71765
+ console.log(` Init ${source_default.yellow("NOT INIT")} ${source_default.dim(`Run: vibe init (cwd: ${results.scope.project.cwd})`)}`);
71640
71766
  }
71641
71767
  console.log(` Agents ${source_default.dim(results.scope.agentHosts.summary)}`);
71642
71768
  if (verbose) {
@@ -71792,8 +71918,9 @@ async function runLiveKeyTests(results) {
71792
71918
  console.log();
71793
71919
  }
71794
71920
  function pickNextStep(results, hasMissingProviders) {
71795
- if (!results.scope.user.configured) {
71796
- return "Next: run 'vibe setup' to configure your user scope (API keys + LLM provider).";
71921
+ const anyConfigured = results.scope.user.configured || results.scope.project.configFileExists;
71922
+ if (!anyConfigured) {
71923
+ return "Next: run 'vibe setup' (or 'vibe setup --scope project' to keep config inside the project) to configure API keys + LLM provider.";
71797
71924
  }
71798
71925
  if (!results.scope.project.initialized) {
71799
71926
  return "Next: run 'vibe init' in your project directory to scaffold AGENTS.md / CLAUDE.md / .env.example.";
@@ -73146,6 +73273,162 @@ var walkthroughCommand = new Command("walkthrough").description("Step-by-step au
73146
73273
  console.log();
73147
73274
  });
73148
73275
 
73276
+ // src/commands/completion.ts
73277
+ init_esm();
73278
+ init_output();
73279
+ init_cost_tier();
73280
+ var completionCommand = new Command("completion").description("Print a shell completion script for `vibe`").argument("<shell>", "Target shell: zsh | bash | fish").addHelpText(
73281
+ "after",
73282
+ `
73283
+ Examples:
73284
+ $ vibe completion zsh >> ~/.zsh/completions/_vibe # then ensure that dir is in fpath
73285
+ $ echo 'eval "$(vibe completion bash)"' >> ~/.bashrc
73286
+ $ vibe completion fish > ~/.config/fish/completions/vibe.fish
73287
+
73288
+ The output is generated from the live command tree, so it stays in
73289
+ sync with the CLI surface. Re-run after upgrades.
73290
+ `
73291
+ ).action((shell) => {
73292
+ const program3 = completionCommand.parent;
73293
+ if (!program3) {
73294
+ exitWithError(usageError("completion: program tree not available"));
73295
+ }
73296
+ const valid = /* @__PURE__ */ new Set(["zsh", "bash", "fish"]);
73297
+ if (!valid.has(shell)) {
73298
+ exitWithError(usageError(`Unknown shell: ${shell}`, "Supported: zsh, bash, fish"));
73299
+ }
73300
+ if (shell === "zsh") console.log(generateZsh(program3));
73301
+ else if (shell === "bash") console.log(generateBash(program3));
73302
+ else console.log(generateFish(program3));
73303
+ });
73304
+ function collectCommandTree(program3) {
73305
+ const skipTopLevel = /* @__PURE__ */ new Set(["help"]);
73306
+ const nodes = [];
73307
+ for (const top of program3.commands) {
73308
+ const name = top.name();
73309
+ if (skipTopLevel.has(name)) continue;
73310
+ const desc = top.description() || "";
73311
+ if (desc.toLowerCase().includes("deprecated")) continue;
73312
+ const subs = top.commands.filter((s) => !(s.description() || "").toLowerCase().includes("deprecated")).map((s) => ({
73313
+ name: s.name(),
73314
+ description: s.description() || "",
73315
+ cost: getCostTier(s)
73316
+ }));
73317
+ nodes.push({ name, description: desc, cost: getCostTier(top), subcommands: subs });
73318
+ }
73319
+ return nodes;
73320
+ }
73321
+ function tierBadge(cost) {
73322
+ if (!cost) return "";
73323
+ return `[${cost.toUpperCase()}] `;
73324
+ }
73325
+ function shellEscape(s) {
73326
+ return s.replace(/\n/g, " ").replace(/\s+/g, " ").trim().replace(/'/g, `'"'"'`);
73327
+ }
73328
+ function generateZsh(program3) {
73329
+ const nodes = collectCommandTree(program3);
73330
+ const topLines = nodes.map((n) => `'${n.name}:${shellEscape(tierBadge(n.cost) + n.description)}'`).join(" \\\n ");
73331
+ const subBlocks = nodes.filter((n) => n.subcommands.length > 0).map((n) => {
73332
+ const subs = n.subcommands.map((s) => `'${s.name}:${shellEscape(tierBadge(s.cost) + s.description)}'`).join(" \\\n ");
73333
+ return ` ${n.name})
73334
+ local -a subcommands
73335
+ subcommands=(
73336
+ ${subs}
73337
+ )
73338
+ _describe 'subcommand' subcommands
73339
+ ;;`;
73340
+ }).join("\n");
73341
+ return `#compdef vibe
73342
+ # vibe shell completion for zsh \u2014 generated by \`vibe completion zsh\`.
73343
+ # Re-run after upgrades; the script reflects the live command tree.
73344
+
73345
+ _vibe() {
73346
+ local -a commands
73347
+ commands=(
73348
+ ${topLines}
73349
+ )
73350
+
73351
+ _arguments -C \\
73352
+ '1: :->cmd' \\
73353
+ '*:: :->args'
73354
+
73355
+ case $state in
73356
+ cmd)
73357
+ _describe 'command' commands
73358
+ ;;
73359
+ args)
73360
+ case $words[1] in
73361
+ ${subBlocks}
73362
+ esac
73363
+ ;;
73364
+ esac
73365
+ }
73366
+
73367
+ _vibe "$@"
73368
+ `;
73369
+ }
73370
+ function generateBash(program3) {
73371
+ const nodes = collectCommandTree(program3);
73372
+ const topNames = nodes.map((n) => n.name).join(" ");
73373
+ const subCases = nodes.filter((n) => n.subcommands.length > 0).map((n) => {
73374
+ const names = n.subcommands.map((s) => s.name).join(" ");
73375
+ return ` ${n.name})
73376
+ COMPREPLY=( $(compgen -W "${names}" -- "$cur") )
73377
+ ;;`;
73378
+ }).join("\n");
73379
+ return `# vibe shell completion for bash \u2014 generated by \`vibe completion bash\`.
73380
+ # Re-run after upgrades; the script reflects the live command tree.
73381
+ # Bash completions are word-only \u2014 no description hints \u2014 so consider
73382
+ # zsh or fish for richer feedback.
73383
+
73384
+ _vibe_complete() {
73385
+ local cur prev cmd
73386
+ cur="\${COMP_WORDS[COMP_CWORD]}"
73387
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
73388
+ cmd="\${COMP_WORDS[1]}"
73389
+
73390
+ if [[ $COMP_CWORD == 1 ]]; then
73391
+ COMPREPLY=( $(compgen -W "${topNames}" -- "$cur") )
73392
+ return 0
73393
+ fi
73394
+
73395
+ case "$cmd" in
73396
+ ${subCases}
73397
+ *)
73398
+ COMPREPLY=()
73399
+ ;;
73400
+ esac
73401
+ }
73402
+
73403
+ complete -F _vibe_complete vibe
73404
+ `;
73405
+ }
73406
+ function generateFish(program3) {
73407
+ const nodes = collectCommandTree(program3);
73408
+ const lines = [
73409
+ "# vibe shell completion for fish \u2014 generated by `vibe completion fish`.",
73410
+ "# Re-run after upgrades; the script reflects the live command tree.",
73411
+ "",
73412
+ "complete -c vibe -f",
73413
+ ""
73414
+ ];
73415
+ for (const n of nodes) {
73416
+ const desc = shellEscape(tierBadge(n.cost) + n.description);
73417
+ lines.push(`complete -c vibe -n '__fish_use_subcommand' -a '${n.name}' -d '${desc}'`);
73418
+ }
73419
+ lines.push("");
73420
+ for (const n of nodes) {
73421
+ if (n.subcommands.length === 0) continue;
73422
+ for (const s of n.subcommands) {
73423
+ const desc = shellEscape(tierBadge(s.cost) + s.description);
73424
+ lines.push(
73425
+ `complete -c vibe -n '__fish_seen_subcommand_from ${n.name}' -a '${s.name}' -d '${desc}'`
73426
+ );
73427
+ }
73428
+ }
73429
+ return lines.join("\n") + "\n";
73430
+ }
73431
+
73149
73432
  // src/index.ts
73150
73433
  init_api_key();
73151
73434
 
@@ -73372,6 +73655,7 @@ program2.addCommand(batchCommand);
73372
73655
  program2.addCommand(schemaCommand);
73373
73656
  program2.addCommand(contextCommand);
73374
73657
  program2.addCommand(walkthroughCommand);
73658
+ program2.addCommand(completionCommand);
73375
73659
  program2.addCommand(mediaCommand, { hidden: true });
73376
73660
  function propagateErrorHandling(cmd) {
73377
73661
  for (const sub of cmd.commands) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.88.0",
3
+ "version": "0.90.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.88.0",
187
- "@vibeframe/core": "0.88.0"
186
+ "@vibeframe/ai-providers": "0.90.0",
187
+ "@vibeframe/core": "0.90.0"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"