@vibeframe/cli 0.91.2 → 0.91.4

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 +124 -26
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -31683,6 +31683,18 @@ var init_open_url = __esm({
31683
31683
  // src/utils/tty.ts
31684
31684
  import { createInterface as createInterface2 } from "node:readline";
31685
31685
  import { ReadStream } from "node:tty";
31686
+ function splitTTYInput(chunk) {
31687
+ const tokens = [];
31688
+ for (let i = 0; i < chunk.length; i++) {
31689
+ if (chunk[i] === "\x1B" && chunk[i + 1] === "[" && i + 2 < chunk.length) {
31690
+ tokens.push(chunk.slice(i, i + 3));
31691
+ i += 2;
31692
+ } else {
31693
+ tokens.push(chunk[i]);
31694
+ }
31695
+ }
31696
+ return tokens;
31697
+ }
31686
31698
  function getTTYInputStream() {
31687
31699
  if (process.stdin.isTTY) {
31688
31700
  return process.stdin;
@@ -31736,14 +31748,16 @@ async function prompt2(question) {
31736
31748
  async function promptHidden(question, opts = {}) {
31737
31749
  const input = getTTYInputStream();
31738
31750
  return new Promise((resolve64) => {
31739
- process.stdout.write(question);
31740
31751
  let value = "";
31741
31752
  if (typeof input.setRawMode === "function") {
31742
31753
  input.setRawMode(true);
31743
31754
  input.resume();
31744
31755
  input.setEncoding("utf8");
31745
- const onData = (char) => {
31756
+ let finished = false;
31757
+ const handleChar = (char) => {
31758
+ if (finished) return;
31746
31759
  if (char === "\n" || char === "\r" || char === "") {
31760
+ finished = true;
31747
31761
  input.setRawMode(false);
31748
31762
  input.removeListener("data", onData);
31749
31763
  process.stdout.write("\n");
@@ -31764,10 +31778,14 @@ async function promptHidden(question, opts = {}) {
31764
31778
  process.stdout.write("*".repeat(char.length));
31765
31779
  }
31766
31780
  };
31781
+ const onData = (chunk) => {
31782
+ for (const char of splitTTYInput(chunk)) handleChar(char);
31783
+ };
31767
31784
  input.on("data", onData);
31785
+ process.stdout.write(question);
31768
31786
  } else {
31769
31787
  const rl = createInterface2({ input, output: process.stdout });
31770
- rl.question("", (answer) => {
31788
+ rl.question(question, (answer) => {
31771
31789
  rl.close();
31772
31790
  resolve64(answer);
31773
31791
  });
@@ -31796,8 +31814,11 @@ async function promptSelect(_question, options, defaultIndex = 0) {
31796
31814
  input.setRawMode(true);
31797
31815
  input.resume();
31798
31816
  input.setEncoding("utf8");
31799
- const onData = (char) => {
31817
+ let finished = false;
31818
+ const handleChar = (char) => {
31819
+ if (finished) return;
31800
31820
  if (char === "\r" || char === "\n") {
31821
+ finished = true;
31801
31822
  input.setRawMode(false);
31802
31823
  input.removeListener("data", onData);
31803
31824
  process.stdout.write("\n");
@@ -31814,6 +31835,7 @@ async function promptSelect(_question, options, defaultIndex = 0) {
31814
31835
  render();
31815
31836
  } else if (char >= "1" && char <= String(options.length)) {
31816
31837
  selected = parseInt(char, 10) - 1;
31838
+ finished = true;
31817
31839
  input.setRawMode(false);
31818
31840
  input.removeListener("data", onData);
31819
31841
  render();
@@ -31821,6 +31843,9 @@ async function promptSelect(_question, options, defaultIndex = 0) {
31821
31843
  resolve64(selected);
31822
31844
  }
31823
31845
  };
31846
+ const onData = (chunk) => {
31847
+ for (const char of splitTTYInput(chunk)) handleChar(char);
31848
+ };
31824
31849
  input.on("data", onData);
31825
31850
  });
31826
31851
  }
@@ -31861,8 +31886,11 @@ async function promptConfirm(question, defaultYes = true) {
31861
31886
  input.setRawMode(true);
31862
31887
  input.resume();
31863
31888
  input.setEncoding("utf8");
31864
- const onData = (char) => {
31889
+ let finished = false;
31890
+ const handleChar = (char) => {
31891
+ if (finished) return;
31865
31892
  if (char === "\r" || char === "\n") {
31893
+ finished = true;
31866
31894
  input.setRawMode(false);
31867
31895
  input.removeListener("data", onData);
31868
31896
  resolve64(selected === 0);
@@ -31875,18 +31903,23 @@ async function promptConfirm(question, defaultYes = true) {
31875
31903
  render();
31876
31904
  } else if (char === "y" || char === "Y") {
31877
31905
  selected = 0;
31906
+ finished = true;
31878
31907
  input.setRawMode(false);
31879
31908
  input.removeListener("data", onData);
31880
31909
  render();
31881
31910
  resolve64(true);
31882
31911
  } else if (char === "n" || char === "N") {
31883
31912
  selected = 1;
31913
+ finished = true;
31884
31914
  input.setRawMode(false);
31885
31915
  input.removeListener("data", onData);
31886
31916
  render();
31887
31917
  resolve64(false);
31888
31918
  }
31889
31919
  };
31920
+ const onData = (chunk) => {
31921
+ for (const char of splitTTYInput(chunk)) handleChar(char);
31922
+ };
31890
31923
  input.on("data", onData);
31891
31924
  });
31892
31925
  }
@@ -31896,12 +31929,14 @@ async function promptConfirm(question, defaultYes = true) {
31896
31929
  return answer.toLowerCase().startsWith("y");
31897
31930
  }
31898
31931
  async function promptMultiSelect(_question, options, defaultSelected = [], opts = {}) {
31932
+ const pickFocused = opts.pickFocusedOnEnter ?? opts.enterSelectsFocusedWhenEmpty ?? false;
31899
31933
  const input = getTTYInputStream();
31900
31934
  const selected = options.map((_, i) => Boolean(defaultSelected[i]));
31901
31935
  if (typeof input.setRawMode === "function") {
31902
31936
  return new Promise((resolve64) => {
31903
31937
  let cursor = 0;
31904
31938
  let renderCount = 0;
31939
+ let userToggled = false;
31905
31940
  const render = () => {
31906
31941
  if (renderCount > 0) {
31907
31942
  process.stdout.write(`\x1B[${options.length}A`);
@@ -31929,11 +31964,16 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
31929
31964
  }
31930
31965
  resolve64(result);
31931
31966
  };
31932
- const onData = (char) => {
31967
+ let finished = false;
31968
+ const handleChar = (char) => {
31969
+ if (finished) return;
31933
31970
  if (char === "\r" || char === "\n") {
31934
- if (opts.enterSelectsFocusedWhenEmpty && selected.every((v) => !v)) {
31971
+ if (pickFocused && !userToggled) {
31972
+ for (let i = 0; i < selected.length; i++) selected[i] = false;
31935
31973
  selected[cursor] = true;
31974
+ render();
31936
31975
  }
31976
+ finished = true;
31937
31977
  finish();
31938
31978
  } else if (char === "") {
31939
31979
  input.setRawMode(false);
@@ -31941,6 +31981,7 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
31941
31981
  process.exit(1);
31942
31982
  } else if (char === " ") {
31943
31983
  selected[cursor] = !selected[cursor];
31984
+ userToggled = true;
31944
31985
  render();
31945
31986
  } else if (char === "\x1B[A" || char === "k") {
31946
31987
  cursor = (cursor - 1 + options.length) % options.length;
@@ -31951,13 +31992,18 @@ async function promptMultiSelect(_question, options, defaultSelected = [], opts
31951
31992
  } else if (char === "a") {
31952
31993
  const allOn = selected.every(Boolean);
31953
31994
  for (let i = 0; i < selected.length; i++) selected[i] = !allOn;
31995
+ userToggled = true;
31954
31996
  render();
31955
31997
  } else if (char >= "1" && char <= String(Math.min(options.length, 9))) {
31956
31998
  const i = parseInt(char, 10) - 1;
31957
31999
  selected[i] = !selected[i];
32000
+ userToggled = true;
31958
32001
  render();
31959
32002
  }
31960
32003
  };
32004
+ const onData = (chunk) => {
32005
+ for (const char of splitTTYInput(chunk)) handleChar(char);
32006
+ };
31961
32007
  input.on("data", onData);
31962
32008
  });
31963
32009
  }
@@ -62913,7 +62959,7 @@ init_source();
62913
62959
  // package.json
62914
62960
  var package_default = {
62915
62961
  name: "@vibeframe/cli",
62916
- version: "0.91.2",
62962
+ version: "0.91.4",
62917
62963
  description: "VibeFrame CLI - natural language video editing from the terminal",
62918
62964
  private: false,
62919
62965
  license: "MIT",
@@ -70103,6 +70149,15 @@ function applyProviderDefault(config2, choice) {
70103
70149
  config2.defaults.videoProvider = choice.defaultFor.value;
70104
70150
  }
70105
70151
  }
70152
+ function mergeSelectedFeatures(current, next) {
70153
+ const merged = [...current];
70154
+ for (const feature of next) {
70155
+ if (!merged.some((f) => f.label === feature.label)) {
70156
+ merged.push(feature);
70157
+ }
70158
+ }
70159
+ return merged;
70160
+ }
70106
70161
  async function runNonInteractiveSetup(opts) {
70107
70162
  const scope = opts.scope ?? "user";
70108
70163
  let config2 = await loadConfig({ scope });
@@ -70228,11 +70283,13 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70228
70283
  console.log();
70229
70284
  if (topIndex === 0) {
70230
70285
  await saveConfig(config2, { scope });
70231
- await showComplete(config2, "vibe edit silence-cut video.mp4 -o clean.mp4", [], scope);
70286
+ await completeOrContinue(config2, "vibe edit silence-cut video.mp4 -o clean.mp4", [], scope);
70232
70287
  return;
70233
70288
  }
70234
70289
  if (topIndex === 3) {
70235
- await runCustomSetup(config2, scope);
70290
+ await runCustomSetup(config2, scope, { showComplete: false });
70291
+ await saveConfig(config2, { scope });
70292
+ await completeOrContinue(config2, "vibe doctor --test-keys", [], scope);
70236
70293
  return;
70237
70294
  }
70238
70295
  if (topIndex === 2) {
@@ -70249,13 +70306,48 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70249
70306
  console.log();
70250
70307
  await collectKeys(config2, pipelineKeys);
70251
70308
  await saveConfig(config2, { scope });
70252
- await showComplete(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format", [], scope);
70309
+ await completeOrContinue(config2, "vibe build my-story/ # see CONTRIBUTING.md for STORYBOARD.md format", [], scope);
70253
70310
  return;
70254
70311
  }
70312
+ const result = await runAIFeaturesSetup(config2, scope);
70313
+ await saveConfig(config2, { scope });
70314
+ await completeOrContinue(config2, result.tryCommand, result.selectedFeatures, scope);
70315
+ }
70316
+ async function completeOrContinue(config2, tryCommand, selectedFeatures, scope) {
70317
+ console.log(source_default.dim("Setup checkpoint"));
70318
+ console.log(source_default.bold("Anything else to configure?"));
70319
+ console.log();
70320
+ const action = await promptSelect(
70321
+ source_default.cyan(" Next: "),
70322
+ [
70323
+ `Add or update AI features ${source_default.dim("(images, videos, audio, editing)")}`,
70324
+ `Open full provider list ${source_default.dim("(every supported provider)")}`,
70325
+ "Finish setup"
70326
+ ],
70327
+ 2
70328
+ );
70329
+ console.log();
70330
+ if (action === 0) {
70331
+ const result = await runAIFeaturesSetup(config2, scope);
70332
+ const mergedFeatures = mergeSelectedFeatures(selectedFeatures, result.selectedFeatures);
70333
+ const mergedTryCommand = selectedFeatures.length > 0 ? tryCommand : result.tryCommand;
70334
+ await saveConfig(config2, { scope });
70335
+ await completeOrContinue(config2, mergedTryCommand, mergedFeatures, scope);
70336
+ return;
70337
+ }
70338
+ if (action === 1) {
70339
+ await runCustomSetup(config2, scope, { showComplete: false });
70340
+ await saveConfig(config2, { scope });
70341
+ await completeOrContinue(config2, tryCommand, selectedFeatures, scope);
70342
+ return;
70343
+ }
70344
+ await showComplete(config2, tryCommand, selectedFeatures, scope);
70345
+ }
70346
+ async function runAIFeaturesSetup(config2, _scope) {
70255
70347
  console.log(source_default.dim("2. Features"));
70256
70348
  console.log(source_default.bold("Which AI features do you need?"));
70257
- console.log(source_default.dim(" \u2191\u2193 navigate \xB7 space to toggle \xB7 enter to confirm"));
70258
- console.log(source_default.dim(" Tip: pressing enter on the highlighted item selects it if nothing is checked yet."));
70349
+ 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.`));
70350
+ console.log(source_default.dim(" Shortcut: enter alone picks only the highlighted feature."));
70259
70351
  console.log();
70260
70352
  const featureLabels = AI_FEATURES.map((f) => {
70261
70353
  const keyCount = f.keys.length;
@@ -70266,21 +70358,19 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70266
70358
  source_default.cyan(" Pick (e.g. 1,3 or 'all'): "),
70267
70359
  featureLabels,
70268
70360
  [],
70269
- { enterSelectsFocusedWhenEmpty: true }
70361
+ { pickFocusedOnEnter: true }
70270
70362
  );
70271
70363
  const selectedFeatures = picked.map((i) => AI_FEATURES[i]);
70272
70364
  console.log();
70273
70365
  if (selectedFeatures.length === 0) {
70274
70366
  console.log(source_default.dim(" No features selected. You can re-run setup anytime."));
70275
70367
  console.log();
70276
- await saveConfig(config2, { scope });
70277
- await showComplete(config2, "vibe --help", [], scope);
70278
- return;
70368
+ return { tryCommand: "vibe --help", selectedFeatures: [] };
70279
70369
  }
70280
70370
  const plannedKeys = /* @__PURE__ */ new Map();
70281
70371
  console.log(source_default.dim("3. Providers"));
70282
70372
  console.log(source_default.bold("Choose providers for each selected feature"));
70283
- console.log(source_default.dim(" Recommended defaults are pre-selected. You can toggle more than one."));
70373
+ 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.`));
70284
70374
  console.log();
70285
70375
  for (const feature of selectedFeatures) {
70286
70376
  const keysForFeature = [];
@@ -70291,15 +70381,22 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70291
70381
  console.log(source_default.dim(` Alternatives: ${feature.alsoAvailable}`));
70292
70382
  }
70293
70383
  console.log();
70294
- const defaultSelected = feature.providerChoices.map((_, i) => i === 0);
70384
+ const defaultSelected = feature.providerChoices.map(() => false);
70295
70385
  const providerLabels = feature.providerChoices.map((choice) => {
70296
70386
  const keyHint = choice.keyless ? source_default.dim("no key") : choice.key ? source_default.dim(choice.key.envVar) : source_default.dim("no key");
70297
- return `${choice.label} ${source_default.dim(`- ${choice.desc}`)} ${keyHint}`;
70387
+ const defaultHint = choice === feature.providerChoices?.[0] ? source_default.dim("recommended default") : "";
70388
+ return [
70389
+ choice.label,
70390
+ defaultHint,
70391
+ source_default.dim(`- ${choice.desc}`),
70392
+ keyHint
70393
+ ].filter(Boolean).join(" ");
70298
70394
  });
70299
70395
  const pickedProviders = await promptMultiSelect(
70300
- source_default.cyan(" Providers (space toggles, enter confirms): "),
70396
+ source_default.cyan(" Providers (arrow + enter picks one, space adds more): "),
70301
70397
  providerLabels,
70302
- defaultSelected
70398
+ defaultSelected,
70399
+ { pickFocusedOnEnter: true }
70303
70400
  );
70304
70401
  const selectedChoices = pickedProviders.map((i) => feature.providerChoices[i]);
70305
70402
  const defaultChoice = selectedChoices.find((choice) => choice.defaultFor);
@@ -70380,8 +70477,7 @@ async function runSetupWizard(fullSetup = false, scope = "user") {
70380
70477
  console.log(source_default.dim(" No API keys needed for the selected provider(s)."));
70381
70478
  console.log();
70382
70479
  }
70383
- await saveConfig(config2, { scope });
70384
- await showComplete(config2, selectedFeatures[0].tryCommand, selectedFeatures, scope);
70480
+ return { tryCommand: selectedFeatures[0].tryCommand, selectedFeatures };
70385
70481
  }
70386
70482
  async function collectKeys(config2, keys) {
70387
70483
  loadEnv();
@@ -70417,7 +70513,7 @@ async function collectKeys(config2, keys) {
70417
70513
  }
70418
70514
  console.log();
70419
70515
  }
70420
- async function runCustomSetup(config2, scope = "user") {
70516
+ async function runCustomSetup(config2, scope = "user", opts = {}) {
70421
70517
  console.log(source_default.bold("1. Agent LLM Provider") + source_default.dim(" (for vibe agent)"));
70422
70518
  console.log(source_default.dim(" Only needed if you use the interactive Agent mode."));
70423
70519
  console.log();
@@ -70488,7 +70584,9 @@ async function runCustomSetup(config2, scope = "user") {
70488
70584
  }
70489
70585
  console.log();
70490
70586
  await saveConfig(config2, { scope });
70491
- await showComplete(config2, "vibe doctor", [], scope);
70587
+ if (opts.showComplete !== false) {
70588
+ await showComplete(config2, "vibe doctor", [], scope);
70589
+ }
70492
70590
  }
70493
70591
  async function showComplete(config2, defaultTryCommand, features = [], scope = "user") {
70494
70592
  console.log(source_default.dim("\u2500".repeat(40)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibeframe/cli",
3
- "version": "0.91.2",
3
+ "version": "0.91.4",
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.2",
187
- "@vibeframe/core": "0.91.2"
186
+ "@vibeframe/ai-providers": "0.91.4",
187
+ "@vibeframe/core": "0.91.4"
188
188
  },
189
189
  "engines": {
190
190
  "node": ">=20"