vibecodingmachine-cli 2026.3.10-1812 → 2026.3.14-1528

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 (36) hide show
  1. package/README.md +85 -85
  2. package/bin/vibecodingmachine.js +3 -0
  3. package/package.json +4 -3
  4. package/scripts/postinstall.js +161 -161
  5. package/src/commands/auth.js +100 -100
  6. package/src/commands/auto-direct.js +16 -5
  7. package/src/commands/auto-execution.js +25 -0
  8. package/src/commands/auto-requirement-management.js +8 -8
  9. package/src/commands/auto-status-helpers.js +5 -3
  10. package/src/commands/computers.js +318 -318
  11. package/src/commands/feature.js +123 -123
  12. package/src/commands/locale.js +72 -72
  13. package/src/commands/repo.js +163 -163
  14. package/src/commands/setup.js +93 -93
  15. package/src/commands/sync.js +287 -287
  16. package/src/index.js +5 -5
  17. package/src/utils/agent-selector.js +50 -50
  18. package/src/utils/asset-cleanup.js +60 -60
  19. package/src/utils/auto-mode-ansi-ui.js +237 -237
  20. package/src/utils/auto-mode-simple-ui.js +141 -141
  21. package/src/utils/copy-with-progress.js +167 -167
  22. package/src/utils/download-with-progress.js +84 -84
  23. package/src/utils/keyboard-handler.js +153 -153
  24. package/src/utils/kiro-installer.js +178 -178
  25. package/src/utils/logger.js +4 -4
  26. package/src/utils/persistent-header.js +114 -114
  27. package/src/utils/prompt-helper.js +63 -63
  28. package/src/utils/provider-checker/agent-checker.js +25 -1
  29. package/src/utils/provider-checker/agent-runner.js +115 -37
  30. package/src/utils/provider-checker/agents-manager.js +210 -0
  31. package/src/utils/provider-checker/provider-validator.js +5 -49
  32. package/src/utils/provider-checker/requirements-manager.js +86 -65
  33. package/src/utils/provider-checker/test-requirements.js +25 -17
  34. package/src/utils/status-card.js +121 -121
  35. package/src/utils/stdout-interceptor.js +127 -127
  36. package/src/utils/user-tracking.js +299 -299
