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.
- package/dist/src/commands/connect-cmd.d.ts +5 -0
- package/dist/src/commands/connect-cmd.js +12 -0
- package/dist/src/commands/connect.d.ts +2 -5
- package/dist/src/commands/connect.js +78 -353
- package/dist/src/commands/providers.js +31 -0
- package/dist/src/config/config.js +4 -1
- package/dist/src/gemini.js +20 -0
- package/dist/src/providers/custom-provider-manager.d.ts +18 -0
- package/dist/src/providers/custom-provider-manager.js +105 -0
- package/dist/src/providers/registry.js +2 -17
- package/dist/src/services/RecoderAuthService.js +25 -17
- package/dist/src/zed-integration/schema.d.ts +1466 -1466
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
2
|
+
* Interactive provider configuration command
|
|
3
3
|
*/
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
337
|
-
name: name
|
|
338
|
-
engine:
|
|
339
|
-
baseUrl: baseUrl
|
|
340
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
console.log(
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
}
|
package/dist/src/gemini.js
CHANGED
|
@@ -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;
|