claude-code-router-config 1.0.1 ā 1.1.0
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/README.md +169 -8
- package/cli/analytics.js +509 -0
- package/cli/benchmark.js +342 -0
- package/cli/commands.js +300 -0
- package/config/smart-intent-router.js +543 -0
- package/docs/v1.1.0-FEATURES.md +752 -0
- package/logging/enhanced-logger.js +410 -0
- package/logging/health-monitor.js +472 -0
- package/logging/middleware.js +384 -0
- package/package.json +29 -6
- package/plugins/plugin-manager.js +607 -0
- package/templates/README.md +161 -0
- package/templates/balanced.json +111 -0
- package/templates/cost-optimized.json +96 -0
- package/templates/development.json +104 -0
- package/templates/performance-optimized.json +88 -0
- package/templates/quality-focused.json +105 -0
- package/web-dashboard/public/css/dashboard.css +575 -0
- package/web-dashboard/public/index.html +308 -0
- package/web-dashboard/public/js/dashboard.js +512 -0
- package/web-dashboard/server.js +352 -0
package/cli/analytics.js
ADDED
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const os = require('os');
|
|
7
|
+
|
|
8
|
+
// Analytics storage path
|
|
9
|
+
const analyticsDir = path.join(os.homedir(), '.claude-code-router', 'analytics');
|
|
10
|
+
const dataFile = path.join(analyticsDir, 'usage.json');
|
|
11
|
+
const costFile = path.join(analyticsDir, 'costs.json');
|
|
12
|
+
|
|
13
|
+
// Pricing information (approximate, per 1M tokens)
|
|
14
|
+
const PRICING = {
|
|
15
|
+
openai: {
|
|
16
|
+
'gpt-4o': { input: 5.0, output: 15.0 },
|
|
17
|
+
'gpt-4-turbo': { input: 10.0, output: 30.0 },
|
|
18
|
+
'gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
19
|
+
'o1': { input: 15.0, output: 60.0 },
|
|
20
|
+
'o1-mini': { input: 3.0, output: 12.0 }
|
|
21
|
+
},
|
|
22
|
+
anthropic: {
|
|
23
|
+
'claude-sonnet-4-latest': { input: 15.0, output: 75.0 },
|
|
24
|
+
'claude-3-5-sonnet-latest': { input: 3.0, output: 15.0 },
|
|
25
|
+
'claude-3-5-haiku-latest': { input: 1.0, output: 5.0 }
|
|
26
|
+
},
|
|
27
|
+
gemini: {
|
|
28
|
+
'gemini-2.5-flash': { input: 0.075, output: 0.30 },
|
|
29
|
+
'gemini-2.5-pro': { input: 1.25, output: 5.0 },
|
|
30
|
+
'gemini-2.0-flash': { input: 0.075, output: 0.30 }
|
|
31
|
+
},
|
|
32
|
+
qwen: {
|
|
33
|
+
'qwen-plus': { input: 0.5, output: 2.0 },
|
|
34
|
+
'qwen-max': { input: 2.0, output: 6.0 },
|
|
35
|
+
'qwen-turbo': { input: 0.3, output: 0.6 },
|
|
36
|
+
'qwen3-coder-plus': { input: 2.0, output: 6.0 }
|
|
37
|
+
},
|
|
38
|
+
glm: {
|
|
39
|
+
'glm-4.6': { input: 0.5, output: 2.0 },
|
|
40
|
+
'glm-4.5': { input: 0.5, output: 2.0 },
|
|
41
|
+
'glm-4-plus': { input: 1.0, output: 2.0 }
|
|
42
|
+
},
|
|
43
|
+
openrouter: {
|
|
44
|
+
// OpenRouter pricing varies by model
|
|
45
|
+
'deepseek/deepseek-chat': { input: 0.14, output: 0.28 },
|
|
46
|
+
'meta-llama/llama-3.2-3b-instruct': { input: 0.10, output: 0.10 }
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Initialize analytics directory
|
|
51
|
+
function initializeAnalytics() {
|
|
52
|
+
if (!fs.existsSync(analyticsDir)) {
|
|
53
|
+
fs.mkdirSync(analyticsDir, { recursive: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Initialize data files if they don't exist
|
|
57
|
+
if (!fs.existsSync(dataFile)) {
|
|
58
|
+
fs.writeFileSync(dataFile, JSON.stringify({
|
|
59
|
+
requests: [],
|
|
60
|
+
daily: {},
|
|
61
|
+
monthly: {},
|
|
62
|
+
providers: {},
|
|
63
|
+
models: {}
|
|
64
|
+
}, null, 2));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!fs.existsSync(costFile)) {
|
|
68
|
+
fs.writeFileSync(costFile, JSON.stringify({
|
|
69
|
+
daily: {},
|
|
70
|
+
monthly: {},
|
|
71
|
+
providers: {},
|
|
72
|
+
total: 0
|
|
73
|
+
}, null, 2));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Record a request
|
|
78
|
+
function recordRequest(provider, model, inputTokens, outputTokens, latency, success = true) {
|
|
79
|
+
initializeAnalytics();
|
|
80
|
+
|
|
81
|
+
const timestamp = new Date().toISOString();
|
|
82
|
+
const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
|
|
83
|
+
const month = date.substring(0, 7); // YYYY-MM
|
|
84
|
+
|
|
85
|
+
const request = {
|
|
86
|
+
timestamp,
|
|
87
|
+
provider,
|
|
88
|
+
model,
|
|
89
|
+
inputTokens,
|
|
90
|
+
outputTokens,
|
|
91
|
+
totalTokens: inputTokens + outputTokens,
|
|
92
|
+
latency,
|
|
93
|
+
success,
|
|
94
|
+
cost: calculateCost(provider, model, inputTokens, outputTokens)
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Update analytics data
|
|
98
|
+
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
|
|
99
|
+
|
|
100
|
+
// Add request to history
|
|
101
|
+
data.requests.push(request);
|
|
102
|
+
|
|
103
|
+
// Update daily stats
|
|
104
|
+
if (!data.daily[date]) {
|
|
105
|
+
data.daily[date] = { requests: 0, tokens: 0, latency: [], providers: {}, models: {} };
|
|
106
|
+
}
|
|
107
|
+
data.daily[date].requests++;
|
|
108
|
+
data.daily[date].tokens += request.totalTokens;
|
|
109
|
+
data.daily[date].latency.push(latency);
|
|
110
|
+
data.daily[date].providers[provider] = (data.daily[date].providers[provider] || 0) + 1;
|
|
111
|
+
data.daily[date].models[model] = (data.daily[date].models[model] || 0) + 1;
|
|
112
|
+
|
|
113
|
+
// Update monthly stats
|
|
114
|
+
if (!data.monthly[month]) {
|
|
115
|
+
data.monthly[month] = { requests: 0, tokens: 0, latency: [], providers: {}, models: {} };
|
|
116
|
+
}
|
|
117
|
+
data.monthly[month].requests++;
|
|
118
|
+
data.monthly[month].tokens += request.totalTokens;
|
|
119
|
+
data.monthly[month].latency.push(latency);
|
|
120
|
+
data.monthly[month].providers[provider] = (data.monthly[month].providers[provider] || 0) + 1;
|
|
121
|
+
data.monthly[month].models[model] = (data.monthly[month].models[model] || 0) + 1;
|
|
122
|
+
|
|
123
|
+
// Update provider stats
|
|
124
|
+
if (!data.providers[provider]) {
|
|
125
|
+
data.providers[provider] = { requests: 0, tokens: 0, models: {} };
|
|
126
|
+
}
|
|
127
|
+
data.providers[provider].requests++;
|
|
128
|
+
data.providers[provider].tokens += request.totalTokens;
|
|
129
|
+
data.providers[provider].models[model] = (data.providers[provider].models[model] || 0) + 1;
|
|
130
|
+
|
|
131
|
+
// Update model stats
|
|
132
|
+
if (!data.models[`${provider}/${model}`]) {
|
|
133
|
+
data.models[`${provider}/${model}`] = { requests: 0, tokens: 0, avgLatency: 0 };
|
|
134
|
+
}
|
|
135
|
+
const modelStats = data.models[`${provider}/${model}`];
|
|
136
|
+
modelStats.requests++;
|
|
137
|
+
modelStats.tokens += request.totalTokens;
|
|
138
|
+
modelStats.avgLatency = (modelStats.avgLatency * (modelStats.requests - 1) + latency) / modelStats.requests;
|
|
139
|
+
|
|
140
|
+
// Save updated data
|
|
141
|
+
fs.writeFileSync(dataFile, JSON.stringify(data, null, 2));
|
|
142
|
+
|
|
143
|
+
// Update cost data
|
|
144
|
+
updateCosts(provider, model, request.cost, date, month);
|
|
145
|
+
|
|
146
|
+
return request;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Calculate cost for a request
|
|
150
|
+
function calculateCost(provider, model, inputTokens, outputTokens) {
|
|
151
|
+
const providerPricing = PRICING[provider];
|
|
152
|
+
if (!providerPricing) {
|
|
153
|
+
return 0; // Unknown provider
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const modelPricing = providerPricing[model];
|
|
157
|
+
if (!modelPricing) {
|
|
158
|
+
return 0; // Unknown model
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Convert tokens to millions
|
|
162
|
+
const inputMillions = inputTokens / 1000000;
|
|
163
|
+
const outputMillions = outputTokens / 1000000;
|
|
164
|
+
|
|
165
|
+
const inputCost = inputMillions * modelPricing.input;
|
|
166
|
+
const outputCost = outputMillions * modelPricing.output;
|
|
167
|
+
|
|
168
|
+
return inputCost + outputCost;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Update cost tracking
|
|
172
|
+
function updateCosts(provider, model, cost, date, month) {
|
|
173
|
+
const costs = JSON.parse(fs.readFileSync(costFile, 'utf8'));
|
|
174
|
+
|
|
175
|
+
// Update daily costs
|
|
176
|
+
if (!costs.daily[date]) {
|
|
177
|
+
costs.daily[date] = {};
|
|
178
|
+
}
|
|
179
|
+
costs.daily[date][provider] = (costs.daily[date][provider] || 0) + cost;
|
|
180
|
+
|
|
181
|
+
// Update monthly costs
|
|
182
|
+
if (!costs.monthly[month]) {
|
|
183
|
+
costs.monthly[month] = {};
|
|
184
|
+
}
|
|
185
|
+
costs.monthly[month][provider] = (costs.monthly[month][provider] || 0) + cost;
|
|
186
|
+
|
|
187
|
+
// Update provider costs
|
|
188
|
+
costs.providers[provider] = (costs.providers[provider] || 0) + cost;
|
|
189
|
+
|
|
190
|
+
// Update total cost
|
|
191
|
+
costs.total += cost;
|
|
192
|
+
|
|
193
|
+
fs.writeFileSync(costFile, JSON.stringify(costs, null, 2));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Get analytics for today
|
|
197
|
+
function getTodayAnalytics() {
|
|
198
|
+
initializeAnalytics();
|
|
199
|
+
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
|
|
200
|
+
const costs = JSON.parse(fs.readFileSync(costFile, 'utf8'));
|
|
201
|
+
const today = new Date().toISOString().split('T')[0];
|
|
202
|
+
|
|
203
|
+
const todayData = data.daily[today];
|
|
204
|
+
const todayCosts = costs.daily[today];
|
|
205
|
+
|
|
206
|
+
if (!todayData) {
|
|
207
|
+
return {
|
|
208
|
+
date: today,
|
|
209
|
+
requests: 0,
|
|
210
|
+
tokens: 0,
|
|
211
|
+
cost: 0,
|
|
212
|
+
avgLatency: 0,
|
|
213
|
+
providers: {},
|
|
214
|
+
models: {}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const avgLatency = todayData.latency.length > 0
|
|
219
|
+
? Math.round(todayData.latency.reduce((a, b) => a + b, 0) / todayData.latency.length)
|
|
220
|
+
: 0;
|
|
221
|
+
|
|
222
|
+
const totalCost = todayCosts
|
|
223
|
+
? Object.values(todayCosts).reduce((sum, cost) => sum + cost, 0)
|
|
224
|
+
: 0;
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
date: today,
|
|
228
|
+
requests: todayData.requests,
|
|
229
|
+
tokens: todayData.tokens,
|
|
230
|
+
cost: totalCost,
|
|
231
|
+
avgLatency,
|
|
232
|
+
providers: todayData.providers,
|
|
233
|
+
models: todayData.models
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Get analytics summary
|
|
238
|
+
function getAnalyticsSummary(period = 'week') {
|
|
239
|
+
initializeAnalytics();
|
|
240
|
+
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
|
|
241
|
+
const costs = JSON.parse(fs.readFileSync(costFile, 'utf8'));
|
|
242
|
+
|
|
243
|
+
const dates = getDateRange(period);
|
|
244
|
+
let totalRequests = 0;
|
|
245
|
+
let totalTokens = 0;
|
|
246
|
+
let totalCost = 0;
|
|
247
|
+
let allLatencies = [];
|
|
248
|
+
const providers = {};
|
|
249
|
+
const models = {};
|
|
250
|
+
|
|
251
|
+
dates.forEach(date => {
|
|
252
|
+
const dayData = data.daily[date];
|
|
253
|
+
const dayCosts = costs.daily[date];
|
|
254
|
+
|
|
255
|
+
if (dayData) {
|
|
256
|
+
totalRequests += dayData.requests;
|
|
257
|
+
totalTokens += dayData.tokens;
|
|
258
|
+
allLatencies = allLatencies.concat(dayData.latency);
|
|
259
|
+
|
|
260
|
+
Object.entries(dayData.providers).forEach(([provider, count]) => {
|
|
261
|
+
providers[provider] = (providers[provider] || 0) + count;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
Object.entries(dayData.models).forEach(([model, count]) => {
|
|
265
|
+
models[model] = (models[model] || 0) + count;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (dayCosts) {
|
|
270
|
+
totalCost += Object.values(dayCosts).reduce((sum, cost) => sum + cost, 0);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const avgLatency = allLatencies.length > 0
|
|
275
|
+
? Math.round(allLatencies.reduce((a, b) => a + b, 0) / allLatencies.length)
|
|
276
|
+
: 0;
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
period,
|
|
280
|
+
dates: dates.length,
|
|
281
|
+
totalRequests,
|
|
282
|
+
totalTokens,
|
|
283
|
+
totalCost,
|
|
284
|
+
avgLatency,
|
|
285
|
+
providers,
|
|
286
|
+
models
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Get date range for period
|
|
291
|
+
function getDateRange(period) {
|
|
292
|
+
const dates = [];
|
|
293
|
+
const today = new Date();
|
|
294
|
+
const start = new Date(today);
|
|
295
|
+
|
|
296
|
+
switch (period) {
|
|
297
|
+
case 'today':
|
|
298
|
+
dates.push(today.toISOString().split('T')[0]);
|
|
299
|
+
break;
|
|
300
|
+
case 'week':
|
|
301
|
+
start.setDate(today.getDate() - 7);
|
|
302
|
+
break;
|
|
303
|
+
case 'month':
|
|
304
|
+
start.setMonth(today.getMonth() - 1);
|
|
305
|
+
break;
|
|
306
|
+
case 'quarter':
|
|
307
|
+
start.setMonth(today.getMonth() - 3);
|
|
308
|
+
break;
|
|
309
|
+
case 'year':
|
|
310
|
+
start.setFullYear(today.getFullYear() - 1);
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (period !== 'today') {
|
|
315
|
+
const current = new Date(start);
|
|
316
|
+
while (current <= today) {
|
|
317
|
+
dates.push(current.toISOString().split('T')[0]);
|
|
318
|
+
current.setDate(current.getDate() + 1);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return dates;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Export analytics
|
|
326
|
+
function exportAnalytics(format = 'json', period = 'all') {
|
|
327
|
+
initializeAnalytics();
|
|
328
|
+
const data = JSON.parse(fs.readFileSync(dataFile, 'utf8'));
|
|
329
|
+
const costs = JSON.parse(fs.readFileSync(costFile, 'utf8'));
|
|
330
|
+
|
|
331
|
+
const exportData = {
|
|
332
|
+
timestamp: new Date().toISOString(),
|
|
333
|
+
period,
|
|
334
|
+
usage: data,
|
|
335
|
+
costs: costs
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const filename = `claude-code-router-analytics-${period}-${Date.now()}.${format}`;
|
|
339
|
+
const filepath = path.join(process.cwd(), filename);
|
|
340
|
+
|
|
341
|
+
if (format === 'json') {
|
|
342
|
+
fs.writeFileSync(filepath, JSON.stringify(exportData, null, 2));
|
|
343
|
+
} else if (format === 'csv') {
|
|
344
|
+
// Convert to CSV
|
|
345
|
+
const headers = ['timestamp', 'provider', 'model', 'inputTokens', 'outputTokens', 'totalTokens', 'latency', 'success', 'cost'];
|
|
346
|
+
const rows = data.requests.map(req => [
|
|
347
|
+
req.timestamp,
|
|
348
|
+
req.provider,
|
|
349
|
+
req.model,
|
|
350
|
+
req.inputTokens,
|
|
351
|
+
req.outputTokens,
|
|
352
|
+
req.totalTokens,
|
|
353
|
+
req.latency,
|
|
354
|
+
req.success,
|
|
355
|
+
req.cost
|
|
356
|
+
]);
|
|
357
|
+
|
|
358
|
+
const csv = [headers, ...rows].map(row => row.join(',')).join('\n');
|
|
359
|
+
fs.writeFileSync(filepath, csv);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return filepath;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Display analytics
|
|
366
|
+
function displayAnalytics(period = 'today', options = {}) {
|
|
367
|
+
const { detailed = false, export: exportFormat = null } = options;
|
|
368
|
+
|
|
369
|
+
console.log(chalk.blue(`š Claude Code Router Analytics - ${period.toUpperCase()}`));
|
|
370
|
+
console.log(chalk.gray('ā'.repeat(60)));
|
|
371
|
+
|
|
372
|
+
if (period === 'today') {
|
|
373
|
+
const today = getTodayAnalytics();
|
|
374
|
+
|
|
375
|
+
console.log(chalk.yellow('\nš
Today\'s Stats:'));
|
|
376
|
+
console.log(` Requests: ${today.requests}`);
|
|
377
|
+
console.log(` Tokens: ${today.tokens.toLocaleString()}`);
|
|
378
|
+
console.log(` Cost: $${today.cost.toFixed(4)}`);
|
|
379
|
+
console.log(` Avg Latency: ${today.avgLatency}ms`);
|
|
380
|
+
|
|
381
|
+
if (detailed && Object.keys(today.providers).length > 0) {
|
|
382
|
+
console.log(chalk.yellow('\nš Providers:'));
|
|
383
|
+
Object.entries(today.providers).forEach(([provider, count]) => {
|
|
384
|
+
const percentage = ((count / today.requests) * 100).toFixed(1);
|
|
385
|
+
console.log(` ${provider}: ${count} requests (${percentage}%)`);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
console.log(chalk.yellow('\nš¤ Models:'));
|
|
389
|
+
Object.entries(today.models).forEach(([model, count]) => {
|
|
390
|
+
const percentage = ((count / today.requests) * 100).toFixed(1);
|
|
391
|
+
console.log(` ${model}: ${count} requests (${percentage}%)`);
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
} else {
|
|
395
|
+
const summary = getAnalyticsSummary(period);
|
|
396
|
+
|
|
397
|
+
console.log(chalk.yellow(`\nš Last ${period} Summary:`));
|
|
398
|
+
console.log(` Total Requests: ${summary.totalRequests.toLocaleString()}`);
|
|
399
|
+
console.log(` Total Tokens: ${summary.totalTokens.toLocaleString()}`);
|
|
400
|
+
console.log(` Total Cost: $${summary.totalCost.toFixed(4)}`);
|
|
401
|
+
console.log(` Avg Latency: ${summary.avgLatency}ms`);
|
|
402
|
+
console.log(` Days Analyzed: ${summary.dates}`);
|
|
403
|
+
|
|
404
|
+
if (detailed && Object.keys(summary.providers).length > 0) {
|
|
405
|
+
console.log(chalk.yellow('\nš Provider Breakdown:'));
|
|
406
|
+
const sortedProviders = Object.entries(summary.providers)
|
|
407
|
+
.sort(([,a], [,b]) => b - a);
|
|
408
|
+
|
|
409
|
+
sortedProviders.forEach(([provider, count]) => {
|
|
410
|
+
const percentage = ((count / summary.totalRequests) * 100).toFixed(1);
|
|
411
|
+
console.log(` ${provider}: ${count.toLocaleString()} requests (${percentage}%)`);
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (detailed && Object.keys(summary.models).length > 0) {
|
|
416
|
+
console.log(chalk.yellow('\nš¤ Model Breakdown:'));
|
|
417
|
+
const sortedModels = Object.entries(summary.models)
|
|
418
|
+
.sort(([,a], [,b]) => b - a)
|
|
419
|
+
.slice(0, 10); // Top 10 models
|
|
420
|
+
|
|
421
|
+
sortedModels.forEach(([model, count]) => {
|
|
422
|
+
const percentage = ((count / summary.totalRequests) * 100).toFixed(1);
|
|
423
|
+
console.log(` ${model}: ${count.toLocaleString()} requests (${percentage}%)`);
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Export if requested
|
|
429
|
+
if (exportFormat) {
|
|
430
|
+
const filepath = exportAnalytics(exportFormat, period);
|
|
431
|
+
console.log(chalk.green(`\nš¾ Data exported to: ${filepath}`));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// CLI interface
|
|
436
|
+
async function main() {
|
|
437
|
+
const args = process.argv.slice(2);
|
|
438
|
+
const command = args[0];
|
|
439
|
+
|
|
440
|
+
switch (command) {
|
|
441
|
+
case 'today':
|
|
442
|
+
displayAnalytics('today', { detailed: args.includes('--detailed') });
|
|
443
|
+
break;
|
|
444
|
+
|
|
445
|
+
case 'week':
|
|
446
|
+
displayAnalytics('week', { detailed: args.includes('--detailed') });
|
|
447
|
+
break;
|
|
448
|
+
|
|
449
|
+
case 'month':
|
|
450
|
+
displayAnalytics('month', { detailed: args.includes('--detailed') });
|
|
451
|
+
break;
|
|
452
|
+
|
|
453
|
+
case 'export':
|
|
454
|
+
const format = args.find(arg => arg.startsWith('--format='))?.split('=')[1] || 'json';
|
|
455
|
+
const period = args.find(arg => arg.startsWith('--period='))?.split('=')[1] || 'all';
|
|
456
|
+
exportAnalytics(format, period);
|
|
457
|
+
console.log(chalk.green(`Analytics exported as ${format} for ${period}`));
|
|
458
|
+
break;
|
|
459
|
+
|
|
460
|
+
case 'record':
|
|
461
|
+
// For internal use - record a request
|
|
462
|
+
const [provider, model, inputTokens, outputTokens, latency, success] = args.slice(1);
|
|
463
|
+
if (provider && model && inputTokens && outputTokens && latency) {
|
|
464
|
+
recordRequest(
|
|
465
|
+
provider,
|
|
466
|
+
model,
|
|
467
|
+
parseInt(inputTokens),
|
|
468
|
+
parseInt(outputTokens),
|
|
469
|
+
parseInt(latency),
|
|
470
|
+
success === 'true'
|
|
471
|
+
);
|
|
472
|
+
} else {
|
|
473
|
+
console.error(chalk.red('Usage: ccr analytics record <provider> <model> <inputTokens> <outputTokens> <latency> [success]'));
|
|
474
|
+
}
|
|
475
|
+
break;
|
|
476
|
+
|
|
477
|
+
default:
|
|
478
|
+
console.log(chalk.blue('Claude Code Router - Analytics CLI'));
|
|
479
|
+
console.log(chalk.gray('ā'.repeat(45)));
|
|
480
|
+
console.log(chalk.yellow('Available commands:'));
|
|
481
|
+
console.log('');
|
|
482
|
+
console.log('View Analytics:');
|
|
483
|
+
console.log(' ccr analytics today [--detailed] - Today\'s statistics');
|
|
484
|
+
console.log(' ccr analytics week [--detailed] - Last week');
|
|
485
|
+
console.log(' ccr analytics month [--detailed] - Last month');
|
|
486
|
+
console.log('');
|
|
487
|
+
console.log('Export Data:');
|
|
488
|
+
console.log(' ccr analytics export [--format=json|csv] [--period=all] - Export analytics');
|
|
489
|
+
console.log('');
|
|
490
|
+
console.log('Internal Use:');
|
|
491
|
+
console.log(' ccr analytics record <provider> <model> <tokens> <latency> - Record request');
|
|
492
|
+
console.log('');
|
|
493
|
+
console.log('Examples:');
|
|
494
|
+
console.log(' ccr analytics today --detailed');
|
|
495
|
+
console.log(' ccr analytics export --format=csv --period=month');
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (require.main === module) {
|
|
500
|
+
main().catch(console.error);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
module.exports = {
|
|
504
|
+
recordRequest,
|
|
505
|
+
getTodayAnalytics,
|
|
506
|
+
getAnalyticsSummary,
|
|
507
|
+
exportAnalytics,
|
|
508
|
+
calculateCost
|
|
509
|
+
};
|