antigravity-claude-proxy 2.4.1 → 2.4.2

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/README.md CHANGED
@@ -154,15 +154,13 @@ Add this configuration:
154
154
  "ANTHROPIC_MODEL": "claude-opus-4-5-thinking",
155
155
  "ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-5-thinking",
156
156
  "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-5-thinking",
157
- "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-2.5-flash-lite[1m]",
157
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-sonnet-4-5",
158
158
  "CLAUDE_CODE_SUBAGENT_MODEL": "claude-sonnet-4-5-thinking",
159
159
  "ENABLE_EXPERIMENTAL_MCP_CLI": "true"
160
160
  }
161
161
  }
162
162
  ```
163
163
 
164
- (Please use **gemini-2.5-flash-lite** as the default haiku model, even if others are claude, as claude code makes several calls via the haiku model for background tasks. If you use claude model for it, you may use you claude usage sooner)
165
-
166
164
  Or to use Gemini models:
167
165
 
168
166
  ```json
@@ -173,7 +171,7 @@ Or to use Gemini models:
173
171
  "ANTHROPIC_MODEL": "gemini-3-pro-high[1m]",
174
172
  "ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini-3-pro-high[1m]",
175
173
  "ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini-3-flash[1m]",
176
- "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-2.5-flash-lite[1m]",
174
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gemini-3-flash[1m]",
177
175
  "CLAUDE_CODE_SUBAGENT_MODEL": "gemini-3-flash[1m]",
178
176
  "ENABLE_EXPERIMENTAL_MCP_CLI": "true"
179
177
  }
@@ -435,6 +433,64 @@ npm run test:cache-control # Cache control field stripping
435
433
 
436
434
  ## Troubleshooting
437
435
 
436
+ ### Windows: OAuth Port Error (EACCES)
437
+
438
+ On Windows, the default OAuth callback port (51121) may be reserved by Hyper-V, WSL2, or Docker. If you see:
439
+
440
+ ```
441
+ Error: listen EACCES: permission denied 0.0.0.0:51121
442
+ ```
443
+
444
+ The proxy will automatically try fallback ports (51122-51126). If all ports fail, try these solutions:
445
+
446
+ #### Option 1: Use a Custom Port (Recommended)
447
+
448
+ Set a custom port outside the reserved range:
449
+
450
+ ```bash
451
+ # Windows PowerShell
452
+ $env:OAUTH_CALLBACK_PORT = "3456"
453
+ antigravity-claude-proxy start
454
+
455
+ # Windows CMD
456
+ set OAUTH_CALLBACK_PORT=3456
457
+ antigravity-claude-proxy start
458
+
459
+ # Or add to your .env file
460
+ OAUTH_CALLBACK_PORT=3456
461
+ ```
462
+
463
+ #### Option 2: Reset Windows NAT
464
+
465
+ Run as Administrator:
466
+
467
+ ```powershell
468
+ net stop winnat
469
+ net start winnat
470
+ ```
471
+
472
+ #### Option 3: Check Reserved Ports
473
+
474
+ See which ports are reserved:
475
+
476
+ ```powershell
477
+ netsh interface ipv4 show excludedportrange protocol=tcp
478
+ ```
479
+
480
+ If 51121 is in a reserved range, use Option 1 with a port outside those ranges.
481
+
482
+ #### Option 4: Permanently Exclude Port (Admin)
483
+
484
+ Reserve the port before Hyper-V claims it (run as Administrator):
485
+
486
+ ```powershell
487
+ netsh int ipv4 add excludedportrange protocol=tcp startport=51121 numberofports=1
488
+ ```
489
+
490
+ > **Note:** The server automatically tries fallback ports (51122-51126) if the primary port fails.
491
+
492
+ ---
493
+
438
494
  ### "Could not extract token from Antigravity"
439
495
 
440
496
  If using single-account mode with Antigravity:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antigravity-claude-proxy",
3
- "version": "2.4.1",
3
+ "version": "2.4.2",
4
4
  "description": "Proxy server to use Antigravity's Claude models with Claude Code CLI",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/public/index.html CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  <body
