ai-account-switch 1.5.6 → 1.6.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 +325 -5
- package/README_ZH.md +316 -5
- package/package.json +6 -4
- package/src/commands/README.md +124 -0
- package/src/commands/account.js +997 -0
- package/src/commands/helpers.js +35 -0
- package/src/commands/index.js +53 -0
- package/src/commands/model.js +383 -0
- package/src/commands/utility.js +326 -0
- package/src/config.js +158 -7
- package/src/index.js +55 -55
- package/src/ui-server.js +494 -47
- package/src/commands.js +0 -1182
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const ConfigManager = require('../config');
|
|
3
|
+
const { validateAccount } = require('./helpers');
|
|
4
|
+
|
|
5
|
+
const config = new ConfigManager();
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Show configuration paths
|
|
9
|
+
*/
|
|
10
|
+
function showPaths() {
|
|
11
|
+
const paths = config.getConfigPaths();
|
|
12
|
+
const projectRoot = config.findProjectRoot();
|
|
13
|
+
|
|
14
|
+
console.log(chalk.bold('\n📂 Configuration Paths (配置路径):\n'));
|
|
15
|
+
console.log(`${chalk.cyan('Global config file (全局配置文件):')} ${paths.global}`);
|
|
16
|
+
console.log(`${chalk.cyan('Global config directory (全局配置目录):')} ${paths.globalDir}`);
|
|
17
|
+
console.log(`${chalk.cyan('Project config file (项目配置文件):')} ${paths.project}`);
|
|
18
|
+
|
|
19
|
+
if (projectRoot) {
|
|
20
|
+
const claudeConfigPath = require('path').join(projectRoot, '.claude', 'settings.local.json');
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
console.log(`${chalk.cyan('Claude config file (Claude 配置文件):')} ${claudeConfigPath}`);
|
|
23
|
+
console.log(`${chalk.cyan('Claude config exists (Claude 配置是否存在):')} ${fs.existsSync(claudeConfigPath) ? chalk.green('✓ Yes (是)') : chalk.red('✗ No (否)')}`);
|
|
24
|
+
console.log(`${chalk.cyan('Project root (项目根目录):')} ${projectRoot}`);
|
|
25
|
+
console.log(`${chalk.cyan('Current directory (当前目录):')} ${process.cwd()}`);
|
|
26
|
+
} else {
|
|
27
|
+
console.log(chalk.yellow('\n⚠ Not in a configured project directory (不在已配置的项目目录中)'));
|
|
28
|
+
}
|
|
29
|
+
console.log('');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Diagnose Claude Code configuration issues
|
|
34
|
+
*/
|
|
35
|
+
async function doctor() {
|
|
36
|
+
const path = require('path');
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const os = require('os');
|
|
39
|
+
|
|
40
|
+
console.log(chalk.bold.cyan('\n🔍 Claude Code Configuration Diagnostics\n'));
|
|
41
|
+
|
|
42
|
+
// Check current directory
|
|
43
|
+
console.log(chalk.bold('1. Current Directory:'));
|
|
44
|
+
console.log(` ${process.cwd()}\n`);
|
|
45
|
+
|
|
46
|
+
// Check project root
|
|
47
|
+
const projectRoot = config.findProjectRoot();
|
|
48
|
+
console.log(chalk.bold('2. Project Root Detection:'));
|
|
49
|
+
if (projectRoot) {
|
|
50
|
+
console.log(chalk.green(` ✓ Found project root: ${projectRoot}`));
|
|
51
|
+
} else {
|
|
52
|
+
console.log(chalk.yellow(' ⚠ No project root found (not in a configured project)'));
|
|
53
|
+
console.log(chalk.yellow(' Run "ais use <account>" in your project root first (请先在项目根目录运行 "ais use <账号名>")\n'));
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check ais project config
|
|
58
|
+
const aisConfigPath = path.join(projectRoot, '.ais-project-config');
|
|
59
|
+
console.log(chalk.bold('\n3. AIS Project Configuration:'));
|
|
60
|
+
if (fs.existsSync(aisConfigPath)) {
|
|
61
|
+
console.log(chalk.green(` ✓ Config exists: ${aisConfigPath}`));
|
|
62
|
+
try {
|
|
63
|
+
const aisConfig = JSON.parse(fs.readFileSync(aisConfigPath, 'utf8'));
|
|
64
|
+
console.log(` Account: ${chalk.cyan(aisConfig.activeAccount)}`);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
console.log(chalk.red(` ✗ Error reading config: ${e.message}`));
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
console.log(chalk.red(` ✗ Config not found: ${aisConfigPath}`));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check Claude config
|
|
73
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
74
|
+
const claudeConfigPath = path.join(claudeDir, 'settings.local.json');
|
|
75
|
+
|
|
76
|
+
console.log(chalk.bold('\n4. Claude Code Configuration:'));
|
|
77
|
+
console.log(` Expected location: ${claudeConfigPath}`);
|
|
78
|
+
|
|
79
|
+
if (fs.existsSync(claudeConfigPath)) {
|
|
80
|
+
console.log(chalk.green(' ✓ Claude config exists'));
|
|
81
|
+
try {
|
|
82
|
+
const claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf8'));
|
|
83
|
+
|
|
84
|
+
if (claudeConfig.env && claudeConfig.env.ANTHROPIC_AUTH_TOKEN) {
|
|
85
|
+
const token = claudeConfig.env.ANTHROPIC_AUTH_TOKEN;
|
|
86
|
+
const masked = token.substring(0, 6) + '****' + token.substring(token.length - 4);
|
|
87
|
+
console.log(` API Token: ${masked}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (claudeConfig.env && claudeConfig.env.ANTHROPIC_BASE_URL) {
|
|
91
|
+
console.log(` API URL: ${claudeConfig.env.ANTHROPIC_BASE_URL}`);
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.log(chalk.red(` ✗ Error reading Claude config: ${e.message}`));
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
console.log(chalk.red(' ✗ Claude config not found'));
|
|
98
|
+
console.log(chalk.yellow(' Run "ais use <account>" to generate it'));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check Droids config
|
|
102
|
+
const droidsDir = path.join(projectRoot, '.droids');
|
|
103
|
+
const droidsConfigPath = path.join(droidsDir, 'config.json');
|
|
104
|
+
|
|
105
|
+
console.log(chalk.bold('\n5. Droids Configuration:'));
|
|
106
|
+
console.log(` Expected location: ${droidsConfigPath}`);
|
|
107
|
+
|
|
108
|
+
if (fs.existsSync(droidsConfigPath)) {
|
|
109
|
+
console.log(chalk.green(' ✓ Droids config exists'));
|
|
110
|
+
try {
|
|
111
|
+
const droidsConfig = JSON.parse(fs.readFileSync(droidsConfigPath, 'utf8'));
|
|
112
|
+
|
|
113
|
+
if (droidsConfig.apiKey) {
|
|
114
|
+
const masked = droidsConfig.apiKey.substring(0, 6) + '****' + droidsConfig.apiKey.substring(droidsConfig.apiKey.length - 4);
|
|
115
|
+
console.log(` API Key: ${masked}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (droidsConfig.baseUrl) {
|
|
119
|
+
console.log(` Base URL: ${droidsConfig.baseUrl}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (droidsConfig.model) {
|
|
123
|
+
console.log(` Model: ${droidsConfig.model}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (droidsConfig.customSettings) {
|
|
127
|
+
console.log(` Custom Settings: ${Object.keys(droidsConfig.customSettings).join(', ')}`);
|
|
128
|
+
}
|
|
129
|
+
} catch (e) {
|
|
130
|
+
console.log(chalk.red(` ✗ Error reading Droids config: ${e.message}`));
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
console.log(chalk.yellow(' ⚠ Droids config not found'));
|
|
134
|
+
console.log(chalk.yellow(' Run "ais use <droids-account>" to generate it'));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Check Codex profile
|
|
138
|
+
const codexProfilePath = path.join(projectRoot, '.codex-profile');
|
|
139
|
+
const globalCodexConfig = path.join(os.homedir(), '.codex', 'config.toml');
|
|
140
|
+
|
|
141
|
+
console.log(chalk.bold('\n6. Codex Configuration:'));
|
|
142
|
+
console.log(` Profile file: ${codexProfilePath}`);
|
|
143
|
+
|
|
144
|
+
if (fs.existsSync(codexProfilePath)) {
|
|
145
|
+
const profileName = fs.readFileSync(codexProfilePath, 'utf8').trim();
|
|
146
|
+
console.log(chalk.green(` ✓ Codex profile exists: ${profileName}`));
|
|
147
|
+
console.log(chalk.cyan(` Usage: codex --profile ${profileName} [prompt]`));
|
|
148
|
+
|
|
149
|
+
// Check if profile exists in global config
|
|
150
|
+
if (fs.existsSync(globalCodexConfig)) {
|
|
151
|
+
try {
|
|
152
|
+
const globalConfig = fs.readFileSync(globalCodexConfig, 'utf8');
|
|
153
|
+
const profilePattern = new RegExp(`\\[profiles\\.${profileName}\\]`);
|
|
154
|
+
|
|
155
|
+
if (profilePattern.test(globalConfig)) {
|
|
156
|
+
console.log(chalk.green(` ✓ Profile configured in ~/.codex/config.toml`));
|
|
157
|
+
|
|
158
|
+
// Parse profile info
|
|
159
|
+
const providerMatch = globalConfig.match(new RegExp(`\\[profiles\\.${profileName}\\][\\s\\S]*?model_provider\\s*=\\s*"([^"]+)"`));
|
|
160
|
+
const modelMatch = globalConfig.match(new RegExp(`\\[profiles\\.${profileName}\\][\\s\\S]*?model\\s*=\\s*"([^"]+)"`));
|
|
161
|
+
|
|
162
|
+
if (providerMatch) {
|
|
163
|
+
console.log(` Model Provider: ${providerMatch[1]}`);
|
|
164
|
+
|
|
165
|
+
// Find provider details
|
|
166
|
+
const providerName = providerMatch[1];
|
|
167
|
+
const baseUrlMatch = globalConfig.match(new RegExp(`\\[model_providers\\.${providerName}\\][\\s\\S]*?base_url\\s*=\\s*"([^"]+)"`));
|
|
168
|
+
if (baseUrlMatch) {
|
|
169
|
+
console.log(` API URL: ${baseUrlMatch[1]}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
if (modelMatch) {
|
|
173
|
+
console.log(` Model: ${modelMatch[1]}`);
|
|
174
|
+
}
|
|
175
|
+
} else {
|
|
176
|
+
console.log(chalk.yellow(` ⚠ Profile not found in global config`));
|
|
177
|
+
console.log(chalk.yellow(` Run "ais use <account>" to regenerate it`));
|
|
178
|
+
}
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.log(chalk.red(` ✗ Error reading global Codex config: ${e.message}`));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
console.log(chalk.yellow(' ⚠ No Codex profile configured'));
|
|
185
|
+
console.log(chalk.yellow(' Run "ais use <codex-account>" to create one'));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check global Claude config
|
|
189
|
+
const globalClaudeConfig = path.join(os.homedir(), '.claude', 'settings.json');
|
|
190
|
+
console.log(chalk.bold('\n7. Global Claude Configuration:'));
|
|
191
|
+
console.log(` Location: ${globalClaudeConfig}`);
|
|
192
|
+
|
|
193
|
+
if (fs.existsSync(globalClaudeConfig)) {
|
|
194
|
+
console.log(chalk.yellow(' ⚠ Global config exists (may override project config in some cases)'));
|
|
195
|
+
try {
|
|
196
|
+
const globalConfig = JSON.parse(fs.readFileSync(globalClaudeConfig, 'utf8'));
|
|
197
|
+
if (globalConfig.env && globalConfig.env.ANTHROPIC_AUTH_TOKEN) {
|
|
198
|
+
const token = globalConfig.env.ANTHROPIC_AUTH_TOKEN;
|
|
199
|
+
const masked = token.substring(0, 6) + '****' + token.substring(token.length - 4);
|
|
200
|
+
console.log(` Global API Token: ${masked}`);
|
|
201
|
+
}
|
|
202
|
+
if (globalConfig.env && globalConfig.env.ANTHROPIC_BASE_URL) {
|
|
203
|
+
console.log(` Global API URL: ${globalConfig.env.ANTHROPIC_BASE_URL}`);
|
|
204
|
+
}
|
|
205
|
+
} catch (e) {
|
|
206
|
+
console.log(chalk.red(` ✗ Error reading global config: ${e.message}`));
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
console.log(chalk.green(' ✓ No global config (good - project config will be used)'));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Check current account availability
|
|
213
|
+
console.log(chalk.bold('\n8. Current Account Availability:'));
|
|
214
|
+
const projectAccount = config.getProjectAccount();
|
|
215
|
+
|
|
216
|
+
if (projectAccount && projectAccount.apiKey) {
|
|
217
|
+
console.log(` Testing account: ${chalk.cyan(projectAccount.name)}`);
|
|
218
|
+
console.log(` Account type: ${chalk.cyan(projectAccount.type)}`);
|
|
219
|
+
|
|
220
|
+
if (projectAccount.type === 'Claude') {
|
|
221
|
+
console.log(' Testing with Claude CLI...');
|
|
222
|
+
const { execSync } = require('child_process');
|
|
223
|
+
try {
|
|
224
|
+
execSync('claude --version', { stdio: 'pipe', timeout: 5000 });
|
|
225
|
+
console.log(chalk.green(' ✓ Claude CLI is available'));
|
|
226
|
+
|
|
227
|
+
// Interactive CLI test
|
|
228
|
+
console.log(' Running interactive test...');
|
|
229
|
+
try {
|
|
230
|
+
const testResult = execSync('echo "test" | claude', {
|
|
231
|
+
encoding: 'utf8',
|
|
232
|
+
timeout: 10000,
|
|
233
|
+
env: { ...process.env, ANTHROPIC_API_KEY: projectAccount.apiKey }
|
|
234
|
+
});
|
|
235
|
+
console.log(chalk.green(' ✓ Claude CLI interactive test passed'));
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.log(chalk.yellow(' ⚠ Claude CLI interactive test failed'));
|
|
238
|
+
console.log(chalk.gray(` Error: ${e.message}`));
|
|
239
|
+
}
|
|
240
|
+
} catch (e) {
|
|
241
|
+
console.log(chalk.yellow(' ⚠ Claude CLI not found, using API validation'));
|
|
242
|
+
}
|
|
243
|
+
} else if (projectAccount.type === 'Codex') {
|
|
244
|
+
console.log(' Testing with Codex CLI...');
|
|
245
|
+
const { execSync } = require('child_process');
|
|
246
|
+
try {
|
|
247
|
+
execSync('codex --version', { stdio: 'pipe', timeout: 5000 });
|
|
248
|
+
console.log(chalk.green(' ✓ Codex CLI is available'));
|
|
249
|
+
} catch (e) {
|
|
250
|
+
console.log(chalk.yellow(' ⚠ Codex CLI not found'));
|
|
251
|
+
}
|
|
252
|
+
} else if (projectAccount.type === 'Droids') {
|
|
253
|
+
console.log(' Testing with Droids CLI...');
|
|
254
|
+
const { execSync } = require('child_process');
|
|
255
|
+
try {
|
|
256
|
+
execSync('droid --version', { stdio: 'pipe', timeout: 5000 });
|
|
257
|
+
console.log(chalk.green(' ✓ Droids CLI is available'));
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.log(chalk.yellow(' ⚠ Droids CLI not found'));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
console.log(` API URL: ${projectAccount.apiUrl || 'https://api.anthropic.com'}`);
|
|
264
|
+
console.log(' Validating API key...');
|
|
265
|
+
|
|
266
|
+
const result = await validateAccount(projectAccount.apiKey, projectAccount.apiUrl);
|
|
267
|
+
|
|
268
|
+
if (result.valid) {
|
|
269
|
+
console.log(chalk.green(' ✓ Account is valid and accessible'));
|
|
270
|
+
if (result.statusCode) {
|
|
271
|
+
console.log(chalk.gray(` Response status: ${result.statusCode}`));
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
console.log(chalk.red(' ✗ Account validation failed'));
|
|
275
|
+
if (result.error) {
|
|
276
|
+
console.log(chalk.red(` Error: ${result.error}`));
|
|
277
|
+
} else if (result.statusCode) {
|
|
278
|
+
console.log(chalk.red(` Status code: ${result.statusCode}`));
|
|
279
|
+
if (result.statusCode === 401 || result.statusCode === 403) {
|
|
280
|
+
console.log(chalk.yellow(' ⚠ API key appears to be invalid or expired'));
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
} else {
|
|
285
|
+
console.log(chalk.yellow(' ⚠ No account configured or API key missing'));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Recommendations
|
|
289
|
+
console.log(chalk.bold('\n9. Recommendations:'));
|
|
290
|
+
|
|
291
|
+
if (projectRoot && process.cwd() !== projectRoot) {
|
|
292
|
+
console.log(chalk.yellow(` ⚠ You are in a subdirectory (${path.relative(projectRoot, process.cwd())})`));
|
|
293
|
+
console.log(chalk.cyan(' • Claude Code should still find the project config'));
|
|
294
|
+
console.log(chalk.cyan(' • Make sure to start Claude Code from this directory or parent directories'));
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (fs.existsSync(globalClaudeConfig)) {
|
|
298
|
+
console.log(chalk.yellow(' ⚠ Global Claude config exists:'));
|
|
299
|
+
console.log(chalk.cyan(' • Project config should take precedence'));
|
|
300
|
+
console.log(chalk.cyan(' • If issues persist, consider removing global env settings'));
|
|
301
|
+
console.log(chalk.gray(` • File: ${globalClaudeConfig}`));
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.log(chalk.bold('\n10. Next Steps:'));
|
|
305
|
+
console.log(chalk.cyan(' • Start Claude Code/Codex/Droids from your project directory or subdirectory'));
|
|
306
|
+
console.log(chalk.cyan(' • Check which account is being used'));
|
|
307
|
+
console.log(chalk.cyan(' • If wrong account is used, run: ais use <correct-account>'));
|
|
308
|
+
console.log('');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Start Web UI server
|
|
313
|
+
*/
|
|
314
|
+
function startUI() {
|
|
315
|
+
const UIServer = require('../ui-server');
|
|
316
|
+
const server = new UIServer();
|
|
317
|
+
|
|
318
|
+
console.log(chalk.cyan('\n🌐 Starting AIS Web UI... (正在启动 AIS Web 界面...)\n'));
|
|
319
|
+
server.start();
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = {
|
|
323
|
+
showPaths,
|
|
324
|
+
doctor,
|
|
325
|
+
startUI
|
|
326
|
+
};
|
package/src/config.js
CHANGED
|
@@ -146,6 +146,13 @@ class ConfigManager {
|
|
|
146
146
|
if (account.type === 'Codex') {
|
|
147
147
|
// Codex type accounts only need Codex configuration
|
|
148
148
|
this.generateCodexConfig(account, projectRoot);
|
|
149
|
+
} else if (account.type === 'Droids') {
|
|
150
|
+
// Droids type accounts only need Droids configuration
|
|
151
|
+
this.generateDroidsConfig(account, projectRoot);
|
|
152
|
+
} else if (account.type === 'CCR') {
|
|
153
|
+
// CCR type accounts need both CCR and Claude configuration
|
|
154
|
+
this.generateCCRConfig(account, projectRoot);
|
|
155
|
+
this.generateClaudeConfigForCCR(account, projectRoot);
|
|
149
156
|
} else {
|
|
150
157
|
// Claude and other types need Claude Code configuration
|
|
151
158
|
this.generateClaudeConfig(account, projectRoot);
|
|
@@ -173,7 +180,8 @@ class ConfigManager {
|
|
|
173
180
|
const filesToIgnore = [
|
|
174
181
|
this.projectConfigFilename,
|
|
175
182
|
'.claude/settings.local.json',
|
|
176
|
-
'.codex-profile'
|
|
183
|
+
'.codex-profile',
|
|
184
|
+
'.droids/config.json'
|
|
177
185
|
];
|
|
178
186
|
|
|
179
187
|
let gitignoreContent = '';
|
|
@@ -346,6 +354,152 @@ class ConfigManager {
|
|
|
346
354
|
fs.writeFileSync(claudeConfigFile, JSON.stringify(claudeConfig, null, 2), 'utf8');
|
|
347
355
|
}
|
|
348
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Generate Droids configuration in .droids/config.json
|
|
359
|
+
*/
|
|
360
|
+
generateDroidsConfig(account, projectRoot = process.cwd()) {
|
|
361
|
+
const droidsDir = path.join(projectRoot, '.droids');
|
|
362
|
+
const droidsConfigFile = path.join(droidsDir, 'config.json');
|
|
363
|
+
|
|
364
|
+
// Create .droids directory if it doesn't exist
|
|
365
|
+
if (!fs.existsSync(droidsDir)) {
|
|
366
|
+
fs.mkdirSync(droidsDir, { recursive: true });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Build Droids configuration
|
|
370
|
+
const droidsConfig = {
|
|
371
|
+
apiKey: account.apiKey
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
// Add API URL if specified
|
|
375
|
+
if (account.apiUrl) {
|
|
376
|
+
droidsConfig.baseUrl = account.apiUrl;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Add model configuration - Droids uses simple model field
|
|
380
|
+
if (account.model) {
|
|
381
|
+
droidsConfig.model = account.model;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Add custom environment variables as customSettings
|
|
385
|
+
if (account.customEnv && typeof account.customEnv === 'object') {
|
|
386
|
+
droidsConfig.customSettings = account.customEnv;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Write Droids configuration
|
|
390
|
+
fs.writeFileSync(droidsConfigFile, JSON.stringify(droidsConfig, null, 2), 'utf8');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Generate CCR configuration in ~/.claude-code-router/config.json
|
|
395
|
+
*/
|
|
396
|
+
generateCCRConfig(account, projectRoot = process.cwd()) {
|
|
397
|
+
const ccrConfigDir = path.join(os.homedir(), '.claude-code-router');
|
|
398
|
+
const ccrConfigFile = path.join(ccrConfigDir, 'config.json');
|
|
399
|
+
|
|
400
|
+
// Read existing config
|
|
401
|
+
let config = {};
|
|
402
|
+
if (fs.existsSync(ccrConfigFile)) {
|
|
403
|
+
const data = fs.readFileSync(ccrConfigFile, 'utf8');
|
|
404
|
+
config = JSON.parse(data);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (!account.ccrConfig) return;
|
|
408
|
+
|
|
409
|
+
const { providerName, models, defaultModel, backgroundModel, thinkModel } = account.ccrConfig;
|
|
410
|
+
|
|
411
|
+
// Check if provider exists
|
|
412
|
+
const providerIndex = config.Providers?.findIndex(p => p.name === providerName);
|
|
413
|
+
|
|
414
|
+
const provider = {
|
|
415
|
+
api_base_url: account.apiUrl || '',
|
|
416
|
+
api_key: account.apiKey,
|
|
417
|
+
models: models,
|
|
418
|
+
name: providerName
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
if (providerIndex >= 0) {
|
|
422
|
+
config.Providers[providerIndex] = provider;
|
|
423
|
+
} else {
|
|
424
|
+
if (!config.Providers) config.Providers = [];
|
|
425
|
+
config.Providers.push(provider);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Update Router configuration
|
|
429
|
+
if (!config.Router) config.Router = {};
|
|
430
|
+
config.Router.default = `${providerName},${defaultModel}`;
|
|
431
|
+
config.Router.background = `${providerName},${backgroundModel}`;
|
|
432
|
+
config.Router.think = `${providerName},${thinkModel}`;
|
|
433
|
+
|
|
434
|
+
fs.writeFileSync(ccrConfigFile, JSON.stringify(config, null, 2), 'utf8');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Generate Claude configuration for CCR type accounts
|
|
439
|
+
*/
|
|
440
|
+
generateClaudeConfigForCCR(account, projectRoot = process.cwd()) {
|
|
441
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
442
|
+
const claudeConfigFile = path.join(claudeDir, 'settings.local.json');
|
|
443
|
+
const ccrConfigFile = path.join(os.homedir(), '.claude-code-router', 'config.json');
|
|
444
|
+
|
|
445
|
+
// Create .claude directory if it doesn't exist
|
|
446
|
+
if (!fs.existsSync(claudeDir)) {
|
|
447
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Read CCR config to get PORT
|
|
451
|
+
let port = 3456; // default port
|
|
452
|
+
if (fs.existsSync(ccrConfigFile)) {
|
|
453
|
+
try {
|
|
454
|
+
const ccrConfig = JSON.parse(fs.readFileSync(ccrConfigFile, 'utf8'));
|
|
455
|
+
if (ccrConfig.PORT) {
|
|
456
|
+
port = ccrConfig.PORT;
|
|
457
|
+
}
|
|
458
|
+
} catch (e) {
|
|
459
|
+
// Use default port if reading fails
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Read existing config if it exists
|
|
464
|
+
let existingConfig = {};
|
|
465
|
+
if (fs.existsSync(claudeConfigFile)) {
|
|
466
|
+
try {
|
|
467
|
+
const data = fs.readFileSync(claudeConfigFile, 'utf8');
|
|
468
|
+
existingConfig = JSON.parse(data);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
existingConfig = {};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const claudeConfig = {
|
|
475
|
+
...existingConfig,
|
|
476
|
+
env: {
|
|
477
|
+
...(existingConfig.env || {}),
|
|
478
|
+
ANTHROPIC_AUTH_TOKEN: account.apiKey,
|
|
479
|
+
ANTHROPIC_BASE_URL: `http://127.0.0.1:${port}`
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// Add custom environment variables if specified
|
|
484
|
+
if (account.customEnv && typeof account.customEnv === 'object') {
|
|
485
|
+
Object.keys(account.customEnv).forEach(key => {
|
|
486
|
+
claudeConfig.env[key] = account.customEnv[key];
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// Preserve existing permissions if any
|
|
491
|
+
if (!claudeConfig.permissions) {
|
|
492
|
+
claudeConfig.permissions = existingConfig.permissions || {
|
|
493
|
+
allow: [],
|
|
494
|
+
deny: [],
|
|
495
|
+
ask: []
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Write Claude configuration
|
|
500
|
+
fs.writeFileSync(claudeConfigFile, JSON.stringify(claudeConfig, null, 2), 'utf8');
|
|
501
|
+
}
|
|
502
|
+
|
|
349
503
|
/**
|
|
350
504
|
* Generate Codex profile in global ~/.codex/config.toml
|
|
351
505
|
*/
|
|
@@ -385,12 +539,9 @@ class ConfigManager {
|
|
|
385
539
|
|
|
386
540
|
profileConfig += `model_provider = "${providerName}"\n`;
|
|
387
541
|
|
|
388
|
-
// Add model configuration
|
|
389
|
-
if (account.
|
|
390
|
-
|
|
391
|
-
if (activeGroup && activeGroup.DEFAULT_MODEL) {
|
|
392
|
-
profileConfig += `model = "${activeGroup.DEFAULT_MODEL}"\n`;
|
|
393
|
-
}
|
|
542
|
+
// Add model configuration - Codex uses simple model field
|
|
543
|
+
if (account.model) {
|
|
544
|
+
profileConfig += `model = "${account.model}"\n`;
|
|
394
545
|
}
|
|
395
546
|
|
|
396
547
|
// Ensure API URL has proper path
|