vibecodingmachine-cli 2025.12.6-1702 → 2025.12.22-2230

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -35,7 +35,13 @@ const { cleanupBrokenAssets } = require('../src/utils/asset-cleanup');
35
35
  const { program } = require('commander');
36
36
  const chalk = require('chalk');
37
37
  const inquirer = require('inquirer');
38
- const { execSync } = require('child_process');
38
+ const { execSync, spawn } = require('child_process');
39
+ const ora = require('ora');
40
+ const https = require('https');
41
+ const os = require('os');
42
+ const { pipeline } = require('stream');
43
+ const { promisify } = require('util');
44
+ const streamPipeline = promisify(pipeline);
39
45
  // const path = require('path'); // Moved to top for .env.auth loading
40
46
  const packageJson = require('../package.json');
41
47
 
@@ -117,6 +123,11 @@ program
117
123
  .option('--no-never-stop', 'Disable never stop mode')
118
124
  .action(autoCommands.config);
119
125
 
126
+ program
127
+ .command('auto:agents')
128
+ .description('List available agents and their quota status')
129
+ .action(autoCommands.listAgents);
130
+
120
131
  // Requirements management commands
121
132
  program
122
133
  .command('req:list')
@@ -298,18 +309,33 @@ program
298
309
  // Error handling
299
310
  process.on('uncaughtException', (error) => {
300
311
  console.error(chalk.red('Error:'), error.message);
312
+ if (process.env.DEBUG) {
313
+ console.error(chalk.gray('Stack:'), error.stack);
314
+ }
301
315
  process.exit(1);
302
316
  });
303
317
 
304
318
  process.on('unhandledRejection', (error) => {
305
319
  console.error(chalk.red('Error:'), error.message);
320
+ if (process.env.DEBUG) {
321
+ console.error(chalk.gray('Stack:'), error.stack);
322
+ }
306
323
  process.exit(1);
307
324
  });
308
325
 
309
326
  // Check for updates and display notification
