claude-code-router-config 1.0.0 → 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 +190 -18
- 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/AGENTSKILLS_INTEGRATION.md +500 -0
- package/docs/AGENTSKILLS_SETUP.md +743 -0
- package/docs/AGENTSKILLS_SETUP_TR.md +736 -0
- package/docs/FULL_DOCUMENTATION.md +23 -2
- package/docs/FULL_DOCUMENTATION_EN.md +23 -2
- package/docs/HOMEBREW_SETUP.md +252 -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 +42 -10
- 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
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
const { logger } = require('./enhanced-logger');
|
|
2
|
+
|
|
3
|
+
// Express middleware for HTTP requests
|
|
4
|
+
function httpLogger(options = {}) {
|
|
5
|
+
return (req, res, next) => {
|
|
6
|
+
const startTime = Date.now();
|
|
7
|
+
const requestId = generateRequestId();
|
|
8
|
+
|
|
9
|
+
// Add request ID to request object
|
|
10
|
+
req.requestId = requestId;
|
|
11
|
+
req.logData = {
|
|
12
|
+
requestId,
|
|
13
|
+
method: req.method,
|
|
14
|
+
url: req.url,
|
|
15
|
+
userAgent: req.get('User-Agent'),
|
|
16
|
+
ip: req.ip || req.connection.remoteAddress
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Log request start
|
|
20
|
+
logger.info('HTTP Request Started', {
|
|
21
|
+
event: 'http_request_start',
|
|
22
|
+
requestId,
|
|
23
|
+
method: req.method,
|
|
24
|
+
url: req.url,
|
|
25
|
+
userAgent: req.get('User-Agent'),
|
|
26
|
+
ip: req.ip || req.connection.remoteAddress
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// Override res.end to log response
|
|
30
|
+
const originalEnd = res.end;
|
|
31
|
+
res.end = function(chunk, encoding) {
|
|
32
|
+
const endTime = Date.now();
|
|
33
|
+
const duration = endTime - startTime;
|
|
34
|
+
|
|
35
|
+
logger.info('HTTP Request Completed', {
|
|
36
|
+
event: 'http_request_complete',
|
|
37
|
+
requestId,
|
|
38
|
+
method: req.method,
|
|
39
|
+
url: req.url,
|
|
40
|
+
statusCode: res.statusCode,
|
|
41
|
+
duration,
|
|
42
|
+
responseSize: chunk ? chunk.length : 0
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
originalEnd.call(this, chunk, encoding);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
next();
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Request/response interceptor for API calls
|
|
53
|
+
function apiInterceptor(provider, model) {
|
|
54
|
+
return {
|
|
55
|
+
beforeRequest: (request) => {
|
|
56
|
+
const requestId = generateRequestId();
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
|
|
59
|
+
logger.info('API Request Starting', {
|
|
60
|
+
event: 'api_request_start',
|
|
61
|
+
requestId,
|
|
62
|
+
provider,
|
|
63
|
+
model,
|
|
64
|
+
requestSize: JSON.stringify(request).length,
|
|
65
|
+
timestamp: new Date().toISOString()
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return { requestId, startTime };
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
afterRequest: (context, response, success) => {
|
|
72
|
+
const endTime = Date.now();
|
|
73
|
+
const duration = endTime - context.startTime;
|
|
74
|
+
|
|
75
|
+
const meta = {
|
|
76
|
+
event: 'api_request_complete',
|
|
77
|
+
requestId: context.requestId,
|
|
78
|
+
provider,
|
|
79
|
+
model,
|
|
80
|
+
duration,
|
|
81
|
+
success,
|
|
82
|
+
responseSize: JSON.stringify(response).length,
|
|
83
|
+
timestamp: new Date().toISOString()
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Extract token usage if available
|
|
87
|
+
if (response.usage) {
|
|
88
|
+
meta.inputTokens = response.usage.prompt_tokens || response.usage.input_tokens || 0;
|
|
89
|
+
meta.outputTokens = response.usage.completion_tokens || response.usage.output_tokens || 0;
|
|
90
|
+
meta.totalTokens = meta.inputTokens + meta.outputTokens;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Extract cost if available
|
|
94
|
+
if (response.cost !== undefined) {
|
|
95
|
+
meta.cost = response.cost;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const level = success ? 'info' : 'error';
|
|
99
|
+
logger.log(level, `API Request ${success ? 'Succeeded' : 'Failed'}`, meta);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
onError: (context, error) => {
|
|
103
|
+
const endTime = Date.now();
|
|
104
|
+
const duration = endTime - context.startTime;
|
|
105
|
+
|
|
106
|
+
logger.error('API Request Failed', {
|
|
107
|
+
event: 'api_request_error',
|
|
108
|
+
requestId: context.requestId,
|
|
109
|
+
provider,
|
|
110
|
+
model,
|
|
111
|
+
duration,
|
|
112
|
+
error: error.message,
|
|
113
|
+
stack: error.stack,
|
|
114
|
+
timestamp: new Date().toISOString()
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Router decision logger
|
|
121
|
+
function routeLogger() {
|
|
122
|
+
return {
|
|
123
|
+
logDecision: (request, provider, model, reason, alternatives = []) => {
|
|
124
|
+
logger.logRoute(request, provider, model, reason, alternatives);
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
logFallback: (originalProvider, newProvider, reason) => {
|
|
128
|
+
logger.warn('Route Fallback', {
|
|
129
|
+
event: 'route_fallback',
|
|
130
|
+
originalProvider,
|
|
131
|
+
newProvider,
|
|
132
|
+
reason,
|
|
133
|
+
timestamp: new Date().toISOString()
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
logProviderHealth: (provider, isHealthy, latency = null, error = null) => {
|
|
138
|
+
logger.logHealthCheck(provider, isHealthy ? 'healthy' : 'unhealthy', latency, error);
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Performance monitor
|
|
144
|
+
function performanceMonitor() {
|
|
145
|
+
const metrics = {
|
|
146
|
+
requests: 0,
|
|
147
|
+
errors: 0,
|
|
148
|
+
totalLatency: 0,
|
|
149
|
+
minLatency: Infinity,
|
|
150
|
+
maxLatency: 0,
|
|
151
|
+
providerStats: {},
|
|
152
|
+
lastCleanup: Date.now()
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
recordRequest: (provider, latency, success) => {
|
|
157
|
+
metrics.requests++;
|
|
158
|
+
metrics.totalLatency += latency;
|
|
159
|
+
metrics.minLatency = Math.min(metrics.minLatency, latency);
|
|
160
|
+
metrics.maxLatency = Math.max(metrics.maxLatency, latency);
|
|
161
|
+
|
|
162
|
+
if (!success) {
|
|
163
|
+
metrics.errors++;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Provider-specific stats
|
|
167
|
+
if (!metrics.providerStats[provider]) {
|
|
168
|
+
metrics.providerStats[provider] = {
|
|
169
|
+
requests: 0,
|
|
170
|
+
errors: 0,
|
|
171
|
+
totalLatency: 0,
|
|
172
|
+
avgLatency: 0
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const providerStat = metrics.providerStats[provider];
|
|
177
|
+
providerStat.requests++;
|
|
178
|
+
providerStat.totalLatency += latency;
|
|
179
|
+
providerStat.avgLatency = Math.round(providerStat.totalLatency / providerStat.requests);
|
|
180
|
+
|
|
181
|
+
if (!success) {
|
|
182
|
+
providerStat.errors++;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Log performance issues
|
|
186
|
+
if (latency > 10000) { // 10 seconds
|
|
187
|
+
logger.warn('Slow Request Detected', {
|
|
188
|
+
event: 'slow_request',
|
|
189
|
+
provider,
|
|
190
|
+
latency,
|
|
191
|
+
threshold: 10000
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
getMetrics: () => {
|
|
197
|
+
const avgLatency = metrics.requests > 0 ? Math.round(metrics.totalLatency / metrics.requests) : 0;
|
|
198
|
+
const errorRate = metrics.requests > 0 ? (metrics.errors / metrics.requests * 100).toFixed(2) : 0;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
totalRequests: metrics.requests,
|
|
202
|
+
totalErrors: metrics.errors,
|
|
203
|
+
errorRate: parseFloat(errorRate),
|
|
204
|
+
avgLatency,
|
|
205
|
+
minLatency: metrics.minLatency === Infinity ? 0 : metrics.minLatency,
|
|
206
|
+
maxLatency: metrics.maxLatency,
|
|
207
|
+
providerStats: metrics.providerStats
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
reset: () => {
|
|
212
|
+
metrics.requests = 0;
|
|
213
|
+
metrics.errors = 0;
|
|
214
|
+
metrics.totalLatency = 0;
|
|
215
|
+
metrics.minLatency = Infinity;
|
|
216
|
+
metrics.maxLatency = 0;
|
|
217
|
+
metrics.lastCleanup = Date.now();
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Utility functions
|
|
223
|
+
function generateRequestId() {
|
|
224
|
+
return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Log formatter for different outputs
|
|
228
|
+
function formatLogForOutput(logs, format = 'json') {
|
|
229
|
+
switch (format) {
|
|
230
|
+
case 'json':
|
|
231
|
+
return JSON.stringify(logs, null, 2);
|
|
232
|
+
|
|
233
|
+
case 'csv':
|
|
234
|
+
if (logs.length === 0) return '';
|
|
235
|
+
const headers = Object.keys(logs[0]);
|
|
236
|
+
const csvRows = [headers.join(',')];
|
|
237
|
+
logs.forEach(log => {
|
|
238
|
+
const values = headers.map(header => {
|
|
239
|
+
const value = log[header];
|
|
240
|
+
return typeof value === 'string' && value.includes(',')
|
|
241
|
+
? `"${value.replace(/"/g, '""')}"`
|
|
242
|
+
: value;
|
|
243
|
+
});
|
|
244
|
+
csvRows.push(values.join(','));
|
|
245
|
+
});
|
|
246
|
+
return csvRows.join('\n');
|
|
247
|
+
|
|
248
|
+
case 'table':
|
|
249
|
+
if (logs.length === 0) return 'No logs to display';
|
|
250
|
+
const cols = Object.keys(logs[0]);
|
|
251
|
+
const maxWidths = cols.map(col =>
|
|
252
|
+
Math.max(col.length, ...logs.map(log => String(log[col] || '').length))
|
|
253
|
+
);
|
|
254
|
+
|
|
255
|
+
let table = cols.map((col, i) => col.padEnd(maxWidths[i])).join(' | ') + '\n';
|
|
256
|
+
table += '-'.repeat(table.length) + '\n';
|
|
257
|
+
|
|
258
|
+
logs.forEach(log => {
|
|
259
|
+
const row = cols.map((col, i) =>
|
|
260
|
+
String(log[col] || '').padEnd(maxWidths[i])
|
|
261
|
+
).join(' | ');
|
|
262
|
+
table += row + '\n';
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return table;
|
|
266
|
+
|
|
267
|
+
default:
|
|
268
|
+
return logs.map(log => JSON.stringify(log)).join('\n');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Search and filter logs
|
|
273
|
+
function searchLogs(query, options = {}) {
|
|
274
|
+
const { level, provider, startDate, endDate, limit = 100 } = options;
|
|
275
|
+
|
|
276
|
+
const logs = logger.getRecentLogs(limit * 10); // Get more to filter
|
|
277
|
+
|
|
278
|
+
let filtered = logs;
|
|
279
|
+
|
|
280
|
+
// Filter by level
|
|
281
|
+
if (level) {
|
|
282
|
+
filtered = filtered.filter(log => log.level === level.toUpperCase());
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Filter by provider
|
|
286
|
+
if (provider) {
|
|
287
|
+
filtered = filtered.filter(log =>
|
|
288
|
+
log.provider === provider ||
|
|
289
|
+
log.message?.toLowerCase().includes(provider.toLowerCase())
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Filter by date range
|
|
294
|
+
if (startDate) {
|
|
295
|
+
const start = new Date(startDate);
|
|
296
|
+
filtered = filtered.filter(log => new Date(log.timestamp) >= start);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (endDate) {
|
|
300
|
+
const end = new Date(endDate);
|
|
301
|
+
filtered = filtered.filter(log => new Date(log.timestamp) <= end);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Text search
|
|
305
|
+
if (query) {
|
|
306
|
+
const searchTerms = query.toLowerCase().split(' ');
|
|
307
|
+
filtered = filtered.filter(log => {
|
|
308
|
+
const text = JSON.stringify(log).toLowerCase();
|
|
309
|
+
return searchTerms.every(term => text.includes(term));
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return filtered.slice(0, limit);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Real-time log streaming
|
|
317
|
+
function streamLogs(options = {}) {
|
|
318
|
+
const { level, provider, follow = false } = options;
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
start: (callback) => {
|
|
322
|
+
let lastPosition = 0;
|
|
323
|
+
|
|
324
|
+
const readNewLogs = () => {
|
|
325
|
+
try {
|
|
326
|
+
if (fs.existsSync(logger.logFile)) {
|
|
327
|
+
const stats = fs.statSync(logger.logFile);
|
|
328
|
+
if (stats.size > lastPosition) {
|
|
329
|
+
const content = fs.readFileSync(logger.logFile, 'utf8');
|
|
330
|
+
const newContent = content.slice(lastPosition);
|
|
331
|
+
lastPosition = stats.size;
|
|
332
|
+
|
|
333
|
+
const lines = newContent.trim().split('\n').filter(line => line);
|
|
334
|
+
const logs = lines.map(line => {
|
|
335
|
+
try {
|
|
336
|
+
return JSON.parse(line);
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
}).filter(Boolean);
|
|
341
|
+
|
|
342
|
+
// Filter and send logs
|
|
343
|
+
logs.forEach(log => {
|
|
344
|
+
if (level && log.level !== level.toUpperCase()) return;
|
|
345
|
+
if (provider && log.provider !== provider) return;
|
|
346
|
+
callback(log);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
} catch (error) {
|
|
351
|
+
console.error('Error streaming logs:', error);
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// Initial read
|
|
356
|
+
readNewLogs();
|
|
357
|
+
|
|
358
|
+
// Set up file watcher if following
|
|
359
|
+
if (follow) {
|
|
360
|
+
const fs = require('fs');
|
|
361
|
+
fs.watchFile(logger.logFile, { interval: 1000 }, readNewLogs);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
stop: () => {
|
|
366
|
+
if (follow) {
|
|
367
|
+
fs.unwatchFile(logger.logFile);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
module.exports = {
|
|
376
|
+
httpLogger,
|
|
377
|
+
apiInterceptor,
|
|
378
|
+
routeLogger,
|
|
379
|
+
performanceMonitor,
|
|
380
|
+
formatLogForOutput,
|
|
381
|
+
searchLogs,
|
|
382
|
+
streamLogs,
|
|
383
|
+
generateRequestId
|
|
384
|
+
};
|
package/package.json
CHANGED
|
@@ -1,21 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-router-config",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Multi-provider configuration for Claude Code Router with intent-based routing, advanced CLI tools, analytics, and smart routing. Setup OpenAI, Anthropic, Gemini, Qwen, GLM, OpenRouter, and GitHub Copilot with intelligent routing.",
|
|
5
5
|
"main": "install.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"ccr-setup": "install.js"
|
|
7
|
+
"ccr-setup": "install.js",
|
|
8
|
+
"ccr": "cli/commands.js",
|
|
9
|
+
"ccr-benchmark": "cli/benchmark.js",
|
|
10
|
+
"ccr-analytics": "cli/analytics.js",
|
|
11
|
+
"ccr-plugin": "plugins/plugin-manager.js"
|
|
8
12
|
},
|
|
9
13
|
"scripts": {
|
|
10
14
|
"install": "node install.js",
|
|
11
15
|
"setup": "chmod +x install.sh && ./install.sh",
|
|
12
|
-
"postinstall": "node postinstall.js"
|
|
16
|
+
"postinstall": "node postinstall.js",
|
|
17
|
+
"test": "node test/test.js",
|
|
18
|
+
"cli": "node cli/commands.js",
|
|
19
|
+
"benchmark": "node cli/benchmark.js",
|
|
20
|
+
"analytics": "node cli/analytics.js",
|
|
21
|
+
"health": "node logging/health-monitor.js"
|
|
13
22
|
},
|
|
14
23
|
"files": [
|
|
15
24
|
"config/",
|
|
25
|
+
"cli/",
|
|
26
|
+
"logging/",
|
|
27
|
+
"templates/",
|
|
28
|
+
"plugins/",
|
|
29
|
+
"web-dashboard/",
|
|
16
30
|
"install.sh",
|
|
17
31
|
".env.example",
|
|
18
|
-
"docs/"
|
|
32
|
+
"docs/",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"README.md"
|
|
19
35
|
],
|
|
20
36
|
"keywords": [
|
|
21
37
|
"claude",
|
|
@@ -31,7 +47,18 @@
|
|
|
31
47
|
"claude-code",
|
|
32
48
|
"cli",
|
|
33
49
|
"automation",
|
|
34
|
-
"configuration"
|
|
50
|
+
"configuration",
|
|
51
|
+
"homebrew",
|
|
52
|
+
"intent-based",
|
|
53
|
+
"llm",
|
|
54
|
+
"smart-routing",
|
|
55
|
+
"analytics",
|
|
56
|
+
"benchmarking",
|
|
57
|
+
"performance-monitoring",
|
|
58
|
+
"health-checks",
|
|
59
|
+
"cost-optimization",
|
|
60
|
+
"quality-optimization",
|
|
61
|
+
"advanced-cli"
|
|
35
62
|
],
|
|
36
63
|
"author": {
|
|
37
64
|
"name": "Halil Ertekin",
|
|
@@ -41,12 +68,12 @@
|
|
|
41
68
|
"license": "MIT",
|
|
42
69
|
"repository": {
|
|
43
70
|
"type": "git",
|
|
44
|
-
"url": "https://github.com/halilertekin/
|
|
71
|
+
"url": "https://github.com/halilertekin/CC-RouterMultiProvider.git"
|
|
45
72
|
},
|
|
46
73
|
"bugs": {
|
|
47
|
-
"url": "https://github.com/halilertekin/
|
|
74
|
+
"url": "https://github.com/halilertekin/CC-RouterMultiProvider/issues"
|
|
48
75
|
},
|
|
49
|
-
"homepage": "https://github.com/halilertekin/
|
|
76
|
+
"homepage": "https://github.com/halilertekin/CC-RouterMultiProvider#readme",
|
|
50
77
|
"engines": {
|
|
51
78
|
"node": ">=16.0.0"
|
|
52
79
|
},
|
|
@@ -54,6 +81,11 @@
|
|
|
54
81
|
"inquirer": "^9.2.0",
|
|
55
82
|
"chalk": "^5.3.0",
|
|
56
83
|
"fs-extra": "^11.1.1",
|
|
57
|
-
"dotenv": "^16.3.1"
|
|
84
|
+
"dotenv": "^16.3.1",
|
|
85
|
+
"express": "^4.18.2",
|
|
86
|
+
"cors": "^2.8.5"
|
|
87
|
+
},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"jest": "^29.7.0"
|
|
58
90
|
}
|
|
59
91
|
}
|