recoder-code 2.4.6 → 2.5.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.
@@ -1,363 +1,88 @@
1
1
  /**
2
- * /connect command - Interactive provider setup and health check
2
+ * Interactive provider configuration command
3
3
  */
4
- import chalk from 'chalk';
5
- import * as fs from 'fs';
6
- import * as path from 'path';
7
- import * as os from 'os';
8
- import * as readline from 'readline';
9
- import { getProviderRegistry } from '../providers/registry.js';
10
- const CONFIG_DIR = path.join(os.homedir(), '.recoder-code');
11
- const ENV_FILE = path.join(CONFIG_DIR, '.env');
12
- const CUSTOM_PROVIDERS_DIR = path.join(CONFIG_DIR, 'custom_providers');
13
- /**
14
- * Check health of all providers
15
- */
16
- async function checkProviderHealth(provider) {
17
- const start = Date.now();
18
- try {
19
- let url;
20
- let headers = {};
21
- if (provider.isLocal) {
22
- // Local providers - check if running
23
- url = provider.engine === 'ollama'
24
- ? `${provider.baseUrl}/api/tags`
25
- : `${provider.baseUrl}/models`;
26
- }
27
- else {
28
- // Cloud providers - check API with key
29
- const apiKey = provider.apiKeyEnv ? process.env[provider.apiKeyEnv] : undefined;
30
- if (!apiKey) {
31
- return { provider, available: false, error: 'No API key configured' };
32
- }
33
- if (provider.id === 'openrouter') {
34
- url = 'https://openrouter.ai/api/v1/models';
35
- headers['Authorization'] = `Bearer ${apiKey}`;
36
- }
37
- else if (provider.id === 'openai') {
38
- url = 'https://api.openai.com/v1/models';
39
- headers['Authorization'] = `Bearer ${apiKey}`;
40
- }
41
- else if (provider.id === 'anthropic') {
42
- url = 'https://api.anthropic.com/v1/messages';
43
- headers['x-api-key'] = apiKey;
44
- headers['anthropic-version'] = '2023-06-01';
45
- // For Anthropic, we just check if key format is valid
46
- if (apiKey.startsWith('sk-ant-')) {
47
- return { provider, available: true, latency: Date.now() - start };
4
+ import inquirer from 'inquirer';
5
+ import { customProviderManager } from '../providers/custom-provider-manager.js';
6
+ export async function connectProvider() {
7
+ console.log('šŸ”Œ Add Custom Provider\n');
8
+ const answers = await inquirer.prompt([
9
+ {
10
+ type: 'input',
11
+ name: 'id',
12
+ message: 'Provider ID (e.g., my-llm):',
13
+ validate: (input) => input.length > 0 || 'ID is required',
14
+ },
15
+ {
16
+ type: 'input',
17
+ name: 'name',
18
+ message: 'Provider Name:',
19
+ validate: (input) => input.length > 0 || 'Name is required',
20
+ },
21
+ {
22
+ type: 'list',
23
+ name: 'engine',
24
+ message: 'API Compatibility:',
25
+ choices: [
26
+ { name: 'OpenAI-compatible (LM Studio, vLLM, llama.cpp)', value: 'openai' },
27
+ { name: 'Anthropic-compatible', value: 'anthropic' },
28
+ { name: 'Ollama-compatible', value: 'ollama' },
29
+ { name: 'Google Gemini-compatible', value: 'google' },
30
+ ],
31
+ },
32
+ {
33
+ type: 'input',
34
+ name: 'baseUrl',
35
+ message: 'Base URL:',
36
+ default: 'http://localhost:1234/v1',
37
+ validate: (input) => {
38
+ try {
39
+ new URL(input);
40
+ return true;
48
41
  }
49
- }
50
- else if (provider.id === 'groq') {
51
- url = 'https://api.groq.com/openai/v1/models';
52
- headers['Authorization'] = `Bearer ${apiKey}`;
53
- }
54
- else {
55
- // Generic OpenAI-compatible check
56
- url = `${provider.baseUrl}/models`;
57
- headers['Authorization'] = `Bearer ${apiKey}`;
58
- }
59
- }
60
- const response = await fetch(url, {
61
- headers,
62
- signal: AbortSignal.timeout(5000),
63
- });
64
- const latency = Date.now() - start;
65
- if (response.ok || response.status === 401) {
66
- // 401 means the endpoint exists but key is wrong
67
- return {
68
- provider,
69
- available: response.ok,
70
- latency,
71
- error: response.status === 401 ? 'Invalid API key' : undefined
72
- };
73
- }
74
- return { provider, available: false, latency, error: `HTTP ${response.status}` };
75
- }
76
- catch (error) {
77
- return {
78
- provider,
79
- available: false,
80
- error: error.code === 'ECONNREFUSED' ? 'Not running' : error.message
81
- };
82
- }
83
- }
84
- /**
85
- * Interactive connect command
86
- */
87
- export async function connectCommand(args = []) {
88
- const registry = getProviderRegistry();
89
- const subcommand = args[0];
90
- if (!subcommand || subcommand === 'status') {
91
- await showProviderStatus();
92
- return;
93
- }
94
- if (subcommand === 'add') {
95
- await addProvider(args[1]);
96
- return;
97
- }
98
- if (subcommand === 'test') {
99
- await testProvider(args[1]);
100
- return;
101
- }
102
- if (subcommand === 'local') {
103
- await detectLocalProviders();
104
- return;
105
- }
106
- if (subcommand === 'custom') {
107
- await addCustomProvider();
108
- return;
109
- }
110
- // If provider name given, configure it
111
- await configureProviderKey(subcommand);
112
- }
113
- /**
114
- * Show status of all providers
115
- */
116
- async function showProviderStatus() {
117
- const registry = getProviderRegistry();
118
- const providers = registry.getAllProviders();
119
- console.log(chalk.bold.cyan('\nšŸ”Œ Provider Status\n'));
120
- // Group by type
121
- const local = providers.filter(p => p.isLocal);
122
- const cloud = providers.filter(p => !p.isLocal && p.isBuiltin);
123
- const custom = providers.filter(p => !p.isBuiltin);
124
- // Check local providers
125
- console.log(chalk.bold('Local AI:'));
126
- for (const provider of local) {
127
- const status = await checkProviderHealth(provider);
128
- const icon = status.available ? chalk.green('āœ“') : chalk.gray('ā—‹');
129
- const latencyStr = status.latency ? chalk.gray(` (${status.latency}ms)`) : '';
130
- const errorStr = status.error ? chalk.red(` - ${status.error}`) : '';
131
- console.log(` ${icon} ${provider.name}${latencyStr}${errorStr}`);
132
- }
133
- // Check cloud providers
134
- console.log(chalk.bold('\nCloud Providers:'));
135
- for (const provider of cloud) {
136
- const hasKey = provider.apiKeyEnv ? !!process.env[provider.apiKeyEnv] : false;
137
- const icon = hasKey ? chalk.green('āœ“') : chalk.gray('ā—‹');
138
- const keyStatus = hasKey ? chalk.gray(' (configured)') : chalk.yellow(' (no key)');
139
- console.log(` ${icon} ${provider.name}${keyStatus}`);
140
- }
141
- // Custom providers
142
- if (custom.length > 0) {
143
- console.log(chalk.bold('\nCustom Providers:'));
144
- for (const provider of custom) {
145
- const status = await checkProviderHealth(provider);
146
- const icon = status.available ? chalk.green('āœ“') : chalk.gray('ā—‹');
147
- console.log(` ${icon} ${provider.name} (${provider.baseUrl})`);
148
- }
149
- }
150
- console.log(chalk.gray('\nCommands:'));
151
- console.log(chalk.gray(' /connect <provider> - Configure API key'));
152
- console.log(chalk.gray(' /connect local - Detect local AI servers'));
153
- console.log(chalk.gray(' /connect custom - Add custom provider'));
154
- console.log(chalk.gray(' /connect test <provider> - Test connection'));
155
- console.log();
156
- }
157
- /**
158
- * Configure provider API key
159
- */
160
- async function configureProviderKey(providerId) {
161
- const registry = getProviderRegistry();
162
- const provider = registry.getProvider(providerId);
163
- if (!provider) {
164
- console.log(chalk.red(`\nāŒ Unknown provider: ${providerId}`));
165
- console.log(chalk.gray('Run /connect to see available providers\n'));
166
- return;
167
- }
168
- if (provider.isLocal) {
169
- console.log(chalk.yellow(`\n${provider.name} is a local provider - no API key needed.`));
170
- console.log(chalk.gray('Run /connect local to check if it\'s running.\n'));
171
- return;
172
- }
173
- if (!provider.apiKeyEnv) {
174
- console.log(chalk.yellow(`\n${provider.name} doesn't require an API key.\n`));
175
- return;
176
- }
177
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
178
- const question = (q) => new Promise((resolve) => rl.question(q, resolve));
179
- console.log(chalk.bold.cyan(`\nāš™ļø Configure ${provider.name}\n`));
180
- // Show help for getting API key
181
- const keyUrls = {
182
- openrouter: 'https://openrouter.ai/keys',
183
- anthropic: 'https://console.anthropic.com/settings/keys',
184
- openai: 'https://platform.openai.com/api-keys',
185
- groq: 'https://console.groq.com/keys',
186
- deepseek: 'https://platform.deepseek.com/api_keys',
187
- together: 'https://api.together.xyz/settings/api-keys',
188
- fireworks: 'https://fireworks.ai/account/api-keys',
189
- mistral: 'https://console.mistral.ai/api-keys',
190
- google: 'https://aistudio.google.com/apikey',
191
- };
192
- if (keyUrls[provider.id]) {
193
- console.log(chalk.gray(`Get your key: ${keyUrls[provider.id]}\n`));
194
- }
195
- const currentKey = process.env[provider.apiKeyEnv];
196
- if (currentKey) {
197
- const masked = currentKey.substring(0, 8) + '...' + currentKey.substring(currentKey.length - 4);
198
- console.log(chalk.gray(`Current key: ${masked}\n`));
199
- }
200
- const apiKey = await question(chalk.white(`Enter ${provider.apiKeyEnv}: `));
201
- rl.close();
202
- if (!apiKey.trim()) {
203
- console.log(chalk.yellow('\nNo key provided, skipping.\n'));
204
- return;
205
- }
206
- // Test the key before saving
207
- console.log(chalk.gray('\nTesting connection...'));
208
- process.env[provider.apiKeyEnv] = apiKey.trim();
209
- const status = await checkProviderHealth(provider);
210
- if (!status.available && status.error === 'Invalid API key') {
211
- console.log(chalk.red('\nāŒ API key appears to be invalid.'));
212
- const save = await new Promise((resolve) => {
213
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
214
- rl2.question(chalk.yellow('Save anyway? (y/N): '), (answer) => {
215
- rl2.close();
216
- resolve(answer);
217
- });
218
- });
219
- if (save.toLowerCase() !== 'y') {
220
- console.log(chalk.gray('Key not saved.\n'));
221
- return;
222
- }
223
- }
224
- else if (status.available) {
225
- console.log(chalk.green(`āœ“ Connected! (${status.latency}ms)`));
226
- }
227
- // Save to env file
228
- saveApiKey(provider.apiKeyEnv, apiKey.trim());
229
- console.log(chalk.green(`\nāœ“ Saved to ${ENV_FILE}`));
230
- console.log(chalk.gray(`\nTo use immediately, run:`));
231
- console.log(chalk.cyan(` export ${provider.apiKeyEnv}="${apiKey.trim()}"\n`));
232
- }
233
- /**
234
- * Save API key to env file
235
- */
236
- function saveApiKey(envVar, value) {
237
- if (!fs.existsSync(CONFIG_DIR)) {
238
- fs.mkdirSync(CONFIG_DIR, { recursive: true });
239
- }
240
- let envContent = '';
241
- if (fs.existsSync(ENV_FILE)) {
242
- envContent = fs.readFileSync(ENV_FILE, 'utf-8');
243
- }
244
- const regex = new RegExp(`^${envVar}=.*$`, 'm');
245
- if (regex.test(envContent)) {
246
- envContent = envContent.replace(regex, `${envVar}=${value}`);
247
- }
248
- else {
249
- envContent += `\n${envVar}=${value}`;
250
- }
251
- fs.writeFileSync(ENV_FILE, envContent.trim() + '\n');
252
- }
253
- /**
254
- * Detect local AI servers
255
- */
256
- async function detectLocalProviders() {
257
- console.log(chalk.bold.cyan('\nšŸ” Detecting Local AI Servers\n'));
258
- const localServers = [
259
- { name: 'Ollama', url: 'http://localhost:11434/api/tags', port: 11434 },
260
- { name: 'LM Studio', url: 'http://localhost:1234/v1/models', port: 1234 },
261
- { name: 'llama.cpp', url: 'http://localhost:8080/v1/models', port: 8080 },
262
- { name: 'vLLM', url: 'http://localhost:8000/v1/models', port: 8000 },
263
- { name: 'LocalAI', url: 'http://localhost:8080/v1/models', port: 8080 },
264
- ];
265
- for (const server of localServers) {
266
- try {
267
- const start = Date.now();
268
- const response = await fetch(server.url, { signal: AbortSignal.timeout(2000) });
269
- const latency = Date.now() - start;
270
- if (response.ok) {
271
- console.log(chalk.green(`āœ“ ${server.name}`), chalk.gray(`(localhost:${server.port}, ${latency}ms)`));
272
- // Get model list for Ollama
273
- if (server.name === 'Ollama') {
274
- const data = await response.json();
275
- if (data.models && data.models.length > 0) {
276
- console.log(chalk.gray(` Models: ${data.models.slice(0, 5).map(m => m.name).join(', ')}${data.models.length > 5 ? '...' : ''}`));
277
- }
42
+ catch {
43
+ return 'Invalid URL';
278
44
  }
279
- }
280
- }
281
- catch {
282
- console.log(chalk.gray(`ā—‹ ${server.name}`), chalk.gray(`(not running on port ${server.port})`));
283
- }
284
- }
285
- console.log(chalk.gray('\nTo use a local model:'));
286
- console.log(chalk.cyan(' recoder --model ollama/llama3.2'));
287
- console.log(chalk.cyan(' recoder --model lmstudio/local-model\n'));
288
- }
289
- /**
290
- * Test specific provider connection
291
- */
292
- async function testProvider(providerId) {
293
- if (!providerId) {
294
- console.log(chalk.yellow('\nUsage: /connect test <provider>\n'));
295
- return;
296
- }
297
- const registry = getProviderRegistry();
298
- const provider = registry.getProvider(providerId);
299
- if (!provider) {
300
- console.log(chalk.red(`\nāŒ Unknown provider: ${providerId}\n`));
301
- return;
302
- }
303
- console.log(chalk.cyan(`\nTesting ${provider.name}...`));
304
- const status = await checkProviderHealth(provider);
305
- if (status.available) {
306
- console.log(chalk.green(`āœ“ Connected! (${status.latency}ms)\n`));
307
- }
308
- else {
309
- console.log(chalk.red(`āœ— Failed: ${status.error}\n`));
310
- }
311
- }
312
- /**
313
- * Add custom provider interactively
314
- */
315
- async function addCustomProvider() {
316
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
317
- const question = (q) => new Promise((resolve) => rl.question(q, resolve));
318
- console.log(chalk.bold.cyan('\nāž• Add Custom Provider\n'));
319
- console.log(chalk.gray('Add any OpenAI-compatible API endpoint.\n'));
320
- const id = await question(chalk.white('Provider ID (e.g., my-api): '));
321
- if (!id.trim()) {
322
- rl.close();
323
- return;
324
- }
325
- const name = await question(chalk.white('Display Name: ')) || id;
326
- const baseUrl = await question(chalk.white('Base URL (e.g., http://localhost:8000/v1): '));
327
- if (!baseUrl.trim()) {
328
- console.log(chalk.yellow('\nBase URL required.\n'));
329
- rl.close();
330
- return;
331
- }
332
- const apiKeyEnv = await question(chalk.white('API Key env var (optional, e.g., MY_API_KEY): '));
333
- const isLocalStr = await question(chalk.white('Is local server? (y/N): '));
334
- rl.close();
45
+ },
46
+ },
47
+ {
48
+ type: 'confirm',
49
+ name: 'isLocal',
50
+ message: 'Is this a local provider?',
51
+ default: true,
52
+ },
53
+ {
54
+ type: 'input',
55
+ name: 'apiKey',
56
+ message: 'API Key (leave empty for local):',
57
+ default: '',
58
+ },
59
+ ]);
335
60
  const config = {
336
- id: id.trim().toLowerCase().replace(/\s+/g, '-'),
337
- name: name.trim(),
338
- engine: 'openai',
339
- baseUrl: baseUrl.trim(),
340
- apiKeyEnv: apiKeyEnv.trim() || undefined,
341
- isLocal: isLocalStr.toLowerCase() === 'y',
61
+ id: answers.id,
62
+ name: answers.name,
63
+ engine: answers.engine,
64
+ baseUrl: answers.baseUrl,
65
+ isLocal: answers.isLocal,
342
66
  models: [],
343
67
  supportsStreaming: true,
344
68
  };
345
- // Save to file
346
- if (!fs.existsSync(CUSTOM_PROVIDERS_DIR)) {
347
- fs.mkdirSync(CUSTOM_PROVIDERS_DIR, { recursive: true });
348
- }
349
- const filePath = path.join(CUSTOM_PROVIDERS_DIR, `${config.id}.json`);
350
- fs.writeFileSync(filePath, JSON.stringify(config, null, 2));
351
- console.log(chalk.green(`\nāœ“ Custom provider saved to ${filePath}`));
352
- console.log(chalk.gray(`\nTo use: recoder --model ${config.id}/model-name\n`));
353
- }
354
- /**
355
- * Add provider (alias for configure)
356
- */
357
- async function addProvider(providerId) {
358
- if (!providerId) {
359
- console.log(chalk.yellow('\nUsage: /connect add <provider>\n'));
360
- return;
69
+ if (answers.apiKey) {
70
+ config.apiKeyEnv = `RECODER_${answers.id.toUpperCase().replace(/-/g, '_')}_API_KEY`;
71
+ console.log(`\nšŸ’” Set your API key: export ${config.apiKeyEnv}="${answers.apiKey}"`);
72
+ }
73
+ await customProviderManager.addProvider(config);
74
+ console.log(`\nāœ… Provider "${answers.name}" added successfully!`);
75
+ console.log(` Use it with: recoder --provider ${answers.id}`);
76
+ // Test connection
77
+ const provider = customProviderManager.get(answers.id);
78
+ if (provider) {
79
+ console.log('\nšŸ” Testing connection...');
80
+ const isConnected = await customProviderManager.testConnection(provider);
81
+ if (isConnected) {
82
+ console.log('āœ… Connection successful!');
83
+ }
84
+ else {
85
+ console.log('āš ļø Could not connect. Check your configuration.');
86
+ }
361
87
  }
362
- await configureProviderKey(providerId);
363
88
  }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Multi-model comparison mode
3
+ */
4
+ export declare function compareModels(): Promise<void>;
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Multi-model comparison mode
3
+ */
4
+ import inquirer from 'inquirer';
5
+ import chalk from 'chalk';
6
+ import { getProviderRegistry } from '../../providers/registry.js';
7
+ export async function compareModels() {
8
+ console.log(chalk.bold.cyan('\nāš–ļø Multi-Model Comparison\n'));
9
+ const registry = getProviderRegistry();
10
+ const providers = registry.getAllProviders();
11
+ // Get available models
12
+ const modelChoices = [];
13
+ for (const provider of providers) {
14
+ try {
15
+ const models = await registry.getModels(provider.id);
16
+ models.forEach(m => {
17
+ modelChoices.push(`${provider.id}/${m.id}`);
18
+ });
19
+ }
20
+ catch {
21
+ // Skip
22
+ }
23
+ }
24
+ if (modelChoices.length < 2) {
25
+ console.log(chalk.red('āŒ Need at least 2 models to compare'));
26
+ return;
27
+ }
28
+ const answers = await inquirer.prompt([
29
+ {
30
+ type: 'checkbox',
31
+ name: 'models',
32
+ message: 'Select models to compare (2-4):',
33
+ choices: modelChoices.slice(0, 20),
34
+ validate: (input) => {
35
+ if (input.length < 2)
36
+ return 'Select at least 2 models';
37
+ if (input.length > 4)
38
+ return 'Select at most 4 models';
39
+ return true;
40
+ },
41
+ },
42
+ {
43
+ type: 'input',
44
+ name: 'prompt',
45
+ message: 'Enter prompt to test:',
46
+ validate: (input) => input.length > 0 || 'Prompt required',
47
+ },
48
+ ]);
49
+ console.log(chalk.cyan('\nšŸ”„ Running comparison...\n'));
50
+ const results = [];
51
+ for (const model of answers.models) {
52
+ const start = Date.now();
53
+ try {
54
+ // Simulate API call (replace with actual implementation)
55
+ const response = `Response from ${model}`;
56
+ const responseTime = Date.now() - start;
57
+ results.push({
58
+ model,
59
+ response,
60
+ responseTime,
61
+ });
62
+ }
63
+ catch (err) {
64
+ results.push({
65
+ model,
66
+ response: '',
67
+ responseTime: Date.now() - start,
68
+ error: err.message,
69
+ });
70
+ }
71
+ }
72
+ // Display results
73
+ console.log(chalk.bold('Comparison Results:'));
74
+ console.log(chalk.gray('═'.repeat(60)));
75
+ results.forEach((result, i) => {
76
+ console.log(chalk.bold.cyan(`\n${i + 1}. ${result.model}`));
77
+ console.log(chalk.gray(` Response time: ${result.responseTime}ms`));
78
+ if (result.error) {
79
+ console.log(chalk.red(` Error: ${result.error}`));
80
+ }
81
+ else {
82
+ console.log(chalk.white(` ${result.response.substring(0, 200)}...`));
83
+ }
84
+ });
85
+ console.log(chalk.gray('\n═'.repeat(60)));
86
+ // Show fastest
87
+ const fastest = results.filter(r => !r.error).sort((a, b) => a.responseTime - b.responseTime)[0];
88
+ if (fastest) {
89
+ console.log(chalk.green(`\n⚔ Fastest: ${fastest.model} (${fastest.responseTime}ms)`));
90
+ }
91
+ console.log();
92
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Interactive model selection with fuzzy search
3
+ */
4
+ export declare function selectModel(): Promise<void>;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Interactive model selection with fuzzy search
3
+ */
4
+ import inquirer from 'inquirer';
5
+ import { getProviderRegistry } from '../../providers/registry.js';
6
+ import chalk from 'chalk';
7
+ export async function selectModel() {
8
+ console.log(chalk.bold.cyan('\nšŸ¤– Select AI Model\n'));
9
+ const registry = getProviderRegistry();
10
+ const providers = registry.getAllProviders();
11
+ // Gather all models from all providers
12
+ const allModels = [];
13
+ for (const provider of providers) {
14
+ try {
15
+ const models = await registry.getModels(provider.id);
16
+ models.forEach(model => {
17
+ const contextInfo = model.contextLength ? ` (${Math.round(model.contextLength / 1000)}k ctx)` : '';
18
+ const freeTag = model.isFree ? chalk.green(' [FREE]') : '';
19
+ const providerTag = chalk.gray(` - ${provider.name}`);
20
+ allModels.push({
21
+ name: `${model.id}${contextInfo}${freeTag}${providerTag}`,
22
+ value: `${provider.id}/${model.id}`,
23
+ provider: provider.id,
24
+ contextLength: model.contextLength,
25
+ isFree: model.isFree,
26
+ });
27
+ });
28
+ }
29
+ catch (err) {
30
+ // Skip providers that fail to load models
31
+ }
32
+ }
33
+ if (allModels.length === 0) {
34
+ console.log(chalk.red('āŒ No models available'));
35
+ console.log(chalk.gray(' Run: recoder providers detect'));
36
+ return;
37
+ }
38
+ // Sort: free models first, then by context length
39
+ allModels.sort((a, b) => {
40
+ if (a.isFree && !b.isFree)
41
+ return -1;
42
+ if (!a.isFree && b.isFree)
43
+ return 1;
44
+ return (b.contextLength || 0) - (a.contextLength || 0);
45
+ });
46
+ const answer = await inquirer.prompt([
47
+ {
48
+ type: 'list',
49
+ name: 'model',
50
+ message: 'Select a model:',
51
+ choices: allModels.slice(0, 30),
52
+ pageSize: 15,
53
+ },
54
+ ]);
55
+ const selected = allModels.find(m => m.value === answer.model);
56
+ if (selected) {
57
+ console.log(chalk.green(`\nāœ… Selected: ${selected.value}`));
58
+ console.log(chalk.cyan('\nšŸ’” Usage:'));
59
+ console.log(chalk.gray(` recoder --model ${selected.value}`));
60
+ console.log(chalk.gray(` Or set as default: recoder models set-default ${selected.value}`));
61
+ }
62
+ }
@@ -6,6 +6,8 @@ import * as fs from 'fs';
6
6
  import * as path from 'path';
7
7
  import * as os from 'os';
8
8
  import { getOllamaProvider, getOpenRouterProvider, getAnthropicProvider, getOpenAIProvider, getGroqProvider, parseModelId, } from '../providers/index.js';
9
+ import { selectModel } from './models/select.js';
10
+ import { compareModels } from './models/compare.js';
9
11
  const CONFIG_DIR = path.join(os.homedir(), '.recoder-code');
10
12
  const CUSTOM_MODELS_FILE = path.join(CONFIG_DIR, 'custom-models.json');
11
13
  const DEFAULT_MODEL_FILE = path.join(CONFIG_DIR, 'default-model.json');
@@ -162,11 +164,29 @@ const setDefaultCommand = {
162
164
  process.exit(0);
163
165
  },
164
166
  };
167
+ const selectCommand = {
168
+ command: 'select',
169
+ describe: 'Interactive model selection with fuzzy search',
170
+ handler: async () => {
171
+ await selectModel();
172
+ process.exit(0);
173
+ },
174
+ };
175
+ const compareCommand = {
176
+ command: 'compare',
177
+ describe: 'Compare multiple models side-by-side',
178
+ handler: async () => {
179
+ await compareModels();
180
+ process.exit(0);
181
+ },
182
+ };
165
183
  export const modelsCommand = {
166
184
  command: 'models',
167
185
  describe: 'Manage AI models',
168
186
  builder: (yargs) => yargs
169
187
  .command(listCommand)
188
+ .command(selectCommand)
189
+ .command(compareCommand)
170
190
  .command(addCommand)
171
191
  .command(removeCommand)
172
192
  .command(setDefaultCommand)
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Provider health monitoring
3
+ */
4
+ export declare function monitorProviders(): Promise<void>;