claude-connect 0.1.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.
@@ -0,0 +1,482 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { DatabaseSync } from 'node:sqlite';
4
+
5
+ export const defaultCatalogDbPath = path.join(process.cwd(), 'storage', 'claude-connect.sqlite');
6
+
7
+ const schemaSql = `
8
+ PRAGMA foreign_keys = ON;
9
+
10
+ CREATE TABLE IF NOT EXISTS providers (
11
+ id TEXT PRIMARY KEY,
12
+ name TEXT NOT NULL,
13
+ vendor TEXT NOT NULL,
14
+ description TEXT NOT NULL,
15
+ docs_url TEXT,
16
+ docs_verified_at TEXT,
17
+ base_url TEXT NOT NULL,
18
+ default_model_id TEXT,
19
+ default_auth_method_id TEXT,
20
+ default_api_key_env_var TEXT NOT NULL
21
+ );
22
+
23
+ CREATE TABLE IF NOT EXISTS models (
24
+ id TEXT PRIMARY KEY,
25
+ provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
26
+ name TEXT NOT NULL,
27
+ category TEXT NOT NULL,
28
+ context_window TEXT NOT NULL,
29
+ summary TEXT NOT NULL,
30
+ sort_order INTEGER NOT NULL DEFAULT 0,
31
+ is_default INTEGER NOT NULL DEFAULT 0
32
+ );
33
+
34
+ CREATE TABLE IF NOT EXISTS auth_methods (
35
+ id TEXT PRIMARY KEY,
36
+ name TEXT NOT NULL,
37
+ description TEXT NOT NULL,
38
+ credential_kind TEXT NOT NULL
39
+ );
40
+
41
+ CREATE TABLE IF NOT EXISTS provider_auth_methods (
42
+ provider_id TEXT NOT NULL REFERENCES providers(id) ON DELETE CASCADE,
43
+ auth_method_id TEXT NOT NULL REFERENCES auth_methods(id) ON DELETE CASCADE,
44
+ sort_order INTEGER NOT NULL DEFAULT 0,
45
+ is_default INTEGER NOT NULL DEFAULT 0,
46
+ PRIMARY KEY (provider_id, auth_method_id)
47
+ );
48
+
49
+ CREATE TABLE IF NOT EXISTS provider_oauth_configs (
50
+ provider_id TEXT PRIMARY KEY REFERENCES providers(id) ON DELETE CASCADE,
51
+ authorize_url TEXT NOT NULL,
52
+ token_url TEXT NOT NULL,
53
+ callback_url TEXT NOT NULL,
54
+ access_type TEXT NOT NULL,
55
+ scope TEXT,
56
+ client_id TEXT,
57
+ app_secret_id TEXT
58
+ );
59
+ `;
60
+
61
+ const seedProviders = [
62
+ {
63
+ id: 'kimi',
64
+ name: 'Kimi',
65
+ vendor: 'Moonshot AI',
66
+ description: 'Kimi Code para Claude Code usando el endpoint Anthropic oficial de Kimi.',
67
+ docsUrl: 'https://www.kimi.com/code/docs/en/more/third-party-agents.html',
68
+ docsVerifiedAt: '2026-04-01',
69
+ baseUrl: 'https://api.kimi.com/coding/',
70
+ defaultModelId: 'kimi-for-coding',
71
+ defaultAuthMethodId: 'token',
72
+ defaultApiKeyEnvVar: 'KIMI_API_KEY',
73
+ models: [
74
+ {
75
+ id: 'kimi-for-coding',
76
+ name: 'Kimi For Coding',
77
+ category: 'Coding',
78
+ contextWindow: '262144',
79
+ summary: 'Modelo oficial de Kimi Code para Claude Code. El modo thinking se conmuta con Tab dentro de Claude Code.',
80
+ sortOrder: 1,
81
+ isDefault: 1
82
+ }
83
+ ],
84
+ authMethods: [
85
+ {
86
+ id: 'token',
87
+ name: 'Token',
88
+ description: 'Conexion por API key contra el endpoint Anthropic oficial del proveedor.',
89
+ credentialKind: 'env_var',
90
+ sortOrder: 1,
91
+ isDefault: 1
92
+ }
93
+ ]
94
+ },
95
+ {
96
+ id: 'deepseek',
97
+ name: 'DeepSeek',
98
+ vendor: 'DeepSeek',
99
+ description: 'DeepSeek API compatible con OpenAI usando API key y modelos chat/reasoner.',
100
+ docsUrl: 'https://api-docs.deepseek.com/',
101
+ docsVerifiedAt: '2026-03-31',
102
+ baseUrl: 'https://api.deepseek.com',
103
+ defaultModelId: 'deepseek-chat',
104
+ defaultAuthMethodId: 'token',
105
+ defaultApiKeyEnvVar: 'DEEPSEEK_API_KEY',
106
+ models: [
107
+ {
108
+ id: 'deepseek-chat',
109
+ name: 'DeepSeek Chat',
110
+ category: 'General',
111
+ contextWindow: '128K',
112
+ summary: 'Modo no razonador de DeepSeek V3.2, apto como opcion base para Claude Code.',
113
+ sortOrder: 1,
114
+ isDefault: 1
115
+ },
116
+ {
117
+ id: 'deepseek-reasoner',
118
+ name: 'DeepSeek Reasoner',
119
+ category: 'Reasoning',
120
+ contextWindow: '128K',
121
+ summary: 'Modo razonador de DeepSeek V3.2 con soporte de Tool Calls segun la documentacion oficial.',
122
+ sortOrder: 2,
123
+ isDefault: 0
124
+ }
125
+ ],
126
+ authMethods: [
127
+ {
128
+ id: 'token',
129
+ name: 'Token',
130
+ description: 'Conexion por API key contra el endpoint Anthropic oficial del proveedor.',
131
+ credentialKind: 'env_var',
132
+ sortOrder: 1,
133
+ isDefault: 1
134
+ }
135
+ ]
136
+ },
137
+ {
138
+ id: 'qwen',
139
+ name: 'Qwen',
140
+ vendor: 'Qwen',
141
+ description: 'Qwen Code con OAuth propio de qwen.ai y modo token compatible con OpenAI.',
142
+ docsUrl: 'https://github.com/QwenLM/qwen-code',
143
+ docsVerifiedAt: '2026-03-31',
144
+ baseUrl: 'https://dashscope.aliyuncs.com/compatible-mode/v1',
145
+ defaultModelId: 'qwen3-coder-plus',
146
+ defaultAuthMethodId: 'token',
147
+ defaultApiKeyEnvVar: 'DASHSCOPE_API_KEY',
148
+ models: [
149
+ {
150
+ id: 'qwen3-coder-plus',
151
+ name: 'Qwen Coder',
152
+ category: 'Coding',
153
+ contextWindow: 'Auto',
154
+ summary: 'Modelo fijo para esta primera version, siguiendo el flujo de Qwen Code.',
155
+ sortOrder: 1,
156
+ isDefault: 1
157
+ }
158
+ ],
159
+ authMethods: [
160
+ {
161
+ id: 'token',
162
+ name: 'Token',
163
+ description: 'Conexion por API key contra el endpoint compatible con OpenAI del proveedor.',
164
+ credentialKind: 'env_var',
165
+ sortOrder: 1,
166
+ isDefault: 1
167
+ },
168
+ {
169
+ id: 'oauth',
170
+ name: 'OAuth',
171
+ description: 'Login de Qwen Code mediante device flow en qwen.ai.',
172
+ credentialKind: 'oauth',
173
+ sortOrder: 2,
174
+ isDefault: 0
175
+ }
176
+ ],
177
+ oauth: {
178
+ authorizeUrl: 'https://chat.qwen.ai/api/v1/oauth2/device/code',
179
+ tokenUrl: 'https://chat.qwen.ai/api/v1/oauth2/token',
180
+ callbackUrl: 'https://chat.qwen.ai/auth',
181
+ accessType: 'device_code',
182
+ scope: 'openid profile email model.completion',
183
+ clientId: 'f0304373b74a44d2b584a3fb70ca9e56',
184
+ appSecretId: ''
185
+ }
186
+ }
187
+ ];
188
+
189
+ function seedCatalog(db) {
190
+ const insertProvider = db.prepare(`
191
+ INSERT INTO providers (
192
+ id, name, vendor, description, docs_url, docs_verified_at, base_url,
193
+ default_model_id, default_auth_method_id, default_api_key_env_var
194
+ )
195
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
196
+ ON CONFLICT(id) DO UPDATE SET
197
+ name = excluded.name,
198
+ vendor = excluded.vendor,
199
+ description = excluded.description,
200
+ docs_url = excluded.docs_url,
201
+ docs_verified_at = excluded.docs_verified_at,
202
+ base_url = excluded.base_url,
203
+ default_model_id = excluded.default_model_id,
204
+ default_auth_method_id = excluded.default_auth_method_id,
205
+ default_api_key_env_var = excluded.default_api_key_env_var
206
+ `);
207
+
208
+ const insertModel = db.prepare(`
209
+ INSERT INTO models (
210
+ id, provider_id, name, category, context_window, summary, sort_order, is_default
211
+ )
212
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
213
+ ON CONFLICT(id) DO UPDATE SET
214
+ provider_id = excluded.provider_id,
215
+ name = excluded.name,
216
+ category = excluded.category,
217
+ context_window = excluded.context_window,
218
+ summary = excluded.summary,
219
+ sort_order = excluded.sort_order,
220
+ is_default = excluded.is_default
221
+ `);
222
+
223
+ const insertAuthMethod = db.prepare(`
224
+ INSERT INTO auth_methods (id, name, description, credential_kind)
225
+ VALUES (?, ?, ?, ?)
226
+ ON CONFLICT(id) DO UPDATE SET
227
+ name = excluded.name,
228
+ description = excluded.description,
229
+ credential_kind = excluded.credential_kind
230
+ `);
231
+
232
+ const insertProviderAuthMethod = db.prepare(`
233
+ INSERT INTO provider_auth_methods (provider_id, auth_method_id, sort_order, is_default)
234
+ VALUES (?, ?, ?, ?)
235
+ ON CONFLICT(provider_id, auth_method_id) DO UPDATE SET
236
+ sort_order = excluded.sort_order,
237
+ is_default = excluded.is_default
238
+ `);
239
+
240
+ const insertOAuthConfig = db.prepare(`
241
+ INSERT INTO provider_oauth_configs (
242
+ provider_id, authorize_url, token_url, callback_url, access_type, scope, client_id, app_secret_id
243
+ )
244
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
245
+ ON CONFLICT(provider_id) DO UPDATE SET
246
+ authorize_url = excluded.authorize_url,
247
+ token_url = excluded.token_url,
248
+ callback_url = excluded.callback_url,
249
+ access_type = excluded.access_type,
250
+ scope = excluded.scope,
251
+ client_id = excluded.client_id,
252
+ app_secret_id = excluded.app_secret_id
253
+ `);
254
+
255
+ db.exec('BEGIN');
256
+
257
+ try {
258
+ for (const seedProvider of seedProviders) {
259
+ db.prepare('DELETE FROM models WHERE provider_id = ?').run(seedProvider.id);
260
+ db.prepare('DELETE FROM provider_auth_methods WHERE provider_id = ?').run(seedProvider.id);
261
+
262
+ insertProvider.run(
263
+ seedProvider.id,
264
+ seedProvider.name,
265
+ seedProvider.vendor,
266
+ seedProvider.description,
267
+ seedProvider.docsUrl,
268
+ seedProvider.docsVerifiedAt,
269
+ seedProvider.baseUrl,
270
+ seedProvider.defaultModelId,
271
+ seedProvider.defaultAuthMethodId,
272
+ seedProvider.defaultApiKeyEnvVar
273
+ );
274
+
275
+ for (const model of seedProvider.models) {
276
+ insertModel.run(
277
+ model.id,
278
+ seedProvider.id,
279
+ model.name,
280
+ model.category,
281
+ model.contextWindow,
282
+ model.summary,
283
+ model.sortOrder,
284
+ model.isDefault
285
+ );
286
+ }
287
+
288
+ for (const authMethod of seedProvider.authMethods) {
289
+ insertAuthMethod.run(
290
+ authMethod.id,
291
+ authMethod.name,
292
+ authMethod.description,
293
+ authMethod.credentialKind
294
+ );
295
+
296
+ insertProviderAuthMethod.run(
297
+ seedProvider.id,
298
+ authMethod.id,
299
+ authMethod.sortOrder,
300
+ authMethod.isDefault
301
+ );
302
+ }
303
+
304
+ if (seedProvider.oauth) {
305
+ insertOAuthConfig.run(
306
+ seedProvider.id,
307
+ seedProvider.oauth.authorizeUrl,
308
+ seedProvider.oauth.tokenUrl,
309
+ seedProvider.oauth.callbackUrl,
310
+ seedProvider.oauth.accessType,
311
+ seedProvider.oauth.scope,
312
+ seedProvider.oauth.clientId,
313
+ seedProvider.oauth.appSecretId
314
+ );
315
+ } else {
316
+ db.prepare('DELETE FROM provider_oauth_configs WHERE provider_id = ?').run(seedProvider.id);
317
+ }
318
+ }
319
+
320
+ db.exec('COMMIT');
321
+ } catch (error) {
322
+ db.exec('ROLLBACK');
323
+ throw error;
324
+ }
325
+ }
326
+
327
+ function mapProviderRow(row) {
328
+ return {
329
+ id: row.id,
330
+ name: row.name,
331
+ vendor: row.vendor,
332
+ description: row.description,
333
+ docsUrl: row.docs_url,
334
+ docsVerifiedAt: row.docs_verified_at,
335
+ baseUrl: row.base_url,
336
+ defaultModelId: row.default_model_id,
337
+ defaultAuthMethodId: row.default_auth_method_id,
338
+ defaultApiKeyEnvVar: row.default_api_key_env_var,
339
+ modelCount: Number(row.model_count ?? 0),
340
+ authCount: Number(row.auth_count ?? 0)
341
+ };
342
+ }
343
+
344
+ function mapModelRow(row) {
345
+ return {
346
+ id: row.id,
347
+ providerId: row.provider_id,
348
+ name: row.name,
349
+ category: row.category,
350
+ contextWindow: row.context_window,
351
+ summary: row.summary,
352
+ sortOrder: Number(row.sort_order),
353
+ isDefault: Boolean(row.is_default)
354
+ };
355
+ }
356
+
357
+ function mapAuthRow(row) {
358
+ return {
359
+ id: row.id,
360
+ name: row.name,
361
+ description: row.description,
362
+ credentialKind: row.credential_kind,
363
+ sortOrder: Number(row.sort_order),
364
+ isDefault: Boolean(row.is_default)
365
+ };
366
+ }
367
+
368
+ function mapOAuthRow(row) {
369
+ return {
370
+ providerId: row.provider_id,
371
+ deviceCodeUrl: row.authorize_url,
372
+ tokenUrl: row.token_url,
373
+ browserAuthUrl: row.callback_url,
374
+ flowType: row.access_type,
375
+ scope: row.scope ?? '',
376
+ clientId: row.client_id ?? '',
377
+ appSecretId: row.app_secret_id ?? ''
378
+ };
379
+ }
380
+
381
+ export function createCatalogStore({ filename = defaultCatalogDbPath } = {}) {
382
+ if (filename !== ':memory:') {
383
+ fs.mkdirSync(path.dirname(filename), { recursive: true });
384
+ }
385
+
386
+ const db = new DatabaseSync(filename);
387
+ db.exec(schemaSql);
388
+ seedCatalog(db);
389
+
390
+ const providerListStatement = db.prepare(`
391
+ SELECT
392
+ p.*,
393
+ COUNT(DISTINCT m.id) AS model_count,
394
+ COUNT(DISTINCT pam.auth_method_id) AS auth_count
395
+ FROM providers p
396
+ LEFT JOIN models m ON m.provider_id = p.id
397
+ LEFT JOIN provider_auth_methods pam ON pam.provider_id = p.id
398
+ GROUP BY p.id
399
+ ORDER BY p.name ASC
400
+ `);
401
+
402
+ const providerStatement = db.prepare(`
403
+ SELECT *
404
+ FROM providers
405
+ WHERE id = ?
406
+ `);
407
+
408
+ const modelListStatement = db.prepare(`
409
+ SELECT *
410
+ FROM models
411
+ WHERE provider_id = ?
412
+ ORDER BY is_default DESC, sort_order ASC, name ASC
413
+ `);
414
+
415
+ const authListStatement = db.prepare(`
416
+ SELECT am.*, pam.sort_order, pam.is_default
417
+ FROM provider_auth_methods pam
418
+ JOIN auth_methods am ON am.id = pam.auth_method_id
419
+ WHERE pam.provider_id = ?
420
+ ORDER BY pam.is_default DESC, pam.sort_order ASC, am.name ASC
421
+ `);
422
+
423
+ const oauthConfigStatement = db.prepare(`
424
+ SELECT *
425
+ FROM provider_oauth_configs
426
+ WHERE provider_id = ?
427
+ `);
428
+
429
+ return {
430
+ filename,
431
+ close() {
432
+ db.close();
433
+ },
434
+ getProviders() {
435
+ return providerListStatement.all().map(mapProviderRow);
436
+ },
437
+ getProviderById(providerId) {
438
+ const row = providerStatement.get(providerId);
439
+ return row ? mapProviderRow(row) : null;
440
+ },
441
+ getModelsByProviderId(providerId) {
442
+ return modelListStatement.all(providerId).map(mapModelRow);
443
+ },
444
+ getAuthMethodsByProviderId(providerId) {
445
+ return authListStatement.all(providerId).map(mapAuthRow);
446
+ },
447
+ getOAuthConfigByProviderId(providerId) {
448
+ const row = oauthConfigStatement.get(providerId);
449
+ return row ? mapOAuthRow(row) : null;
450
+ },
451
+ getProviderCatalog(providerId) {
452
+ const provider = this.getProviderById(providerId);
453
+
454
+ if (!provider) {
455
+ return null;
456
+ }
457
+
458
+ const models = this.getModelsByProviderId(providerId);
459
+ const authMethods = this.getAuthMethodsByProviderId(providerId);
460
+ const oauth = this.getOAuthConfigByProviderId(providerId);
461
+
462
+ return {
463
+ ...provider,
464
+ modelCount: models.length,
465
+ authCount: authMethods.length,
466
+ models,
467
+ authMethods,
468
+ oauth
469
+ };
470
+ }
471
+ };
472
+ }
473
+
474
+ let catalogStore;
475
+
476
+ export function getCatalogStore() {
477
+ if (!catalogStore) {
478
+ catalogStore = createCatalogStore();
479
+ }
480
+
481
+ return catalogStore;
482
+ }
@@ -0,0 +1,4 @@
1
+ export const gatewayHost = '127.0.0.1';
2
+ export const gatewayPort = 4310;
3
+ export const gatewayBasePath = '/anthropic';
4
+ export const gatewayBaseUrl = `http://${gatewayHost}:${gatewayPort}${gatewayBasePath}`;