310
327
  async function checkForUpdates() {
311
328
  try {
329
+ if (process.env.VCM_SKIP_UPDATE_CHECK === '1' || process.env.VCM_SKIP_UPDATE_CHECK === 'true') {
330
+ return;
331
+ }
312
332
  const { checkForCLIUpdates } = require('vibecodingmachine-core');
333
+ // If running inside the repository (local development), skip update checks entirely
334
+ const isDevWorkspace = fs.existsSync(path.join(rootDir, '.git'));
335
+ if (isDevWorkspace) {
336
+ console.log(chalk.yellow('\nDetected local development workspace; skipping update check.'));
337
+ return;
338
+ }
313
339
  console.log(chalk.gray(`\n🔍 Checking for updates... (current: v${packageJson.version})`));
314
340
  const updateInfo = await checkForCLIUpdates(packageJson.version);
315
341
  console.log(chalk.gray(` Update check result: ${JSON.stringify(updateInfo)}\n`));
@@ -335,14 +361,136 @@ async function checkForUpdates() {
335
361
  if (answer.shouldUpdate) {
336
362
  console.log(chalk.cyan('\n🔄 Updating VibeCodingMachine CLI...\n'));
337
363
  try {
338
- // Run npm install with inherited stdio to show progress
339
- execSync('npm install -g vibecodingmachine-cli@latest', {
340
- stdio: 'inherit',
341
- encoding: 'utf8'
364
+ // If we're running from the repository (local dev), avoid performing a global install
365
+ const isDevWorkspace = fs.existsSync(path.join(rootDir, '.git'));
366
+ if (isDevWorkspace) {
367
+ console.log(chalk.yellow('\nDetected local development workspace; skipping automatic global install.'));
368
+ console.log(chalk.gray(' To update the globally installed CLI run:') + ' ' + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
369
+ // Do not attempt global install during development to avoid confusing local dev flow
370
+ return;
371
+ }
372
+ const spinner = ora('Fetching package metadata...').start();
373
+
374
+ // Get latest package metadata from npm registry
375
+ const registryUrl = 'https://registry.npmjs.org/vibecodingmachine-cli/latest';
376
+ const meta = await new Promise((resolve, reject) => {
377
+ https.get(registryUrl, (res) => {
378
+ let data = '';
379
+ res.on('data', (chunk) => data += chunk);
380
+ res.on('end', () => {
381
+ try {
382
+ resolve(JSON.parse(data));
383
+ } catch (err) {
384
+ reject(err);
385
+ }
386
+ });
387
+ }).on('error', reject);
342
388
  });
343
- console.log(chalk.green('\n✅ Successfully updated to v' + updateInfo.latestVersion + '!'));
344
- console.log(chalk.gray(' Please restart your command to use the new version.\n'));
345
- process.exit(0);
389
+
390
+ const tarball = meta && meta.dist && meta.dist.tarball;
391
+ if (!tarball) {
392
+ spinner.fail('Could not determine tarball URL for update');
393
+ console.log(chalk.yellow(' You can manually update with: ') + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
394
+ return;
395
+ }
396
+
397
+ spinner.text = 'Resolving tarball...';
398
+
399
+ // HEAD to get content-length
400
+ const totalBytes = await new Promise((resolve) => {
401
+ const req = https.request(tarball, { method: 'HEAD' }, (res) => {
402
+ const len = parseInt(res.headers['content-length'] || '0', 10);
403
+ resolve(Number.isFinite(len) ? len : 0);
404
+ });
405
+ req.on('error', () => resolve(0));
406
+ req.end();
407
+ });
408
+
409
+ const tmpFile = path.join(os.tmpdir(), `vcm-update-${Date.now()}.tgz`);
410
+
411
+ // Download tarball with progress
412
+ spinner.text = 'Downloading update...';
413
+ // Stop the spinner so we can write a single-line progress indicator without conflicts
414
+ try { spinner.stop(); } catch (e) {}
415
+ // Print initial progress line (clear line first)
416
+ process.stdout.write('\r\x1b[2KDownloading: 0% — 0.0 MB');
417
+ const progressStart = Date.now();
418
+ function formatEta(sec) {
419
+ if (!isFinite(sec) || sec === null) return '--:--';
420
+ const s = Math.max(0, Math.round(sec));
421
+ const m = Math.floor(s / 60);
422
+ const ss = s % 60;
423
+ return `${m}:${ss.toString().padStart(2, '0')}`;
424
+ }
425
+ await new Promise((resolve, reject) => {
426
+ https.get(tarball, (res) => {
427
+ const fileStream = fs.createWriteStream(tmpFile);
428
+ let downloaded = 0;
429
+
430
+ // Print an updating single-line progress indicator (percent + MB)
431
+ let lastPercent = -1;
432
+ let lastMbReported = -1;
433
+ res.on('data', (chunk) => {
434
+ downloaded += chunk.length;
435
+ const percent = totalBytes ? Math.round((downloaded / totalBytes) * 100) : null;
436
+ const mbDownloaded = +(downloaded / (1024 * 1024));
437
+ const mbTotal = totalBytes ? (totalBytes / (1024 * 1024)) : null;
438
+
439
+ // ETA and speed
440
+ const elapsedSec = Math.max(0.001, (Date.now() - progressStart) / 1000);
441
+ const speed = downloaded / elapsedSec; // bytes/sec
442
+ const remaining = totalBytes ? Math.max(0, totalBytes - downloaded) : null;
443
+ const etaSec = remaining && speed > 0 ? remaining / speed : null;
444
+
445
+ // Build simple ASCII progress bar
446
+ const width = 30;
447
+ const fill = percent !== null ? Math.round((percent / 100) * width) : Math.min(width, Math.max(0, Math.round((mbDownloaded / (mbTotal || 1)) * width)));
448
+ const bar = '█'.repeat(fill) + '-'.repeat(Math.max(0, width - fill));
449
+
450
+ const pctText = percent !== null ? `${percent}%` : '--%';
451
+ const mbText = mbTotal ? `${mbDownloaded.toFixed(1)} MB / ${mbTotal.toFixed(1)} MB` : `${mbDownloaded.toFixed(1)} MB`;
452
+ const etaText = etaSec ? formatEta(etaSec) : '--:--';
453
+
454
+ // Update only when percent changes or every 0.5 MB to reduce noise
455
+ const mbReport = Math.floor(mbDownloaded * 2) / 2;
456
+ if ((percent !== null && percent !== lastPercent) || (percent === null && mbReport !== lastMbReported) || Math.random() < 0.001) {
457
+ lastPercent = percent;
458
+ lastMbReported = mbReport;
459
+ process.stdout.write(`\r\x1b[2K[${bar}] ${pctText} ${mbText} ETA: ${etaText}`);
460
+ }
461
+ });
462
+
463
+ res.on('end', () => fileStream.end());
464
+ res.on('error', (err) => reject(err));
465
+
466
+ fileStream.on('finish', () => resolve());
467
+ fileStream.on('error', (err) => reject(err));
468
+
469
+ res.pipe(fileStream);
470
+ }).on('error', reject);
471
+ });
472
+
473
+ // Ensure progress line ends and move to next line
474
+ process.stdout.write('\n');
475
+ spinner.start();
476
+ spinner.succeed('Downloaded update');
477
+
478
+ // Install the downloaded tarball
479
+ spinner.start('Installing update...');
480
+ try {
481
+ execSync(`npm install -g "${tmpFile}"`, { stdio: 'inherit', encoding: 'utf8' });
482
+ spinner.succeed('Installed update');
483
+ console.log(chalk.green('\n✅ Successfully updated to v' + updateInfo.latestVersion + '!'));
484
+ console.log(chalk.gray(' Please restart your command to use the new version.\n'));
485
+ // Cleanup
486
+ try { fs.unlinkSync(tmpFile); } catch (e) {}
487
+ process.exit(0);
488
+ } catch (err) {
489
+ spinner.fail('Installation failed');
490
+ console.log(chalk.red('\n❌ Update failed:'), err.message);
491
+ console.log(chalk.yellow(' You can manually update with: ') + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
492
+ try { fs.unlinkSync(tmpFile); } catch (e) {}
493
+ }
346
494
  } catch (error) {
347
495
  console.log(chalk.red('\n❌ Update failed:'), error.message);
348
496
  console.log(chalk.yellow(' You can manually update with: ') + chalk.bold.white('npm install -g vibecodingmachine-cli@latest\n'));
@@ -384,6 +532,18 @@ if (!process.argv.slice(2).length) {
384
532
  }
385
533
  }
386
534
 
535
+ // Check compliance after authentication
536
+ const { checkCompliance } = require('../src/utils/compliance-check');
537
+ const isCompliant = await checkCompliance();
538
+
539
+ if (!isCompliant) {
540
+ console.log(chalk.red('\n✗ Compliance check failed. Exiting.\n'));
541
+ process.exit(1);
542
+ }
543
+
544
+ // Track CLI usage - interactive mode
545
+ await auth.trackCLIActivity('interactive_mode_started');
546
+
387
547
  const { startInteractive } = require('../src/utils/interactive');
388
548
  await startInteractive();
389
549
  })();
@@ -391,6 +551,47 @@ if (!process.argv.slice(2).length) {
391
551
  // Check for updates before parsing commands
392
552
  (async () => {
393
553
  await checkForUpdates();
554
+
555
+ // Check authentication for all commands (except auth commands)
556
+ const command = process.argv[2] || 'unknown';
557
+ const authCommands = ['auth:login', 'auth:logout', 'auth:status'];
558
+ const skipAuthCheck = authCommands.includes(command);
559
+
560
+ const auth = require('../src/utils/auth');
561
+
562
+ if (!skipAuthCheck) {
563
+ const isAuth = await auth.isAuthenticated();
564
+
565
+ if (!isAuth) {
566
+ console.log(chalk.cyan('\n🔐 Opening browser for authentication...\n'));
567
+ try {
568
+ await auth.login();
569
+ console.log(chalk.green('\n✓ Authentication successful!\n'));
570
+ } catch (error) {
571
+ console.log(chalk.red('\n✗ Authentication failed:'), error.message);
572
+ process.exit(1);
573
+ }
574
+ }
575
+ }
576
+
577
+ // Check compliance after authentication (skip for auth commands)
578
+ if (!skipAuthCheck) {
579
+ const { checkCompliance } = require('../src/utils/compliance-check');
580
+ const isCompliant = await checkCompliance();
581
+
582
+ if (!isCompliant) {
583
+ console.log(chalk.red('\n✗ Compliance check failed. Exiting.\n'));
584
+ process.exit(1);
585
+ }
586
+
587
+ // Track CLI usage - command execution
588
+ const args = process.argv.slice(3);
589
+ await auth.trackCLIActivity('command_executed', {
590
+ command,
591
+ args: args.filter(arg => !arg.includes('password') && !arg.includes('token'))
592
+ });
593
+ }
594
+
394
595
  // Parse arguments only if commands were provided
395
596
  program.parse(process.argv);
396
597
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibecodingmachine-cli",
3
- "version": "2025.12.06-1702",
3
+ "version": "2025.12.22-2230",
4
4
  "description": "Command-line interface for Vibe Coding Machine - Autonomous development",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "author": "Vibe Coding Machine Team",
26
26
  "license": "MIT",
27
27
  "dependencies": {
28
- "vibecodingmachine-core": "^2025.12.06-1702",
28
+ "vibecodingmachine-core": "^2025.12.22-2230",
29
29
  "@aws-sdk/client-dynamodb": "^3.600.0",
30
30
  "@aws-sdk/lib-dynamodb": "^3.600.0",
31
31
  "boxen": "^5.1.2",
@@ -0,0 +1,160 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const { getRequirementsPath } = require('vibecodingmachine-core');
4
+ const { getRepoPath } = require('./src/utils/config');
5
+ const requirements = require('./src/commands/requirements');
6
+
7
+ // Mock specific functions we need from interactive.js's logic
8
+ // helping function to move requirement to recycled (deletion logic)
9
+ async function moveRequirementToRecycled(reqPath, requirementTitle, fromSection) {
10
+ const content = await fs.readFile(reqPath, 'utf8');
11
+ const lines = content.split('\n');
12
+
13
+ let requirementStartIndex = -1;
14
+ let requirementEndIndex = -1;
15
+
16
+ for (let i = 0; i < lines.length; i++) {
17
+ const line = lines[i].trim();
18
+ if (line.startsWith('###')) {
19
+ const title = line.replace(/^###\s*/, '').trim();
20
+ // Logic from interactive.js
21
+ if (title && title.includes(requirementTitle)) {
22
+ requirementStartIndex = i;
23
+ for (let j = i + 1; j < lines.length; j++) {
24
+ const nextLine = lines[j].trim();
25
+ if (nextLine.startsWith('###') || (nextLine.startsWith('##') && !nextLine.startsWith('###'))) {
26
+ requirementEndIndex = j;
27
+ break;
28
+ }
29
+ }
30
+ if (requirementEndIndex === -1) {
31
+ requirementEndIndex = lines.length;
32
+ }
33
+ break;
34
+ }
35
+ }
36
+ }
37
+
38
+ if (requirementStartIndex === -1) {
39
+ console.log('⚠️ Could not find requirement to recycle');
40
+ return false;
41
+ }
42
+
43
+ console.log(`Found requirement at lines ${requirementStartIndex}-${requirementEndIndex}`);
44
+
45
+ const requirementBlock = lines.slice(requirementStartIndex, requirementEndIndex);
46
+ lines.splice(requirementStartIndex, requirementEndIndex - requirementStartIndex);
47
+
48
+ if (requirementStartIndex < lines.length) {
49
+ const nextLine = lines[requirementStartIndex]?.trim();
50
+ const packageNames = ['cli', 'core', 'electron-app', 'web', 'mobile', 'vscode-extension', 'sync-server'];
51
+ if (nextLine && packageNames.includes(nextLine.toLowerCase()) &&
52
+ !nextLine.startsWith('###') && !nextLine.startsWith('PACKAGE:')) {
53
+ lines.splice(requirementStartIndex, 1);
54
+ }
55
+ while (requirementStartIndex < lines.length && lines[requirementStartIndex]?.trim() === '') {
56
+ lines.splice(requirementStartIndex, 1);
57
+ }
58
+ }
59
+
60
+ let recycledIndex = -1;
61
+ for (let i = 0; i < lines.length; i++) {
62
+ if (lines[i].includes('♻️ Recycled') || lines[i].includes('🗑️ Recycled')) {
63
+ recycledIndex = i;
64
+ break;
65
+ }
66
+ }
67
+
68
+ if (recycledIndex === -1) {
69
+ let lastSectionIndex = -1;
70
+ for (let i = lines.length - 1; i >= 0; i--) {
71
+ if (lines[i].startsWith('##') && !lines[i].startsWith('###')) {
72
+ lastSectionIndex = i;
73
+ break;
74
+ }
75
+ }
76
+ const insertIndex = lastSectionIndex > 0 ? lastSectionIndex : lines.length;
77
+ lines.splice(insertIndex, 0, '', '## ♻️ Recycled', '');
78
+ recycledIndex = insertIndex + 1;
79
+ }
80
+
81
+ let insertIndex = recycledIndex + 1;
82
+ while (insertIndex < lines.length && lines[insertIndex].trim() === '') {
83
+ insertIndex++;
84
+ }
85
+ lines.splice(insertIndex, 0, ...requirementBlock);
86
+
87
+ if (insertIndex + requirementBlock.length < lines.length && lines[insertIndex + requirementBlock.length].trim() !== '') {
88
+ lines.splice(insertIndex + requirementBlock.length, 0, '');
89
+ }
90
+
91
+ await fs.writeFile(reqPath, lines.join('\n'));
92
+ return true;
93
+ }
94
+
95
+ async function run() {
96
+ try {
97
+ const repoPath = '/Users/jesse/code/mediawink/vibecodingmachine'; // Hardcoded valid repo path
98
+ console.log('Repo path:', repoPath);
99
+
100
+ const reqPath = await getRequirementsPath(repoPath);
101
+ console.log('<<< PATH >>>', reqPath);
102
+
103
+ if (await fs.pathExists(reqPath)) {
104
+ const content = await fs.readFile(reqPath, 'utf8');
105
+ console.log('Current content length:', content.length);
106
+ console.log('Current content PRE-TEST:\n', content);
107
+ } else {
108
+ console.log('Requirements file does not exist');
109
+ }
110
+
111
+ console.log('\n--- Adding TESTREQ1 ---');
112
+ await requirements.add('TESTREQ1', 'all', 'Description 1');
113
+
114
+ console.log('\n--- Adding TESTREQ2 ---');
115
+ await requirements.add('TESTREQ2', 'all', 'Description 2');
116
+
117
+ let content = await fs.readFile(reqPath, 'utf8');
118
+ console.log('Content after adding:\n', content);
119
+
120
+ // Verify order
121
+ // We expect TESTREQ2 to be above TESTREQ1 if it inserts at the top of the section
122
+ const lines = content.split('\n');
123
+ let idx1 = -1, idx2 = -1;
124
+ for (let i = 0; i < lines.length; i++) {
125
+ if (lines[i].includes('### TESTREQ1')) idx1 = i;
126
+ if (lines[i].includes('### TESTREQ2')) idx2 = i;
127
+ }
128
+ console.log(`TESTREQ1 line: ${idx1}, TESTREQ2 line: ${idx2}`);
129
+ if (idx2 < idx1 && idx2 > -1) {
130
+ console.log('SUCCESS: TESTREQ2 is above TESTREQ1 (Correct LIFO/Stack behavior for "Top of list")');
131
+ } else {
132
+ console.log('FAIL: Order is not correct for "Top of list" insertion');
133
+ }
134
+
135
+ console.log('\n--- Deleting TESTREQ1 ---');
136
+ const success = await moveRequirementToRecycled(reqPath, 'TESTREQ1', 'todo');
137
+ console.log('Delete success:', success);
138
+
139
+ content = await fs.readFile(reqPath, 'utf8');
140
+ console.log('Content after delete:\n', content);
141
+
142
+ if (content.includes('### TESTREQ1')) {
143
+ // It should be in recycled section
144
+ const recycledIndex = content.indexOf('## ♻️ Recycled');
145
+ const reqIndex = content.indexOf('### TESTREQ1');
146
+ if (reqIndex > recycledIndex) {
147
+ console.log('SUCCESS: TESTREQ1 moved to Recycled');
148
+ } else {
149
+ console.log('FAIL: TESTREQ1 is still in TODO or wrong place');
150
+ }
151
+ } else {
152
+ console.log('FAIL: TESTREQ1 disappeared completely (should be recycled)');
153
+ }
154
+
155
+ } catch (error) {
156
+ console.error('Error:', error);
157
+ }
158
+ }
159
+
160
+ run();
@@ -58,7 +58,6 @@ async function status() {
58
58
  }
59
59
 
60
60
  const profile = await auth.getUserProfile();
61
- const token = await auth.getToken();
62
61
 
63
62
  // Get usage stats
64
63
  const canRun = await auth.canRunAutoMode();
@@ -5,21 +5,29 @@
5
5
  */
6
6
 
7
7
  const chalk = require('chalk');
8
- const { DirectLLMManager } = require('vibecodingmachine-core');
9
- const { getRepoPath, getAutoConfig, setAutoConfig } = require('../utils/config');
8
+ const { DirectLLMManager, AppleScriptManager } = require('vibecodingmachine-core');
9
+ const { getRepoPath, getAutoConfig, setAutoConfig, getStages, DEFAULT_STAGES } = require('../utils/config');
10
10
  const { getRequirementsPath, readRequirements } = require('vibecodingmachine-core');
11
11
  const fs = require('fs-extra');
12
12
  const path = require('path');
13
13
  const { spawn } = require('child_process');
14
14
  const chokidar = require('chokidar');
15
+ const stringWidth = require('string-width');
16
+ const { getProviderPreferences, getProviderDefinition } = require('../utils/provider-registry');
17
+ const { createKeyboardHandler } = require('../utils/keyboard-handler');
18
+ const logger = require('../utils/logger');
19
+ const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
20
+ const { checkAntigravityRateLimit, handleAntigravityRateLimit } = require('../utils/antigravity-js-handler');
21
+
15
22
  // Status management will use in-process tracking instead of external file
16
23
  const CLI_ENTRY_POINT = path.join(__dirname, '../../bin/vibecodingmachine.js');
17
- const { getProviderPreferences, getProviderDefinition } = require('../utils/provider-registry');
18
24
 
19
25
  // CRITICAL: Shared ProviderManager instance to track rate limits across all function calls
20
- const ProviderManager = require('vibecodingmachine-core/src/ide-integration/provider-manager.cjs');
21
26
  const sharedProviderManager = new ProviderManager();
22
27
 
28
+ // Configured stages (will be loaded in handleAutoStart)
29
+ let configuredStages = DEFAULT_STAGES;
30
+
23
31
  /**
24
32
  * Get timestamp for logging
25
33
  */
@@ -46,7 +54,6 @@ function stripAnsi(str) {
46
54
  * Uses string-width library for accurate Unicode width calculation
47
55
  */
48
56
  function getVisualWidth(str) {
49
- const stringWidth = require('string-width');
50
57
  return stringWidth(str);
51
58
  }
52
59
 
@@ -111,7 +118,8 @@ async function updateRequirementsStatus(repoPath, status) {
111
118
  break;
112
119
  }
113
120
 
114
- if (inStatusSection && line.trim().match(/^(PREPARE|CREATE|ACT|CLEAN UP|VERIFY|DONE)$/)) {
121
+ // Check against configured stages
122
+ if (inStatusSection && configuredStages.includes(line.trim())) {
115
123
  statusLineIndex = i;
116
124
  break;
117
125
  }
@@ -160,14 +168,9 @@ let storedStatus = '';
160
168
  * Now uses persistent header that stays at top while output scrolls
161
169
  */
162
170
  function printStatusCard(currentTitle, currentStatus) {
163
- const stages = ['PREPARE', 'ACT', 'CLEAN UP', 'VERIFY', 'DONE'];
164
- const stageMap = {
165
- 'PREPARE': 0,
166
- 'ACT': 1,
167
- 'CLEAN UP': 2,
168
- 'VERIFY': 3,
169
- 'DONE': 4
170
- };
171
+ const stages = configuredStages;
172
+ const stageMap = {};
173
+ stages.forEach((s, i) => stageMap[s] = i);
171
174
 
172
175
  const currentIndex = stageMap[currentStatus] || 0;
173
176
 
@@ -249,7 +252,7 @@ async function getCurrentRequirement(repoPath) {
249
252
  // Skip empty titles
250
253
  if (title && title.length > 0) {
251
254
  // Read package and description (optional)
252
- let package = null;
255
+ let pkg = null;
253
256
  let description = '';
254
257
  let j = i + 1;
255
258
 
@@ -262,7 +265,7 @@ async function getCurrentRequirement(repoPath) {
262
265
  }
263
266
  // Check for PACKAGE line
264
267
  if (nextLine.startsWith('PACKAGE:')) {
265
- package = nextLine.replace(/^PACKAGE:\s*/, '').trim();
268
+ pkg = nextLine.replace(/^PACKAGE:\s*/, '').trim();
266
269
  } else if (nextLine && !nextLine.startsWith('PACKAGE:')) {
267
270
  // Description line (not empty, not package)
268
271
  if (description) {
@@ -277,7 +280,7 @@ async function getCurrentRequirement(repoPath) {
277
280
  return {
278
281
  text: title,
279
282
  fullLine: lines[i],
280
- package: package,
283
+ package: pkg,
281
284
  description: description
282
285
  };
283
286
  }
@@ -1334,11 +1337,14 @@ async function runIdeProviderIteration(providerConfig, repoPath) {
1334
1337
  resolve({ success: true, output: combinedOutput });
1335
1338
  } else {
1336
1339
  const message = `${providerConfig.displayName} exited with code ${code}`;
1340
+ const antigravityRateLimit = checkAntigravityRateLimit(combinedOutput);
1341
+
1337
1342
  resolve({
1338
1343
  success: false,
1339
1344
  error: combinedOutput ? `${message}\n${combinedOutput}` : message,
1340
1345
  output: combinedOutput,
1341
- rateLimited: isRateLimitMessage(combinedOutput)
1346
+ rateLimited: isRateLimitMessage(combinedOutput) || antigravityRateLimit.isRateLimited,
1347
+ antigravityRateLimited: antigravityRateLimit.isRateLimited
1342
1348
  });
1343
1349
  }
1344
1350
  });
@@ -1435,26 +1441,15 @@ async function waitForIdeCompletion(repoPath, requirementText, ideType = '', tim
1435
1441
  // Check 3: Quota limit detection for Antigravity (after 2 minutes of waiting)
1436
1442
  const elapsed = Date.now() - startTime;
1437
1443
  if (ideType === 'antigravity' && !quotaHandled && elapsed >= 120000) {
1438
- console.log(chalk.yellow('\n⚠️ No progress detected after 2 minutes - checking for quota limit...\n'));
1439
- try {
1440
- const { AppleScriptManager } = require('vibecodingmachine-core');
1441
- const manager = new AppleScriptManager();
1442
- const result = await manager.handleAntigravityQuotaLimit();
1443
-
1444
- if (result.success) {
1445
- console.log(chalk.green(`✓ Switched to model: ${result.model || 'alternative'}`));
1446
- console.log(chalk.cyan(' Resuming work with new model...\n'));
1447
- quotaHandled = true;
1448
- // Reset start time to give new model time to work
1449
- startTime = Date.now();
1450
- } else {
1451
- console.log(chalk.yellow(`⚠️ Could not switch models: ${result.error}\n`));
1452
- quotaHandled = true; // Don't try again
1453
- }
1454
- } catch (error) {
1455
- console.error(chalk.red(`Error handling quota limit: ${error.message}\n`));
1456
- quotaHandled = true; // Don't try again
1457
- }
1444
+ console.log(chalk.yellow('\n⚠️ Antigravity quota limit detected after 2 minutes\n'));
1445
+ console.log(chalk.cyan(' Switching to next available IDE agent...\n'));
1446
+ watcher.close();
1447
+ resolve({
1448
+ success: false,
1449
+ reason: 'antigravity-quota',
1450
+ antigravityRateLimited: true // This triggers switching to next provider
1451
+ });
1452
+ return;
1458
1453
  }
1459
1454
 
1460
1455
  // Check 4: Timeout
@@ -1511,6 +1506,10 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
1511
1506
  const ideResult = await runIdeProviderIteration(providerConfig, repoPath);
1512
1507
 
1513
1508
  if (!ideResult.success) {
1509
+ if (ideResult.antigravityRateLimited) {
1510
+ await handleAntigravityRateLimit();
1511
+ return { success: false, error: 'Antigravity rate limit detected, retrying with next provider.', shouldRetry: true };
1512
+ }
1514
1513
  // CRITICAL: Mark provider as unavailable for ANY error so acquireProviderConfig() will skip it
1515
1514
  providerManager.markRateLimited(providerConfig.provider, providerConfig.model, ideResult.output || ideResult.error || 'IDE provider failed');
1516
1515
  return { success: false, error: ideResult.error || 'IDE provider failed' };
@@ -1522,6 +1521,12 @@ async function runIdeFallbackIteration(requirement, providerConfig, repoPath, pr
1522
1521
  const completionResult = await waitForIdeCompletion(repoPath, requirement.text, providerConfig.ide || providerConfig.provider);
1523
1522
 
1524
1523
  if (!completionResult.success) {
1524
+ if (completionResult.antigravityRateLimited) {
1525
+ console.log(chalk.yellow('⚠️ Antigravity quota exhausted, switching to next IDE\n'));
1526
+ providerManager.markRateLimited(providerConfig.provider, providerConfig.model, 'Quota limit reached');
1527
+ return { success: false, error: 'Antigravity quota limit', shouldRetry: true };
1528
+ }
1529
+
1525
1530
  const errorMsg = completionResult.reason === 'timeout'
1526
1531
  ? 'IDE agent timed out'
1527
1532
  : 'IDE agent failed to complete';
@@ -1891,6 +1896,9 @@ async function handleAutoStart(options) {
1891
1896
  console.log();
1892
1897
 
1893
1898
  // Get repo path
1899
+ // Load configured stages
1900
+ configuredStages = await getStages();
1901
+
1894
1902
  const repoPath = await getRepoPath();
1895
1903
  if (!repoPath) {
1896
1904
  console.log(chalk.red('✗ No repository configured'));
@@ -1900,8 +1908,15 @@ async function handleAutoStart(options) {
1900
1908
 
1901
1909
  console.log(chalk.white('Repository:'), chalk.cyan(repoPath));
1902
1910
 
1903
- // Get provider configuration
1904
- let providerConfig = await acquireProviderConfig();
1911
+ // Get effective agent using centralized selector
1912
+ const { getEffectiveAgent } = require('../utils/agent-selector');
1913
+ const PROVIDER_DEFINITIONS = require('./auto').PROVIDER_DEFINITIONS;
1914
+ const PROVIDER_DEFINITION_MAP = new Map(PROVIDER_DEFINITIONS.map(def => [def.id, def]));
1915
+
1916
+ const { effectiveAgent } = await getEffectiveAgent(options, PROVIDER_DEFINITIONS, PROVIDER_DEFINITION_MAP);
1917
+
1918
+ // Get provider configuration for the selected agent
1919
+ let providerConfig = await acquireProviderConfig(effectiveAgent);
1905
1920
  if (!providerConfig) {
1906
1921
  return;
1907
1922
  }