coder-config 0.53.4-beta → 0.53.6-beta
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/config-loader.js +15 -0
- package/lib/cli.js +155 -0
- package/lib/constants.js +1 -1
- package/lib/router.js +191 -0
- package/package.json +1 -1
- package/ui/dist/assets/index-Bxw7V3Gk.css +32 -0
- package/ui/dist/assets/{index-2aErGrWE.js → index-DqWhZcAQ.js} +195 -190
- package/ui/dist/index.html +2 -2
- package/ui/routes/index.js +2 -0
- package/ui/routes/router.js +182 -0
- package/ui/server.cjs +38 -0
- package/ui/dist/assets/index-BiF3i3y5.css +0 -32
package/config-loader.js
CHANGED
|
@@ -35,6 +35,7 @@ const { heartbeat: runHeartbeat, formatReport, getExitCode, saveLastHeartbeat, s
|
|
|
35
35
|
const { getSessionStatus, showSessionStatus, flushContext, clearContext, installHooks: sessionInstallHooks, getFlushedContext, installFlushCommand, installAll: sessionInstallAll, SESSION_DIR, FLUSHED_CONTEXT_FILE } = require('./lib/sessions');
|
|
36
36
|
const { runCli } = require('./lib/cli');
|
|
37
37
|
const { shellStatus, shellInstall, shellUninstall, printShellStatus } = require('./lib/shell');
|
|
38
|
+
const { getConfig: routerGetConfig, saveConfig: routerSaveConfig, listProviders: routerListProviders, addProvider: routerAddProvider, removeProvider: routerRemoveProvider, getRouterRules, setRouterRule, getActivationEnv: routerGetActivationEnv, getStatus: routerGetStatus, listPresets: routerListPresets, savePreset: routerSavePreset, loadPreset: routerLoadPreset } = require('./lib/router');
|
|
38
39
|
|
|
39
40
|
class ClaudeConfigManager {
|
|
40
41
|
constructor() {
|
|
@@ -332,6 +333,20 @@ class ClaudeConfigManager {
|
|
|
332
333
|
shellUninstall() { return shellUninstall(); }
|
|
333
334
|
getShellStatus() { return shellStatus(); }
|
|
334
335
|
|
|
336
|
+
// Router (CCR)
|
|
337
|
+
routerGetConfig() { return routerGetConfig(); }
|
|
338
|
+
routerSaveConfig(config) { return routerSaveConfig(config); }
|
|
339
|
+
routerListProviders() { return routerListProviders(); }
|
|
340
|
+
routerAddProvider(name, options) { return routerAddProvider(name, options); }
|
|
341
|
+
routerRemoveProvider(name) { return routerRemoveProvider(name); }
|
|
342
|
+
routerGetRules() { return getRouterRules(); }
|
|
343
|
+
routerSetRule(task, providerModel) { return setRouterRule(task, providerModel); }
|
|
344
|
+
routerGetActivationEnv() { return routerGetActivationEnv(); }
|
|
345
|
+
routerGetStatus() { return routerGetStatus(); }
|
|
346
|
+
routerListPresets() { return routerListPresets(); }
|
|
347
|
+
routerSavePreset(name) { return routerSavePreset(name); }
|
|
348
|
+
routerLoadPreset(name) { return routerLoadPreset(name); }
|
|
349
|
+
|
|
335
350
|
// Update - check npm for updates or update from local source
|
|
336
351
|
async update(args = []) {
|
|
337
352
|
const https = require('https');
|
package/lib/cli.js
CHANGED
|
@@ -352,6 +352,147 @@ function runCli(manager) {
|
|
|
352
352
|
}
|
|
353
353
|
break;
|
|
354
354
|
|
|
355
|
+
// Router (CCR)
|
|
356
|
+
case 'router': {
|
|
357
|
+
const sub = args[1];
|
|
358
|
+
if (sub === 'status') {
|
|
359
|
+
const status = manager.routerGetStatus();
|
|
360
|
+
console.log(`CCR installed: ${status.installed ? 'yes' : 'no'}`);
|
|
361
|
+
console.log(`Proxy running: ${status.proxyRunning ? 'yes' : 'no'}`);
|
|
362
|
+
if (status.proxyUrl) console.log(`Proxy URL: ${status.proxyUrl}`);
|
|
363
|
+
if (status.providers) console.log(`Providers: ${status.providers.length}`);
|
|
364
|
+
} else if (sub === 'list') {
|
|
365
|
+
const providers = manager.routerListProviders();
|
|
366
|
+
if (providers.length === 0) {
|
|
367
|
+
console.log('No providers configured.');
|
|
368
|
+
} else {
|
|
369
|
+
console.log('Providers:');
|
|
370
|
+
for (const p of providers) {
|
|
371
|
+
const models = p.models && p.models.length > 0 ? ` (${p.models.join(', ')})` : '';
|
|
372
|
+
console.log(` ${p.name} — ${p.url}${models}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
console.log('');
|
|
376
|
+
const rules = manager.routerGetRules();
|
|
377
|
+
const ruleKeys = Object.keys(rules);
|
|
378
|
+
if (ruleKeys.length === 0) {
|
|
379
|
+
console.log('No routing rules configured.');
|
|
380
|
+
} else {
|
|
381
|
+
console.log('Routing rules:');
|
|
382
|
+
for (const key of ruleKeys) {
|
|
383
|
+
console.log(` ${key} → ${rules[key]}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
} else if (sub === 'add-provider') {
|
|
387
|
+
const name = args[2];
|
|
388
|
+
if (!name) {
|
|
389
|
+
console.error('Usage: coder-config router add-provider <name> --url <url> [--key <key>] [--models <m1,m2>]');
|
|
390
|
+
process.exitCode = 1;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
const urlIdx = args.indexOf('--url');
|
|
394
|
+
const keyIdx = args.indexOf('--key');
|
|
395
|
+
const modelsIdx = args.indexOf('--models');
|
|
396
|
+
const url = urlIdx !== -1 ? args[urlIdx + 1] : undefined;
|
|
397
|
+
const apiKey = keyIdx !== -1 ? args[keyIdx + 1] : undefined;
|
|
398
|
+
const models = modelsIdx !== -1 ? args[modelsIdx + 1].split(',') : undefined;
|
|
399
|
+
if (!url) {
|
|
400
|
+
console.error('--url is required');
|
|
401
|
+
process.exitCode = 1;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
manager.routerAddProvider(name, { url, apiKey, models });
|
|
405
|
+
console.log(`Provider '${name}' added.`);
|
|
406
|
+
} else if (sub === 'remove-provider') {
|
|
407
|
+
const name = args[2];
|
|
408
|
+
if (!name) {
|
|
409
|
+
console.error('Usage: coder-config router remove-provider <name>');
|
|
410
|
+
process.exitCode = 1;
|
|
411
|
+
break;
|
|
412
|
+
}
|
|
413
|
+
manager.routerRemoveProvider(name);
|
|
414
|
+
console.log(`Provider '${name}' removed.`);
|
|
415
|
+
} else if (sub === 'set-rule') {
|
|
416
|
+
const task = args[2];
|
|
417
|
+
const providerModel = args[3];
|
|
418
|
+
if (!task || !providerModel) {
|
|
419
|
+
console.error('Usage: coder-config router set-rule <task> <provider,model>');
|
|
420
|
+
process.exitCode = 1;
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
manager.routerSetRule(task, providerModel);
|
|
424
|
+
console.log(`Rule set: ${task} → ${providerModel}`);
|
|
425
|
+
} else if (sub === 'preset') {
|
|
426
|
+
const presetCmd = args[2];
|
|
427
|
+
if (presetCmd === 'list') {
|
|
428
|
+
const presets = manager.routerListPresets();
|
|
429
|
+
if (presets.length === 0) {
|
|
430
|
+
console.log('No presets saved.');
|
|
431
|
+
} else {
|
|
432
|
+
console.log('Presets:');
|
|
433
|
+
for (const p of presets) {
|
|
434
|
+
console.log(` ${p}`);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} else if (presetCmd === 'save') {
|
|
438
|
+
const name = args[3];
|
|
439
|
+
if (!name) {
|
|
440
|
+
console.error('Usage: coder-config router preset save <name>');
|
|
441
|
+
process.exitCode = 1;
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
manager.routerSavePreset(name);
|
|
445
|
+
console.log(`Preset '${name}' saved.`);
|
|
446
|
+
} else if (presetCmd === 'load') {
|
|
447
|
+
const name = args[3];
|
|
448
|
+
if (!name) {
|
|
449
|
+
console.error('Usage: coder-config router preset load <name>');
|
|
450
|
+
process.exitCode = 1;
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
manager.routerLoadPreset(name);
|
|
454
|
+
console.log(`Preset '${name}' loaded.`);
|
|
455
|
+
} else {
|
|
456
|
+
console.log('Usage: coder-config router preset <list|save|load> [name]');
|
|
457
|
+
}
|
|
458
|
+
} else if (sub === 'start') {
|
|
459
|
+
const { execSync } = require('child_process');
|
|
460
|
+
try {
|
|
461
|
+
execSync('ccr code', { stdio: 'inherit' });
|
|
462
|
+
} catch (e) {
|
|
463
|
+
console.error('Failed to start CCR proxy. Is ccr installed?');
|
|
464
|
+
process.exitCode = 1;
|
|
465
|
+
}
|
|
466
|
+
} else if (sub === 'stop') {
|
|
467
|
+
const { execSync } = require('child_process');
|
|
468
|
+
try {
|
|
469
|
+
execSync('ccr stop', { stdio: 'inherit' });
|
|
470
|
+
} catch (e) {
|
|
471
|
+
console.error('Failed to stop CCR proxy. Is ccr installed?');
|
|
472
|
+
process.exitCode = 1;
|
|
473
|
+
}
|
|
474
|
+
} else if (sub === 'activate') {
|
|
475
|
+
const env = manager.routerGetActivationEnv();
|
|
476
|
+
for (const [key, value] of Object.entries(env)) {
|
|
477
|
+
console.log(`export ${key}="${value}"`);
|
|
478
|
+
}
|
|
479
|
+
} else {
|
|
480
|
+
console.log('Usage: coder-config router <command>');
|
|
481
|
+
console.log('');
|
|
482
|
+
console.log('Commands:');
|
|
483
|
+
console.log(' status Show CCR install/proxy status');
|
|
484
|
+
console.log(' list Show providers and routing rules');
|
|
485
|
+
console.log(' add-provider <name> --url <url> Add a provider');
|
|
486
|
+
console.log(' remove-provider <name> Remove a provider');
|
|
487
|
+
console.log(' set-rule <task> <provider,model> Set routing rule');
|
|
488
|
+
console.log(' preset list|save|load [name] Manage presets');
|
|
489
|
+
console.log(' start Start CCR proxy');
|
|
490
|
+
console.log(' stop Stop CCR proxy');
|
|
491
|
+
console.log(' activate Print env vars for shell eval');
|
|
492
|
+
}
|
|
493
|
+
break;
|
|
494
|
+
}
|
|
495
|
+
|
|
355
496
|
case 'version':
|
|
356
497
|
case '-v':
|
|
357
498
|
case '--version':
|
|
@@ -495,6 +636,20 @@ ${chalk.dim('Configuration manager for AI coding tools (Claude Code, Gemini CLI,
|
|
|
495
636
|
]));
|
|
496
637
|
console.log();
|
|
497
638
|
|
|
639
|
+
// Router (CCR)
|
|
640
|
+
console.log(box('Router (CCR)', [
|
|
641
|
+
cmd('router status', 'Show CCR install/proxy status'),
|
|
642
|
+
cmd('router list', 'Show providers and routing rules'),
|
|
643
|
+
cmd('router add-provider <name>', 'Add a provider'),
|
|
644
|
+
cmd('router remove-provider <name>', 'Remove a provider'),
|
|
645
|
+
cmd('router set-rule <task> <p,m>', 'Set routing rule'),
|
|
646
|
+
cmd('router preset list|save|load', 'Manage presets'),
|
|
647
|
+
cmd('router start', 'Start CCR proxy'),
|
|
648
|
+
cmd('router stop', 'Stop CCR proxy'),
|
|
649
|
+
cmd('router activate', 'Print env vars for shell eval'),
|
|
650
|
+
]));
|
|
651
|
+
console.log();
|
|
652
|
+
|
|
498
653
|
// UI & Maintenance
|
|
499
654
|
console.log(box('UI & Maintenance', [
|
|
500
655
|
cmd('ui', 'Start web UI (daemon mode)'),
|
package/lib/constants.js
CHANGED
package/lib/router.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Router (CCR) configuration management
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const CCR_DIR_NAME = '.claude-code-router';
|
|
10
|
+
const CONFIG_FILE = 'config.json';
|
|
11
|
+
const PRESET_DIR_NAME = 'coder-config-presets';
|
|
12
|
+
const CCR_PORT = 3456;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get path to CCR config file
|
|
16
|
+
*/
|
|
17
|
+
function getConfigPath() {
|
|
18
|
+
const homeDir = process.env.HOME || '';
|
|
19
|
+
return path.join(homeDir, CCR_DIR_NAME, CONFIG_FILE);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read and parse CCR config, return {} if missing or invalid
|
|
24
|
+
*/
|
|
25
|
+
function getConfig() {
|
|
26
|
+
const configPath = getConfigPath();
|
|
27
|
+
try {
|
|
28
|
+
if (!fs.existsSync(configPath)) return {};
|
|
29
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
30
|
+
} catch {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Write config JSON, creating directory if needed
|
|
37
|
+
*/
|
|
38
|
+
function saveConfig(config) {
|
|
39
|
+
const configPath = getConfigPath();
|
|
40
|
+
const dir = path.dirname(configPath);
|
|
41
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
42
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Return Providers array from config, or []
|
|
47
|
+
*/
|
|
48
|
+
function listProviders() {
|
|
49
|
+
const config = getConfig();
|
|
50
|
+
return Array.isArray(config.Providers) ? config.Providers : [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add or replace a provider by name
|
|
55
|
+
*/
|
|
56
|
+
function addProvider(name, providerConfig) {
|
|
57
|
+
const config = getConfig();
|
|
58
|
+
if (!Array.isArray(config.Providers)) {
|
|
59
|
+
config.Providers = [];
|
|
60
|
+
}
|
|
61
|
+
// Remove existing provider with same name
|
|
62
|
+
config.Providers = config.Providers.filter(p => p.name !== name);
|
|
63
|
+
config.Providers.push({ name, ...providerConfig });
|
|
64
|
+
saveConfig(config);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Remove a provider by name
|
|
69
|
+
*/
|
|
70
|
+
function removeProvider(name) {
|
|
71
|
+
const config = getConfig();
|
|
72
|
+
if (!Array.isArray(config.Providers)) return;
|
|
73
|
+
config.Providers = config.Providers.filter(p => p.name !== name);
|
|
74
|
+
saveConfig(config);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Return Router rules object or {}
|
|
79
|
+
*/
|
|
80
|
+
function getRouterRules() {
|
|
81
|
+
const config = getConfig();
|
|
82
|
+
return config.Router || {};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Set a single router rule (task -> providerModel string)
|
|
87
|
+
*/
|
|
88
|
+
function setRouterRule(task, providerModel) {
|
|
89
|
+
const config = getConfig();
|
|
90
|
+
if (!config.Router) {
|
|
91
|
+
config.Router = {};
|
|
92
|
+
}
|
|
93
|
+
config.Router[task] = providerModel;
|
|
94
|
+
saveConfig(config);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Return environment variables needed to activate CCR proxy
|
|
99
|
+
*/
|
|
100
|
+
function getActivationEnv() {
|
|
101
|
+
const config = getConfig();
|
|
102
|
+
return {
|
|
103
|
+
ANTHROPIC_BASE_URL: `http://127.0.0.1:${CCR_PORT}`,
|
|
104
|
+
ANTHROPIC_AUTH_TOKEN: config.APIKEY || '',
|
|
105
|
+
NO_PROXY: '127.0.0.1',
|
|
106
|
+
DISABLE_TELEMETRY: '1',
|
|
107
|
+
DISABLE_COST_WARNINGS: '1',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check CCR installation and running status
|
|
113
|
+
*/
|
|
114
|
+
function getStatus() {
|
|
115
|
+
let installed = false;
|
|
116
|
+
try {
|
|
117
|
+
execSync('which ccr', { stdio: 'pipe' });
|
|
118
|
+
installed = true;
|
|
119
|
+
} catch {
|
|
120
|
+
// not installed
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let running = false;
|
|
124
|
+
try {
|
|
125
|
+
execSync(`lsof -i :${CCR_PORT}`, { stdio: 'pipe' });
|
|
126
|
+
running = true;
|
|
127
|
+
} catch {
|
|
128
|
+
// not running
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const configExists = fs.existsSync(getConfigPath());
|
|
132
|
+
|
|
133
|
+
return { installed, running, configExists };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get presets directory path
|
|
138
|
+
*/
|
|
139
|
+
function getPresetsDir() {
|
|
140
|
+
const homeDir = process.env.HOME || '';
|
|
141
|
+
return path.join(homeDir, CCR_DIR_NAME, PRESET_DIR_NAME);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* List saved presets (names without .json extension)
|
|
146
|
+
*/
|
|
147
|
+
function listPresets() {
|
|
148
|
+
const presetsDir = getPresetsDir();
|
|
149
|
+
if (!fs.existsSync(presetsDir)) return [];
|
|
150
|
+
return fs.readdirSync(presetsDir)
|
|
151
|
+
.filter(f => f.endsWith('.json'))
|
|
152
|
+
.map(f => f.replace(/\.json$/, ''));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Snapshot current config to a named preset
|
|
157
|
+
*/
|
|
158
|
+
function savePreset(name) {
|
|
159
|
+
const config = getConfig();
|
|
160
|
+
const presetsDir = getPresetsDir();
|
|
161
|
+
fs.mkdirSync(presetsDir, { recursive: true });
|
|
162
|
+
fs.writeFileSync(path.join(presetsDir, `${name}.json`), JSON.stringify(config, null, 2) + '\n');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Restore config from a named preset
|
|
167
|
+
*/
|
|
168
|
+
function loadPreset(name) {
|
|
169
|
+
const presetPath = path.join(getPresetsDir(), `${name}.json`);
|
|
170
|
+
if (!fs.existsSync(presetPath)) {
|
|
171
|
+
throw new Error(`Preset "${name}" not found`);
|
|
172
|
+
}
|
|
173
|
+
const config = JSON.parse(fs.readFileSync(presetPath, 'utf8'));
|
|
174
|
+
saveConfig(config);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
getConfigPath,
|
|
179
|
+
getConfig,
|
|
180
|
+
saveConfig,
|
|
181
|
+
listProviders,
|
|
182
|
+
addProvider,
|
|
183
|
+
removeProvider,
|
|
184
|
+
getRouterRules,
|
|
185
|
+
setRouterRule,
|
|
186
|
+
getActivationEnv,
|
|
187
|
+
getStatus,
|
|
188
|
+
listPresets,
|
|
189
|
+
savePreset,
|
|
190
|
+
loadPreset,
|
|
191
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coder-config",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.6-beta",
|
|
4
4
|
"description": "Configuration manager for AI coding tools - Claude Code, Gemini CLI, Codex CLI, Antigravity. Manage MCPs, rules, permissions, memory, and workstreams.",
|
|
5
5
|
"author": "regression.io",
|
|
6
6
|
"main": "config-loader.js",
|