claude-ai-switcher 1.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +265 -0
  2. package/ARCHITECTURE.md +162 -0
  3. package/CLAUDE.md +267 -0
  4. package/LICENSE +21 -0
  5. package/QWEN.md +429 -0
  6. package/README.md +833 -0
  7. package/dist/clients/claude-code.d.ts +92 -0
  8. package/dist/clients/claude-code.d.ts.map +1 -0
  9. package/dist/clients/claude-code.js +312 -0
  10. package/dist/clients/claude-code.js.map +1 -0
  11. package/dist/clients/opencode.d.ts +71 -0
  12. package/dist/clients/opencode.d.ts.map +1 -0
  13. package/dist/clients/opencode.js +604 -0
  14. package/dist/clients/opencode.js.map +1 -0
  15. package/dist/config.d.ts +37 -0
  16. package/dist/config.d.ts.map +1 -0
  17. package/dist/config.js +122 -0
  18. package/dist/config.js.map +1 -0
  19. package/dist/display.d.ts +51 -0
  20. package/dist/display.d.ts.map +1 -0
  21. package/dist/display.js +118 -0
  22. package/dist/display.js.map +1 -0
  23. package/dist/hooks/index.d.ts +60 -0
  24. package/dist/hooks/index.d.ts.map +1 -0
  25. package/dist/hooks/index.js +223 -0
  26. package/dist/hooks/index.js.map +1 -0
  27. package/dist/hooks/token-tracker.js +280 -0
  28. package/dist/hooks/visual-enhancements.js +364 -0
  29. package/dist/index.d.ts +9 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +1091 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/models.d.ts +34 -0
  34. package/dist/models.d.ts.map +1 -0
  35. package/dist/models.js +343 -0
  36. package/dist/models.js.map +1 -0
  37. package/dist/providers/alibaba.d.ts +25 -0
  38. package/dist/providers/alibaba.d.ts.map +1 -0
  39. package/dist/providers/alibaba.js +37 -0
  40. package/dist/providers/alibaba.js.map +1 -0
  41. package/dist/providers/anthropic.d.ts +14 -0
  42. package/dist/providers/anthropic.d.ts.map +1 -0
  43. package/dist/providers/anthropic.js +19 -0
  44. package/dist/providers/anthropic.js.map +1 -0
  45. package/dist/providers/gemini.d.ts +44 -0
  46. package/dist/providers/gemini.d.ts.map +1 -0
  47. package/dist/providers/gemini.js +156 -0
  48. package/dist/providers/gemini.js.map +1 -0
  49. package/dist/providers/glm.d.ts +25 -0
  50. package/dist/providers/glm.d.ts.map +1 -0
  51. package/dist/providers/glm.js +89 -0
  52. package/dist/providers/glm.js.map +1 -0
  53. package/dist/providers/ollama.d.ts +48 -0
  54. package/dist/providers/ollama.d.ts.map +1 -0
  55. package/dist/providers/ollama.js +174 -0
  56. package/dist/providers/ollama.js.map +1 -0
  57. package/dist/providers/openrouter.d.ts +24 -0
  58. package/dist/providers/openrouter.d.ts.map +1 -0
  59. package/dist/providers/openrouter.js +36 -0
  60. package/dist/providers/openrouter.js.map +1 -0
  61. package/dist/verify.d.ts +24 -0
  62. package/dist/verify.d.ts.map +1 -0
  63. package/dist/verify.js +262 -0
  64. package/dist/verify.js.map +1 -0
  65. package/package.json +57 -0
  66. package/scripts/copy-hooks.js +15 -0
  67. package/src/clients/claude-code.ts +340 -0
  68. package/src/clients/opencode.ts +618 -0
  69. package/src/config.ts +101 -0
  70. package/src/display.ts +151 -0
  71. package/src/hooks/index.ts +208 -0
  72. package/src/hooks/token-tracker.js +280 -0
  73. package/src/hooks/visual-enhancements.js +364 -0
  74. package/src/index.ts +1263 -0
  75. package/src/models.ts +366 -0
  76. package/src/providers/alibaba.ts +43 -0
  77. package/src/providers/anthropic.ts +23 -0
  78. package/src/providers/gemini.ts +136 -0
  79. package/src/providers/glm.ts +60 -0
  80. package/src/providers/ollama.ts +146 -0
  81. package/src/providers/openrouter.ts +42 -0
  82. package/src/verify.ts +258 -0
  83. package/tsconfig.json +19 -0
