claude-ai-switcher 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +265 -0
  2. package/ARCHITECTURE.md +162 -0
  3. package/CLAUDE.md +267 -0
  4. package/LICENSE +21 -0
  5. package/QWEN.md +429 -0
  6. package/README.md +833 -0
  7. package/dist/clients/claude-code.d.ts +92 -0
  8. package/dist/clients/claude-code.d.ts.map +1 -0
  9. package/dist/clients/claude-code.js +312 -0
  10. package/dist/clients/claude-code.js.map +1 -0
  11. package/dist/clients/opencode.d.ts +71 -0
  12. package/dist/clients/opencode.d.ts.map +1 -0
  13. package/dist/clients/opencode.js +604 -0
  14. package/dist/clients/opencode.js.map +1 -0
  15. package/dist/config.d.ts +37 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +122 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/display.d.ts +51 -0
  20. package/dist/display.d.ts.map +1 -0
  21. package/dist/display.js +118 -0
  22. package/dist/display.js.map +1 -0
  23. package/dist/hooks/index.d.ts +60 -0
  24. package/dist/hooks/index.d.ts.map +1 -0
  25. package/dist/hooks/index.js +223 -0
  26. package/dist/hooks/index.js.map +1 -0
  27. package/dist/hooks/token-tracker.js +280 -0
  28. package/dist/hooks/visual-enhancements.js +364 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +1091 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/models.d.ts +34 -0
  34. package/dist/models.d.ts.map +1 -0
  35. package/dist/models.js +343 -0
  36. package/dist/models.js.map +1 -0
  37. package/dist/providers/alibaba.d.ts +25 -0
  38. package/dist/providers/alibaba.d.ts.map +1 -0
  39. package/dist/providers/alibaba.js +37 -0
  40. package/dist/providers/alibaba.js.map +1 -0
  41. package/dist/providers/anthropic.d.ts +14 -0
  42. package/dist/providers/anthropic.d.ts.map +1 -0
  43. package/dist/providers/anthropic.js +19 -0
  44. package/dist/providers/anthropic.js.map +1 -0
  45. package/dist/providers/gemini.d.ts +44 -0
  46. package/dist/providers/gemini.d.ts.map +1 -0
  47. package/dist/providers/gemini.js +156 -0
  48. package/dist/providers/gemini.js.map +1 -0
  49. package/dist/providers/glm.d.ts +25 -0
  50. package/dist/providers/glm.d.ts.map +1 -0
  51. package/dist/providers/glm.js +89 -0
  52. package/dist/providers/glm.js.map +1 -0
  53. package/dist/providers/ollama.d.ts +48 -0
  54. package/dist/providers/ollama.d.ts.map +1 -0
  55. package/dist/providers/ollama.js +174 -0
  56. package/dist/providers/ollama.js.map +1 -0
  57. package/dist/providers/openrouter.d.ts +24 -0
  58. package/dist/providers/openrouter.d.ts.map +1 -0
  59. package/dist/providers/openrouter.js +36 -0
  60. package/dist/providers/openrouter.js.map +1 -0
  61. package/dist/verify.d.ts +24 -0
  62. package/dist/verify.d.ts.map +1 -0
  63. package/dist/verify.js +262 -0
  64. package/dist/verify.js.map +1 -0
  65. package/package.json +57 -0
  66. package/scripts/copy-hooks.js +15 -0
  67. package/src/clients/claude-code.ts +340 -0
  68. package/src/clients/opencode.ts +618 -0
  69. package/src/config.ts +101 -0
  70. package/src/display.ts +151 -0
  71. package/src/hooks/index.ts +208 -0
  72. package/src/hooks/token-tracker.js +280 -0
  73. package/src/hooks/visual-enhancements.js +364 -0
  74. package/src/index.ts +1263 -0
  75. package/src/models.ts +366 -0
  76. package/src/providers/alibaba.ts +43 -0
  77. package/src/providers/anthropic.ts +23 -0
  78. package/src/providers/gemini.ts +136 -0
  79. package/src/providers/glm.ts +60 -0
  80. package/src/providers/ollama.ts +146 -0
  81. package/src/providers/openrouter.ts +42 -0
  82. package/src/verify.ts +258 -0
  83. package/tsconfig.json +19 -0
@@ -0,0 +1,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
+ }
@@ -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"}