ai-speedometer 1.0.0 → 1.0.1

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,14 @@ 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
+ } from './ai-config.js';
18
26
  import 'dotenv/config';
19
27
  import Table from 'cli-table3';
20
28
 
@@ -45,17 +53,12 @@ function createAnthropicProvider(baseUrl, apiKey) {
45
53
  log(`Creating Anthropic provider with baseUrl: ${baseUrl}`);
46
54
  log(`API Key length: ${apiKey ? apiKey.length : 0}`);
47
55
 
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
- }
56
+ // Use baseUrl as provided - no automatic normalization needed
54
57
 
55
58
  // Try with baseURL parameter (correct according to docs)
56
59
  const provider = createAnthropic({
57
60
  apiKey: apiKey,
58
- baseURL: normalizedBaseUrl,
61
+ baseURL: baseUrl,
59
62
  // Add minimal fetch logging for debugging
60
63
  fetch: debugMode ? async (input, init) => {
61
64
  log(`API Request to: ${input}`);
@@ -123,13 +126,13 @@ function showHeader() {
123
126
  console.log('');
124
127
  }
125
128
 
126
- // Configuration management - now using opencode files
129
+ // Configuration management - now using ai-benchmark-config.json for custom providers
127
130
  async function loadConfig() {
128
131
  try {
129
132
  // Check if we need to migrate from old config
130
133
  const oldConfigFile = 'ai-benchmark-config.json';
131
134
  if (fs.existsSync(oldConfigFile)) {
132
- console.log(colorText('Migrating from old config format to opencode format...', 'yellow'));
135
+ console.log(colorText('Migrating from old config format to new format...', 'yellow'));
133
136
 
134
137
  try {
135
138
  const data = fs.readFileSync(oldConfigFile, 'utf8');
@@ -156,7 +159,7 @@ async function loadConfig() {
156
159
  }
157
160
  }
158
161
 
159
- // Load providers from opencode integration
162
+ // Load providers from both auth.json (verified) and ai-benchmark-config.json (custom)
160
163
  const providers = await getAllAvailableProviders();
161
164
 
162
165
  return {
@@ -169,11 +172,11 @@ async function loadConfig() {
169
172
  }
170
173
  }
171
174
 
172
- // Save config - now using opencode files
175
+ // Save config - now using ai-benchmark-config.json and auth.json
173
176
  async function saveConfig(config) {
174
177
  // 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'));
178
+ // is handled by the ai-config.js and opencode-integration.js functions
179
+ console.log(colorText('Note: Configuration is now automatically saved to ai-benchmark-config.json and auth.json', 'cyan'));
177
180
  }
178
181
 
179
182
  // Keyboard input handling
@@ -822,18 +825,15 @@ async function displayColorfulResults(results, method = 'AI SDK') {
822
825
  }
823
826
 
824
827
  // Helper function to calculate visible items based on terminal height
825
- function getVisibleItemsCount(headerHeight = 8) {
828
+ function getVisibleItemsCount(headerHeight = 10) {
826
829
  const terminalHeight = process.stdout.rows || 24;
827
- return Math.max(5, terminalHeight - headerHeight);
830
+ return Math.max(3, terminalHeight - headerHeight);
828
831
  }
829
832
 
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
-
833
+
834
+
835
+ // Add a verified provider (saves to both auth.json and ai-benchmark-config.json)
836
+ async function addVerifiedProvider() {
837
837
  let searchQuery = '';
838
838
  let allProviders = [];
839
839
  let filteredProviders = [];
@@ -845,6 +845,10 @@ async function addProvider() {
845
845
  allProviders = await getAllProviders();
846
846
  filteredProviders = allProviders;
847
847
  } catch (error) {
848
+ clearScreen();
849
+ showHeader();
850
+ console.log(colorText('Add Verified Provider', 'magenta'));
851
+ console.log('');
848
852
  console.log(colorText('Error loading providers: ', 'red') + error.message);
849
853
  await question(colorText('Press Enter to continue...', 'yellow'));
850
854
  return;
@@ -860,19 +864,19 @@ async function addProvider() {
860
864
  screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
861
865
  screenContent += '\n';
862
866
 
863
- screenContent += colorText('Add Provider', 'magenta') + '\n';
867
+ screenContent += colorText('Add Verified Provider', 'magenta') + '\n';
864
868
  screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
865
869
  screenContent += colorText('Type to search (real-time filtering)', 'cyan') + '\n';
866
870
  screenContent += colorText('Navigation is circular', 'dim') + '\n';
867
871
  screenContent += '\n';
868
872
 
869
873
  // Search interface - always visible
870
- screenContent += colorText('Search: ', 'yellow') + colorText(searchQuery + '_', 'bright') + '\n';
874
+ screenContent += colorText('🔍 Search: ', 'yellow') + colorText(searchQuery + '_', 'bright') + '\n';
871
875
  screenContent += '\n';
872
876
 
873
877
  // Calculate pagination
874
- const visibleItemsCount = getVisibleItemsCount();
875
- const totalItems = filteredProviders.length + 1; // +1 for custom provider option
878
+ const visibleItemsCount = getVisibleItemsCount(11); // Account for search bar and header
879
+ const totalItems = filteredProviders.length;
876
880
  const totalPages = Math.ceil(totalItems / visibleItemsCount);
877
881
 
878
882
  // Ensure current page is valid
@@ -883,7 +887,7 @@ async function addProvider() {
883
887
  const endIndex = Math.min(startIndex + visibleItemsCount, totalItems);
884
888
 
885
889
  // Display providers with pagination
886
- screenContent += colorText('Available Providers:', 'cyan') + '\n';
890
+ screenContent += colorText('Available Verified Providers:', 'cyan') + '\n';
887
891
  screenContent += '\n';
888
892
 
889
893
  // Show current page of providers
@@ -897,16 +901,6 @@ async function addProvider() {
897
901
  screenContent += `${indicator} ${providerName} ${providerType}\n`;
898
902
  }
899
903
 
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
904
  // Show pagination info
911
905
  screenContent += '\n';
912
906
  if (totalPages > 1) {
@@ -959,14 +953,8 @@ async function addProvider() {
959
953
  currentIndex = currentPage * visibleItemsCount;
960
954
  }
961
955
  } 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
- }
956
+ // Enter - select current provider
957
+ await addVerifiedProviderAuto(filteredProviders[currentIndex]);
970
958
  break;
971
959
  } else if (key === '\u0003') {
972
960
  // Ctrl+C
@@ -1036,18 +1024,26 @@ async function addVerifiedProviderAuto(provider) {
1036
1024
  return;
1037
1025
  }
1038
1026
 
1039
- // Add API key to opencode auth.json
1040
- const success = await addApiKey(provider.id, apiKey);
1027
+ // Add API key to auth.json (for opencode integration)
1028
+ const authSuccess = await addApiKey(provider.id, apiKey);
1041
1029
 
1042
- if (!success) {
1043
- console.log(colorText('Failed to save API key to opencode auth.json', 'red'));
1030
+ if (!authSuccess) {
1031
+ console.log(colorText('Failed to save API key to auth.json', 'red'));
1044
1032
  await question(colorText('Press Enter to continue...', 'yellow'));
1045
1033
  return;
1046
1034
  }
1047
1035
 
1036
+ // Also add to ai-benchmark-config.json for consistency
1037
+ const { addVerifiedProvider: addVerifiedProviderToConfig } = await import('./ai-config.js');
1038
+ const configSuccess = await addVerifiedProviderToConfig(provider.id, apiKey);
1039
+
1040
+ if (!configSuccess) {
1041
+ console.log(colorText('Warning: Could not save to ai-benchmark-config.json', 'yellow'));
1042
+ }
1043
+
1048
1044
  console.log('');
1049
1045
  console.log(colorText('Provider added successfully!', 'green'));
1050
- console.log(colorText(`API key saved to opencode auth.json`, 'cyan'));
1046
+ console.log(colorText(`API key saved to auth.json`, 'cyan'));
1051
1047
  console.log(colorText(`Models will be loaded dynamically from ${provider.name}`, 'cyan'));
1052
1048
  console.log(colorText(`Found ${models.length} available models`, 'cyan'));
1053
1049
 
@@ -1056,19 +1052,19 @@ async function addVerifiedProviderAuto(provider) {
1056
1052
 
1057
1053
 
1058
1054
 
1059
- // Add a custom provider (now integrated with opencode.json)
1060
- async function addCustomProvider() {
1055
+ // Add a custom provider (now using ai-benchmark-config.json)
1056
+ async function addCustomProviderCLI() {
1061
1057
  clearScreen();
1062
1058
  showHeader();
1063
1059
  console.log(colorText('Add Custom Provider', 'magenta'));
1064
1060
  console.log('');
1065
- console.log(colorText('Note: Custom providers are saved to opencode.json', 'cyan'));
1061
+ console.log(colorText('Note: Custom providers are saved to ai-benchmark-config.json', 'cyan'));
1066
1062
  console.log('');
1067
1063
 
1068
1064
  const providerOptions = [
1069
1065
  { id: 1, text: 'OpenAI Compatible', type: 'openai-compatible' },
1070
1066
  { id: 2, text: 'Anthropic', type: 'anthropic' },
1071
- { id: 3, text: 'Back to provider selection', action: 'back' }
1067
+ { id: 3, text: 'Back to Custom Models menu', action: 'back' }
1072
1068
  ];
1073
1069
 
1074
1070
  let currentIndex = 0;
@@ -1125,178 +1121,108 @@ async function addCustomProvider() {
1125
1121
 
1126
1122
  if (selectedChoice.action === 'back') return;
1127
1123
 
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
1124
+ const providerId = await question(colorText('Enter provider ID (e.g., my-openai): ', 'cyan'));
1125
+ const name = await question(colorText('Enter provider name (e.g., MyOpenAI): ', 'cyan'));
1126
+ const baseUrl = await question(colorText('Enter base URL (e.g., https://api.openai.com/v1): ', 'cyan'));
1127
+ const apiKey = await question(colorText('Enter API key: ', 'cyan'));
1128
+
1129
+ // Ask if user wants to add multiple models
1130
+ console.log('');
1131
+ console.log(colorText('Do you want to add multiple models?', 'cyan'));
1132
+ console.log(colorText('1. Add single model', 'yellow'));
1133
+ console.log(colorText('2. Add multiple models', 'yellow'));
1134
+
1135
+ const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
1136
+
1137
+ let models = [];
1138
+
1139
+ if (modelChoice === '2') {
1140
+ // Multiple models mode
1136
1141
  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
1142
+ console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
1143
+ console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
1210
1144
  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
1145
 
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('');
1146
+ while (true) {
1147
+ const modelName = await question(colorText('Model name: ', 'cyan'));
1148
+ if (!modelName.trim()) break;
1225
1149
 
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;
1150
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1151
+ models.push({
1152
+ name: modelName.trim(),
1153
+ id: modelId
1154
+ });
1248
1155
  }
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'));
1156
+ } else {
1157
+ // Single model mode
1158
+ const modelName = await question(colorText('Enter model name (e.g., gpt-4): ', 'cyan'));
1159
+ if (modelName.trim()) {
1160
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1161
+ models.push({
1162
+ name: modelName.trim(),
1163
+ id: modelId
1164
+ });
1270
1165
  }
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
1166
  }
1276
1167
 
1168
+ if (models.length === 0) {
1169
+ console.log(colorText('At least one model is required.', 'red'));
1170
+ await question(colorText('Press Enter to continue...', 'yellow'));
1171
+ return;
1172
+ }
1173
+
1174
+ // Create provider data for ai-benchmark-config.json format
1175
+ const providerData = {
1176
+ id: providerId,
1177
+ name: name,
1178
+ type: selectedChoice.type,
1179
+ baseUrl: baseUrl,
1180
+ apiKey: apiKey,
1181
+ models: models
1182
+ };
1183
+
1184
+ // Save to ai-benchmark-config.json
1185
+ const success = await addCustomProvider(providerData);
1186
+
1187
+ if (!success) {
1188
+ console.log(colorText('Failed to save custom provider.', 'red'));
1189
+ await question(colorText('Press Enter to continue...', 'yellow'));
1190
+ return;
1191
+ }
1192
+
1193
+ console.log(colorText('Custom provider added successfully!', 'green'));
1194
+ console.log(colorText(`Added ${models.length} model(s)`, 'cyan'));
1195
+ console.log(colorText(`Saved to ai-benchmark-config.json`, 'cyan'));
1196
+
1277
1197
  await question(colorText('\nPress Enter to continue...', 'yellow'));
1278
1198
  }
1279
1199
 
1280
- // Show debug information about opencode integration
1200
+ // Show debug information about config system
1281
1201
  async function showDebugInfo() {
1282
1202
  clearScreen();
1283
1203
  showHeader();
1284
- console.log(colorText('OpenCode Integration Debug Info', 'magenta'));
1204
+ console.log(colorText('Config System Debug Info', 'magenta'));
1285
1205
  console.log('');
1286
1206
 
1287
1207
  const debugInfo = await getDebugInfo();
1288
1208
 
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'));
1209
+ console.log(colorText('OpenCode Paths (deprecated):', 'cyan'));
1210
+ console.log(colorText(` auth.json: ${debugInfo.opencodePaths.authJson}`, 'white'));
1211
+ console.log(colorText(` opencode.json: ${debugInfo.opencodePaths.opencodeJson}`, 'white'));
1212
+ console.log('');
1213
+
1214
+ console.log(colorText('AI Speedometer Config Paths:', 'cyan'));
1215
+ console.log(colorText(` ai-benchmark-config.json: ${debugInfo.aiConfigPaths.configJson}`, 'white'));
1216
+ console.log(colorText(` Config directory: ${debugInfo.aiConfigPaths.configDir}`, 'white'));
1292
1217
  console.log('');
1293
1218
 
1294
1219
  console.log(colorText('File Status:', 'cyan'));
1295
1220
  console.log(colorText(` auth.json exists: ${debugInfo.authExists ? 'Yes' : 'No'}`, 'white'));
1296
- console.log(colorText(` opencode.json exists: ${debugInfo.configExists ? 'Yes' : 'No'}`, 'white'));
1221
+ console.log(colorText(` opencode.json exists: ${debugInfo.configExists ? 'No' : 'No'}`, 'white'));
1222
+ console.log(colorText(` ai-benchmark-config.json exists: ${debugInfo.aiConfigPaths.configExists ? 'Yes' : 'No'}`, 'white'));
1297
1223
  console.log('');
1298
1224
 
1299
- console.log(colorText('Authenticated Providers:', 'cyan'));
1225
+ console.log(colorText('Authenticated Providers (auth.json):', 'cyan'));
1300
1226
  if (debugInfo.authData.length === 0) {
1301
1227
  console.log(colorText(' None', 'dim'));
1302
1228
  } else {
@@ -1306,17 +1232,27 @@ async function showDebugInfo() {
1306
1232
  }
1307
1233
  console.log('');
1308
1234
 
1309
- console.log(colorText('Custom Providers:', 'cyan'));
1310
- if (debugInfo.configProviders.length === 0) {
1235
+ console.log(colorText('Verified Providers (ai-benchmark-config.json):', 'cyan'));
1236
+ if (debugInfo.aiConfigData.verifiedProviders.length === 0) {
1237
+ console.log(colorText(' None', 'dim'));
1238
+ } else {
1239
+ debugInfo.aiConfigData.verifiedProviders.forEach(provider => {
1240
+ console.log(colorText(` - ${provider}`, 'white'));
1241
+ });
1242
+ }
1243
+ console.log('');
1244
+
1245
+ console.log(colorText('Custom Providers (ai-benchmark-config.json):', 'cyan'));
1246
+ if (debugInfo.aiConfigData.customProviders.length === 0) {
1311
1247
  console.log(colorText(' None', 'dim'));
1312
1248
  } else {
1313
- debugInfo.configProviders.forEach(provider => {
1249
+ debugInfo.aiConfigData.customProviders.forEach(provider => {
1314
1250
  console.log(colorText(` - ${provider}`, 'white'));
1315
1251
  });
1316
1252
  }
1317
1253
  console.log('');
1318
1254
 
1319
- console.log(colorText('XDG Paths:', 'cyan'));
1255
+ console.log(colorText('XDG Paths (for OpenCode):', 'cyan'));
1320
1256
  console.log(colorText(` Data: ${debugInfo.xdgPaths.data}`, 'white'));
1321
1257
  console.log(colorText(` Config: ${debugInfo.xdgPaths.config}`, 'white'));
1322
1258
  console.log('');
@@ -1335,35 +1271,129 @@ async function listProviders() {
1335
1271
  if (config.providers.length === 0) {
1336
1272
  console.log(colorText('No providers configured yet.', 'yellow'));
1337
1273
  } 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('');
1274
+ // Separate verified and custom providers
1275
+ const verifiedProviders = config.providers.filter(p => {
1276
+ // Verified providers come from auth.json via models.dev
1277
+ return p.baseUrl && p.baseUrl.includes('api.'); // Simple heuristic
1351
1278
  });
1279
+
1280
+ const customProviders = config.providers.filter(p => {
1281
+ return !verifiedProviders.includes(p);
1282
+ });
1283
+
1284
+ // Show verified providers
1285
+ if (verifiedProviders.length > 0) {
1286
+ console.log(colorText('Verified Providers (from models.dev):', 'green'));
1287
+ verifiedProviders.forEach((provider, index) => {
1288
+ console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
1289
+
1290
+ if (provider.models.length > 0) {
1291
+ console.log(colorText(' Models:', 'dim'));
1292
+ provider.models.forEach((model, modelIndex) => {
1293
+ console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
1294
+ });
1295
+ } else {
1296
+ console.log(colorText(' Models: None', 'dim'));
1297
+ }
1298
+
1299
+ console.log('');
1300
+ });
1301
+ }
1302
+
1303
+ // Show custom providers
1304
+ if (customProviders.length > 0) {
1305
+ console.log(colorText('Custom Providers:', 'magenta'));
1306
+ customProviders.forEach((provider, index) => {
1307
+ console.log(colorText(`${index + 1}. ${provider.name} (${provider.type})`, 'cyan'));
1308
+
1309
+ if (provider.models.length > 0) {
1310
+ console.log(colorText(' Models:', 'dim'));
1311
+ provider.models.forEach((model, modelIndex) => {
1312
+ console.log(colorText(` ${modelIndex + 1}. ${model.name}`, 'yellow'));
1313
+ });
1314
+ } else {
1315
+ console.log(colorText(' Models: None', 'dim'));
1316
+ }
1317
+
1318
+ console.log('');
1319
+ });
1320
+ }
1352
1321
  }
1353
1322
 
1354
1323
  await question(colorText('Press Enter to continue...', 'yellow'));
1355
1324
  }
1356
1325
 
1357
- async function addModelToProvider() {
1326
+ // Add Custom Models submenu
1327
+ async function addCustomModelsMenu() {
1328
+ const menuOptions = [
1329
+ { id: 1, text: 'Add Models to Existing Provider', action: () => addModelsToExistingProvider() },
1330
+ { id: 2, text: 'Add Custom Provider', action: () => addCustomProviderCLI() },
1331
+ { id: 3, text: 'Back to Model Management', action: () => 'back' }
1332
+ ];
1333
+
1334
+ let currentIndex = 0;
1335
+
1336
+ while (true) {
1337
+ // Build screen content in memory (double buffering)
1338
+ let screenContent = '';
1339
+
1340
+ // Add header
1341
+ screenContent += colorText('Ai-speedometer', 'cyan') + '\n';
1342
+ screenContent += colorText('=============================', 'cyan') + '\n';
1343
+ screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
1344
+ screenContent += '\n';
1345
+
1346
+ screenContent += colorText('Add Custom Models', 'magenta') + '\n';
1347
+ screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
1348
+ screenContent += colorText('Navigation is circular', 'dim') + '\n';
1349
+ screenContent += '\n';
1350
+
1351
+ // Display menu options
1352
+ menuOptions.forEach((option, index) => {
1353
+ const isCurrent = index === currentIndex;
1354
+ const indicator = isCurrent ? colorText('●', 'green') : colorText('○', 'dim');
1355
+ const optionText = isCurrent ? colorText(option.text, 'bright') : colorText(option.text, 'yellow');
1356
+
1357
+ screenContent += `${indicator} ${optionText}\n`;
1358
+ });
1359
+
1360
+ // Clear screen and output entire buffer at once
1361
+ clearScreen();
1362
+ console.log(screenContent);
1363
+
1364
+ const key = await getKeyPress();
1365
+
1366
+ if (key === '\u001b[A') {
1367
+ // Up arrow - circular navigation
1368
+ currentIndex = (currentIndex - 1 + menuOptions.length) % menuOptions.length;
1369
+ } else if (key === '\u001b[B') {
1370
+ // Down arrow - circular navigation
1371
+ currentIndex = (currentIndex + 1) % menuOptions.length;
1372
+ } else if (key === '\r') {
1373
+ // Enter - select current option
1374
+ const result = await menuOptions[currentIndex].action();
1375
+ if (result === 'back') {
1376
+ return;
1377
+ }
1378
+ } else if (key === '\u0003') {
1379
+ // Ctrl+C
1380
+ process.exit(0);
1381
+ }
1382
+ }
1383
+ }
1384
+
1385
+ // Add models to existing custom provider
1386
+ async function addModelsToExistingProvider() {
1358
1387
  clearScreen();
1359
1388
  showHeader();
1360
- console.log(colorText('Add Model to Provider', 'magenta'));
1389
+ console.log(colorText('Add Models to Existing Provider', 'magenta'));
1361
1390
  console.log('');
1362
1391
 
1363
- const config = await loadConfig();
1392
+ // Get custom providers from config
1393
+ const customProviders = await getCustomProvidersFromConfig();
1364
1394
 
1365
- if (config.providers.length === 0) {
1366
- console.log(colorText('No providers available. Please add a provider first.', 'red'));
1395
+ if (customProviders.length === 0) {
1396
+ console.log(colorText('No custom providers available. Please add a custom provider first.', 'red'));
1367
1397
  await question(colorText('Press Enter to continue...', 'yellow'));
1368
1398
  return;
1369
1399
  }
@@ -1380,21 +1410,22 @@ async function addModelToProvider() {
1380
1410
  screenContent += colorText('Note: opencode uses ai-sdk', 'dim') + '\n';
1381
1411
  screenContent += '\n';
1382
1412
 
1383
- screenContent += colorText('Add Model to Provider', 'magenta') + '\n';
1413
+ screenContent += colorText('Add Models to Existing Provider', 'magenta') + '\n';
1384
1414
  screenContent += colorText('Use ↑↓ arrows to navigate, ENTER to select', 'cyan') + '\n';
1385
1415
  screenContent += colorText('Navigation is circular', 'dim') + '\n';
1386
1416
  screenContent += '\n';
1387
1417
 
1388
- screenContent += colorText('Select provider:', 'cyan') + '\n';
1418
+ screenContent += colorText('Select custom provider:', 'cyan') + '\n';
1389
1419
  screenContent += '\n';
1390
1420
 
1391
- // Display providers with arrow key navigation
1392
- config.providers.forEach((provider, index) => {
1421
+ // Display custom providers with arrow key navigation
1422
+ customProviders.forEach((provider, index) => {
1393
1423
  const isCurrent = index === currentIndex;
1394
1424
  const indicator = isCurrent ? colorText('●', 'green') : colorText('○', 'dim');
1395
1425
  const providerName = isCurrent ? colorText(provider.name, 'bright') : colorText(provider.name, 'yellow');
1426
+ const providerType = isCurrent ? colorText(`(${provider.type})`, 'cyan') : colorText(`(${provider.type})`, 'dim');
1396
1427
 
1397
- screenContent += `${indicator} ${providerName}\n`;
1428
+ screenContent += `${indicator} ${providerName} ${providerType}\n`;
1398
1429
  });
1399
1430
 
1400
1431
  // Clear screen and output entire buffer at once
@@ -1405,10 +1436,10 @@ async function addModelToProvider() {
1405
1436
 
1406
1437
  if (key === '\u001b[A') {
1407
1438
  // Up arrow - circular navigation
1408
- currentIndex = (currentIndex - 1 + config.providers.length) % config.providers.length;
1439
+ currentIndex = (currentIndex - 1 + customProviders.length) % customProviders.length;
1409
1440
  } else if (key === '\u001b[B') {
1410
1441
  // Down arrow - circular navigation
1411
- currentIndex = (currentIndex + 1) % config.providers.length;
1442
+ currentIndex = (currentIndex + 1) % customProviders.length;
1412
1443
  } else if (key === '\r') {
1413
1444
  // Enter - select current provider
1414
1445
  break;
@@ -1418,31 +1449,73 @@ async function addModelToProvider() {
1418
1449
  }
1419
1450
  }
1420
1451
 
1421
- const provider = config.providers[currentIndex];
1422
- const modelName = await question(colorText('Enter new model name: ', 'cyan'));
1452
+ const provider = customProviders[currentIndex];
1423
1453
 
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
- });
1454
+ console.log('');
1455
+ console.log(colorText('Selected provider: ', 'cyan') + colorText(provider.name, 'white'));
1456
+ console.log('');
1457
+
1458
+ // Ask if user wants to add multiple models
1459
+ console.log(colorText('Do you want to add multiple models?', 'cyan'));
1460
+ console.log(colorText('1. Add single model', 'yellow'));
1461
+ console.log(colorText('2. Add multiple models', 'yellow'));
1462
+
1463
+ const modelChoice = await question(colorText('Enter choice (1 or 2): ', 'cyan'));
1464
+
1465
+ let modelsAdded = 0;
1466
+
1467
+ if (modelChoice === '2') {
1468
+ // Multiple models mode
1469
+ console.log('');
1470
+ console.log(colorText('Enter model names (one per line, empty line to finish):', 'cyan'));
1471
+ console.log(colorText('Examples: gpt-4, gpt-4-turbo, gpt-3.5-turbo', 'dim'));
1472
+ console.log('');
1473
+
1474
+ while (true) {
1475
+ const modelName = await question(colorText('Model name: ', 'cyan'));
1476
+ if (!modelName.trim()) break;
1477
+
1478
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1479
+ const modelData = {
1480
+ name: modelName.trim(),
1481
+ id: modelId
1482
+ };
1483
+
1484
+ const success = await addModelToCustomProvider(provider.id, modelData);
1485
+ if (success) {
1486
+ modelsAdded++;
1487
+ console.log(colorText(`✓ Added model: ${modelName.trim()}`, 'green'));
1488
+ } else {
1489
+ console.log(colorText(`✗ Failed to add model: ${modelName.trim()}`, 'red'));
1490
+ }
1491
+ }
1431
1492
  } 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];
1493
+ // Single model mode
1494
+ const modelName = await question(colorText('Enter model name: ', 'cyan'));
1495
+ if (modelName.trim()) {
1496
+ const modelId = modelName.trim().toLowerCase().replace(/[^a-z0-9-]/g, '-') + '_' + Date.now();
1497
+ const modelData = {
1498
+ name: modelName.trim(),
1499
+ id: modelId
1500
+ };
1501
+
1502
+ const success = await addModelToCustomProvider(provider.id, modelData);
1503
+ if (success) {
1504
+ modelsAdded = 1;
1505
+ console.log(colorText(`✓ Added model: ${modelName.trim()}`, 'green'));
1506
+ } else {
1507
+ console.log(colorText(`✗ Failed to add model: ${modelName.trim()}`, 'red'));
1508
+ }
1440
1509
  }
1441
- config.customProviders.push(provider);
1442
1510
  }
1443
1511
 
1444
- await saveConfig(config);
1445
- console.log(colorText('Model added successfully!', 'green'));
1512
+ if (modelsAdded > 0) {
1513
+ console.log('');
1514
+ console.log(colorText(`Successfully added ${modelsAdded} model(s) to ${provider.name}`, 'green'));
1515
+ } else {
1516
+ console.log(colorText('No models were added.', 'yellow'));
1517
+ }
1518
+
1446
1519
  await question(colorText('\nPress Enter to continue...', 'yellow'));
1447
1520
  }
1448
1521
 
@@ -1698,9 +1771,9 @@ async function showMainMenu() {
1698
1771
 
1699
1772
  async function showModelMenu() {
1700
1773
  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() },
1774
+ { id: 1, text: 'Add Verified Provider', action: () => addVerifiedProvider() },
1775
+ { id: 2, text: 'Add Custom Models', action: () => addCustomModelsMenu() },
1776
+ { id: 3, text: 'List Existing Providers', action: () => listProviders() },
1704
1777
  { id: 4, text: 'Debug Info', action: () => showDebugInfo() },
1705
1778
  { id: 5, text: 'Back to Main Menu', action: () => 'back' }
1706
1779
  ];
@@ -1770,4 +1843,4 @@ if (import.meta.url === `file://${process.argv[1]}` ||
1770
1843
  showMainMenu();
1771
1844
  }
1772
1845
 
1773
- export { showMainMenu, addProvider, listProviders, selectModelsCircular, runStreamingBenchmark, loadConfig, saveConfig };
1846
+ export { showMainMenu, listProviders, selectModelsCircular, runStreamingBenchmark, loadConfig, saveConfig };