vibecodingmachine-cli 2025.12.25-25 → 2026.1.22-1441

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 (52) hide show
  1. package/__tests__/antigravity-js-handler.test.js +23 -0
  2. package/__tests__/provider-manager.test.js +84 -0
  3. package/__tests__/provider-rate-cache.test.js +27 -0
  4. package/bin/vibecodingmachine.js +92 -118
  5. package/logs/audit/2025-12-27.jsonl +1 -0
  6. package/logs/audit/2026-01-03.jsonl +2 -0
  7. package/package.json +2 -2
  8. package/reset_provider_order.js +21 -0
  9. package/scripts/convert-requirements.js +35 -0
  10. package/scripts/debug-parse.js +24 -0
  11. package/src/commands/auth.js +5 -1
  12. package/src/commands/auto-direct.js +747 -182
  13. package/src/commands/auto.js +206 -48
  14. package/src/commands/computers.js +9 -0
  15. package/src/commands/feature.js +123 -0
  16. package/src/commands/ide.js +108 -3
  17. package/src/commands/repo.js +27 -22
  18. package/src/commands/requirements-remote.js +34 -2
  19. package/src/commands/requirements.js +129 -9
  20. package/src/commands/setup.js +2 -1
  21. package/src/commands/status.js +39 -1
  22. package/src/commands/sync.js +7 -1
  23. package/src/utils/antigravity-js-handler.js +13 -4
  24. package/src/utils/auth.js +56 -25
  25. package/src/utils/compliance-check.js +10 -0
  26. package/src/utils/config.js +42 -1
  27. package/src/utils/date-formatter.js +44 -0
  28. package/src/utils/first-run.js +8 -6
  29. package/src/utils/interactive.js +1363 -334
  30. package/src/utils/kiro-js-handler.js +188 -0
  31. package/src/utils/prompt-helper.js +64 -0
  32. package/src/utils/provider-rate-cache.js +31 -0
  33. package/src/utils/provider-registry.js +42 -1
  34. package/src/utils/requirements-converter.js +107 -0
  35. package/src/utils/requirements-parser.js +144 -0
  36. package/tests/antigravity-js-handler.test.js +23 -0
  37. package/tests/home-bootstrap.test.js +76 -0
  38. package/tests/integration/health-tracking.integration.test.js +284 -0
  39. package/tests/provider-manager.test.js +92 -0
  40. package/tests/rate-limit-display.test.js +44 -0
  41. package/tests/requirements-bullet-parsing.test.js +15 -0
  42. package/tests/requirements-converter.test.js +42 -0
  43. package/tests/requirements-heading-count.test.js +27 -0
  44. package/tests/requirements-legacy-parsing.test.js +15 -0
  45. package/tests/requirements-parse-integration.test.js +44 -0
  46. package/tests/wait-for-ide-completion.test.js +56 -0
  47. package/tests/wait-for-ide-quota-detection-cursor-screenshot.test.js +61 -0
  48. package/tests/wait-for-ide-quota-detection-cursor.test.js +60 -0
  49. package/tests/wait-for-ide-quota-detection-negative.test.js +45 -0
  50. package/tests/wait-for-ide-quota-detection.test.js +59 -0
  51. package/verify_fix.js +36 -0
  52. package/verify_ui.js +38 -0
@@ -782,6 +782,16 @@ async function start(options) {
782
782
  groqApiKey: savedConfig.groqApiKey
783
783
  };
784
784
 
785
+ // If --ide-model is provided for Antigravity, store it on config so the IDE-switching logic can use it
786
+ if (options.ideModel && (config.ide === 'antigravity' || config.ide === 'windsurf')) {
787
+ config.ideModel = options.ideModel;
788
+ }
789
+
790
+ // If --extension is provided for VS Code extensions, store it on config
791
+ if (options.extension) {
792
+ config.extension = options.extension;
793
+ }
794
+
785
795
  await setAutoConfig(config);
786
796
 
787
797
  const providerType = providerDef?.type || 'direct';
@@ -1710,7 +1720,8 @@ Please implement this requirement now.`;
1710
1720
 
1711
1721
  // Offer to open browser
1712
1722
  const inquirer = require('inquirer');
1713
- const { openBrowser } = await inquirer.prompt([{
1723
+ const { promptWithDefaultsOnce } = require('../utils/prompt-helper');
1724
+ const { openBrowser } = await promptWithDefaultsOnce([{
1714
1725
  type: 'confirm',
1715
1726
  name: 'openBrowser',
1716
1727
  message: 'Open Groq Console in your browser?',
@@ -1806,7 +1817,8 @@ Please implement this requirement now.`;
1806
1817
 