20
20
  class="bg-space-950 text-gray-300 font-sans antialiased min-h-screen overflow-hidden selection:bg-neon-purple selection:text-white"
21
- x-cloak x-data="app" x-init="console.log('App initialized')">
21
+ x-cloak x-data="app" x-init="if(window.UILogger) window.UILogger.debug('App initialized')">
22
22
 
23
23
  <!-- Toast Notification -->
24
24
  <div class="fixed top-4 right-4 z-[100] flex flex-col gap-2 pointer-events-none">
@@ -389,6 +389,7 @@
389
389
  <!-- Scripts - Loading Order Matters! -->
390
390
  <!-- 1. Config & Utils (global helpers) -->
391
391
  <script src="js/config/constants.js"></script>
392
+ <script src="js/utils/ui-logger.js"></script><!-- Issue #183: Conditional logging utility -->
392
393
  <script src="js/utils.js"></script>
393
394
  <script src="js/utils/error-handler.js"></script>
394
395
  <script src="js/utils/account-actions.js"></script>
@@ -106,7 +106,7 @@ window.DashboardCharts.createDataset = function (label, data, color, canvas) {
106
106
  }
107
107
  }
108
108
  } catch (e) {
109
- console.warn("Failed to create gradient, using solid color fallback:", e);
109
+ if (window.UILogger) window.UILogger.debug("Gradient fallback:", e.message);
110
110
  gradient = null;
111
111
  }
112
112
 
@@ -149,7 +149,7 @@ window.DashboardCharts.updateCharts = function (component) {
149
149
  console.debug("Destroying existing quota chart from canvas property");
150
150
  try {
151
151
  canvas._chartInstance.destroy();
152
- } catch(e) { console.warn(e); }
152
+ } catch(e) { if (window.UILogger) window.UILogger.debug(e); }
153
153
  canvas._chartInstance = null;
154
154
  }
155
155
 
@@ -170,11 +170,11 @@ window.DashboardCharts.updateCharts = function (component) {
170
170
  }
171
171
 
172
172
  if (typeof Chart === "undefined") {
173
- console.warn("Chart.js not loaded");
173
+ if (window.UILogger) window.UILogger.warn("Chart.js not loaded");
174
174
  return;
175
175
  }
176
176
  if (!isCanvasReady(canvas)) {
177
- console.debug("quotaChart canvas not ready, skipping update");
177
+ if (window.UILogger) window.UILogger.debug("quotaChart canvas not ready, skipping update");
178
178
  return;
179
179
  }
180
180
 
