vibecodingmachine-cli 2025.12.25-1541 → 2026.1.3-2209

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.
@@ -5,6 +5,7 @@ const path = require('path');
5
5
  const os = require('os');
6
6
  const fs = require('fs-extra');
7
7
  const readline = require('readline');
8
+ const { execSync } = require('child_process');
8
9
  const repo = require('../commands/repo');
9
10
  const auto = require('../commands/auto');
10
11
  const status = require('../commands/status');
@@ -22,12 +23,94 @@ const {
22
23
  detectLocale,
23
24
  setLocale
24
25
  } = require('vibecodingmachine-core');
26
+ const { promptWithDefaultsOnce } = require('./prompt-helper');
25
27
 
26
28
  // Initialize locale detection for interactive mode
27
29
  const detectedLocale = detectLocale();
28
30
  setLocale(detectedLocale);
29
31
  const pkg = require('../../package.json');
30
32
 
33
+ async function checkAppOrBinary(names = [], binaries = []) {
34
+ // names: app bundle base names (e.g., 'Cursor' -> /Applications/Cursor.app)
35
+ // binaries: CLI binary names to check on PATH (e.g., 'code')
36
+ const platform = os.platform();
37
+ // Check common application directories
38
+ if (platform === 'darwin') {
39
+ const appDirs = ['/Applications', path.join(os.homedir(), 'Applications')];
40
+ for (const appName of names) {
41
+ for (const dir of appDirs) {
42
+ try {
43
+ const p = path.join(dir, `${appName}.app`);
44
+ if (await fs.pathExists(p)) {
45
+ // Ensure this is a real application bundle (has Contents/MacOS executable)
46
+ try {
47
+ const macosDir = path.join(p, 'Contents', 'MacOS');
48
+ const exists = await fs.pathExists(macosDir);
49
+ if (exists) {
50
+ const files = await fs.readdir(macosDir);
51
+ if (files && files.length > 0) {
52
+ // Prefer to ensure the app is usable: use spctl to assess, fallback to quarantine xattr
53
+ try {
54
+ // spctl returns non-zero on rejected/invalid apps
55
+ execSync(`spctl --assess -v "${p}"`, { stdio: 'ignore', timeout: 5000 });
56
+ // additionally validate codesign quickly (timeout to avoid hangs)
57
+ try {
58
+ execSync(`codesign -v --deep --strict "${p}"`, { stdio: 'ignore', timeout: 5000 });
59
+ return true;
60
+ } catch (csErr) {
61
+ // codesign failed or timed out — treat as not usable/damaged
62
+ return false;
63
+ }
64
+ } catch (e) {
65
+ // spctl failed or timed out — check if app has quarantine attribute
66
+ try {
67
+ const out = execSync(`xattr -p com.apple.quarantine "${p}" 2>/dev/null || true`, { encoding: 'utf8' }).trim();
68
+ if (!out) {
69
+ // no quarantine attribute but spctl failed — be conservative and treat as not installed
70
+ return false;
71
+ }
72
+ // If quarantine attribute exists, treat as not installed (damaged/not allowed)
73
+ return false;
74
+ } catch (e2) {
75
+ return false;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ } catch (e) {
81
+ // if we can't stat inside, be conservative and continue searching
82
+ }
83
+ }
84
+ } catch (e) {
85
+ /* ignore */
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ // Check PATH for known binaries
92
+ for (const bin of binaries) {
93
+ try {
94
+ execSync(`which ${bin}`, { stdio: 'ignore' });
95
+ return true;
96
+ } catch (e) {
97
+ /* not found */
98
+ }
99
+ }
100
+
101
+ // Check common Homebrew bin locations
102
+ const brewPaths = ['/opt/homebrew/bin', '/usr/local/bin'];
103
+ for (const bin of binaries) {
104
+ for (const brew of brewPaths) {
105
+ try {
106
+ if (await fs.pathExists(path.join(brew, bin))) return true;
107
+ } catch (e) { /* ignore */ }
108
+ }
109
+ }
110
+
111
+ return false;
112
+ }
113
+
31
114
  /**
32
115
  * Translate workflow stage names
33
116
  */
@@ -47,6 +130,78 @@ function translateStage(stage) {
47
130
  return key ? t(key) : stage;
48
131
  }
49
132
 
133
+ function normalizeProjectDirName(input) {
134
+ const s = String(input || '').trim().toLowerCase();
135
+ const replaced = s.replace(/\s+/g, '-');
136
+ const cleaned = replaced.replace(/[^a-z0-9._-]/g, '-');
137
+ const collapsed = cleaned.replace(/-+/g, '-').replace(/^[-._]+|[-._]+$/g, '');
138
+ return collapsed || 'my-project';
139
+ }
140
+
141
+ async function bootstrapProjectIfInHomeDir() {
142
+ let cwdResolved = path.resolve(process.cwd());
143
+ let homeResolved = path.resolve(os.homedir());
144
+
145
+ try {
146
+ cwdResolved = await fs.realpath(cwdResolved);
147
+ } catch (err) {
148
+ // Ignore
149
+ }
150
+
151
+ try {
152
+ homeResolved = await fs.realpath(homeResolved);
153
+ } catch (err) {
154
+ // Ignore
155
+ }
156
+
157
+ if (cwdResolved !== homeResolved) {
158
+ return;
159
+ }
160
+
161
+ const codeDir = path.join(homeResolved, 'code');
162
+ const codeDirExists = await fs.pathExists(codeDir);
163
+
164
+ if (!codeDirExists) {
165
+ const { shouldCreateCodeDir } = await inquirer.prompt([
166
+ {
167
+ type: 'confirm',
168
+ name: 'shouldCreateCodeDir',
169
+ message: `No code directory found at ${codeDir}. Would you like to create it?`,
170
+ default: true
171
+ }
172
+ ]);
173
+
174
+ if (!shouldCreateCodeDir) {
175
+ return;
176
+ }
177
+
178
+ await fs.ensureDir(codeDir);
179
+ }
180
+
181
+ const { projectName } = await inquirer.prompt([
182
+ {
183
+ type: 'input',
184
+ name: 'projectName',
185
+ message: 'What is the project name you want to create?',
186
+ validate: (value) => {
187
+ const trimmed = String(value || '').trim();
188
+ return trimmed.length > 0 || 'Please enter a project name.';
189
+ }
190
+ }
191
+ ]);
192
+
193
+ const dirName = normalizeProjectDirName(projectName);
194
+ const projectDir = path.join(codeDir, dirName);
195
+ await fs.ensureDir(projectDir);
196
+
197
+ process.chdir(projectDir);
198
+ try {
199
+ await writeConfig({ ...(await readConfig()), repoPath: projectDir });
200
+ } catch (err) {
201
+ // Best-effort: interactive mode will still use process.cwd()
202
+ }
203
+ }
204
+
50
205
  /**
51
206
  * Format IDE name for display
52
207
  * @param {string} ide - Internal IDE identifier
@@ -248,6 +403,39 @@ async function countRequirements() {
248
403
  }
249
404
  }
250
405
 
406
+ async function getSyncStatus() {
407
+ try {
408
+ const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
409
+ const syncEngine = new SyncEngine();
410
+ await syncEngine.initialize();
411
+ const status = syncEngine.getStatus();
412
+ syncEngine.stop();
413
+
414
+ if (!status.isOnline && status.queuedChanges > 0) {
415
+ return `[Offline: ${status.queuedChanges} queued]`;
416
+ } else if (!status.isOnline) {
417
+ return '[Offline]';
418
+ } else if (status.isSyncing) {
419
+ return '[Syncing...]';
420
+ } else if (status.lastSyncTime) {
421
+ const timeSinceSync = Date.now() - status.lastSyncTime;
422
+ const minutesAgo = Math.floor(timeSinceSync / (1000 * 60));
423
+ if (minutesAgo < 1) {
424
+ return '[Sync: ✓ just now]';
425
+ } else if (minutesAgo < 60) {
426
+ return `[Sync: ✓ ${minutesAgo}m ago]`;
427
+ } else {
428
+ const hoursAgo = Math.floor(minutesAgo / 60);
429
+ return `[Sync: ✓ ${hoursAgo}h ago]`;
430
+ }
431
+ } else {
432
+ return '[Never synced]';
433
+ }
434
+ } catch (error) {
435
+ return '[Sync unavailable]';
436
+ }
437
+ }
438
+
251
439
  async function getCurrentProgress() {
252
440
  try {
253
441
  const { getRequirementsPath } = require('vibecodingmachine-core');
@@ -333,6 +521,22 @@ async function showWelcomeScreen() {
333
521
  // Display repository and system info
334
522
  console.log();
335
523
  console.log(chalk.gray(t('system.repo').padEnd(25)), formatPath(repoPath));
524
+
525
+ // Display git branch if in a git repo
526
+ try {
527
+ const { isGitRepo, getCurrentBranch, hasUncommittedChanges } = require('vibecodingmachine-core');
528
+ if (isGitRepo(repoPath)) {
529
+ const branch = getCurrentBranch(repoPath);
530
+ if (branch) {
531
+ const isDirty = hasUncommittedChanges(repoPath);
532
+ const branchDisplay = isDirty ? `${chalk.cyan(branch)} ${chalk.yellow(t('system.git.status.dirty'))}` : chalk.cyan(branch);
533
+ console.log(chalk.gray(t('system.git.branch').padEnd(25)), branchDisplay);
534
+ }
535
+ }
536
+ } catch (error) {
537
+ // Ignore git display errors
538
+ }
539
+
336
540
  console.log(chalk.gray(t('system.computer.name').padEnd(25)), chalk.cyan(hostname));
337
541
 
338
542
  // Display auto mode progress if running
@@ -432,16 +636,16 @@ async function showRequirementsTree() {
432
636
  };
433
637
 
434
638
  // Create localized labels
435
- const localizedTodoLabel = todoCount > 0 ?
436
- `⏳ ${t('requirements.section.todo')} (${todoCount} - ${Math.round((todoCount / total) * 100)}%)` :
639
+ const localizedTodoLabel = todoCount > 0 ?
640
+ `⏳ ${t('requirements.section.todo')} (${todoCount} - ${Math.round((todoCount / total) * 100)}%)` :
437
641
  `⏳ ${t('requirements.section.todo')} (0 - 0%)`;
438
-
439
- const localizedToVerifyLabel = toVerifyCount > 0 ?
440
- `✅ ${t('requirements.section.to.verify')} (${toVerifyCount} - ${Math.round((toVerifyCount / total) * 100)}%)` :
642
+
643
+ const localizedToVerifyLabel = toVerifyCount > 0 ?
644
+ `✅ ${t('requirements.section.to.verify')} (${toVerifyCount} - ${Math.round((toVerifyCount / total) * 100)}%)` :
441
645
  `✅ ${t('requirements.section.to.verify')} (0 - 0%)`;
442
-
443
- const localizedVerifiedLabel = verifiedCount > 0 ?
444
- `🎉 ${t('requirements.section.verified')} (${verifiedCount} - ${Math.round((verifiedCount / total) * 100)}%)` :
646
+
647
+ const localizedVerifiedLabel = verifiedCount > 0 ?
648
+ `🎉 ${t('requirements.section.verified')} (${verifiedCount} - ${Math.round((verifiedCount / total) * 100)}%)` :
445
649
  `🎉 ${t('requirements.section.verified')} (0 - 0%)`;
446
650
 
447
651
  const verifiedReqs = tree.verifiedReqs || [];
@@ -450,6 +654,10 @@ async function showRequirementsTree() {
450
654
  const todoReqs = tree.todoReqs || [];
451
655
  const recycledReqs = tree.recycledReqs || [];
452
656
 
657
+ // Calculate percentages for clarification and recycled sections
658
+ const clarificationPercent = total > 0 ? Math.round((clarificationReqs.length / total) * 100) : 0;
659
+ const recycledPercent = total > 0 ? Math.round((recycledReqs.length / total) * 100) : 0;
660
+
453
661
  // VERIFIED section (first) - only show if has requirements
454
662
  if (verifiedReqs.length > 0 || verifiedCount > 0) {
455
663
  tree.items.push({ level: 1, type: 'section', label: localizedVerifiedLabel, key: 'verified' });
@@ -708,49 +916,99 @@ async function showRequirementsTree() {
708
916
 
709
917
  let inSection = false;
710
918
  const requirements = [];
711
- let currentReq = null;
712
919
 
713
920
  for (let i = 0; i < lines.length; i++) {
714
921
  const line = lines[i];
922
+ const trimmed = line.trim();
715
923
 
716
- if (line.includes(' Requirements needing manual feedback')) {
924
+ // Check if we're entering the clarification section
925
+ if (trimmed.includes('❓ Requirements needing manual feedback')) {
717
926
  inSection = true;
718
927
  continue;
719
928
  }
720
929
 
721
- if (inSection && line.startsWith('##') && !line.includes('❓ Requirements needing manual feedback')) {
722
- if (currentReq) requirements.push(currentReq);
930
+ // Check if we're leaving the section (hit another ## section)
931
+ if (inSection && trimmed.startsWith('##') && !trimmed.startsWith('###')) {
723
932
  break;
724
933
  }
725
934
 
726
- if (inSection && line.trim().startsWith('- ')) {
727
- if (currentReq) requirements.push(currentReq);
728
-
729
- const title = line.trim().substring(2);
730
- currentReq = { title, questions: [], findings: null, lineIndex: i };
731
- } else if (inSection && currentReq && line.trim()) {
732
- // Check for AI findings section
733
- if (line.trim().startsWith('**AI found in codebase:**')) {
734
- continue; // Skip the header, capture next line as findings
735
- } else if (!currentReq.findings && !line.trim().startsWith('**Clarifying questions:**') && !line.trim().match(/^\d+\./)) {
736
- // This is the findings content (comes after "AI found in codebase:")
737
- currentReq.findings = line.trim();
738
- } else if (line.trim().startsWith('**Clarifying questions:**')) {
739
- continue; // Skip the questions header
740
- } else if (line.trim().match(/^\d+\./)) {
741
- // This is a question
742
- currentReq.questions.push({ question: line.trim(), response: null });
743
- } else if (currentReq.questions.length > 0) {
744
- // This might be a response to the last question
745
- const lastQuestion = currentReq.questions[currentReq.questions.length - 1];
746
- if (!lastQuestion.response) {
747
- lastQuestion.response = line.trim();
935
+ // Read requirements in new format (### header)
936
+ if (inSection && trimmed.startsWith('###')) {
937
+ const title = trimmed.replace(/^###\s*/, '').trim();
938
+
939
+ // Skip empty titles
940
+ if (!title || title.length === 0) {
941
+ continue;
942
+ }
943
+
944
+ const details = [];
945
+ const questions = [];
946
+ let pkg = null;
947
+ let findings = null;
948
+ let currentQuestion = null;
949
+
950
+ // Read package, description, and clarifying questions
951
+ for (let j = i + 1; j < lines.length; j++) {
952
+ const nextLine = lines[j].trim();
953
+
954
+ // Stop if we hit another requirement or section
955
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
956
+ break;
957
+ }
958
+
959
+ // Check for PACKAGE line
960
+ if (nextLine.startsWith('PACKAGE:')) {
961
+ pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
962
+ }
963
+ // Check for AI findings
964
+ else if (nextLine.startsWith('**AI found in codebase:**')) {
965
+ // Next line will be the findings
966
+ continue;
967
+ }
968
+ else if (nextLine.startsWith('**What went wrong')) {
969
+ // Description line
970
+ details.push(nextLine);
971
+ }
972
+ else if (nextLine.startsWith('**Clarifying questions:**')) {
973
+ // Start of questions section
974
+ continue;
975
+ }
976
+ else if (nextLine.match(/^\d+\./)) {
977
+ // Save previous question if exists
978
+ if (currentQuestion) {
979
+ questions.push(currentQuestion);
980
+ }
981
+ // This is a new question
982
+ currentQuestion = { question: nextLine, response: null };
748
983
  }
984
+ else if (currentQuestion && nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
985
+ // This might be a response to the current question or description/findings
986
+ if (!findings && !currentQuestion.response && questions.length === 0 && !nextLine.match(/^\d+\./)) {
987
+ // This is findings content
988
+ findings = nextLine;
989
+ } else if (currentQuestion && !currentQuestion.response) {
990
+ // This is a response to the current question
991
+ currentQuestion.response = nextLine;
992
+ } else {
993
+ // Description line
994
+ details.push(nextLine);
995
+ }
996
+ }
997
+ else if (nextLine && !nextLine.startsWith('PACKAGE:') && !nextLine.startsWith('**')) {
998
+ // Description line
999
+ details.push(nextLine);
1000
+ }
1001
+ }
1002
+
1003
+ // Save last question if exists
1004
+ if (currentQuestion) {
1005
+ questions.push(currentQuestion);
749
1006
  }
1007
+
1008
+ requirements.push({ title, details, pkg, questions, findings, lineIndex: i });
750
1009
  }
751
1010
  }
752
1011
 
753
- if (currentReq) requirements.push(currentReq);
754
1012
  return requirements;
755
1013
  };
756
1014
 
@@ -2357,7 +2615,7 @@ async function showRequirementsBySection(sectionTitle) {
2357
2615
  }
2358
2616
  break;
2359
2617
  case 'delete':
2360
- const { confirmDelete } = await inquirer.prompt([{
2618
+ const { confirmDelete } = await promptWithDefaultsOnce([{
2361
2619
  type: 'confirm',
2362
2620
  name: 'confirmDelete',
2363
2621
  message: 'Are you sure you want to delete this requirement?',
@@ -2773,23 +3031,39 @@ async function showProviderManagerMenu() {
2773
3031
  const isSelected = idx === selectedIndex;
2774
3032
  const isEnabled = enabled[id] !== false;
2775
3033
 
2776
- // Check for Kiro installation
3034
+ // Check installation status for all IDEs
2777
3035
  let isInstalled = true;
2778
- if (id === 'kiro') {
3036
+ if (def.type === 'ide') {
2779
3037
  try {
2780
- const { isKiroInstalled } = require('./kiro-installer');
2781
- isInstalled = isKiroInstalled();
3038
+ if (id === 'kiro') {
3039
+ const { isKiroInstalled } = require('./kiro-installer');
3040
+ isInstalled = isKiroInstalled();
3041
+ } else if (id === 'cursor') {
3042
+ isInstalled = await checkAppOrBinary(['Cursor'], ['cursor']);
3043
+ } else if (id === 'windsurf') {
3044
+ isInstalled = await checkAppOrBinary(['Windsurf'], ['windsurf']);
3045
+ } else if (id === 'vscode') {
3046
+ isInstalled = await checkAppOrBinary(['Visual Studio Code', 'Visual Studio Code - Insiders'], ['code', 'code-insiders']);
3047
+ } else if (id === 'antigravity') {
3048
+ isInstalled = await checkAppOrBinary(['Antigravity'], ['antigravity']);
3049
+ } else {
3050
+ // For other IDEs, try binary name same as id
3051
+ isInstalled = await checkAppOrBinary([], [id]);
3052
+ }
2782
3053
  } catch (e) {
2783
- // Ignore error provider checks
3054
+ // If detection fails, assume not installed
3055
+ isInstalled = false;
2784
3056
  }
2785
3057
  }
2786
3058
 
2787
- // Determine status emoji: disabled = red alert, rate limited = green circle, enabled = green circle
3059
+ // Determine status emoji: disabled = red alert, not installed = yellow, enabled = green
2788
3060
  let statusEmoji;
2789
- if (id === 'kiro' && !isInstalled) {
3061
+ if (!isEnabled) {
3062
+ statusEmoji = '🚨'; // Red for disabled
3063
+ } else if (def.type === 'ide' && !isInstalled) {
2790
3064
  statusEmoji = '🟡'; // Yellow for not installed
2791
3065
  } else {
2792
- statusEmoji = !isEnabled ? '🚨' : '🟢';
3066
+ statusEmoji = '🟢'; // Green for enabled and installed (or LLM)
2793
3067
  }
2794
3068
 
2795
3069
  const typeLabel = def.type === 'ide' ? chalk.cyan('IDE') : chalk.cyan('LLM');
@@ -3372,8 +3646,36 @@ async function startInteractive() {
3372
3646
  const { stopAutoMode } = require('./auto-mode');
3373
3647
  await stopAutoMode('startup');
3374
3648
 
3649
+ await bootstrapProjectIfInHomeDir();
3650
+
3375
3651
  await showWelcomeScreen();
3376
3652
 
3653
+ // Check if .vibecodingmachine directory exists, offer to create if not
3654
+ const allnightStatusCheck = await checkVibeCodingMachineExists();
3655
+ if (!allnightStatusCheck.insideExists && !allnightStatusCheck.siblingExists) {
3656
+ console.log(chalk.yellow('\n⚠️ No .vibecodingmachine directory found'));
3657
+ console.log(chalk.gray('This directory is needed to save requirements and track progress.\n'));
3658
+
3659
+ const { shouldInit } = await inquirer.prompt([
3660
+ {
3661
+ type: 'confirm',
3662
+ name: 'shouldInit',
3663
+ message: 'Would you like to create the .vibecodingmachine directory now?',
3664
+ default: true
3665
+ }
3666
+ ]);
3667
+
3668
+ if (shouldInit) {
3669
+ // Use the initRepo function to create the directory
3670
+ const { initRepo } = require('../commands/repo');
3671
+ await initRepo({ location: 'inside' });
3672
+ console.log('');
3673
+ } else {
3674
+ console.log(chalk.yellow('\n⚠️ Warning: Without the .vibecodingmachine directory, requirements cannot be saved.'));
3675
+ console.log(chalk.gray('You can create it later by running: ') + chalk.cyan('vcm repo:init\n'));
3676
+ }
3677
+ }
3678
+
3377
3679
  if (process.env.VCM_OPEN_PROVIDER_MENU === '1' || process.env.VCM_OPEN_PROVIDER_MENU === 'true') {
3378
3680
  await showProviderManagerMenu();
3379
3681
  return;
@@ -3516,7 +3818,9 @@ async function startInteractive() {
3516
3818
 
3517
3819
  // Add Requirements as a selectable setting with counts
3518
3820
  const hasRequirements = await requirementsExists();
3519
- const counts = hasRequirements ? await countRequirements() : null;
3821
+ const { getComputerFilter } = require('./config');
3822
+ const computerFilter = await getComputerFilter();
3823
+ const counts = hasRequirements ? await countRequirements(computerFilter) : null;
3520
3824
  let requirementsText = t('interactive.requirements') + ': ';
3521
3825
  if (counts) {
3522
3826
  // Use actual counts for display (not capped by maxChats)
@@ -3538,11 +3842,19 @@ async function startInteractive() {
3538
3842
  if (requirementsText !== '') {
3539
3843
  items.push({
3540
3844
  type: 'setting',
3541
- name: requirementsText,
3845
+ name: requirementsText + ` ${chalk.blue('Sync Status: ' + (await getSyncStatus()))}`,
3542
3846
  value: 'setting:requirements'
3543
3847
  });
3544
3848
  }
3545
3849
 
3850
+ // Add requirement filtering by computer
3851
+ const filterText = computerFilter ? chalk.cyan(computerFilter) : chalk.gray('All Computers');
3852
+ items.push({
3853
+ type: 'setting',
3854
+ name: ` └─ Filter Requirements by Computer: ${filterText}`,
3855
+ value: 'setting:filter-requirements'
3856
+ });
3857
+
3546
3858
  items.push({
3547
3859
  type: 'setting',
3548
3860
  name: ` └─ ${t('interactive.hostname.enabled')}: ${useHostname ? chalk.green('✓') : chalk.red('🛑')} ${useHostname ? '✓ ENABLED' : '🛑 ' + t('interactive.hostname.disabled')}`,
@@ -3559,6 +3871,83 @@ async function startInteractive() {
3559
3871
  value: 'setting:stages'
3560
3872
  });
3561
3873
 
3874
+ // Add Admin Dashboard - Multi-Computer Dashboard
3875
+ items.push({
3876
+ type: 'setting',
3877
+ name: ` └─ Admin Dashboard: ${chalk.cyan('Multi-Computer Dashboard')}`,
3878
+ value: 'setting:admin-dashboard-multi-computer'
3879
+ });
3880
+
3881
+ // Add Admin Dashboard - Compliance and Terms Tracking
3882
+ items.push({
3883
+ type: 'setting',
3884
+ name: ` └─ Admin Dashboard: ${chalk.cyan('Compliance and Terms Tracking')}`,
3885
+ value: 'setting:admin-dashboard-compliance'
3886
+ });
3887
+
3888
+ // Add Admin Dashboard - Authentication and Security
3889
+ items.push({
3890
+ type: 'setting',
3891
+ name: ` └─ Admin Dashboard: ${chalk.cyan('Authentication and Security')}`,
3892
+ value: 'setting:admin-dashboard-auth'
3893
+ });
3894
+
3895
+ // Add Admin Dashboard - Terms and Privacy Policy
3896
+ items.push({
3897
+ type: 'setting',
3898
+ name: ` └─ Admin Dashboard: ${chalk.cyan('Terms and Privacy Policy')}`,
3899
+ value: 'setting:admin-dashboard-terms'
3900
+ });
3901
+
3902
+ // Add Admin Dashboard - Deployment and Infrastructure
3903
+ items.push({
3904
+ type: 'setting',
3905
+ name: ` └─ Admin Dashboard: ${chalk.cyan('Deployment and Infrastructure')}`,
3906
+ value: 'setting:admin-dashboard-deployment'
3907
+ });
3908
+
3909
+ // Add Cloud Sync Infrastructure - AWS DynamoDB Setup
3910
+ items.push({
3911
+ type: 'setting',
3912
+ name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('AWS DynamoDB Setup')}`,
3913
+ value: 'setting:cloud-sync-dynamodb'
3914
+ });
3915
+
3916
+ // Add Cloud Sync Infrastructure - Lambda Functions
3917
+ items.push({
3918
+ type: 'setting',
3919
+ name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('Lambda Functions')}`,
3920
+ value: 'setting:cloud-sync-lambda'
3921
+ });
3922
+
3923
+ // Add Cloud Sync Infrastructure - API Gateway Setup
3924
+ items.push({
3925
+ type: 'setting',
3926
+ name: ` └─ Cloud Sync: ${chalk.cyan('AWS DynamoDB Setup')}`,
3927
+ value: 'setting:cloud-sync-dynamodb'
3928
+ });
3929
+
3930
+ // Add Cloud Sync Infrastructure - AWS API Gateway
3931
+ items.push({
3932
+ type: 'setting',
3933
+ name: ` └─ Cloud Sync: ${chalk.cyan('AWS API Gateway')}`,
3934
+ value: 'setting:cloud-sync-api-gateway'
3935
+ });
3936
+
3937
+ // Add Cloud Sync Infrastructure - AWS Cognito Authentication
3938
+ items.push({
3939
+ type: 'setting',
3940
+ name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('AWS Cognito Authentication')}`,
3941
+ value: 'setting:cloud-sync-cognito'
3942
+ });
3943
+
3944
+ // Add Cloud Sync Infrastructure - AWS DynamoDB Setup
3945
+ items.push({
3946
+ type: 'setting',
3947
+ name: ` └─ Cloud Sync Infrastructure: ${chalk.cyan('AWS DynamoDB Setup')}`,
3948
+ value: 'setting:cloud-sync-dynamodb'
3949
+ });
3950
+
3562
3951
  // Cloud Sync Status
3563
3952
  try {
3564
3953
  const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
@@ -3596,7 +3985,82 @@ async function startInteractive() {
3596
3985
  });
3597
3986
  }
3598
3987
 
3988
+ // Computer and Usage Tracking Status
3989
+ try {
3990
+ const os = require('os');
3991
+ const hostname = os.hostname();
3992
+ const platform = os.platform();
3993
+ const arch = os.arch();
3994
+ const usageIcon = chalk.blue('●');
3995
+ const trackingText = `${hostname} (${platform}/${arch})`;
3996
+
3997
+ items.push({
3998
+ type: 'setting',
3999
+ name: `${t('interactive.usage.tracking') || 'Usage Tracking'}: ${usageIcon} ${chalk.cyan(trackingText)}`,
4000
+ value: 'setting:usage-tracking'
4001
+ });
4002
+ } catch (error) {
4003
+ // Tracking info unavailable
4004
+ items.push({
4005
+ type: 'setting',
4006
+ name: `Usage Tracking: ${chalk.gray('● Unavailable')}`,
4007
+ value: 'setting:usage-tracking'
4008
+ });
4009
+ }
4010
+
4011
+ // Add requirement number tracking status
4012
+ items.push({
4013
+ type: 'setting',
4014
+ name: ` └─ ${t('interactive.requirements.tracking')}: ${counts ? chalk.green('✓ ENABLED') : chalk.red('🛑 DISABLED')}`,
4015
+ value: 'setting:requirements-tracking'
4016
+ });
4017
+ const { readConfig } = require('./config');
4018
+ const config = await readConfig();
4019
+ const lastReqNumber = config.lastRequirementNumber || 0;
4020
+ items.push({
4021
+ type: 'setting',
4022
+ name: ` └─ ${t('interactive.last.requirement.number')}: ${chalk.cyan('R' + lastReqNumber)}`,
4023
+ value: 'setting:req-number'
4024
+ });
4025
+
3599
4026
  // Add "Next TODO Requirement" as a separate menu item if there are TODO items
4027
+ if (counts && counts.todoCount > 0) {
4028
+ items.push({
4029
+ type: 'setting',
4030
+ name: ` └─ ${t('interactive.next.todo.requirement')}`,
4031
+ value: 'setting:next-todo-requirement'
4032
+ });
4033
+ }
4034
+
4035
+ // Save last tab opened
4036
+ const lastTabOpened = config.lastTabOpened;
4037
+ if (lastTabOpened) {
4038
+ items.push({
4039
+ type: 'setting',
4040
+ name: ` └─ ${t('interactive.last.tab.opened')}: ${lastTabOpened}`,
4041
+ value: 'setting:last-tab-opened'
4042
+ });
4043
+ }
4044
+ items.push({
4045
+ type: 'setting',
4046
+ name: `Start Auto: Switch to Console tab`,
4047
+ value: 'setting:start-auto'
4048
+ });
4049
+ // TODO: Implement getNextTodoRequirement function
4050
+ // if (counts && counts.todoCount > 0) {
4051
+ // const { getNextTodoRequirement } = require('./auto-mode');
4052
+ // const nextRequirement = await getNextTodoRequirement();
4053
+ // if (nextRequirement) {
4054
+ // const requirementPreview = nextRequirement.length > 80
4055
+ // ? nextRequirement.substring(0, 77) + '...'
4056
+ // : nextRequirement;
4057
+ // items.push({
4058
+ // type: 'info',
4059
+ // name: ` └─ ${t('interactive.next.requirement')}: ${chalk.cyan(requirementPreview)}`,
4060
+ // value: 'info:next-requirement'
4061
+ // });
4062
+ // }
4063
+ // }
3600
4064
  if (counts && counts.todoCount > 0) {
3601
4065
  // Get the actual next requirement text (new header format)
3602
4066
  let nextReqText = '...';
@@ -3920,6 +4384,69 @@ async function startInteractive() {
3920
4384
  await showWelcomeScreen();
3921
4385
  break;
3922
4386
  }
4387
+ case 'setting:filter-requirements': {
4388
+ // Configure requirement filtering by computer
4389
+ const { getComputerFilter, setComputerFilter } = require('./config');
4390
+ const inquirer = require('inquirer');
4391
+ const repoPath = await getRepoPath();
4392
+
4393
+ if (!repoPath) {
4394
+ console.log(chalk.yellow('No repository configured. Use repo:init or repo:set first.'));
4395
+ await showWelcomeScreen();
4396
+ break;
4397
+ }
4398
+
4399
+ // Find all REQUIREMENTS-*.md files to get available computers
4400
+ const vibeDir = path.join(repoPath, '.vibecodingmachine');
4401
+ const files = await fs.pathExists(vibeDir) ? await fs.readdir(vibeDir) : [];
4402
+ const reqFiles = files.filter(f => f.startsWith('REQUIREMENTS') && f.endsWith('.md'));
4403
+
4404
+ const computers = [];
4405
+ for (const file of reqFiles) {
4406
+ const filePath = path.join(vibeDir, file);
4407
+ const content = await fs.readFile(filePath, 'utf8');
4408
+ const hostnameMatch = content.match(/- \*\*Hostname\*\*:\s*(.+)/);
4409
+ if (hostnameMatch) {
4410
+ computers.push(hostnameMatch[1].trim());
4411
+ }
4412
+ }
4413
+
4414
+ if (computers.length === 0) {
4415
+ console.log(chalk.yellow('No computers found in requirements files.'));
4416
+ await showWelcomeScreen();
4417
+ break;
4418
+ }
4419
+
4420
+ const currentFilter = await getComputerFilter();
4421
+
4422
+ console.log(chalk.cyan(`\n🖥️ ${t('interactive.filter.requirements.by.computer')}\n`));
4423
+ console.log(chalk.gray('Select which computer\'s requirements to show, or "All Computers" to show all.\n'));
4424
+
4425
+ const { selectedComputer } = await inquirer.prompt([
4426
+ {
4427
+ type: 'list',
4428
+ name: 'selectedComputer',
4429
+ message: t('interactive.select.computer'),
4430
+ choices: [
4431
+ { name: chalk.gray('All Computers'), value: null },
4432
+ ...computers.map(c => ({
4433
+ name: c === currentFilter ? chalk.cyan(`${c} (current)`) : c,
4434
+ value: c
4435
+ }))
4436
+ ],
4437
+ default: currentFilter || null,
4438
+ loop: false,
4439
+ pageSize: 15
4440
+ }
4441
+ ]);
4442
+
4443
+ await setComputerFilter(selectedComputer);
4444
+
4445
+ const filterText = selectedComputer ? chalk.cyan(selectedComputer) : chalk.gray('All Computers');
4446
+ console.log(chalk.green('\n✓'), `Requirements filter set to: ${filterText}\n`);
4447
+ await showWelcomeScreen();
4448
+ break;
4449
+ }
3923
4450
  case 'setting:stages': {
3924
4451
  // Configure stages
3925
4452
  const { getStages, setStages, DEFAULT_STAGES } = require('./config');
@@ -3927,11 +4454,11 @@ async function startInteractive() {
3927
4454
 
3928
4455
  // Override inquirer's checkbox help text for current locale
3929
4456
  const CheckboxPrompt = require('inquirer/lib/prompts/checkbox');
3930
- const originalGetHelpText = CheckboxPrompt.prototype.getHelp || function() {
4457
+ const originalGetHelpText = CheckboxPrompt.prototype.getHelp || function () {
3931
4458
  return '(Press <space> to select, <a> to toggle all, <i> to invert selection, and <enter> to proceed)';
3932
4459
  };
3933
4460
 
3934
- CheckboxPrompt.prototype.getHelp = function() {
4461
+ CheckboxPrompt.prototype.getHelp = function () {
3935
4462
  const locale = detectLocale();
3936
4463
  if (locale === 'es') {
3937
4464
  return '(Presiona <espacio> para seleccionar, <a> para alternar todo, <i> para invertir selección, y <enter> para proceder)';
@@ -4013,8 +4540,23 @@ async function startInteractive() {
4013
4540
  const { getAutoConfig } = require('./config');
4014
4541
  const currentConfig = await getAutoConfig();
4015
4542
 
4543
+ // Get first enabled agent from provider preferences (this is what user expects)
4544
+ const { getProviderPreferences } = require('../utils/provider-registry');
4545
+ const prefs = await getProviderPreferences();
4546
+ console.log(chalk.gray('[DEBUG] Provider preferences:'), JSON.stringify(prefs, null, 2));
4547
+ let firstEnabledAgent = null;
4548
+ for (const agentId of prefs.order) {
4549
+ if (prefs.enabled[agentId] !== false) {
4550
+ firstEnabledAgent = agentId;
4551
+ console.log(chalk.gray('[DEBUG] Found first enabled agent:'), firstEnabledAgent);
4552
+ break;
4553
+ }
4554
+ }
4555
+ const agentToUse = firstEnabledAgent || currentIDE;
4556
+ console.log(chalk.gray('[DEBUG] Agent to use:'), agentToUse, '(firstEnabled:', firstEnabledAgent, ', fallback:', currentIDE, ')');
4557
+
4016
4558
  // Use saved maxChats/neverStop settings
4017
- const options = { ide: currentIDE };
4559
+ const options = { ide: agentToUse };
4018
4560
  if (currentConfig.neverStop) {
4019
4561
  options.neverStop = true;
4020
4562
  } else if (currentConfig.maxChats) {
@@ -4034,8 +4576,7 @@ async function startInteractive() {
4034
4576
  }
4035
4577
 
4036
4578
  // ALWAYS use auto:direct (supports both LLM and IDE agents with proper looping)
4037
- const currentAgent = currentConfig.agent || currentConfig.ide || 'ollama';
4038
- console.log(chalk.gray('[DEBUG] Using auto:direct for agent:', currentAgent));
4579
+ console.log(chalk.gray('[DEBUG] Using auto:direct for agent:', agentToUse));
4039
4580
  const { handleAutoStart: handleDirectAutoStart } = require('../commands/auto-direct');
4040
4581
  await handleDirectAutoStart({ maxChats: options.maxChats });
4041
4582
 
@@ -4286,6 +4827,21 @@ async function startInteractive() {
4286
4827
  const hostname = getHostname();
4287
4828
  console.log('[DEBUG] Repo:', repoPath, 'Hostname:', hostname);
4288
4829
 
4830
+ // Get current git branch if in a git repo
4831
+ let currentBranch = '';
4832
+ if (repoPath) {
4833
+ try {
4834
+ const { execSync } = require('child_process');
4835
+ currentBranch = execSync('git branch --show-current', {
4836
+ cwd: repoPath,
4837
+ encoding: 'utf8'
4838
+ }).trim();
4839
+ } catch (err) {
4840
+ // Not a git repo or git not available
4841
+ currentBranch = '';
4842
+ }
4843
+ }
4844
+
4289
4845
  // Build menu content for header
4290
4846
  const menuContent = `╭───────────────────────────────────────────────────────╮
4291
4847
  │ │
@@ -4295,7 +4851,7 @@ async function startInteractive() {
4295
4851
  ╰───────────────────────────────────────────────────────╯
4296
4852
 
4297
4853
  ${t('system.repo').padEnd(25)} ${repoPath || 'Not set'}
4298
- ${t('system.computer.name').padEnd(25)} ${hostname}
4854
+ ${currentBranch ? t('system.git.branch').padEnd(25) + ' ' + currentBranch + '\n' : ''}${t('system.computer.name').padEnd(25)} ${hostname}
4299
4855
  Current IDE: ${formatIDEName(ide)}
4300
4856
  AI Provider: ${getCurrentAIProvider(ide) || 'N/A'}
4301
4857
  Max Chats: ${maxChats || 'Never stop'}`;
@@ -4466,7 +5022,11 @@ Max Chats: ${maxChats || 'Never stop'}`;
4466
5022
  }
4467
5023
  }
4468
5024
 
4469
- module.exports = { startInteractive };
5025
+ module.exports = {
5026
+ startInteractive,
5027
+ bootstrapProjectIfInHomeDir,
5028
+ normalizeProjectDirName
5029
+ };
4470
5030
 
4471
5031
 
4472
5032