claude-ai-switcher 1.1.4

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.
Files changed (83) hide show
  1. package/AGENTS.md +265 -0
  2. package/ARCHITECTURE.md +162 -0
  3. package/CLAUDE.md +267 -0
  4. package/LICENSE +21 -0
  5. package/QWEN.md +429 -0
  6. package/README.md +833 -0
  7. package/dist/clients/claude-code.d.ts +92 -0
  8. package/dist/clients/claude-code.d.ts.map +1 -0
  9. package/dist/clients/claude-code.js +312 -0
  10. package/dist/clients/claude-code.js.map +1 -0
  11. package/dist/clients/opencode.d.ts +71 -0
  12. package/dist/clients/opencode.d.ts.map +1 -0
  13. package/dist/clients/opencode.js +604 -0
  14. package/dist/clients/opencode.js.map +1 -0
  15. package/dist/config.d.ts +37 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +122 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/display.d.ts +51 -0
  20. package/dist/display.d.ts.map +1 -0
  21. package/dist/display.js +118 -0
  22. package/dist/display.js.map +1 -0
  23. package/dist/hooks/index.d.ts +60 -0
  24. package/dist/hooks/index.d.ts.map +1 -0
  25. package/dist/hooks/index.js +223 -0
  26. package/dist/hooks/index.js.map +1 -0
  27. package/dist/hooks/token-tracker.js +280 -0
  28. package/dist/hooks/visual-enhancements.js +364 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +1091 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/models.d.ts +34 -0
  34. package/dist/models.d.ts.map +1 -0
  35. package/dist/models.js +343 -0
  36. package/dist/models.js.map +1 -0
  37. package/dist/providers/alibaba.d.ts +25 -0
  38. package/dist/providers/alibaba.d.ts.map +1 -0
  39. package/dist/providers/alibaba.js +37 -0
  40. package/dist/providers/alibaba.js.map +1 -0
  41. package/dist/providers/anthropic.d.ts +14 -0
  42. package/dist/providers/anthropic.d.ts.map +1 -0
  43. package/dist/providers/anthropic.js +19 -0
  44. package/dist/providers/anthropic.js.map +1 -0
  45. package/dist/providers/gemini.d.ts +44 -0
  46. package/dist/providers/gemini.d.ts.map +1 -0
  47. package/dist/providers/gemini.js +156 -0
  48. package/dist/providers/gemini.js.map +1 -0
  49. package/dist/providers/glm.d.ts +25 -0
  50. package/dist/providers/glm.d.ts.map +1 -0
  51. package/dist/providers/glm.js +89 -0
  52. package/dist/providers/glm.js.map +1 -0
  53. package/dist/providers/ollama.d.ts +48 -0
  54. package/dist/providers/ollama.d.ts.map +1 -0
  55. package/dist/providers/ollama.js +174 -0
  56. package/dist/providers/ollama.js.map +1 -0
  57. package/dist/providers/openrouter.d.ts +24 -0
  58. package/dist/providers/openrouter.d.ts.map +1 -0
  59. package/dist/providers/openrouter.js +36 -0
  60. package/dist/providers/openrouter.js.map +1 -0
  61. package/dist/verify.d.ts +24 -0
  62. package/dist/verify.d.ts.map +1 -0
  63. package/dist/verify.js +262 -0
  64. package/dist/verify.js.map +1 -0
  65. package/package.json +57 -0
  66. package/scripts/copy-hooks.js +15 -0
  67. package/src/clients/claude-code.ts +340 -0
  68. package/src/clients/opencode.ts +618 -0
  69. package/src/config.ts +101 -0
  70. package/src/display.ts +151 -0
  71. package/src/hooks/index.ts +208 -0
  72. package/src/hooks/token-tracker.js +280 -0
  73. package/src/hooks/visual-enhancements.js +364 -0
  74. package/src/index.ts +1263 -0
  75. package/src/models.ts +366 -0
  76. package/src/providers/alibaba.ts +43 -0
  77. package/src/providers/anthropic.ts +23 -0
  78. package/src/providers/gemini.ts +136 -0
  79. package/src/providers/glm.ts +60 -0
  80. package/src/providers/ollama.ts +146 -0
  81. package/src/providers/openrouter.ts +42 -0
  82. package/src/verify.ts +258 -0
  83. package/tsconfig.json +19 -0
