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
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const cors = require('cors');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const chalk = require('chalk');
|
|
9
|
+
|
|
10
|
+
class DashboardServer {
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.port = options.port || 3457;
|
|
13
|
+
this.app = express();
|
|
14
|
+
this.setupMiddleware();
|
|
15
|
+
this.setupRoutes();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setupMiddleware() {
|
|
19
|
+
this.app.use(cors());
|
|
20
|
+
this.app.use(express.json());
|
|
21
|
+
this.app.use(express.static(path.join(__dirname, 'public')));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setupRoutes() {
|
|
25
|
+
// API Routes
|
|
26
|
+
this.app.get('/api/health', (req, res) => {
|
|
27
|
+
res.json({
|
|
28
|
+
success: true,
|
|
29
|
+
message: 'Claude Code Router Dashboard',
|
|
30
|
+
timestamp: new Date().toISOString()
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
this.app.get('/api/config', (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const configPath = path.join(os.homedir(), '.claude-code-router', 'config.json');
|
|
37
|
+
if (fs.existsSync(configPath)) {
|
|
38
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
39
|
+
res.json({ success: true, data: config });
|
|
40
|
+
} else {
|
|
41
|
+
res.status(404).json({ success: false, error: 'Configuration not found' });
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
res.status(500).json({ success: false, error: error.message });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
this.app.get('/api/providers', (req, res) => {
|
|
49
|
+
try {
|
|
50
|
+
const configPath = path.join(os.homedir(), '.claude-code-router', 'config.json');
|
|
51
|
+
if (fs.existsSync(configPath)) {
|
|
52
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
53
|
+
res.json({ success: true, data: config.Providers || [] });
|
|
54
|
+
} else {
|
|
55
|
+
res.status(404).json({ success: false, error: 'Providers not found' });
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
res.status(500).json({ success: false, error: error.message });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
this.app.get('/api/analytics', (req, res) => {
|
|
63
|
+
try {
|
|
64
|
+
const analyticsPath = path.join(os.homedir(), '.claude-code-router', 'analytics', 'usage.json');
|
|
65
|
+
if (fs.existsSync(analyticsPath)) {
|
|
66
|
+
const analytics = JSON.parse(fs.readFileSync(analyticsPath, 'utf8'));
|
|
67
|
+
res.json({ success: true, data: analytics });
|
|
68
|
+
} else {
|
|
69
|
+
res.json({ success: true, data: { requests: [], daily: {}, monthly: {} } });
|
|
70
|
+
}
|
|
71
|
+
} catch (error) {
|
|
72
|
+
res.status(500).json({ success: false, error: error.message });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.app.get('/api/status', (req, res) => {
|
|
77
|
+
const status = {
|
|
78
|
+
uptime: process.uptime(),
|
|
79
|
+
timestamp: new Date().toISOString(),
|
|
80
|
+
version: require('../package.json').version,
|
|
81
|
+
nodeVersion: process.version,
|
|
82
|
+
platform: os.platform(),
|
|
83
|
+
arch: os.arch()
|
|
84
|
+
};
|
|
85
|
+
res.json({ success: true, data: status });
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Serve the main dashboard HTML
|
|
89
|
+
this.app.get('/', (req, res) => {
|
|
90
|
+
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Catch all other routes
|
|
94
|
+
this.app.use((req, res) => {
|
|
95
|
+
res.status(404).json({
|
|
96
|
+
success: false,
|
|
97
|
+
error: 'Endpoint not found'
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async start() {
|
|
103
|
+
return new Promise((resolve, reject) => {
|
|
104
|
+
try {
|
|
105
|
+
const server = this.app.listen(this.port, () => {
|
|
106
|
+
console.log(chalk.green(`🚀 Dashboard server started on port ${this.port}`));
|
|
107
|
+
console.log(chalk.blue(`📊 Open http://localhost:${this.port} to view dashboard`));
|
|
108
|
+
|
|
109
|
+
// Try to open browser
|
|
110
|
+
this.openBrowser().catch(() => {
|
|
111
|
+
console.log(chalk.yellow('Could not open browser automatically'));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
resolve(server);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
server.on('error', (error) => {
|
|
118
|
+
if (error.code === 'EADDRINUSE') {
|
|
119
|
+
reject(new Error(`Port ${this.port} is already in use`));
|
|
120
|
+
} else {
|
|
121
|
+
reject(error);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
} catch (error) {
|
|
125
|
+
reject(error);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async openBrowser() {
|
|
131
|
+
const { spawn } = require('child_process');
|
|
132
|
+
const opener = process.platform === 'darwin' ? 'open' :
|
|
133
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
134
|
+
|
|
135
|
+
spawn(opener, [`http://localhost:${this.port}`], { detached: true }).unref();
|
|
136
|
+
|
|
137
|
+
return new Promise((resolve) => {
|
|
138
|
+
setTimeout(resolve, 1000);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async stop() {
|
|
143
|
+
if (this.server) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
this.server.close(() => {
|
|
146
|
+
console.log(chalk.yellow('Dashboard server stopped'));
|
|
147
|
+
resolve();
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// API routes for analytics and monitoring
|
|
155
|
+
class DashboardAPI {
|
|
156
|
+
static setupAnalyticsRoutes(app) {
|
|
157
|
+
const analyticsRouter = express.Router();
|
|
158
|
+
|
|
159
|
+
// Get analytics summary
|
|
160
|
+
analyticsRouter.get('/summary', async (req, res) => {
|
|
161
|
+
try {
|
|
162
|
+
const { getAnalyticsSummary } = require('../cli/analytics');
|
|
163
|
+
const summary = getAnalyticsSummary('week');
|
|
164
|
+
res.json({ success: true, data: summary });
|
|
165
|
+
} catch (error) {
|
|
166
|
+
res.status(500).json({ success: false, error: error.message });
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Get today's analytics
|
|
171
|
+
analyticsRouter.get('/today', async (req, res) => {
|
|
172
|
+
try {
|
|
173
|
+
const { getTodayAnalytics } = require('../cli/analytics');
|
|
174
|
+
const today = getTodayAnalytics();
|
|
175
|
+
res.json({ success: true, data: today });
|
|
176
|
+
} catch (error) {
|
|
177
|
+
res.status(500).json({ success: false, error: error.message });
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Export analytics
|
|
182
|
+
analyticsRouter.get('/export', async (req, res) => {
|
|
183
|
+
try {
|
|
184
|
+
const { exportAnalytics } = require('../cli/analytics');
|
|
185
|
+
const format = req.query.format || 'json';
|
|
186
|
+
const period = req.query.period || 'all';
|
|
187
|
+
const filepath = exportAnalytics(format, period);
|
|
188
|
+
|
|
189
|
+
res.download(filepath, (err) => {
|
|
190
|
+
if (err) {
|
|
191
|
+
res.status(500).json({ success: false, error: 'Export failed' });
|
|
192
|
+
} else {
|
|
193
|
+
// Clean up the temporary file
|
|
194
|
+
fs.unlink(filepath, () => {});
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
} catch (error) {
|
|
198
|
+
res.status(500).json({ success: false, error: error.message });
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
app.use('/api/analytics', analyticsRouter);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
static setupHealthRoutes(app) {
|
|
206
|
+
const healthRouter = express.Router();
|
|
207
|
+
|
|
208
|
+
// Check provider health
|
|
209
|
+
healthRouter.get('/providers', async (req, res) => {
|
|
210
|
+
try {
|
|
211
|
+
const configPath = path.join(os.homedir(), '.claude-code-router', 'config.json');
|
|
212
|
+
if (fs.existsSync(configPath)) {
|
|
213
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
214
|
+
const providers = config.Providers || [];
|
|
215
|
+
|
|
216
|
+
const healthChecks = await Promise.all(
|
|
217
|
+
providers.map(async (provider) => {
|
|
218
|
+
try {
|
|
219
|
+
const { testProvider } = require('../cli/commands');
|
|
220
|
+
const isHealthy = await testProvider(provider.name);
|
|
221
|
+
return {
|
|
222
|
+
name: provider.name,
|
|
223
|
+
status: isHealthy ? 'healthy' : 'unhealthy',
|
|
224
|
+
models: provider.models,
|
|
225
|
+
lastChecked: new Date().toISOString()
|
|
226
|
+
};
|
|
227
|
+
} catch (error) {
|
|
228
|
+
return {
|
|
229
|
+
name: provider.name,
|
|
230
|
+
status: 'error',
|
|
231
|
+
error: error.message,
|
|
232
|
+
lastChecked: new Date().toISOString()
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
res.json({ success: true, data: healthChecks });
|
|
239
|
+
} else {
|
|
240
|
+
res.status(404).json({ success: false, error: 'Configuration not found' });
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
res.status(500).json({ success: false, error: error.message });
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// System health
|
|
248
|
+
healthRouter.get('/system', async (req, res) => {
|
|
249
|
+
try {
|
|
250
|
+
const health = {
|
|
251
|
+
uptime: process.uptime(),
|
|
252
|
+
memory: process.memoryUsage(),
|
|
253
|
+
cpu: process.cpuUsage(),
|
|
254
|
+
platform: os.platform(),
|
|
255
|
+
nodeVersion: process.version,
|
|
256
|
+
timestamp: new Date().toISOString()
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
res.json({ success: true, data: health });
|
|
260
|
+
} catch (error) {
|
|
261
|
+
res.status(500).json({ success: false, error: error.message });
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
app.use('/api/health', healthRouter);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
static setupConfigRoutes(app) {
|
|
269
|
+
const configRouter = express.Router();
|
|
270
|
+
|
|
271
|
+
// Get current configuration
|
|
272
|
+
configRouter.get('/current', async (req, res) => {
|
|
273
|
+
try {
|
|
274
|
+
const configPath = path.join(os.homedir(), '.claude-code-router', 'config.json');
|
|
275
|
+
if (fs.existsSync(configPath)) {
|
|
276
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
277
|
+
res.json({ success: true, data: config });
|
|
278
|
+
} else {
|
|
279
|
+
res.status(404).json({ success: false, error: 'Configuration not found' });
|
|
280
|
+
}
|
|
281
|
+
} catch (error) {
|
|
282
|
+
res.status(500).json({ success: false, error: error.message });
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Get available templates
|
|
287
|
+
configRouter.get('/templates', async (req, res) => {
|
|
288
|
+
try {
|
|
289
|
+
const templatesDir = path.join(__dirname, '../templates');
|
|
290
|
+
const templates = [];
|
|
291
|
+
|
|
292
|
+
if (fs.existsSync(templatesDir)) {
|
|
293
|
+
const files = fs.readdirSync(templatesDir).filter(f => f.endsWith('.json'));
|
|
294
|
+
|
|
295
|
+
for (const file of files) {
|
|
296
|
+
const templatePath = path.join(templatesDir, file);
|
|
297
|
+
const template = JSON.parse(fs.readFileSync(templatePath, 'utf8'));
|
|
298
|
+
templates.push({
|
|
299
|
+
name: file.replace('.json', ''),
|
|
300
|
+
...template
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
res.json({ success: true, data: templates });
|
|
306
|
+
} catch (error) {
|
|
307
|
+
res.status(500).json({ success: false, error: error.message });
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
app.use('/api/config', configRouter);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// CLI function to start dashboard
|
|
316
|
+
async function startDashboard(options = {}) {
|
|
317
|
+
const dashboard = new DashboardServer(options);
|
|
318
|
+
|
|
319
|
+
// Setup additional API routes
|
|
320
|
+
DashboardAPI.setupAnalyticsRoutes(dashboard.app);
|
|
321
|
+
DashboardAPI.setupHealthRoutes(dashboard.app);
|
|
322
|
+
DashboardAPI.setupConfigRoutes(dashboard.app);
|
|
323
|
+
|
|
324
|
+
try {
|
|
325
|
+
const server = await dashboard.start();
|
|
326
|
+
return server;
|
|
327
|
+
} catch (error) {
|
|
328
|
+
console.error(chalk.red('Failed to start dashboard:'), error.message);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Export for use in other modules
|
|
334
|
+
module.exports = {
|
|
335
|
+
DashboardServer,
|
|
336
|
+
DashboardAPI,
|
|
337
|
+
startDashboard
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
// Run server if called directly
|
|
341
|
+
if (require.main === module) {
|
|
342
|
+
const args = process.argv.slice(2);
|
|
343
|
+
const options = {};
|
|
344
|
+
|
|
345
|
+
// Parse command line arguments
|
|
346
|
+
const portIndex = args.indexOf('--port');
|
|
347
|
+
if (portIndex !== -1 && args[portIndex + 1]) {
|
|
348
|
+
options.port = parseInt(args[portIndex + 1]);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
startDashboard(options);
|
|
352
|
+
}
|