@@ -319,12 +319,13 @@ window.DashboardCharts.updateCharts = function (component) {
319
319
  window.DashboardCharts.updateTrendChart = function (component) {
320
320
  // Prevent concurrent updates (fixes race condition on rapid toggling)
321
321
  if (_trendChartUpdateLock) {
322
- console.log("[updateTrendChart] Update already in progress, skipping");
322
+ if (window.UILogger) window.UILogger.debug("[updateTrendChart] Update already in progress, skipping");
323
323
  return;
324
324
  }
325
325
  _trendChartUpdateLock = true;
326
326
 
327
- console.log("[updateTrendChart] Starting update...");
327
+ const logger = window.UILogger || console;
328
+ logger.debug("[updateTrendChart] Starting update...");
328
329
 
329
330
  const canvas = document.getElementById("usageTrendChart");
330
331
 
@@ -335,7 +336,7 @@ window.DashboardCharts.updateTrendChart = function (component) {
335
336
  try {
336
337
  canvas._chartInstance.stop();
337
338
  canvas._chartInstance.destroy();
338
- } catch(e) { console.warn(e); }
339
+ } catch(e) { if (window.UILogger) window.UILogger.debug(e); }
339
340
  canvas._chartInstance = null;
340
341
  }
341
342
 
@@ -359,17 +360,17 @@ window.DashboardCharts.updateTrendChart = function (component) {
359
360
 
360
361
  // Safety checks
361
362
  if (!canvas) {
362
- console.error("[updateTrendChart] Canvas not found in DOM!");
363
- _trendChartUpdateLock = false; // Release lock!
363
+ if (window.UILogger) window.UILogger.debug("[updateTrendChart] Canvas not found in DOM");
364
+ _trendChartUpdateLock = false;
364
365
  return;
365
366
  }
366
367
  if (typeof Chart === "undefined") {
367
- console.error("[updateTrendChart] Chart.js not loaded");
368
- _trendChartUpdateLock = false; // Release lock!
368
+ if (window.UILogger) window.UILogger.warn("[updateTrendChart] Chart.js not loaded");
369
+ _trendChartUpdateLock = false;
369
370
  return;
370
371
  }
371
372
 
372
- console.log("[updateTrendChart] Canvas element:", {
373
+ if (window.UILogger) window.UILogger.debug("[updateTrendChart] Canvas element:", {
373
374
  exists: !!canvas,
374
375
  isConnected: canvas.isConnected,
375
376
  width: canvas.offsetWidth,
@@ -378,7 +379,7 @@ window.DashboardCharts.updateTrendChart = function (component) {
378
379
  });
379
380
 
380
381
  if (!isCanvasReady(canvas)) {
381
- console.error("[updateTrendChart] Canvas not ready!", {
382
+ if (window.UILogger) window.UILogger.debug("[updateTrendChart] Canvas not ready", {
382
383
  isConnected: canvas.isConnected,
383
384
  width: canvas.offsetWidth,
384
385
  height: canvas.offsetHeight,
@@ -394,17 +395,17 @@ window.DashboardCharts.updateTrendChart = function (component) {
394
395
  ctx.clearRect(0, 0, canvas.width, canvas.height);
395
396
  }
396
397
  } catch (e) {
397
- console.warn("[updateTrendChart] Failed to clear canvas:", e);
398
+ if (window.UILogger) window.UILogger.debug("[updateTrendChart] Failed to clear canvas:", e.message);
398
399
  }
399
400
 
400
- console.log(
401
+ if (window.UILogger) window.UILogger.debug(
401
402
  "[updateTrendChart] Canvas is ready, proceeding with chart creation"
402
403
  );
403
404
 
404
405
  // Use filtered history data based on time range
405
406
  const history = window.DashboardFilters.getFilteredHistoryData(component);
406
407
  if (!history || Object.keys(history).length === 0) {
407
- console.warn("No history data available for trend chart (after filtering)");
408
+ if (window.UILogger) window.UILogger.debug("No history data available for trend chart (after filtering)");
408
409
  component.hasFilteredTrendData = false;
409
410
  _trendChartUpdateLock = false;
410
411
  return;
@@ -50,7 +50,7 @@ window.DashboardFilters.loadPreferences = function(component) {
50
50
  component.selectedModels = prefs.selectedModels || {};
51
51
  }
52
52
  } catch (e) {
53
- console.error('Failed to load dashboard preferences:', e);
53
+ if (window.UILogger) window.UILogger.debug('Failed to load dashboard preferences:', e.message);
54
54
  }
55
55
  };
56
56
 
@@ -67,7 +67,7 @@ window.DashboardFilters.savePreferences = function(component) {
67
67
  selectedModels: component.selectedModels
68
68
  }));
69
69
  } catch (e) {
70
- console.error('Failed to save dashboard preferences:', e);
70
+ if (window.UILogger) window.UILogger.debug('Failed to save dashboard preferences:', e.message);
71
71
  }
72
72
  };
73
73
 
@@ -79,12 +79,12 @@ window.Components.logsViewer = () => ({
79
79
  this.$nextTick(() => this.scrollToBottom());
80
80
  }
81
81
  } catch (e) {
82
- console.error('Log parse error:', e);
82
+ if (window.UILogger) window.UILogger.debug('Log parse error:', e.message);
83
83
  }
84
84
  };
85
85
 
86
86
  this.eventSource.onerror = () => {
87
- console.warn('Log stream disconnected, reconnecting...');
87
+ if (window.UILogger) window.UILogger.debug('Log stream disconnected, reconnecting...');
88
88
  setTimeout(() => this.startLogStream(), 3000);
89
89
  };
90
90
  },
@@ -55,7 +55,7 @@ document.addEventListener('alpine:init', () => {
55
55
 
56
56
  // Check TTL
57
57
  if (data.timestamp && (Date.now() - data.timestamp > CACHE_TTL)) {
58
- console.log('Cache expired, skipping restoration');
58
+ if (window.UILogger) window.UILogger.debug('Cache expired, skipping restoration');
59
59
  localStorage.removeItem('ag_data_cache');
60
60
  return;
61
61
  }
@@ -70,11 +70,11 @@ document.addEventListener('alpine:init', () => {
70
70
  // Don't show loading on initial load if we have cache
71
71
  this.initialLoad = false;
72
72
  this.computeQuotaRows();
73
- console.log('Restored data from cache');
73
+ if (window.UILogger) window.UILogger.debug('Restored data from cache');
74
74
  }
75
75
  }
76
76
  } catch (e) {
77
- console.warn('Failed to load cache', e);
77
+ if (window.UILogger) window.UILogger.debug('Failed to load cache', e.message);
78
78
  }
79
79
  },
80
80
 
@@ -89,7 +89,7 @@ document.addEventListener('alpine:init', () => {
89
89
  };
90
90
  localStorage.setItem('ag_data_cache', JSON.stringify(cacheData));
91
91
  } catch (e) {
92
- console.warn('Failed to save cache', e);
92
+ if (window.UILogger) window.UILogger.debug('Failed to save cache', e.message);
93
93
  }
94
94
  },
95
95
 
@@ -127,6 +127,7 @@ document.addEventListener('alpine:init', () => {
127
127
 
128
128
  this.lastUpdated = new Date().toLocaleTimeString();
129
129
  } catch (error) {
130
+ // Keep error logging for actual fetch failures
130
131
  console.error('Fetch error:', error);
131
132
  const store = Alpine.store('global');
132
133
  store.showToast(store.t('connectionLost'), 'error');
@@ -237,6 +238,7 @@ document.addEventListener('alpine:init', () => {
237
238
  let minResetTime = null;
238
239
 
239
240
  this.accounts.forEach(acc => {
241
+ if (acc.enabled === false) return;
240
242
  if (this.filters.account !== 'all' && acc.email !== this.filters.account) return;
241
243
 
242
244
  const limit = acc.limits?.[modelId];
@@ -352,6 +354,7 @@ document.addEventListener('alpine:init', () => {
352
354
  const quotaInfo = [];
353
355
  // Use ALL accounts (no account filter)
354
356
  this.accounts.forEach(acc => {
357
+ if (acc.enabled === false) return;
355
358
  const limit = acc.limits?.[modelId];
356
359
  if (!limit) return;
357
360
  const pct = limit.remainingFraction !== null ? Math.round(limit.remainingFraction * 100) : 0;
@@ -0,0 +1,143 @@
1
+ /**
2
+ * UI Logger Utility
3
+ * Provides conditional logging for the web UI to reduce console spam in production.
4
+ * Wraps console methods and only outputs when debug mode is enabled.
5
+ *
6
+ * Usage:
7
+ * window.UILogger.debug('message') - Only logs if debug mode enabled
8
+ * window.UILogger.info('message') - Only logs if debug mode enabled
9
+ * window.UILogger.warn('message') - Always logs (important warnings)
10
+ * window.UILogger.error('message') - Always logs (errors should always be visible)
11
+ *
12
+ * Enable debug mode:
13
+ * - Set localStorage.setItem('ag_debug', 'true') in browser console
14
+ * - Or pass ?debug=true in URL
15
+ */
16
+
17
+ (function() {
18
+ 'use strict';
19
+
20
+ // Check if debug mode is enabled
21
+ function isDebugEnabled() {
22
+ // Check URL parameter
23
+ const urlParams = new URLSearchParams(window.location.search);
24
+ if (urlParams.get('debug') === 'true') {
25
+ return true;
26
+ }
27
+
28
+ // Check localStorage
29
+ try {
30
+ return localStorage.getItem('ag_debug') === 'true';
31
+ } catch (e) {
32
+ return false;
33
+ }
34
+ }
35
+
36
+ // Cache debug state (can be refreshed)
37
+ let debugEnabled = isDebugEnabled();
38
+
39
+ window.UILogger = {
40
+ /**
41
+ * Refresh debug state (call after changing localStorage)
42
+ */
43
+ refresh() {
44
+ debugEnabled = isDebugEnabled();
45
+ },
46
+
47
+ /**
48
+ * Enable debug mode
49
+ */
50
+ enableDebug() {
51
+ try {
52
+ localStorage.setItem('ag_debug', 'true');
53
+ debugEnabled = true;
54
+ console.info('[UILogger] Debug mode enabled. Refresh page to see all logs.');
55
+ } catch (e) {
56
+ console.warn('[UILogger] Could not save debug preference');
57
+ }
58
+ },
59
+
60
+ /**
61
+ * Disable debug mode
62
+ */
63
+ disableDebug() {
64
+ try {
65
+ localStorage.removeItem('ag_debug');
66
+ debugEnabled = false;
67
+ console.info('[UILogger] Debug mode disabled.');
68
+ } catch (e) {
69
+ // Ignore
70
+ }
71
+ },
72
+
73
+ /**
74
+ * Check if debug mode is enabled
75
+ * @returns {boolean}
76
+ */
77
+ isDebug() {
78
+ return debugEnabled;
79
+ },
80
+
81
+ /**
82
+ * Debug level - only logs if debug mode enabled
83
+ * Use for verbose debugging info (chart updates, cache operations, etc.)
84
+ */
85
+ debug(...args) {
86
+ if (debugEnabled) {
87
+ console.log('[DEBUG]', ...args);
88
+ }
89
+ },
90
+
91
+ /**
92
+ * Info level - only logs if debug mode enabled
93
+ * Use for informational messages that aren't errors
94
+ */
95
+ info(...args) {
96
+ if (debugEnabled) {
97
+ console.info('[INFO]', ...args);
98
+ }
99
+ },
100
+
101
+ /**
102
+ * Log level - alias for debug
103
+ */
104
+ log(...args) {
105
+ if (debugEnabled) {
106
+ console.log(...args);
107
+ }
108
+ },
109
+
110
+ /**
111
+ * Warn level - always logs
112
+ * Use for important warnings that users should see
113
+ * But suppress noisy/expected warnings unless in debug mode
114
+ */
115
+ warn(...args) {
116
+ // In production, only show critical warnings
117
+ // In debug mode, show all warnings
118
+ if (debugEnabled) {
119
+ console.warn(...args);
120
+ }
121
+ },
122
+
123
+ /**
124
+ * Warn level that always shows (for critical warnings)
125
+ */
126
+ warnAlways(...args) {
127
+ console.warn(...args);
128
+ },
129
+
130
+ /**
131
+ * Error level - always logs
132
+ * Errors should always be visible for debugging
133
+ */
134
+ error(...args) {
135
+ console.error(...args);
136
+ }
137
+ };
138
+
139
+ // Log initial state (only in debug mode)
140
+ if (debugEnabled) {
141
+ console.info('[UILogger] Debug mode is ON. Set localStorage ag_debug=false to disable.');
142
+ }
143
+ })();
package/src/auth/oauth.js CHANGED
@@ -137,22 +137,50 @@ export function extractCodeFromInput(input) {
137
137
  return { code: trimmed, state: null };
138
138
  }
139
139
 
140
+ /**
141
+ * Attempt to bind server to a specific port
142
+ * @param {http.Server} server - HTTP server instance
143
+ * @param {number} port - Port to bind to
144
+ * @returns {Promise<number>} Resolves with port on success, rejects on error
145
+ */
146
+ function tryBindPort(server, port) {
147
+ return new Promise((resolve, reject) => {
148
+ const onError = (err) => {
149
+ server.removeListener('listening', onSuccess);
150
+ reject(err);
151
+ };
152
+ const onSuccess = () => {
153
+ server.removeListener('error', onError);
154
+ resolve(port);
155
+ };
156
+ server.once('error', onError);
157
+ server.once('listening', onSuccess);
158
+ server.listen(port);
159
+ });
160
+ }
161
+
140
162
  /**
141
163
  * Start a local server to receive the OAuth callback
164
+ * Implements automatic port fallback for Windows compatibility (issue #176)
142
165
  * Returns an object with a promise and an abort function
143
166
  *
144
167
  * @param {string} expectedState - Expected state parameter for CSRF protection
145
168
  * @param {number} timeoutMs - Timeout in milliseconds (default 120000)
146
- * @returns {{promise: Promise<string>, abort: Function}} Object with promise and abort function
169
+ * @returns {{promise: Promise<string>, abort: Function, getPort: Function}} Object with promise, abort, and getPort functions
147
170
  */
148
171
  export function startCallbackServer(expectedState, timeoutMs = 120000) {
149
172
  let server = null;
150
173
  let timeoutId = null;
151
174
  let isAborted = false;
175
+ let actualPort = OAUTH_CONFIG.callbackPort;
176
+
177
+ const promise = new Promise(async (resolve, reject) => {
178
+ // Build list of ports to try: primary + fallbacks
179
+ const portsToTry = [OAUTH_CONFIG.callbackPort, ...(OAUTH_CONFIG.callbackFallbackPorts || [])];
180
+ const errors = [];
152
181
 
153
- const promise = new Promise((resolve, reject) => {
154
182
  server = http.createServer((req, res) => {
155
- const url = new URL(req.url, `http://localhost:${OAUTH_CONFIG.callbackPort}`);
183
+ const url = new URL(req.url, `http://localhost:${actualPort}`);
156
184
 
157
185
  if (url.pathname !== '/oauth-callback') {
158
186
  res.writeHead(404);
@@ -232,17 +260,60 @@ export function startCallbackServer(expectedState, timeoutMs = 120000) {
232
260
  resolve(code);
233
261
  });
234
262
 
235
- server.on('error', (err) => {
236
- if (err.code === 'EADDRINUSE') {
237
- reject(new Error(`Port ${OAUTH_CONFIG.callbackPort} is already in use. Close any other OAuth flows and try again.`));
263
+ // Try ports with fallback logic (issue #176 - Windows EACCES fix)
264
+ let boundSuccessfully = false;
265
+ for (const port of portsToTry) {
266
+ try {
267
+ await tryBindPort(server, port);
268
+ actualPort = port;
269
+ boundSuccessfully = true;
270
+
271
+ if (port !== OAUTH_CONFIG.callbackPort) {
272
+ logger.warn(`[OAuth] Primary port ${OAUTH_CONFIG.callbackPort} unavailable, using fallback port ${port}`);
273
+ } else {
274
+ logger.info(`[OAuth] Callback server listening on port ${port}`);
275
+ }
276
+ break;
277
+ } catch (err) {
278
+ const errMsg = err.code === 'EACCES'
279
+ ? `Permission denied on port ${port}`
280
+ : err.code === 'EADDRINUSE'
281
+ ? `Port ${port} already in use`
282
+ : `Failed to bind port ${port}: ${err.message}`;
283
+ errors.push(errMsg);
284
+ logger.warn(`[OAuth] ${errMsg}`);
285
+ }
286
+ }
287
+
288
+ if (!boundSuccessfully) {
289
+ // All ports failed - provide helpful error message
290
+ const isWindows = process.platform === 'win32';
291
+ let errorMsg = `Failed to start OAuth callback server.\nTried ports: ${portsToTry.join(', ')}\n\nErrors:\n${errors.join('\n')}`;
292
+
293
+ if (isWindows) {
294
+ errorMsg += `\n
295
+ ================== WINDOWS TROUBLESHOOTING ==================
296
+ The default port range may be reserved by Hyper-V/WSL2/Docker.
297
+
298
+ Option 1: Use a custom port
299
+ Set OAUTH_CALLBACK_PORT=3456 in your environment or .env file
300
+
301
+ Option 2: Reset Windows NAT (run as Administrator)
302
+ net stop winnat && net start winnat
303
+
304
+ Option 3: Check reserved port ranges
305
+ netsh interface ipv4 show excludedportrange protocol=tcp
306
+
307
+ Option 4: Exclude port from reservation (run as Administrator)
308
+ netsh int ipv4 add excludedportrange protocol=tcp startport=51121 numberofports=1
309
+ ==============================================================`;
238
310
  } else {
239
- reject(err);
311
+ errorMsg += `\n\nTry setting a custom port: OAUTH_CALLBACK_PORT=3456`;
240
312
  }
241
- });
242
313
 
243
- server.listen(OAUTH_CONFIG.callbackPort, () => {
244
- logger.info(`[OAuth] Callback server listening on port ${OAUTH_CONFIG.callbackPort}`);
245
- });
314
+ reject(new Error(errorMsg));
315
+ return;
316
+ }
246
317
 
247
318
  // Timeout after specified duration
248
319
  timeoutId = setTimeout(() => {
@@ -266,7 +337,10 @@ export function startCallbackServer(expectedState, timeoutMs = 120000) {
266
337
  }
267
338
  };
268
339
 
269
- return { promise, abort };
340
+ // Get actual port (useful when fallback is used)
341
+ const getPort = () => actualPort;
342
+
343
+ return { promise, abort, getPort };
270
344
  }
271
345
 
272
346
  /**
package/src/constants.js CHANGED
@@ -188,13 +188,19 @@ export function isThinkingModel(modelName) {
188
188
  }
189
189
 
190
190
  // Google OAuth configuration (from opencode-antigravity-auth)
191
+ // OAuth callback port - configurable via environment variable for Windows compatibility (issue #176)
192
+ // Windows may reserve ports in range 49152-65535 for Hyper-V/WSL2/Docker, causing EACCES errors
193
+ const OAUTH_CALLBACK_PORT = parseInt(process.env.OAUTH_CALLBACK_PORT || '51121', 10);
194
+ const OAUTH_CALLBACK_FALLBACK_PORTS = [51122, 51123, 51124, 51125, 51126];
195
+
191
196
  export const OAUTH_CONFIG = {
192
197
  clientId: '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com',
193
198
  clientSecret: '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',
194
199
  authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
195
200
  tokenUrl: 'https://oauth2.googleapis.com/token',
196
201
  userInfoUrl: 'https://www.googleapis.com/oauth2/v1/userinfo',
197
- callbackPort: 51121,
202
+ callbackPort: OAUTH_CALLBACK_PORT,
203
+ callbackFallbackPorts: OAUTH_CALLBACK_FALLBACK_PORTS,
198
204
  scopes: [
199
205
  'https://www.googleapis.com/auth/cloud-platform',
200
206
  'https://www.googleapis.com/auth/userinfo.email',
@@ -236,7 +242,7 @@ export const DEFAULT_PRESETS = [
236
242
  ANTHROPIC_MODEL: 'claude-opus-4-5-thinking',
237
243
  ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-5-thinking',
238
244
  ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-5-thinking',
239
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite[1m]',
245
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'claude-sonnet-4-5',
240
246
  CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-5-thinking',
241
247
  ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
242
248
  }
@@ -249,7 +255,7 @@ export const DEFAULT_PRESETS = [
249
255
  ANTHROPIC_MODEL: 'gemini-3-pro-high[1m]',
250
256
  ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3-pro-high[1m]',
251
257
  ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3-flash[1m]',
252
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite[1m]',
258
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-3-flash[1m]',
253
259
  CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3-flash[1m]',
254
260
  ENABLE_EXPERIMENTAL_MCP_CLI: 'true'
255
261
  }