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.
@@ -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.0.0",
4
- "description": "Configuration package for @musistudio/claude-code-router with multi-provider intent-based routing",
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/claude-code-router-config.git"
71
+ "url": "https://github.com/halilertekin/CC-RouterMultiProvider.git"
45
72
  },
46
73
  "bugs": {
47
- "url": "https://github.com/halilertekin/claude-code-router-config/issues"
74
+ "url": "https://github.com/halilertekin/CC-RouterMultiProvider/issues"
48
75
  },
49
- "homepage": "https://github.com/halilertekin/claude-code-router-config#readme",
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
  }