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/dist/verify.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API Key Verification
|
|
4
|
+
*
|
|
5
|
+
* Makes lightweight requests to each provider's API to verify keys are valid.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
19
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
20
|
+
}) : function(o, v) {
|
|
21
|
+
o["default"] = v;
|
|
22
|
+
});
|
|
23
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
24
|
+
var ownKeys = function(o) {
|
|
25
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
26
|
+
var ar = [];
|
|
27
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
28
|
+
return ar;
|
|
29
|
+
};
|
|
30
|
+
return ownKeys(o);
|
|
31
|
+
};
|
|
32
|
+
return function (mod) {
|
|
33
|
+
if (mod && mod.__esModule) return mod;
|
|
34
|
+
var result = {};
|
|
35
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
36
|
+
__setModuleDefault(result, mod);
|
|
37
|
+
return result;
|
|
38
|
+
};
|
|
39
|
+
})();
|
|
40
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
|
+
exports.maskKey = maskKey;
|
|
42
|
+
exports.verifyAllKeys = verifyAllKeys;
|
|
43
|
+
const TIMEOUT_MS = 5000;
|
|
44
|
+
function maskKey(key) {
|
|
45
|
+
if (key.length <= 8)
|
|
46
|
+
return "****";
|
|
47
|
+
return key.slice(0, 4) + "..." + key.slice(-4);
|
|
48
|
+
}
|
|
49
|
+
async function fetchWithTimeout(url, init) {
|
|
50
|
+
const controller = new AbortController();
|
|
51
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
52
|
+
try {
|
|
53
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
clearTimeout(timer);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Verify Alibaba Coding Plan API key with a minimal chat completion request.
|
|
61
|
+
*/
|
|
62
|
+
async function verifyAlibaba(apiKey) {
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetchWithTimeout("https://dashscope.aliyuncs.com/compatible-mode/v1/models", {
|
|
65
|
+
method: "GET",
|
|
66
|
+
headers: {
|
|
67
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
// 200 OK means key works, 401/403 means invalid
|
|
71
|
+
if (res.ok) {
|
|
72
|
+
return { provider: "alibaba", status: "ok", message: "Key valid" };
|
|
73
|
+
}
|
|
74
|
+
if (res.status === 401 || res.status === 403) {
|
|
75
|
+
return { provider: "alibaba", status: "invalid", message: "Authentication failed" };
|
|
76
|
+
}
|
|
77
|
+
return { provider: "alibaba", status: "error", message: `HTTP ${res.status}` };
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { provider: "alibaba", status: "error", message: "Connection failed" };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Verify OpenRouter API key by listing models.
|
|
85
|
+
*/
|
|
86
|
+
async function verifyOpenRouter(apiKey) {
|
|
87
|
+
try {
|
|
88
|
+
const res = await fetchWithTimeout("https://openrouter.ai/api/v1/models", {
|
|
89
|
+
method: "GET",
|
|
90
|
+
headers: {
|
|
91
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
92
|
+
"Content-Type": "application/json"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (res.ok) {
|
|
96
|
+
return { provider: "openrouter", status: "ok", message: "Key valid" };
|
|
97
|
+
}
|
|
98
|
+
if (res.status === 401 || res.status === 403) {
|
|
99
|
+
return { provider: "openrouter", status: "invalid", message: "Authentication failed" };
|
|
100
|
+
}
|
|
101
|
+
return { provider: "openrouter", status: "error", message: `HTTP ${res.status}` };
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return { provider: "openrouter", status: "error", message: "Connection failed" };
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Verify GLM/Z.AI by checking if coding-helper is installed and authenticated.
|
|
109
|
+
* Uses the coding-helper CLI status check rather than a direct API call.
|
|
110
|
+
*/
|
|
111
|
+
async function verifyGLM() {
|
|
112
|
+
try {
|
|
113
|
+
const { exec } = await Promise.resolve().then(() => __importStar(require("child_process")));
|
|
114
|
+
const { promisify } = await Promise.resolve().then(() => __importStar(require("util")));
|
|
115
|
+
const execAsync = promisify(exec);
|
|
116
|
+
const { platform } = await Promise.resolve().then(() => __importStar(require("os")));
|
|
117
|
+
const checkCmd = platform() === "win32" ? "where coding-helper" : "which coding-helper";
|
|
118
|
+
try {
|
|
119
|
+
await execAsync(checkCmd);
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return { provider: "glm", status: "error", message: "coding-helper not installed" };
|
|
123
|
+
}
|
|
124
|
+
// Try to ping the GLM API — if env vars are set, the auth should work
|
|
125
|
+
const glmModel = process.env.ZHIPUAI_MODEL || process.env.ZAI_MODEL;
|
|
126
|
+
if (glmModel) {
|
|
127
|
+
return { provider: "glm", status: "ok", message: "coding-helper installed, env vars set" };
|
|
128
|
+
}
|
|
129
|
+
return { provider: "glm", status: "ok", message: "coding-helper installed" };
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { provider: "glm", status: "error", message: "Check failed" };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Verify Anthropic API key (optional — uses ANTHROPIC_API_KEY env var).
|
|
137
|
+
*/
|
|
138
|
+
async function verifyAnthropic(apiKey) {
|
|
139
|
+
try {
|
|
140
|
+
const res = await fetchWithTimeout("https://api.anthropic.com/v1/models", {
|
|
141
|
+
method: "GET",
|
|
142
|
+
headers: {
|
|
143
|
+
"x-api-key": apiKey,
|
|
144
|
+
"anthropic-version": "2023-06-01",
|
|
145
|
+
"Content-Type": "application/json"
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
if (res.ok) {
|
|
149
|
+
return { provider: "anthropic", status: "ok", message: "Key valid" };
|
|
150
|
+
}
|
|
151
|
+
if (res.status === 401 || res.status === 403) {
|
|
152
|
+
return { provider: "anthropic", status: "invalid", message: "Authentication failed" };
|
|
153
|
+
}
|
|
154
|
+
return { provider: "anthropic", status: "error", message: `HTTP ${res.status}` };
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return { provider: "anthropic", status: "error", message: "Connection failed" };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Verify all configured API keys in parallel.
|
|
162
|
+
*/
|
|
163
|
+
async function verifyAllKeys(keys) {
|
|
164
|
+
const checks = [];
|
|
165
|
+
if (keys.alibaba) {
|
|
166
|
+
checks.push(verifyAlibaba(keys.alibaba));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
checks.push(Promise.resolve({ provider: "alibaba", status: "missing" }));
|
|
170
|
+
}
|
|
171
|
+
if (keys.openrouter) {
|
|
172
|
+
checks.push(verifyOpenRouter(keys.openrouter));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
checks.push(Promise.resolve({ provider: "openrouter", status: "missing" }));
|
|
176
|
+
}
|
|
177
|
+
if (keys.anthropic) {
|
|
178
|
+
checks.push(verifyAnthropic(keys.anthropic));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
checks.push(Promise.resolve({ provider: "anthropic", status: "missing" }));
|
|
182
|
+
}
|
|
183
|
+
if (keys.checkGLM) {
|
|
184
|
+
checks.push(verifyGLM());
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
checks.push(Promise.resolve({ provider: "glm", status: "skipped" }));
|
|
188
|
+
}
|
|
189
|
+
if (keys.checkOllama) {
|
|
190
|
+
checks.push(verifyOllama());
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
checks.push(Promise.resolve({ provider: "ollama", status: "skipped" }));
|
|
194
|
+
}
|
|
195
|
+
if (keys.gemini) {
|
|
196
|
+
checks.push(verifyGemini(keys.gemini));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
checks.push(Promise.resolve({ provider: "gemini", status: "missing" }));
|
|
200
|
+
}
|
|
201
|
+
return Promise.all(checks);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Verify Ollama by checking LiteLLM proxy and Ollama service.
|
|
205
|
+
*/
|
|
206
|
+
async function verifyOllama() {
|
|
207
|
+
try {
|
|
208
|
+
// Check LiteLLM proxy on port 4000
|
|
209
|
+
try {
|
|
210
|
+
const res = await fetchWithTimeout("http://localhost:4000/health", { method: "GET" });
|
|
211
|
+
if (!res.ok) {
|
|
212
|
+
return { provider: "ollama", status: "error", message: "LiteLLM proxy not healthy" };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return { provider: "ollama", status: "error", message: "LiteLLM proxy not running on port 4000" };
|
|
217
|
+
}
|
|
218
|
+
// Check Ollama on port 11434
|
|
219
|
+
try {
|
|
220
|
+
const res = await fetchWithTimeout("http://localhost:11434/api/tags", { method: "GET" });
|
|
221
|
+
if (res.ok) {
|
|
222
|
+
return { provider: "ollama", status: "ok", message: "Ollama + LiteLLM proxy running" };
|
|
223
|
+
}
|
|
224
|
+
return { provider: "ollama", status: "error", message: "Ollama not responding" };
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return { provider: "ollama", status: "error", message: "Ollama not running on port 11434" };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
return { provider: "ollama", status: "error", message: "Check failed" };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Verify Gemini API key and LiteLLM proxy.
|
|
236
|
+
*/
|
|
237
|
+
async function verifyGemini(apiKey) {
|
|
238
|
+
// Verify Gemini API key independently of proxy status
|
|
239
|
+
try {
|
|
240
|
+
const res = await fetchWithTimeout("https://generativelanguage.googleapis.com/v1beta/models", { method: "GET", headers: { "x-goog-api-key": apiKey } });
|
|
241
|
+
if (res.ok) {
|
|
242
|
+
// Key is valid — also check proxy status for informational purposes
|
|
243
|
+
let proxyMsg = "";
|
|
244
|
+
try {
|
|
245
|
+
const proxyRes = await fetchWithTimeout("http://localhost:4001/health", { method: "GET" });
|
|
246
|
+
proxyMsg = proxyRes.ok ? ", proxy running" : ", proxy not running";
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
proxyMsg = ", proxy not running";
|
|
250
|
+
}
|
|
251
|
+
return { provider: "gemini", status: "ok", message: `Key valid${proxyMsg}` };
|
|
252
|
+
}
|
|
253
|
+
if (res.status === 400 || res.status === 401 || res.status === 403) {
|
|
254
|
+
return { provider: "gemini", status: "invalid", message: "Authentication failed" };
|
|
255
|
+
}
|
|
256
|
+
return { provider: "gemini", status: "error", message: `HTTP ${res.status}` };
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return { provider: "gemini", status: "error", message: "Connection failed" };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../src/verify.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-ai-switcher",
|
|
3
|
+
"version": "1.1.4",
|
|
4
|
+
"description": "Switch between AI providers (Anthropic, GLM, Alibaba Qwen, OpenRouter, Ollama, Gemini) for Claude Code and OpenCode",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-switch": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/",
|
|
11
|
+
"src/",
|
|
12
|
+
"scripts/",
|
|
13
|
+
"tsconfig.json",
|
|
14
|
+
"AGENTS.md",
|
|
15
|
+
"ARCHITECTURE.md",
|
|
16
|
+
"CLAUDE.md",
|
|
17
|
+
"QWEN.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc && npm run copy-hooks",
|
|
21
|
+
"copy-hooks": "node scripts/copy-hooks.js",
|
|
22
|
+
"start": "node dist/index.js",
|
|
23
|
+
"dev": "ts-node src/index.ts",
|
|
24
|
+
"clean": "rimraf dist"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"claude",
|
|
28
|
+
"ai",
|
|
29
|
+
"qwen",
|
|
30
|
+
"glm",
|
|
31
|
+
"anthropic",
|
|
32
|
+
"alibaba",
|
|
33
|
+
"opencode",
|
|
34
|
+
"openrouter",
|
|
35
|
+
"ollama",
|
|
36
|
+
"gemini",
|
|
37
|
+
"litellm"
|
|
38
|
+
],
|
|
39
|
+
"author": "Chad Yantorno",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"commander": "^11.1.0",
|
|
43
|
+
"fs-extra": "^11.2.0",
|
|
44
|
+
"chalk": "^5.3.0",
|
|
45
|
+
"ora": "^8.0.1"
|
|
46
|
+
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/node": "^20.10.0",
|
|
49
|
+
"@types/fs-extra": "^11.0.4",
|
|
50
|
+
"rimraf": "^5.0.0",
|
|
51
|
+
"typescript": "^5.3.0",
|
|
52
|
+
"ts-node": "^10.9.2"
|
|
53
|
+
},
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const srcDir = path.join(__dirname, '..', 'src', 'hooks');
|
|
5
|
+
const destDir = path.join(__dirname, '..', 'dist', 'hooks');
|
|
6
|
+
|
|
7
|
+
fs.ensureDirSync(destDir);
|
|
8
|
+
fs.copySync(srcDir, destDir, {
|
|
9
|
+
filter: (src) => {
|
|
10
|
+
if (fs.statSync(src).isDirectory()) return true;
|
|
11
|
+
return path.extname(src) === '.js';
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
console.log('Copied hook files to dist/hooks/');
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Client Handler
|
|
3
|
+
*
|
|
4
|
+
* Manages ~/.claude/settings.json and ~/.claude.json for Claude Code
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from "fs-extra";
|
|
8
|
+
import * as path from "path";
|
|
9
|
+
import * as os from "os";
|
|
10
|
+
import { ModelTierMap } from "../models";
|
|
11
|
+
|
|
12
|
+
export interface ClaudeSettings {
|
|
13
|
+
mcpServers?: Record<string, any>;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ClaudeJson {
|
|
18
|
+
hasCompletedOnboarding?: boolean;
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface MCPService {
|
|
23
|
+
type: string;
|
|
24
|
+
command?: string;
|
|
25
|
+
args?: string[];
|
|
26
|
+
env?: Record<string, string>;
|
|
27
|
+
url?: string;
|
|
28
|
+
headers?: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
32
|
+
const SETTINGS_FILE = path.join(CLAUDE_DIR, "settings.json");
|
|
33
|
+
const CLAUDE_JSON = path.join(os.homedir(), ".claude.json");
|
|
34
|
+
|
|
35
|
+
const TIER_ENV_KEYS = {
|
|
36
|
+
opus: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
37
|
+
sonnet: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
38
|
+
haiku: "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
function applyTierMap(settings: ClaudeSettings, tierMap: ModelTierMap): void {
|
|
42
|
+
settings.env = settings.env || {};
|
|
43
|
+
settings.env[TIER_ENV_KEYS.opus] = tierMap.opus;
|
|
44
|
+
settings.env[TIER_ENV_KEYS.sonnet] = tierMap.sonnet;
|
|
45
|
+
settings.env[TIER_ENV_KEYS.haiku] = tierMap.haiku;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function clearTierMap(settings: ClaudeSettings): void {
|
|
49
|
+
if (settings.env) {
|
|
50
|
+
delete settings.env[TIER_ENV_KEYS.opus];
|
|
51
|
+
delete settings.env[TIER_ENV_KEYS.sonnet];
|
|
52
|
+
delete settings.env[TIER_ENV_KEYS.haiku];
|
|
53
|
+
if (Object.keys(settings.env).length === 0) {
|
|
54
|
+
delete settings.env;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if Claude settings file exists
|
|
61
|
+
*/
|
|
62
|
+
export function claudeSettingsExists(): boolean {
|
|
63
|
+
return fs.existsSync(SETTINGS_FILE);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if ~/.claude.json exists
|
|
68
|
+
*/
|
|
69
|
+
export function claudeJsonExists(): boolean {
|
|
70
|
+
return fs.existsSync(CLAUDE_JSON);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read current Claude settings
|
|
75
|
+
*/
|
|
76
|
+
export async function readClaudeSettings(): Promise<ClaudeSettings> {
|
|
77
|
+
if (!claudeSettingsExists()) {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const content = await fs.readFile(SETTINGS_FILE, "utf-8");
|
|
82
|
+
return JSON.parse(content);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read ~/.claude.json
|
|
87
|
+
*/
|
|
88
|
+
export async function readClaudeJson(): Promise<ClaudeJson> {
|
|
89
|
+
if (!claudeJsonExists()) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const content = await fs.readFile(CLAUDE_JSON, "utf-8");
|
|
94
|
+
return JSON.parse(content);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Write Claude settings with backup
|
|
99
|
+
*/
|
|
100
|
+
export async function writeClaudeSettings(settings: ClaudeSettings): Promise<void> {
|
|
101
|
+
// Ensure directory exists
|
|
102
|
+
await fs.ensureDir(CLAUDE_DIR);
|
|
103
|
+
|
|
104
|
+
// Backup existing settings if they exist
|
|
105
|
+
if (claudeSettingsExists()) {
|
|
106
|
+
const backupPath = `${SETTINGS_FILE}.backup.${Date.now()}`;
|
|
107
|
+
await fs.copyFile(SETTINGS_FILE, backupPath);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Write new settings
|
|
111
|
+
await fs.writeFile(SETTINGS_FILE, JSON.stringify(settings, null, 2), "utf-8");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Write ~/.claude.json with backup
|
|
116
|
+
*/
|
|
117
|
+
export async function writeClaudeJson(config: ClaudeJson): Promise<void> {
|
|
118
|
+
// Backup existing file if it exists
|
|
119
|
+
if (claudeJsonExists()) {
|
|
120
|
+
const backupPath = `${CLAUDE_JSON}.backup.${Date.now()}`;
|
|
121
|
+
await fs.copyFile(CLAUDE_JSON, backupPath);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Write new config
|
|
125
|
+
await fs.writeFile(CLAUDE_JSON, JSON.stringify(config, null, 2), "utf-8");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Ensure hasCompletedOnboarding is set to true in ~/.claude.json
|
|
130
|
+
* This prevents "Unable to connect to Anthropic services" error
|
|
131
|
+
*/
|
|
132
|
+
export async function ensureOnboardingComplete(): Promise<void> {
|
|
133
|
+
const config = await readClaudeJson();
|
|
134
|
+
config.hasCompletedOnboarding = true;
|
|
135
|
+
await writeClaudeJson(config);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Configure Claude Code for Alibaba Coding Plan
|
|
140
|
+
*/
|
|
141
|
+
export async function configureAlibaba(apiKey: string, model: string, tierMap: ModelTierMap): Promise<void> {
|
|
142
|
+
await ensureOnboardingComplete();
|
|
143
|
+
|
|
144
|
+
const settings = await readClaudeSettings();
|
|
145
|
+
|
|
146
|
+
settings.env = settings.env || {};
|
|
147
|
+
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
148
|
+
settings.env["ANTHROPIC_BASE_URL"] = "https://coding-intl.dashscope.aliyuncs.com/apps/anthropic";
|
|
149
|
+
settings.env["ANTHROPIC_MODEL"] = model;
|
|
150
|
+
|
|
151
|
+
applyTierMap(settings, tierMap);
|
|
152
|
+
await writeClaudeSettings(settings);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Configure Claude Code for Anthropic (default)
|
|
157
|
+
* Removes MCP overrides and tier map env vars to use native Claude
|
|
158
|
+
*/
|
|
159
|
+
export async function configureAnthropic(): Promise<void> {
|
|
160
|
+
await ensureOnboardingComplete();
|
|
161
|
+
|
|
162
|
+
const settings = await readClaudeSettings();
|
|
163
|
+
|
|
164
|
+
if (settings.mcpServers) {
|
|
165
|
+
delete settings.mcpServers["alibaba-coding-plan"];
|
|
166
|
+
delete settings.mcpServers["glm-coding-plan"];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Clear Alibaba env vars
|
|
170
|
+
if (settings.env) {
|
|
171
|
+
delete settings.env["ANTHROPIC_AUTH_TOKEN"];
|
|
172
|
+
delete settings.env["ANTHROPIC_BASE_URL"];
|
|
173
|
+
delete settings.env["ANTHROPIC_MODEL"];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
clearTierMap(settings);
|
|
177
|
+
await writeClaudeSettings(settings);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Configure Claude Code for GLM via coding-helper
|
|
182
|
+
* Clears provider-specific env vars (e.g. Alibaba) before applying GLM tier map
|
|
183
|
+
*/
|
|
184
|
+
export async function configureGLM(tierMap: ModelTierMap): Promise<void> {
|
|
185
|
+
await ensureOnboardingComplete();
|
|
186
|
+
|
|
187
|
+
const settings = await readClaudeSettings();
|
|
188
|
+
|
|
189
|
+
// Clear other provider env vars (e.g. Alibaba)
|
|
190
|
+
if (settings.env) {
|
|
191
|
+
delete settings.env["ANTHROPIC_AUTH_TOKEN"];
|
|
192
|
+
delete settings.env["ANTHROPIC_BASE_URL"];
|
|
193
|
+
delete settings.env["ANTHROPIC_MODEL"];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
applyTierMap(settings, tierMap);
|
|
197
|
+
await writeClaudeSettings(settings);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Configure Claude Code for OpenRouter
|
|
202
|
+
* Sets env vars to route through OpenRouter's Anthropic-compatible API
|
|
203
|
+
*/
|
|
204
|
+
export async function configureOpenRouter(apiKey: string, model: string, tierMap: ModelTierMap): Promise<void> {
|
|
205
|
+
await ensureOnboardingComplete();
|
|
206
|
+
|
|
207
|
+
const settings = await readClaudeSettings();
|
|
208
|
+
|
|
209
|
+
settings.env = settings.env || {};
|
|
210
|
+
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
211
|
+
settings.env["ANTHROPIC_BASE_URL"] = "https://openrouter.ai/api/v1";
|
|
212
|
+
settings.env["ANTHROPIC_MODEL"] = model;
|
|
213
|
+
|
|
214
|
+
applyTierMap(settings, tierMap);
|
|
215
|
+
await writeClaudeSettings(settings);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Configure Claude Code for Ollama (via LiteLLM proxy on port 4000)
|
|
220
|
+
*/
|
|
221
|
+
export async function configureOllama(model: string, tierMap: ModelTierMap): Promise<void> {
|
|
222
|
+
await ensureOnboardingComplete();
|
|
223
|
+
|
|
224
|
+
const settings = await readClaudeSettings();
|
|
225
|
+
|
|
226
|
+
settings.env = settings.env || {};
|
|
227
|
+
settings.env["ANTHROPIC_AUTH_TOKEN"] = "ollama";
|
|
228
|
+
settings.env["ANTHROPIC_BASE_URL"] = "http://localhost:4000";
|
|
229
|
+
settings.env["ANTHROPIC_MODEL"] = model;
|
|
230
|
+
|
|
231
|
+
applyTierMap(settings, tierMap);
|
|
232
|
+
await writeClaudeSettings(settings);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Configure Claude Code for Gemini (via LiteLLM proxy on port 4001)
|
|
237
|
+
*/
|
|
238
|
+
export async function configureGemini(apiKey: string, model: string, tierMap: ModelTierMap): Promise<void> {
|
|
239
|
+
await ensureOnboardingComplete();
|
|
240
|
+
|
|
241
|
+
const settings = await readClaudeSettings();
|
|
242
|
+
|
|
243
|
+
settings.env = settings.env || {};
|
|
244
|
+
settings.env["ANTHROPIC_AUTH_TOKEN"] = apiKey;
|
|
245
|
+
settings.env["ANTHROPIC_BASE_URL"] = "http://localhost:4001";
|
|
246
|
+
settings.env["ANTHROPIC_MODEL"] = model;
|
|
247
|
+
|
|
248
|
+
applyTierMap(settings, tierMap);
|
|
249
|
+
await writeClaudeSettings(settings);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Get current provider from Claude settings
|
|
254
|
+
*/
|
|
255
|
+
export async function getCurrentProvider(): Promise<{
|
|
256
|
+
provider: string;
|
|
257
|
+
model?: string;
|
|
258
|
+
endpoint?: string;
|
|
259
|
+
tierMap?: { opus?: string; sonnet?: string; haiku?: string };
|
|
260
|
+
} | null> {
|
|
261
|
+
if (!claudeSettingsExists()) {
|
|
262
|
+
return { provider: "anthropic" };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const settings = await readClaudeSettings();
|
|
266
|
+
|
|
267
|
+
const tierMap = settings.env ? {
|
|
268
|
+
opus: settings.env[TIER_ENV_KEYS.opus],
|
|
269
|
+
sonnet: settings.env[TIER_ENV_KEYS.sonnet],
|
|
270
|
+
haiku: settings.env[TIER_ENV_KEYS.haiku]
|
|
271
|
+
} : undefined;
|
|
272
|
+
|
|
273
|
+
// Check for Alibaba via env vars
|
|
274
|
+
if (settings.env?.["ANTHROPIC_BASE_URL"]?.includes("coding-intl.dashscope.aliyuncs.com")) {
|
|
275
|
+
return {
|
|
276
|
+
provider: "alibaba",
|
|
277
|
+
model: settings.env["ANTHROPIC_MODEL"],
|
|
278
|
+
endpoint: settings.env["ANTHROPIC_BASE_URL"],
|
|
279
|
+
tierMap
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Check for OpenRouter via env vars
|
|
284
|
+
if (settings.env?.["ANTHROPIC_BASE_URL"]?.includes("openrouter.ai")) {
|
|
285
|
+
return {
|
|
286
|
+
provider: "openrouter",
|
|
287
|
+
model: settings.env["ANTHROPIC_MODEL"],
|
|
288
|
+
endpoint: settings.env["ANTHROPIC_BASE_URL"],
|
|
289
|
+
tierMap
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for Ollama via LiteLLM proxy on port 4000
|
|
294
|
+
if (settings.env?.["ANTHROPIC_BASE_URL"]?.includes("localhost:4000")) {
|
|
295
|
+
return {
|
|
296
|
+
provider: "ollama",
|
|
297
|
+
model: settings.env["ANTHROPIC_MODEL"],
|
|
298
|
+
endpoint: settings.env["ANTHROPIC_BASE_URL"],
|
|
299
|
+
tierMap
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Check for Gemini via LiteLLM proxy on port 4001
|
|
304
|
+
if (settings.env?.["ANTHROPIC_BASE_URL"]?.includes("localhost:4001")) {
|
|
305
|
+
return {
|
|
306
|
+
provider: "gemini",
|
|
307
|
+
model: settings.env["ANTHROPIC_MODEL"],
|
|
308
|
+
endpoint: settings.env["ANTHROPIC_BASE_URL"],
|
|
309
|
+
tierMap
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (settings.mcpServers?.["glm-coding-plan"]) {
|
|
314
|
+
return {
|
|
315
|
+
provider: "glm",
|
|
316
|
+
model: settings.mcpServers["glm-coding-plan"].model,
|
|
317
|
+
tierMap
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Check for GLM via z.ai endpoint (set by coding-helper auth reload)
|
|
322
|
+
if (settings.env?.["ANTHROPIC_BASE_URL"]?.includes(".z.ai")) {
|
|
323
|
+
return {
|
|
324
|
+
provider: "glm",
|
|
325
|
+
model: settings.env["ANTHROPIC_MODEL"],
|
|
326
|
+
endpoint: settings.env["ANTHROPIC_BASE_URL"],
|
|
327
|
+
tierMap
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Check for GLM via tier map env vars (no BASE_URL but tier aliases are set)
|
|
332
|
+
if (!settings.env?.["ANTHROPIC_BASE_URL"] && tierMap?.opus) {
|
|
333
|
+
return {
|
|
334
|
+
provider: "glm",
|
|
335
|
+
tierMap
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return { provider: "anthropic" };
|
|
340
|
+
}
|