vibecodingmachine-cli 2025.12.6-1702 → 2025.12.22-2230

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.
@@ -9,7 +9,7 @@ const repo = require('../commands/repo');
9
9
  const auto = require('../commands/auto');
10
10
  const status = require('../commands/status');
11
11
  const requirements = require('../commands/requirements');
12
- const { getRepoPath, readConfig, writeConfig } = require('./config');
12
+ const { getRepoPath, readConfig, writeConfig, getAutoConfig } = require('./config');
13
13
  const { getProviderPreferences, saveProviderPreferences, getProviderDefinitions } = require('../utils/provider-registry');
14
14
  const { checkAutoModeStatus } = require('./auto-mode');
15
15
  const {
@@ -19,6 +19,7 @@ const {
19
19
  requirementsExists,
20
20
  isComputerNameEnabled
21
21
  } = require('vibecodingmachine-core');
22
+ const pkg = require('../../package.json');
22
23
 
23
24
  /**
24
25
  * Format IDE name for display
@@ -213,95 +214,9 @@ function formatPath(fullPath) {
213
214
 
214
215
  async function countRequirements() {
215
216
  try {
216
- const { getRequirementsPath, getVibeCodingMachineDir } = require('vibecodingmachine-core');
217
+ const { getProjectRequirementStats } = require('vibecodingmachine-core');
217
218
  const repoPath = await getRepoPath();
218
- const reqPath = await getRequirementsPath(repoPath);
219
-
220
- if (!reqPath || !await fs.pathExists(reqPath)) {
221
- return null;
222
- }
223
-
224
- const content = await fs.readFile(reqPath, 'utf8');
225
-
226
- // Count requirements in each section
227
- let todoCount = 0;
228
- let toVerifyCount = 0;
229
- let verifiedCount = 0;
230
-
231
- // Split by sections
232
- const lines = content.split('\n');
233
- let currentSection = '';
234
-
235
- for (const line of lines) {
236
- const trimmed = line.trim();
237
-
238
- // Check for requirement headers first (###), then section headers (##)
239
- // This prevents ### from being treated as section headers
240
- if (trimmed.startsWith('###')) {
241
- // Count requirements (### headers in new format)
242
- // IMPORTANT: Only count if we're in a recognized section
243
- if (currentSection) {
244
- const requirementText = trimmed.replace(/^###\s*/, '').trim();
245
- if (requirementText) { // Only count if requirement text is not empty
246
- if (currentSection === 'todo') {
247
- todoCount++;
248
- } else if (currentSection === 'toverify') {
249
- toVerifyCount++;
250
- }
251
- }
252
- }
253
- } else if (trimmed.startsWith('##') && !trimmed.startsWith('###')) {
254
- // Detect section headers (must start with ## but not ###)
255
- if (trimmed.includes('ā³ Requirements not yet completed') ||
256
- trimmed.includes('Requirements not yet completed')) {
257
- currentSection = 'todo';
258
- } else if (trimmed.includes('šŸ” TO VERIFY BY HUMAN') ||
259
- trimmed.includes('TO VERIFY BY HUMAN') ||
260
- trimmed.includes('šŸ” TO VERIFY') ||
261
- trimmed.includes('TO VERIFY') ||
262
- trimmed.includes('āœ… Verified by AI') ||
263
- trimmed.includes('Verified by AI')) {
264
- currentSection = 'toverify';
265
- } else {
266
- // Any other section header clears the current section
267
- currentSection = '';
268
- }
269
- }
270
- }
271
-
272
- // Count verified requirements from CHANGELOG.md (at repository root)
273
- const allnightDir = await getVibeCodingMachineDir();
274
- if (allnightDir) {
275
- // CHANGELOG.md is at the repository root
276
- // If .vibecodingmachine is inside repo: go up one level
277
- // If .vibecodingmachine is sibling (../.vibecodingmachine-reponame): go up one level then into repo
278
- let changelogPath;
279
- const allnightStatus = await require('vibecodingmachine-core').checkVibeCodingMachineExists();
280
-
281
- if (allnightStatus.insideExists) {
282
- // .vibecodingmachine is inside repo, so go up one level
283
- changelogPath = path.join(path.dirname(allnightDir), 'CHANGELOG.md');
284
- } else if (allnightStatus.siblingExists) {
285
- // .vibecodingmachine is sibling, use current working directory
286
- changelogPath = path.join(process.cwd(), 'CHANGELOG.md');
287
- }
288
-
289
- if (changelogPath && await fs.pathExists(changelogPath)) {
290
- const changelogContent = await fs.readFile(changelogPath, 'utf8');
291
- // Count entries that look like completed requirements
292
- // Each entry typically starts with "- " followed by date/description
293
- const changelogLines = changelogContent.split('\n');
294
- for (const line of changelogLines) {
295
- const trimmed = line.trim();
296
- // Count lines that start with "- " and have substantial content (not just empty bullets)
297
- if (trimmed.startsWith('- ') && trimmed.length > 10) {
298
- verifiedCount++;
299
- }
300
- }
301
- }
302
- }
303
-
304
- return { todoCount, toVerifyCount, verifiedCount };
219
+ return await getProjectRequirementStats(repoPath);
305
220
  } catch (error) {
306
221
  return null;
307
222
  }
