ai-speedometer 1.0.0 → 1.2.0

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/cli.js CHANGED
@@ -15,6 +15,17 @@ import {
15
15
  migrateFromOldConfig,
16
16
  getDebugInfo
17
17
  } from './opencode-integration.js';
18
+ import {
19
+ readAIConfig,
20
+ getCustomProvidersFromConfig,
21
+ getVerifiedProvidersFromConfig,
22
+ addCustomProvider,
23
+ addModelToCustomProvider,
24
+ getAIConfigDebugPaths,
25
+ addToRecentModels,
26
+ getRecentModels,
27
+ cleanupRecentModelsFromConfig
28
+ } from './ai-config.js';
18
29
  import 'dotenv/config';
19
30
  import Table from 'cli-table3';
20
31
 
@@ -45,17 +56,12 @@ function createAnthropicProvider(baseUrl, apiKey) {
45
56
  log(`Creating Anthropic provider with baseUrl: ${baseUrl}`);
46
57
  log(`API Key length: ${apiKey ? apiKey.length : 0}`);
47
58
 
48
- // Ensure base URL ends with /v1 for AI SDK compatibility
49
- let normalizedBaseUrl = baseUrl;
50
- if (!baseUrl.endsWith('/v1')) {
51
- normalizedBaseUrl = baseUrl.endsWith('/') ? `${baseUrl}v1` : `${baseUrl}/v1`;
52
- log(`Normalized base URL to: ${normalizedBaseUrl}`);
53
- }
59
+ // Use baseUrl as provided - no automatic normalization needed
54
60
 
55
61
  // Try with baseURL parameter (correct according to docs)
56
62
  const provider = createAnthropic({
57
63
  apiKey: apiKey,
58
- baseURL: normalizedBaseUrl,
64
+ baseURL: baseUrl,
59
65
  // Add minimal fetch logging for debugging
60
66
  fetch: debugMode ? async (input, init) => {
61
67
  log(`API Request to: ${input}`);
@@ -123,13 +129,13 @@ function showHeader() {
123
129
  console.log('');
124
130
  }
125
131
 
126
- // Configuration management - now using opencode files
132
+ // Configuration management - now using ai-benchmark-config.json for custom providers
127
133
  async function loadConfig() {
128
134
  try {
129
135
  // Check if we need to migrate from old config
130
136
  const oldConfigFile = 'ai-benchmark-config.json';
131
137
  if (fs.existsSync(oldConfigFile)) {
132
- console.log(colorText('Migrating from old config format to opencode format...', 'yellow'));
138
+ console.log(colorText('Migrating from old config format to new format...', 'yellow'));
133
139
 
134
140
  try {
135
141
  const data = fs.readFileSync(oldConfigFile, 'utf8');
@@ -156,7 +162,7 @@ async function loadConfig() {
156
162
  }
157
163
  }
158
164
 
159
- // Load providers from opencode integration
165
+ // Load providers from both auth.json (verified) and ai-benchmark-config.json (custom)
160
166
  const providers = await getAllAvailableProviders();
161
167
 
162
168
  return {
@@ -169,11 +175,11 @@ async function loadConfig() {
169
175
  }
170
176
  }
171
177
 
172
- // Save config - now using opencode files
178
+ // Save config - now using ai-benchmark-config.json and auth.json
173
179
  async function saveConfig(config) {
174
180
  // Note: This function is kept for compatibility but the actual saving
175
- // is handled by the opencode integration functions (addApiKey, etc.)
176
- console.log(colorText('Note: Configuration is now automatically saved to opencode files', 'cyan'));
181
+ // is handled by the ai-config.js and opencode-integration.js functions
182
+ console.log(colorText('Note: Configuration is now automatically saved to ai-benchmark-config.json and auth.json', 'cyan'));
177
183
  }
178
184
 
179
185
  // Keyboard input handling
@@ -200,9 +206,12 @@ async function selectModelsCircular() {
200
206
  showHeader();
201
207
  console.log(colorText('Select Models for Benchmark', 'magenta'));
202
208
  console.log('');
203
-
209
+
204
210
  const config = await loadConfig();
205
-
211
+
212
+ // Clean up recent models from main config and migrate to cache
213
+ await cleanupRecentModelsFromConfig();
214
+
206
215
  if (config.providers.length === 0) {
207
216
  console.log(colorText('No providers available. Please add a provider first.', 'red'));
208
217
  await question(colorText('Press Enter to continue...', 'yellow'));
@@ -227,16 +236,38 @@ async function selectModelsCircular() {
227
236
  });
228
237
  });
229
238
 
239
+ // Load recent models
240
+ const recentModelsData = await getRecentModels();
241
+
242
+ // Create a mapping of recent models to actual model objects
243
+ const recentModelObjects = [];
244
+ recentModelsData.forEach(recentModel => {
245
+ const modelObj = allModels.find(model =>
246
+ model.id === recentModel.modelId &&
247
+ model.providerName === recentModel.providerName
248
+ );
249
+ if (modelObj) {
250
+ recentModelObjects.push({
251
+ ...modelObj,
252
+ isRecent: true
253
+ });
254
+ }
255
+ });
256
+
230
257
  let currentIndex = 0;
231
258
  let currentPage = 0;
232
259
  let searchQuery = '';
233
- let filteredModels = [...allModels];
234
260
 
235
261
  // Create a reusable filter function to avoid code duplication
236
262
  const filterModels = (query) => {
237
263
  if (!query.trim()) {
238
- return [...allModels];
264
+ // When search is empty, return the combined list with recent models at top
265
+ const recentModelIds = new Set(recentModelObjects.map(m => m.id));
266
+ const nonRecentModels = allModels.filter(model => !recentModelIds.has(model.id));
267
+ return [...recentModelObjects, ...nonRecentModels];
239
268
  }
269
+
270
+ // When searching, search through all models (no recent section)
240
271
  const lowercaseQuery = query.toLowerCase();
241
272
  return allModels.filter(model => {
242
273
  const modelNameMatch = model.name.toLowerCase().includes(lowercaseQuery);
@@ -248,6 +279,9 @@ async function selectModelsCircular() {
248
279
  });
249
280
  };
250
281
 
282
+ // Initialize filtered models using the filter function
283
+ let filteredModels = filterModels('');
284
+
251
285
  // Debounce function to reduce filtering frequency
252
286
  let searchTimeout;
253
287
  const debouncedFilter = (query, callback) => {
@@ -272,7 +306,7 @@ async function selectModelsCircular() {
272
306
  screenContent += colorText('Type to search (real-time filtering)', 'cyan') + '\n';
273
307
  screenContent += colorText('Press "A" to select all models, "N" to deselect all', 'cyan') + '\n';
274
308
  screenContent += colorText('Circle states: ●=Current+Selected ○=Current+Unselected ●=Selected ○=Unselected', 'dim') + '\n';
275
- screenContent += colorText('Quick run: ENTER on any model | Multi-select: TAB then ENTER', 'dim') + '\n';
309
+ screenContent += colorText('Quick run: ENTER on any model | Multi-select: TAB then ENTER | Recent: R', 'dim') + '\n';
276
310
  screenContent += '\n';
277
311
 
278
312
  // Search interface - always visible
@@ -291,13 +325,51 @@ async function selectModelsCircular() {
291
325
  const endIndex = Math.min(startIndex + visibleItemsCount, filteredModels.length);
292
326
 
293
327
  // Display models in a vertical layout with pagination
294
- screenContent += colorText('Available Models:', 'yellow') + '\n';
295
- screenContent += '\n';
328
+ let hasRecentModelsInCurrentPage = false;
329
+ let recentSectionDisplayed = false;
330
+ let nonRecentSectionDisplayed = false;
331
+
332
+ // Only show recent section when search is empty and we have recent models
333
+ const showRecentSection = searchQuery.length === 0 && recentModelObjects.length > 0;
334
+
335
+ // Check if current page contains any recent models (only when search is empty)
336
+ if (showRecentSection) {
337
+ for (let i = startIndex; i < endIndex; i++) {
338
+ if (filteredModels[i].isRecent) {
339
+ hasRecentModelsInCurrentPage = true;
340
+ break;
341
+ }
342
+ }
343
+ }
296
344
 
345
+ // Display models with proper section headers
297
346
  for (let i = startIndex; i < endIndex; i++) {
298
347
  const model = filteredModels[i];
299
348
  const isCurrent = i === currentIndex;
300
- const isSelected = model.selected;
349
+ // For recent models, check selection state from the original model
350
+ let isSelected;
351
+ if (model.isRecent) {
352
+ const originalModelIndex = allModels.findIndex(originalModel =>
353
+ originalModel.id === model.id &&
354
+ originalModel.providerName === model.providerName &&
355
+ !originalModel.isRecent
356
+ );
357
+ isSelected = originalModelIndex !== -1 ? allModels[originalModelIndex].selected : false;
358
+ } else {
359
+ isSelected = model.selected;
360
+ }
361
+
362
+ // Show recent section header if we encounter a recent model and haven't shown the header yet
363
+ if (model.isRecent && !recentSectionDisplayed && hasRecentModelsInCurrentPage && showRecentSection) {
364
+ screenContent += colorText('-------recent--------', 'dim') + '\n';
365
+ recentSectionDisplayed = true;
366
+ }
367
+
368
+ // Show separator between recent and non-recent models
369
+ if (!model.isRecent && recentSectionDisplayed && !nonRecentSectionDisplayed && showRecentSection) {
370
+ screenContent += colorText('-------recent--------', 'dim') + '\n';
371
+ nonRecentSectionDisplayed = true;
372
+ }
301
373
 
302
374
  // Single circle that shows both current state and selection
303
375
  let circle;
@@ -375,10 +447,28 @@ async function selectModelsCircular() {
375
447
  }
376
448
  } else if (key === '\t') {
377
449
  // Tab - select/deselect current model
378
- const actualModelIndex = allModels.indexOf(filteredModels[currentIndex]);
450
+ const currentModel = filteredModels[currentIndex];
451
+ let actualModelIndex;
452
+
453
+ if (currentModel.isRecent) {
454
+ // For recent models, find by matching the original model ID and provider name
455
+ actualModelIndex = allModels.findIndex(model =>
456
+ model.id === currentModel.id &&
457
+ model.providerName === currentModel.providerName &&
458
+ !model.isRecent // Don't match the recent copy, match the original
459
+ );
460
+ } else {
461
+ // For regular models, use the standard matching
462
+ actualModelIndex = allModels.findIndex(model =>
463
+ model.id === currentModel.id && model.providerName === currentModel.providerName
464
+ );
465
+ }
466
+
379
467
  if (actualModelIndex !== -1) {
380
468
  allModels[actualModelIndex].selected = !allModels[actualModelIndex].selected;
381
469
  }
470
+ // Force immediate screen redraw by continuing to next iteration
471
+ continue;
382
472
  } else if (key === '\r') {
383
473
  // Enter - run benchmark on selected models
384
474
  const currentModel = filteredModels[currentIndex];
@@ -445,6 +535,35 @@ async function selectModelsCircular() {
445
535
  currentPage = 0;
446
536
  });
447
537
  }
538
+ } else if (key === 'R' || key === 'r') {
539
+ // Run recent models - only when search is empty and we have recent models
540
+ if (searchQuery.length === 0 && recentModelObjects.length > 0) {
541
+ // Deselect all models first
542
+ allModels.forEach(model => model.selected = false);
543
+
544
+ // Select all recent models by finding the original models
545
+ recentModelObjects.forEach(recentModel => {
546
+ const actualModelIndex = allModels.findIndex(model =>
547
+ model.id === recentModel.id &&
548
+ model.providerName === recentModel.providerName &&
549
+ !model.isRecent // Match the original, not the recent copy
550
+ );
551
+ if (actualModelIndex !== -1) {
552
+ allModels[actualModelIndex].selected = true;
553
+ }
554
+ });
555
+
556
+ // Break out of loop to run benchmark
557
+ break;
558
+ } else {
559
+ // If search is active or no recent models, add 'R' to search query
560
+ searchQuery += key;
561
+ debouncedFilter(searchQuery, (newFilteredModels) => {
562
+ filteredModels = newFilteredModels;
563
+ currentIndex = 0;
564
+ currentPage = 0;
565
+ });
566
+ }
448
567
  } else if (key === 'a' || key === 'n') {
449
568
  // Lowercase 'a' and 'n' go to search field (not select all/none)
450
569
  searchQuery += key;
@@ -650,11 +769,11 @@ async function runStreamingBenchmark(models) {
650
769
  console.log('');
651
770
  console.log(colorText('All benchmarks completed!', 'green'));
652
771
 
653
- await displayColorfulResults(results, 'AI SDK');
772
+ await displayColorfulResults(results, 'AI SDK', models);
654
773
  }
655
774
 
656
775
  // Colorful results display with comprehensive table and enhanced bars
657
- async function displayColorfulResults(results, method = 'AI SDK') {
776
+ async function displayColorfulResults(results, method = 'AI SDK', models = []) {
658
777
  clearScreen();
659
778
  showHeader();
660
779
  console.log(colorText('BENCHMARK RESULTS', 'magenta'));
@@ -817,23 +936,40 @@ async function displayColorfulResults(results, method = 'AI SDK') {
817
936
  console.log('');
818
937
  }
819
938
 
939
+ // Add successful models to recent models list
940
+ const successfulModels = results
941
+ .filter(r => r.success)
942
+ .map(r => {
943
+ // Find the actual model object that matches this benchmark result
944
+ const modelObj = models.find(model =>
945
+ model.name === r.model && model.providerName === r.provider
946
+ );
947
+
948
+ return {
949
+ modelId: modelObj ? modelObj.id : r.model, // Use actual ID if found, fallback to name
950
+ modelName: r.model,
951
+ providerName: r.provider
952
+ };
953
+ });
954
+
955
+ if (successfulModels.length > 0) {
956
+ await addToRecentModels(successfulModels);
957
+ }
958
+
820
959
  console.log(colorText('Benchmark completed!', 'green'));
821
960
  await question(colorText('Press Enter to continue...', 'yellow'));
822
961
  }
823
962
 
824
963
  // Helper function to calculate visible items based on terminal height
825
- function getVisibleItemsCount(headerHeight = 8) {
964
+ function getVisibleItemsCount(headerHeight = 10) {
826
965
  const terminalHeight = process.stdout.rows || 24;
827
- return Math.max(5, terminalHeight - headerHeight);
966
+ return Math.max(3, terminalHeight - headerHeight);
828
967
  }
829
968
 
830
- // Provider management with models.dev integration and pagination
831
- async function addProvider() {
832
- clearScreen();
833
- showHeader();
834
- console.log(colorText('Add Provider', 'magenta'));
835
- console.log('');
836
-
969
+
970
+
971
+ // Add a verified provider (saves to both auth.json and ai-benchmark-config.json)
972
+ async function addVerifiedProvider() {
837
973
  let searchQuery = '';
838
974
  let allProviders = [];
839
975
  let filteredProviders = [];
@@ -845,6 +981,10 @@ async function addProvider() {
845
981
  allProviders = await getAllProviders();
846
982
  filteredProviders = allProviders;
847
983
  } catch (error) {
984
+ clearScreen();
985
+ showHeader();
986
+ console.log(colorText('Add Verified Provider', 'magenta'));
987
+ console.log('');
848
988
  console.log(colorText('Error loading providers: ', 'red') + error.message);
849
989
  await question(colorText('Press Enter to continue...', 'yellow'));
850
990
  return;
@@ -860,19 +1000,19 @@ async function addProvider() {
860
1000
  screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
861
1001
  screenContent += '\n';
862
1002
 
863
- screenContent += colorText('Add Provider', 'magenta') + '\n';
1003
+ screenContent += colorText('Add Verified Provider', 'magenta') + '\n';
864
1004
  screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
865
1005
  screenContent += colorText('Type to search (real-time filtering)', 'cyan') + '\n';
866
1006
  screenContent += colorText('Navigation is circular', 'dim') + '\n';
867
1007
  screenContent += '\n';
868
1008
 
869
1009
  // Search interface - always visible
870
- screenContent += colorText('Search: ', 'yellow') + colorText(searchQuery + '_', 'bright') + '\n';
1010
+ screenContent += colorText('🔍 Search: ', 'yellow') + colorText(searchQuery + '_', 'bright') + '\n';
871
1011
  screenContent += '\n';
872
1012
 
873
1013
  // Calculate pagination
874
- const visibleItemsCount = getVisibleItemsCount();
875
- const totalItems = filteredProviders.length + 1; // +1 for custom provider option
1014
+ const visibleItemsCount = getVisibleItemsCount(11); // Account for search bar and header
1015
+ const totalItems = filteredProviders.length;
876
1016
  const totalPages = Math.ceil(totalItems / visibleItemsCount);
877
1017
 
878
1018
  // Ensure current page is valid
@@ -883,7 +1023,7 @@ async function addProvider() {
883
1023
  const endIndex = Math.min(startIndex + visibleItemsCount, totalItems);
884
1024
 
885
1025
  // Display providers with pagination
886
- screenContent += colorText('Available Providers:', 'cyan') + '\n';
1026
+ screenContent += colorText('Available Verified Providers:', 'cyan') + '\n';
887
1027
  screenContent += '\n';
888
1028
 
889
1029
  // Show current page of providers
@@ -897,16 +1037,6 @@ async function addProvider() {
897
1037
  screenContent += `${indicator} ${providerName} ${providerType}\n`;
898
1038
  }
899
1039
 
900
- // Show "Add Custom Provider" option if it's on current page
901
- const customIndex = filteredProviders.length;
902
- if (customIndex >= startIndex && customIndex < endIndex) {
903
- const isCustomCurrent = customIndex === currentIndex;
904
- const customIndicator = isCustomCurrent ? colorText('●', 'green') : colorText('○', 'dim');
905
- const customText = isCustomCurrent ? colorText('Add Custom Provider', 'bright') : colorText('Add Custom Provider', 'yellow');
906
-
907
- screenContent += `${customIndicator} ${customText}\n`;
908
- }
909
-
910
1040
  // Show pagination info
911
1041
  screenContent += '\n';
912
1042
  if (totalPages > 1) {
@@ -959,14 +1089,8 @@ async function addProvider() {
959
1089
  currentIndex = currentPage * visibleItemsCount;
960
1090
  }
961
1091
  } else if (key === '\r') {
962
- // Enter - select current option
963
- if (currentIndex === filteredProviders.length) {
964
- // Custom provider selected
965
- await addCustomProvider();
966
- } else {
967
- // Verified provider selected - auto-add all models
968
- await addVerifiedProviderAuto(filteredProviders[currentIndex]);
969
- }
1092
+ // Enter - select current provider
1093
+ await addVerifiedProviderAuto(filteredProviders[currentIndex]);
970
1094
  break;
971
1095
  } else if (key === '\u0003') {
972
1096
  // Ctrl+C
@@ -1036,18 +1160,26 @@ async function addVerifiedProviderAuto(provider) {
1036
1160
  return;
1037
1161
  }
1038
1162
 
1039
- // Add API key to opencode auth.json
1040
- const success = await addApiKey(provider.id, apiKey);
1163
+ // Add API key to auth.json (for opencode integration)
1164
+ const authSuccess = await addApiKey(provider.id, apiKey);
1041
1165
 
1042
- if (!success) {
1043
- console.log(colorText('Failed to save API key to opencode auth.json', 'red'));
1166
+ if (!authSuccess) {
1167
+ console.log(colorText('Failed to save API key to auth.json', 'red'));
1044
1168
  await question(colorText('Press Enter to continue...', 'yellow'));
1045
1169
  return;
1046
1170
  }
1047
1171
 
1172
+ // Also add to ai-benchmark-config.json for consistency
1173
+ const { addVerifiedProvider: addVerifiedProviderToConfig } = await import('./ai-config.js');
1174
+ const configSuccess = await addVerifiedProviderToConfig(provider.id, apiKey);
1175
+
1176
+ if (!configSuccess) {
1177
+ console.log(colorText('Warning: Could not save to ai-benchmark-config.json', 'yellow'));
1178
+ }
1179
+
1048
1180
  console.log('');
1049
1181
  console.log(colorText('Provider added successfully!', 'green'));
1050
- console.log(colorText(`API key saved to opencode auth.json`, 'cyan'));
1182
+ console.log(colorText(`API key saved to auth.json`, 'cyan'));
1051
1183
  console.log(colorText(`Models will be loaded dynamically from ${provider.name}`, 'cyan'));
1052
1184
  console.log(colorText(`Found ${models.length} available models`, 'cyan'));
1053
1185
 
@@ -1056,19 +1188,19 @@ async function addVerifiedProviderAuto(provider) {
1056
1188
 
1057
1189
 
1058
1190
 
1059
- // Add a custom provider (now integrated with opencode.json)
1060
- async function addCustomProvider() {
1191
+ // Add a custom provider (now using ai-benchmark-config.json)
1192
+ async function addCustomProviderCLI() {
1061
1193
  clearScreen();
1062
1194
  showHeader();
1063
1195
  console.log(colorText('Add Custom Provider', 'magenta'));
1064
1196
  console.log('');
1065
- console.log(colorText('Note: Custom providers are saved to opencode.json', 'cyan'));
1197
+ console.log(colorText('Note: Custom providers are saved to ai-benchmark-config.json', 'cyan'));
1066
1198
  console.log('');
1067
1199
 
1068
1200
  const providerOptions = [
1069
1201
  { id: 1, text: 'OpenAI Compatible', type: 'openai-compatible' },
1070
1202
  { id: 2, text: 'Anthropic', type: 'anthropic' },
1071
- { id: 3, text: 'Back to provider selection', action: 'back' }
1203
+ { id: 3, text: 'Back to Custom Models menu', action: 'back' }
1072
1204
  ];
1073
1205
 
1074
1206
  let currentIndex = 0;
@@ -1125,178 +1257,108 @@ async function addCustomProvider() {
1125
1257
 
1126
1258
  if (selectedChoice.action === 'back') return;
1127
1259
 
1128
- if (selectedChoice.type === 'openai-compatible') {
1129
- // OpenAI Compatible
1130
- const providerId = await question(colorText('Enter provider ID (e.g., my-openai): ', 'cyan'));
1131
- const name = await question(colorText('Enter provider name (e.g., MyOpenAI): ', 'cyan'));
1132
- const baseUrl = await question(colorText('Enter base URL (e.g., https://api.openai.com/v1): ', 'cyan'));
1133
- const apiKey = await question(colorText('Enter API key: ', 'cyan'));
1134
-
1135
- // Ask if user wants to add multiple models
1260
+ const providerId = await question(colorText('Enter provider ID (e.g., my-openai): ', 'cyan'));
1261
+ const name = await question(colorText('Enter provider name (e.g., MyOpenAI): ', 'cyan'));
1262
+ const baseUrl = await question(colorText('Enter base URL (e.g., https://api.openai.com/v1): ', 'cyan'));
1263
+ const apiKey = await question(colorText('Enter API key: ', 'cyan'));
1264
+
1265
+ // Ask if user wants to add multiple models
1266
+ console.log('');
1267
+ console.log(colorText('Do you want to add multiple models?', 'cyan'));
1268
+ console.log(colorText('1. Add single model', 'yellow'));
1269
+ console.log(colorText('2. Add multiple models', 'yellow'));
1270
+
1271
+ const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
1272
+
1273
+ let models = [];
1274
+
1275
+ if (modelChoice === '2') {
1276
+ // Multiple models mode
1136
1277
  console.log('');
1137
- console.log(colorText('Do you want to add multiple models?', 'cyan'));
1138
- console.log(colorText('1. Add single model', 'yellow'));
1139
- console.log(colorText('2. Add multiple models', 'yellow'));
1140
-
1141
- const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
1142
-
1143
- let models = {};
1144
-
1145
- if (modelChoice === '2') {
1146
- // Multiple models mode
1147
- console.log('');
1148
- console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
1149
- console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
1150
- console.log('');
1151
-
1152
- while (true) {
1153
- const modelName = await question(colorText('Model name: ', 'cyan'));
1154
- if (!modelName.trim()) break;
1155
-
1156
- const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
1157
- models[modelId] = {
1158
- name: modelName.trim()
1159
- };
1160
- }
1161
- } else {
1162
- // Single model mode
1163
- const modelName = await question(colorText('Enter model name (e.g., gpt-4): ', 'cyan'));
1164
- const modelId = modelName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
1165
- models[modelId] = {
1166
- name: modelName
1167
- };
1168
- }
1169
-
1170
- if (Object.keys(models).length === 0) {
1171
- console.log(colorText('At least one model is required.', 'red'));
1172
- await question(colorText('Press Enter to continue...', 'yellow'));
1173
- return;
1174
- }
1175
-
1176
- // Create opencode.json format
1177
- const { readOpencodeConfig } = await import('./opencode-integration.js');
1178
- const config = await readOpencodeConfig();
1179
-
1180
- config.provider = config.provider || {};
1181
- config.provider[providerId] = {
1182
- name,
1183
- options: {
1184
- apiKey,
1185
- baseURL: baseUrl
1186
- },
1187
- models
1188
- };
1189
-
1190
- // Save to opencode.json using the integration module
1191
- const { writeOpencodeConfig } = await import('./opencode-integration.js');
1192
- const success = await writeOpencodeConfig(config);
1193
-
1194
- if (!success) {
1195
- console.log(colorText('Warning: Could not save to opencode.json', 'yellow'));
1196
- }
1197
-
1198
- console.log(colorText('Provider added successfully!', 'green'));
1199
- console.log(colorText(`Added ${Object.keys(models).length} model(s)`, 'cyan'));
1200
- console.log(colorText(`Saved to opencode.json`, 'cyan'));
1201
-
1202
- } else if (selectedChoice.type === 'anthropic') {
1203
- // Anthropic
1204
- const providerId = await question(colorText('Enter provider ID (e.g., my-anthropic): ', 'cyan'));
1205
- const name = await question(colorText('Enter provider name (e.g., MyAnthropic): ', 'cyan'));
1206
- const baseUrl = await question(colorText('Enter base URL (e.g., https://api.anthropic.com): ', 'cyan'));
1207
- const apiKey = await question(colorText('Enter Anthropic API key: ', 'cyan'));
1208
-
1209
- // Ask if user wants to add multiple models
1278
+ console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
1279
+ console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
1210
1280
  console.log('');
1211
- console.log(colorText('Do you want to add multiple models?', 'cyan'));
1212
- console.log(colorText('1. Add single model', 'yellow'));
1213
- console.log(colorText('2. Add multiple models', 'yellow'));
1214
-
1215
- const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
1216
1281
 
1217
- let models = {};
1218
-
1219
- if (modelChoice === '2') {
1220
- // Multiple models mode
1221
- console.log('');
1222
- console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
1223
- console.log(colorText('Examples: claude-3-sonnet-20240229, claude-3-haiku-20240307', 'dim'));
1224
- console.log('');
1282
+ while (true) {
1283
+ const modelName = await question(colorText('Model name: ', 'cyan'));
1284
+ if (!modelName.trim()) break;
1225
1285
 
1226
- while (true) {
1227
- const modelName = await question(colorText('Model name: ', 'cyan'));
1228
- if (!modelName.trim()) break;
1229
-
1230
- const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-');
1231
- models[modelId] = {
1232
- name: modelName.trim()
1233
- };
1234
- }
1235
- } else {
1236
- // Single model mode
1237
- const modelName = await question(colorText('Enter model name (e.g., claude-3-sonnet-20240229): ', 'cyan'));
1238
- const modelId = modelName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
1239
- models[modelId] = {
1240
- name: modelName
1241
- };
1242
- }
1243
-
1244
- if (Object.keys(models).length === 0) {
1245
- console.log(colorText('At least one model is required.', 'red'));
1246
- await question(colorText('Press Enter to continue...', 'yellow'));
1247
- return;
1286
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1287
+ models.push({
1288
+ name: modelName.trim(),
1289
+ id: modelId
1290
+ });
1248
1291
  }
1249
-
1250
- // Create opencode.json format
1251
- const { readOpencodeConfig } = await import('./opencode-integration.js');
1252
- const config = await readOpencodeConfig();
1253
-
1254
- config.provider = config.provider || {};
1255
- config.provider[providerId] = {
1256
- name,
1257
- options: {
1258
- apiKey,
1259
- baseURL: baseUrl
1260
- },
1261
- models
1262
- };
1263
-
1264
- // Save to opencode.json using the integration module
1265
- const { writeOpencodeConfig } = await import('./opencode-integration.js');
1266
- const success = await writeOpencodeConfig(config);
1267
-
1268
- if (!success) {
1269
- console.log(colorText('Warning: Could not save to opencode.json', 'yellow'));
1292
+ } else {
1293
+ // Single model mode
1294
+ const modelName = await question(colorText('Enter model name (e.g., gpt-4): ', 'cyan'));
1295
+ if (modelName.trim()) {
1296
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1297
+ models.push({
1298
+ name: modelName.trim(),
1299
+ id: modelId
1300
+ });
1270
1301
  }
1271
-
1272
- console.log(colorText('Provider added successfully!', 'green'));
1273
- console.log(colorText(`Added ${Object.keys(models).length} model(s)`, 'cyan'));
1274
- console.log(colorText(`Saved to opencode.json`, 'cyan'));
1275
1302
  }
1276
1303
 
1304
+ if (models.length === 0) {
1305
+ console.log(colorText('At least one model is required.', 'red'));
1306
+ await question(colorText('Press Enter to continue...', 'yellow'));
1307
+ return;
1308
+ }
1309
+
1310
+ // Create provider data for ai-benchmark-config.json format
1311
+ const providerData = {
1312
+ id: providerId,
1313
+ name: name,
1314
+ type: selectedChoice.type,
1315
+ baseUrl: baseUrl,
1316
+ apiKey: apiKey,
1317
+ models: models
1318
+ };
1319
+
1320
+ // Save to ai-benchmark-config.json
1321
+ const success = await addCustomProvider(providerData);
1322
+
1323
+ if (!success) {
1324
+ console.log(colorText('Failed to save custom provider.', 'red'));
1325
+ await question(colorText('Press Enter to continue...', 'yellow'));
1326
+ return;
1327
+ }
1328
+
1329
+ console.log(colorText('Custom provider added successfully!', 'green'));
1330
+ console.log(colorText(`Added ${models.length} model(s)`, 'cyan'));
1331
+ console.log(colorText(`Saved to ai-benchmark-config.json`, 'cyan'));
1332
+
1277
1333
  await question(colorText('\nPress Enter to continue...', 'yellow'));
1278
1334
  }
1279
1335
 
1280
- // Show debug information about opencode integration
1336
+ // Show debug information about config system
1281
1337
  async function showDebugInfo() {
1282
1338
  clearScreen();
1283
1339
  showHeader();
1284
- console.log(colorText('OpenCode Integration Debug Info', 'magenta'));
1340
+ console.log(colorText('Config System Debug Info', 'magenta'));
1285
1341
  console.log('');
1286
1342
 
1287
1343
  const debugInfo = await getDebugInfo();
1288
1344
 
1289
- console.log(colorText('File Paths:', 'cyan'));
1290
- console.log(colorText(` auth.json: ${debugInfo.paths.authJson}`, 'white'));
1291
- console.log(colorText(` opencode.json: ${debugInfo.paths.opencodeJson}`, 'white'));
1345
+ console.log(colorText('OpenCode Paths (deprecated):', 'cyan'));
1346
+ console.log(colorText(` auth.json: ${debugInfo.opencodePaths.authJson}`, 'white'));
1347
+ console.log(colorText(` opencode.json: ${debugInfo.opencodePaths.opencodeJson}`, 'white'));
1348
+ console.log('');
1349
+
1350
+ console.log(colorText('AI Speedometer Config Paths:', 'cyan'));
1351
+ console.log(colorText(` ai-benchmark-config.json: ${debugInfo.aiConfigPaths.configJson}`, 'white'));
1352
+ console.log(colorText(` Config directory: ${debugInfo.aiConfigPaths.configDir}`, 'white'));
1292
1353
  console.log('');
1293
1354
 
1294
1355
  console.log(colorText('File Status:', 'cyan'));
1295
1356
  console.log(colorText(` auth.json exists: ${debugInfo.authExists ? 'Yes' : 'No'}`, 'white'));
1296
- console.log(colorText(` opencode.json exists: ${debugInfo.configExists ? 'Yes' : 'No'}`, 'white'));
1357
+ console.log(colorText(` opencode.json exists: ${debugInfo.configExists ? 'No' : 'No'}`, 'white'));
1358
+ console.log(colorText(` ai-benchmark-config.json exists: ${debugInfo.aiConfigPaths.configExists ? 'Yes' : 'No'}`, 'white'));
1297
1359
  console.log('');
1298
1360
 
1299
- console.log(colorText('Authenticated Providers:', 'cyan'));
1361
+ console.log(colorText('Authenticated Providers (auth.json):', 'cyan'));
1300
1362
  if (debugInfo.authData.length === 0) {
1301
1363
  console.log(colorText(' None', 'dim'));
1302
1364
  } else {
@@ -1306,17 +1368,27 @@ async function showDebugInfo() {
1306
1368
  }
1307
1369
  console.log('');
1308
1370
 
1309
- console.log(colorText('Custom Providers:', 'cyan'));
1310
- if (debugInfo.configProviders.length === 0) {
1371
+ console.log(colorText('Verified Providers (ai-benchmark-config.json):', 'cyan'));
1372
+ if (debugInfo.aiConfigData.verifiedProviders.length === 0) {
1373
+ console.log(colorText(' None', 'dim'));
1374
+ } else {
1375
+ debugInfo.aiConfigData.verifiedProviders.forEach(provider => {
1376
+ console.log(colorText(` - ${provider}`, 'white'));
1377
+ });
1378
+ }
1379
+ console.log('');
1380
+
1381
+ console.log(colorText('Custom Providers (ai-benchmark-config.json):', 'cyan'));
1382
+ if (debugInfo.aiConfigData.customProviders.length === 0) {
1311
1383
  console.log(colorText(' None', 'dim'));
1312
1384
  } else {
1313
- debugInfo.configProviders.forEach(provider => {
1385
+ debugInfo.aiConfigData.customProviders.forEach(provider => {
1314
1386
  console.log(colorText(` - ${provider}`, 'white'));
1315
1387
  });
1316
1388
  }
1317
1389
  console.log('');
1318
1390
 
1319
- console.log(colorText('XDG Paths:', 'cyan'));
1391
+ console.log(colorText('XDG Paths (for OpenCode):', 'cyan'));
1320
1392
  console.log(colorText(` Data: ${debugInfo.xdgPaths.data}`, 'white'));
1321
1393
  console.log(colorText(` Config: ${debugInfo.xdgPaths.config}`, 'white'));
1322
1394
  console.log('');
@@ -1335,35 +1407,129 @@ async function listProviders() {
1335
1407
  if (config.providers.length === 0) {
1336
1408
  console.log(colorText('No providers configured yet.', 'yellow'));
1337
1409
  } else {
1338
- config.providers.forEach((provider, index) => {
1339
- console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
1340
-
1341
- if (provider.models.length > 0) {
1342
- console.log(colorText(' Models:', 'dim'));
1343
- provider.models.forEach((model, modelIndex) => {
1344
- console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
1345
- });
1346
- } else {
1347
- console.log(colorText(' Models: None', 'dim'));
1348
- }
1349
-
1350
- console.log('');
1410
+ // Separate verified and custom providers
1411
+ const verifiedProviders = config.providers.filter(p => {
1412
+ // Verified providers come from auth.json via models.dev
1413
+ return p.baseUrl && p.baseUrl.includes('api.'); // Simple heuristic
1351
1414
  });
1415
+
1416
+ const customProviders = config.providers.filter(p => {
1417
+ return !verifiedProviders.includes(p);
1418
+ });
1419
+
1420
+ // Show verified providers
1421
+ if (verifiedProviders.length > 0) {
1422
+ console.log(colorText('Verified Providers (from models.dev):', 'green'));
1423
+ verifiedProviders.forEach((provider, index) => {
1424
+ console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
1425
+
1426
+ if (provider.models.length > 0) {
1427
+ console.log(colorText(' Models:', 'dim'));
1428
+ provider.models.forEach((model, modelIndex) => {
1429
+ console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
1430
+ });
1431
+ } else {
1432
+ console.log(colorText(' Models: None', 'dim'));
1433
+ }
1434
+
1435
+ console.log('');
1436
+ });
1437
+ }
1438
+
1439
+ // Show custom providers
1440
+ if (customProviders.length > 0) {
1441
+ console.log(colorText('Custom Providers:', 'magenta'));
1442
+ customProviders.forEach((provider, index) => {
1443
+ console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
1444
+
1445
+ if (provider.models.length > 0) {
1446
+ console.log(colorText(' Models:', 'dim'));
1447
+ provider.models.forEach((model, modelIndex) => {
1448
+ console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
1449
+ });
1450
+ } else {
1451
+ console.log(colorText(' Models: None', 'dim'));
1452
+ }
1453
+
1454
+ console.log('');
1455
+ });
1456
+ }
1352
1457
  }
1353
1458
 
1354
1459
  await question(colorText('Press Enter to continue...', 'yellow'));
1355
1460
  }
1356
1461
 
1357
- async function addModelToProvider() {
1462
+ // Add Custom Models submenu
1463
+ async function addCustomModelsMenu() {
1464
+ const menuOptions = [
1465
+ { id: 1, text: 'Add Models to Existing Provider', action: () => addModelsToExistingProvider() },
1466
+ { id: 2, text: 'Add Custom Provider', action: () => addCustomProviderCLI() },
1467
+ { id: 3, text: 'Back to Model Management', action: () => 'back' }
1468
+ ];
1469
+
1470
+ let currentIndex = 0;
1471
+
1472
+ while (true) {
1473
+ // Build screen content in memory (double buffering)
1474
+ let screenContent = '';
1475
+
1476
+ // Add header
1477
+ screenContent += colorText('Ai-speedometer', 'cyan') + '\n';
1478
+ screenContent += colorText('=============================', 'cyan') + '\n';
1479
+ screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
1480
+ screenContent += '\n';
1481
+
1482
+ screenContent += colorText('Add Custom Models', 'magenta') + '\n';
1483
+ screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
1484
+ screenContent += colorText('Navigation is circular', 'dim') + '\n';
1485
+ screenContent += '\n';
1486
+
1487
+ // Display menu options
1488
+ menuOptions.forEach((option, index) => {
1489
+ const isCurrent = index === currentIndex;
1490
+ const indicator = isCurrent ? colorText('●', 'green') : colorText('○', 'dim');
1491
+ const optionText = isCurrent ? colorText(option.text, 'bright') : colorText(option.text, 'yellow');
1492
+
1493
+ screenContent += `${indicator} ${optionText}\n`;
1494
+ });
1495
+
1496
+ // Clear screen and output entire buffer at once
1497
+ clearScreen();
1498
+ console.log(screenContent);
1499
+
1500
+ const key = await getKeyPress();
1501
+
1502
+ if (key === '\u001b[A') {
1503
+ // Up arrow - circular navigation
1504
+ currentIndex = (currentIndex - 1 + menuOptions.length) % menuOptions.length;
1505
+ } else if (key === '\u001b[B') {
1506
+ // Down arrow - circular navigation
1507
+ currentIndex = (currentIndex + 1) % menuOptions.length;
1508
+ } else if (key === '\r') {
1509
+ // Enter - select current option
1510
+ const result = await menuOptions[currentIndex].action();
1511
+ if (result === 'back') {
1512
+ return;
1513
+ }
1514
+ } else if (key === '\u0003') {
1515
+ // Ctrl+C
1516
+ process.exit(0);
1517
+ }
1518
+ }
1519
+ }
1520
+
1521
+ // Add models to existing custom provider
1522
+ async function addModelsToExistingProvider() {
1358
1523
  clearScreen();
1359
1524
  showHeader();
1360
- console.log(colorText('Add Model to Provider', 'magenta'));
1525
+ console.log(colorText('Add Models to Existing Provider', 'magenta'));
1361
1526
  console.log('');
1362
1527
 
1363
- const config = await loadConfig();
1528
+ // Get custom providers from config
1529
+ const customProviders = await getCustomProvidersFromConfig();
1364
1530
 
1365
- if (config.providers.length === 0) {
1366
- console.log(colorText('No providers available. Please add a provider first.', 'red'));
1531
+ if (customProviders.length === 0) {
1532
+ console.log(colorText('No custom providers available. Please add a custom provider first.', 'red'));
1367
1533
  await question(colorText('Press Enter to continue...', 'yellow'));
1368
1534
  return;
1369
1535
  }
@@ -1380,21 +1546,22 @@ async function addModelToProvider() {
1380
1546
  screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
1381
1547
  screenContent += '\n';
1382
1548
 
1383
- screenContent += colorText('Add Model to Provider', 'magenta') + '\n';
1549
+ screenContent += colorText('Add Models to Existing Provider', 'magenta') + '\n';
1384
1550
  screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
1385
1551
  screenContent += colorText('Navigation is circular', 'dim') + '\n';
1386
1552
  screenContent += '\n';
1387
1553
 
1388
- screenContent += colorText('Select provider:', 'cyan') + '\n';
1554
+ screenContent += colorText('Select custom provider:', 'cyan') + '\n';
1389
1555
  screenContent += '\n';
1390
1556
 
1391
- // Display providers with arrow key navigation
1392
- config.providers.forEach((provider, index) => {
1557
+ // Display custom providers with arrow key navigation
1558
+ customProviders.forEach((provider, index) => {
1393
1559
  const isCurrent = index === currentIndex;
1394
1560
  const indicator = isCurrent ? colorText('●', 'green') : colorText('○', 'dim');
1395
1561
  const providerName = isCurrent ? colorText(provider.name, 'bright') : colorText(provider.name, 'yellow');
1562
+ const providerType = isCurrent ? colorText(`(${provider.type})`, 'cyan') : colorText(`(${provider.type})`, 'dim');
1396
1563
 
1397
- screenContent += `${indicator} ${providerName}\n`;
1564
+ screenContent += `${indicator} ${providerName} ${providerType}\n`;
1398
1565
  });
1399
1566
 
1400
1567
  // Clear screen and output entire buffer at once
@@ -1405,10 +1572,10 @@ async function addModelToProvider() {
1405
1572
 
1406
1573
  if (key === '\u001b[A') {
1407
1574
  // Up arrow - circular navigation
1408
- currentIndex = (currentIndex - 1 + config.providers.length) % config.providers.length;
1575
+ currentIndex = (currentIndex - 1 + customProviders.length) % customProviders.length;
1409
1576
  } else if (key === '\u001b[B') {
1410
1577
  // Down arrow - circular navigation
1411
- currentIndex = (currentIndex + 1) % config.providers.length;
1578
+ currentIndex = (currentIndex + 1) % customProviders.length;
1412
1579
  } else if (key === '\r') {
1413
1580
  // Enter - select current provider
1414
1581
  break;
@@ -1418,31 +1585,73 @@ async function addModelToProvider() {
1418
1585
  }
1419
1586
  }
1420
1587
 
1421
- const provider = config.providers[currentIndex];
1422
- const modelName = await question(colorText('Enter new model name: ', 'cyan'));
1588
+ const provider = customProviders[currentIndex];
1423
1589
 
1424
- // Find the provider in customProviders and add the model
1425
- const customProvider = config.customProviders.find(p => p.id === provider.id);
1426
- if (customProvider) {
1427
- customProvider.models.push({
1428
- name: modelName,
1429
- id: Date.now().toString() + '_model'
1430
- });
1590
+ console.log('');
1591
+ console.log(colorText('Selected provider: ', 'cyan') + colorText(provider.name, 'white'));
1592
+ console.log('');
1593
+
1594
+ // Ask if user wants to add multiple models
1595
+ console.log(colorText('Do you want to add multiple models?', 'cyan'));
1596
+ console.log(colorText('1. Add single model', 'yellow'));
1597
+ console.log(colorText('2. Add multiple models', 'yellow'));
1598
+
1599
+ const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
1600
+
1601
+ let modelsAdded = 0;
1602
+
1603
+ if (modelChoice === '2') {
1604
+ // Multiple models mode
1605
+ console.log('');
1606
+ console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
1607
+ console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
1608
+ console.log('');
1609
+
1610
+ while (true) {
1611
+ const modelName = await question(colorText('Model name: ', 'cyan'));
1612
+ if (!modelName.trim()) break;
1613
+
1614
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1615
+ const modelData = {
1616
+ name: modelName.trim(),
1617
+ id: modelId
1618
+ };
1619
+
1620
+ const success = await addModelToCustomProvider(provider.id, modelData);
1621
+ if (success) {
1622
+ modelsAdded++;
1623
+ console.log(colorText(`✓ Added model: ${modelName.trim()}`, 'green'));
1624
+ } else {
1625
+ console.log(colorText(`✗ Failed to add model: ${modelName.trim()}`, 'red'));
1626
+ }
1627
+ }
1431
1628
  } else {
1432
- // If it's a verified provider, we need to convert it to custom
1433
- provider.models.push({
1434
- name: modelName,
1435
- id: Date.now().toString() + '_model'
1436
- });
1437
- // Remove from verifiedProviders and add to customProviders
1438
- if (config.verifiedProviders && config.verifiedProviders[provider.id]) {
1439
- delete config.verifiedProviders[provider.id];
1629
+ // Single model mode
1630
+ const modelName = await question(colorText('Enter model name: ', 'cyan'));
1631
+ if (modelName.trim()) {
1632
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1633
+ const modelData = {
1634
+ name: modelName.trim(),
1635
+ id: modelId
1636
+ };
1637
+
1638
+ const success = await addModelToCustomProvider(provider.id, modelData);
1639
+ if (success) {
1640
+ modelsAdded = 1;
1641
+ console.log(colorText(`✓ Added model: ${modelName.trim()}`, 'green'));
1642
+ } else {
1643
+ console.log(colorText(`✗ Failed to add model: ${modelName.trim()}`, 'red'));
1644
+ }
1440
1645
  }
1441
- config.customProviders.push(provider);
1442
1646
  }
1443
1647
 
1444
- await saveConfig(config);
1445
- console.log(colorText('Model added successfully!', 'green'));
1648
+ if (modelsAdded > 0) {
1649
+ console.log('');
1650
+ console.log(colorText(`Successfully added ${modelsAdded} model(s) to ${provider.name}`, 'green'));
1651
+ } else {
1652
+ console.log(colorText('No models were added.', 'yellow'));
1653
+ }
1654
+
1446
1655
  await question(colorText('\nPress Enter to continue...', 'yellow'));
1447
1656
  }
1448
1657
 
@@ -1622,7 +1831,27 @@ async function runRestApiBenchmark(models) {
1622
1831
  console.log('');
1623
1832
  console.log(colorText('All REST API benchmarks completed!', 'green'));
1624
1833
 
1625
- await displayColorfulResults(results, 'REST API');
1834
+ await displayColorfulResults(results, 'REST API', models);
1835
+
1836
+ // Add successful models to recent models list
1837
+ const successfulModels = results
1838
+ .filter(r => r.success)
1839
+ .map(r => {
1840
+ // Find the actual model object that matches this benchmark result
1841
+ const modelObj = models.find(model =>
1842
+ model.name === r.model && model.providerName === r.provider
1843
+ );
1844
+
1845
+ return {
1846
+ modelId: modelObj ? modelObj.id : r.model, // Use actual ID if found, fallback to name
1847
+ modelName: r.model,
1848
+ providerName: r.provider
1849
+ };
1850
+ });
1851
+
1852
+ if (successfulModels.length > 0) {
1853
+ await addToRecentModels(successfulModels);
1854
+ }
1626
1855
  }
1627
1856
 
1628
1857
  // Main menu with arrow key navigation
@@ -1698,9 +1927,9 @@ async function showMainMenu() {
1698
1927
 
1699
1928
  async function showModelMenu() {
1700
1929
  const menuOptions = [
1701
- { id: 1, text: 'Add Provider', action: () => addProvider() },
1702
- { id: 2, text: 'List Existing Providers', action: () => listProviders() },
1703
- { id: 3, text: 'Add Model to Provider', action: () => addModelToProvider() },
1930
+ { id: 1, text: 'Add Verified Provider', action: () => addVerifiedProvider() },
1931
+ { id: 2, text: 'Add Custom Models', action: () => addCustomModelsMenu() },
1932
+ { id: 3, text: 'List Existing Providers', action: () => listProviders() },
1704
1933
  { id: 4, text: 'Debug Info', action: () => showDebugInfo() },
1705
1934
  { id: 5, text: 'Back to Main Menu', action: () => 'back' }
1706
1935
  ];
@@ -1767,7 +1996,13 @@ process.on('SIGINT', () => {
1767
1996
  if (import.meta.url === `file://${process.argv[1]}` ||
1768
1997
  process.argv.length === 2 ||
1769
1998
  (process.argv.length === 3 && process.argv[2] === '--debug')) {
1770
- showMainMenu();
1999
+
2000
+ // Clean up recent models from main config and migrate to cache on startup
2001
+ cleanupRecentModelsFromConfig().then(() => {
2002
+ showMainMenu();
2003
+ }).catch(() => {
2004
+ showMainMenu();
2005
+ });
1771
2006
  }
1772
2007
 
1773
- export { showMainMenu, addProvider, listProviders, selectModelsCircular, runStreamingBenchmark, loadConfig, saveConfig };
2008
+ export { showMainMenu, listProviders, selectModelsCircular, runStreamingBenchmark, loadConfig, saveConfig };