claude-ai-switcher 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +265 -0
- package/ARCHITECTURE.md +162 -0
- package/CLAUDE.md +267 -0
- package/LICENSE +21 -0
- package/QWEN.md +429 -0
- package/README.md +833 -0
- package/dist/clients/claude-code.d.ts +92 -0
- package/dist/clients/claude-code.d.ts.map +1 -0
- package/dist/clients/claude-code.js +312 -0
- package/dist/clients/claude-code.js.map +1 -0
- package/dist/clients/opencode.d.ts +71 -0
- package/dist/clients/opencode.d.ts.map +1 -0
- package/dist/clients/opencode.js +604 -0
- package/dist/clients/opencode.js.map +1 -0
- package/dist/config.d.ts +37 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +122 -0
- package/dist/config.js.map +1 -0
- package/dist/display.d.ts +51 -0
- package/dist/display.d.ts.map +1 -0
- package/dist/display.js +118 -0
- package/dist/display.js.map +1 -0
- package/dist/hooks/index.d.ts +60 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +223 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/token-tracker.js +280 -0
- package/dist/hooks/visual-enhancements.js +364 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1091 -0
- package/dist/index.js.map +1 -0
- package/dist/models.d.ts +34 -0
- package/dist/models.d.ts.map +1 -0
- package/dist/models.js +343 -0
- package/dist/models.js.map +1 -0
- package/dist/providers/alibaba.d.ts +25 -0
- package/dist/providers/alibaba.d.ts.map +1 -0
- package/dist/providers/alibaba.js +37 -0
- package/dist/providers/alibaba.js.map +1 -0
- package/dist/providers/anthropic.d.ts +14 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/anthropic.js +19 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/gemini.d.ts +44 -0
- package/dist/providers/gemini.d.ts.map +1 -0
- package/dist/providers/gemini.js +156 -0
- package/dist/providers/gemini.js.map +1 -0
- package/dist/providers/glm.d.ts +25 -0
- package/dist/providers/glm.d.ts.map +1 -0
- package/dist/providers/glm.js +89 -0
- package/dist/providers/glm.js.map +1 -0
- package/dist/providers/ollama.d.ts +48 -0
- package/dist/providers/ollama.d.ts.map +1 -0
- package/dist/providers/ollama.js +174 -0
- package/dist/providers/ollama.js.map +1 -0
- package/dist/providers/openrouter.d.ts +24 -0
- package/dist/providers/openrouter.d.ts.map +1 -0
- package/dist/providers/openrouter.js +36 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/verify.d.ts +24 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +262 -0
- package/dist/verify.js.map +1 -0
- package/package.json +57 -0
- package/scripts/copy-hooks.js +15 -0
- package/src/clients/claude-code.ts +340 -0
- package/src/clients/opencode.ts +618 -0
- package/src/config.ts +101 -0
- package/src/display.ts +151 -0
- package/src/hooks/index.ts +208 -0
- package/src/hooks/token-tracker.js +280 -0
- package/src/hooks/visual-enhancements.js +364 -0
- package/src/index.ts +1263 -0
- package/src/models.ts +366 -0
- package/src/providers/alibaba.ts +43 -0
- package/src/providers/anthropic.ts +23 -0
- package/src/providers/gemini.ts +136 -0
- package/src/providers/glm.ts +60 -0
- package/src/providers/ollama.ts +146 -0
- package/src/providers/openrouter.ts +42 -0
- package/src/verify.ts +258 -0
- package/tsconfig.json +19 -0
package/src/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
|
+
}
|