recoder-code 2.4.6 → 2.4.7

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.
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Connect command module - Add custom providers interactively
3
+ */
4
+ import type { CommandModule } from 'yargs';
5
+ export declare const connectCommand: CommandModule;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Connect command module - Add custom providers interactively
3
+ */
4
+ import { connectProvider } from './connect.js';
5
+ export const connectCommand = {
6
+ command: 'connect',
7
+ describe: 'Add a custom AI provider (LM Studio, vLLM, etc.)',
8
+ handler: async () => {
9
+ await connectProvider();
10
+ process.exit(0);
11
+ },
12
+ };
@@ -1,7 +1,4 @@
1
1
  /**
2
- * /connect command - Interactive provider setup and health check
2
+ * Interactive provider configuration command
3
3
  */
4
- /**
5
- * Interactive connect command
6
- */
7
- export declare function connectCommand(args?: string[]): Promise<void>;
4
+ export declare function connectProvider(): Promise<void>;
@@ -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
  }
@@ -5,10 +5,17 @@ import { listProviders } from './providers/list.js';
5
5
  import { listProviderModels } from './providers/models.js';
6
6
  import { pullModel } from './providers/pull.js';
7
7
  import { configureProvider } from './providers/config.js';
8
+ import { RecoderAuthService } from '../services/RecoderAuthService.js';
8
9
  const listCommand = {
9
10
  command: 'list',
10
11
  describe: 'List all available providers',
11
12
  handler: async () => {
13
+ const authService = new RecoderAuthService();
14
+ const session = await authService.getSession();
15
+ if (!session) {
16
+ console.error('āŒ Please login first: recoder auth login');
17
+ process.exit(1);
18
+ }
12
19
  await listProviders();
13
20
  process.exit(0);
14
21
  },
@@ -21,6 +28,12 @@ const modelsCommand = {
21
28
  type: 'string',
22
29
  }),
23
30
  handler: async (argv) => {
31
+ const authService = new RecoderAuthService();
32
+ const session = await authService.getSession();
33
+ if (!session) {
34
+ console.error('āŒ Please login first: recoder auth login');
35
+ process.exit(1);
36
+ }
24
37
  await listProviderModels(argv.provider);
25
38
  process.exit(0);
26
39
  },
@@ -34,6 +47,12 @@ const pullCommand = {
34
47
  demandOption: true,
35
48
  }),
36
49
  handler: async (argv) => {
50
+ const authService = new RecoderAuthService();
51
+ const session = await authService.getSession();
52
+ if (!session) {
53
+ console.error('āŒ Please login first: recoder auth login');
54
+ process.exit(1);
55
+ }
37
56
  await pullModel(argv.model);
38
57
  process.exit(0);
39
58
  },
@@ -46,6 +65,12 @@ const configCommand = {
46
65
  type: 'string',
47
66
  }),
48
67
  handler: async (argv) => {
68
+ const authService = new RecoderAuthService();
69
+ const session = await authService.getSession();
70
+ if (!session) {
71
+ console.error('āŒ Please login first: recoder auth login');
72
+ process.exit(1);
73
+ }
49
74
  await configureProvider(argv.provider);
50
75
  process.exit(0);
51
76
  },
@@ -61,6 +86,12 @@ export const providersCommand = {
61
86
  .demandCommand(0)
62
87
  .version(false),
63
88
  handler: async () => {
89
+ const authService = new RecoderAuthService();
90
+ const session = await authService.getSession();
91
+ if (!session) {
92
+ console.error('āŒ Please login first: recoder auth login');
93
+ process.exit(1);
94
+ }
64
95
  await listProviders();
65
96
  process.exit(0);
66
97
  },
@@ -22,6 +22,7 @@ import { agentsCommand } from '../commands/agents.js';
22
22
  import { hintsCommand } from '../commands/hints.js';
23
23
  import { modelsCommand } from '../commands/models-cmd.js';
24
24
  import { configureCommand } from '../commands/configure.js';
25
+ import { connectCommand } from '../commands/connect-cmd.js';
25
26
  import { resolvePath } from '../utils/resolvePath.js';
26
27
  import { getCliVersion } from '../utils/version.js';
27
28
  import { annotateActiveExtensions } from './extension.js';
@@ -277,7 +278,9 @@ export async function parseArguments(settings) {
277
278
  // Register models command for model management
278
279
  .command(modelsCommand)
279
280
  // Register configure command for interactive setup
280
- .command(configureCommand);
281
+ .command(configureCommand)
282
+ // Register connect command for custom providers
283
+ .command(connectCommand);
281
284
  if (settings?.experimental?.extensionManagement ?? false) {
282
285
  yargsInstance.command(extensionsCommand);
283
286
  }
@@ -127,6 +127,26 @@ export async function main() {
127
127
  await runApiKeySetup();
128
128
  process.exit(0);
129
129
  }
130
+ // ===== RECODER.XYZ AUTHENTICATION GUARD =====
131
+ // Require users to sign up/login before using recoder-code
132
+ const { RecoderAuthService } = await import('./services/RecoderAuthService.js');
133
+ const authService = new RecoderAuthService();
134
+ // Skip auth check for auth commands themselves
135
+ const isAuthCommand = process.argv.some(arg => arg === 'auth');
136
+ if (!isAuthCommand) {
137
+ const isAuthenticated = await authService.isAuthenticated();
138
+ if (!isAuthenticated) {
139
+ console.log('\nšŸš€ Welcome to Recoder Code!\n');
140
+ console.log('Please sign up to continue. It\'s free - no credit card required.');
141
+ console.log('Just bring your own API key (OpenRouter, OpenAI, Anthropic, etc.)\n');
142
+ console.log('🌐 https://recoder.xyz');
143
+ console.log('🐦 https://x.com/recoderxyz\n');
144
+ console.log('To authenticate, run:\n');
145
+ console.log(' recoder auth login\n');
146
+ process.exit(1);
147
+ }
148
+ }
149
+ // ===== END AUTH GUARD =====
130
150
  // Check if API key is configured before proceeding
131
151
  const { isApiKeyConfigured, checkAndSetupApiKey } = await import('./setup/apiKeySetup.js');
132
152
  if (!isApiKeyConfigured()) {
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Custom Provider Manager - Load and manage user-defined providers
3
+ */
4
+ import type { CustomProviderConfig, AIProvider } from './types.js';
5
+ export declare class CustomProviderManager {
6
+ private customProviders;
7
+ private initialized;
8
+ constructor();
9
+ private init;
10
+ private ensureDirectories;
11
+ private loadCustomProviders;
12
+ addProvider(config: CustomProviderConfig): Promise<void>;
13
+ removeProvider(id: string): Promise<void>;
14
+ getAll(): AIProvider[];
15
+ get(id: string): AIProvider | undefined;
16
+ testConnection(provider: AIProvider): Promise<boolean>;
17
+ }
18
+ export declare const customProviderManager: CustomProviderManager;