@@ -1,64 +1,64 @@
1
- const inquirer = require('inquirer');
2
- const chalk = require('chalk');
3
-
4
- /**
5
- * Enhanced inquirer.prompt that shows a helpful hint for questions with defaults
6
- * @param {Array} questions - Array of inquirer question objects
7
- * @param {Object} options - Options object
8
- * @param {boolean} options.showDefaultHint - Whether to show the default hint (default: true)
9
- * @returns {Promise} - Promise resolving to answers
10
- */
11
- async function promptWithDefaults(questions, options = {}) {
12
- const { showDefaultHint = true } = options;
13
-
14
- // Check if any questions have defaults and if we should show the hint
15
- const hasDefaults = questions.some(q => q.default !== undefined);
16
-
17
- if (showDefaultHint && hasDefaults) {
18
- // Show the hint in gray before the first question with a default
19
- console.log(chalk.gray('(Capital letters are defaults--press return to select the defaults)'));
20
- }
21
-
22
- return await inquirer.prompt(questions);
23
- }
24
-
25
- /**
26
- * Track whether we've shown the default hint in this session
27
- * This ensures we only show it once per CLI session
28
- */
29
- let hasShownDefaultHint = false;
30
-
31
- /**
32
- * Enhanced inquirer.prompt that shows the default hint only once per session
33
- * @param {Array} questions - Array of inquirer question objects
34
- * @param {Object} options - Options object
35
- * @param {boolean} options.forceShowHint - Force showing the hint even if already shown
36
- * @returns {Promise} - Promise resolving to answers
37
- */
38
- async function promptWithDefaultsOnce(questions, options = {}) {
39
- const { forceShowHint = false } = options;
40
-
41
- // Check if any questions have defaults
42
- const hasDefaults = questions.some(q => q.default !== undefined);
43
-
44
- if (hasDefaults && (!hasShownDefaultHint || forceShowHint)) {
45
- // Show the hint in gray before the first question with a default
46
- console.log(chalk.gray('(Capital letters are defaults--press return to select the defaults)'));
47
- hasShownDefaultHint = true;
48
- }
49
-
50
- return await inquirer.prompt(questions);
51
- }
52
-
53
- /**
54
- * Reset the hint tracking (useful for testing or new sessions)
55
- */
56
- function resetDefaultHint() {
57
- hasShownDefaultHint = false;
58
- }
59
-
60
- module.exports = {
61
- promptWithDefaults,
62
- promptWithDefaultsOnce,
63
- resetDefaultHint
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+
4
+ /**
5
+ * Enhanced inquirer.prompt that shows a helpful hint for questions with defaults
6
+ * @param {Array} questions - Array of inquirer question objects
7
+ * @param {Object} options - Options object
8
+ * @param {boolean} options.showDefaultHint - Whether to show the default hint (default: true)
9
+ * @returns {Promise} - Promise resolving to answers
10
+ */
11
+ async function promptWithDefaults(questions, options = {}) {
12
+ const { showDefaultHint = true } = options;
13
+
14
+ // Check if any questions have defaults and if we should show the hint
15
+ const hasDefaults = questions.some(q => q.default !== undefined);
16
+
17
+ if (showDefaultHint && hasDefaults) {
18
+ // Show the hint in gray before the first question with a default
19
+ console.log(chalk.gray('(Capital letters are defaults--press return to select the defaults)'));
20
+ }
21
+
22
+ return await inquirer.prompt(questions);
23
+ }
24
+
25
+ /**
26
+ * Track whether we've shown the default hint in this session
27
+ * This ensures we only show it once per CLI session
28
+ */
29
+ let hasShownDefaultHint = false;
30
+
31
+ /**
32
+ * Enhanced inquirer.prompt that shows the default hint only once per session
33
+ * @param {Array} questions - Array of inquirer question objects
34
+ * @param {Object} options - Options object
35
+ * @param {boolean} options.forceShowHint - Force showing the hint even if already shown
36
+ * @returns {Promise} - Promise resolving to answers
37
+ */
38
+ async function promptWithDefaultsOnce(questions, options = {}) {
39
+ const { forceShowHint = false } = options;
40
+
41
+ // Check if any questions have defaults
42
+ const hasDefaults = questions.some(q => q.default !== undefined);
43
+
44
+ if (hasDefaults && (!hasShownDefaultHint || forceShowHint)) {
45
+ // Show the hint in gray before the first question with a default
46
+ console.log(chalk.gray('(Capital letters are defaults--press return to select the defaults)'));
47
+ hasShownDefaultHint = true;
48
+ }
49
+
50
+ return await inquirer.prompt(questions);
51
+ }
52
+
53
+ /**
54
+ * Reset the hint tracking (useful for testing or new sessions)
55
+ */
56
+ function resetDefaultHint() {
57
+ hasShownDefaultHint = false;
58
+ }
59
+
60
+ module.exports = {
61
+ promptWithDefaults,
62
+ promptWithDefaultsOnce,
63
+ resetDefaultHint
64
64
  };
@@ -173,6 +173,7 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
173
173
  */
174
174
  async function checkProvider(providerId, config = {}, repoPath, onProgress = null, signal = null) {
175
175
  const { getProviderDefinition } = require('../provider-registry');
176
+ const { VERIFIED_HEADER } = require('./test-requirements');
176
177
  console.log(`[AGENT CHECK] Checking provider: ${providerId} in repo: ${repoPath}`);
177
178
 
178
179
  const def = getProviderDefinition(providerId);
@@ -191,6 +192,15 @@ async function checkProvider(providerId, config = {}, repoPath, onProgress = nul
191
192
  // Add test requirement
192
193
  addTestRequirement(reqPath, resultFile);
193
194
 
195
+ // Log initial status (requirement in PENDING section)
196
+ console.log(`[AGENT CHECK] Initial STATUS: PREPARING - Test requirement added to ${reqPath}`);
197
+ let initialContent = '';
198
+ try {
199
+ initialContent = fs.readFileSync(reqPath, 'utf8');
200
+ } catch (err) {
201
+ console.log(`[AGENT CHECK] Warning: Could not read requirements file: ${err.message}`);
202
+ }
203
+
194
204
  try {
195
205
  // Choose timeout based on provider type
196
206
  const timeoutMs = def.type === 'cli' ? DIRECT_TIMEOUT_MS : IDE_TIMEOUT_MS;
@@ -205,12 +215,26 @@ async function checkProvider(providerId, config = {}, repoPath, onProgress = nul
205
215
  fs.unlinkSync(resultFile);
206
216
  }
207
217
 
218
+ // If we reached "DONE" status, the agent is communicating successfully
219
+ // The automation layer works - text was sent and processed
220
+ if (result.status === 'success') {
221
+ console.log(`[AGENT CHECK] ✅ Test PASSED: Agent reached DONE status - communication verified`);
222
+ return {
223
+ ...result,
224
+ requirementLeftPending: false
225
+ };
226
+ }
227
+
228
+ console.log(`[AGENT CHECK] ❌ Test FAILED: Agent check did not reach DONE status`);
208
229
  return {
209
- ...result,
230
+ status: 'error',
231
+ message: 'Agent check failed: did not reach DONE status',
232
+ checkedAt: result.checkedAt,
210
233
  requirementLeftPending: false
211
234
  };
212
235
  } catch (error) {
213
236
  // Ensure cleanup on error
237
+ console.log(`[AGENT CHECK] Error during check: ${error.message}`);
214
238
  removeTestRequirement(reqPath);
215
239
  if (fs.existsSync(resultFile)) {
216
240
  fs.unlinkSync(resultFile);
@@ -7,7 +7,7 @@ const chokidar = require('chokidar');
7
7
 
8
8
  const { NODE_EXECUTABLE } = require('./node-detector');
9
9
  const { IDE_INFO, isIDERunning, openIDEApp } = require('./ide-manager');
10
- const { getResultFilePath, addTestRequirement, removeTestRequirement } = require('./requirements-manager');
10
+ const { getResultFilePath, getAgentsFilePath, updateAgentStatus, getAgentStatus } = require('./agents-manager');
11
11
 
12
12
  // Timeout for direct LLM round-trips via auto:direct (ms)
13
13
  const DIRECT_TIMEOUT_MS = 60000;
@@ -15,7 +15,6 @@ const DIRECT_TIMEOUT_MS = 60000;
15
15
  const IDE_TIMEOUT_MS = 90000;
16
16
 
17
17
  const CLI_ENTRY_POINT = path.resolve(path.join(__dirname, '../../../bin/vibecodingmachine.js'));
18
- const TEST_REQ_TITLE = 'VCM agent connectivity check';
19
18
 
20
19
  /**
21
20
  * Run an agent for 1 iteration and watch for the result file to be written.
@@ -34,11 +33,6 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
34
33
  // Remove stale result file so we detect a fresh write
35
34
  try { fs.unlinkSync(resultFile); } catch { }
36
35
 
37
- // Calculate requirements path early so it's available in the Promise scope
38
- const os = require('os');
39
- const hostname = os.hostname();
40
- const requirementsPath = path.join(repoPath, `REQUIREMENTS-${hostname}.md`);
41
-
42
36
  // For IDE providers: verify the IDE is running/installed, install if needed
43
37
  let ideLaunchNote = '';
44
38
  if (def.type === 'ide') {
@@ -52,7 +46,7 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
52
46
  ideLaunchNote = ` (VCM launched ${info.app})`;
53
47
  } else {
54
48
  // Failed to launch on macOS - check if installed
55
- const { IDEInstaller } = require('../../../electron-app/src/main/ide-installer');
49
+ const { IDEInstaller } = require('../../../../electron-app/src/main/ide-installer');
56
50
  const installer = new IDEInstaller();
57
51
  const ideKey = def.ide || providerId;
58
52
  const checkResult = await installer.checkIDEInstallation(ideKey);
@@ -67,7 +61,7 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
67
61
  }
68
62
  // Windows: check if installed, install if possible, launch if installed
69
63
  else if (process.platform === 'win32') {
70
- const { IDEInstaller } = require('../../../electron-app/src/main/ide-installer');
64
+ const { IDEInstaller } = require('../../../../electron-app/src/main/ide-installer');
71
65
  const installer = new IDEInstaller();
72
66
  const ideKey = def.ide || providerId;
73
67
  const ideConfig = IDEInstaller.IDE_CONFIG[ideKey];
@@ -147,7 +141,7 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
147
141
  }
148
142
  }
149
143
 
150
- return new Promise((resolve) => {
144
+ return new Promise(async (resolve) => {
151
145
  let resolved = false;
152
146
  let child = null;
153
147
  let output = '';
@@ -163,12 +157,32 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
163
157
  clearInterval(cancelCheckInterval);
164
158
  try { if (child) child.kill(); } catch { }
165
159
 
166
- // Remove test requirement after check completes (pass or fail)
160
+ // Update agent status based on result
167
161
  try {
168
- removeTestRequirement(requirementsPath);
169
- console.log(`[AGENT CHECK] Removed test requirement from ${requirementsPath}`);
162
+ if (result.status === 'success') {
163
+ // Status already set to 'operational' by the IDE agent
164
+ console.log(`[AGENT CHECK] ${providerId} check passed - status is 'operational'`);
165
+ } else if (result.rateLimited) {
166
+ updateAgentStatus(providerId, 'rate_limited', {
167
+ rateLimitResume: result.rateLimitResume
168
+ });
169
+ console.log(`[AGENT CHECK] Set ${providerId} status to 'rate_limited'`);
170
+ } else {
171
+ updateAgentStatus(providerId, 'error', {
172
+ error: result.message
173
+ });
174
+ console.log(`[AGENT CHECK] Set ${providerId} status to 'error'`);
175
+ }
170
176
  } catch (err) {
171
- console.warn(`[AGENT CHECK] Could not remove test requirement: ${err.message}`);
177
+ console.warn(`[AGENT CHECK] Could not update agent status: ${err.message}`);
178
+ }
179
+
180
+ // Notify UI that check is done
181
+ if (onProgress) {
182
+ const statusMessage = result.status === 'success'
183
+ ? 'Check completed successfully'
184
+ : (result.rateLimited ? 'Rate limited' : result.message);
185
+ onProgress(providerId, 'done', result.status !== 'success' ? result.message : null, statusMessage);
172
186
  }
173
187
 
174
188
  resolve(result);
@@ -191,20 +205,31 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
191
205
  watcher.on('add', checkResult);
192
206
  watcher.on('change', checkResult);
193
207
 
194
- // Also watch requirements file for status changes to [DONE]
195
- requirementsWatcher = chokidar.watch(requirementsPath, { persistent: true, ignoreInitial: true });
208
+ // Watch agents.json for status changes to 'operational'
209
+ // Use debouncing to prevent multiple rapid status checks
210
+ let debounceTimer = null;
211
+ const agentsFilePath = getAgentsFilePath();
212
+ requirementsWatcher = chokidar.watch(agentsFilePath, { persistent: true, ignoreInitial: true });
196
213
  requirementsWatcher.on('change', () => {
214
+ // Clear existing debounce timer
215
+ if (debounceTimer) clearTimeout(debounceTimer);
216
+
217
+ // Set new timer (50ms debounce - fast response while avoiding duplicate reads)
218
+ debounceTimer = setTimeout(() => {
197
219
  try {
198
- const content = fs.readFileSync(requirementsPath, 'utf8');
199
- // Check if test requirement status is [DONE]
200
- if (content.includes(TEST_REQ_TITLE) && content.match(/VCM agent connectivity check[\s\S]*?Status:.*?\[DONE\]/)) {
201
- console.log(`[AGENT CHECK] Detected [DONE] status in requirements file for ${providerId}`);
202
- if (onProgress) onProgress(providerId, 'response_detected', null, 'Status updated to DONE.');
203
- done({ status: 'success', message: 'Agent updated status to DONE', checkedAt });
220
+ const agentStatus = getAgentStatus(providerId);
221
+ console.log(`[AGENT CHECK] Current status for ${providerId}: ${agentStatus.status}`);
222
+
223
+ // Check if agent status is 'operational'
224
+ if (agentStatus.status === 'operational') {
225
+ console.log(`[AGENT CHECK] Detected 'operational' status in agents.json for ${providerId}`);
226
+ if (onProgress) onProgress(providerId, 'response_detected', null, 'Status updated to operational.');
227
+ done({ status: 'success', message: 'Agent updated status to operational', checkedAt });
204
228
  }
205
229
  } catch (err) {
206
- console.warn(`[AGENT CHECK] Error reading requirements file: ${err.message}`);
230
+ console.warn(`[AGENT CHECK] Error reading agents.json: ${err.message}`);
207
231
  }
232
+ }, 50); // End of debounce setTimeout - reduced from 300ms to 50ms for faster response
208
233
  });
209
234
 
210
235
  const timeout = setTimeout(() => {
@@ -245,14 +270,68 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
245
270
  onProgress(providerId, 'awaiting', null, 'Awaiting response...');
246
271
  }
247
272
 
248
- let args;
273
+ // For IDE providers: send connectivity test directly using Windows automation
274
+ // Don't spawn auto:start since it expects requirements files (which we removed)
249
275
  if (def.type === 'ide') {
250
- args = [CLI_ENTRY_POINT, 'auto:start', '--ide', def.ide || providerId, '--max-chats', '5'];
251
- if (def.defaultModel) args.push('--ide-model', String(def.defaultModel));
252
- if (def.extension) args.push('--extension', String(def.extension));
253
- } else {
254
- // Direct LLM: use auto:direct --provider to force this specific provider
276
+ try {
277
+ const { generateConnectivityTestMessage } = require('./agents-manager');
278
+ const { WindowsAutomationManager } = require('vibecodingmachine-core');
279
+
280
+ console.log(`[AGENT CHECK] Sending connectivity test directly to ${providerId}`);
281
+
282
+ // Get the test message
283
+ const testMessage = generateConnectivityTestMessage(def.name || providerId, providerId);
284
+
285
+ // Send directly to IDE using Windows automation
286
+ const automation = new WindowsAutomationManager();
287
+
288
+ // Call the appropriate method based on provider
289
+ let sendResult;
290
+ try {
291
+ switch(providerId.toLowerCase()) {
292
+ case 'windsurf':
293
+ sendResult = await automation.sendTextToWindsurf(testMessage);
294
+ break;
295
+ case 'cursor':
296
+ sendResult = await automation.sendTextToCursor(testMessage);
297
+ break;
298
+ default:
299
+ sendResult = { success: false, error: `No automation method for ${providerId}` };
300
+ }
301
+ } catch (sendErr) {
302
+ sendResult = { success: false, error: sendErr.message };
303
+ }
304
+
305
+ if (!sendResult || !sendResult.success) {
306
+ done({
307
+ status: 'error',
308
+ message: `Failed to send text to IDE: ${sendResult?.error || 'Unknown error'}`,
309
+ checkedAt
310
+ });
311
+ return;
312
+ }
313
+
314
+ console.log(`[AGENT CHECK] Successfully sent connectivity test to ${providerId}`);
315
+ console.log(`[AGENT CHECK] Waiting for IDE to update agents.json status to 'operational'...`);
316
+
317
+ // Don't spawn child process - just wait for file watcher to detect status change
318
+ return;
319
+
320
+ } catch (err) {
321
+ console.error(`[AGENT CHECK] Error sending connectivity test: ${err.message}`);
322
+ done({ status: 'error', message: `Error sending test: ${err.message}`, checkedAt });
323
+ return;
324
+ }
325
+ }
326
+
327
+ // For direct LLM providers: still use auto:direct
328
+ let args;
329
+ if (def.type !== 'ide') {
255
330
  args = [CLI_ENTRY_POINT, 'auto:direct', '--provider', providerId, '--max-chats', '1'];
331
+ } else {
332
+ // IDE handling moved above - this shouldn't be reached
333
+ done({ status: 'error', message: 'IDE handling logic error', checkedAt });
334
+ return;
256
335
  }
257
336
 
258
337
  // Verify CLI entry point exists and is readable
@@ -292,15 +371,13 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
292
371
  console.log(`[AGENT CHECK] CWD: ${repoPath}`);
293
372
  console.log(`[AGENT CHECK] Result file: ${resultFile}`);
294
373
 
295
- // Add test requirement to REQUIREMENTS-<hostname>.md
296
- // This tells the IDE agent to respond, and will be deleted after check completes
297
- // (requirementsPath already calculated at function start)
298
- console.log(`[AGENT CHECK] Using requirements file: ${requirementsPath}`);
374
+ // Set agent status to 'checking' in agents.json before sending test
375
+ console.log(`[AGENT CHECK] Setting ${providerId} status to 'checking' in agents.json`);
299
376
  try {
300
- addTestRequirement(requirementsPath, resultFile);
301
- console.log(`[AGENT CHECK] Added test requirement to ${requirementsPath}`);
377
+ updateAgentStatus(providerId, 'checking');
378
+ console.log(`[AGENT CHECK] Set ${providerId} status to 'checking'`);
302
379
  } catch (err) {
303
- console.warn(`[AGENT CHECK] Could not add test requirement: ${err.message}`);
380
+ console.warn(`[AGENT CHECK] Could not set agent status: ${err.message}`);
304
381
  }
305
382
 
306
383
  // Prepare environment - inherit all process.env and add NODE_PATH if needed
@@ -366,7 +443,8 @@ async function runAgentCheck(providerId, def, repoPath, resultFile, timeoutMs, s
366
443
 
367
444
  // Check for DONE status in output (indicates successful completion)
368
445
  if (code === 0 && output.includes('Status: ✅ DONE') && output.includes('Requirement complete')) {
369
- console.log(`[AGENT CHECK] Detected successful completion in output for ${providerId}`);
446
+ console.log(`[AGENT CHECK] Detected DONE in console output for ${providerId}`);
447
+ console.log(`[AGENT CHECK] ✅ Test PASSED: Agent reached DONE status - communication verified`);
370
448
  if (onProgress) onProgress(providerId, 'response_detected', null, 'Auto mode completed successfully.');
371
449
  done({ status: 'success', message: 'Agent completed requirement successfully', checkedAt });
372
450
  return;
@@ -0,0 +1,210 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+
7
+ /**
8
+ * Parse relative time string (e.g., "5h 30m", "2h", "45m") to absolute Date
9
+ * @param {string} relativeTime - Time string like "5h 30m"
10
+ * @returns {Date} Absolute date when the time will occur
11
+ */
12
+ function parseRelativeTimeToAbsolute(relativeTime) {
13
+ const now = new Date();
14
+ let totalMinutes = 0;
15
+
16
+ // Parse hours
17
+ const hoursMatch = relativeTime.match(/(\d+)h/);
18
+ if (hoursMatch) {
19
+ totalMinutes += parseInt(hoursMatch[1]) * 60;
20
+ }
21
+
22
+ // Parse minutes
23
+ const minutesMatch = relativeTime.match(/(\d+)m/);
24
+ if (minutesMatch) {
25
+ totalMinutes += parseInt(minutesMatch[1]);
26
+ }
27
+
28
+ // Parse seconds
29
+ const secondsMatch = relativeTime.match(/(\d+)s/);
30
+ if (secondsMatch) {
31
+ totalMinutes += parseInt(secondsMatch[1]) / 60;
32
+ }
33
+
34
+ return new Date(now.getTime() + totalMinutes * 60 * 1000);
35
+ }
36
+
37
+ /**
38
+ * Get the path to the result file used by agent checks
39
+ */
40
+ function getResultFilePath(repoPath) {
41
+ return path.join(repoPath, '.vibecodingmachine', 'temp', 'TEMP_agent_check.txt');
42
+ }
43
+
44
+ /**
45
+ * Get the path to the user's agents.json file in ~/.vibecodingmachine/
46
+ */
47
+ function getAgentsFilePath() {
48
+ const homeDir = os.homedir();
49
+ const vcmDir = path.join(homeDir, '.vibecodingmachine');
50
+
51
+ // Ensure directory exists
52
+ if (!fs.existsSync(vcmDir)) {
53
+ fs.mkdirSync(vcmDir, { recursive: true });
54
+ }
55
+
56
+ return path.join(vcmDir, 'agents.json');
57
+ }
58
+
59
+ /**
60
+ * Read the agents.json file
61
+ * Returns the agents object or creates a new one if it doesn't exist
62
+ */
63
+ function readAgentsFile() {
64
+ const agentsPath = getAgentsFilePath();
65
+
66
+ try {
67
+ if (fs.existsSync(agentsPath)) {
68
+ const content = fs.readFileSync(agentsPath, 'utf8');
69
+ return JSON.parse(content);
70
+ }
71
+ } catch (err) {
72
+ console.warn(`[AGENTS] Error reading agents file: ${err.message}`);
73
+ }
74
+
75
+ // Return default structure
76
+ return { agents: {} };
77
+ }
78
+
79
+ /**
80
+ * Write the agents.json file
81
+ */
82
+ function writeAgentsFile(agentsData) {
83
+ const agentsPath = getAgentsFilePath();
84
+
85
+ try {
86
+ fs.writeFileSync(agentsPath, JSON.stringify(agentsData, null, 2), 'utf8');
87
+ return true;
88
+ } catch (err) {
89
+ console.error(`[AGENTS] Error writing agents file: ${err.message}`);
90
+ return false;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Update agent status
96
+ * @param {string} agentId - Agent ID (e.g., 'windsurf', 'cursor', 'antigravity')
97
+ * @param {string} status - Status: 'unknown', 'checking', 'operational', 'rate_limited', 'error'
98
+ * @param {object} metadata - Optional metadata (e.g., { rateLimitResume: '5m30s', error: 'message' })
99
+ */
100
+ function updateAgentStatus(agentId, status, metadata = {}) {
101
+ const agentsData = readAgentsFile();
102
+
103
+ // Ensure agents object exists
104
+ if (!agentsData.agents) {
105
+ agentsData.agents = {};
106
+ }
107
+
108
+ // Ensure agent entry exists
109
+ if (!agentsData.agents[agentId]) {
110
+ agentsData.agents[agentId] = {
111
+ id: agentId,
112
+ status: 'unknown'
113
+ };
114
+ }
115
+
116
+ // Update status
117
+ agentsData.agents[agentId].status = status;
118
+ agentsData.agents[agentId].lastChecked = new Date().toISOString();
119
+
120
+ // Add metadata
121
+ if (status === 'rate_limited' && metadata.rateLimitResume) {
122
+ // Convert relative time (e.g., "5h 30m") to absolute timestamp
123
+ const resumeTime = parseRelativeTimeToAbsolute(metadata.rateLimitResume);
124
+ agentsData.agents[agentId].rateLimitResumeAt = resumeTime.toISOString();
125
+ agentsData.agents[agentId].rateLimitResume = metadata.rateLimitResume; // Keep original for reference
126
+ }
127
+
128
+ if (status === 'error' && metadata.error) {
129
+ agentsData.agents[agentId].error = metadata.error;
130
+ }
131
+
132
+ // Clear rate limit fields if no longer rate limited
133
+ if (status !== 'rate_limited') {
134
+ delete agentsData.agents[agentId].rateLimitResumeAt;
135
+ delete agentsData.agents[agentId].rateLimitResume;
136
+ }
137
+
138
+ // Write file
139
+ writeAgentsFile(agentsData);
140
+
141
+ console.log(`[AGENTS] Updated ${agentId} status to: ${status}`);
142
+
143
+ return agentsData.agents[agentId];
144
+ }
145
+
146
+ /**
147
+ * Get agent status
148
+ * @param {string} agentId - Agent ID
149
+ * @returns {object} Agent status object
150
+ */
151
+ function getAgentStatus(agentId) {
152
+ const agentsData = readAgentsFile();
153
+
154
+ if (agentsData.agents && agentsData.agents[agentId]) {
155
+ return agentsData.agents[agentId];
156
+ }
157
+
158
+ return {
159
+ id: agentId,
160
+ status: 'unknown'
161
+ };
162
+ }
163
+
164
+ /**
165
+ * Get all agents with their statuses
166
+ * @returns {object} All agents
167
+ */
168
+ function getAllAgents() {
169
+ const agentsData = readAgentsFile();
170
+ return agentsData.agents || {};
171
+ }
172
+
173
+ /**
174
+ * Generate the connectivity test message for IDE agents
175
+ * @param {string} agentName - Human-readable agent name (e.g., 'Windsurf', 'Cursor')
176
+ * @param {string} agentId - Agent ID (e.g., 'windsurf', 'cursor')
177
+ * @returns {string} Test message to send to IDE
178
+ */
179
+ function generateConnectivityTestMessage(agentName, agentId) {
180
+ const agentsPath = getAgentsFilePath();
181
+ const relativeAgentsPath = agentsPath.replace(os.homedir(), '~');
182
+
183
+ return `# VCM Agent Connectivity Check
184
+
185
+ ACTION REQUIRED - VCM Connectivity Test:
186
+
187
+ VCM is testing communication with your IDE. To complete this test, please:
188
+
189
+ 1. Open the file at: ${relativeAgentsPath}
190
+ 2. Find the "${agentId}" entry
191
+ 3. Change the "status" field from "checking" to "operational"
192
+ 4. Save the file
193
+
194
+ Example:
195
+ Before: "status": "checking"
196
+ After: "status": "operational"
197
+
198
+ VCM will detect the change within seconds and complete the test.`;
199
+ }
200
+
201
+ module.exports = {
202
+ getResultFilePath,
203
+ getAgentsFilePath,
204
+ readAgentsFile,
205
+ writeAgentsFile,
206
+ updateAgentStatus,
207
+ getAgentStatus,
208
+ getAllAgents,
209
+ generateConnectivityTestMessage
210
+ };