vibecodingmachine-cli 2026.1.3-2209 → 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.
- package/__tests__/antigravity-js-handler.test.js +23 -0
- package/__tests__/provider-manager.test.js +84 -0
- package/__tests__/provider-rate-cache.test.js +27 -0
- package/bin/vibecodingmachine.js +8 -0
- package/package.json +2 -2
- package/reset_provider_order.js +21 -0
- package/scripts/convert-requirements.js +35 -0
- package/scripts/debug-parse.js +24 -0
- package/src/commands/auto-direct.js +679 -120
- package/src/commands/auto.js +200 -45
- package/src/commands/ide.js +108 -3
- package/src/commands/requirements-remote.js +10 -1
- package/src/commands/status.js +39 -1
- package/src/utils/antigravity-js-handler.js +13 -4
- package/src/utils/auth.js +37 -13
- package/src/utils/compliance-check.js +10 -0
- package/src/utils/config.js +29 -1
- package/src/utils/date-formatter.js +44 -0
- package/src/utils/interactive.js +1006 -537
- package/src/utils/kiro-js-handler.js +188 -0
- package/src/utils/provider-rate-cache.js +31 -0
- package/src/utils/provider-registry.js +42 -1
- package/src/utils/requirements-converter.js +107 -0
- package/src/utils/requirements-parser.js +144 -0
- package/tests/antigravity-js-handler.test.js +23 -0
- package/tests/integration/health-tracking.integration.test.js +284 -0
- package/tests/provider-manager.test.js +92 -0
- package/tests/rate-limit-display.test.js +44 -0
- package/tests/requirements-bullet-parsing.test.js +15 -0
- package/tests/requirements-converter.test.js +42 -0
- package/tests/requirements-heading-count.test.js +27 -0
- package/tests/requirements-legacy-parsing.test.js +15 -0
- package/tests/requirements-parse-integration.test.js +44 -0
- package/tests/wait-for-ide-completion.test.js +56 -0
- package/tests/wait-for-ide-quota-detection-cursor-screenshot.test.js +61 -0
- package/tests/wait-for-ide-quota-detection-cursor.test.js +60 -0
- package/tests/wait-for-ide-quota-detection-negative.test.js +45 -0
- package/tests/wait-for-ide-quota-detection.test.js +59 -0
- package/verify_fix.js +36 -0
- package/verify_ui.js +38 -0
package/src/commands/auto.js
CHANGED
|
@@ -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';
|
|
@@ -4650,7 +4660,28 @@ Example BAD questions (never ask these):
|
|
|
4650
4660
|
await stopAutoMode('error');
|
|
4651
4661
|
}
|
|
4652
4662
|
} else {
|
|
4653
|
-
// 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}`));
|
|
4654
4685
|
|
|
4655
4686
|
// Check if IDE is installed, download if needed
|
|
4656
4687
|
spinner.text = `Checking ${config.ide} installation...`;
|
|
@@ -4662,7 +4693,8 @@ Example BAD questions (never ask these):
|
|
|
4662
4693
|
'cursor': '/Applications/Cursor.app',
|
|
4663
4694
|
'windsurf': '/Applications/Windsurf.app',
|
|
4664
4695
|
'antigravity': '/Applications/Antigravity.app',
|
|
4665
|
-
'vscode': '/Applications/Visual Studio Code.app'
|
|
4696
|
+
'vscode': '/Applications/Visual Studio Code.app',
|
|
4697
|
+
'replit': 'web-based' // Replit is web-based, no desktop app
|
|
4666
4698
|
};
|
|
4667
4699
|
|
|
4668
4700
|
const ideDownloadUrls = {
|
|
@@ -4673,9 +4705,39 @@ Example BAD questions (never ask these):
|
|
|
4673
4705
|
|
|
4674
4706
|
const ideAppPath = ideAppPaths[config.ide];
|
|
4675
4707
|
|
|
4676
|
-
if (ideAppPath && !fs.existsSync(ideAppPath)) {
|
|
4708
|
+
if (ideAppPath && ideAppPath !== 'web-based' && !fs.existsSync(ideAppPath)) {
|
|
4677
4709
|
spinner.warn(chalk.yellow(`${config.ide} not found at ${ideAppPath}`));
|
|
4678
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)) {
|
|
4679
4741
|
const downloadUrl = ideDownloadUrls[config.ide];
|
|
4680
4742
|
if (downloadUrl) {
|
|
4681
4743
|
console.log(chalk.cyan(`📥 Downloading ${config.ide}...`));
|
|
@@ -4780,56 +4842,113 @@ Example BAD questions (never ask these):
|
|
|
4780
4842
|
spinner.text = `Launching ${config.ide}...`;
|
|
4781
4843
|
const { execSync } = require('child_process');
|
|
4782
4844
|
|
|
4845
|
+
// Use AppleScriptManager to open IDE with auto-installation support
|
|
4846
|
+
const ideManager = new AppleScriptManager();
|
|
4847
|
+
|
|
4783
4848
|
try {
|
|
4784
|
-
|
|
4785
|
-
const
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
{ encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
4790
|
-
if (isRunning) {
|
|
4791
|
-
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}`);
|
|
4792
4854
|
}
|
|
4793
|
-
} catch (err) {
|
|
4794
|
-
// Not running, launch it
|
|
4795
|
-
console.log(chalk.gray(` Launching ${config.ide}...`));
|
|
4796
|
-
const appName = config.ide === 'antigravity' ? 'Antigravity' :
|
|
4797
|
-
config.ide === 'cursor' ? 'Cursor' :
|
|
4798
|
-
config.ide === 'windsurf' ? 'Windsurf' :
|
|
4799
|
-
config.ide === 'vscode' ? 'Visual Studio Code' : config.ide;
|
|
4800
4855
|
|
|
4801
|
-
|
|
4856
|
+
console.log(chalk.green(` ✓ ${config.ide} opened successfully`));
|
|
4802
4857
|
|
|
4803
|
-
// Wait for the app to
|
|
4858
|
+
// Wait for the app to be ready
|
|
4804
4859
|
const waitTime = (config.ide === 'antigravity' || config.ide === 'cursor' || config.ide === 'windsurf') ? 5000 : 3000;
|
|
4805
|
-
console.log(chalk.gray(` Waiting for ${config.ide} to start...`));
|
|
4806
4860
|
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
4807
4861
|
|
|
4808
|
-
|
|
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
|
+
|
|
4809
4866
|
try {
|
|
4810
|
-
|
|
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' :
|
|
4811
4873
|
config.ide === 'cursor' ? 'Cursor' :
|
|
4812
4874
|
config.ide === 'windsurf' ? 'Windsurf' :
|
|
4813
|
-
config.ide === 'vscode' ? 'Code' :
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
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;
|
|
4819
4910
|
}
|
|
4820
4911
|
}
|
|
4821
4912
|
|
|
4822
4913
|
spinner.text = 'Sending initial message to IDE...';
|
|
4823
|
-
const asManager =
|
|
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
|
+
}
|
|
4824
4921
|
|
|
4825
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
|
+
|
|
4826
4934
|
const result = await asManager.sendText(textToSend, config.ide);
|
|
4827
4935
|
|
|
4828
4936
|
if (!result.success) {
|
|
4829
4937
|
logIDEMessage(config.ide, `[FAILED] ${textToSend}`);
|
|
4830
4938
|
spinner.warn('Auto mode started but failed to send initial message to IDE');
|
|
4831
|
-
|
|
4832
|
-
|
|
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
|
+
}
|
|
4833
4952
|
} else {
|
|
4834
4953
|
logIDEMessage(config.ide, textToSend);
|
|
4835
4954
|
spinner.succeed(t('auto.direct.ide.autonomous.started'));
|
|
@@ -4863,6 +4982,14 @@ Example BAD questions (never ask these):
|
|
|
4863
4982
|
} else if (config.maxChats) {
|
|
4864
4983
|
console.log(chalk.gray(` ${t('auto.max.chats.label')}`), chalk.cyan(config.maxChats));
|
|
4865
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
|
+
}
|
|
4866
4993
|
} catch (error) {
|
|
4867
4994
|
spinner.stop();
|
|
4868
4995
|
spinner.fail('Failed to start autonomous mode');
|
|
@@ -4967,12 +5094,24 @@ async function listAgents() {
|
|
|
4967
5094
|
|
|
4968
5095
|
console.log(chalk.blue('\n📋 Available IDE Agents and Quota Status:\n'));
|
|
4969
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
|
+
|
|
4970
5108
|
// Get all provider definitions
|
|
4971
5109
|
const providers = getProviderDefinitions();
|
|
4972
5110
|
|
|
4973
5111
|
// Check each provider's quota status
|
|
4974
5112
|
for (const provider of providers) {
|
|
4975
|
-
const
|
|
5113
|
+
const hasSubAgents = Array.isArray(provider.subAgents) && provider.subAgents.length > 0;
|
|
5114
|
+
const rateLimitInfo = hasSubAgents ? { isRateLimited: false } : providerManager.getRateLimitInfo(provider.id);
|
|
4976
5115
|
|
|
4977
5116
|
let statusIcon = '✅';
|
|
4978
5117
|
let statusText = chalk.green('Available');
|
|
@@ -4983,18 +5122,9 @@ async function listAgents() {
|
|
|
4983
5122
|
statusText = chalk.yellow('Quota Limit');
|
|
4984
5123
|
|
|
4985
5124
|
if (rateLimitInfo.resetTime) {
|
|
4986
|
-
const
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
if (minutesRemaining > 60) {
|
|
4991
|
-
const hoursRemaining = Math.ceil(minutesRemaining / 60);
|
|
4992
|
-
quotaInfo = chalk.gray(` (resets in ${hoursRemaining}h)`);
|
|
4993
|
-
} else if (minutesRemaining > 0) {
|
|
4994
|
-
quotaInfo = chalk.gray(` (resets in ${minutesRemaining}m)`);
|
|
4995
|
-
} else {
|
|
4996
|
-
quotaInfo = chalk.gray(' (resetting soon)');
|
|
4997
|
-
}
|
|
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})`) : '';
|
|
4998
5128
|
}
|
|
4999
5129
|
|
|
5000
5130
|
if (rateLimitInfo.reason) {
|
|
@@ -5006,6 +5136,31 @@ async function listAgents() {
|
|
|
5006
5136
|
if (provider.description) {
|
|
5007
5137
|
console.log(` ${chalk.gray(provider.description)}`);
|
|
5008
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
|
+
}
|
|
5009
5164
|
}
|
|
5010
5165
|
|
|
5011
5166
|
console.log(chalk.gray('\n💡 Tip: Auto mode will automatically skip quota-limited agents\n'));
|
package/src/commands/ide.js
CHANGED
|
@@ -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
|
|
|
@@ -305,11 +305,20 @@ function parseRequirements(content) {
|
|
|
305
305
|
|
|
306
306
|
// Parse requirements
|
|
307
307
|
if (currentSection && line.startsWith('### ')) {
|
|
308
|
+
const title = line.replace(/^###\s*/, '').trim();
|
|
309
|
+
|
|
310
|
+
// Skip header-only lines like "Conflict Resolution:" which are used as group labels
|
|
311
|
+
if (/^Conflict Resolution:?$/i.test(title)) {
|
|
312
|
+
// Reset currentRequirement so we don't treat this header as an actual requirement
|
|
313
|
+
currentRequirement = null;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
|
|
308
317
|
if (currentRequirement) {
|
|
309
318
|
sections[currentSection].push(currentRequirement);
|
|
310
319
|
}
|
|
311
320
|
currentRequirement = {
|
|
312
|
-
title
|
|
321
|
+
title,
|
|
313
322
|
description: ''
|
|
314
323
|
};
|
|
315
324
|
} else if (currentRequirement && line.trim() && !line.startsWith('#')) {
|
package/src/commands/status.js
CHANGED
|
@@ -4,7 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const chalk = require('chalk');
|
|
5
5
|
const { getRepoPath } = require('../utils/config');
|
|
6
6
|
const { checkAutoModeStatus } = require('../utils/auto-mode');
|
|
7
|
-
const { getRequirementsPath, t } = require('vibecodingmachine-core');
|
|
7
|
+
const { getRequirementsPath, t, IDEHealthTracker } = require('vibecodingmachine-core');
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Format IDE name for display
|
|
@@ -34,12 +34,50 @@ async function show() {
|
|
|
34
34
|
const repoPath = await getRepoPath();
|
|
35
35
|
console.log(chalk.bold(`\n${t('status.title')}`));
|
|
36
36
|
console.log(chalk.gray(`${t('status.repository')}:`), chalk.cyan(formatPath(repoPath)));
|
|
37
|
+
|
|
37
38
|
const status = await checkAutoModeStatus();
|
|
38
39
|
console.log(chalk.gray(`${t('status.auto.mode')}:`), status.running ? chalk.green(t('status.running')) : chalk.yellow(t('status.stopped')));
|
|
40
|
+
|
|
39
41
|
if (status.running) {
|
|
40
42
|
console.log(chalk.gray(`${t('status.ide')}:`), chalk.cyan(formatIDEName(status.ide || 'cline')));
|
|
41
43
|
console.log(chalk.gray(`${t('status.chats')}:`), chalk.cyan(status.chatCount || 0));
|
|
42
44
|
}
|
|
45
|
+
|
|
46
|
+
// Display IDE health metrics
|
|
47
|
+
try {
|
|
48
|
+
const healthTracker = new IDEHealthTracker();
|
|
49
|
+
const allMetrics = await healthTracker.getAllHealthMetrics();
|
|
50
|
+
|
|
51
|
+
if (allMetrics.size > 0) {
|
|
52
|
+
console.log(chalk.bold('\n📊 IDE Health Metrics:'));
|
|
53
|
+
|
|
54
|
+
for (const [ideId, metrics] of allMetrics) {
|
|
55
|
+
const successRate = metrics.successRate > 0 ? `${(metrics.successRate * 100).toFixed(1)}%` : 'N/A';
|
|
56
|
+
const avgResponseTime = metrics.averageResponseTime > 0 ? `${Math.round(metrics.averageResponseTime / 1000)}s` : 'N/A';
|
|
57
|
+
|
|
58
|
+
console.log(chalk.gray(` ${formatIDEName(ideId)}:`));
|
|
59
|
+
console.log(chalk.gray(` Success Rate:`), chalk.green(successRate));
|
|
60
|
+
console.log(chalk.gray(` Avg Response:`), chalk.cyan(avgResponseTime));
|
|
61
|
+
console.log(chalk.gray(` Interactions:`), chalk.blue(`${metrics.totalInteractions}`));
|
|
62
|
+
console.log(chalk.gray(` Health:`),
|
|
63
|
+
metrics.consecutiveFailures === 0 ?
|
|
64
|
+
chalk.green('✅ Healthy') :
|
|
65
|
+
chalk.yellow(`⚠️ ${metrics.consecutiveFailures} consecutive failures`)
|
|
66
|
+
);
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Show recommended IDE
|
|
71
|
+
const recommendedIDE = await healthTracker.getRecommendedIDE();
|
|
72
|
+
if (recommendedIDE) {
|
|
73
|
+
console.log(chalk.bold('💡 Recommended IDE:'), chalk.cyan(formatIDEName(recommendedIDE)));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (error) {
|
|
77
|
+
// Health tracking not available or error reading metrics
|
|
78
|
+
console.log(chalk.gray('\n📊 IDE Health:'), chalk.yellow('Not available'));
|
|
79
|
+
}
|
|
80
|
+
|
|
43
81
|
console.log();
|
|
44
82
|
}
|
|
45
83
|
|
|
@@ -3,6 +3,7 @@ const {
|
|
|
3
3
|
getProviderPreferences,
|
|
4
4
|
saveProviderPreferences
|
|
5
5
|
} = require('./provider-registry');
|
|
6
|
+
const { AppleScriptManager } = require('vibecodingmachine-core');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Check if Antigravity agent has hit a rate limit.
|
|
@@ -34,12 +35,20 @@ function checkAntigravityRateLimit(stderr) {
|
|
|
34
35
|
* @returns {Promise<{success: boolean, nextProvider: string|null, error: string|null}>}
|
|
35
36
|
*/
|
|
36
37
|
async function handleAntigravityRateLimit() {
|
|
37
|
-
console.log(chalk.yellow('Antigravity rate limit detected.
|
|
38
|
+
console.log(chalk.yellow('Antigravity rate limit detected. Attempting to switch models within Antigravity first...'));
|
|
38
39
|
|
|
39
40
|
try {
|
|
41
|
+
const appleScript = new AppleScriptManager();
|
|
42
|
+
const result = await appleScript.handleAntigravityQuotaLimit();
|
|
43
|
+
|
|
44
|
+
if (result.success) {
|
|
45
|
+
console.log(chalk.green(`Successfully switched to Antigravity model: ${result.model}`));
|
|
46
|
+
return { success: true, nextProvider: 'antigravity', modelSwitched: true, nextModel: result.model, error: null };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(chalk.yellow(`Could not switch models within Antigravity: ${result.error || 'Unknown error'}. Switching to next provider...`));
|
|
50
|
+
|
|
40
51
|
const prefs = await getProviderPreferences();
|
|
41
|
-
prefs.enabled.antigravity = false;
|
|
42
|
-
await saveProviderPreferences(prefs);
|
|
43
52
|
|
|
44
53
|
const nextProvider = prefs.order.find(p => p !== 'antigravity' && prefs.enabled[p]);
|
|
45
54
|
|
|
@@ -50,7 +59,7 @@ async function handleAntigravityRateLimit() {
|
|
|
50
59
|
return { success: false, nextProvider: null, error: 'No fallback providers available.' };
|
|
51
60
|
}
|
|
52
61
|
} catch (error) {
|
|
53
|
-
return { success: false, nextProvider: null, error:
|
|
62
|
+
return { success: false, nextProvider: null, error: `Failed to handle rate limit: ${error.message}` };
|
|
54
63
|
}
|
|
55
64
|
}
|
|
56
65
|
|