@kamel-ahmed/proxy-claude 1.0.4 → 1.0.5

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli/onboard.js +180 -69
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kamel-ahmed/proxy-claude",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Proxy server to use Antigravity's Claude models with Claude Code CLI - run 'proxy-claude' to start",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -50,41 +50,36 @@ const ACCOUNTS_FILE = path.join(CONFIG_DIR, 'accounts.json');
50
50
  const CLAUDE_CONFIG_DIR = path.join(PLATFORM.homeDir, '.claude');
51
51
  const CLAUDE_SETTINGS_FILE = path.join(CLAUDE_CONFIG_DIR, 'settings.json');
52
52
 
53
- // Default model configurations
54
- const MODEL_PRESETS = {
55
- claude: {
56
- name: 'Claude Models (Recommended)',
57
- description: 'Best for coding tasks with extended thinking',
58
- models: {
59
- ANTHROPIC_MODEL: 'claude-sonnet-4-5-thinking',
60
- ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-5-thinking',
61
- ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-5-thinking',
62
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite',
63
- CLAUDE_CODE_SUBAGENT_MODEL: 'claude-sonnet-4-5-thinking',
64
- }
53
+ // All available models in Antigravity (fetched dynamically, but this is a fallback)
54
+ const ALL_MODELS = [
55
+ // Claude models
56
+ { id: 'claude-opus-4-5-thinking', family: 'claude', tier: 'opus', description: 'Claude Opus 4.5 with thinking' },
57
+ { id: 'claude-sonnet-4-5-thinking', family: 'claude', tier: 'sonnet', description: 'Claude Sonnet 4.5 with thinking' },
58
+ { id: 'claude-sonnet-4-5', family: 'claude', tier: 'sonnet', description: 'Claude Sonnet 4.5' },
59
+ // Gemini models
60
+ { id: 'gemini-3-pro-high', family: 'gemini', tier: 'opus', description: 'Gemini 3 Pro High (best quality)' },
61
+ { id: 'gemini-3-pro-low', family: 'gemini', tier: 'sonnet', description: 'Gemini 3 Pro Low' },
62
+ { id: 'gemini-3-flash', family: 'gemini', tier: 'sonnet', description: 'Gemini 3 Flash (fast)' },
63
+ { id: 'gemini-2.5-flash-lite', family: 'gemini', tier: 'haiku', description: 'Gemini 2.5 Flash Lite (fastest)' },
64
+ ];
65
+
66
+ // Model tier descriptions
67
+ const TIER_INFO = {
68
+ opus: {
69
+ name: 'Opus (Primary)',
70
+ description: 'Main model for complex tasks',
71
+ envKey: 'ANTHROPIC_DEFAULT_OPUS_MODEL',
65
72
  },
66
- gemini: {
67
- name: 'Gemini Models',
68
- description: 'Google\'s Gemini models with thinking support',
69
- models: {
70
- ANTHROPIC_MODEL: 'gemini-3-pro-high',
71
- ANTHROPIC_DEFAULT_OPUS_MODEL: 'gemini-3-pro-high',
72
- ANTHROPIC_DEFAULT_SONNET_MODEL: 'gemini-3-flash',
73
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite',
74
- CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3-flash',
75
- }
73
+ sonnet: {
74
+ name: 'Sonnet (Default)',
75
+ description: 'Balanced model for most tasks',
76
+ envKey: 'ANTHROPIC_DEFAULT_SONNET_MODEL',
77
+ },
78
+ haiku: {
79
+ name: 'Haiku (Fast)',
80
+ description: 'Quick model for simple tasks & background',
81
+ envKey: 'ANTHROPIC_DEFAULT_HAIKU_MODEL',
76
82
  },
77
- balanced: {
78
- name: 'Balanced (Claude + Gemini)',
79
- description: 'Claude for main tasks, Gemini for background',
80
- models: {
81
- ANTHROPIC_MODEL: 'claude-sonnet-4-5-thinking',
82
- ANTHROPIC_DEFAULT_OPUS_MODEL: 'claude-opus-4-5-thinking',
83
- ANTHROPIC_DEFAULT_SONNET_MODEL: 'claude-sonnet-4-5-thinking',
84
- ANTHROPIC_DEFAULT_HAIKU_MODEL: 'gemini-2.5-flash-lite',
85
- CLAUDE_CODE_SUBAGENT_MODEL: 'gemini-3-flash',
86
- }
87
- }
88
83
  };
89
84
 
90
85
  /**
@@ -215,6 +210,65 @@ function getExistingAccounts() {
215
210
  return [];
216
211
  }
217
212
 
213
+ /**
214
+ * Fetch available models from the API using account token
215
+ */
216
+ async function fetchAvailableModels(accounts) {
217
+ if (!accounts || accounts.length === 0) {
218
+ return ALL_MODELS;
219
+ }
220
+
221
+ try {
222
+ // Get token from first account
223
+ const account = accounts[0];
224
+ const token = account.accessToken;
225
+
226
+ if (!token) {
227
+ return ALL_MODELS;
228
+ }
229
+
230
+ // Import the model API
231
+ const { fetchAvailableModels: fetchModels } = await import('../cloudcode/model-api.js');
232
+ const data = await fetchModels(token);
233
+
234
+ if (data && data.models) {
235
+ const models = [];
236
+ for (const [modelId, modelData] of Object.entries(data.models)) {
237
+ // Only include Claude and Gemini models
238
+ if (!modelId.includes('claude') && !modelId.includes('gemini')) continue;
239
+
240
+ // Determine family
241
+ const family = modelId.includes('claude') ? 'claude' : 'gemini';
242
+
243
+ // Determine tier based on model name
244
+ let tier = 'sonnet';
245
+ if (modelId.includes('opus') || modelId.includes('pro-high')) tier = 'opus';
246
+ else if (modelId.includes('haiku') || modelId.includes('flash-lite')) tier = 'haiku';
247
+
248
+ // Check if model has quota (remaining > 0)
249
+ const hasQuota = modelData.remainingFraction === undefined || modelData.remainingFraction > 0;
250
+
251
+ models.push({
252
+ id: modelId,
253
+ family,
254
+ tier,
255
+ description: modelData.displayName || modelId,
256
+ hasQuota,
257
+ remainingFraction: modelData.remainingFraction,
258
+ });
259
+ }
260
+
261
+ if (models.length > 0) {
262
+ return models;
263
+ }
264
+ }
265
+ } catch (error) {
266
+ print(`Could not fetch models from API: ${error.message}`, 'warn');
267
+ }
268
+
269
+ return ALL_MODELS;
270
+ }
271
+
218
272
  /**
219
273
  * Add Google account via OAuth
220
274
  */
@@ -260,9 +314,69 @@ function saveClaudeSettings(settings) {
260
314
  }
261
315
 
262
316
  /**
263
- * Configure Claude Code settings
317
+ * Select a model for a specific tier
318
+ */
319
+ async function selectModelForTier(rl, tier, availableModels) {
320
+ const tierInfo = TIER_INFO[tier];
321
+ console.log('');
322
+ console.log(`${COLORS.bold}${tierInfo.name}${COLORS.reset} - ${COLORS.dim}${tierInfo.description}${COLORS.reset}`);
323
+ console.log('');
324
+
325
+ // Filter models that are suitable for this tier or higher
326
+ // opus can use any model, sonnet can use sonnet/haiku, haiku uses haiku
327
+ const tierPriority = { opus: 3, sonnet: 2, haiku: 1 };
328
+ const minTier = tierPriority[tier];
329
+
330
+ const suitableModels = availableModels.filter(m => {
331
+ const modelTier = tierPriority[m.tier] || 2;
332
+ return modelTier >= minTier - 1; // Allow one tier lower
333
+ });
334
+
335
+ // Sort: models with quota first, then by family (claude first), then by tier
336
+ suitableModels.sort((a, b) => {
337
+ // Quota status
338
+ if (a.hasQuota !== b.hasQuota) return a.hasQuota ? -1 : 1;
339
+ // Family (claude first for opus/sonnet, gemini first for haiku)
340
+ if (tier === 'haiku') {
341
+ if (a.family !== b.family) return a.family === 'gemini' ? -1 : 1;
342
+ } else {
343
+ if (a.family !== b.family) return a.family === 'claude' ? -1 : 1;
344
+ }
345
+ // Tier priority
346
+ return (tierPriority[b.tier] || 0) - (tierPriority[a.tier] || 0);
347
+ });
348
+
349
+ // Display models
350
+ console.log(`${COLORS.dim}Available models:${COLORS.reset}`);
351
+ suitableModels.forEach((model, index) => {
352
+ const quotaStatus = model.hasQuota === false
353
+ ? `${COLORS.red}(no quota)${COLORS.reset}`
354
+ : model.remainingFraction !== undefined
355
+ ? `${COLORS.green}(${Math.round(model.remainingFraction * 100)}% quota)${COLORS.reset}`
356
+ : '';
357
+ const familyBadge = model.family === 'claude'
358
+ ? `${COLORS.magenta}[Claude]${COLORS.reset}`
359
+ : `${COLORS.blue}[Gemini]${COLORS.reset}`;
360
+ console.log(` ${COLORS.cyan}${index + 1}.${COLORS.reset} ${model.id} ${familyBadge} ${quotaStatus}`);
361
+ });
362
+ console.log('');
363
+
364
+ // Default to first model with quota
365
+ const defaultIndex = suitableModels.findIndex(m => m.hasQuota !== false) + 1 || 1;
366
+
367
+ const choice = await prompt(rl, `Select model for ${tier} (1-${suitableModels.length})`, String(defaultIndex));
368
+ const index = parseInt(choice, 10) - 1;
369
+
370
+ if (index >= 0 && index < suitableModels.length) {
371
+ return suitableModels[index].id;
372
+ }
373
+ return suitableModels[0]?.id || ALL_MODELS.find(m => m.tier === tier)?.id;
374
+ }
375
+
376
+ /**
377
+ * Configure Claude Code settings with selected models
264
378
  */
265
- function configureClaudeSettings(modelPreset, port = 8080) {
379
+ function configureClaudeSettingsWithModels(modelConfig, port = 8080) {
266
380
  const existing = loadClaudeSettings();
267
381
 
268
382
  const newSettings = {
@@ -271,7 +385,11 @@ function configureClaudeSettings(modelPreset, port = 8080) {
271
385
  ...(existing.env || {}),
272
386
  ANTHROPIC_AUTH_TOKEN: 'proxy-claude',
273
387
  ANTHROPIC_BASE_URL: `http://localhost:${port}`,
274
- ...modelPreset.models,
388
+ ANTHROPIC_MODEL: modelConfig.sonnet, // Default model is sonnet
389
+ ANTHROPIC_DEFAULT_OPUS_MODEL: modelConfig.opus,
390
+ ANTHROPIC_DEFAULT_SONNET_MODEL: modelConfig.sonnet,
391
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: modelConfig.haiku,
392
+ CLAUDE_CODE_SUBAGENT_MODEL: modelConfig.haiku, // Subagent uses haiku for efficiency
275
393
  ENABLE_EXPERIMENTAL_MCP_CLI: 'true',
276
394
  }
277
395
  };
@@ -286,33 +404,9 @@ function configureClaudeSettings(modelPreset, port = 8080) {
286
404
  }
287
405
 
288
406
  /**
289
- * Display model selection menu
407
+ * Print summary with selected models
290
408
  */
291
- async function selectModelPreset(rl) {
292
- console.log('');
293
- console.log(`${COLORS.bold}Available model configurations:${COLORS.reset}`);
294
- console.log('');
295
-
296
- const presets = Object.entries(MODEL_PRESETS);
297
- presets.forEach(([key, preset], index) => {
298
- console.log(` ${COLORS.cyan}${index + 1}.${COLORS.reset} ${COLORS.bold}${preset.name}${COLORS.reset}`);
299
- console.log(` ${COLORS.dim}${preset.description}${COLORS.reset}`);
300
- });
301
- console.log('');
302
-
303
- const choice = await prompt(rl, 'Select configuration (1-3)', '1');
304
- const index = parseInt(choice, 10) - 1;
305
-
306
- if (index >= 0 && index < presets.length) {
307
- return presets[index];
308
- }
309
- return presets[0]; // Default to first option
310
- }
311
-
312
- /**
313
- * Print summary
314
- */
315
- function printSummary(accounts, modelPreset, port) {
409
+ function printSummary(accounts, modelConfig, port) {
316
410
  console.log('');
317
411
  console.log(`${COLORS.green}╔══════════════════════════════════════════════════════════╗${COLORS.reset}`);
318
412
  console.log(`${COLORS.green}║${COLORS.reset} ${COLORS.bold}${COLORS.green}Setup Complete!${COLORS.reset} ${COLORS.green}║${COLORS.reset}`);
@@ -321,7 +415,9 @@ function printSummary(accounts, modelPreset, port) {
321
415
 
322
416
  console.log(`${COLORS.bold}Configuration Summary:${COLORS.reset}`);
323
417
  console.log(` ${COLORS.dim}•${COLORS.reset} Accounts: ${accounts.length} configured`);
324
- console.log(` ${COLORS.dim}•${COLORS.reset} Model preset: ${modelPreset[1].name}`);
418
+ console.log(` ${COLORS.dim}•${COLORS.reset} Opus model: ${COLORS.cyan}${modelConfig.opus}${COLORS.reset}`);
419
+ console.log(` ${COLORS.dim}•${COLORS.reset} Sonnet model: ${COLORS.cyan}${modelConfig.sonnet}${COLORS.reset}`);
420
+ console.log(` ${COLORS.dim}•${COLORS.reset} Haiku model: ${COLORS.cyan}${modelConfig.haiku}${COLORS.reset}`);
325
421
  console.log(` ${COLORS.dim}•${COLORS.reset} Proxy port: ${port}`);
326
422
  console.log(` ${COLORS.dim}•${COLORS.reset} Settings saved to: ${CLAUDE_SETTINGS_FILE}`);
327
423
  console.log('');
@@ -422,11 +518,26 @@ export async function runOnboarding(options = {}) {
422
518
  // Step 3: Model Configuration
423
519
  printStep(++currentStep, totalSteps, 'Model Configuration');
424
520
 
425
- print('Select which AI models to use with Claude Code.', 'info');
426
- print(`${COLORS.dim}(Haiku model uses Gemini to preserve Claude quota)${COLORS.reset}`, 'info');
521
+ print('Fetching available models from your account...', 'step');
522
+ const availableModels = await fetchAvailableModels(accounts);
523
+ print(`Found ${availableModels.length} models available`, 'success');
524
+
525
+ console.log('');
526
+ print('Now select a model for each tier. Models with quota are shown first.', 'info');
527
+ print(`${COLORS.dim}Tip: Use Gemini for Haiku to save your Claude quota!${COLORS.reset}`, 'info');
528
+
529
+ // Select model for each tier
530
+ const modelConfig = {
531
+ opus: await selectModelForTier(rl, 'opus', availableModels),
532
+ sonnet: await selectModelForTier(rl, 'sonnet', availableModels),
533
+ haiku: await selectModelForTier(rl, 'haiku', availableModels),
534
+ };
427
535
 
428
- const modelPreset = await selectModelPreset(rl);
429
- print(`Selected: ${modelPreset[1].name}`, 'success');
536
+ console.log('');
537
+ print('Model configuration:', 'success');
538
+ console.log(` ${COLORS.dim}Opus:${COLORS.reset} ${COLORS.cyan}${modelConfig.opus}${COLORS.reset}`);
539
+ console.log(` ${COLORS.dim}Sonnet:${COLORS.reset} ${COLORS.cyan}${modelConfig.sonnet}${COLORS.reset}`);
540
+ console.log(` ${COLORS.dim}Haiku:${COLORS.reset} ${COLORS.cyan}${modelConfig.haiku}${COLORS.reset}`);
430
541
 
431
542
  // Step 4: Apply Configuration
432
543
  printStep(++currentStep, totalSteps, 'Applying Configuration');
@@ -435,11 +546,11 @@ export async function runOnboarding(options = {}) {
435
546
 
436
547
  // Configure Claude Code settings
437
548
  print('Configuring Claude Code settings...', 'step');
438
- configureClaudeSettings(modelPreset[1], port);
549
+ configureClaudeSettingsWithModels(modelConfig, port);
439
550
  print(`Settings saved to ${CLAUDE_SETTINGS_FILE}`, 'success');
440
551
 
441
552
  // Print summary
442
- printSummary(accounts, modelPreset, port);
553
+ printSummary(accounts, modelConfig, port);
443
554
 
444
555
  rl.close();
445
556
  return true;