vibecodingmachine-cli 2025.12.6-1702 → 2025.12.24-2348

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.
@@ -1,16 +1,17 @@
1
1
  const chalk = require('chalk');
2
2
  const Table = require('cli-table3');
3
3
  const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
4
+ const { t } = require('vibecodingmachine-core');
4
5
 
5
6
  /**
6
7
  * Trigger immediate sync
7
8
  */
8
- async function syncNow(options = {}) {
9
+ async function syncNow() {
9
10
  const syncEngine = new SyncEngine();
10
11
 
11
12
  try {
12
- console.log(chalk.cyan('\nšŸ”„ Starting sync...\n'));
13
-
13
+ console.log(chalk.cyan(`\nšŸ”„ ${t('sync.starting')}\n`));
14
+
14
15
  await syncEngine.initialize();
15
16
 
16
17
  // Listen for sync completion
@@ -31,18 +32,18 @@ async function syncNow(options = {}) {
31
32
  const result = await syncPromise;
32
33
 
33
34
  const status = syncEngine.getStatus();
34
-
35
- console.log(chalk.green('āœ“ Sync complete!\n'));
36
- console.log(chalk.white('Last Sync: ') + new Date(status.lastSyncTime).toLocaleString());
37
- console.log(chalk.white('Remote Changes: ') + (result.remoteChanges || 0));
38
- console.log(chalk.white('Local Changes: ') + (result.localChanges || 0));
39
- console.log(chalk.white('Conflicts: ') + (result.conflicts || 0));
40
- console.log(chalk.white('Queued Changes: ') + status.queuedChanges);
35
+
36
+ console.log(chalk.green(`āœ“ ${t('sync.complete')}\n`));
37
+ console.log(chalk.white(t('sync.last.sync').padEnd(17)) + new Date(status.lastSyncTime).toLocaleString());
38
+ console.log(chalk.white(t('sync.remote.changes').padEnd(17)) + (result.remoteChanges || 0));
39
+ console.log(chalk.white(t('sync.local.changes').padEnd(17)) + (result.localChanges || 0));
40
+ console.log(chalk.white(t('sync.conflicts').padEnd(17)) + (result.conflicts || 0));
41
+ console.log(chalk.white(t('sync.queued.changes').padEnd(17)) + status.queuedChanges);
41
42
  console.log('');
42
43
 
43
44
  } catch (error) {
44
- console.error(chalk.red('\nāœ— Sync failed:'), error.message);
45
- console.log(chalk.gray('\nTip: Check AWS credentials and DynamoDB table configuration.\n'));
45
+ console.error(chalk.red(`\nāœ— ${t('sync.failed')}`), error.message);
46
+ console.log(chalk.gray(`\n${t('sync.tip.aws')}\n`));
46
47
  // Don't throw - just log the error
47
48
  } finally {
48
49
  syncEngine.stop();
@@ -52,49 +53,49 @@ async function syncNow(options = {}) {
52
53
  /**
53
54
  * Show sync status and statistics
54
55
  */
55
- async function syncStatus(options = {}) {
56
+ async function syncStatus() {
56
57
  const syncEngine = new SyncEngine();
57
58
 
58
59
  try {
59
60
  await syncEngine.initialize();
60
61
 
61
62
  const status = syncEngine.getStatus();
62
-
63
- console.log('\n' + chalk.bold.cyan('Sync Status'));
63
+
64
+ console.log('\n' + chalk.bold.cyan(t('sync.status.title')));
64
65
  console.log(chalk.gray('─'.repeat(60)));
65
-
66
+
66
67
  // Connection status
67
68
  const onlineIcon = status.isOnline ? chalk.green('ā—') : chalk.red('ā—');
68
- const onlineText = status.isOnline ? 'Online' : 'Offline';
69
- console.log(chalk.white('Connection: ') + onlineIcon + ' ' + onlineText);
70
-
69
+ const onlineText = status.isOnline ? t('sync.status.online') : t('sync.status.offline');
70
+ console.log(chalk.white(t('sync.connection').padEnd(17)) + onlineIcon + ' ' + onlineText);
71
+
71
72
  // Sync status
72
73
  const syncingIcon = status.isSyncing ? chalk.yellow('⟳') : chalk.gray('ā—‹');
73
- const syncingText = status.isSyncing ? 'Syncing...' : 'Idle';
74
- console.log(chalk.white('Status: ') + syncingIcon + ' ' + syncingText);
75
-
74
+ const syncingText = status.isSyncing ? t('sync.status.syncing') : t('sync.status.idle');
75
+ console.log(chalk.white(t('sync.status').padEnd(17)) + syncingIcon + ' ' + syncingText);
76
+
76
77
  // Last sync
77
- const lastSync = status.lastSyncTime
78
+ const lastSync = status.lastSyncTime
78
79
  ? new Date(status.lastSyncTime).toLocaleString()
79
- : 'Never';
80
- console.log(chalk.white('Last Sync: ') + lastSync);
81
-
80
+ : t('sync.never');
81
+ console.log(chalk.white(t('sync.last.sync').padEnd(17)) + lastSync);
82
+
82
83
  // Queued changes
83
84
  const queueColor = status.queuedChanges > 0 ? chalk.yellow : chalk.gray;
84
- console.log(chalk.white('Queued Changes: ') + queueColor(status.queuedChanges));
85
-
85
+ console.log(chalk.white(t('sync.queued.changes').padEnd(17)) + queueColor(status.queuedChanges));
86
+
86
87
  // Conflict strategy
87
- console.log(chalk.white('Conflict Mode: ') + status.conflictStrategy);
88
-
88
+ console.log(chalk.white(t('sync.conflict.mode').padEnd(17)) + status.conflictStrategy);
89
+
89
90
  // Computer ID
90
- console.log(chalk.white('Computer ID: ') + status.computerId);
91
-
91
+ console.log(chalk.white(t('sync.computer.id').padEnd(17)) + status.computerId);
92
+
92
93
  console.log('');
93
94
 
94
95
  // Show recent sync events
95
96
  const history = syncEngine.getHistory(5);
96
97
  if (history.length > 0) {
97
- console.log(chalk.bold.cyan('Recent Sync Events'));
98
+ console.log(chalk.bold.cyan(t('sync.recent.events')));
98
99
  console.log(chalk.gray('─'.repeat(60)));
99
100
 
100
101
  for (const event of history.reverse()) {
@@ -107,7 +108,7 @@ async function syncStatus(options = {}) {
107
108
  }
108
109
 
109
110
  } catch (error) {
110
- console.error(chalk.red('\nāœ— Failed to get sync status:'), error.message);
111
+ console.error(chalk.red(`\nāœ— ${t('sync.status.failed')}`), error.message);
111
112
  throw error;
112
113
  } finally {
113
114
  syncEngine.stop();
@@ -117,28 +118,28 @@ async function syncStatus(options = {}) {
117
118
  /**
118
119
  * View pending changes in offline queue
119
120
  */
120
- async function viewQueue(options = {}) {
121
+ async function viewQueue() {
121
122
  const syncEngine = new SyncEngine();
122
123
 
123
124
  try {
124
125
  await syncEngine.initialize();
125
126
 
126
127
  const status = syncEngine.getStatus();
127
-
128
+
128
129
  if (status.queuedChanges === 0) {
129
- console.log(chalk.gray('\nNo pending changes in queue.\n'));
130
+ console.log(chalk.gray(`\n${t('sync.queue.no.pending')}\n`));
130
131
  return;
131
132
  }
132
-
133
- console.log(chalk.cyan(`\nšŸ“‹ Offline Queue (${status.queuedChanges} changes)\n`));
134
-
133
+
134
+ console.log(chalk.cyan(`\nšŸ“‹ ${t('sync.queue.title', { count: status.queuedChanges })}\n`));
135
+
135
136
  // Create table
136
137
  const table = new Table({
137
138
  head: [
138
- chalk.cyan('#'),
139
- chalk.cyan('Type'),
140
- chalk.cyan('Requirement'),
141
- chalk.cyan('Timestamp')
139
+ chalk.cyan(t('sync.queue.header.number')),
140
+ chalk.cyan(t('sync.queue.header.type')),
141
+ chalk.cyan(t('sync.queue.header.requirement')),
142
+ chalk.cyan(t('sync.queue.header.timestamp'))
142
143
  ],
143
144
  colWidths: [5, 15, 40, 20]
144
145
  });
@@ -154,15 +155,15 @@ async function viewQueue(options = {}) {
154
155
  });
155
156
 
156
157
  console.log(table.toString() + '\n');
157
-
158
- console.log(chalk.gray('These changes will be synced when connection is restored.\n'));
159
- console.log(chalk.white('Commands:'));
160
- console.log(chalk.gray(' vcm sync:force ') + '- Force sync now');
161
- console.log(chalk.gray(' vcm sync:now ') + '- Sync when online');
158
+
159
+ console.log(chalk.gray(`${t('sync.queue.will.sync')}\n`));
160
+ console.log(chalk.white(t('sync.queue.commands')));
161
+ console.log(chalk.gray(' vcm sync:force ') + `- ${t('sync.queue.force.now')}`);
162
+ console.log(chalk.gray(' vcm sync:now ') + `- ${t('sync.queue.sync.online')}`);
162
163
  console.log('');
163
164
 
164
165
  } catch (error) {
165
- console.error(chalk.red('\nāœ— Failed to view queue:'), error.message);
166
+ console.error(chalk.red(`\nāœ— ${t('sync.queue.view.failed')}`), error.message);
166
167
  throw error;
167
168
  } finally {
168
169
  syncEngine.stop();
@@ -172,29 +173,29 @@ async function viewQueue(options = {}) {
172
173
  /**
173
174
  * Force sync even if offline
174
175
  */
175
- async function forceSync(options = {}) {
176
+ async function forceSync() {
176
177
  const syncEngine = new SyncEngine();
177
178
 
178
179
  try {
179
- console.log(chalk.yellow('\n⚠ Forcing sync (ignoring offline status)...\n'));
180
-
180
+ console.log(chalk.yellow(`\n⚠ ${t('sync.force.starting')}\n`));
181
+
181
182
  await syncEngine.initialize();
182
-
183
+
183
184
  // Temporarily set online
184
185
  const wasOnline = syncEngine.isOnline;
185
186
  syncEngine.setOnlineMode(true);
186
-
187
+
187
188
  try {
188
189
  await syncEngine.sync();
189
- console.log(chalk.green('āœ“ Force sync complete!\n'));
190
+ console.log(chalk.green(`āœ“ ${t('sync.force.complete')}\n`));
190
191
  } finally {
191
192
  // Restore original online status
192
193
  syncEngine.setOnlineMode(wasOnline);
193
194
  }
194
195
 
195
196
  } catch (error) {
196
- console.error(chalk.red('\nāœ— Force sync failed:'), error.message);
197
- console.log(chalk.gray('\nThis usually means the server is unreachable.\n'));
197
+ console.error(chalk.red(`\nāœ— ${t('sync.force.failed')}`), error.message);
198
+ console.log(chalk.gray(`\n${t('sync.force.unreachable')}\n`));
198
199
  throw error;
199
200
  } finally {
200
201
  syncEngine.stop();
@@ -212,20 +213,20 @@ async function viewHistory(options = {}) {
212
213
 
213
214
  const limit = parseInt(options.limit) || 50;
214
215
  const history = syncEngine.getHistory(limit);
215
-
216
+
216
217
  if (history.length === 0) {
217
- console.log(chalk.gray('\nNo sync history available.\n'));
218
+ console.log(chalk.gray(`\n${t('sync.history.no.events')}\n`));
218
219
  return;
219
220
  }
220
-
221
- console.log(chalk.cyan(`\nšŸ“œ Sync History (last ${history.length} events)\n`));
222
-
221
+
222
+ console.log(chalk.cyan(`\nšŸ“œ ${t('sync.history.title', { count: history.length })}\n`));
223
+
223
224
  // Create table
224
225
  const table = new Table({
225
226
  head: [
226
- chalk.cyan('Time'),
227
- chalk.cyan('Type'),
228
- chalk.cyan('Details')
227
+ chalk.cyan(t('sync.history.header.time')),
228
+ chalk.cyan(t('sync.history.header.type')),
229
+ chalk.cyan(t('sync.history.header.details'))
229
230
  ],
230
231
  colWidths: [20, 20, 50]
231
232
  });
@@ -242,7 +243,7 @@ async function viewHistory(options = {}) {
242
243
  console.log(table.toString() + '\n');
243
244
 
244
245
  } catch (error) {
245
- console.error(chalk.red('\nāœ— Failed to view history:'), error.message);
246
+ console.error(chalk.red(`\nāœ— ${t('sync.history.view.failed')}`), error.message);
246
247
  throw error;
247
248
  } finally {
248
249
  syncEngine.stop();
@@ -259,13 +260,13 @@ function truncate(str, maxLength) {
259
260
  function getEventDetails(event) {
260
261
  switch (event.type) {
261
262
  case 'conflict-resolution':
262
- return `Resolved: ${event.resolution?.requirementId || 'unknown'}`;
263
+ return t('sync.event.resolved', { id: event.resolution?.requirementId || 'unknown' });
263
264
  case 'sync-complete':
264
- return `Remote: ${event.remoteChanges || 0}, Local: ${event.localChanges || 0}`;
265
+ return t('sync.event.remote.local', { remote: event.remoteChanges || 0, local: event.localChanges || 0 });
265
266
  case 'remote-change-applied':
266
- return `Applied: ${event.change?.requirementId || 'unknown'}`;
267
+ return t('sync.event.applied', { id: event.change?.requirementId || 'unknown' });
267
268
  case 'local-change-pushed':
268
- return `Pushed: ${event.change?.requirementId || 'unknown'}`;
269
+ return t('sync.event.pushed', { id: event.change?.requirementId || 'unknown' });
269
270
  default:
270
271
  return JSON.stringify(event).substring(0, 47);
271
272
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Centralized agent selection logic
3
+ * Respects provider preferences order and ensures DRY principle
4
+ */
5
+
6
+ const { getProviderPreferences } = require('./provider-registry');
7
+
8
+ /**
9
+ * Get the effective agent based on provider preferences and options
10
+ * @param {Object} options - Command line options (may include ide)
11
+ * @param {Array} providerDefinitions - Array of provider definitions
12
+ * @param {Map} providerDefinitionMap - Map of provider definitions by ID
13
+ * @returns {Object} - { effectiveAgent: string, providerDef: Object }
14
+ */
15
+ async function getEffectiveAgent(options = {}, providerDefinitions, providerDefinitionMap) {
16
+ const prefs = await getProviderPreferences();
17
+
18
+ // Get all available providers in the order specified in preferences
19
+ const availableProviders = [];
20
+ for (const id of prefs.order) {
21
+ if (prefs.enabled[id] !== false && providerDefinitionMap.has(id)) {
22
+ availableProviders.push(id);
23
+ }
24
+ }
25
+
26
+ // If no providers are available, use the first one from definitions as fallback
27
+ if (availableProviders.length === 0) {
28
+ availableProviders.push(providerDefinitions[0]?.id || 'claude-code');
29
+ }
30
+
31
+ // Use the first available provider by default, unless overridden by options
32
+ let effectiveAgent = options.ide || availableProviders[0];
33
+
34
+ // If the requested agent isn't available, use the first available one
35
+ if (!availableProviders.includes(effectiveAgent)) {
36
+ effectiveAgent = availableProviders[0];
37
+ }
38
+
39
+ const providerDef = providerDefinitionMap.get(effectiveAgent);
40
+
41
+ return {
42
+ effectiveAgent,
43
+ providerDef,
44
+ availableProviders
45
+ };
46
+ }
47
+
48
+ module.exports = {
49
+ getEffectiveAgent
50
+ };
@@ -0,0 +1,212 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const { execSync } = require('child_process');
5
+ const ora = require('ora');
6
+ const chalk = require('chalk');
7
+
8
+ async function installAntigravity() {
9
+ console.log(chalk.cyan('\nšŸš€ Initiating Google Antigravity IDE Installation...'));
10
+
11
+ const spinner = ora('Checking system requirements...').start();
12
+ await new Promise((r) => setTimeout(r, 800));
13
+
14
+ if (os.platform() !== 'darwin') {
15
+ spinner.fail('Automated Antigravity installation is only supported on macOS');
16
+ return false;
17
+ }
18
+
19
+ const arch = os.arch();
20
+ const isArm = arch === 'arm64';
21
+
22
+ // URLs from electron-app IDE installer
23
+ const downloadUrl = isArm
24
+ ? 'https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/1.11.2-6251250307170304/darwin-arm/Antigravity.dmg'
25
+ : 'https://edgedl.me.gvt1.com/edgedl/release2/j0qc3/antigravity/stable/1.11.2-6251250307170304/darwin-x64/Antigravity.dmg';
26
+
27
+ spinner.succeed(`System OK (${isArm ? 'Apple Silicon' : 'Intel'})`);
28
+
29
+ try {
30
+ const cacheDir = path.join(os.homedir(), '.vibecodingmachine', 'cache');
31
+ await fs.ensureDir(cacheDir);
32
+ const cachedPath = path.join(cacheDir, 'Antigravity.dmg');
33
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'antigravity-'));
34
+ const dmgPath = path.join(tempDir, 'Antigravity.dmg');
35
+ const { downloadWithProgress } = require('./download-with-progress');
36
+
37
+ // Prefer cached DMG if it exists and matches upstream size (or exists at all)
38
+ let usedCache = false;
39
+ try {
40
+ if (await fs.pathExists(cachedPath)) {
41
+ // Try HEAD to compare sizes
42
+ try {
43
+ const fetch = require('node-fetch');
44
+ const head = await fetch(downloadUrl, { method: 'HEAD' });
45
+ const total = Number(head.headers.get('content-length')) || 0;
46
+ const stat = await fs.stat(cachedPath);
47
+ if (total && stat.size === total) {
48
+ await fs.copy(cachedPath, dmgPath);
49
+ usedCache = true;
50
+ console.log(chalk.gray('Using cached Antigravity DMG'));
51
+ }
52
+ } catch (e) {
53
+ // If HEAD fails, still allow reuse of cache to avoid re-download
54
+ await fs.copy(cachedPath, dmgPath);
55
+ usedCache = true;
56
+ console.log(chalk.gray('Using cached Antigravity DMG (no HEAD)'));
57
+ }
58
+ }
59
+ } catch (e) {
60
+ // ignore cache errors
61
+ usedCache = false;
62
+ }
63
+
64
+ // Use download helper which displays progress and ETA if we didn't reuse cache
65
+ if (!usedCache) {
66
+ await downloadWithProgress(downloadUrl, dmgPath, { label: 'Downloading Antigravity...' });
67
+ // Save to cache for future runs
68
+ try { await fs.copy(dmgPath, cachedPath); } catch (e) { /* ignore cache write errors */ }
69
+ }
70
+
71
+ const installSpinner = ora('Installing Antigravity...').start();
72
+
73
+ try {
74
+ installSpinner.text = 'Mounting disk image...';
75
+ execSync(`hdiutil attach "${dmgPath}" -nobrowse -noverify -mountpoint "${tempDir}/mount"`);
76
+
77
+ installSpinner.text = 'Copying to /Applications...';
78
+ const files = await fs.readdir(path.join(tempDir, 'mount'));
79
+ const appName = files.find((f) => f.endsWith('.app'));
80
+ if (!appName) throw new Error('Could not find .app in DMG');
81
+
82
+ const src = path.join(tempDir, 'mount', appName);
83
+ const dest = path.join('/Applications', appName);
84
+
85
+ if (await fs.pathExists(dest)) {
86
+ installSpinner.text = 'Replacing existing application...';
87
+ await fs.remove(dest);
88
+ }
89
+
90
+ // Prefer rsync/ditto; fallback to node-stream copy with progress
91
+ try {
92
+ const { copyAppWithProgress } = require('./copy-with-progress');
93
+ installSpinner.stop();
94
+ const ok = await copyAppWithProgress(src, dest, { spinner: installSpinner });
95
+ if (!ok) {
96
+ installSpinner.text = 'Copying (fs fallback) to /Applications...';
97
+ await fs.copy(src, dest);
98
+ }
99
+ } catch (e) {
100
+ // final fallback
101
+ installSpinner.text = 'Copying (fs fallback) to /Applications...';
102
+ try {
103
+ execSync(`cp -R "${src}" "/Applications/"`, { stdio: 'inherit' });
104
+ } catch (err) {
105
+ await fs.copy(src, dest);
106
+ }
107
+ }
108
+
109
+ installSpinner.text = 'Cleaning up...';
110
+ execSync(`hdiutil detach "${tempDir}/mount" -force`);
111
+ await fs.remove(tempDir);
112
+
113
+ installSpinner.succeed('Antigravity installed');
114
+ console.log(chalk.green(`\nāœ… Installed to ${dest}`));
115
+ // Attempt to automatically configure Antigravity's first-run settings
116
+ try {
117
+ await configureAntigravityDefaults(dest);
118
+ } catch (e) {
119
+ // Non-fatal: log and continue
120
+ console.log(chalk.yellow('Warning: could not auto-configure Antigravity:'), e.message || e);
121
+ }
122
+
123
+ return true;
124
+ } catch (err) {
125
+ installSpinner.fail('Installation failed');
126
+ try {
127
+ if (await fs.pathExists(path.join(tempDir, 'mount'))) {
128
+ execSync(`hdiutil detach "${tempDir}/mount" -force`);
129
+ }
130
+ try { await fs.remove(tempDir); } catch (e) { /* ignore */ }
131
+ console.log(chalk.red('\nInstallation error:'), err.message);
132
+ return false;
133
+ }
134
+ } catch (error) {
135
+ console.log(chalk.red('\nFailed to install Antigravity:'), error.message);
136
+ return false;
137
+ }
138
+ }
139
+
140
+ async function configureAntigravityDefaults() {
141
+ // Attempt to open Antigravity and set the onboarding defaults via AppleScript
142
+ // Desired selections:
143
+ // - Agent-driven development (instead of recommended Agent-assisted)
144
+ // - Terminal execution policy: Turbo
145
+ // - Review policy: Always Proceed
146
+ // - Use the default allowlist for the browser (checked)
147
+ try {
148
+ const script = `
149
+ tell application "Antigravity"
150
+ activate
151
+ end tell
152
+ delay 0.8
153
+ tell application "System Events"
154
+ tell process "Antigravity"
155
+ set frontmost to true
156
+ delay 0.5
157
+ -- Try to select Agent-driven development radio button
158
+ try
159
+ click radio button "Agent-driven development" of radio group 1 of window 1
160
+ delay 0.2
161
+ end try
162
+
163
+ -- Set Terminal execution policy to Turbo (attempt by label, then by index)
164
+ try
165
+ try
166
+ click pop up button "Terminal execution policy" of window 1
167
+ delay 0.2
168
+ click menu item "Turbo" of menu 1 of pop up button "Terminal execution policy" of window 1
169
+ on error
170
+ -- fallback: click first pop up button and choose Turbo
171
+ click pop up button 1 of window 1
172
+ delay 0.2
173
+ click menu item "Turbo" of menu 1 of pop up button 1 of window 1
174
+ end try
175
+ delay 0.2
176
+ end try
177
+
178
+ -- Set Review policy to Always Proceed
179
+ try
180
+ click pop up button "Review policy" of window 1
181
+ delay 0.2
182
+ click menu item "Always Proceed" of menu 1 of pop up button "Review policy" of window 1
183
+ delay 0.2
184
+ end try
185
+
186
+ -- Ensure default allowlist checkbox is checked
187
+ try
188
+ set cb to checkbox "Use the default allowlist for the browser" of window 1
189
+ if (value of cb as boolean) is false then click cb
190
+ delay 0.2
191
+ end try
192
+
193
+ -- Advance the onboarding if a Next/Done button exists
194
+ try
195
+ if exists button "Next" of window 1 then click button "Next" of window 1
196
+ delay 0.3
197
+ end try
198
+ end tell
199
+ end tell
200
+ `;
201
+
202
+ // Use osascript to run the UI automation. JSON-stringify ensures safe quoting.
203
+ const { execSync } = require('child_process');
204
+ execSync(`osascript -e ${JSON.stringify(script)}`, { stdio: 'ignore', timeout: 15000 });
205
+ console.log(chalk.gray('Auto-configured Antigravity first-run preferences'));
206
+ return true;
207
+ } catch (err) {
208
+ throw new Error(err.message || err);
209
+ }
210
+ }
211
+
212
+ module.exports = { installAntigravity };
@@ -0,0 +1,60 @@
1
+ const chalk = require('chalk');
2
+ const {
3
+ getProviderPreferences,
4
+ saveProviderPreferences
5
+ } = require('./provider-registry');
6
+
7
+ /**
8
+ * Check if Antigravity agent has hit a rate limit.
9
+ * @param {string} stderr - Standard error output from the agent.
10
+ * @returns {{isRateLimited: boolean, message: string|null}} - Rate limit status and message.
11
+ */
12
+ function checkAntigravityRateLimit(stderr) {
13
+ const rateLimitPatterns = [
14
+ /quota limit/i,
15
+ /rate limit/i,
16
+ /too many requests/i,
17
+ /limit exceeded/i
18
+ ];
19
+
20
+ for (const pattern of rateLimitPatterns) {
21
+ if (pattern.test(stderr)) {
22
+ return {
23
+ isRateLimited: true,
24
+ message: 'Antigravity quota limit reached.'
25
+ };
26
+ }
27
+ }
28
+
29
+ return { isRateLimited: false, message: null };
30
+ }
31
+
32
+ /**
33
+ * Handle rate limit for Antigravity by disabling it and selecting the next available provider.
34
+ * @returns {Promise<{success: boolean, nextProvider: string|null, error: string|null}>}
35
+ */
36
+ async function handleAntigravityRateLimit() {
37
+ console.log(chalk.yellow('Antigravity rate limit detected. Disabling for this session.'));
38
+
39
+ try {
40
+ const prefs = await getProviderPreferences();
41
+ prefs.enabled.antigravity = false;
42
+ await saveProviderPreferences(prefs);
43
+
44
+ const nextProvider = prefs.order.find(p => p !== 'antigravity' && prefs.enabled[p]);
45
+
46
+ if (nextProvider) {
47
+ console.log(chalk.cyan(`Switching to next available provider: ${nextProvider}`));
48
+ return { success: true, nextProvider, error: null };
49
+ } else {
50
+ return { success: false, nextProvider: null, error: 'No fallback providers available.' };
51
+ }
52
+ } catch (error) {
53
+ return { success: false, nextProvider: null, error: 'Failed to update provider preferences.' };
54
+ }
55
+ }
56
+
57
+ module.exports = {
58
+ checkAntigravityRateLimit,
59
+ handleAntigravityRateLimit
60
+ };
@@ -1,6 +1,5 @@
1
1
  const fs = require('fs-extra');
2
2
  const path = require('path');
3
- const chalk = require('chalk');
4
3
 
5
4
  /**
6
5
  * Scans the assets directory and removes 0-byte PNG files