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