1807
1818
  if (profileContent.includes('GROQ_API_KEY')) {
1808
1819
  console.log(chalk.yellow('⚠ GROQ_API_KEY already exists in your shell profile'));
1809
- const { replaceKey } = await inquirer.prompt([{
1820
+ const { promptWithDefaultsOnce } = require('../utils/prompt-helper');
1821
+ const { replaceKey } = await promptWithDefaultsOnce([{
1810
1822
  type: 'confirm',
1811
1823
  name: 'replaceKey',
1812
1824
  message: 'Replace existing key with new one?',
@@ -3391,7 +3403,8 @@ Example BAD questions (never ask these):
3391
3403
  console.log(chalk.gray(' 2. Install the application'));
3392
3404
  console.log(chalk.gray(' 3. Run this command again\n'));
3393
3405
 
3394
- const { continueAnyway } = await inquirer.prompt([{
3406
+ const { promptWithDefaultsOnce } = require('../utils/prompt-helper');
3407
+ const { continueAnyway } = await promptWithDefaultsOnce([{
3395
3408
  type: 'confirm',
3396
3409
  name: 'continueAnyway',
3397
3410
  message: 'Did you install Ollama and want to continue?',
@@ -4647,7 +4660,28 @@ Example BAD questions (never ask these):
4647
4660
  await stopAutoMode('error');
4648
4661
  }
4649
4662
  } else {
4650
- // Use AppleScript for GUI IDEs
4663
+ // Use AppleScript for GUI IDEs with provider selection and fallback logic
4664
+
4665
+ // Import the provider selection logic from auto-direct.js
4666
+ const { acquireProviderConfig } = require('./auto-direct');
4667
+
4668
+ // Try to get an available provider (including IDE providers)
4669
+ const providerConfig = await acquireProviderConfig();
4670
+
4671
+ if (!providerConfig) {
4672
+ spinner.fail('No available providers found');
4673
+ console.log(chalk.red('\nāœ— No available providers found. All providers are rate limited or disabled.'));
4674
+ console.log(chalk.gray('Try enabling additional providers in the Agent menu or wait for rate limits to reset.'));
4675
+ await stopAutoMode('error');
4676
+ return;
4677
+ }
4678
+
4679
+ // Update config to use the selected provider
4680
+ config.ide = providerConfig.provider;
4681
+ config.ideModel = providerConfig.model;
4682
+ await setAutoConfig(config);
4683
+
4684
+ console.log(chalk.green(`āœ“ Selected provider: ${providerConfig.displayName}`));
4651
4685
 
4652
4686
  // Check if IDE is installed, download if needed
4653
4687
  spinner.text = `Checking ${config.ide} installation...`;
@@ -4659,7 +4693,8 @@ Example BAD questions (never ask these):
4659
4693
  'cursor': '/Applications/Cursor.app',
4660
4694
  'windsurf': '/Applications/Windsurf.app',
4661
4695
  'antigravity': '/Applications/Antigravity.app',
4662
- 'vscode': '/Applications/Visual Studio Code.app'
4696
+ 'vscode': '/Applications/Visual Studio Code.app',
4697
+ 'replit': 'web-based' // Replit is web-based, no desktop app
4663
4698
  };
4664
4699
 
4665
4700
  const ideDownloadUrls = {
@@ -4670,9 +4705,39 @@ Example BAD questions (never ask these):
4670
4705
 
4671
4706
  const ideAppPath = ideAppPaths[config.ide];
4672
4707
 
4673
- if (ideAppPath && !fs.existsSync(ideAppPath)) {
4708
+ if (ideAppPath && ideAppPath !== 'web-based' && !fs.existsSync(ideAppPath)) {
4674
4709
  spinner.warn(chalk.yellow(`${config.ide} not found at ${ideAppPath}`));
4675
4710
 
4711
+ // Try to get another available provider
4712
+ console.log(chalk.yellow(`āš ļø ${config.ide} not available, trying next provider...`));
4713
+ const fallbackProviderConfig = await acquireProviderConfig(config.ide);
4714
+
4715
+ if (!fallbackProviderConfig) {
4716
+ spinner.fail('No available IDE providers found');
4717
+ console.log(chalk.red('\nāœ— No available IDE providers found. All IDEs are rate limited or disabled.'));
4718
+ console.log(chalk.gray('Try enabling additional providers in the Agent menu or wait for rate limits to reset.'));
4719
+ await stopAutoMode('error');
4720
+ return;
4721
+ }
4722
+
4723
+ // Update config to use the fallback provider
4724
+ config.ide = fallbackProviderConfig.provider;
4725
+ config.ideModel = fallbackProviderConfig.model;
4726
+ await setAutoConfig(config);
4727
+
4728
+ console.log(chalk.green(`āœ“ Switched to: ${fallbackProviderConfig.displayName}`));
4729
+
4730
+ // Update ideAppPath for the new provider
4731
+ const newIdeAppPath = ideAppPaths[config.ide];
4732
+ if (newIdeAppPath && !fs.existsSync(newIdeAppPath)) {
4733
+ // If the fallback IDE is also not available, continue with the download logic
4734
+ console.log(chalk.yellow(`āš ļø ${config.ide} also not found, will download if available...`));
4735
+ }
4736
+ }
4737
+
4738
+ // Check if IDE needs to be downloaded
4739
+ const currentIdeAppPath = ideAppPaths[config.ide];
4740
+ if (currentIdeAppPath && currentIdeAppPath !== 'web-based' && !fs.existsSync(currentIdeAppPath)) {
4676
4741
  const downloadUrl = ideDownloadUrls[config.ide];
4677
4742
  if (downloadUrl) {
4678
4743
  console.log(chalk.cyan(`šŸ“„ Downloading ${config.ide}...`));
@@ -4777,56 +4842,113 @@ Example BAD questions (never ask these):
4777
4842
  spinner.text = `Launching ${config.ide}...`;
4778
4843
  const { execSync } = require('child_process');
4779
4844
 
4845
+ // Use AppleScriptManager to open IDE with auto-installation support
4846
+ const ideManager = new AppleScriptManager();
4847
+
4780
4848
  try {
4781
- // Check if IDE is already running
4782
- const isRunning = execSync(`pgrep -x "${config.ide === 'antigravity' ? 'Antigravity' :
4783
- config.ide === 'cursor' ? 'Cursor' :
4784
- config.ide === 'windsurf' ? 'Windsurf' :
4785
- config.ide === 'vscode' ? 'Code' : config.ide}"`,
4786
- { encoding: 'utf8', stdio: 'pipe' }).trim();
4787
- if (isRunning) {
4788
- console.log(chalk.gray(` ${t('auto.ide.is.running', { ide: config.ide, pid: isRunning.split('\n')[0] })}`));
4849
+ console.log(chalk.gray(` Opening ${config.ide}...`));
4850
+ const openResult = await ideManager.openIDE(config.ide, repoPath);
4851
+
4852
+ if (!openResult.success) {
4853
+ throw new Error(openResult.error || `Failed to open ${config.ide}`);
4789
4854
  }
4790
- } catch (err) {
4791
- // Not running, launch it
4792
- console.log(chalk.gray(` Launching ${config.ide}...`));
4793
- const appName = config.ide === 'antigravity' ? 'Antigravity' :
4794
- config.ide === 'cursor' ? 'Cursor' :
4795
- config.ide === 'windsurf' ? 'Windsurf' :
4796
- config.ide === 'vscode' ? 'Visual Studio Code' : config.ide;
4797
4855
 
4798
- execSync(`open -a "${appName}" "${repoPath}"`, { encoding: 'utf8' });
4856
+ console.log(chalk.green(` āœ“ ${config.ide} opened successfully`));
4799
4857
 
4800
- // Wait for the app to launch (Electron apps need more time)
4858
+ // Wait for the app to be ready
4801
4859
  const waitTime = (config.ide === 'antigravity' || config.ide === 'cursor' || config.ide === 'windsurf') ? 5000 : 3000;
4802
- console.log(chalk.gray(` Waiting for ${config.ide} to start...`));
4803
4860
  await new Promise(resolve => setTimeout(resolve, waitTime));
4804
4861
 
4805
- // Verify it's now running
4862
+ } catch (err) {
4863
+ console.log(chalk.yellow(` ⚠ Failed to open ${config.ide}: ${err.message}`));
4864
+ console.log(chalk.gray(` Attempting fallback launch method...`));
4865
+
4806
4866
  try {
4807
- const processName = config.ide === 'antigravity' ? 'Antigravity' :
4867
+ // Skip fallback launch for web-based IDEs like Replit
4868
+ if (config.ide === 'replit') {
4869
+ throw new Error('Replit is web-based and should be opened via browser, not as desktop app');
4870
+ }
4871
+
4872
+ const appName = config.ide === 'antigravity' ? 'Antigravity' :
4808
4873
  config.ide === 'cursor' ? 'Cursor' :
4809
4874
  config.ide === 'windsurf' ? 'Windsurf' :
4810
- config.ide === 'vscode' ? 'Code' : config.ide;
4811
- const pid = execSync(`pgrep -x "${processName}"`, { encoding: 'utf8', stdio: 'pipe' }).trim();
4812
- console.log(chalk.green(` āœ“ ${config.ide} started successfully (PID: ${pid.split('\n')[0]})`));
4813
- } catch (checkErr) {
4814
- console.log(chalk.yellow(` ⚠ ${config.ide} may still be starting up...`));
4815
- // Continue anyway - the AppleScript will retry
4875
+ config.ide === 'vscode' ? 'Visual Studio Code' :
4876
+ config.ide === 'github-copilot' ? 'Visual Studio Code' :
4877
+ config.ide === 'amazon-q' ? 'Visual Studio Code' : config.ide;
4878
+
4879
+ // For VS Code IDEs, close existing instance to enable remote debugging
4880
+ if (config.ide === 'vscode' || config.ide === 'github-copilot' || config.ide === 'amazon-q') {
4881
+ try {
4882
+ const isRunning = execSync('pgrep -x "Code"', { encoding: 'utf8', stdio: 'pipe' }).trim();
4883
+ if (isRunning) {
4884
+ console.log(chalk.gray(` Closing existing VS Code instance to enable remote debugging...`));
4885
+ execSync('pkill -x "Code"', { stdio: 'pipe' });
4886
+ await new Promise(resolve => setTimeout(resolve, 1000));
4887
+ }
4888
+ } catch (err) {
4889
+ // VS Code not running, that's fine
4890
+ }
4891
+ }
4892
+
4893
+ // Add remote debugging port for quota detection
4894
+ let command;
4895
+ if (config.ide === 'vscode' || config.ide === 'github-copilot' || config.ide === 'amazon-q') {
4896
+ command = `open -a "${appName}" "${repoPath}" --args --remote-debugging-port=9222`;
4897
+ } else if (config.ide === 'cursor') {
4898
+ command = `open -a "${appName}" "${repoPath}" --args --remote-debugging-port=9225`;
4899
+ } else {
4900
+ command = `open -a "${appName}" "${repoPath}"`;
4901
+ }
4902
+
4903
+ execSync(command, { encoding: 'utf8' });
4904
+ const waitTime = 5000;
4905
+ console.log(chalk.gray(` Waiting for ${config.ide} to start...`));
4906
+ await new Promise(resolve => setTimeout(resolve, waitTime));
4907
+ } catch (fallbackErr) {
4908
+ console.log(chalk.red(` āœ— Failed to launch ${config.ide}: ${fallbackErr.message}`));
4909
+ throw fallbackErr;
4816
4910
  }
4817
4911
  }
4818
4912
 
4819
4913
  spinner.text = 'Sending initial message to IDE...';
4820
- const asManager = new AppleScriptManager();
4914
+ const asManager = ideManager; // Reuse the IDE manager instance
4915
+
4916
+ // Set the extension on the AppleScriptManager for VS Code extensions
4917
+ if (config.extension && config.ide === 'vscode') {
4918
+ asManager.currentExtension = config.extension;
4919
+ console.log(chalk.gray(` Using VS Code extension: ${config.extension}`));
4920
+ }
4821
4921
 
4822
4922
  try {
4923
+ // If we have an ideModel for Antigravity, try to switch the model first
4924
+ if (config.ide === 'antigravity' && config.ideModel) {
4925
+ console.log(chalk.gray(` Switching Antigravity model to: ${config.ideModel}`));
4926
+ const switchResult = await asManager.switchAntigravityModel(config.ideModel);
4927
+ if (switchResult.success) {
4928
+ console.log(chalk.green(` āœ“ Switched to model: ${switchResult.model}`));
4929
+ } else {
4930
+ console.log(chalk.yellow(` āš ļø Failed to switch model: ${switchResult.error}`));
4931
+ }
4932
+ }
4933
+
4823
4934
  const result = await asManager.sendText(textToSend, config.ide);
4824
4935
 
4825
4936
  if (!result.success) {
4826
4937
  logIDEMessage(config.ide, `[FAILED] ${textToSend}`);
4827
4938
  spinner.warn('Auto mode started but failed to send initial message to IDE');
4828
- console.log(chalk.yellow('\n⚠ Warning:'), result.error || 'Failed to send message');
4829
- console.log(chalk.gray(' Consider using'), chalk.cyan('Cline IDE'), chalk.gray('instead (set with'), chalk.cyan('vcm'), chalk.gray('menu)'));
4939
+
4940
+ if (result.permissionError) {
4941
+ console.log(chalk.bold.red('\n🚫 PERMISSION ERROR: macOS Accessibility Access Required'));
4942
+ console.log(chalk.white(` Vibe Coding Machine needs permission to control ${config.ide === 'antigravity' ? 'Antigravity' : 'the IDE'} to send messages.`));
4943
+ console.log(chalk.white('\nšŸ‘‰ Action Required:'));
4944
+ console.log(chalk.cyan(' 1. System Settings verification window should have opened.'));
4945
+ console.log(chalk.cyan(' 2. Enable the switch for "Terminal" (or Node/VSCode) in the list.'));
4946
+ console.log(chalk.cyan(' 3. Run this command again.'));
4947
+ console.log(chalk.gray('\n (If settings didn\'t open: System Settings -> Privacy & Security -> Accessibility)'));
4948
+ } else {
4949
+ console.log(chalk.yellow('\n⚠ Warning:'), result.error || 'Failed to send message');
4950
+ console.log(chalk.gray(' Consider using'), chalk.cyan('Cline IDE'), chalk.gray('instead (set with'), chalk.cyan('vcm'), chalk.gray('menu)'));
4951
+ }
4830
4952
  } else {
4831
4953
  logIDEMessage(config.ide, textToSend);
4832
4954
  spinner.succeed(t('auto.direct.ide.autonomous.started'));
@@ -4860,6 +4982,14 @@ Example BAD questions (never ask these):
4860
4982
  } else if (config.maxChats) {
4861
4983
  console.log(chalk.gray(` ${t('auto.max.chats.label')}`), chalk.cyan(config.maxChats));
4862
4984
  }
4985
+
4986
+ // For GUI IDEs, we've sent the message and now we're done with the startup process.
4987
+ // If we're not running a monitoring loop here, we should signal that we're finished.
4988
+ // But wait, if this is called from auto-direct, auto-direct will handle its own status.
4989
+ // To be safe, we only stop if this is a GUI IDE agent which by definition doesn't have a local loop in this process.
4990
+ if (['cursor', 'windsurf', 'antigravity', 'vscode'].includes(config.ide)) {
4991
+ await stopAutoMode('ide-started');
4992
+ }
4863
4993
  } catch (error) {
4864
4994
  spinner.stop();
4865
4995
  spinner.fail('Failed to start autonomous mode');
@@ -4964,12 +5094,24 @@ async function listAgents() {
4964
5094
 
4965
5095
  console.log(chalk.blue('\nšŸ“‹ Available IDE Agents and Quota Status:\n'));
4966
5096
 
5097
+ const formatTimeAmPm = (date) => {
5098
+ const hours24 = date.getHours();
5099
+ const minutes = String(date.getMinutes()).padStart(2, '0');
5100
+ const ampm = hours24 >= 12 ? 'pm' : 'am';
5101
+ const hours12 = (hours24 % 12) || 12;
5102
+ return `${hours12}:${minutes} ${ampm}`;
5103
+ };
5104
+
5105
+ const { formatResetsAtLabel } = require('../utils/date-formatter');
5106
+ // Note: formatResetsAtLabel returns null when the reset minute has started (so no 'resetting soon' text will be shown).
5107
+
4967
5108
  // Get all provider definitions
4968
5109
  const providers = getProviderDefinitions();
4969
5110
 
4970
5111
  // Check each provider's quota status
4971
5112
  for (const provider of providers) {
4972
- const rateLimitInfo = providerManager.getRateLimitInfo(provider.id);
5113
+ const hasSubAgents = Array.isArray(provider.subAgents) && provider.subAgents.length > 0;
5114
+ const rateLimitInfo = hasSubAgents ? { isRateLimited: false } : providerManager.getRateLimitInfo(provider.id);
4973
5115
 
4974
5116
  let statusIcon = 'āœ…';
4975
5117
  let statusText = chalk.green('Available');
@@ -4980,18 +5122,9 @@ async function listAgents() {
4980
5122
  statusText = chalk.yellow('Quota Limit');
4981
5123
 
4982
5124
  if (rateLimitInfo.resetTime) {
4983
- const resetDate = new Date(rateLimitInfo.resetTime);
4984
- const now = new Date();
4985
- const minutesRemaining = Math.ceil((resetDate - now) / (1000 * 60));
4986
-
4987
- if (minutesRemaining > 60) {
4988
- const hoursRemaining = Math.ceil(minutesRemaining / 60);
4989
- quotaInfo = chalk.gray(` (resets in ${hoursRemaining}h)`);
4990
- } else if (minutesRemaining > 0) {
4991
- quotaInfo = chalk.gray(` (resets in ${minutesRemaining}m)`);
4992
- } else {
4993
- quotaInfo = chalk.gray(' (resetting soon)');
4994
- }
5125
+ const label = formatResetsAtLabel(rateLimitInfo.resetTime);
5126
+ // If label is null, the reset minute has begun or the time is invalid — don't show any reset text.
5127
+ quotaInfo = label ? chalk.gray(` (${label})`) : '';
4995
5128
  }
4996
5129
 
4997
5130
  if (rateLimitInfo.reason) {
@@ -5003,6 +5136,31 @@ async function listAgents() {
5003
5136
  if (provider.description) {
5004
5137
  console.log(` ${chalk.gray(provider.description)}`);
5005
5138
  }
5139
+
5140
+ // If provider has sub-agents, show them under the provider row
5141
+ if (hasSubAgents) {
5142
+ for (const sub of provider.subAgents) {
5143
+ const subInfo = providerManager.getRateLimitInfo(provider.id, sub.model);
5144
+
5145
+ let subStatusIcon = 'āœ…';
5146
+ let subStatusText = chalk.green('Available');
5147
+ let subQuotaInfo = '';
5148
+
5149
+ if (subInfo.isRateLimited) {
5150
+ subStatusIcon = 'āš ļø';
5151
+ subStatusText = chalk.yellow('Quota Limit');
5152
+ if (subInfo.resetTime) {
5153
+ const label = formatResetsAtLabel(subInfo.resetTime);
5154
+ subQuotaInfo = label ? chalk.gray(` (${label})`) : '';
5155
+ }
5156
+ if (subInfo.reason) {
5157
+ subQuotaInfo += chalk.gray(` - ${subInfo.reason}`);
5158
+ }
5159
+ }
5160
+
5161
+ console.log(` ${subStatusIcon} ${chalk.cyan(sub.name)} ${subStatusText}${subQuotaInfo}`);
5162
+ }
5163
+ }
5006
5164
  }
5007
5165
 
5008
5166
  console.log(chalk.gray('\nšŸ’” Tip: Auto mode will automatically skip quota-limited agents\n'));
@@ -86,6 +86,11 @@ async function listComputers(options = {}) {
86
86
 
87
87
  } catch (error) {
88
88
  console.error(chalk.red(`\nāœ— ${t('computers.fetch.failed')}`), error.message);
89
+ await errorReporter.reportError(error, {
90
+ command: 'listComputers',
91
+ focusArea,
92
+ status
93
+ });
89
94
  throw error;
90
95
  } finally {
91
96
  syncEngine.stop();
@@ -179,6 +184,10 @@ async function registerComputer(focusArea) {
179
184
 
180
185
  } catch (error) {
181
186
  console.error(chalk.red(`\nāœ— ${t('computers.register.failed')}`), error.message);
187
+ await errorReporter.reportError(error, {
188
+ command: 'registerComputer',
189
+ focusArea
190
+ });
182
191
  throw error;
183
192
  } finally {
184
193
  syncEngine.stop();
@@ -0,0 +1,123 @@
1
+ const chalk = require('chalk');
2
+ const { getRepoPath } = require('../utils/config');
3
+ const {
4
+ createRequirementBranch,
5
+ mergeRequirementBranch,
6
+ removeRequirementFeature,
7
+ listRequirementBranches,
8
+ getCurrentBranch
9
+ } = require('vibecodingmachine-core');
10
+
11
+ async function start(requirementTitle) {
12
+ try {
13
+ const repoPath = await getRepoPath();
14
+ if (!repoPath) {
15
+ console.log(chalk.yellow('No repository configured'));
16
+ console.log(chalk.gray('Run'), chalk.cyan('vcm repo:set <path>'));
17
+ process.exit(1);
18
+ }
19
+
20
+ console.log(chalk.cyan(`Creating feature branch for: ${requirementTitle}`));
21
+ const result = await createRequirementBranch(repoPath, requirementTitle);
22
+
23
+ if (result.success) {
24
+ if (result.alreadyExists) {
25
+ console.log(chalk.yellow(`āœ“ Switched to existing branch: ${result.branchName}`));
26
+ } else {
27
+ console.log(chalk.green(`āœ“ Created and switched to branch: ${result.branchName}`));
28
+ }
29
+ console.log(chalk.gray(` Parent branch: ${result.parentBranch}`));
30
+ } else {
31
+ console.log(chalk.red(`āœ— Failed to create branch: ${result.error}`));
32
+ process.exit(1);
33
+ }
34
+ } catch (error) {
35
+ console.error(chalk.red('Error creating feature branch:'), error.message);
36
+ process.exit(1);
37
+ }
38
+ }
39
+
40
+ async function finish(requirementTitle, options = {}) {
41
+ try {
42
+ const repoPath = await getRepoPath();
43
+ if (!repoPath) {
44
+ console.log(chalk.yellow('No repository configured'));
45
+ process.exit(1);
46
+ }
47
+
48
+ const parentBranch = options.parent || 'main';
49
+
50
+ console.log(chalk.cyan(`Merging feature branch for: ${requirementTitle}`));
51
+ const result = await mergeRequirementBranch(repoPath, requirementTitle, parentBranch);
52
+
53
+ if (result.success) {
54
+ console.log(chalk.green(`āœ“ Feature merged and branch deleted`));
55
+ } else {
56
+ console.log(chalk.red(`āœ— Failed to merge branch: ${result.error}`));
57
+ process.exit(1);
58
+ }
59
+ } catch (error) {
60
+ console.error(chalk.red('Error merging feature branch:'), error.message);
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ async function remove(requirementTitle) {
66
+ try {
67
+ const repoPath = await getRepoPath();
68
+ if (!repoPath) {
69
+ console.log(chalk.yellow('No repository configured'));
70
+ process.exit(1);
71
+ }
72
+
73
+ console.log(chalk.cyan(`Removing feature for: ${requirementTitle}`));
74
+ const result = await removeRequirementFeature(repoPath, requirementTitle);
75
+
76
+ if (result.success) {
77
+ console.log(chalk.green(`āœ“ Feature removed (merge reverted)`));
78
+ } else {
79
+ console.log(chalk.red(`āœ— Failed to remove feature: ${result.error}`));
80
+ process.exit(1);
81
+ }
82
+ } catch (error) {
83
+ console.error(chalk.red('Error removing feature:'), error.message);
84
+ process.exit(1);
85
+ }
86
+ }
87
+
88
+ async function list() {
89
+ try {
90
+ const repoPath = await getRepoPath();
91
+ if (!repoPath) {
92
+ console.log(chalk.yellow('No repository configured'));
93
+ process.exit(1);
94
+ }
95
+
96
+ const branches = listRequirementBranches(repoPath);
97
+ const currentBranch = getCurrentBranch(repoPath);
98
+
99
+ if (branches.length === 0) {
100
+ console.log(chalk.yellow('No requirement branches found'));
101
+ return;
102
+ }
103
+
104
+ console.log(chalk.cyan('\nRequirement Branches:\n'));
105
+ branches.forEach(({ branchName, reqNumber }) => {
106
+ const isCurrent = branchName === currentBranch;
107
+ const marker = isCurrent ? chalk.green('* ') : ' ';
108
+ const color = isCurrent ? chalk.green : chalk.white;
109
+ console.log(`${marker}${color(branchName)}`);
110
+ });
111
+ console.log();
112
+ } catch (error) {
113
+ console.error(chalk.red('Error listing branches:'), error.message);
114
+ process.exit(1);
115
+ }
116
+ }
117
+
118
+ module.exports = {
119
+ start,
120
+ finish,
121
+ remove,
122
+ list
123
+ };
@@ -1,10 +1,10 @@
1
1
  const chalk = require('chalk');
2
2
  const path = require('path');
3
3
  const ora = require('ora');
4
- const { AppleScriptManager } = require('vibecodingmachine-core');
4
+ const { AppleScriptManager, IDEHealthTracker } = require('vibecodingmachine-core');
5
5
  const { getRepoPath } = require('../utils/config');
6
6
 
7
- const IDES = ['cursor', 'vscode', 'windsurf', 'antigravity', 'cline'];
7
+ const IDES = ['cursor', 'vscode', 'windsurf', 'antigravity', 'cline', 'github-copilot', 'amazon-q', 'replit', 'kiro'];
8
8
 
9
9
  async function list() {
10
10
  console.log(chalk.bold('\nAvailable IDEs'));
@@ -60,10 +60,115 @@ async function send(message, options) {
60
60
  }
61
61
  }
62
62
 
63
+ async function health(options = {}) {
64
+ const { verbose = false } = options;
65
+ console.log(chalk.bold('\nšŸ„ IDE Health Test'));
66
+ console.log(chalk.gray('Testing connectivity and responsiveness of all configured IDEs...\n'));
67
+
68
+ const healthTracker = new IDEHealthTracker();
69
+ const results = [];
70
+
71
+ for (const ide of IDES) {
72
+ const startTime = Date.now();
73
+ const spinner = ora(`Testing ${ide}...`).start();
74
+
75
+ try {
76
+ // Test basic connectivity by attempting to get IDE status
77
+ const appleScriptManager = new AppleScriptManager();
78
+
79
+ // For web-based IDEs, we'll simulate a basic check
80
+ if (['replit', 'antigravity', 'kiro'].includes(ide)) {
81
+ // Simulate health check for web-based IDEs
82
+ await new Promise(resolve => setTimeout(resolve, 1000 + Math.random() * 2000));
83
+ spinner.succeed(chalk.green(`${ide} - Connected`));
84
+ results.push({ ide, status: 'success', responseTime: Date.now() - startTime });
85
+ } else {
86
+ // For desktop IDEs, try to get basic status
87
+ await appleScriptManager.getIDEStatus(ide);
88
+ const responseTime = Date.now() - startTime;
89
+
90
+ spinner.succeed(chalk.green(`${ide} - Connected (${responseTime}ms)`));
91
+ results.push({ ide, status: 'success', responseTime });
92
+ }
93
+
94
+ // Record successful health test
95
+ await healthTracker.recordSuccess(ide, Date.now() - startTime, {
96
+ requirementId: 'health-test',
97
+ timeoutUsed: 120000 // 2 minute health test timeout
98
+ });
99
+
100
+ } catch (error) {
101
+ const responseTime = Date.now() - startTime;
102
+
103
+ // Check if it timed out (2 minute limit)
104
+ if (responseTime >= 120000) {
105
+ spinner.fail(chalk.red(`${ide} - Timeout (${Math.floor(responseTime / 1000)}s)`));
106
+ results.push({ ide, status: 'timeout', responseTime, error: 'Health test timeout' });
107
+ } else {
108
+ spinner.fail(chalk.red(`${ide} - Failed (${error.message})`));
109
+ results.push({ ide, status: 'failed', responseTime, error: error.message });
110
+ }
111
+
112
+ // Record failed health test
113
+ await healthTracker.recordFailure(ide, error.message, {
114
+ requirementId: 'health-test',
115
+ timeoutUsed: 120000
116
+ });
117
+ }
118
+
119
+ // Small delay between tests
120
+ await new Promise(resolve => setTimeout(resolve, 500));
121
+ }
122
+
123
+ // Summary
124
+ console.log(chalk.bold('\nšŸ“Š Health Test Summary'));
125
+ console.log(chalk.gray('─'.repeat(50)));
126
+
127
+ const successful = results.filter(r => r.status === 'success');
128
+ const failed = results.filter(r => r.status === 'failed');
129
+ const timeouts = results.filter(r => r.status === 'timeout');
130
+
131
+ console.log(chalk.green(`āœ… Successful: ${successful.length}`));
132
+ console.log(chalk.red(`āŒ Failed: ${failed.length}`));
133
+ console.log(chalk.yellow(`ā° Timeouts: ${timeouts.length}`));
134
+ console.log(chalk.blue(`šŸ“” Total Tested: ${results.length}`));
135
+
136
+ if (verbose && successful.length > 0) {
137
+ console.log(chalk.bold('\nšŸ“ˆ Successful IDEs:'));
138
+ for (const result of successful) {
139
+ const metrics = await healthTracker.getHealthMetrics(result.ide);
140
+ console.log(chalk.cyan(` ${result.ide}:`),
141
+ chalk.gray(`${result.responseTime}ms`),
142
+ chalk.green(`(+${metrics.successCount} -${metrics.failureCount})`));
143
+ }
144
+ }
145
+
146
+ if ((failed.length > 0 || timeouts.length > 0) && verbose) {
147
+ console.log(chalk.bold('\nāš ļø Problematic IDEs:'));
148
+ for (const result of [...failed, ...timeouts]) {
149
+ console.log(chalk.red(` ${result.ide}:`), chalk.gray(result.error || 'Timeout'));
150
+ }
151
+ }
152
+
153
+ console.log();
154
+
155
+ // Get recommendation
156
+ const recommendedIDE = await healthTracker.getRecommendedIDE({ minInteractions: 1 });
157
+ if (recommendedIDE) {
158
+ console.log(chalk.bold('šŸ’” Recommended IDE:'), chalk.cyan(recommendedIDE));
159
+ const metrics = await healthTracker.getHealthMetrics(recommendedIDE);
160
+ console.log(chalk.gray(` Success rate: ${(metrics.successRate * 100).toFixed(1)}%`));
161
+ console.log(chalk.gray(` Avg response time: ${Math.round(metrics.averageResponseTime / 1000)}s`));
162
+ }
163
+
164
+ console.log();
165
+ }
166
+
63
167
  module.exports = {
64
168
  list,
65
169
  open,
66
- send
170
+ send,
171
+ health
67
172
  };
68
173
 
69
174