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,607 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const chalk = require('chalk');
|
|
7
|
+
|
|
8
|
+
class PluginManager {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.pluginsDir = options.pluginsDir || path.join(os.homedir(), '.claude-code-router', 'plugins');
|
|
11
|
+
this.plugins = new Map();
|
|
12
|
+
this.hooks = new Map();
|
|
13
|
+
this.middleware = [];
|
|
14
|
+
|
|
15
|
+
this.initPluginDirectory();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
initPluginDirectory() {
|
|
19
|
+
if (!fs.existsSync(this.pluginsDir)) {
|
|
20
|
+
fs.mkdirSync(this.pluginsDir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Plugin structure validation
|
|
25
|
+
validatePlugin(plugin) {
|
|
26
|
+
const required = ['name', 'version', 'description', 'main'];
|
|
27
|
+
const missing = required.filter(field => !plugin[field]);
|
|
28
|
+
|
|
29
|
+
if (missing.length > 0) {
|
|
30
|
+
throw new Error(`Missing required fields: ${missing.join(', ')}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Validate plugin structure
|
|
34
|
+
if (!plugin.provider && !plugin.hooks && !plugin.middleware) {
|
|
35
|
+
throw new Error('Plugin must define at least provider, hooks, or middleware');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Load a plugin from directory or file
|
|
42
|
+
async loadPlugin(pluginPath) {
|
|
43
|
+
try {
|
|
44
|
+
const fullPath = path.isAbsolute(pluginPath)
|
|
45
|
+
? pluginPath
|
|
46
|
+
: path.join(this.pluginsDir, pluginPath);
|
|
47
|
+
|
|
48
|
+
const pluginJsonPath = path.join(fullPath, 'plugin.json');
|
|
49
|
+
|
|
50
|
+
if (!fs.existsSync(pluginJsonPath)) {
|
|
51
|
+
throw new Error(`Plugin configuration not found: ${pluginJsonPath}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const pluginConfig = JSON.parse(fs.readFileSync(pluginJsonPath, 'utf8'));
|
|
55
|
+
this.validatePlugin(pluginConfig);
|
|
56
|
+
|
|
57
|
+
// Load plugin main file
|
|
58
|
+
const mainPath = path.join(fullPath, pluginConfig.main);
|
|
59
|
+
if (!fs.existsSync(mainPath)) {
|
|
60
|
+
throw new Error(`Plugin main file not found: ${mainPath}`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const PluginClass = require(mainPath);
|
|
64
|
+
const plugin = new PluginClass(pluginConfig);
|
|
65
|
+
|
|
66
|
+
// Initialize plugin
|
|
67
|
+
if (typeof plugin.initialize === 'function') {
|
|
68
|
+
await plugin.initialize();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Register plugin
|
|
72
|
+
this.plugins.set(pluginConfig.name, {
|
|
73
|
+
instance: plugin,
|
|
74
|
+
config: pluginConfig,
|
|
75
|
+
path: fullPath
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Register hooks
|
|
79
|
+
if (plugin.hooks) {
|
|
80
|
+
Object.entries(plugin.hooks).forEach(([hookName, handler]) => {
|
|
81
|
+
this.registerHook(hookName, handler, pluginConfig.name);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Register middleware
|
|
86
|
+
if (plugin.middleware) {
|
|
87
|
+
this.middleware.push({
|
|
88
|
+
name: pluginConfig.name,
|
|
89
|
+
handler: plugin.middleware
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(chalk.green(`✅ Plugin loaded: ${pluginConfig.name} v${pluginConfig.version}`));
|
|
94
|
+
return plugin;
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error(chalk.red(`❌ Failed to load plugin ${pluginPath}:`), error.message);
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Load all plugins from directory
|
|
103
|
+
async loadAllPlugins() {
|
|
104
|
+
if (!fs.existsSync(this.pluginsDir)) {
|
|
105
|
+
console.log(chalk.yellow('⚠️ No plugins directory found'));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const pluginDirs = fs.readdirSync(this.pluginsDir)
|
|
110
|
+
.filter(item => {
|
|
111
|
+
const itemPath = path.join(this.pluginsDir, item);
|
|
112
|
+
return fs.statSync(itemPath).isDirectory();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
console.log(chalk.blue(`Loading ${pluginDirs.length} plugins...`));
|
|
116
|
+
|
|
117
|
+
const results = [];
|
|
118
|
+
for (const dir of pluginDirs) {
|
|
119
|
+
try {
|
|
120
|
+
const plugin = await this.loadPlugin(dir);
|
|
121
|
+
results.push({ success: true, plugin: plugin.config.name });
|
|
122
|
+
} catch (error) {
|
|
123
|
+
results.push({ success: false, plugin: dir, error: error.message });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
console.log(chalk.green(`\n✅ Successfully loaded ${results.filter(r => r.success).length} plugins`));
|
|
128
|
+
const failed = results.filter(r => !r.success);
|
|
129
|
+
if (failed.length > 0) {
|
|
130
|
+
console.log(chalk.red(`❌ Failed to load ${failed.length} plugins:`));
|
|
131
|
+
failed.forEach(f => {
|
|
132
|
+
console.log(chalk.red(` - ${f.plugin}: ${f.error}`));
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return results;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Unload a plugin
|
|
140
|
+
async unloadPlugin(pluginName) {
|
|
141
|
+
if (!this.plugins.has(pluginName)) {
|
|
142
|
+
throw new Error(`Plugin not found: ${pluginName}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const pluginData = this.plugins.get(pluginName);
|
|
146
|
+
const plugin = pluginData.instance;
|
|
147
|
+
|
|
148
|
+
// Cleanup plugin
|
|
149
|
+
if (typeof plugin.cleanup === 'function') {
|
|
150
|
+
await plugin.cleanup();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Remove hooks
|
|
154
|
+
this.removeHooksByPlugin(pluginName);
|
|
155
|
+
|
|
156
|
+
// Remove middleware
|
|
157
|
+
this.middleware = this.middleware.filter(mw => mw.name !== pluginName);
|
|
158
|
+
|
|
159
|
+
// Remove from plugins
|
|
160
|
+
this.plugins.delete(pluginName);
|
|
161
|
+
|
|
162
|
+
console.log(chalk.yellow(`⏏️ Plugin unloaded: ${pluginName}`));
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Reload a plugin
|
|
166
|
+
async reloadPlugin(pluginName) {
|
|
167
|
+
if (!this.plugins.has(pluginName)) {
|
|
168
|
+
throw new Error(`Plugin not found: ${pluginName}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const pluginPath = this.plugins.get(pluginName).path;
|
|
172
|
+
await this.unloadPlugin(pluginName);
|
|
173
|
+
await this.loadPlugin(pluginPath);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Get loaded plugins
|
|
177
|
+
getLoadedPlugins() {
|
|
178
|
+
return Array.from(this.plugins.entries()).map(([name, data]) => ({
|
|
179
|
+
name,
|
|
180
|
+
config: data.config,
|
|
181
|
+
loaded: true
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Get plugin by name
|
|
186
|
+
getPlugin(pluginName) {
|
|
187
|
+
const pluginData = this.plugins.get(pluginName);
|
|
188
|
+
return pluginData ? pluginData.instance : null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Register hook
|
|
192
|
+
registerHook(hookName, handler, pluginName) {
|
|
193
|
+
if (!this.hooks.has(hookName)) {
|
|
194
|
+
this.hooks.set(hookName, []);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
this.hooks.get(hookName).push({
|
|
198
|
+
handler,
|
|
199
|
+
plugin: pluginName
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Remove hooks by plugin
|
|
204
|
+
removeHooksByPlugin(pluginName) {
|
|
205
|
+
for (const [hookName, hooks] of this.hooks.entries()) {
|
|
206
|
+
this.hooks.set(hookName, hooks.filter(hook => hook.plugin !== pluginName));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Execute hook
|
|
211
|
+
async executeHook(hookName, ...args) {
|
|
212
|
+
if (!this.hooks.has(hookName)) {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const hooks = this.hooks.get(hookName);
|
|
217
|
+
const results = [];
|
|
218
|
+
|
|
219
|
+
for (const hook of hooks) {
|
|
220
|
+
try {
|
|
221
|
+
const result = await hook.handler(...args);
|
|
222
|
+
results.push({ plugin: hook.plugin, result });
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error(chalk.red(`Hook error in ${hookName} (${hook.plugin}):`), error.message);
|
|
225
|
+
results.push({ plugin: hook.plugin, error: error.message });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Apply middleware to request
|
|
233
|
+
async applyMiddleware(req, res, next) {
|
|
234
|
+
let index = 0;
|
|
235
|
+
|
|
236
|
+
const runNext = async () => {
|
|
237
|
+
if (index >= this.middleware.length) {
|
|
238
|
+
return next();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const middleware = this.middleware[index++];
|
|
242
|
+
try {
|
|
243
|
+
await middleware.handler(req, res, runNext);
|
|
244
|
+
} catch (error) {
|
|
245
|
+
console.error(chalk.red(`Middleware error (${middleware.name}):`), error.message);
|
|
246
|
+
next(error);
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
await runNext();
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Create plugin scaffold
|
|
254
|
+
createPlugin(pluginName, options = {}) {
|
|
255
|
+
const {
|
|
256
|
+
type = 'provider', // provider, hooks, middleware
|
|
257
|
+
author = 'Anonymous',
|
|
258
|
+
description = ''
|
|
259
|
+
} = options;
|
|
260
|
+
|
|
261
|
+
const pluginDir = path.join(this.pluginsDir, pluginName);
|
|
262
|
+
|
|
263
|
+
if (fs.existsSync(pluginDir)) {
|
|
264
|
+
throw new Error(`Plugin directory already exists: ${pluginName}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fs.mkdirSync(pluginDir, { recursive: true });
|
|
268
|
+
|
|
269
|
+
// Create plugin.json
|
|
270
|
+
const pluginConfig = {
|
|
271
|
+
name: pluginName,
|
|
272
|
+
version: '1.0.0',
|
|
273
|
+
description,
|
|
274
|
+
author,
|
|
275
|
+
type,
|
|
276
|
+
main: 'index.js',
|
|
277
|
+
keywords: [],
|
|
278
|
+
dependencies: {},
|
|
279
|
+
created: new Date().toISOString()
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
fs.writeFileSync(
|
|
283
|
+
path.join(pluginDir, 'plugin.json'),
|
|
284
|
+
JSON.stringify(pluginConfig, null, 2)
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Create main file based on type
|
|
288
|
+
let mainContent = '';
|
|
289
|
+
|
|
290
|
+
switch (type) {
|
|
291
|
+
case 'provider':
|
|
292
|
+
mainContent = this.getProviderTemplate(pluginName);
|
|
293
|
+
break;
|
|
294
|
+
case 'hooks':
|
|
295
|
+
mainContent = this.getHooksTemplate(pluginName);
|
|
296
|
+
break;
|
|
297
|
+
case 'middleware':
|
|
298
|
+
mainContent = this.getMiddlewareTemplate(pluginName);
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
fs.writeFileSync(path.join(pluginDir, 'index.js'), mainContent);
|
|
303
|
+
|
|
304
|
+
// Create README
|
|
305
|
+
const readmeContent = this.getReadmeTemplate(pluginName, pluginConfig);
|
|
306
|
+
fs.writeFileSync(path.join(pluginDir, 'README.md'), readmeContent);
|
|
307
|
+
|
|
308
|
+
console.log(chalk.green(`✅ Plugin scaffold created: ${pluginName}`));
|
|
309
|
+
console.log(chalk.blue(`📁 Location: ${pluginDir}`));
|
|
310
|
+
|
|
311
|
+
return pluginDir;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Plugin templates
|
|
315
|
+
getProviderTemplate(pluginName) {
|
|
316
|
+
return `class ${pluginName}Provider {
|
|
317
|
+
constructor(config) {
|
|
318
|
+
this.name = config.name;
|
|
319
|
+
this.apiBase = config.apiBase || 'https://api.example.com/v1';
|
|
320
|
+
this.models = config.models || ['model-1', 'model-2'];
|
|
321
|
+
this.pricing = config.pricing || { input: 0.01, output: 0.02 };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
async initialize() {
|
|
325
|
+
console.log(\`Initializing \${this.name} plugin...\`);
|
|
326
|
+
// Initialize connections, validate API keys, etc.
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async cleanup() {
|
|
330
|
+
console.log(\`Cleaning up \${this.name} plugin...\`);
|
|
331
|
+
// Close connections, cleanup resources
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Required methods for provider plugins
|
|
335
|
+
async createRequest(prompt, options = {}) {
|
|
336
|
+
const model = options.model || this.models[0];
|
|
337
|
+
return {
|
|
338
|
+
model,
|
|
339
|
+
messages: [{ role: 'user', content: prompt }],
|
|
340
|
+
max_tokens: options.maxTokens || 1000,
|
|
341
|
+
temperature: options.temperature || 0.7
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async parseResponse(response) {
|
|
346
|
+
return response;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Optional: Custom methods
|
|
350
|
+
async checkHealth() {
|
|
351
|
+
// Check if the provider is accessible
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
getCapabilities() {
|
|
356
|
+
return {
|
|
357
|
+
chat: true,
|
|
358
|
+
tools: false,
|
|
359
|
+
vision: false
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
module.exports = ${pluginName}Provider;
|
|
365
|
+
`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
getHooksTemplate(pluginName) {
|
|
369
|
+
return `class ${pluginName}Hooks {
|
|
370
|
+
constructor(config) {
|
|
371
|
+
this.name = config.name;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Plugin initialization
|
|
375
|
+
async initialize() {
|
|
376
|
+
console.log(\`Initializing \${this.name} hooks...\`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Plugin cleanup
|
|
380
|
+
async cleanup() {
|
|
381
|
+
console.log(\`Cleaning up \${this.name} hooks...\`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Define hooks
|
|
385
|
+
get hooks() {
|
|
386
|
+
return {
|
|
387
|
+
// Called before request
|
|
388
|
+
'beforeRequest': async (req, config) => {
|
|
389
|
+
console.log(\`Before request hook: \${req.method || 'Unknown'}\`);
|
|
390
|
+
return req;
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
// Called after request
|
|
394
|
+
'afterRequest': async (req, response, latency) => {
|
|
395
|
+
console.log(\`After request hook: \${latency}ms\`);
|
|
396
|
+
return { req, response, latency };
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
// Called on error
|
|
400
|
+
'onError': async (req, error) => {
|
|
401
|
+
console.error(\`Error hook: \${error.message}\`);
|
|
402
|
+
return error;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
module.exports = ${pluginName}Hooks;
|
|
409
|
+
`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
getMiddlewareTemplate(pluginName) {
|
|
413
|
+
return `class ${pluginName}Middleware {
|
|
414
|
+
constructor(config) {
|
|
415
|
+
this.name = config.name;
|
|
416
|
+
this.options = config.options || {};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async initialize() {
|
|
420
|
+
console.log(\`Initializing \${this.name} middleware...\`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
async cleanup() {
|
|
424
|
+
console.log(\`Cleaning up \${this.name} middleware...\`);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Middleware function
|
|
428
|
+
async middleware(req, res, next) {
|
|
429
|
+
// Add custom headers, logging, rate limiting, etc.
|
|
430
|
+
console.log(\`\${this.name} middleware processing request\`);
|
|
431
|
+
|
|
432
|
+
// Continue to next middleware
|
|
433
|
+
await next();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
module.exports = ${pluginName}Middleware;
|
|
438
|
+
`;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
getReadmeTemplate(pluginName, config) {
|
|
442
|
+
return `# ${pluginName} Plugin
|
|
443
|
+
|
|
444
|
+
${config.description}
|
|
445
|
+
|
|
446
|
+
## Installation
|
|
447
|
+
|
|
448
|
+
1. Place this plugin in your Claude Code Router plugins directory
|
|
449
|
+
2. Restart Claude Code Router
|
|
450
|
+
3. The plugin will be automatically loaded
|
|
451
|
+
|
|
452
|
+
## Configuration
|
|
453
|
+
|
|
454
|
+
Edit \`plugin.json\` to customize the plugin settings.
|
|
455
|
+
|
|
456
|
+
## Usage
|
|
457
|
+
|
|
458
|
+
This plugin provides:
|
|
459
|
+
- Type: ${config.type}
|
|
460
|
+
- Version: ${config.version}
|
|
461
|
+
- Author: ${config.author}
|
|
462
|
+
|
|
463
|
+
## Development
|
|
464
|
+
|
|
465
|
+
Modify \`index.js\` to extend the plugin functionality.
|
|
466
|
+
|
|
467
|
+
## Support
|
|
468
|
+
|
|
469
|
+
For issues or questions, contact: ${config.author}
|
|
470
|
+
`;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// CLI interface
|
|
475
|
+
async function main() {
|
|
476
|
+
const args = process.argv.slice(2);
|
|
477
|
+
const command = args[0];
|
|
478
|
+
|
|
479
|
+
const manager = new PluginManager();
|
|
480
|
+
|
|
481
|
+
switch (command) {
|
|
482
|
+
case 'list':
|
|
483
|
+
const plugins = manager.getLoadedPlugins();
|
|
484
|
+
if (plugins.length === 0) {
|
|
485
|
+
console.log(chalk.yellow('No plugins loaded'));
|
|
486
|
+
} else {
|
|
487
|
+
console.log(chalk.blue('\nLoaded Plugins:'));
|
|
488
|
+
plugins.forEach(plugin => {
|
|
489
|
+
console.log(` 📦 ${plugin.name} v${plugin.config.version}`);
|
|
490
|
+
console.log(` ${plugin.config.description}`);
|
|
491
|
+
});
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
|
|
495
|
+
case 'load':
|
|
496
|
+
const pluginName = args[1];
|
|
497
|
+
if (!pluginName) {
|
|
498
|
+
console.error(chalk.red('Please specify plugin name'));
|
|
499
|
+
process.exit(1);
|
|
500
|
+
}
|
|
501
|
+
try {
|
|
502
|
+
await manager.loadPlugin(pluginName);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error(chalk.red('Failed to load plugin:'), error.message);
|
|
505
|
+
process.exit(1);
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
508
|
+
|
|
509
|
+
case 'unload':
|
|
510
|
+
const unloadName = args[1];
|
|
511
|
+
if (!unloadName) {
|
|
512
|
+
console.error(chalk.red('Please specify plugin name'));
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
try {
|
|
516
|
+
await manager.unloadPlugin(unloadName);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error(chalk.red('Failed to unload plugin:'), error.message);
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
521
|
+
break;
|
|
522
|
+
|
|
523
|
+
case 'reload':
|
|
524
|
+
const reloadName = args[1];
|
|
525
|
+
if (!reloadName) {
|
|
526
|
+
console.error(chalk.red('Please specify plugin name'));
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
try {
|
|
530
|
+
await manager.reloadPlugin(reloadName);
|
|
531
|
+
} catch (error) {
|
|
532
|
+
console.error(chalk.red('Failed to reload plugin:'), error.message);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
break;
|
|
536
|
+
|
|
537
|
+
case 'create':
|
|
538
|
+
const createName = args[1];
|
|
539
|
+
const options = {};
|
|
540
|
+
|
|
541
|
+
// Parse options
|
|
542
|
+
const typeIndex = args.indexOf('--type');
|
|
543
|
+
if (typeIndex !== -1) {
|
|
544
|
+
options.type = args[typeIndex + 1];
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
const authorIndex = args.indexOf('--author');
|
|
548
|
+
if (authorIndex !== -1) {
|
|
549
|
+
options.author = args[authorIndex + 1];
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const descIndex = args.indexOf('--description');
|
|
553
|
+
if (descIndex !== -1) {
|
|
554
|
+
options.description = args.slice(descIndex + 1).join(' ');
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (!createName) {
|
|
558
|
+
console.error(chalk.red('Please specify plugin name'));
|
|
559
|
+
console.log(chalk.blue('\nUsage: ccr plugin create <name> [--type provider|hooks|middleware] [--author <author>] [--description <description>]'));
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
try {
|
|
564
|
+
manager.createPlugin(createName, options);
|
|
565
|
+
} catch (error) {
|
|
566
|
+
console.error(chalk.red('Failed to create plugin:'), error.message);
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
break;
|
|
570
|
+
|
|
571
|
+
case 'load-all':
|
|
572
|
+
await manager.loadAllPlugins();
|
|
573
|
+
break;
|
|
574
|
+
|
|
575
|
+
default:
|
|
576
|
+
console.log(chalk.blue('Claude Code Router - Plugin Manager'));
|
|
577
|
+
console.log(chalk.gray('─'.repeat(40)));
|
|
578
|
+
console.log(chalk.yellow('Available commands:'));
|
|
579
|
+
console.log('');
|
|
580
|
+
console.log('Plugin Management:');
|
|
581
|
+
console.log(' ccr plugin list - List loaded plugins');
|
|
582
|
+
console.log(' ccr plugin load <name> - Load a plugin');
|
|
583
|
+
console.log(' ccr plugin unload <name> - Unload a plugin');
|
|
584
|
+
console.log(' ccr plugin reload <name> - Reload a plugin');
|
|
585
|
+
console.log(' ccr plugin create <name> [options] - Create plugin scaffold');
|
|
586
|
+
console.log(' ccr plugin load-all - Load all plugins');
|
|
587
|
+
console.log('');
|
|
588
|
+
console.log('Plugin Creation Options:');
|
|
589
|
+
console.log(' --type <provider|hooks|middleware> Plugin type');
|
|
590
|
+
console.log(' --author <name> Author name');
|
|
591
|
+
console.log(' --description <text> Plugin description');
|
|
592
|
+
console.log('');
|
|
593
|
+
console.log('Examples:');
|
|
594
|
+
console.log(' ccr plugin create my-provider --type provider');
|
|
595
|
+
console.log(' ccr plugin create logger --type middleware --author "John Doe"');
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Export for use in other modules
|
|
600
|
+
module.exports = {
|
|
601
|
+
PluginManager
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// Run CLI if called directly
|
|
605
|
+
if (require.main === module) {
|
|
606
|
+
main().catch(console.error);
|
|
607
|
+
}
|