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
package/dist/index.js ADDED
@@ -0,0 +1,1091 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Claude AI Switcher
5
+ *
6
+ * Switch between AI providers (Anthropic, GLM, Alibaba Qwen) for Claude Code.
7
+ * Also provides helper commands to add/remove Alibaba Coding Plan provider for OpenCode.
8
+ */
9
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ var desc = Object.getOwnPropertyDescriptor(m, k);
12
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13
+ desc = { enumerable: true, get: function() { return m[k]; } };
14
+ }
15
+ Object.defineProperty(o, k2, desc);
16
+ }) : (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ o[k2] = m[k];
19
+ }));
20
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
22
+ }) : function(o, v) {
23
+ o["default"] = v;
24
+ });
25
+ var __importStar = (this && this.__importStar) || (function () {
26
+ var ownKeys = function(o) {
27
+ ownKeys = Object.getOwnPropertyNames || function (o) {
28
+ var ar = [];
29
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
30
+ return ar;
31
+ };
32
+ return ownKeys(o);
33
+ };
34
+ return function (mod) {
35
+ if (mod && mod.__esModule) return mod;
36
+ var result = {};
37
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
38
+ __setModuleDefault(result, mod);
39
+ return result;
40
+ };
41
+ })();
42
+ var __importDefault = (this && this.__importDefault) || function (mod) {
43
+ return (mod && mod.__esModule) ? mod : { "default": mod };
44
+ };
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ const commander_1 = require("commander");
47
+ const chalk_1 = __importDefault(require("chalk"));
48
+ const readline = __importStar(require("readline"));
49
+ const fs = __importStar(require("fs-extra"));
50
+ const path = __importStar(require("path"));
51
+ const models_1 = require("./models");
52
+ const claude_code_1 = require("./clients/claude-code");
53
+ const opencode_1 = require("./clients/opencode");
54
+ const config_1 = require("./config");
55
+ const display_1 = require("./display");
56
+ const glm_1 = require("./providers/glm");
57
+ const ollama_1 = require("./providers/ollama");
58
+ const gemini_1 = require("./providers/gemini");
59
+ const verify_1 = require("./verify");
60
+ const index_1 = require("./hooks/index");
61
+ // Read version from package.json at runtime so `claude-switch --version` never drifts.
62
+ // package.json lives outside src/rootDir, so resolve it relative to this compiled file.
63
+ const pkgVersion = fs.readJsonSync(path.join(__dirname, "..", "package.json")).version;
64
+ const program = new commander_1.Command();
65
+ program
66
+ .name("claude-switch")
67
+ .description("Switch between AI providers for Claude Code. Also provides OpenCode helper commands.")
68
+ .version(pkgVersion);
69
+ // ---------------------------------------------------------------------------
70
+ // Helpers
71
+ // ---------------------------------------------------------------------------
72
+ async function promptApiKey(provider, helpUrl) {
73
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
74
+ console.log(chalk_1.default.yellow(`\n⚠ ${provider} API Key not found`));
75
+ console.log(chalk_1.default.dim(` Get your API key from: ${helpUrl}`));
76
+ console.log();
77
+ const answer = await new Promise((resolve) => {
78
+ rl.question(`Enter your ${provider} API Key: `, resolve);
79
+ });
80
+ rl.close();
81
+ if (!answer.trim()) {
82
+ (0, display_1.displayError)("API Key is required");
83
+ process.exit(1);
84
+ }
85
+ return answer.trim();
86
+ }
87
+ function buildTierMap(defaultMap, opts) {
88
+ return {
89
+ opus: opts.opus || defaultMap.opus,
90
+ sonnet: opts.sonnet || defaultMap.sonnet,
91
+ haiku: opts.haiku || defaultMap.haiku
92
+ };
93
+ }
94
+ function displayTierMap(tierMap) {
95
+ console.log(chalk_1.default.dim(" Claude model aliases:"));
96
+ console.log(chalk_1.default.dim(` ANTHROPIC_DEFAULT_OPUS_MODEL → ${tierMap.opus}`));
97
+ console.log(chalk_1.default.dim(` ANTHROPIC_DEFAULT_SONNET_MODEL → ${tierMap.sonnet}`));
98
+ console.log(chalk_1.default.dim(` ANTHROPIC_DEFAULT_HAIKU_MODEL → ${tierMap.haiku}`));
99
+ }
100
+ function addTierOptions(cmd) {
101
+ return cmd
102
+ .option("--opus <model>", "Override opus tier model alias")
103
+ .option("--sonnet <model>", "Override sonnet tier model alias")
104
+ .option("--haiku <model>", "Override haiku tier model alias");
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Provider switch implementations (Claude Code)
108
+ // ---------------------------------------------------------------------------
109
+ async function switchAnthropic() {
110
+ await (0, claude_code_1.configureAnthropic)();
111
+ (0, display_1.displaySuccess)("Switched to Anthropic (default)");
112
+ console.log(chalk_1.default.dim(" Provider: Anthropic"));
113
+ console.log(chalk_1.default.dim(" Using native Claude models"));
114
+ console.log();
115
+ }
116
+ async function switchAlibaba(model, tierOpts) {
117
+ const selectedModel = model || "qwen3.7-plus";
118
+ let apiKey = await (0, config_1.getApiKey)("alibaba");
119
+ if (!apiKey) {
120
+ apiKey = await promptApiKey("Alibaba", "https://modelstudio.console.alibabacloud.com/");
121
+ await (0, config_1.setApiKey)("alibaba", apiKey);
122
+ }
123
+ const alibabaModels = (0, models_1.getModels)("alibaba");
124
+ const validModel = alibabaModels.find((m) => m.id === selectedModel);
125
+ if (!validModel) {
126
+ (0, display_1.displayError)(`Invalid model: ${selectedModel}`);
127
+ console.log(chalk_1.default.dim(" Valid models: ") + alibabaModels.map((m) => m.id).join(", "));
128
+ process.exit(1);
129
+ }
130
+ const tierMap = buildTierMap((0, models_1.getAlibabaTierMap)(selectedModel), tierOpts);
131
+ await (0, claude_code_1.configureAlibaba)(apiKey, selectedModel, tierMap);
132
+ console.log(chalk_1.default.green(`\n✓ Switched to: Alibaba Coding Plan`));
133
+ console.log(chalk_1.default.dim("─".repeat(60)));
134
+ console.log(` ${chalk_1.default.cyan.bold("Model:")} ${chalk_1.default.white(validModel.name)}`);
135
+ console.log(` ${chalk_1.default.cyan.bold("Context:")} ${chalk_1.default.yellow((0, models_1.formatContext)(validModel.contextWindow))}`);
136
+ console.log(` ${chalk_1.default.cyan.bold("Endpoint:")} ${chalk_1.default.dim("https://coding-intl.dashscope.aliyuncs.com/apps/anthropic")}`);
137
+ console.log(` ${chalk_1.default.cyan.bold("Capabilities:")} ${chalk_1.default.gray(validModel.capabilities.join(", "))}`);
138
+ console.log(chalk_1.default.dim(` ${validModel.description}`));
139
+ console.log();
140
+ displayTierMap(tierMap);
141
+ console.log();
142
+ }
143
+ async function switchGLM(tierOpts) {
144
+ const hasCodingHelper = await (0, glm_1.isCodingHelperInstalled)();
145
+ if (!hasCodingHelper) {
146
+ (0, display_1.displayWarning)("coding-helper not found");
147
+ console.log(chalk_1.default.dim(" Install with: npm install -g @z_ai/coding-helper"));
148
+ console.log(chalk_1.default.dim(" Then run: coding-helper auth"));
149
+ console.log();
150
+ }
151
+ const tierMap = buildTierMap(models_1.GLM_DEFAULT_TIER_MAP, tierOpts);
152
+ await (0, claude_code_1.configureGLM)(tierMap);
153
+ if (hasCodingHelper) {
154
+ const result = await (0, glm_1.reloadGLMConfig)();
155
+ if (!result.success) {
156
+ (0, display_1.displayWarning)("coding-helper reload failed, but local config updated");
157
+ }
158
+ }
159
+ (0, display_1.displaySuccess)("Switched to GLM/Z.AI");
160
+ console.log(chalk_1.default.dim(" Provider: GLM/Z.AI"));
161
+ if (hasCodingHelper)
162
+ console.log(chalk_1.default.dim(" Managed by: coding-helper"));
163
+ console.log();
164
+ displayTierMap(tierMap);
165
+ console.log();
166
+ }
167
+ async function switchOpenRouter(model, tierOpts) {
168
+ const selectedModel = model || "qwen/qwen3.6-plus:free";
169
+ let apiKey = await (0, config_1.getApiKey)("openrouter");
170
+ if (!apiKey) {
171
+ apiKey = await promptApiKey("OpenRouter", "https://openrouter.ai/settings/keys");
172
+ await (0, config_1.setApiKey)("openrouter", apiKey);
173
+ }
174
+ const openrouterModels = (0, models_1.getModels)("openrouter");
175
+ const validModel = openrouterModels.find((m) => m.id === selectedModel);
176
+ if (!validModel) {
177
+ (0, display_1.displayError)(`Invalid model: ${selectedModel}`);
178
+ console.log(chalk_1.default.dim(" Valid models: ") + openrouterModels.map((m) => m.id).join(", "));
179
+ process.exit(1);
180
+ }
181
+ const tierMap = buildTierMap(models_1.OPENROUTER_DEFAULT_TIER_MAP, tierOpts);
182
+ await (0, claude_code_1.configureOpenRouter)(apiKey, selectedModel, tierMap);
183
+ console.log(chalk_1.default.green(`\n✓ Switched to: OpenRouter`));
184
+ console.log(chalk_1.default.dim("─".repeat(60)));
185
+ console.log(` ${chalk_1.default.cyan.bold("Model:")} ${chalk_1.default.white(validModel.name)}`);
186
+ console.log(` ${chalk_1.default.cyan.bold("Context:")} ${chalk_1.default.yellow((0, models_1.formatContext)(validModel.contextWindow))}`);
187
+ console.log(` ${chalk_1.default.cyan.bold("Endpoint:")} ${chalk_1.default.dim("https://openrouter.ai/api/v1")}`);
188
+ console.log(` ${chalk_1.default.cyan.bold("Capabilities:")} ${chalk_1.default.gray(validModel.capabilities.join(", "))}`);
189
+ console.log(chalk_1.default.dim(` ${validModel.description}`));
190
+ console.log();
191
+ displayTierMap(tierMap);
192
+ console.log();
193
+ }
194
+ async function switchOllama(model, tierOpts) {
195
+ // Pre-flight: check litellm
196
+ const hasLitellm = await (0, ollama_1.isLitellmInstalled)();
197
+ if (!hasLitellm) {
198
+ (0, display_1.displayError)("LiteLLM is required for Ollama support");
199
+ console.log(chalk_1.default.dim(" Install with: pip install 'litellm[proxy]'"));
200
+ process.exit(1);
201
+ }
202
+ // Pre-flight: check ollama
203
+ const hasOllama = await (0, ollama_1.isOllamaInstalled)();
204
+ if (!hasOllama) {
205
+ (0, display_1.displayError)("Ollama is not installed");
206
+ console.log(chalk_1.default.dim(" Install from: https://ollama.com"));
207
+ process.exit(1);
208
+ }
209
+ // Check if Ollama is running
210
+ const ollamaRunning = await (0, ollama_1.isOllamaRunning)();
211
+ if (!ollamaRunning) {
212
+ (0, display_1.displayError)("Ollama is not running");
213
+ console.log(chalk_1.default.dim(" Start with: ollama serve"));
214
+ process.exit(1);
215
+ }
216
+ const selectedModel = model || "deepseek-r1:latest";
217
+ const validModel = (0, ollama_1.findModel)(selectedModel);
218
+ if (!validModel) {
219
+ const ollamaModels = (0, models_1.getModels)("ollama");
220
+ (0, display_1.displayError)(`Invalid model: ${selectedModel}`);
221
+ console.log(chalk_1.default.dim(" Valid models: ") + ollamaModels.map((m) => m.id).join(", "));
222
+ process.exit(1);
223
+ }
224
+ // Start LiteLLM proxy
225
+ const proxyResult = await (0, ollama_1.startLitellmProxy)(selectedModel);
226
+ if (!proxyResult.success) {
227
+ (0, display_1.displayError)(`Failed to start LiteLLM proxy: ${proxyResult.error}`);
228
+ process.exit(1);
229
+ }
230
+ const tierMap = buildTierMap(models_1.OLLAMA_DEFAULT_TIER_MAP, tierOpts);
231
+ await (0, claude_code_1.configureOllama)(selectedModel, tierMap);
232
+ console.log(chalk_1.default.green(`\n✓ Switched to: Ollama (Local)`));
233
+ console.log(chalk_1.default.dim("─".repeat(60)));
234
+ console.log(` ${chalk_1.default.cyan.bold("Model:")} ${chalk_1.default.white(validModel.name)}`);
235
+ console.log(` ${chalk_1.default.cyan.bold("Context:")} ${chalk_1.default.yellow((0, models_1.formatContext)(validModel.contextWindow))}`);
236
+ console.log(` ${chalk_1.default.cyan.bold("Endpoint:")} ${chalk_1.default.dim("http://localhost:4000 (LiteLLM proxy)")}`);
237
+ console.log(` ${chalk_1.default.cyan.bold("Capabilities:")} ${chalk_1.default.gray(validModel.capabilities.join(", "))}`);
238
+ console.log(chalk_1.default.dim(` ${validModel.description}`));
239
+ console.log();
240
+ displayTierMap(tierMap);
241
+ console.log();
242
+ }
243
+ async function switchGemini(model, tierOpts) {
244
+ // Pre-flight: check litellm
245
+ const hasLitellm = await (0, gemini_1.isLitellmInstalled)();
246
+ if (!hasLitellm) {
247
+ (0, display_1.displayError)("LiteLLM is required for Gemini support");
248
+ console.log(chalk_1.default.dim(" Install with: pip install 'litellm[proxy]'"));
249
+ process.exit(1);
250
+ }
251
+ const selectedModel = model || "gemini-2.5-pro";
252
+ const validModel = (0, gemini_1.findModel)(selectedModel);
253
+ if (!validModel) {
254
+ const geminiModels = (0, models_1.getModels)("gemini");
255
+ (0, display_1.displayError)(`Invalid model: ${selectedModel}`);
256
+ console.log(chalk_1.default.dim(" Valid models: ") + geminiModels.map((m) => m.id).join(", "));
257
+ process.exit(1);
258
+ }
259
+ // Get API key
260
+ let apiKey = await (0, config_1.getApiKey)("gemini");
261
+ if (!apiKey) {
262
+ apiKey = await promptApiKey("Gemini", "https://aistudio.google.com/apikey");
263
+ await (0, config_1.setApiKey)("gemini", apiKey);
264
+ }
265
+ // Start LiteLLM proxy
266
+ const proxyResult = await (0, gemini_1.startGeminiLitellmProxy)(apiKey, selectedModel);
267
+ if (!proxyResult.success) {
268
+ (0, display_1.displayError)(`Failed to start LiteLLM proxy: ${proxyResult.error}`);
269
+ process.exit(1);
270
+ }
271
+ const tierMap = buildTierMap(models_1.GEMINI_DEFAULT_TIER_MAP, tierOpts);
272
+ await (0, claude_code_1.configureGemini)(apiKey, selectedModel, tierMap);
273
+ console.log(chalk_1.default.green(`\n✓ Switched to: Gemini (Google)`));
274
+ console.log(chalk_1.default.dim("─".repeat(60)));
275
+ console.log(` ${chalk_1.default.cyan.bold("Model:")} ${chalk_1.default.white(validModel.name)}`);
276
+ console.log(` ${chalk_1.default.cyan.bold("Context:")} ${chalk_1.default.yellow((0, models_1.formatContext)(validModel.contextWindow))}`);
277
+ console.log(` ${chalk_1.default.cyan.bold("Endpoint:")} ${chalk_1.default.dim("http://localhost:4001 (LiteLLM proxy)")}`);
278
+ console.log(` ${chalk_1.default.cyan.bold("Capabilities:")} ${chalk_1.default.gray(validModel.capabilities.join(", "))}`);
279
+ console.log(chalk_1.default.dim(` ${validModel.description}`));
280
+ console.log();
281
+ displayTierMap(tierMap);
282
+ console.log();
283
+ }
284
+ // ---------------------------------------------------------------------------
285
+ // Top-level commands — Claude Code only
286
+ // ---------------------------------------------------------------------------
287
+ addTierOptions(program
288
+ .command("alibaba [model]")
289
+ .description("Switch Claude Code to Alibaba Coding Plan")).action(async (model, options) => {
290
+ try {
291
+ await switchAlibaba(model, options);
292
+ }
293
+ catch (error) {
294
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Alibaba");
295
+ process.exit(1);
296
+ }
297
+ });
298
+ program
299
+ .command("anthropic")
300
+ .description("Switch Claude Code to Anthropic (default)")
301
+ .action(async () => {
302
+ try {
303
+ await switchAnthropic();
304
+ }
305
+ catch (error) {
306
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Anthropic");
307
+ process.exit(1);
308
+ }
309
+ });
310
+ addTierOptions(program
311
+ .command("glm")
312
+ .description("Switch Claude Code to GLM/Z.AI (requires @z_ai/coding-helper)")).action(async (options) => {
313
+ try {
314
+ await switchGLM(options);
315
+ }
316
+ catch (error) {
317
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to GLM");
318
+ process.exit(1);
319
+ }
320
+ });
321
+ addTierOptions(program
322
+ .command("openrouter [model]")
323
+ .description("Switch Claude Code to OpenRouter")).action(async (model, options) => {
324
+ try {
325
+ await switchOpenRouter(model, options);
326
+ }
327
+ catch (error) {
328
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to OpenRouter");
329
+ process.exit(1);
330
+ }
331
+ });
332
+ addTierOptions(program
333
+ .command("ollama [model]")
334
+ .description("Switch Claude Code to Ollama (local models, requires LiteLLM proxy)")).action(async (model, options) => {
335
+ try {
336
+ await switchOllama(model, options);
337
+ }
338
+ catch (error) {
339
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Ollama");
340
+ process.exit(1);
341
+ }
342
+ });
343
+ addTierOptions(program
344
+ .command("gemini [model]")
345
+ .description("Switch Claude Code to Gemini (Google, requires LiteLLM proxy)")).action(async (model, options) => {
346
+ try {
347
+ await switchGemini(model, options);
348
+ }
349
+ catch (error) {
350
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Gemini");
351
+ process.exit(1);
352
+ }
353
+ });
354
+ // ---------------------------------------------------------------------------
355
+ // `claude` subcommand — explicit Claude Code targeting
356
+ // ---------------------------------------------------------------------------
357
+ const claudeCmd = program
358
+ .command("claude")
359
+ .description("Configure Claude Code (explicit targeting)");
360
+ claudeCmd
361
+ .command("anthropic")
362
+ .description("Switch Claude Code to Anthropic (default)")
363
+ .action(async () => {
364
+ try {
365
+ await switchAnthropic();
366
+ }
367
+ catch (error) {
368
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Anthropic");
369
+ process.exit(1);
370
+ }
371
+ });
372
+ addTierOptions(claudeCmd
373
+ .command("alibaba [model]")
374
+ .description("Switch Claude Code to Alibaba Coding Plan")).action(async (model, options) => {
375
+ try {
376
+ await switchAlibaba(model, options);
377
+ }
378
+ catch (error) {
379
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Alibaba");
380
+ process.exit(1);
381
+ }
382
+ });
383
+ addTierOptions(claudeCmd
384
+ .command("glm")
385
+ .description("Switch Claude Code to GLM/Z.AI (requires @z_ai/coding-helper)")).action(async (options) => {
386
+ try {
387
+ await switchGLM(options);
388
+ }
389
+ catch (error) {
390
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to GLM");
391
+ process.exit(1);
392
+ }
393
+ });
394
+ addTierOptions(claudeCmd
395
+ .command("openrouter [model]")
396
+ .description("Switch Claude Code to OpenRouter")).action(async (model, options) => {
397
+ try {
398
+ await switchOpenRouter(model, options);
399
+ }
400
+ catch (error) {
401
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to OpenRouter");
402
+ process.exit(1);
403
+ }
404
+ });
405
+ addTierOptions(claudeCmd
406
+ .command("ollama [model]")
407
+ .description("Switch Claude Code to Ollama (local models, requires LiteLLM proxy)")).action(async (model, options) => {
408
+ try {
409
+ await switchOllama(model, options);
410
+ }
411
+ catch (error) {
412
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Ollama");
413
+ process.exit(1);
414
+ }
415
+ });
416
+ addTierOptions(claudeCmd
417
+ .command("gemini [model]")
418
+ .description("Switch Claude Code to Gemini (Google, requires LiteLLM proxy)")).action(async (model, options) => {
419
+ try {
420
+ await switchGemini(model, options);
421
+ }
422
+ catch (error) {
423
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to switch to Gemini");
424
+ process.exit(1);
425
+ }
426
+ });
427
+ // ---------------------------------------------------------------------------
428
+ // `opencode` subcommand — OpenCode helper commands
429
+ // ---------------------------------------------------------------------------
430
+ const opencodeCmd = program
431
+ .command("opencode")
432
+ .description("OpenCode helper commands");
433
+ const opencodeAddCmd = opencodeCmd
434
+ .command("add")
435
+ .description("Add a provider to OpenCode");
436
+ opencodeAddCmd
437
+ .command("alibaba")
438
+ .description("Add Alibaba Coding Plan provider to OpenCode")
439
+ .action(async () => {
440
+ try {
441
+ let apiKey = await (0, config_1.getApiKey)("alibaba");
442
+ if (!apiKey) {
443
+ apiKey = await promptApiKey("Alibaba", "https://modelstudio.console.alibabacloud.com/");
444
+ await (0, config_1.setApiKey)("alibaba", apiKey);
445
+ }
446
+ await (0, opencode_1.configureAlibaba)(apiKey);
447
+ (0, display_1.displaySuccess)("Added Alibaba Coding Plan provider to OpenCode");
448
+ console.log(chalk_1.default.dim(" Config: ~/.config/opencode/opencode.json"));
449
+ console.log(chalk_1.default.dim(" Provider: bailian-coding-plan"));
450
+ console.log(chalk_1.default.dim(" Models: qwen3.7-plus, qwen3.6-plus, qwen3-max-2026-01-23, qwen3-coder-next, qwen3-coder-plus, MiniMax-M2.5, glm-5, glm-4.7, kimi-k2.5"));
451
+ console.log();
452
+ }
453
+ catch (error) {
454
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to add Alibaba provider");
455
+ process.exit(1);
456
+ }
457
+ });
458
+ opencodeAddCmd
459
+ .command("openrouter")
460
+ .description("Add OpenRouter provider to OpenCode")
461
+ .action(async () => {
462
+ try {
463
+ let apiKey = await (0, config_1.getApiKey)("openrouter");
464
+ if (!apiKey) {
465
+ apiKey = await promptApiKey("OpenRouter", "https://openrouter.ai/settings/keys");
466
+ await (0, config_1.setApiKey)("openrouter", apiKey);
467
+ }
468
+ await (0, opencode_1.configureOpenRouter)(apiKey);
469
+ (0, display_1.displaySuccess)("Added OpenRouter provider to OpenCode");
470
+ console.log(chalk_1.default.dim(" Config: ~/.config/opencode/opencode.json"));
471
+ console.log(chalk_1.default.dim(" Provider: openrouter"));
472
+ console.log(chalk_1.default.dim(" Models: qwen/qwen3.6-plus:free, openrouter/free"));
473
+ console.log();
474
+ }
475
+ catch (error) {
476
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to add OpenRouter provider");
477
+ process.exit(1);
478
+ }
479
+ });
480
+ opencodeAddCmd
481
+ .command("ollama")
482
+ .description("Add Ollama provider to OpenCode (requires LiteLLM proxy)")
483
+ .action(async () => {
484
+ try {
485
+ await (0, opencode_1.configureOllama)();
486
+ (0, display_1.displaySuccess)("Added Ollama provider to OpenCode");
487
+ console.log(chalk_1.default.dim(" Config: ~/.config/opencode/opencode.json"));
488
+ console.log(chalk_1.default.dim(" Provider: ollama"));
489
+ console.log(chalk_1.default.dim(" Models: deepseek-r1:latest, qwen2.5-coder:latest, llama3.1:latest, codellama:latest"));
490
+ console.log(chalk_1.default.yellow(" Note: Requires LiteLLM proxy running on port 4000"));
491
+ console.log();
492
+ }
493
+ catch (error) {
494
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to add Ollama provider");
495
+ process.exit(1);
496
+ }
497
+ });
498
+ opencodeAddCmd
499
+ .command("gemini")
500
+ .description("Add Gemini provider to OpenCode (requires LiteLLM proxy)")
501
+ .action(async () => {
502
+ try {
503
+ let apiKey = await (0, config_1.getApiKey)("gemini");
504
+ if (!apiKey) {
505
+ apiKey = await promptApiKey("Gemini", "https://aistudio.google.com/apikey");
506
+ await (0, config_1.setApiKey)("gemini", apiKey);
507
+ }
508
+ await (0, opencode_1.configureGemini)(apiKey);
509
+ (0, display_1.displaySuccess)("Added Gemini provider to OpenCode");
510
+ console.log(chalk_1.default.dim(" Config: ~/.config/opencode/opencode.json"));
511
+ console.log(chalk_1.default.dim(" Provider: gemini"));
512
+ console.log(chalk_1.default.dim(" Models: gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite"));
513
+ console.log(chalk_1.default.yellow(" Note: Requires LiteLLM proxy running on port 4001"));
514
+ console.log();
515
+ }
516
+ catch (error) {
517
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to add Gemini provider");
518
+ process.exit(1);
519
+ }
520
+ });
521
+ opencodeAddCmd
522
+ .command("glm")
523
+ .description("Add GLM/Z.AI provider to OpenCode (requires @z_ai/coding-helper)")
524
+ .action(async () => {
525
+ try {
526
+ // Check coding-helper
527
+ const hasCodingHelper = await (0, glm_1.isCodingHelperInstalled)();
528
+ if (!hasCodingHelper) {
529
+ (0, display_1.displayWarning)("coding-helper not found");
530
+ console.log(chalk_1.default.dim(" Install with: npm install -g @z_ai/coding-helper"));
531
+ console.log(chalk_1.default.dim(" Then run: coding-helper auth"));
532
+ console.log();
533
+ }
534
+ // Read GLM auth from Claude settings (set by coding-helper auth reload claude)
535
+ const claudeSettings = await (0, claude_code_1.readClaudeSettings)();
536
+ let baseURL = claudeSettings.env?.["ANTHROPIC_BASE_URL"] || "";
537
+ let apiKey = claudeSettings.env?.["ANTHROPIC_AUTH_TOKEN"] || "";
538
+ if (!baseURL || !baseURL.includes(".z.ai")) {
539
+ (0, display_1.displayWarning)("GLM not configured in Claude Code yet");
540
+ console.log(chalk_1.default.dim(" Run 'claude-switch glm' first to set up coding-helper auth"));
541
+ console.log();
542
+ return;
543
+ }
544
+ await (0, opencode_1.configureGLM)(baseURL, apiKey);
545
+ (0, display_1.displaySuccess)("Added GLM/Z.AI provider to OpenCode");
546
+ console.log(chalk_1.default.dim(" Config: ~/.config/opencode/opencode.json"));
547
+ console.log(chalk_1.default.dim(" Provider: glm"));
548
+ console.log(chalk_1.default.dim(" Models: glm-5.1, glm-5v-turbo, glm-5-turbo, glm-4.7, glm-4.7-flash"));
549
+ if (hasCodingHelper)
550
+ console.log(chalk_1.default.dim(" Managed by: coding-helper"));
551
+ console.log();
552
+ }
553
+ catch (error) {
554
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to add GLM provider");
555
+ process.exit(1);
556
+ }
557
+ });
558
+ const opencodeRemoveCmd = opencodeCmd
559
+ .command("remove")
560
+ .description("Remove a provider from OpenCode");
561
+ opencodeRemoveCmd
562
+ .command("alibaba")
563
+ .description("Remove Alibaba Coding Plan provider from OpenCode")
564
+ .action(async () => {
565
+ try {
566
+ const { removeProvider } = await Promise.resolve().then(() => __importStar(require("./clients/opencode")));
567
+ await removeProvider("bailian-coding-plan");
568
+ (0, display_1.displaySuccess)("Removed Alibaba Coding Plan provider from OpenCode");
569
+ console.log(chalk_1.default.dim(" Other providers remain unchanged"));
570
+ console.log();
571
+ }
572
+ catch (error) {
573
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove Alibaba provider");
574
+ process.exit(1);
575
+ }
576
+ });
577
+ opencodeRemoveCmd
578
+ .command("openrouter")
579
+ .description("Remove OpenRouter provider from OpenCode")
580
+ .action(async () => {
581
+ try {
582
+ const { removeProvider } = await Promise.resolve().then(() => __importStar(require("./clients/opencode")));
583
+ await removeProvider("openrouter");
584
+ (0, display_1.displaySuccess)("Removed OpenRouter provider from OpenCode");
585
+ console.log(chalk_1.default.dim(" Other providers remain unchanged"));
586
+ console.log();
587
+ }
588
+ catch (error) {
589
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove OpenRouter provider");
590
+ process.exit(1);
591
+ }
592
+ });
593
+ opencodeRemoveCmd
594
+ .command("ollama")
595
+ .description("Remove Ollama provider from OpenCode")
596
+ .action(async () => {
597
+ try {
598
+ const { removeProvider } = await Promise.resolve().then(() => __importStar(require("./clients/opencode")));
599
+ await removeProvider("ollama");
600
+ (0, display_1.displaySuccess)("Removed Ollama provider from OpenCode");
601
+ console.log(chalk_1.default.dim(" Other providers remain unchanged"));
602
+ console.log();
603
+ }
604
+ catch (error) {
605
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove Ollama provider");
606
+ process.exit(1);
607
+ }
608
+ });
609
+ opencodeRemoveCmd
610
+ .command("gemini")
611
+ .description("Remove Gemini provider from OpenCode")
612
+ .action(async () => {
613
+ try {
614
+ const { removeProvider } = await Promise.resolve().then(() => __importStar(require("./clients/opencode")));
615
+ await removeProvider("gemini");
616
+ (0, display_1.displaySuccess)("Removed Gemini provider from OpenCode");
617
+ console.log(chalk_1.default.dim(" Other providers remain unchanged"));
618
+ console.log();
619
+ }
620
+ catch (error) {
621
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove Gemini provider");
622
+ process.exit(1);
623
+ }
624
+ });
625
+ opencodeRemoveCmd
626
+ .command("glm")
627
+ .description("Remove GLM/Z.AI provider from OpenCode")
628
+ .action(async () => {
629
+ try {
630
+ const { removeProvider } = await Promise.resolve().then(() => __importStar(require("./clients/opencode")));
631
+ await removeProvider("glm");
632
+ (0, display_1.displaySuccess)("Removed GLM/Z.AI provider from OpenCode");
633
+ console.log(chalk_1.default.dim(" Other providers remain unchanged"));
634
+ console.log();
635
+ }
636
+ catch (error) {
637
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove GLM provider");
638
+ process.exit(1);
639
+ }
640
+ });
641
+ // ---------------------------------------------------------------------------
642
+ // Info commands
643
+ // ---------------------------------------------------------------------------
644
+ program
645
+ .command("status")
646
+ .description("Show current config and verify API keys")
647
+ .action(async () => {
648
+ try {
649
+ // ── Current Configuration ──
650
+ console.log(chalk_1.default.green("\n=== Claude AI Switcher Status ===\n"));
651
+ // Claude Code
652
+ console.log(chalk_1.default.cyan.bold(" Claude Code:"));
653
+ if ((0, claude_code_1.claudeSettingsExists)()) {
654
+ const claudeProvider = await (0, claude_code_1.getCurrentProvider)();
655
+ if (claudeProvider) {
656
+ console.log(` Provider: ${chalk_1.default.white(claudeProvider.provider)}`);
657
+ if (claudeProvider.model)
658
+ console.log(` Model: ${chalk_1.default.white(claudeProvider.model)}`);
659
+ if (claudeProvider.endpoint)
660
+ console.log(` Endpoint: ${chalk_1.default.dim(claudeProvider.endpoint)}`);
661
+ if (claudeProvider.tierMap?.opus) {
662
+ console.log(chalk_1.default.dim(" Aliases:"));
663
+ console.log(chalk_1.default.dim(` opus → ${claudeProvider.tierMap.opus}`));
664
+ console.log(chalk_1.default.dim(` sonnet → ${claudeProvider.tierMap.sonnet}`));
665
+ console.log(chalk_1.default.dim(` haiku → ${claudeProvider.tierMap.haiku}`));
666
+ }
667
+ }
668
+ else {
669
+ console.log(chalk_1.default.dim(" Unable to read configuration"));
670
+ }
671
+ }
672
+ else {
673
+ console.log(chalk_1.default.dim(" Not configured (using defaults)"));
674
+ }
675
+ console.log();
676
+ // OpenCode
677
+ console.log(chalk_1.default.cyan.bold(" OpenCode:"));
678
+ if ((0, opencode_1.opencodeSettingsExists)()) {
679
+ const opencodeProvider = await (0, opencode_1.getCurrentProvider)();
680
+ if (opencodeProvider) {
681
+ console.log(` Provider: ${chalk_1.default.white(opencodeProvider.provider)}`);
682
+ if (opencodeProvider.model)
683
+ console.log(` Model: ${chalk_1.default.white(opencodeProvider.model)}`);
684
+ if (opencodeProvider.endpoint)
685
+ console.log(` Endpoint: ${chalk_1.default.dim(opencodeProvider.endpoint)}`);
686
+ }
687
+ else {
688
+ console.log(chalk_1.default.dim(" Unable to read configuration"));
689
+ }
690
+ }
691
+ else {
692
+ console.log(chalk_1.default.dim(" Not installed"));
693
+ }
694
+ // ── API Key Verification ──
695
+ console.log();
696
+ console.log(chalk_1.default.cyan.bold(" API Key Verification:"));
697
+ console.log(chalk_1.default.dim("─".repeat(50)));
698
+ const alibabaKey = await (0, config_1.getApiKey)("alibaba");
699
+ const openrouterKey = await (0, config_1.getApiKey)("openrouter");
700
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
701
+ const geminiKey = await (0, config_1.getApiKey)("gemini");
702
+ // Show spinner while verifying
703
+ const ora = (await Promise.resolve().then(() => __importStar(require("ora")))).default;
704
+ const spinner = ora("Verifying API keys...").start();
705
+ const results = await (0, verify_1.verifyAllKeys)({
706
+ alibaba: alibabaKey,
707
+ openrouter: openrouterKey,
708
+ anthropic: anthropicKey,
709
+ checkGLM: true,
710
+ checkOllama: true,
711
+ gemini: geminiKey
712
+ });
713
+ spinner.stop();
714
+ for (const result of results) {
715
+ const label = result.provider.padEnd(12);
716
+ let icon;
717
+ let detail = result.message || "";
718
+ switch (result.status) {
719
+ case "ok":
720
+ icon = chalk_1.default.green("✓");
721
+ break;
722
+ case "invalid":
723
+ icon = chalk_1.default.red("✗");
724
+ break;
725
+ case "missing":
726
+ icon = chalk_1.default.dim("○");
727
+ detail = "No key configured";
728
+ break;
729
+ case "error":
730
+ icon = chalk_1.default.yellow("⚠");
731
+ break;
732
+ default:
733
+ icon = chalk_1.default.dim("–");
734
+ detail = "Skipped";
735
+ }
736
+ // Show masked key if available
737
+ let keyDisplay = "";
738
+ if (result.provider === "alibaba" && alibabaKey) {
739
+ keyDisplay = chalk_1.default.dim(` (${(0, verify_1.maskKey)(alibabaKey)})`);
740
+ }
741
+ else if (result.provider === "openrouter" && openrouterKey) {
742
+ keyDisplay = chalk_1.default.dim(` (${(0, verify_1.maskKey)(openrouterKey)})`);
743
+ }
744
+ else if (result.provider === "anthropic" && anthropicKey) {
745
+ keyDisplay = chalk_1.default.dim(` (${(0, verify_1.maskKey)(anthropicKey)})`);
746
+ }
747
+ else if (result.provider === "gemini" && geminiKey) {
748
+ keyDisplay = chalk_1.default.dim(` (${(0, verify_1.maskKey)(geminiKey)})`);
749
+ }
750
+ console.log(` ${icon} ${chalk_1.default.white(label)} ${chalk_1.default.gray(detail)}${keyDisplay}`);
751
+ }
752
+ console.log(chalk_1.default.dim("─".repeat(50)));
753
+ console.log();
754
+ }
755
+ catch (error) {
756
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to get status");
757
+ process.exit(1);
758
+ }
759
+ });
760
+ program
761
+ .command("current")
762
+ .description("Show current provider and model for both clients")
763
+ .action(async () => {
764
+ try {
765
+ console.log(chalk_1.default.green("\nCurrent Configuration:\n"));
766
+ console.log(chalk_1.default.cyan.bold(" Claude Code:"));
767
+ if ((0, claude_code_1.claudeSettingsExists)()) {
768
+ const claudeProvider = await (0, claude_code_1.getCurrentProvider)();
769
+ if (claudeProvider) {
770
+ console.log(` Provider: ${chalk_1.default.white(claudeProvider.provider)}`);
771
+ if (claudeProvider.model)
772
+ console.log(` Model: ${chalk_1.default.white(claudeProvider.model)}`);
773
+ if (claudeProvider.endpoint)
774
+ console.log(` Endpoint: ${chalk_1.default.dim(claudeProvider.endpoint)}`);
775
+ if (claudeProvider.tierMap?.opus) {
776
+ console.log(chalk_1.default.dim(" Model aliases:"));
777
+ console.log(chalk_1.default.dim(` opus → ${claudeProvider.tierMap.opus}`));
778
+ console.log(chalk_1.default.dim(` sonnet → ${claudeProvider.tierMap.sonnet}`));
779
+ console.log(chalk_1.default.dim(` haiku → ${claudeProvider.tierMap.haiku}`));
780
+ }
781
+ }
782
+ else {
783
+ console.log(chalk_1.default.dim(" Unable to read configuration"));
784
+ }
785
+ }
786
+ else {
787
+ console.log(chalk_1.default.dim(" Not configured (using defaults)"));
788
+ }
789
+ console.log();
790
+ console.log(chalk_1.default.cyan.bold(" OpenCode:"));
791
+ if ((0, opencode_1.opencodeSettingsExists)()) {
792
+ const opencodeProvider = await (0, opencode_1.getCurrentProvider)();
793
+ if (opencodeProvider) {
794
+ console.log(` Provider: ${chalk_1.default.white(opencodeProvider.provider)}`);
795
+ if (opencodeProvider.model)
796
+ console.log(` Model: ${chalk_1.default.white(opencodeProvider.model)}`);
797
+ if (opencodeProvider.endpoint)
798
+ console.log(` Endpoint: ${chalk_1.default.dim(opencodeProvider.endpoint)}`);
799
+ }
800
+ else {
801
+ console.log(chalk_1.default.dim(" Unable to read configuration"));
802
+ }
803
+ }
804
+ else {
805
+ console.log(chalk_1.default.dim(" Not configured (using defaults)"));
806
+ }
807
+ console.log();
808
+ }
809
+ catch (error) {
810
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to get current provider");
811
+ process.exit(1);
812
+ }
813
+ });
814
+ program
815
+ .command("list")
816
+ .description("List all providers and their models")
817
+ .action(() => {
818
+ const providerList = Object.values(models_1.providers).map((p) => ({
819
+ id: p.id,
820
+ name: p.name,
821
+ endpoint: p.endpoint,
822
+ modelCount: p.models.length
823
+ }));
824
+ (0, display_1.displayProviders)(providerList);
825
+ for (const provider of Object.values(models_1.providers)) {
826
+ (0, display_1.displayModels)(provider.name, provider.models);
827
+ }
828
+ });
829
+ program
830
+ .command("models [provider]")
831
+ .description("Show models for a specific provider")
832
+ .action((providerName) => {
833
+ if (!providerName) {
834
+ (0, display_1.displayError)("Please specify a provider: anthropic, alibaba, openrouter, glm, ollama, or gemini");
835
+ console.log(chalk_1.default.dim(" Example: claude-switch models alibaba"));
836
+ process.exit(1);
837
+ }
838
+ const provider = models_1.providers[providerName.toLowerCase()];
839
+ if (!provider) {
840
+ (0, display_1.displayError)(`Unknown provider: ${providerName}`);
841
+ console.log(chalk_1.default.dim(" Valid providers: ") + Object.keys(models_1.providers).join(", "));
842
+ process.exit(1);
843
+ }
844
+ (0, display_1.displayModels)(provider.name, provider.models);
845
+ });
846
+ program
847
+ .command("key <provider> [apikey]")
848
+ .description("Set or show API key for a provider")
849
+ .action(async (provider, apikey) => {
850
+ try {
851
+ if (!apikey) {
852
+ const hasKey = await (0, config_1.hasApiKey)(provider);
853
+ if (hasKey) {
854
+ (0, display_1.displaySuccess)(`API key is set for ${provider}`);
855
+ }
856
+ else {
857
+ (0, display_1.displayWarning)(`No API key set for ${provider}`);
858
+ console.log(chalk_1.default.dim(" Set with: claude-switch key " + provider + " <your-key>"));
859
+ }
860
+ return;
861
+ }
862
+ await (0, config_1.setApiKey)(provider, apikey);
863
+ (0, display_1.displaySuccess)(`API key set for ${provider}`);
864
+ }
865
+ catch (error) {
866
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to manage API key");
867
+ process.exit(1);
868
+ }
869
+ });
870
+ program
871
+ .command("setup")
872
+ .description("Interactive setup wizard")
873
+ .action(async () => {
874
+ try {
875
+ console.log(chalk_1.default.green("\n=== Claude AI Switcher Setup ===\n"));
876
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
877
+ const hasAlibabaKey = await (0, config_1.hasApiKey)("alibaba");
878
+ if (!hasAlibabaKey) {
879
+ console.log(chalk_1.default.yellow("Alibaba Coding Plan Setup"));
880
+ console.log(chalk_1.default.dim(" Get your API key from: https://modelstudio.console.alibabacloud.com/"));
881
+ console.log();
882
+ const answer = await new Promise((resolve) => {
883
+ rl.question("Enter your Alibaba API Key (or press Enter to skip): ", resolve);
884
+ });
885
+ if (answer.trim()) {
886
+ await (0, config_1.setApiKey)("alibaba", answer.trim());
887
+ (0, display_1.displaySuccess)("Alibaba API key saved");
888
+ }
889
+ }
890
+ const hasOpenRouterKey = await (0, config_1.hasApiKey)("openrouter");
891
+ if (!hasOpenRouterKey) {
892
+ console.log(chalk_1.default.yellow("\nOpenRouter Setup"));
893
+ console.log(chalk_1.default.dim(" Get your API key from: https://openrouter.ai/settings/keys"));
894
+ console.log();
895
+ const answer = await new Promise((resolve) => {
896
+ rl.question("Enter your OpenRouter API Key (or press Enter to skip): ", resolve);
897
+ });
898
+ if (answer.trim()) {
899
+ await (0, config_1.setApiKey)("openrouter", answer.trim());
900
+ (0, display_1.displaySuccess)("OpenRouter API key saved");
901
+ }
902
+ }
903
+ const hasGeminiKey = await (0, config_1.hasApiKey)("gemini");
904
+ if (!hasGeminiKey) {
905
+ console.log(chalk_1.default.yellow("\nGemini Setup"));
906
+ console.log(chalk_1.default.dim(" Get your API key from: https://aistudio.google.com/apikey"));
907
+ console.log();
908
+ const answer = await new Promise((resolve) => {
909
+ rl.question("Enter your Gemini API Key (or press Enter to skip): ", resolve);
910
+ });
911
+ if (answer.trim()) {
912
+ await (0, config_1.setApiKey)("gemini", answer.trim());
913
+ (0, display_1.displaySuccess)("Gemini API key saved");
914
+ }
915
+ }
916
+ rl.close();
917
+ console.log(chalk_1.default.green("\n✓ Setup complete!\n"));
918
+ console.log("Available commands:");
919
+ console.log(chalk_1.default.dim(" claude-switch alibaba [model] - Switch Claude Code to Alibaba"));
920
+ console.log(chalk_1.default.dim(" claude-switch anthropic - Switch Claude Code to Anthropic"));
921
+ console.log(chalk_1.default.dim(" claude-switch glm - Switch Claude Code to GLM/Z.AI"));
922
+ console.log(chalk_1.default.dim(" claude-switch openrouter [model] - Switch Claude Code to OpenRouter"));
923
+ console.log(chalk_1.default.dim(" claude-switch ollama [model] - Switch Claude Code to Ollama"));
924
+ console.log(chalk_1.default.dim(" claude-switch gemini [model] - Switch Claude Code to Gemini"));
925
+ console.log(chalk_1.default.dim(" claude-switch claude alibaba - Explicit Claude Code targeting"));
926
+ console.log(chalk_1.default.dim(" claude-switch opencode add alibaba - Add Alibaba provider to OpenCode"));
927
+ console.log(chalk_1.default.dim(" claude-switch opencode add openrouter - Add OpenRouter provider to OpenCode"));
928
+ console.log(chalk_1.default.dim(" claude-switch opencode add ollama - Add Ollama provider to OpenCode"));
929
+ console.log(chalk_1.default.dim(" claude-switch opencode add gemini - Add Gemini provider to OpenCode"));
930
+ console.log(chalk_1.default.dim(" claude-switch opencode add glm - Add GLM/Z.AI provider to OpenCode"));
931
+ console.log(chalk_1.default.dim(" claude-switch opencode remove alibaba - Remove Alibaba from OpenCode"));
932
+ console.log(chalk_1.default.dim(" claude-switch opencode remove openrouter - Remove OpenRouter from OpenCode"));
933
+ console.log(chalk_1.default.dim(" claude-switch opencode remove ollama - Remove Ollama from OpenCode"));
934
+ console.log(chalk_1.default.dim(" claude-switch opencode remove gemini - Remove Gemini from OpenCode"));
935
+ console.log(chalk_1.default.dim(" claude-switch opencode remove glm - Remove GLM/Z.AI from OpenCode"));
936
+ console.log(chalk_1.default.dim(" claude-switch openrouter --opus <model> - Custom model aliases"));
937
+ console.log(chalk_1.default.dim(" claude-switch list - List all providers"));
938
+ console.log(chalk_1.default.dim(" claude-switch status - Show current config + verify API keys"));
939
+ console.log(chalk_1.default.dim(" claude-switch current - Show current config"));
940
+ console.log(chalk_1.default.dim(" claude-switch hooks install - Install token tracking & visual enhancements"));
941
+ console.log(chalk_1.default.dim(" claude-switch hooks status - Show token usage and visual status"));
942
+ console.log();
943
+ }
944
+ catch (error) {
945
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Setup failed");
946
+ process.exit(1);
947
+ }
948
+ });
949
+ // ---------------------------------------------------------------------------
950
+ // Hooks commands - Token tracking and visual enhancements
951
+ // ---------------------------------------------------------------------------
952
+ const hooksCmd = program
953
+ .command("hooks")
954
+ .description("Manage Claude Code hooks (token tracking, visual enhancements)");
955
+ hooksCmd
956
+ .command("install")
957
+ .description("Install all visual enhancements and token tracking")
958
+ .action(async () => {
959
+ try {
960
+ const ora = await Promise.resolve().then(() => __importStar(require("ora"))).catch(() => null);
961
+ const spinner = ora ? ora.default("Installing hooks...").start() : null;
962
+ await (0, index_1.installAllHooks)();
963
+ spinner?.stop();
964
+ console.log(chalk_1.default.green("\n✓ Hooks installed successfully!\n"));
965
+ console.log(chalk_1.default.cyan.bold(" Installed:"));
966
+ console.log(chalk_1.default.dim(" • Token Tracker (~/.claude/token-tracker.js)"));
967
+ console.log(chalk_1.default.dim(" • Visual Enhancements (~/.claude/visual-enhancements.js)"));
968
+ console.log();
969
+ console.log(chalk_1.default.yellow(" Usage:"));
970
+ console.log(chalk_1.default.dim(" • Token usage is tracked automatically"));
971
+ console.log(chalk_1.default.dim(" • Run 'claude-switch hooks status' to see current usage"));
972
+ console.log(chalk_1.default.dim(" • Run 'claude-switch hooks reset' to reset counters"));
973
+ console.log();
974
+ }
975
+ catch (error) {
976
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to install hooks");
977
+ process.exit(1);
978
+ }
979
+ });
980
+ hooksCmd
981
+ .command("install-token")
982
+ .description("Install only token tracker")
983
+ .action(async () => {
984
+ try {
985
+ await (0, index_1.installTokenTracker)();
986
+ (0, display_1.displaySuccess)("Token tracker installed");
987
+ console.log(chalk_1.default.dim(" Location: ~/.claude/token-tracker.js"));
988
+ console.log();
989
+ }
990
+ catch (error) {
991
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to install token tracker");
992
+ process.exit(1);
993
+ }
994
+ });
995
+ hooksCmd
996
+ .command("install-visual")
997
+ .description("Install only visual enhancements")
998
+ .action(async () => {
999
+ try {
1000
+ await (0, index_1.installVisualEnhancements)();
1001
+ (0, display_1.displaySuccess)("Visual enhancements installed");
1002
+ console.log(chalk_1.default.dim(" Location: ~/.claude/visual-enhancements.js"));
1003
+ console.log();
1004
+ }
1005
+ catch (error) {
1006
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to install visual enhancements");
1007
+ process.exit(1);
1008
+ }
1009
+ });
1010
+ hooksCmd
1011
+ .command("status")
1012
+ .description("Show token usage and visual status")
1013
+ .action(async () => {
1014
+ try {
1015
+ const installed = await (0, index_1.areHooksInstalled)();
1016
+ console.log(chalk_1.default.green("\n=== Hooks Status ===\n"));
1017
+ console.log(` Token Tracker: ${installed.tokenTracking ? chalk_1.default.green("✓ Installed") : chalk_1.default.red("Not installed")}`);
1018
+ console.log(` Visual Enhancements: ${installed.visualEnhancements ? chalk_1.default.green("✓ Installed") : chalk_1.default.red("Not installed")}`);
1019
+ console.log();
1020
+ if (installed.tokenTracking) {
1021
+ await (0, index_1.showTokenStatus)();
1022
+ }
1023
+ if (installed.visualEnhancements) {
1024
+ await (0, index_1.showVisualStatus)();
1025
+ }
1026
+ if (!installed.tokenTracking && !installed.visualEnhancements) {
1027
+ console.log(chalk_1.default.yellow(" Run 'claude-switch hooks install' to install hooks"));
1028
+ console.log();
1029
+ }
1030
+ }
1031
+ catch (error) {
1032
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to get hooks status");
1033
+ process.exit(1);
1034
+ }
1035
+ });
1036
+ hooksCmd
1037
+ .command("reset")
1038
+ .description("Reset token usage counters")
1039
+ .action(async () => {
1040
+ try {
1041
+ await (0, index_1.resetTokenUsage)();
1042
+ }
1043
+ catch (error) {
1044
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to reset token usage");
1045
+ process.exit(1);
1046
+ }
1047
+ });
1048
+ hooksCmd
1049
+ .command("remove")
1050
+ .description("Remove all hooks")
1051
+ .action(async () => {
1052
+ try {
1053
+ await (0, index_1.removeAllHooks)();
1054
+ (0, display_1.displaySuccess)("All hooks removed");
1055
+ console.log();
1056
+ }
1057
+ catch (error) {
1058
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove hooks");
1059
+ process.exit(1);
1060
+ }
1061
+ });
1062
+ hooksCmd
1063
+ .command("remove-token")
1064
+ .description("Remove token tracker")
1065
+ .action(async () => {
1066
+ try {
1067
+ await (0, index_1.removeTokenTracker)();
1068
+ (0, display_1.displaySuccess)("Token tracker removed");
1069
+ console.log();
1070
+ }
1071
+ catch (error) {
1072
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove token tracker");
1073
+ process.exit(1);
1074
+ }
1075
+ });
1076
+ hooksCmd
1077
+ .command("remove-visual")
1078
+ .description("Remove visual enhancements")
1079
+ .action(async () => {
1080
+ try {
1081
+ await (0, index_1.removeVisualEnhancements)();
1082
+ (0, display_1.displaySuccess)("Visual enhancements removed");
1083
+ console.log();
1084
+ }
1085
+ catch (error) {
1086
+ (0, display_1.displayError)(error instanceof Error ? error.message : "Failed to remove visual enhancements");
1087
+ process.exit(1);
1088
+ }
1089
+ });
1090
+ program.parse();
1091
+ //# sourceMappingURL=index.js.map