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
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Token Tracker
|
|
3
|
+
*
|
|
4
|
+
* Tracks token usage across Claude Code sessions and displays
|
|
5
|
+
* context usage percentage with visual bar.
|
|
6
|
+
*
|
|
7
|
+
* Installed to: ~/.claude/token-tracker.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
const TRACKER_FILE = path.join(os.homedir(), '.claude', 'token-usage.json');
|
|
15
|
+
const SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json');
|
|
16
|
+
|
|
17
|
+
// Model context windows (matches src/models.ts)
|
|
18
|
+
const MODEL_CONTEXT_WINDOWS = {
|
|
19
|
+
// Alibaba Models
|
|
20
|
+
'qwen3.7-plus': 1000000,
|
|
21
|
+
'qwen3.6-plus': 1000000,
|
|
22
|
+
'qwen3-max-2026-01-23': 262144,
|
|
23
|
+
'qwen3-coder-next': 262144,
|
|
24
|
+
'qwen3-coder-plus': 1000000,
|
|
25
|
+
'glm-5': 200000,
|
|
26
|
+
'glm-4.7': 256000,
|
|
27
|
+
'glm-4.7-flash': 256000,
|
|
28
|
+
'kimi-k2.5': 200000,
|
|
29
|
+
'MiniMax-M2.5': 200000,
|
|
30
|
+
|
|
31
|
+
// GLM Models
|
|
32
|
+
'glm-5.1': 200000,
|
|
33
|
+
'glm-5.2[1m]': 1000000,
|
|
34
|
+
'glm-5v-turbo': 200000,
|
|
35
|
+
'glm-5-turbo': 200000,
|
|
36
|
+
|
|
37
|
+
// OpenRouter Models
|
|
38
|
+
'qwen/qwen3.6-plus:free': 131072,
|
|
39
|
+
'openrouter/free': 131072,
|
|
40
|
+
|
|
41
|
+
// Ollama Models
|
|
42
|
+
'deepseek-r1:latest': 128000,
|
|
43
|
+
'qwen2.5-coder:latest': 128000,
|
|
44
|
+
'llama3.1:latest': 128000,
|
|
45
|
+
'codellama:latest': 100000,
|
|
46
|
+
|
|
47
|
+
// Gemini Models
|
|
48
|
+
'gemini-2.5-pro': 1000000,
|
|
49
|
+
'gemini-2.5-flash': 1000000,
|
|
50
|
+
'gemini-2.5-flash-lite': 1000000,
|
|
51
|
+
|
|
52
|
+
// Anthropic Models
|
|
53
|
+
'claude-opus-4-6-20250205': 200000,
|
|
54
|
+
'claude-opus-4-5-20251101': 200000,
|
|
55
|
+
'claude-sonnet-4-6-20250219': 200000,
|
|
56
|
+
'claude-sonnet-4-5-20250814': 200000,
|
|
57
|
+
'claude-haiku-4-5-20251015': 200000,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get current model from Claude settings
|
|
62
|
+
*/
|
|
63
|
+
function getCurrentModel() {
|
|
64
|
+
try {
|
|
65
|
+
if (!fs.existsSync(SETTINGS_FILE)) {
|
|
66
|
+
return 'claude-opus-4-6-20250205'; // Default
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf-8'));
|
|
70
|
+
|
|
71
|
+
// Check ANTHROPIC_MODEL env var
|
|
72
|
+
if (settings.env?.ANTHROPIC_MODEL) {
|
|
73
|
+
return settings.env.ANTHROPIC_MODEL;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check tier map aliases (use opus as primary)
|
|
77
|
+
if (settings.env?.ANTHROPIC_DEFAULT_OPUS_MODEL) {
|
|
78
|
+
return settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return 'claude-opus-4-6-20250205';
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return 'claude-opus-4-6-20250205';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get context window for current model
|
|
89
|
+
*/
|
|
90
|
+
function getContextWindow(modelId) {
|
|
91
|
+
return MODEL_CONTEXT_WINDOWS[modelId] || 200000; // Default to 200K
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load token usage data
|
|
96
|
+
*/
|
|
97
|
+
function loadTokenUsage() {
|
|
98
|
+
try {
|
|
99
|
+
if (!fs.existsSync(TRACKER_FILE)) {
|
|
100
|
+
return {
|
|
101
|
+
totalInputTokens: 0,
|
|
102
|
+
totalOutputTokens: 0,
|
|
103
|
+
sessionStart: new Date().toISOString(),
|
|
104
|
+
lastUpdated: new Date().toISOString()
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const data = JSON.parse(fs.readFileSync(TRACKER_FILE, 'utf-8'));
|
|
109
|
+
if (
|
|
110
|
+
typeof data.totalInputTokens !== 'number' ||
|
|
111
|
+
typeof data.totalOutputTokens !== 'number'
|
|
112
|
+
) {
|
|
113
|
+
throw new Error('Invalid token usage data');
|
|
114
|
+
}
|
|
115
|
+
return data;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
return {
|
|
118
|
+
totalInputTokens: 0,
|
|
119
|
+
totalOutputTokens: 0,
|
|
120
|
+
sessionStart: new Date().toISOString(),
|
|
121
|
+
lastUpdated: new Date().toISOString()
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Save token usage data
|
|
128
|
+
*/
|
|
129
|
+
function saveTokenUsage(usage) {
|
|
130
|
+
try {
|
|
131
|
+
fs.writeFileSync(TRACKER_FILE, JSON.stringify(usage, null, 2), 'utf-8');
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Silently fail - don't break Claude Code
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Reset token usage for new session
|
|
139
|
+
*/
|
|
140
|
+
function resetTokenUsage() {
|
|
141
|
+
const usage = {
|
|
142
|
+
totalInputTokens: 0,
|
|
143
|
+
totalOutputTokens: 0,
|
|
144
|
+
sessionStart: new Date().toISOString(),
|
|
145
|
+
lastUpdated: new Date().toISOString()
|
|
146
|
+
};
|
|
147
|
+
saveTokenUsage(usage);
|
|
148
|
+
return usage;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Add tokens to tracker
|
|
153
|
+
*/
|
|
154
|
+
function addTokens(inputTokens, outputTokens) {
|
|
155
|
+
const usage = loadTokenUsage();
|
|
156
|
+
usage.totalInputTokens += inputTokens || 0;
|
|
157
|
+
usage.totalOutputTokens += outputTokens || 0;
|
|
158
|
+
usage.lastUpdated = new Date().toISOString();
|
|
159
|
+
saveTokenUsage(usage);
|
|
160
|
+
return usage;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Format number with commas
|
|
165
|
+
*/
|
|
166
|
+
function formatNumber(num) {
|
|
167
|
+
return num.toLocaleString();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Create visual context bar
|
|
172
|
+
*/
|
|
173
|
+
function createContextBar(percentage) {
|
|
174
|
+
const barWidth = 20;
|
|
175
|
+
const filled = Math.round((percentage / 100) * barWidth);
|
|
176
|
+
const empty = barWidth - filled;
|
|
177
|
+
|
|
178
|
+
const filledChar = '█';
|
|
179
|
+
const emptyChar = '░';
|
|
180
|
+
|
|
181
|
+
return filledChar.repeat(filled) + emptyChar.repeat(empty);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Get color based on percentage
|
|
186
|
+
*/
|
|
187
|
+
function getPercentageColor(percentage) {
|
|
188
|
+
if (percentage < 50) return '\x1b[32m'; // Green
|
|
189
|
+
if (percentage < 75) return '\x1b[33m'; // Yellow
|
|
190
|
+
if (percentage < 90) return '\x1b[31m'; // Red
|
|
191
|
+
return '\x1b[35m'; // Magenta (critical)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Display token usage with context bar
|
|
196
|
+
*/
|
|
197
|
+
function displayTokenUsage() {
|
|
198
|
+
const model = getCurrentModel();
|
|
199
|
+
const contextWindow = getContextWindow(model);
|
|
200
|
+
const usage = loadTokenUsage();
|
|
201
|
+
|
|
202
|
+
const totalTokens = usage.totalInputTokens + usage.totalOutputTokens;
|
|
203
|
+
const percentage = Math.min((totalTokens / contextWindow) * 100, 100);
|
|
204
|
+
|
|
205
|
+
const color = getPercentageColor(percentage);
|
|
206
|
+
const reset = '\x1b[0m';
|
|
207
|
+
const bar = createContextBar(percentage);
|
|
208
|
+
|
|
209
|
+
// Format model name (truncate if too long for the box)
|
|
210
|
+
let modelName = model.split('-').map(
|
|
211
|
+
word => word.charAt(0).toUpperCase() + word.slice(1)
|
|
212
|
+
).join(' ');
|
|
213
|
+
if (modelName.length > 41) {
|
|
214
|
+
modelName = modelName.substring(0, 38) + '...';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(`${color}╔══════════════════════════════════════════════════════════════╗${reset}`);
|
|
219
|
+
console.log(`${color}║${reset} ${color}🤖 Active Model:${reset} ${modelName.padEnd(41)}${color}║${reset}`);
|
|
220
|
+
console.log(`${color}╠══════════════════════════════════════════════════════════════╣${reset}`);
|
|
221
|
+
console.log(`${color}║${reset} ${color}📊 Token Usage:${reset}${' '.repeat(38)}${color}║${reset}`);
|
|
222
|
+
console.log(`${color}║${reset} Input: ${formatNumber(usage.totalInputTokens).padEnd(10)}tokens${' '.repeat(18)}${color}║${reset}`);
|
|
223
|
+
console.log(`${color}║${reset} Output: ${formatNumber(usage.totalOutputTokens).padEnd(10)}tokens${' '.repeat(18)}${color}║${reset}`);
|
|
224
|
+
console.log(`${color}║${reset} Total: ${formatNumber(totalTokens).padEnd(10)}tokens${' '.repeat(18)}${color}║${reset}`);
|
|
225
|
+
console.log(`${color}╠══════════════════════════════════════════════════════════════╣${reset}`);
|
|
226
|
+
console.log(`${color}║${reset} ${color}📈 Context Window:${reset}${' '.repeat(34)}${color}║${reset}`);
|
|
227
|
+
console.log(`${color}║${reset} Used: ${formatNumber(totalTokens).padEnd(10)}tokens${' '.repeat(18)}${color}║${reset}`);
|
|
228
|
+
console.log(`${color}║${reset} Total: ${formatNumber(contextWindow).padEnd(10)}tokens${' '.repeat(18)}${color}║${reset}`);
|
|
229
|
+
console.log(`${color}║${reset} ${color}${bar}${reset} ${percentage.toFixed(1).padStart(5)}%${' '.repeat(10)}${color}║${reset}`);
|
|
230
|
+
console.log(`${color}╚══════════════════════════════════════════════════════════════╝${reset}`);
|
|
231
|
+
console.log('');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Hook: Called when Claude Code starts
|
|
236
|
+
*/
|
|
237
|
+
function onSessionStart() {
|
|
238
|
+
resetTokenUsage();
|
|
239
|
+
displayTokenUsage();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Hook: Called after each API response
|
|
244
|
+
* This would need to be integrated with Claude Code's response handler
|
|
245
|
+
*/
|
|
246
|
+
function onApiResponse(inputTokens, outputTokens) {
|
|
247
|
+
addTokens(inputTokens, outputTokens);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Hook: Display current status (can be called manually)
|
|
252
|
+
*/
|
|
253
|
+
function showStatus() {
|
|
254
|
+
displayTokenUsage();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Export functions for use
|
|
259
|
+
*/
|
|
260
|
+
module.exports = {
|
|
261
|
+
onSessionStart,
|
|
262
|
+
onApiResponse,
|
|
263
|
+
showStatus,
|
|
264
|
+
addTokens,
|
|
265
|
+
loadTokenUsage,
|
|
266
|
+
resetTokenUsage,
|
|
267
|
+
getCurrentModel,
|
|
268
|
+
getContextWindow
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Auto-run if called directly
|
|
272
|
+
if (require.main === module) {
|
|
273
|
+
const args = process.argv.slice(2);
|
|
274
|
+
if (args.includes('--reset')) {
|
|
275
|
+
resetTokenUsage();
|
|
276
|
+
console.log('Token usage reset.');
|
|
277
|
+
} else {
|
|
278
|
+
displayTokenUsage();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Visual Enhancements
|
|
3
|
+
*
|
|
4
|
+
* Provides visual enhancements for Claude Code including:
|
|
5
|
+
* - Active model display with provider info
|
|
6
|
+
* - Context usage bar (percentage)
|
|
7
|
+
* - Provider endpoint display
|
|
8
|
+
* - Custom system prompt injection
|
|
9
|
+
*
|
|
10
|
+
* Installed to: ~/.claude/visual-enhancements.js
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
const SETTINGS_FILE = path.join(os.homedir(), '.claude', 'settings.json');
|
|
18
|
+
const PROMPT_FILE = path.join(os.homedir(), '.claude', 'prompt.json');
|
|
19
|
+
|
|
20
|
+
// Provider information
|
|
21
|
+
const PROVIDER_INFO = {
|
|
22
|
+
anthropic: {
|
|
23
|
+
name: 'Anthropic',
|
|
24
|
+
endpoint: 'api.anthropic.com',
|
|
25
|
+
icon: '🎭'
|
|
26
|
+
},
|
|
27
|
+
alibaba: {
|
|
28
|
+
name: 'Alibaba Model Studio',
|
|
29
|
+
endpoint: 'coding-intl.dashscope.aliyuncs.com',
|
|
30
|
+
icon: ''
|
|
31
|
+
},
|
|
32
|
+
glm: {
|
|
33
|
+
name: 'GLM/Z.AI',
|
|
34
|
+
endpoint: 'z.ai',
|
|
35
|
+
icon: '🤖'
|
|
36
|
+
},
|
|
37
|
+
openrouter: {
|
|
38
|
+
name: 'OpenRouter',
|
|
39
|
+
endpoint: 'openrouter.ai',
|
|
40
|
+
icon: '🌐'
|
|
41
|
+
},
|
|
42
|
+
ollama: {
|
|
43
|
+
name: 'Ollama (Local)',
|
|
44
|
+
endpoint: 'localhost:4000',
|
|
45
|
+
icon: ''
|
|
46
|
+
},
|
|
47
|
+
gemini: {
|
|
48
|
+
name: 'Gemini (Google)',
|
|
49
|
+
endpoint: 'localhost:4001',
|
|
50
|
+
icon: '💎'
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Model capabilities for display
|
|
55
|
+
const MODEL_CAPABILITIES = {
|
|
56
|
+
// Alibaba Models
|
|
57
|
+
'qwen3.7-plus': ['Text Generation', 'Deep Thinking', 'Visual Understanding'],
|
|
58
|
+
'qwen3.6-plus': ['Text Generation', 'Deep Thinking', 'Visual Understanding'],
|
|
59
|
+
'qwen3-max-2026-01-23': ['Text Generation', 'Deep Thinking'],
|
|
60
|
+
'qwen3-coder-next': ['Text Generation', 'Coding Agent'],
|
|
61
|
+
'qwen3-coder-plus': ['Text Generation', 'Coding', '1M Context'],
|
|
62
|
+
'glm-5': ['Text Generation', 'Deep Thinking'],
|
|
63
|
+
'glm-4.7': ['Text Generation', 'Deep Thinking'],
|
|
64
|
+
'glm-4.7-flash': ['Text Generation', 'Fast Inference'],
|
|
65
|
+
'kimi-k2.5': ['Text Generation', 'Deep Thinking', 'Visual Understanding'],
|
|
66
|
+
'MiniMax-M2.5': ['Text Generation', 'Deep Thinking'],
|
|
67
|
+
|
|
68
|
+
// GLM Models
|
|
69
|
+
'glm-5.1': ['Text Generation', 'Deep Thinking'],
|
|
70
|
+
'glm-5.2[1m]': ['Text Generation', 'Deep Thinking', '1M Context'],
|
|
71
|
+
'glm-5v-turbo': ['Text Generation', 'Deep Thinking', 'Multimodal'],
|
|
72
|
+
'glm-5-turbo': ['Text Generation', 'Deep Thinking', 'Fast'],
|
|
73
|
+
|
|
74
|
+
// OpenRouter Models
|
|
75
|
+
'qwen/qwen3.6-plus:free': ['Text Generation', 'Deep Thinking'],
|
|
76
|
+
'openrouter/free': ['Text Generation'],
|
|
77
|
+
|
|
78
|
+
// Ollama Models
|
|
79
|
+
'deepseek-r1:latest': ['Text Generation', 'Deep Thinking', 'Reasoning'],
|
|
80
|
+
'qwen2.5-coder:latest': ['Text Generation', 'Coding', 'Tool Calling'],
|
|
81
|
+
'llama3.1:latest': ['Text Generation', 'Code', 'Vision'],
|
|
82
|
+
'codellama:latest': ['Text Generation', 'Coding'],
|
|
83
|
+
|
|
84
|
+
// Gemini Models
|
|
85
|
+
'gemini-2.5-pro': ['Text Generation', 'Deep Thinking', 'Code', 'Vision'],
|
|
86
|
+
'gemini-2.5-flash': ['Text Generation', 'Fast Responses', 'Code'],
|
|
87
|
+
'gemini-2.5-flash-lite': ['Text Generation', 'Cost-optimized'],
|
|
88
|
+
|
|
89
|
+
// Anthropic Models
|
|
90
|
+
'claude-opus-4-6-20250205': ['Text Generation', 'Code', 'Vision', 'Complex Reasoning'],
|
|
91
|
+
'claude-opus-4-5-20251101': ['Text Generation', 'Code', 'Vision', 'Complex Reasoning'],
|
|
92
|
+
'claude-sonnet-4-6-20250219': ['Text Generation', 'Code', 'Vision'],
|
|
93
|
+
'claude-sonnet-4-5-20250814': ['Text Generation', 'Code', 'Vision'],
|
|
94
|
+
'claude-haiku-4-5-20251015': ['Text Generation', 'Fast Responses'],
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detect current provider from settings
|
|
99
|
+
*/
|
|
100
|
+
function detectProvider() {
|
|
101
|
+
try {
|
|
102
|
+
if (!fs.existsSync(SETTINGS_FILE)) {
|
|
103
|
+
return 'anthropic';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf-8'));
|
|
107
|
+
const baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
108
|
+
|
|
109
|
+
if (baseUrl.includes('coding-intl.dashscope.aliyuncs.com')) {
|
|
110
|
+
return 'alibaba';
|
|
111
|
+
}
|
|
112
|
+
if (baseUrl.includes('openrouter.ai')) {
|
|
113
|
+
return 'openrouter';
|
|
114
|
+
}
|
|
115
|
+
if (baseUrl.includes('localhost:4000')) {
|
|
116
|
+
return 'ollama';
|
|
117
|
+
}
|
|
118
|
+
if (baseUrl.includes('localhost:4001')) {
|
|
119
|
+
return 'gemini';
|
|
120
|
+
}
|
|
121
|
+
if (baseUrl.includes('z.ai')) {
|
|
122
|
+
return 'glm';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Check tier map aliases (GLM indicator)
|
|
126
|
+
if (settings.env?.ANTHROPIC_DEFAULT_OPUS_MODEL && !baseUrl) {
|
|
127
|
+
return 'glm';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return 'anthropic';
|
|
131
|
+
} catch (error) {
|
|
132
|
+
return 'anthropic';
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get current model from settings
|
|
138
|
+
*/
|
|
139
|
+
function getCurrentModel() {
|
|
140
|
+
try {
|
|
141
|
+
if (!fs.existsSync(SETTINGS_FILE)) {
|
|
142
|
+
return 'claude-opus-4-6-20250205';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_FILE, 'utf-8'));
|
|
146
|
+
|
|
147
|
+
if (settings.env?.ANTHROPIC_MODEL) {
|
|
148
|
+
return settings.env.ANTHROPIC_MODEL;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (settings.env?.ANTHROPIC_DEFAULT_OPUS_MODEL) {
|
|
152
|
+
return settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return 'claude-opus-4-6-20250205';
|
|
156
|
+
} catch (error) {
|
|
157
|
+
return 'claude-opus-4-6-20250205';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get context window for model
|
|
163
|
+
*/
|
|
164
|
+
function getContextWindow(modelId) {
|
|
165
|
+
const CONTEXT_WINDOWS = {
|
|
166
|
+
'qwen3.7-plus': 1000000,
|
|
167
|
+
'qwen3.6-plus': 1000000,
|
|
168
|
+
'qwen3-max-2026-01-23': 262144,
|
|
169
|
+
'qwen3-coder-next': 262144,
|
|
170
|
+
'qwen3-coder-plus': 1000000,
|
|
171
|
+
'glm-5': 200000,
|
|
172
|
+
'glm-4.7': 256000,
|
|
173
|
+
'glm-4.7-flash': 256000,
|
|
174
|
+
'kimi-k2.5': 200000,
|
|
175
|
+
'MiniMax-M2.5': 200000,
|
|
176
|
+
'glm-5.1': 200000,
|
|
177
|
+
'glm-5.2[1m]': 1000000,
|
|
178
|
+
'glm-5v-turbo': 200000,
|
|
179
|
+
'glm-5-turbo': 200000,
|
|
180
|
+
'qwen/qwen3.6-plus:free': 131072,
|
|
181
|
+
'openrouter/free': 131072,
|
|
182
|
+
'deepseek-r1:latest': 128000,
|
|
183
|
+
'qwen2.5-coder:latest': 128000,
|
|
184
|
+
'llama3.1:latest': 128000,
|
|
185
|
+
'codellama:latest': 100000,
|
|
186
|
+
'gemini-2.5-pro': 1000000,
|
|
187
|
+
'gemini-2.5-flash': 1000000,
|
|
188
|
+
'gemini-2.5-flash-lite': 1000000,
|
|
189
|
+
'claude-opus-4-6-20250205': 200000,
|
|
190
|
+
'claude-opus-4-5-20251101': 200000,
|
|
191
|
+
'claude-sonnet-4-6-20250219': 200000,
|
|
192
|
+
'claude-sonnet-4-5-20250814': 200000,
|
|
193
|
+
'claude-haiku-4-5-20251015': 200000,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
return CONTEXT_WINDOWS[modelId] || 200000;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Format context number
|
|
201
|
+
*/
|
|
202
|
+
function formatContext(tokens) {
|
|
203
|
+
if (tokens >= 1000000) {
|
|
204
|
+
return `${(tokens / 1000000).toFixed(1)}M`;
|
|
205
|
+
}
|
|
206
|
+
if (tokens >= 1000) {
|
|
207
|
+
return `${(tokens / 1000).toFixed(0)}K`;
|
|
208
|
+
}
|
|
209
|
+
return tokens.toString();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Create visual model card
|
|
214
|
+
*/
|
|
215
|
+
function createModelCard() {
|
|
216
|
+
const provider = detectProvider();
|
|
217
|
+
const model = getCurrentModel();
|
|
218
|
+
const contextWindow = getContextWindow(model);
|
|
219
|
+
const providerInfo = PROVIDER_INFO[provider];
|
|
220
|
+
const capabilities = MODEL_CAPABILITIES[model] || [];
|
|
221
|
+
|
|
222
|
+
const reset = '\x1b[0m';
|
|
223
|
+
const bold = '\x1b[1m';
|
|
224
|
+
const dim = '\x1b[2m';
|
|
225
|
+
|
|
226
|
+
// Colors
|
|
227
|
+
const headerColor = '\x1b[36m'; // Cyan
|
|
228
|
+
const accentColor = '\x1b[33m'; // Yellow
|
|
229
|
+
const infoColor = '\x1b[37m'; // White
|
|
230
|
+
const dimColor = '\x1b[90m'; // Gray
|
|
231
|
+
|
|
232
|
+
const modelName = model.includes('claude-') ? model.replace('claude-', 'Claude ').replace(/-/g, ' ') : model;
|
|
233
|
+
|
|
234
|
+
let output = '';
|
|
235
|
+
output += `${dimColor}┌─────────────────────────────────────────────────────────────┐${reset}\n`;
|
|
236
|
+
output += `${dimColor}│${reset} ${headerColor}${bold}${providerInfo.icon} ${providerInfo.name}${reset}${dimColor}${' '.repeat(48)}│${reset}\n`;
|
|
237
|
+
output += `${dimColor}├─────────────────────────────────────────────────────────────┤${reset}\n`;
|
|
238
|
+
output += `${dimColor}│${reset} ${accentColor}Model:${reset} ${infoColor}${modelName}${' '.repeat(Math.max(0, 35 - modelName.length))}${dimColor}│${reset}\n`;
|
|
239
|
+
output += `${dimColor}│${reset} ${accentColor}Context:${reset} ${infoColor}${formatContext(contextWindow)} tokens${' '.repeat(Math.max(0, 31 - formatContext(contextWindow).length - 8))}${dimColor}│${reset}\n`;
|
|
240
|
+
|
|
241
|
+
if (capabilities.length > 0) {
|
|
242
|
+
const capStr = capabilities.slice(0, 3).join(' • ');
|
|
243
|
+
output += `${dimColor}│${reset} ${accentColor}Capabilities:${reset}${' '.repeat(Math.max(0, 38 - capStr.length - 14))}${dimColor}│${reset}\n`;
|
|
244
|
+
output += `${dimColor}│${reset} ${dimColor}${capStr}${' '.repeat(Math.max(0, 52 - capStr.length))}${dimColor}│${reset}\n`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
output += `${dimColor}└─────────────────────────────────────────────────────────────┘${reset}\n`;
|
|
248
|
+
|
|
249
|
+
return output;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Create context usage bar
|
|
254
|
+
*/
|
|
255
|
+
function createContextBar(usedTokens, totalTokens) {
|
|
256
|
+
if (!totalTokens || totalTokens <= 0) {
|
|
257
|
+
return '\x1b[32m' + '░'.repeat(30) + '\x1b[0m 0.0%';
|
|
258
|
+
}
|
|
259
|
+
const percentage = Math.min((usedTokens / totalTokens) * 100, 100);
|
|
260
|
+
const barWidth = 30;
|
|
261
|
+
const filled = Math.round((percentage / 100) * barWidth);
|
|
262
|
+
const empty = barWidth - filled;
|
|
263
|
+
|
|
264
|
+
const reset = '\x1b[0m';
|
|
265
|
+
const filledChar = '█';
|
|
266
|
+
const emptyChar = '░';
|
|
267
|
+
|
|
268
|
+
// Color based on usage
|
|
269
|
+
let color;
|
|
270
|
+
if (percentage < 50) {
|
|
271
|
+
color = '\x1b[32m'; // Green
|
|
272
|
+
} else if (percentage < 75) {
|
|
273
|
+
color = '\x1b[33m'; // Yellow
|
|
274
|
+
} else if (percentage < 90) {
|
|
275
|
+
color = '\x1b[31m'; // Red
|
|
276
|
+
} else {
|
|
277
|
+
color = '\x1b[35m'; // Magenta
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const bar = filledChar.repeat(filled) + emptyChar.repeat(empty);
|
|
281
|
+
|
|
282
|
+
return `${color}${bar}${reset} ${percentage.toFixed(1).padStart(5)}%`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Display full status (called on session start)
|
|
287
|
+
*/
|
|
288
|
+
function displayStatus() {
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log(createModelCard());
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Generate custom system prompt based on provider and model
|
|
295
|
+
*/
|
|
296
|
+
function generateSystemPrompt() {
|
|
297
|
+
const provider = detectProvider();
|
|
298
|
+
const model = getCurrentModel();
|
|
299
|
+
const providerInfo = PROVIDER_INFO[provider];
|
|
300
|
+
|
|
301
|
+
const prompt = {
|
|
302
|
+
system: [
|
|
303
|
+
`You are running on ${providerInfo.name} using the ${model} model.`,
|
|
304
|
+
`Provider endpoint: ${providerInfo.endpoint}`,
|
|
305
|
+
``,
|
|
306
|
+
`Current configuration:`,
|
|
307
|
+
`- Provider: ${providerInfo.name}`,
|
|
308
|
+
`- Model: ${model}`,
|
|
309
|
+
`- Context Window: ${formatContext(getContextWindow(model))} tokens`,
|
|
310
|
+
].join('\n')
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return prompt;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Write custom prompt to prompt.json
|
|
318
|
+
*/
|
|
319
|
+
function writeCustomPrompt() {
|
|
320
|
+
try {
|
|
321
|
+
const prompt = generateSystemPrompt();
|
|
322
|
+
fs.writeFileSync(PROMPT_FILE, JSON.stringify(prompt, null, 2), 'utf-8');
|
|
323
|
+
return true;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Initialize visual enhancements
|
|
331
|
+
*/
|
|
332
|
+
function init() {
|
|
333
|
+
displayStatus();
|
|
334
|
+
writeCustomPrompt();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Update visual enhancements (called after provider switch)
|
|
339
|
+
*/
|
|
340
|
+
function update() {
|
|
341
|
+
displayStatus();
|
|
342
|
+
writeCustomPrompt();
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Export functions
|
|
347
|
+
*/
|
|
348
|
+
module.exports = {
|
|
349
|
+
init,
|
|
350
|
+
update,
|
|
351
|
+
displayStatus,
|
|
352
|
+
createModelCard,
|
|
353
|
+
createContextBar,
|
|
354
|
+
generateSystemPrompt,
|
|
355
|
+
writeCustomPrompt,
|
|
356
|
+
detectProvider,
|
|
357
|
+
getCurrentModel,
|
|
358
|
+
getContextWindow
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// Auto-run if called directly
|
|
362
|
+
if (require.main === module) {
|
|
363
|
+
init();
|
|
364
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude AI Switcher
|
|
4
|
+
*
|
|
5
|
+
* Switch between AI providers (Anthropic, GLM, Alibaba Qwen) for Claude Code.
|
|
6
|
+
* Also provides helper commands to add/remove Alibaba Coding Plan provider for OpenCode.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG"}
|