package/src/display.ts ADDED
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Display utilities for formatted output
3
+ */
4
+
5
+ import chalk from "chalk";
6
+
7
+ /**
8
+ * Format a table row with proper padding
9
+ */
10
+ export function formatTableRow(
11
+ columns: string[],
12
+ widths: number[]
13
+ ): string {
14
+ return columns
15
+ .map((col, i) => col.padEnd(widths[i]))
16
+ .join(" ");
17
+ }
18
+
19
+ /**
20
+ * Display model list with descriptions and context sizes
21
+ */
22
+ export function displayModels(
23
+ providerName: string,
24
+ models: Array<{
25
+ id: string;
26
+ name: string;
27
+ contextWindow: number;
28
+ capabilities: string[];
29
+ description: string;
30
+ }>,
31
+ currentModel?: string
32
+ ): void {
33
+ console.log(chalk.green(`\n✓ Provider: ${providerName}`));
34
+ console.log(chalk.dim("─".repeat(80)) + "\n");
35
+
36
+ // Calculate column widths
37
+ const modelWidth = Math.max(20, ...models.map(m => m.id.length)) + 2;
38
+ const contextWidth = 15;
39
+
40
+ // Header
41
+ console.log(
42
+ chalk.cyan.bold(
43
+ formatTableRow(["Model", "Context", "Capabilities"], [modelWidth, contextWidth, 40])
44
+ )
45
+ );
46
+ console.log(chalk.dim("─".repeat(80)));
47
+
48
+ // Models
49
+ for (const model of models) {
50
+ const isCurrent = model.id === currentModel;
51
+ const modelDisplay = isCurrent
52
+ ? chalk.green.bold(`● ${model.id}`)
53
+ : chalk.white(` ${model.id}`);
54
+
55
+ const contextDisplay = chalk.yellow(formatContext(model.contextWindow));
56
+ const capabilitiesDisplay = chalk.gray(model.capabilities.join(", "));
57
+
58
+ console.log(
59
+ formatTableRow(
60
+ [modelDisplay, contextDisplay, capabilitiesDisplay],
61
+ [modelWidth, contextWidth, 40]
62
+ )
63
+ );
64
+
65
+ // Description on next line
66
+ console.log(chalk.dim(` ${model.description}`));
67
+ console.log();
68
+ }
69
+
70
+ console.log(chalk.dim("─".repeat(80)));
71
+ }
72
+
73
+ /**
74
+ * Display current provider status
75
+ */
76
+ export function displayCurrentStatus(
77
+ provider: string,
78
+ model?: string,
79
+ endpoint?: string
80
+ ): void {
81
+ console.log(chalk.green("\n✓ Current Configuration:\n"));
82
+ console.log(` ${chalk.cyan.bold("Provider:")} ${chalk.white(provider)}`);
83
+ if (model) {
84
+ console.log(` ${chalk.cyan.bold("Model:")} ${chalk.white(model)}`);
85
+ }
86
+ if (endpoint) {
87
+ console.log(` ${chalk.cyan.bold("Endpoint:")} ${chalk.dim(endpoint)}`);
88
+ }
89
+ console.log();
90
+ }
91
+
92
+ /**
93
+ * Display success message
94
+ */
95
+ export function displaySuccess(message: string): void {
96
+ console.log(chalk.green(`\n✓ ${message}\n`));
97
+ }
98
+
99
+ /**
100
+ * Display error message
101
+ */
102
+ export function displayError(message: string): void {
103
+ console.log(chalk.red(`\n✗ Error: ${message}\n`));
104
+ }
105
+
106
+ /**
107
+ * Display warning message
108
+ */
109
+ export function displayWarning(message: string): void {
110
+ console.log(chalk.yellow(`\n⚠ Warning: ${message}\n`));
111
+ }
112
+
113
+ /**
114
+ * Display info message
115
+ */
116
+ export function displayInfo(message: string): void {
117
+ console.log(chalk.blue(`\nℹ ${message}\n`));
118
+ }
119
+
120
+ /**
121
+ * Format context window for display
122
+ */
123
+ export function formatContext(tokens: number): string {
124
+ if (tokens >= 1000000) {
125
+ return `${(tokens / 1000000).toFixed(0)}M tokens`;
126
+ } else if (tokens >= 1000) {
127
+ return `${(tokens / 1000).toFixed(0)}K tokens`;
128
+ }
129
+ return `${tokens} tokens`;
130
+ }
131
+
132
+ /**
133
+ * Display provider list
134
+ */
135
+ export function displayProviders(providers: Array<{
136
+ id: string;
137
+ name: string;
138
+ endpoint?: string;
139
+ modelCount: number;
140
+ }>): void {
141
+ console.log(chalk.green("\n✓ Available Providers:\n"));
142
+
143
+ for (const provider of providers) {
144
+ console.log(chalk.cyan.bold(` ${provider.name} (${provider.id})`));
145
+ console.log(chalk.dim(` Models: ${provider.modelCount}`));
146
+ if (provider.endpoint) {
147
+ console.log(chalk.dim(` Endpoint: ${provider.endpoint}`));
148
+ }
149
+ console.log();
150
+ }
151
+ }
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Hook Manager
3
+ *
4
+ * Installs and manages Claude Code hooks for:
5
+ * - Token tracking
6
+ * - Visual enhancements (context bar, model display)
7
+ * - Custom system prompts
8
+ *
9
+ * Hooks are installed to ~/.claude/ directory
10
+ */
11
+
12
+ import * as fs from "fs-extra";
13
+ import * as path from "path";
14
+ import * as os from "os";
15
+ import { execFileSync } from "child_process";
16
+
17
+ const CLAUDE_DIR = path.join(os.homedir(), ".claude");
18
+ const TOKEN_TRACKER_SRC = path.join(__dirname, "..", "hooks", "token-tracker.js");
19
+ const VISUAL_ENHANCEMENTS_SRC = path.join(__dirname, "..", "hooks", "visual-enhancements.js");
20
+ const TOKEN_TRACKER_DEST = path.join(CLAUDE_DIR, "token-tracker.js");
21
+ const VISUAL_ENHANCEMENTS_DEST = path.join(CLAUDE_DIR, "visual-enhancements.js");
22
+ const HOOKS_CONFIG = path.join(CLAUDE_DIR, "hooks-config.json");
23
+
24
+ export interface HooksConfig {
25
+ tokenTracking: boolean;
26
+ visualEnhancements: boolean;
27
+ customPrompts: boolean;
28
+ lastInstalled?: string;
29
+ }
30
+
31
+ /**
32
+ * Check if hooks are installed
33
+ */
34
+ export async function areHooksInstalled(): Promise<{
35
+ tokenTracking: boolean;
36
+ visualEnhancements: boolean;
37
+ }> {
38
+ return {
39
+ tokenTracking: await fs.pathExists(TOKEN_TRACKER_DEST),
40
+ visualEnhancements: await fs.pathExists(VISUAL_ENHANCEMENTS_DEST)
41
+ };
42
+ }
43
+
44
+ /**
45
+ * Install token tracker hook
46
+ */
47
+ export async function installTokenTracker(): Promise<void> {
48
+ if (!await fs.pathExists(TOKEN_TRACKER_SRC)) {
49
+ throw new Error("Token tracker source not found. Please rebuild the project.");
50
+ }
51
+
52
+ await fs.ensureDir(CLAUDE_DIR);
53
+ await fs.copy(TOKEN_TRACKER_SRC, TOKEN_TRACKER_DEST, { overwrite: true });
54
+
55
+ const config = await readHooksConfig();
56
+ config.tokenTracking = true;
57
+ config.lastInstalled = new Date().toISOString();
58
+ await writeHooksConfig(config);
59
+ }
60
+
61
+ /**
62
+ * Install visual enhancements hook
63
+ */
64
+ export async function installVisualEnhancements(): Promise<void> {
65
+ if (!await fs.pathExists(VISUAL_ENHANCEMENTS_SRC)) {
66
+ throw new Error("Visual enhancements source not found. Please rebuild the project.");
67
+ }
68
+
69
+ await fs.ensureDir(CLAUDE_DIR);
70
+ await fs.copy(VISUAL_ENHANCEMENTS_SRC, VISUAL_ENHANCEMENTS_DEST, { overwrite: true });
71
+
72
+ const config = await readHooksConfig();
73
+ config.visualEnhancements = true;
74
+ config.lastInstalled = new Date().toISOString();
75
+ await writeHooksConfig(config);
76
+ }
77
+
78
+ /**
79
+ * Install all hooks
80
+ */
81
+ export async function installAllHooks(): Promise<void> {
82
+ await installTokenTracker();
83
+ await installVisualEnhancements();
84
+ }
85
+
86
+ /**
87
+ * Remove token tracker hook
88
+ */
89
+ export async function removeTokenTracker(): Promise<void> {
90
+ if (await fs.pathExists(TOKEN_TRACKER_DEST)) {
91
+ await fs.remove(TOKEN_TRACKER_DEST);
92
+ }
93
+
94
+ const config = await readHooksConfig();
95
+ config.tokenTracking = false;
96
+ await writeHooksConfig(config);
97
+ }
98
+
99
+ /**
100
+ * Remove visual enhancements hook
101
+ */
102
+ export async function removeVisualEnhancements(): Promise<void> {
103
+ if (await fs.pathExists(VISUAL_ENHANCEMENTS_DEST)) {
104
+ await fs.remove(VISUAL_ENHANCEMENTS_DEST);
105
+ }
106
+
107
+ const config = await readHooksConfig();
108
+ config.visualEnhancements = false;
109
+ await writeHooksConfig(config);
110
+ }
111
+
112
+ /**
113
+ * Remove all hooks
114
+ */
115
+ export async function removeAllHooks(): Promise<void> {
116
+ await removeTokenTracker();
117
+ await removeVisualEnhancements();
118
+ }
119
+
120
+ /**
121
+ * Read hooks configuration
122
+ */
123
+ async function readHooksConfig(): Promise<HooksConfig> {
124
+ if (!await fs.pathExists(HOOKS_CONFIG)) {
125
+ return {
126
+ tokenTracking: false,
127
+ visualEnhancements: false,
128
+ customPrompts: false
129
+ };
130
+ }
131
+
132
+ try {
133
+ const content = await fs.readFile(HOOKS_CONFIG, "utf-8");
134
+ return JSON.parse(content);
135
+ } catch {
136
+ return {
137
+ tokenTracking: false,
138
+ visualEnhancements: false,
139
+ customPrompts: false
140
+ };
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Write hooks configuration
146
+ */
147
+ async function writeHooksConfig(config: HooksConfig): Promise<void> {
148
+ await fs.writeFile(HOOKS_CONFIG, JSON.stringify(config, null, 2), "utf-8");
149
+ }
150
+
151
+ /**
152
+ * Run a hook script in a subprocess for isolation
153
+ */
154
+ function runHookScript(scriptPath: string, args: string[] = []): void {
155
+ execFileSync("node", [scriptPath, ...args], {
156
+ stdio: "inherit",
157
+ timeout: 10000
158
+ });
159
+ }
160
+
161
+ /**
162
+ * Run token tracker display
163
+ */
164
+ export async function showTokenStatus(): Promise<void> {
165
+ if (!await fs.pathExists(TOKEN_TRACKER_DEST)) {
166
+ console.log("Token tracker not installed. Run: claude-switch hooks install");
167
+ return;
168
+ }
169
+
170
+ try {
171
+ runHookScript(TOKEN_TRACKER_DEST);
172
+ } catch (error) {
173
+ console.error("Failed to run token tracker:", error instanceof Error ? error.message : error);
174
+ }
175
+ }
176
+
177
+ /**
178
+ * Run visual enhancements display
179
+ */
180
+ export async function showVisualStatus(): Promise<void> {
181
+ if (!await fs.pathExists(VISUAL_ENHANCEMENTS_DEST)) {
182
+ console.log("Visual enhancements not installed. Run: claude-switch hooks install");
183
+ return;
184
+ }
185
+
186
+ try {
187
+ runHookScript(VISUAL_ENHANCEMENTS_DEST);
188
+ } catch (error) {
189
+ console.error("Failed to run visual enhancements:", error instanceof Error ? error.message : error);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Reset token usage
195
+ */
196
+ export async function resetTokenUsage(): Promise<void> {
197
+ if (!await fs.pathExists(TOKEN_TRACKER_DEST)) {
198
+ console.log("Token tracker not installed. Run: claude-switch hooks install");
199
+ return;
200
+ }
201
+
202
+ try {
203
+ runHookScript(TOKEN_TRACKER_DEST, ["--reset"]);
204
+ console.log("Token usage reset complete.");
205
+ } catch (error) {
206
+ console.error("Failed to reset token usage:", error instanceof Error ? error.message : error);
207
+ }
208
+ }
@@ -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
+ }