@@ -0,0 +1,618 @@
1
+ /**
2
+ * OpenCode Client Handler
3
+ *
4
+ * Manages ~/.config/opencode/opencode.json for OpenCode
5
+ */
6
+
7
+ import * as fs from "fs-extra";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+
11
+ export interface OpenCodeSettings {
12
+ $schema?: string;
13
+ provider?: Record<string, any>;
14
+ mcpServers?: Record<string, any>;
15
+ agents?: Record<string, any>;
16
+ [key: string]: any;
17
+ }
18
+
19
+ /**
20
+ * Get the OpenCode config path
21
+ * Priority: ~/.config/opencode/opencode.json
22
+ */
23
+ export function getOpenCodeConfigPath(): string {
24
+ return path.join(os.homedir(), ".config", "opencode", "opencode.json");
25
+ }
26
+
27
+ /**
28
+ * Check if OpenCode settings file exists
29
+ */
30
+ export function opencodeSettingsExists(): boolean {
31
+ const configPath = getOpenCodeConfigPath();
32
+ return fs.existsSync(configPath);
33
+ }
34
+
35
+ /**
36
+ * Read current OpenCode settings
37
+ */
38
+ export async function readOpenCodeSettings(): Promise<OpenCodeSettings> {
39
+ const configPath = getOpenCodeConfigPath();
40
+
41
+ if (!fs.existsSync(configPath)) {
42
+ return {};
43
+ }
44
+
45
+ const content = await fs.readFile(configPath, "utf-8");
46
+ return JSON.parse(content);
47
+ }
48
+
49
+ /**
50
+ * Write OpenCode settings with backup
51
+ */
52
+ export async function writeOpenCodeSettings(settings: OpenCodeSettings): Promise<void> {
53
+ const configPath = getOpenCodeConfigPath();
54
+ const configDir = path.dirname(configPath);
55
+
56
+ // Ensure directory exists
57
+ await fs.ensureDir(configDir);
58
+
59
+ // Backup existing settings if they exist
60
+ if (opencodeSettingsExists()) {
61
+ const backupPath = `${configPath}.backup.${Date.now()}`;
62
+ await fs.copyFile(configPath, backupPath);
63
+ }
64
+
65
+ // Write new settings
66
+ await fs.writeFile(configPath, JSON.stringify(settings, null, 2), "utf-8");
67
+ }
68
+
69
+ /**
70
+ * Configure OpenCode for Alibaba Coding Plan
71
+ * Writes the full provider configuration with all models
72
+ */
73
+ export async function configureAlibaba(apiKey: string): Promise<void> {
74
+ const settings = await readOpenCodeSettings();
75
+
76
+ // Set schema
77
+ settings.$schema = "https://opencode.ai/config.json";
78
+
79
+ // Configure bailian-coding-plan provider with all models
80
+ settings.provider = settings.provider || {};
81
+ settings.provider["bailian-coding-plan"] = {
82
+ npm: "@ai-sdk/anthropic",
83
+ name: "Model Studio Coding Plan",
84
+ options: {
85
+ baseURL: "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic/v1",
86
+ apiKey: apiKey
87
+ },
88
+ models: {
89
+ "qwen3.7-plus": {
90
+ name: "Qwen3.7 Plus",
91
+ modalities: {
92
+ input: ["text", "image"],
93
+ output: ["text"]
94
+ },
95
+ options: {
96
+ thinking: {
97
+ type: "enabled",
98
+ budgetTokens: 8192
99
+ }
100
+ },
101
+ limit: {
102
+ context: 1000000,
103
+ output: 65536
104
+ }
105
+ },
106
+ "qwen3.6-plus": {
107
+ name: "Qwen3.6 Plus",
108
+ modalities: {
109
+ input: ["text", "image"],
110
+ output: ["text"]
111
+ },
112
+ options: {
113
+ thinking: {
114
+ type: "enabled",
115
+ budgetTokens: 8192
116
+ }
117
+ },
118
+ limit: {
119
+ context: 1000000,
120
+ output: 65536
121
+ }
122
+ },
123
+ "qwen3-max-2026-01-23": {
124
+ name: "Qwen3 Max 2026-01-23",
125
+ modalities: {
126
+ input: ["text"],
127
+ output: ["text"]
128
+ },
129
+ limit: {
130
+ context: 262144,
131
+ output: 32768
132
+ }
133
+ },
134
+ "qwen3-coder-next": {
135
+ name: "Qwen3 Coder Next",
136
+ modalities: {
137
+ input: ["text"],
138
+ output: ["text"]
139
+ },
140
+ limit: {
141
+ context: 262144,
142
+ output: 65536
143
+ }
144
+ },
145
+ "qwen3-coder-plus": {
146
+ name: "Qwen3 Coder Plus",
147
+ modalities: {
148
+ input: ["text"],
149
+ output: ["text"]
150
+ },
151
+ limit: {
152
+ context: 1000000,
153
+ output: 65536
154
+ }
155
+ },
156
+ "MiniMax-M2.5": {
157
+ name: "MiniMax M2.5",
158
+ modalities: {
159
+ input: ["text"],
160
+ output: ["text"]
161
+ },
162
+ options: {
163
+ thinking: {
164
+ type: "enabled",
165
+ budgetTokens: 8192
166
+ }
167
+ },
168
+ limit: {
169
+ context: 200000,
170
+ output: 24576
171
+ }
172
+ },
173
+ "glm-5": {
174
+ name: "GLM-5",
175
+ modalities: {
176
+ input: ["text"],
177
+ output: ["text"]
178
+ },
179
+ options: {
180
+ thinking: {
181
+ type: "enabled",
182
+ budgetTokens: 8192
183
+ }
184
+ },
185
+ limit: {
186
+ context: 200000,
187
+ output: 16384
188
+ }
189
+ },
190
+ "glm-4.7": {
191
+ name: "GLM-4.7",
192
+ modalities: {
193
+ input: ["text"],
194
+ output: ["text"]
195
+ },
196
+ options: {
197
+ thinking: {
198
+ type: "enabled",
199
+ budgetTokens: 8192
200
+ }
201
+ },
202
+ limit: {
203
+ context: 256000,
204
+ output: 16384
205
+ }
206
+ },
207
+ "kimi-k2.5": {
208
+ name: "Kimi K2.5",
209
+ modalities: {
210
+ input: ["text", "image"],
211
+ output: ["text"]
212
+ },
213
+ options: {
214
+ thinking: {
215
+ type: "enabled",
216
+ budgetTokens: 8192
217
+ }
218
+ },
219
+ limit: {
220
+ context: 200000,
221
+ output: 32768
222
+ }
223
+ }
224
+ }
225
+ };
226
+
227
+ await writeOpenCodeSettings(settings);
228
+ }
229
+
230
+ /**
231
+ * Configure OpenCode for Anthropic (default)
232
+ * Removes bailian-coding-plan provider to use native Anthropic
233
+ */
234
+ export async function configureAnthropic(): Promise<void> {
235
+ const settings = await readOpenCodeSettings();
236
+
237
+ // Remove bailian-coding-plan provider
238
+ if (settings.provider?.["bailian-coding-plan"]) {
239
+ delete settings.provider["bailian-coding-plan"];
240
+ }
241
+
242
+ // Remove openrouter provider
243
+ if (settings.provider?.["openrouter"]) {
244
+ delete settings.provider["openrouter"];
245
+ }
246
+
247
+ // Remove ollama provider
248
+ if (settings.provider?.["ollama"]) {
249
+ delete settings.provider["ollama"];
250
+ }
251
+
252
+ // Remove gemini provider
253
+ if (settings.provider?.["gemini"]) {
254
+ delete settings.provider["gemini"];
255
+ }
256
+
257
+ // Remove glm provider
258
+ if (settings.provider?.["glm"]) {
259
+ delete settings.provider["glm"];
260
+ }
261
+
262
+ // Clean up empty provider object
263
+ if (settings.provider && Object.keys(settings.provider).length === 0) {
264
+ delete settings.provider;
265
+ }
266
+
267
+ await writeOpenCodeSettings(settings);
268
+ }
269
+
270
+ /**
271
+ * Configure OpenCode for GLM/Z.AI
272
+ * Auth is managed by coding-helper — reads baseURL and apiKey from Claude settings
273
+ */
274
+ export async function configureGLM(baseURL: string, apiKey: string): Promise<void> {
275
+ const settings = await readOpenCodeSettings();
276
+
277
+ settings.$schema = "https://opencode.ai/config.json";
278
+
279
+ settings.provider = settings.provider || {};
280
+ settings.provider["glm"] = {
281
+ npm: "@ai-sdk/anthropic",
282
+ name: "GLM/Z.AI",
283
+ options: {
284
+ baseURL,
285
+ apiKey
286
+ },
287
+ models: {
288
+ "glm-5.1": {
289
+ name: "GLM-5.1",
290
+ modalities: {
291
+ input: ["text"],
292
+ output: ["text"]
293
+ },
294
+ options: {
295
+ thinking: {
296
+ type: "enabled",
297
+ budgetTokens: 8192
298
+ }
299
+ },
300
+ limit: {
301
+ context: 200000,
302
+ output: 16384
303
+ }
304
+ },
305
+ "glm-5v-turbo": {
306
+ name: "GLM-5V-Turbo",
307
+ modalities: {
308
+ input: ["text", "image"],
309
+ output: ["text"]
310
+ },
311
+ options: {
312
+ thinking: {
313
+ type: "enabled",
314
+ budgetTokens: 8192
315
+ }
316
+ },
317
+ limit: {
318
+ context: 200000,
319
+ output: 16384
320
+ }
321
+ },
322
+ "glm-5-turbo": {
323
+ name: "GLM-5-Turbo",
324
+ modalities: {
325
+ input: ["text"],
326
+ output: ["text"]
327
+ },
328
+ options: {
329
+ thinking: {
330
+ type: "enabled",
331
+ budgetTokens: 8192
332
+ }
333
+ },
334
+ limit: {
335
+ context: 200000,
336
+ output: 16384
337
+ }
338
+ },
339
+ "glm-4.7": {
340
+ name: "GLM-4.7",
341
+ modalities: {
342
+ input: ["text"],
343
+ output: ["text"]
344
+ },
345
+ options: {
346
+ thinking: {
347
+ type: "enabled",
348
+ budgetTokens: 8192
349
+ }
350
+ },
351
+ limit: {
352
+ context: 256000,
353
+ output: 16384
354
+ }
355
+ },
356
+ "glm-4.7-flash": {
357
+ name: "GLM-4.7-Flash",
358
+ modalities: {
359
+ input: ["text"],
360
+ output: ["text"]
361
+ },
362
+ limit: {
363
+ context: 256000,
364
+ output: 16384
365
+ }
366
+ }
367
+ }
368
+ };
369
+
370
+ await writeOpenCodeSettings(settings);
371
+ }
372
+
373
+ /**
374
+ * Configure OpenCode for OpenRouter
375
+ * Writes the openrouter provider with available models
376
+ */
377
+ export async function configureOpenRouter(apiKey: string): Promise<void> {
378
+ const settings = await readOpenCodeSettings();
379
+
380
+ // Set schema
381
+ settings.$schema = "https://opencode.ai/config.json";
382
+
383
+ // Configure openrouter provider with models
384
+ settings.provider = settings.provider || {};
385
+ settings.provider["openrouter"] = {
386
+ npm: "@ai-sdk/openai",
387
+ name: "OpenRouter",
388
+ options: {
389
+ baseURL: "https://openrouter.ai/api/v1",
390
+ apiKey: apiKey
391
+ },
392
+ models: {
393
+ "qwen/qwen3.6-plus:free": {
394
+ name: "Qwen3.6 Plus (Free)",
395
+ modalities: {
396
+ input: ["text"],
397
+ output: ["text"]
398
+ },
399
+ limit: {
400
+ context: 131072,
401
+ output: 32768
402
+ }
403
+ },
404
+ "openrouter/free": {
405
+ name: "OpenRouter Free",
406
+ modalities: {
407
+ input: ["text"],
408
+ output: ["text"]
409
+ },
410
+ limit: {
411
+ context: 131072,
412
+ output: 32768
413
+ }
414
+ }
415
+ }
416
+ };
417
+
418
+ await writeOpenCodeSettings(settings);
419
+ }
420
+
421
+ /**
422
+ * Configure OpenCode for Ollama (via LiteLLM proxy on port 4000)
423
+ */
424
+ export async function configureOllama(): Promise<void> {
425
+ const settings = await readOpenCodeSettings();
426
+
427
+ settings.$schema = "https://opencode.ai/config.json";
428
+
429
+ settings.provider = settings.provider || {};
430
+ settings.provider["ollama"] = {
431
+ npm: "@ai-sdk/openai",
432
+ name: "Ollama (Local)",
433
+ options: {
434
+ baseURL: "http://localhost:4000/v1",
435
+ apiKey: "ollama"
436
+ },
437
+ models: {
438
+ "deepseek-r1:latest": {
439
+ name: "DeepSeek R1",
440
+ modalities: {
441
+ input: ["text"],
442
+ output: ["text"]
443
+ },
444
+ limit: {
445
+ context: 128000,
446
+ output: 32768
447
+ }
448
+ },
449
+ "qwen2.5-coder:latest": {
450
+ name: "Qwen 2.5 Coder",
451
+ modalities: {
452
+ input: ["text"],
453
+ output: ["text"]
454
+ },
455
+ limit: {
456
+ context: 128000,
457
+ output: 32768
458
+ }
459
+ },
460
+ "llama3.1:latest": {
461
+ name: "Llama 3.1",
462
+ modalities: {
463
+ input: ["text", "image"],
464
+ output: ["text"]
465
+ },
466
+ limit: {
467
+ context: 128000,
468
+ output: 32768
469
+ }
470
+ },
471
+ "codellama:latest": {
472
+ name: "Code Llama",
473
+ modalities: {
474
+ input: ["text"],
475
+ output: ["text"]
476
+ },
477
+ limit: {
478
+ context: 100000,
479
+ output: 32768
480
+ }
481
+ }
482
+ }
483
+ };
484
+
485
+ await writeOpenCodeSettings(settings);
486
+ }
487
+
488
+ /**
489
+ * Configure OpenCode for Gemini (via LiteLLM proxy on port 4001)
490
+ */
491
+ export async function configureGemini(apiKey: string): Promise<void> {
492
+ const settings = await readOpenCodeSettings();
493
+
494
+ settings.$schema = "https://opencode.ai/config.json";
495
+
496
+ settings.provider = settings.provider || {};
497
+ settings.provider["gemini"] = {
498
+ npm: "@ai-sdk/openai",
499
+ name: "Gemini (Google)",
500
+ options: {
501
+ baseURL: "http://localhost:4001/v1",
502
+ apiKey: apiKey
503
+ },
504
+ models: {
505
+ "gemini-2.5-pro": {
506
+ name: "Gemini 2.5 Pro",
507
+ modalities: {
508
+ input: ["text", "image"],
509
+ output: ["text"]
510
+ },
511
+ limit: {
512
+ context: 1000000,
513
+ output: 65536
514
+ }
515
+ },
516
+ "gemini-2.5-flash": {
517
+ name: "Gemini 2.5 Flash",
518
+ modalities: {
519
+ input: ["text", "image"],
520
+ output: ["text"]
521
+ },
522
+ limit: {
523
+ context: 1000000,
524
+ output: 65536
525
+ }
526
+ },
527
+ "gemini-2.5-flash-lite": {
528
+ name: "Gemini 2.5 Flash Lite",
529
+ modalities: {
530
+ input: ["text"],
531
+ output: ["text"]
532
+ },
533
+ limit: {
534
+ context: 1000000,
535
+ output: 65536
536
+ }
537
+ }
538
+ }
539
+ };
540
+
541
+ await writeOpenCodeSettings(settings);
542
+ }
543
+
544
+ /**
545
+ * Remove a specific provider from OpenCode settings
546
+ * Only removes the named provider, preserving others
547
+ */
548
+ export async function removeProvider(providerKey: string): Promise<void> {
549
+ const settings = await readOpenCodeSettings();
550
+
551
+ if (settings.provider?.[providerKey]) {
552
+ delete settings.provider[providerKey];
553
+ }
554
+
555
+ // Clean up empty provider object
556
+ if (settings.provider && Object.keys(settings.provider).length === 0) {
557
+ delete settings.provider;
558
+ }
559
+
560
+ await writeOpenCodeSettings(settings);
561
+ }
562
+
563
+ /**
564
+ * Get current provider from OpenCode settings
565
+ */
566
+ export async function getCurrentProvider(): Promise<{
567
+ provider: string;
568
+ model?: string;
569
+ endpoint?: string;
570
+ } | null> {
571
+ if (!opencodeSettingsExists()) {
572
+ return { provider: "anthropic" };
573
+ }
574
+
575
+ const settings = await readOpenCodeSettings();
576
+
577
+ // Check for bailian-coding-plan (Alibaba) configuration
578
+ if (settings.provider?.["bailian-coding-plan"]) {
579
+ return {
580
+ provider: "alibaba",
581
+ endpoint: settings.provider["bailian-coding-plan"].options?.baseURL
582
+ };
583
+ }
584
+
585
+ // Check for openrouter configuration
586
+ if (settings.provider?.["openrouter"]) {
587
+ return {
588
+ provider: "openrouter",
589
+ endpoint: "https://openrouter.ai/api/v1"
590
+ };
591
+ }
592
+
593
+ // Check for ollama configuration
594
+ if (settings.provider?.["ollama"]) {
595
+ return {
596
+ provider: "ollama",
597
+ endpoint: "http://localhost:4000/v1"
598
+ };
599
+ }
600
+
601
+ // Check for gemini configuration
602
+ if (settings.provider?.["gemini"]) {
603
+ return {
604
+ provider: "gemini",
605
+ endpoint: "http://localhost:4001/v1"
606
+ };
607
+ }
608
+
609
+ // Check for glm configuration
610
+ if (settings.provider?.["glm"]) {
611
+ return {
612
+ provider: "glm",
613
+ endpoint: settings.provider["glm"].options?.baseURL
614
+ };
615
+ }
616
+
617
+ return { provider: "anthropic" };
618
+ }
package/src/config.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Configuration Manager
3
+ *
4
+ * Manages API keys and settings in ~/.claude-ai-switcher/config.json
5
+ */
6
+
7
+ import * as fs from "fs-extra";
8
+ import * as path from "path";
9
+ import * as os from "os";
10
+
11
+ const CONFIG_DIR = path.join(os.homedir(), ".claude-ai-switcher");
12
+ const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
13
+
14
+ export interface UserConfig {
15
+ alibabaApiKey?: string;
16
+ openrouterApiKey?: string;
17
+ geminiApiKey?: string;
18
+ defaultProvider?: string;
19
+ defaultModel?: string;
20
+ }
21
+
22
+ /**
23
+ * Ensure config directory exists
24
+ */
25
+ async function ensureConfigDir(): Promise<void> {
26
+ await fs.ensureDir(CONFIG_DIR);
27
+ }
28
+
29
+ /**
30
+ * Read user configuration
31
+ */
32
+ export async function readConfig(): Promise<UserConfig> {
33
+ if (!fs.existsSync(CONFIG_FILE)) {
34
+ return {};
35
+ }
36
+
37
+ const content = await fs.readFile(CONFIG_FILE, "utf-8");
38
+ return JSON.parse(content);
39
+ }
40
+
41
+ /**
42
+ * Write user configuration
43
+ */
44
+ export async function writeConfig(config: UserConfig): Promise<void> {
45
+ await ensureConfigDir();
46
+ await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
47
+ }
48
+
49
+ /**
50
+ * Get API key for a provider
51
+ */
52
+ export async function getApiKey(provider: string): Promise<string | undefined> {
53
+ const config = await readConfig();
54
+
55
+ switch (provider) {
56
+ case "alibaba":
57
+ return config.alibabaApiKey;
58
+ case "openrouter":
59
+ return config.openrouterApiKey;
60
+ case "gemini":
61
+ return config.geminiApiKey;
62
+ default:
63
+ return undefined;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Set API key for a provider
69
+ */
70
+ export async function setApiKey(provider: string, apiKey: string): Promise<void> {
71
+ const config = await readConfig();
72
+
73
+ switch (provider) {
74
+ case "alibaba":
75
+ config.alibabaApiKey = apiKey;
76
+ break;
77
+ case "openrouter":
78
+ config.openrouterApiKey = apiKey;
79
+ break;
80
+ case "gemini":
81
+ config.geminiApiKey = apiKey;
82
+ break;
83
+ }
84
+
85
+ await writeConfig(config);
86
+ }
87
+
88
+ /**
89
+ * Check if API key is set for a provider
90
+ */
91
+ export async function hasApiKey(provider: string): Promise<boolean> {
92
+ const apiKey = await getApiKey(provider);
93
+ return !!apiKey;
94
+ }
95
+
96
+ /**
97
+ * Get config file path
98
+ */
99
+ export function getConfigPath(): string {
100
+ return CONFIG_FILE;
101
+ }