@@ -357,25 +272,14 @@ async function showWelcomeScreen() {
357
272
 
358
273
  const repoPath = process.cwd(); // Always use current working directory
359
274
  const autoStatus = await checkAutoModeStatus();
360
- const allnightStatus = await checkVibeCodingMachineExists();
361
275
  const hostname = getHostname();
362
- const requirementsFilename = await getRequirementsFilename();
363
- const useHostname = await isComputerNameEnabled();
364
276
 
365
277
  // Get current IDE from config
366
278
  const { getAutoConfig } = require('./config');
367
279
  const autoConfig = await getAutoConfig();
368
- const currentIDE = autoConfig.ide || autoStatus.ide || 'claude-code';
369
280
 
370
281
  // Check for requirements file
371
282
  const hasRequirements = await requirementsExists();
372
- let requirementsLocation = '';
373
-
374
- if (allnightStatus.insideExists) {
375
- requirementsLocation = '.vibecodingmachine';
376
- } else if (allnightStatus.siblingExists) {
377
- requirementsLocation = path.basename(allnightStatus.siblingDir);
378
- }
379
283
 
380
284
  // Count requirements if file exists
381
285
  const counts = hasRequirements ? await countRequirements() : null;
@@ -385,7 +289,6 @@ async function showWelcomeScreen() {
385
289
  console.clear();
386
290
 
387
291
  // Get version from package.json
388
- const pkg = require('../../package.json');
389
292
  const version = `v${pkg.version}`;
390
293
 
391
294
  // Display welcome banner with version
@@ -485,7 +388,7 @@ async function showRequirementsTree() {
485
388
  };
486
389
 
487
390
  // Build tree structure
488
- const buildTree = () => {
391
+ const buildTree = async () => {
489
392
  tree.items = [];
490
393
 
491
394
  // Root: Requirements
@@ -495,23 +398,22 @@ async function showRequirementsTree() {
495
398
  tree.items.push({ level: 1, type: 'add', label: 'āž• Add new requirement', key: 'add-one' });
496
399
  tree.items.push({ level: 1, type: 'add', label: 'āž• Add multiple requirements', key: 'add-many' });
497
400
 
498
- // Calculate counts and percentages
401
+ // Use pre-calculated stats and labels from shared logic
402
+ const stats = await countRequirements();
403
+ const { todoCount, toVerifyCount, verifiedCount, total, todoLabel, toVerifyLabel, verifiedLabel } = stats || {
404
+ todoCount: 0, toVerifyCount: 0, verifiedCount: 0, total: 0,
405
+ todoLabel: 'ā³ TODO (0 - 0%)', toVerifyLabel: 'āœ… TO VERIFY (0 - 0%)', verifiedLabel: 'šŸŽ‰ VERIFIED (0 - 0%)'
406
+ };
407
+
499
408
  const verifiedReqs = tree.verifiedReqs || [];
500
409
  const verifyReqs = tree.verifyReqs || [];
501
410
  const clarificationReqs = tree.clarificationReqs || [];
502
411
  const todoReqs = tree.todoReqs || [];
503
412
  const recycledReqs = tree.recycledReqs || [];
504
- const total = verifiedReqs.length + verifyReqs.length + clarificationReqs.length + todoReqs.length + recycledReqs.length;
505
-
506
- const verifiedPercent = total > 0 ? Math.round((verifiedReqs.length / total) * 100) : 0;
507
- const verifyPercent = total > 0 ? Math.round((verifyReqs.length / total) * 100) : 0;
508
- const clarificationPercent = total > 0 ? Math.round((clarificationReqs.length / total) * 100) : 0;
509
- const todoPercent = total > 0 ? Math.round((todoReqs.length / total) * 100) : 0;
510
- const recycledPercent = total > 0 ? Math.round((recycledReqs.length / total) * 100) : 0;
511
413
 
512
414
  // VERIFIED section (first) - only show if has requirements
513
- if (verifiedReqs.length > 0) {
514
- tree.items.push({ level: 1, type: 'section', label: `šŸŽ‰ VERIFIED (${verifiedReqs.length} - ${verifiedPercent}%)`, key: 'verified' });
415
+ if (verifiedReqs.length > 0 || verifiedCount > 0) {
416
+ tree.items.push({ level: 1, type: 'section', label: `šŸŽ‰ ${verifiedLabel}`, key: 'verified' });
515
417
 
516
418
  if (tree.expanded.verified) {
517
419
  verifiedReqs.forEach((req, idx) => {
@@ -521,8 +423,8 @@ async function showRequirementsTree() {
521
423
  }
522
424
 
523
425
  // TO VERIFY section (second) - only show if has requirements
524
- if (verifyReqs.length > 0) {
525
- tree.items.push({ level: 1, type: 'section', label: `āœ… TO VERIFY (${verifyReqs.length} - ${verifyPercent}%)`, key: 'verify', section: 'āœ… Verified by AI screenshot' });
426
+ if (verifyReqs.length > 0 || toVerifyCount > 0) {
427
+ tree.items.push({ level: 1, type: 'section', label: `āœ… ${toVerifyLabel}`, key: 'verify', section: 'āœ… Verified by AI screenshot' });
526
428
 
527
429
  if (tree.expanded.verify) {
528
430
  verifyReqs.forEach((req, idx) => {
@@ -543,8 +445,8 @@ async function showRequirementsTree() {
543
445
  }
544
446
 
545
447
  // TODO section (fourth) - only show if has requirements
546
- if (todoReqs.length > 0) {
547
- tree.items.push({ level: 1, type: 'section', label: `ā³ TODO (${todoReqs.length} - ${todoPercent}%)`, key: 'todo', section: 'ā³ Requirements not yet completed' });
448
+ if (todoReqs.length > 0 || todoCount > 0) {
449
+ tree.items.push({ level: 1, type: 'section', label: `ā³ ${todoLabel}`, key: 'todo', section: 'ā³ Requirements not yet completed' });
548
450
 
549
451
  if (tree.expanded.todo) {
550
452
  todoReqs.forEach((req, idx) => {
@@ -600,17 +502,6 @@ async function showRequirementsTree() {
600
502
  // Reset inSection if we hit a section header that's not our target section
601
503
  if (sectionKey === 'verify' && inSection) {
602
504
  // Check if this is still a TO VERIFY section header
603
- const isStillToVerify = trimmed === '## šŸ” TO VERIFY BY HUMAN' ||
604
- trimmed.startsWith('## šŸ” TO VERIFY BY HUMAN') ||
605
- trimmed === '## šŸ” TO VERIFY' ||
606
- trimmed.startsWith('## šŸ” TO VERIFY') ||
607
- trimmed === '## TO VERIFY' ||
608
- trimmed.startsWith('## TO VERIFY') ||
609
- trimmed === '## āœ… TO VERIFY' ||
610
- trimmed.startsWith('## āœ… TO VERIFY') ||
611
- trimmed === '## āœ… Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG' ||
612
- (trimmed.startsWith('## āœ… Verified by AI screenshot. Needs Human to Verify and move to CHANGELOG') &&
613
- trimmed.includes('Needs Human to Verify'));
614
505
  if (!isStillToVerify) {
615
506
  // This will be handled by the "leaving section" check below, but ensure we don't process it as entering
616
507
  }
@@ -658,11 +549,6 @@ async function showRequirementsTree() {
658
549
  if (sectionKey === 'verify') {
659
550
  // For TO VERIFY, only break if this is clearly a different section
660
551
  // Check for specific section headers that indicate we've left TO VERIFY
661
- const isVerifiedSection = trimmed === '## šŸ“ VERIFIED' || trimmed.startsWith('## šŸ“ VERIFIED');
662
- const isTodoSection = trimmed.includes('ā³ Requirements not yet completed') || trimmed.includes('Requirements not yet completed');
663
- const isRecycledSection = trimmed === '## ā™»ļø RECYCLED' || trimmed.startsWith('## ā™»ļø RECYCLED') ||
664
- trimmed === '## šŸ“¦ RECYCLED' || trimmed.startsWith('## šŸ“¦ RECYCLED');
665
- const isClarificationSection = trimmed.includes('## ā“ Requirements needing') || trimmed.includes('ā“ Requirements needing');
666
552
 
667
553
  if (isVerifiedSection || isTodoSection || isRecycledSection || isClarificationSection) {
668
554
  break; // Different section, we've left TO VERIFY
@@ -688,7 +574,7 @@ async function showRequirementsTree() {
688
574
  }
689
575
 
690
576
  const details = [];
691
- let package = null;
577
+ let pkg = null;
692
578
 
693
579
  // Read package and description
694
580
  for (let j = i + 1; j < lines.length; j++) {
@@ -699,14 +585,14 @@ async function showRequirementsTree() {
699
585
  }
700
586
  // Check for PACKAGE line
701
587
  if (nextLine.startsWith('PACKAGE:')) {
702
- package = nextLine.replace(/^PACKAGE:\s*/, '').trim();
588
+ pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
703
589
  } else if (nextLine && !nextLine.startsWith('PACKAGE:')) {
704
590
  // Description line
705
591
  details.push(nextLine);
706
592
  }
707
593
  }
708
594
 
709
- requirements.push({ title, details, package, lineIndex: i });
595
+ requirements.push({ title, details, pkg, lineIndex: i });
710
596
  }
711
597
  }
712
598
 
@@ -837,7 +723,7 @@ async function showRequirementsTree() {
837
723
  tree.recycledReqs = await loadSection('recycled', 'ā™»ļø Recycled');
838
724
 
839
725
  let inTree = true;
840
- buildTree();
726
+ await buildTree();
841
727
 
842
728
  while (inTree) {
843
729
  console.clear();
@@ -944,7 +830,7 @@ async function showRequirementsTree() {
944
830
  if (tree.expanded[current.key]) {
945
831
  // Collapse expanded section
946
832
  tree.expanded[current.key] = false;
947
- buildTree();
833
+ await buildTree();
948
834
  } else if (current.level > 0) {
949
835
  // Go to parent
950
836
  for (let i = tree.selected - 1; i >= 0; i--) {
@@ -957,10 +843,12 @@ async function showRequirementsTree() {
957
843
  // At root level, go back to main menu
958
844
  inTree = false;
959
845
  }
960
- } else if (key.name === 'up') {
846
+ } else if (key.name === 'k' || key.name === 'up') {
961
847
  tree.selected = Math.max(0, tree.selected - 1);
962
- } else if (key.name === 'down') {
848
+ await buildTree();
849
+ } else if (key.name === 'j' || key.name === 'down') {
963
850
  tree.selected = Math.min(tree.items.length - 1, tree.selected + 1);
851
+ await buildTree();
964
852
  } else if (key.name === 'right' || key.name === 'return' || key.name === 'space') {
965
853
  const current = tree.items[tree.selected];
966
854
  if (!current) continue; // Safety check
@@ -977,19 +865,19 @@ async function showRequirementsTree() {
977
865
  } else if (current.key === 'recycled') {
978
866
  tree.recycledReqs = await loadSection(current.key, current.section);
979
867
  }
980
- buildTree();
868
+ await buildTree();
981
869
  } else {
982
870
  tree.expanded[current.key] = false;
983
- buildTree();
871
+ await buildTree();
984
872
  }
985
873
  } else if (current.type === 'requirement') {
986
874
  // Show requirement actions
987
875
  await showRequirementActions(current.req, current.sectionKey, tree);
988
- buildTree();
876
+ await buildTree();
989
877
  } else if (current.type === 'clarification') {
990
878
  // Show clarification requirement with questions
991
879
  await showClarificationActions(current.req, tree, loadClarification);
992
- buildTree();
880
+ await buildTree();
993
881
  } else if (current.type === 'verified') {
994
882
  // Show verified item details (read-only)
995
883
  console.clear();
@@ -1016,7 +904,7 @@ async function showRequirementsTree() {
1016
904
  await handleAddRequirement(current.key);
1017
905
  // Reload TODO section
1018
906
  tree.todoReqs = await loadSection('todo', 'ā³ Requirements not yet completed');
1019
- buildTree();
907
+ await buildTree();
1020
908
  }
1021
909
  } else if (key.name === 'r') {
1022
910
  const current = tree.items[tree.selected];
@@ -1030,15 +918,15 @@ async function showRequirementsTree() {
1030
918
  } else if (current.sectionKey === 'verify') {
1031
919
  tree.verifyReqs = await loadSection('verify', 'āœ… Verified by AI screenshot');
1032
920
  }
1033
- buildTree();
921
+ await buildTree();
1034
922
  } else if (current.type === 'clarification') {
1035
923
  await deleteClarification(current.req, tree);
1036
924
  tree.clarificationReqs = await loadClarification();
1037
- buildTree();
925
+ await buildTree();
1038
926
  } else if (current.type === 'recycled') {
1039
927
  await permanentlyDeleteRequirement(current.req, current.sectionKey, tree);
1040
928
  tree.recycledReqs = await loadSection('recycled', 'ā™»ļø Recycled');
1041
- buildTree();
929
+ await buildTree();
1042
930
  }
1043
931
  } else if (key.name === 'j') {
1044
932
  const current = tree.items[tree.selected];
@@ -1046,7 +934,7 @@ async function showRequirementsTree() {
1046
934
 
1047
935
  if (current.type === 'requirement') {
1048
936
  await moveRequirementDown(current.req, current.sectionKey, tree);
1049
- buildTree();
937
+ await buildTree();
1050
938
  // Move selection down to follow the item
1051
939
  if (tree.selected < tree.items.length - 1) {
1052
940
  tree.selected++;
@@ -1058,7 +946,7 @@ async function showRequirementsTree() {
1058
946
 
1059
947
  if (current.type === 'requirement') {
1060
948
  await moveRequirementUp(current.req, current.sectionKey, tree);
1061
- buildTree();
949
+ await buildTree();
1062
950
  // Move selection up to follow the item
1063
951
  if (tree.selected > 0) {
1064
952
  tree.selected--;
@@ -1070,7 +958,7 @@ async function showRequirementsTree() {
1070
958
 
1071
959
  if (current.type === 'requirement') {
1072
960
  await promoteRequirement(current.req, current.sectionKey, tree, loadSection, loadVerified);
1073
- buildTree();
961
+ await buildTree();
1074
962
  }
1075
963
  } else if (key.name === 'd') {
1076
964
  const current = tree.items[tree.selected];
@@ -1081,12 +969,12 @@ async function showRequirementsTree() {
1081
969
  await moveClarificationToTodo(current.req, tree);
1082
970
  tree.clarificationReqs = await loadClarification();
1083
971
  tree.todoReqs = await loadSection('todo', 'ā³ Requirements not yet completed');
1084
- buildTree();
972
+ await buildTree();
1085
973
  } else if (current.type === 'requirement' || current.type === 'verified') {
1086
974
  const sectionKey = current.type === 'verified' ? 'verified' : current.sectionKey;
1087
975
  const reqTitle = current.type === 'verified' ? current.label : current.req.title;
1088
976
  await demoteRequirement(reqTitle, sectionKey, tree, loadSection, loadVerified);
1089
- buildTree();
977
+ await buildTree();
1090
978
  }
1091
979
  }
1092
980
  }
@@ -1853,7 +1741,7 @@ async function permanentlyDeleteRequirement(req, sectionKey, tree) {
1853
1741
  const line = lines[i].trim();
1854
1742
  if (line.startsWith('###')) {
1855
1743
  const title = line.replace(/^###\s*/, '').trim();
1856
- if (title && title.includes(req.title)) {
1744
+ if (title && title === req.title) {
1857
1745
  requirementStartIndex = i;
1858
1746
  // Find the end of this requirement (next ### or ## header)
1859
1747
  for (let j = i + 1; j < lines.length; j++) {
@@ -1898,7 +1786,7 @@ async function moveRequirementToRecycled(reqPath, requirementTitle, fromSection)
1898
1786
  const line = lines[i].trim();
1899
1787
  if (line.startsWith('###')) {
1900
1788
  const title = line.replace(/^###\s*/, '').trim();
1901
- if (title && title.includes(requirementTitle)) {
1789
+ if (title && title === requirementTitle) {
1902
1790
  requirementStartIndex = i;
1903
1791
  // Find the end of this requirement (next ### or ## header)
1904
1792
  for (let j = i + 1; j < lines.length; j++) {
@@ -2219,7 +2107,7 @@ async function handleAddRequirement(type) {
2219
2107
  while (!done) {
2220
2108
  try {
2221
2109
  // Ask for package
2222
- const { package } = await inquirer.prompt([{
2110
+ const { package: pkg } = await inquirer.prompt([{
2223
2111
  type: 'list',
2224
2112
  name: 'package',
2225
2113
  message: `Package for requirement ${requirements.length + 1}:`,
@@ -2268,7 +2156,7 @@ async function handleAddRequirement(type) {
2268
2156
  }
2269
2157
 
2270
2158
  const description = descriptionLines.join('\n');
2271
- requirements.push({ name, package, description });
2159
+ requirements.push({ name, package: pkg, description });
2272
2160
  }
2273
2161
  } catch (err) {
2274
2162
  done = true;
@@ -2565,7 +2453,6 @@ async function showQuickMenu(items, initialSelectedIndex = 0) {
2565
2453
  if (selectedIndex >= items.length) selectedIndex = 0;
2566
2454
 
2567
2455
  let isFirstRender = true;
2568
- let lastLinesPrinted = 0;
2569
2456
 
2570
2457
  // Helper to calculate visual lines occupied by text
2571
2458
  const getVisualLineCount = (text) => {
@@ -2751,22 +2638,27 @@ async function showQuickMenu(items, initialSelectedIndex = 0) {
2751
2638
 
2752
2639
  // Arrow keys for navigation
2753
2640
  if (key.name === 'up') {
2754
- // Skip blank and info lines when navigating
2755
- let newIndex = selectedIndex > 0 ? selectedIndex - 1 : items.length - 1;
2756
- while ((items[newIndex].type === 'blank' || items[newIndex].type === 'info') && newIndex !== selectedIndex) {
2757
- newIndex = newIndex > 0 ? newIndex - 1 : items.length - 1;
2641
+ // Search backwards for the previous selectable item
2642
+ let testIndex = selectedIndex - 1;
2643
+ while (testIndex >= 0) {
2644
+ if (items[testIndex].type !== 'blank' && items[testIndex].type !== 'info') {
2645
+ selectedIndex = testIndex;
2646
+ await displayMenu();
2647
+ break;
2648
+ }
2649
+ testIndex--;
2758
2650
  }
2759
- selectedIndex = newIndex;
2760
- await displayMenu();
2761
2651
  } else if (key.name === 'down') {
2762
- // Skip blank and info lines when navigating
2763
- let newIndex = selectedIndex < items.length - 1 ? selectedIndex + 1 : 0;
2764
- while ((items[newIndex].type === 'blank' || items[newIndex].type === 'info') && newIndex !== selectedIndex) {
2765
- newIndex = newIndex < items.length - 1 ? newIndex + 1 : 0;
2652
+ // Search forwards for the next selectable item
2653
+ let testIndex = selectedIndex + 1;
2654
+ while (testIndex < items.length) {
2655
+ if (items[testIndex].type !== 'blank' && items[testIndex].type !== 'info') {
2656
+ selectedIndex = testIndex;
2657
+ await displayMenu();
2658
+ break;
2659
+ }
2660
+ testIndex++;
2766
2661
  }
2767
- selectedIndex = newIndex;
2768
- await displayMenu();
2769
-
2770
2662
  } else if (key.name === 'return' || key.name === 'right') {
2771
2663
  // Don't allow selecting blank or info lines
2772
2664
  if (items[selectedIndex].type !== 'blank' && items[selectedIndex].type !== 'info') {
@@ -2789,24 +2681,59 @@ async function showProviderManagerMenu() {
2789
2681
  let selectedIndex = 0;
2790
2682
  let dirty = false;
2791
2683
 
2792
- // Initialize ProviderManager to check rate limits
2793
- const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
2794
- const providerManager = new ProviderManager();
2684
+ const { fetchQuotaForAgent } = require('vibecodingmachine-core/src/quota-management');
2795
2685
 
2796
- const render = () => {
2686
+ const debugQuota = process.env.VCM_DEBUG_QUOTA === '1' || process.env.VCM_DEBUG_QUOTA === 'true';
2687
+
2688
+ const formatDuration = (ms) => {
2689
+ if (!ms || ms <= 0) return 'now';
2690
+ const totalSeconds = Math.ceil(ms / 1000);
2691
+ const hours = Math.floor(totalSeconds / 3600);
2692
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
2693
+ const seconds = totalSeconds % 60;
2694
+
2695
+ if (hours > 0) return `${hours}h ${minutes}m`;
2696
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
2697
+ return `${seconds}s`;
2698
+ };
2699
+
2700
+ const render = async () => {
2797
2701
  process.stdout.write('\x1Bc');
2798
2702
  console.log(chalk.bold.cyan('āš™ Provider Order & Availability\n'));
2703
+
2704
+ // Fetch quota info
2705
+ const sharedAuth = require('vibecodingmachine-core/src/auth/shared-auth-storage');
2706
+ const autoConfig = await getAutoConfig();
2707
+ const quotaInfo = await sharedAuth.canRunAutoMode();
2708
+ const remaining = Math.max(0, (quotaInfo.maxIterations || 10) - (quotaInfo.todayUsage || 0));
2709
+
2710
+ // Calculate time until reset (midnight)
2711
+ const now = new Date();
2712
+ const tonight = new Date(now);
2713
+ tonight.setHours(24, 0, 0, 0);
2714
+ const msUntilReset = tonight.getTime() - now.getTime();
2715
+ const hoursUntilReset = Math.floor(msUntilReset / (1000 * 60 * 60));
2716
+ const minsUntilReset = Math.floor((msUntilReset % (1000 * 60 * 60)) / (1000 * 60));
2717
+
2718
+ // Display quota as time-based instead of numeric (0/1 or 1/1 format)
2719
+ let quotaDisplay;
2720
+ if (remaining === 0) {
2721
+ // Rate limit active - show when it resets (in red)
2722
+ quotaDisplay = chalk.gray(' Overall Quota: ') + chalk.red(`ā° Rate limit resets in ${hoursUntilReset}h ${minsUntilReset}m`);
2723
+ } else {
2724
+ // Quota available - show when it resets (in green)
2725
+ quotaDisplay = chalk.gray(' Overall Quota: ') + chalk.green(`āœ“ Available (${remaining}/${quotaInfo.maxIterations})`) + chalk.gray(' • Resets in ') + chalk.cyan(`${hoursUntilReset}h ${minsUntilReset}m`);
2726
+ }
2727
+ console.log(quotaDisplay);
2799
2728
  console.log(chalk.gray(' ↑/↓ move selection j/k reorder e enable d disable Space toggle Enter save/select Esc cancel\n'));
2800
2729
 
2801
- order.forEach((id, idx) => {
2730
+ for (let idx = 0; idx < order.length; idx++) {
2731
+ const id = order[idx];
2802
2732
  const def = defMap.get(id);
2803
- if (!def) return;
2733
+ if (!def) continue;
2804
2734
  const isSelected = idx === selectedIndex;
2805
2735
  const isEnabled = enabled[id] !== false;
2806
2736
 
2807
- // Check for rate limits
2808
- const timeUntilReset = providerManager.getTimeUntilReset(id, def.model || id);
2809
-
2810
2737
  // Check for Kiro installation
2811
2738
  let isInstalled = true;
2812
2739
  if (id === 'kiro') {
@@ -2830,50 +2757,53 @@ async function showProviderManagerMenu() {
2830
2757
  const prefix = isSelected ? chalk.cyan('āÆ') : ' ';
2831
2758
  let line = `${prefix} ${statusEmoji} ${idx + 1}. ${def.name} ${chalk.gray(`(${def.id})`)} ${typeLabel}`;
2832
2759
 
2833
- // Add rate limit time if applicable
2834
- if (timeUntilReset && isEnabled) {
2835
- // Calculate reset time
2836
- const now = new Date();
2837
- const resetTime = new Date(now.getTime() + timeUntilReset);
2760
+ // Fetch and display specific quota for this agent
2761
+ try {
2762
+ // Find the active model for this provider if possible
2763
+ let model = def.defaultModel || id;
2764
+ if (id === 'groq') model = autoConfig.groqModel || model;
2765
+ else if (id === 'anthropic') model = autoConfig.anthropicModel || model;
2766
+ else if (id === 'ollama') {
2767
+ const preferredModel = autoConfig.llmModel && autoConfig.llmModel.includes('ollama/')
2768
+ ? autoConfig.llmModel.split('/')[1]
2769
+ : autoConfig.llmModel || autoConfig.aiderModel;
2770
+ model = (preferredModel && preferredModel !== id) ? preferredModel : model;
2771
+ }
2838
2772
 
2839
- // Format reset time in human-readable format
2840
- const timeFormatter = new Intl.DateTimeFormat('en-US', {
2841
- hour: 'numeric',
2842
- minute: '2-digit',
2843
- timeZoneName: 'short'
2844
- });
2845
- const timeStr = timeFormatter.format(resetTime);
2846
-
2847
- // Determine if it's today, tomorrow, or a specific date
2848
- const today = new Date();
2849
- today.setHours(0, 0, 0, 0);
2850
- const resetDate = new Date(resetTime);
2851
- resetDate.setHours(0, 0, 0, 0);
2852
- const tomorrow = new Date(today);
2853
- tomorrow.setDate(tomorrow.getDate() + 1);
2854
-
2855
- let dateStr = '';
2856
- if (resetDate.getTime() === today.getTime()) {
2857
- // Today - just show time
2858
- dateStr = timeStr;
2859
- } else if (resetDate.getTime() === tomorrow.getTime()) {
2860
- // Tomorrow
2861
- dateStr = `tomorrow, ${timeStr}`;
2862
- } else {
2863
- // Specific date - show date and time
2864
- const dateFormatter = new Intl.DateTimeFormat('en-US', {
2865
- month: 'short',
2866
- day: 'numeric',
2867
- year: 'numeric'
2868
- });
2869
- dateStr = `${timeStr} ${dateFormatter.format(resetTime)}`;
2773
+ const agentId = `${id}:${model}`;
2774
+ const quota = await fetchQuotaForAgent(agentId);
2775
+
2776
+ if (debugQuota) {
2777
+ const resetMs = quota?.resetsAt ? (new Date(quota.resetsAt).getTime() - Date.now()) : null;
2778
+ console.error(`[VCM_DEBUG_QUOTA] provider=${id} model=${model} type=${quota?.type} remaining=${quota?.remaining} limit=${quota?.limit} resetsAt=${quota?.resetsAt ? new Date(quota.resetsAt).toISOString() : 'null'} resetIn=${resetMs !== null ? formatDuration(resetMs) : 'null'}`);
2870
2779
  }
2871
2780
 
2872
- line += ` ${chalk.red('rate limited until ' + dateStr)}`;
2781
+ if (quota.type === 'infinite') {
2782
+ line += ` ${chalk.gray('[Quota: Infinite]')}`;
2783
+ } else if (quota.type === 'rate-limit') {
2784
+ if (quota.isExceeded()) {
2785
+ if (quota.resetsAt) {
2786
+ const msUntilReset = new Date(quota.resetsAt).getTime() - Date.now();
2787
+ line += ` ${chalk.red(`[ā³ resets in ${formatDuration(msUntilReset)}]`)}`;
2788
+ } else {
2789
+ line += ` ${chalk.red('[Rate limited]')}`;
2790
+ }
2791
+ } else {
2792
+ // Show time until rate limit starts (when it resets)
2793
+ if (quota.resetsAt) {
2794
+ const msUntilReset = new Date(quota.resetsAt).getTime() - Date.now();
2795
+ line += ` ${chalk.green(`[āœ“ available • resets in ${formatDuration(msUntilReset)}]`)}`;
2796
+ } else {
2797
+ line += ` ${chalk.green('[Available]')}`;
2798
+ }
2799
+ }
2800
+ }
2801
+ } catch (e) {
2802
+ // Silently skip if quota fetch fails
2873
2803
  }
2874
2804
 
2875
2805
  console.log(line);
2876
- });
2806
+ }
2877
2807
 
2878
2808
  console.log();
2879
2809
  if (dirty) {
@@ -2883,7 +2813,12 @@ async function showProviderManagerMenu() {
2883
2813
  }
2884
2814
  };
2885
2815
 
2886
- return new Promise((resolve) => {
2816
+ if (process.env.VCM_RENDER_ONCE === '1' || process.env.VCM_RENDER_ONCE === 'true') {
2817
+ await render();
2818
+ return;
2819
+ }
2820
+
2821
+ return new Promise(async (resolve) => {
2887
2822
  const cleanup = () => {
2888
2823
  if (process.stdin.isTTY && process.stdin.setRawMode) {
2889
2824
  process.stdin.setRawMode(false);
@@ -2926,15 +2861,15 @@ async function showProviderManagerMenu() {
2926
2861
  resolve(null);
2927
2862
  };
2928
2863
 
2929
- const moveSelection = (delta) => {
2864
+ const moveSelection = async (delta) => {
2930
2865
  const next = selectedIndex + delta;
2931
2866
  if (next >= 0 && next < order.length) {
2932
2867
  selectedIndex = next;
2933
- render();
2868
+ await render();
2934
2869
  }
2935
2870
  };
2936
2871
 
2937
- const reorder = (delta) => {
2872
+ const reorder = async (delta) => {
2938
2873
  const target = selectedIndex + delta;
2939
2874
  if (target < 0 || target >= order.length) return;
2940
2875
  const temp = order[selectedIndex];
@@ -2942,17 +2877,17 @@ async function showProviderManagerMenu() {
2942
2877
  order[target] = temp;
2943
2878
  selectedIndex = target;
2944
2879
  dirty = true;
2945
- render();
2880
+ await render();
2946
2881
  };
2947
2882
 
2948
- const toggle = (value) => {
2883
+ const toggle = async (value) => {
2949
2884
  const id = order[selectedIndex];
2950
2885
  enabled[id] = value;
2951
2886
  dirty = true;
2952
- render();
2887
+ await render();
2953
2888
  };
2954
2889
 
2955
- const onKeypress = (str, key = {}) => {
2890
+ const onKeypress = async (str, key = {}) => {
2956
2891
  if (key.ctrl && key.name === 'c') {
2957
2892
  cancel();
2958
2893
  return;
@@ -2960,25 +2895,25 @@ async function showProviderManagerMenu() {
2960
2895
 
2961
2896
  switch (key.name) {
2962
2897
  case 'up':
2963
- moveSelection(-1);
2898
+ await moveSelection(-1);
2964
2899
  break;
2965
2900
  case 'down':
2966
- moveSelection(1);
2901
+ await moveSelection(1);
2967
2902
  break;
2968
2903
  case 'j':
2969
- reorder(1);
2904
+ await reorder(1);
2970
2905
  break;
2971
2906
  case 'k':
2972
- reorder(-1);
2907
+ await reorder(-1);
2973
2908
  break;
2974
2909
  case 'e':
2975
- toggle(true);
2910
+ await toggle(true);
2976
2911
  break;
2977
2912
  case 'd':
2978
- toggle(false);
2913
+ await toggle(false);
2979
2914
  break;
2980
2915
  case 'space':
2981
- toggle(!(enabled[order[selectedIndex]] !== false));
2916
+ await toggle(!(enabled[order[selectedIndex]] !== false));
2982
2917
  break;
2983
2918
  case 'return':
2984
2919
  saveAndExit(order[selectedIndex]);
@@ -3000,11 +2935,11 @@ async function showProviderManagerMenu() {
3000
2935
  process.stdin.on('keypress', onKeypress);
3001
2936
  process.stdin.resume();
3002
2937
 
3003
- render();
2938
+ await render();
3004
2939
  });
3005
2940
  }
3006
2941
 
3007
- async function showSettings() {
2942
+ /* async function showSettings() {
3008
2943
  console.log(chalk.bold.cyan('\nāš™ļø Settings\n'));
3009
2944
 
3010
2945
  const { setConfigValue } = require('vibecodingmachine-core');
@@ -3096,7 +3031,7 @@ async function showSettings() {
3096
3031
  async function showCloudSyncMenu() {
3097
3032
  console.clear();
3098
3033
  console.log(chalk.bold.cyan('\nā˜ļø Cloud Sync Management\n'));
3099
-
3034
+
3100
3035
  // Check if cloud sync is configured
3101
3036
  try {
3102
3037
  const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
@@ -3110,7 +3045,7 @@ async function showCloudSyncMenu() {
3110
3045
  console.log(chalk.gray('2. Add AWS configuration to your .env file'));
3111
3046
  console.log(chalk.gray('3. Restart vcm\n'));
3112
3047
  console.log(chalk.gray('For more info, see: ') + chalk.cyan('docs/CLOUD_SYNC.md\n'));
3113
-
3048
+
3114
3049
  console.log(chalk.gray('Press Enter to return to main menu...'));
3115
3050
  await new Promise(resolve => {
3116
3051
  const rl = readline.createInterface({
@@ -3124,10 +3059,10 @@ async function showCloudSyncMenu() {
3124
3059
  });
3125
3060
  return;
3126
3061
  }
3127
-
3062
+
3128
3063
  const computerCommands = require('../commands/computers');
3129
3064
  const syncCommands = require('../commands/sync');
3130
-
3065
+
3131
3066
  const choices = [
3132
3067
  { name: 'šŸ“Š View All Computers', value: 'computers' },
3133
3068
  { name: 'šŸ–„ļø Manage Another Computer\'s Requirements', value: 'manage-remote' },
@@ -3139,7 +3074,7 @@ async function showCloudSyncMenu() {
3139
3074
  { name: 'šŸŽÆ Update Focus Area', value: 'update-focus' },
3140
3075
  { name: chalk.gray('← Back to Main Menu'), value: 'back' }
3141
3076
  ];
3142
-
3077
+
3143
3078
  const { action } = await inquirer.prompt([
3144
3079
  {
3145
3080
  type: 'list',
@@ -3148,7 +3083,7 @@ async function showCloudSyncMenu() {
3148
3083
  choices: choices
3149
3084
  }
3150
3085
  ]);
3151
-
3086
+
3152
3087
  switch (action) {
3153
3088
  case 'computers':
3154
3089
  try {
@@ -3170,23 +3105,23 @@ async function showCloudSyncMenu() {
3170
3105
  });
3171
3106
  await showCloudSyncMenu();
3172
3107
  break;
3173
-
3108
+
3174
3109
  case 'manage-remote':
3175
3110
  try {
3176
3111
  // First, get list of computers
3177
3112
  const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
3178
3113
  const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
3179
-
3114
+
3180
3115
  const syncEngine = new SyncEngine();
3181
3116
  await syncEngine.initialize();
3182
-
3117
+
3183
3118
  const tableName = 'vibecodingmachine-computers';
3184
3119
  const command = new ScanCommand({ TableName: tableName });
3185
3120
  const response = await syncEngine.dynamoClient.send(command);
3186
3121
  const computers = response.Items || [];
3187
-
3122
+
3188
3123
  syncEngine.stop();
3189
-
3124
+
3190
3125
  if (computers.length === 0) {
3191
3126
  console.log(chalk.yellow('\n⚠ No computers registered yet.\n'));
3192
3127
  } else {
@@ -3196,7 +3131,7 @@ async function showCloudSyncMenu() {
3196
3131
  value: c.computerId
3197
3132
  }));
3198
3133
  computerChoices.push({ name: chalk.gray('← Cancel'), value: null });
3199
-
3134
+
3200
3135
  const { selectedComputer } = await inquirer.prompt([
3201
3136
  {
3202
3137
  type: 'list',
@@ -3205,7 +3140,7 @@ async function showCloudSyncMenu() {
3205
3140
  choices: computerChoices
3206
3141
  }
3207
3142
  ]);
3208
-
3143
+
3209
3144
  if (selectedComputer) {
3210
3145
  const remoteReqCommands = require('../commands/requirements-remote');
3211
3146
  await remoteReqCommands.manageRemoteRequirements(selectedComputer);
@@ -3216,7 +3151,7 @@ async function showCloudSyncMenu() {
3216
3151
  }
3217
3152
  await showCloudSyncMenu();
3218
3153
  break;
3219
-
3154
+
3220
3155
  case 'sync-now':
3221
3156
  try {
3222
3157
  await syncCommands.syncNow();
@@ -3236,7 +3171,7 @@ async function showCloudSyncMenu() {
3236
3171
  });
3237
3172
  await showCloudSyncMenu();
3238
3173
  break;
3239
-
3174
+
3240
3175
  case 'sync-status':
3241
3176
  try {
3242
3177
  await syncCommands.syncStatus();
@@ -3256,7 +3191,7 @@ async function showCloudSyncMenu() {
3256
3191
  });
3257
3192
  await showCloudSyncMenu();
3258
3193
  break;
3259
-
3194
+
3260
3195
  case 'sync-history':
3261
3196
  try {
3262
3197
  await syncCommands.viewHistory({ limit: 50 });
@@ -3276,7 +3211,7 @@ async function showCloudSyncMenu() {
3276
3211
  });
3277
3212
  await showCloudSyncMenu();
3278
3213
  break;
3279
-
3214
+
3280
3215
  case 'sync-queue':
3281
3216
  try {
3282
3217
  await syncCommands.viewQueue();
@@ -3296,7 +3231,7 @@ async function showCloudSyncMenu() {
3296
3231
  });
3297
3232
  await showCloudSyncMenu();
3298
3233
  break;
3299
-
3234
+
3300
3235
  case 'register':
3301
3236
  try {
3302
3237
  const { focusArea } = await inquirer.prompt([
@@ -3324,7 +3259,7 @@ async function showCloudSyncMenu() {
3324
3259
  });
3325
3260
  await showCloudSyncMenu();
3326
3261
  break;
3327
-
3262
+
3328
3263
  case 'update-focus':
3329
3264
  try {
3330
3265
  const { newFocus } = await inquirer.prompt([
@@ -3353,7 +3288,7 @@ async function showCloudSyncMenu() {
3353
3288
  });
3354
3289
  await showCloudSyncMenu();
3355
3290
  break;
3356
-
3291
+
3357
3292
  case 'back':
3358
3293
  // Return to main menu
3359
3294
  break;
@@ -3400,6 +3335,11 @@ async function startInteractive() {
3400
3335
 
3401
3336
  await showWelcomeScreen();
3402
3337
 
3338
+ if (process.env.VCM_OPEN_PROVIDER_MENU === '1' || process.env.VCM_OPEN_PROVIDER_MENU === 'true') {
3339
+ await showProviderManagerMenu();
3340
+ return;
3341
+ }
3342
+
3403
3343
  let exit = false;
3404
3344
  let lastSelectedIndex = 0; // Track last selected menu item
3405
3345
  while (!exit) {
@@ -3572,21 +3512,31 @@ async function startInteractive() {
3572
3512
  value: 'setting:hostname'
3573
3513
  });
3574
3514
 
3515
+ // Add Stages configuration
3516
+ const { getStages } = require('./config');
3517
+ const configuredStages = await getStages();
3518
+ const stagesCount = configuredStages.length;
3519
+ items.push({
3520
+ type: 'setting',
3521
+ name: ` └─ Configure Stages: ${chalk.cyan(stagesCount + ' stages')}`,
3522
+ value: 'setting:stages'
3523
+ });
3524
+
3575
3525
  // Cloud Sync Status
3576
3526
  try {
3577
3527
  const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
3578
3528
  const syncEngine = new SyncEngine();
3579
-
3529
+
3580
3530
  // Try to initialize, but don't fail if AWS not configured
3581
3531
  try {
3582
3532
  await syncEngine.initialize();
3583
3533
  const syncStatus = syncEngine.getStatus();
3584
3534
  syncEngine.stop();
3585
-
3535
+
3586
3536
  const onlineIcon = syncStatus.isOnline ? chalk.green('ā—') : chalk.red('ā—');
3587
3537
  const onlineText = syncStatus.isOnline ? 'Online' : 'Offline';
3588
3538
  const queueText = syncStatus.queuedChanges > 0 ? chalk.yellow(` (${syncStatus.queuedChanges} queued)`) : '';
3589
-
3539
+
3590
3540
  items.push({
3591
3541
  type: 'setting',
3592
3542
  name: `Cloud Sync: ${onlineIcon} ${onlineText}${queueText}`,
@@ -3739,7 +3689,6 @@ async function startInteractive() {
3739
3689
  const path = require('path');
3740
3690
  const os = require('os');
3741
3691
  const yaml = require('js-yaml');
3742
- const { spawn } = require('child_process');
3743
3692
  const configPath = path.join(os.homedir(), '.continue', 'config.yaml');
3744
3693
 
3745
3694
  if (fs.existsSync(configPath)) {
@@ -3930,6 +3879,53 @@ async function startInteractive() {
3930
3879
  await showWelcomeScreen();
3931
3880
  break;
3932
3881
  }
3882
+ case 'setting:stages': {
3883
+ // Configure stages
3884
+ const { getStages, setStages, DEFAULT_STAGES } = require('./config');
3885
+ const inquirer = require('inquirer');
3886
+
3887
+ const currentStages = await getStages();
3888
+
3889
+ console.log(chalk.cyan('\nšŸ”Ø Configure Workflow Stages\n'));
3890
+ console.log(chalk.gray('Select the stages you want to include in the auto-mode workflow.'));
3891
+ console.log(chalk.gray('Stages will be executed in the order shown.\n'));
3892
+
3893
+ const { selectedStages } = await inquirer.prompt([
3894
+ {
3895
+ type: 'checkbox',
3896
+ name: 'selectedStages',
3897
+ message: 'Select stages:',
3898
+ choices: DEFAULT_STAGES.map(stage => ({
3899
+ name: stage,
3900
+ checked: currentStages.includes(stage)
3901
+ })),
3902
+ validate: (answer) => {
3903
+ if (answer.length < 1) {
3904
+ return 'You must select at least one stage.';
3905
+ }
3906
+ return true;
3907
+ },
3908
+ loop: false,
3909
+ pageSize: 15
3910
+ }
3911
+ ]);
3912
+
3913
+ // Preserve order from DEFAULT_STAGES for selected items
3914
+ // This ensures stages always run in the correct logical order
3915
+ const newStages = DEFAULT_STAGES.filter(stage => selectedStages.includes(stage));
3916
+
3917
+ await setStages(newStages);
3918
+ console.log(chalk.green('\nāœ“'), `Stages configuration updated: ${newStages.join(' → ')}\n`);
3919
+
3920
+ const { continue: _ } = await inquirer.prompt([{
3921
+ type: 'input',
3922
+ name: 'continue',
3923
+ message: 'Press Enter to return to menu...'
3924
+ }]);
3925
+
3926
+ await showWelcomeScreen();
3927
+ break;
3928
+ }
3933
3929
  case 'setting:auto-start': {
3934
3930
  try {
3935
3931
  console.log(chalk.bold.cyan('\nšŸš€ Starting Auto Mode...\n'));
@@ -4116,7 +4112,7 @@ async function startInteractive() {
4116
4112
  case 'repo:init':
4117
4113
  await repo.initRepo();
4118
4114
  break;
4119
-
4115
+
4120
4116
  case 'computers:list': {
4121
4117
  const computerCommands = require('../commands/computers');
4122
4118
  await computerCommands.listComputers();
@@ -4134,7 +4130,7 @@ async function startInteractive() {
4134
4130
  await showWelcomeScreen();
4135
4131
  break;
4136
4132
  }
4137
-
4133
+
4138
4134
  case 'sync:now': {
4139
4135
  const syncCommands = require('../commands/sync');
4140
4136
  await syncCommands.syncNow();
@@ -4152,13 +4148,13 @@ async function startInteractive() {
4152
4148
  await showWelcomeScreen();
4153
4149
  break;
4154
4150
  }
4155
-
4151
+
4156
4152
  case 'setting:cloud-sync': {
4157
4153
  await showCloudSyncMenu();
4158
4154
  await showWelcomeScreen();
4159
4155
  break;
4160
4156
  }
4161
-
4157
+
4162
4158
  case 'setting:cloud-sync-setup': {
4163
4159
  console.clear();
4164
4160
  console.log(chalk.bold.cyan('\nā˜ļø Cloud Sync Setup\n'));
@@ -4168,7 +4164,7 @@ async function startInteractive() {
4168
4164
  console.log(chalk.gray('2. Add AWS configuration to your .env file'));
4169
4165
  console.log(chalk.gray('3. Register this computer with: ') + chalk.cyan('vcm computer:register "<focus>"'));
4170
4166
  console.log(chalk.gray('\nFor more info, see: ') + chalk.cyan('docs/CLOUD_SYNC.md\n'));
4171
-
4167
+
4172
4168
  console.log(chalk.gray('Press Enter to continue...'));
4173
4169
  await new Promise(resolve => {
4174
4170
  const rl = readline.createInterface({