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
package/src/commands.js
DELETED
|
@@ -1,1182 +0,0 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const inquirer = require('inquirer');
|
|
3
|
-
const ConfigManager = require('./config');
|
|
4
|
-
|
|
5
|
-
const config = new ConfigManager();
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Add a new account
|
|
9
|
-
*/
|
|
10
|
-
async function addAccount(name, options) {
|
|
11
|
-
// If name not provided, prompt for it
|
|
12
|
-
if (!name) {
|
|
13
|
-
const answers = await inquirer.prompt([
|
|
14
|
-
{
|
|
15
|
-
type: 'input',
|
|
16
|
-
name: 'accountName',
|
|
17
|
-
message: 'Enter account name:',
|
|
18
|
-
validate: (input) => input.trim() !== '' || 'Account name is required'
|
|
19
|
-
}
|
|
20
|
-
]);
|
|
21
|
-
name = answers.accountName;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Check if account already exists
|
|
25
|
-
if (config.accountExists(name)) {
|
|
26
|
-
const { overwrite } = await inquirer.prompt([
|
|
27
|
-
{
|
|
28
|
-
type: 'confirm',
|
|
29
|
-
name: 'overwrite',
|
|
30
|
-
message: `Account '${name}' already exists. Overwrite?`,
|
|
31
|
-
default: false
|
|
32
|
-
}
|
|
33
|
-
]);
|
|
34
|
-
|
|
35
|
-
if (!overwrite) {
|
|
36
|
-
console.log(chalk.yellow('Operation cancelled.'));
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Prompt for account type first
|
|
42
|
-
const typeAnswer = await inquirer.prompt([
|
|
43
|
-
{
|
|
44
|
-
type: 'list',
|
|
45
|
-
name: 'type',
|
|
46
|
-
message: 'Select account type:',
|
|
47
|
-
choices: ['Claude', 'Codex', 'Other'],
|
|
48
|
-
default: 'Claude'
|
|
49
|
-
}
|
|
50
|
-
]);
|
|
51
|
-
|
|
52
|
-
// Show configuration tips based on account type
|
|
53
|
-
if (typeAnswer.type === 'Codex') {
|
|
54
|
-
console.log(chalk.cyan('\nš Codex Configuration Tips:'));
|
|
55
|
-
console.log(chalk.gray(' ⢠API URL should include the full path (e.g., https://api.example.com/v1)'));
|
|
56
|
-
console.log(chalk.gray(' ⢠AIS will automatically add /v1 if missing'));
|
|
57
|
-
console.log(chalk.gray(' ⢠Codex uses OpenAI-compatible API format\n'));
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Prompt for remaining account details
|
|
61
|
-
const accountData = await inquirer.prompt([
|
|
62
|
-
{
|
|
63
|
-
type: 'input',
|
|
64
|
-
name: 'apiKey',
|
|
65
|
-
message: 'Enter API Key:',
|
|
66
|
-
validate: (input) => input.trim() !== '' || 'API Key is required'
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
type: 'input',
|
|
70
|
-
name: 'apiUrl',
|
|
71
|
-
message: typeAnswer.type === 'Codex'
|
|
72
|
-
? 'Enter API URL (e.g., https://api.example.com or https://api.example.com/v1):'
|
|
73
|
-
: 'Enter API URL (optional):',
|
|
74
|
-
default: ''
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
type: 'input',
|
|
78
|
-
name: 'email',
|
|
79
|
-
message: 'Enter associated email (optional):',
|
|
80
|
-
default: ''
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
type: 'input',
|
|
84
|
-
name: 'description',
|
|
85
|
-
message: 'Enter description (optional):',
|
|
86
|
-
default: ''
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
type: 'confirm',
|
|
90
|
-
name: 'addCustomEnv',
|
|
91
|
-
message: 'Add custom environment variables? (e.g., CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC)',
|
|
92
|
-
default: false
|
|
93
|
-
}
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
// Merge type into accountData
|
|
97
|
-
accountData.type = typeAnswer.type;
|
|
98
|
-
|
|
99
|
-
// Handle custom environment variables
|
|
100
|
-
if (accountData.addCustomEnv) {
|
|
101
|
-
accountData.customEnv = {};
|
|
102
|
-
let addMore = true;
|
|
103
|
-
|
|
104
|
-
console.log(chalk.cyan('\nš” Tip: Enter in format KEY=VALUE (e.g., CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1)'));
|
|
105
|
-
console.log(chalk.gray(' Or leave empty to finish\n'));
|
|
106
|
-
|
|
107
|
-
while (addMore) {
|
|
108
|
-
const envInput = await inquirer.prompt([
|
|
109
|
-
{
|
|
110
|
-
type: 'input',
|
|
111
|
-
name: 'envVar',
|
|
112
|
-
message: 'Environment variable (KEY=VALUE format):',
|
|
113
|
-
validate: (input) => {
|
|
114
|
-
// Allow empty input to skip
|
|
115
|
-
if (!input.trim()) return true;
|
|
116
|
-
|
|
117
|
-
// Check if input contains '='
|
|
118
|
-
if (!input.includes('=')) {
|
|
119
|
-
return 'Invalid format. Use KEY=VALUE format (e.g., MY_VAR=value)';
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const [key, ...valueParts] = input.split('=');
|
|
123
|
-
const value = valueParts.join('='); // In case value contains '='
|
|
124
|
-
|
|
125
|
-
if (!key.trim()) {
|
|
126
|
-
return 'Variable name cannot be empty';
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (!/^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
|
|
130
|
-
return 'Invalid variable name. Use uppercase letters, numbers, and underscores (e.g., MY_VAR)';
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!value.trim()) {
|
|
134
|
-
return 'Variable value cannot be empty';
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
]);
|
|
141
|
-
|
|
142
|
-
// If user left input empty, skip adding more
|
|
143
|
-
if (!envInput.envVar.trim()) {
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Parse KEY=VALUE
|
|
148
|
-
const [key, ...valueParts] = envInput.envVar.split('=');
|
|
149
|
-
const value = valueParts.join('='); // In case value contains '='
|
|
150
|
-
|
|
151
|
-
accountData.customEnv[key.trim()] = value.trim();
|
|
152
|
-
|
|
153
|
-
// Display currently added variables
|
|
154
|
-
console.log(chalk.green('\nā Added:'), chalk.cyan(`${key.trim()}=${value.trim()}`));
|
|
155
|
-
|
|
156
|
-
if (Object.keys(accountData.customEnv).length > 0) {
|
|
157
|
-
console.log(chalk.bold('\nš Current environment variables:'));
|
|
158
|
-
Object.entries(accountData.customEnv).forEach(([k, v]) => {
|
|
159
|
-
console.log(chalk.gray(' ā¢'), chalk.cyan(`${k}=${v}`));
|
|
160
|
-
});
|
|
161
|
-
console.log('');
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const { continueAdding } = await inquirer.prompt([
|
|
165
|
-
{
|
|
166
|
-
type: 'confirm',
|
|
167
|
-
name: 'continueAdding',
|
|
168
|
-
message: 'Add another environment variable?',
|
|
169
|
-
default: false
|
|
170
|
-
}
|
|
171
|
-
]);
|
|
172
|
-
|
|
173
|
-
addMore = continueAdding;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (Object.keys(accountData.customEnv).length > 0) {
|
|
177
|
-
console.log(chalk.green(`\nā Total: ${Object.keys(accountData.customEnv).length} custom environment variable(s) added\n`));
|
|
178
|
-
} else {
|
|
179
|
-
console.log(chalk.yellow('\nā No custom environment variables added\n'));
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Remove the addCustomEnv flag before saving
|
|
184
|
-
delete accountData.addCustomEnv;
|
|
185
|
-
|
|
186
|
-
// Initialize model groups structure
|
|
187
|
-
accountData.modelGroups = {};
|
|
188
|
-
accountData.activeModelGroup = null;
|
|
189
|
-
|
|
190
|
-
// Prompt for model group configuration
|
|
191
|
-
const { createModelGroup } = await inquirer.prompt([
|
|
192
|
-
{
|
|
193
|
-
type: 'confirm',
|
|
194
|
-
name: 'createModelGroup',
|
|
195
|
-
message: 'Do you want to create a model group? (Recommended)',
|
|
196
|
-
default: true
|
|
197
|
-
}
|
|
198
|
-
]);
|
|
199
|
-
|
|
200
|
-
if (createModelGroup) {
|
|
201
|
-
const groupName = 'default';
|
|
202
|
-
const modelGroupConfig = await promptForModelGroup();
|
|
203
|
-
|
|
204
|
-
if (Object.keys(modelGroupConfig).length > 0) {
|
|
205
|
-
accountData.modelGroups[groupName] = modelGroupConfig;
|
|
206
|
-
accountData.activeModelGroup = groupName;
|
|
207
|
-
console.log(chalk.green(`\nā Created model group '${groupName}'`));
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Save account
|
|
212
|
-
config.addAccount(name, accountData);
|
|
213
|
-
console.log(chalk.green(`ā Account '${name}' added successfully!`));
|
|
214
|
-
|
|
215
|
-
if (accountData.activeModelGroup) {
|
|
216
|
-
console.log(chalk.cyan(`ā Active model group: ${accountData.activeModelGroup}\n`));
|
|
217
|
-
console.log(chalk.gray('š” Tip: Use "ais model add" to create more model groups'));
|
|
218
|
-
console.log(chalk.gray('š” Tip: Use "ais model list" to view all model groups\n'));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Show usage instructions based on account type
|
|
222
|
-
if (accountData.type === 'Codex') {
|
|
223
|
-
console.log(chalk.bold.cyan('\nš Codex Usage Instructions:\n'));
|
|
224
|
-
console.log(chalk.gray('1. Switch to this account in your project:'));
|
|
225
|
-
console.log(chalk.cyan(` ais use ${name}\n`));
|
|
226
|
-
console.log(chalk.gray('2. Use Codex with the generated profile:'));
|
|
227
|
-
console.log(chalk.cyan(` codex --profile ais_<project-name> "your prompt"\n`));
|
|
228
|
-
console.log(chalk.gray('3. The profile name will be shown when you run "ais use"\n'));
|
|
229
|
-
} else if (accountData.type === 'Claude') {
|
|
230
|
-
console.log(chalk.bold.cyan('\nš Claude Usage Instructions:\n'));
|
|
231
|
-
console.log(chalk.gray('1. Switch to this account in your project:'));
|
|
232
|
-
console.log(chalk.cyan(` ais use ${name}\n`));
|
|
233
|
-
console.log(chalk.gray('2. Start Claude Code in your project directory'));
|
|
234
|
-
console.log(chalk.gray('3. Claude Code will automatically use the project configuration\n'));
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Helper function to prompt for model group configuration
|
|
240
|
-
*/
|
|
241
|
-
async function promptForModelGroup() {
|
|
242
|
-
console.log(chalk.cyan('\nš¤ Model Group Configuration'));
|
|
243
|
-
console.log(chalk.gray('Configure which models to use. Leave empty to skip.\n'));
|
|
244
|
-
|
|
245
|
-
const modelQuestions = await inquirer.prompt([
|
|
246
|
-
{
|
|
247
|
-
type: 'input',
|
|
248
|
-
name: 'defaultModel',
|
|
249
|
-
message: 'DEFAULT_MODEL (base model, used if others are not set):',
|
|
250
|
-
default: ''
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
type: 'input',
|
|
254
|
-
name: 'anthropicDefaultOpusModel',
|
|
255
|
-
message: 'ANTHROPIC_DEFAULT_OPUS_MODEL (leave empty to use DEFAULT_MODEL):',
|
|
256
|
-
default: ''
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
type: 'input',
|
|
260
|
-
name: 'anthropicDefaultSonnetModel',
|
|
261
|
-
message: 'ANTHROPIC_DEFAULT_SONNET_MODEL (leave empty to use DEFAULT_MODEL):',
|
|
262
|
-
default: ''
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
type: 'input',
|
|
266
|
-
name: 'anthropicDefaultHaikuModel',
|
|
267
|
-
message: 'ANTHROPIC_DEFAULT_HAIKU_MODEL (leave empty to use DEFAULT_MODEL):',
|
|
268
|
-
default: ''
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
type: 'input',
|
|
272
|
-
name: 'claudeCodeSubagentModel',
|
|
273
|
-
message: 'CLAUDE_CODE_SUBAGENT_MODEL (leave empty to use DEFAULT_MODEL):',
|
|
274
|
-
default: ''
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
type: 'input',
|
|
278
|
-
name: 'anthropicModel',
|
|
279
|
-
message: 'ANTHROPIC_MODEL (leave empty to use DEFAULT_MODEL):',
|
|
280
|
-
default: ''
|
|
281
|
-
}
|
|
282
|
-
]);
|
|
283
|
-
|
|
284
|
-
const modelConfig = {};
|
|
285
|
-
|
|
286
|
-
// Only add non-empty model configurations
|
|
287
|
-
if (modelQuestions.defaultModel.trim()) {
|
|
288
|
-
modelConfig.DEFAULT_MODEL = modelQuestions.defaultModel.trim();
|
|
289
|
-
}
|
|
290
|
-
if (modelQuestions.anthropicDefaultOpusModel.trim()) {
|
|
291
|
-
modelConfig.ANTHROPIC_DEFAULT_OPUS_MODEL = modelQuestions.anthropicDefaultOpusModel.trim();
|
|
292
|
-
}
|
|
293
|
-
if (modelQuestions.anthropicDefaultSonnetModel.trim()) {
|
|
294
|
-
modelConfig.ANTHROPIC_DEFAULT_SONNET_MODEL = modelQuestions.anthropicDefaultSonnetModel.trim();
|
|
295
|
-
}
|
|
296
|
-
if (modelQuestions.anthropicDefaultHaikuModel.trim()) {
|
|
297
|
-
modelConfig.ANTHROPIC_DEFAULT_HAIKU_MODEL = modelQuestions.anthropicDefaultHaikuModel.trim();
|
|
298
|
-
}
|
|
299
|
-
if (modelQuestions.claudeCodeSubagentModel.trim()) {
|
|
300
|
-
modelConfig.CLAUDE_CODE_SUBAGENT_MODEL = modelQuestions.claudeCodeSubagentModel.trim();
|
|
301
|
-
}
|
|
302
|
-
if (modelQuestions.anthropicModel.trim()) {
|
|
303
|
-
modelConfig.ANTHROPIC_MODEL = modelQuestions.anthropicModel.trim();
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (Object.keys(modelConfig).length > 0) {
|
|
307
|
-
console.log(chalk.green('\nā Model configuration:'));
|
|
308
|
-
Object.entries(modelConfig).forEach(([key, value]) => {
|
|
309
|
-
console.log(chalk.gray(' ā¢'), chalk.cyan(`${key}=${value}`));
|
|
310
|
-
});
|
|
311
|
-
console.log('');
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return modelConfig;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* List all accounts
|
|
319
|
-
*/
|
|
320
|
-
function listAccounts() {
|
|
321
|
-
const accounts = config.getAllAccounts();
|
|
322
|
-
const accountNames = Object.keys(accounts);
|
|
323
|
-
|
|
324
|
-
if (accountNames.length === 0) {
|
|
325
|
-
console.log(chalk.yellow('No accounts found. Use "ais add" to add an account.'));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const currentProject = config.getProjectAccount();
|
|
330
|
-
|
|
331
|
-
console.log(chalk.bold('\nš Available Accounts:\n'));
|
|
332
|
-
|
|
333
|
-
accountNames.forEach(name => {
|
|
334
|
-
const account = accounts[name];
|
|
335
|
-
const isActive = currentProject && currentProject.name === name;
|
|
336
|
-
const marker = isActive ? chalk.green('ā ') : ' ';
|
|
337
|
-
const nameDisplay = isActive ? chalk.green.bold(name) : chalk.cyan(name);
|
|
338
|
-
|
|
339
|
-
console.log(`${marker}${nameDisplay}`);
|
|
340
|
-
console.log(` Type: ${account.type}`);
|
|
341
|
-
console.log(` API Key: ${maskApiKey(account.apiKey)}`);
|
|
342
|
-
if (account.email) console.log(` Email: ${account.email}`);
|
|
343
|
-
if (account.description) console.log(` Description: ${account.description}`);
|
|
344
|
-
if (account.customEnv && Object.keys(account.customEnv).length > 0) {
|
|
345
|
-
console.log(` Custom Env: ${Object.keys(account.customEnv).join(', ')}`);
|
|
346
|
-
}
|
|
347
|
-
// Display model groups
|
|
348
|
-
if (account.modelGroups && Object.keys(account.modelGroups).length > 0) {
|
|
349
|
-
const groupNames = Object.keys(account.modelGroups);
|
|
350
|
-
const activeMarker = account.activeModelGroup ? ` (active: ${account.activeModelGroup})` : '';
|
|
351
|
-
console.log(` Model Groups: ${groupNames.join(', ')}${activeMarker}`);
|
|
352
|
-
}
|
|
353
|
-
console.log(` Created: ${new Date(account.createdAt).toLocaleString()}`);
|
|
354
|
-
console.log('');
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
if (currentProject) {
|
|
358
|
-
console.log(chalk.green(`ā Current project is using: ${currentProject.name}\n`));
|
|
359
|
-
} else {
|
|
360
|
-
console.log(chalk.yellow('ā No account set for current project. Use "ais use <account>" to set one.\n'));
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* Switch to a specific account for current project
|
|
366
|
-
*/
|
|
367
|
-
async function useAccount(name) {
|
|
368
|
-
if (!name) {
|
|
369
|
-
// If no name provided, show interactive selection
|
|
370
|
-
const accounts = config.getAllAccounts();
|
|
371
|
-
const accountNames = Object.keys(accounts);
|
|
372
|
-
|
|
373
|
-
if (accountNames.length === 0) {
|
|
374
|
-
console.log(chalk.yellow('No accounts found. Use "ais add" to add an account first.'));
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
const answers = await inquirer.prompt([
|
|
379
|
-
{
|
|
380
|
-
type: 'list',
|
|
381
|
-
name: 'accountName',
|
|
382
|
-
message: 'Select an account to use:',
|
|
383
|
-
choices: accountNames
|
|
384
|
-
}
|
|
385
|
-
]);
|
|
386
|
-
|
|
387
|
-
name = answers.accountName;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
if (!config.accountExists(name)) {
|
|
391
|
-
console.log(chalk.red(`ā Account '${name}' not found.`));
|
|
392
|
-
console.log(chalk.yellow('Use "ais list" to see available accounts.'));
|
|
393
|
-
return;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const success = config.setProjectAccount(name);
|
|
397
|
-
if (success) {
|
|
398
|
-
const fs = require('fs');
|
|
399
|
-
const path = require('path');
|
|
400
|
-
const account = config.getAccount(name);
|
|
401
|
-
|
|
402
|
-
console.log(chalk.green(`ā Switched to account '${name}' for current project.`));
|
|
403
|
-
console.log(chalk.yellow(`Project: ${process.cwd()}`));
|
|
404
|
-
|
|
405
|
-
// Show different messages based on account type
|
|
406
|
-
if (account && account.type === 'Codex') {
|
|
407
|
-
const profileFile = path.join(process.cwd(), '.codex-profile');
|
|
408
|
-
if (fs.existsSync(profileFile)) {
|
|
409
|
-
const profileName = fs.readFileSync(profileFile, 'utf8').trim();
|
|
410
|
-
console.log(chalk.cyan(`ā Codex profile created: ${profileName}`));
|
|
411
|
-
console.log(chalk.yellow(` Use: codex --profile ${profileName} [prompt]`));
|
|
412
|
-
}
|
|
413
|
-
} else {
|
|
414
|
-
console.log(chalk.cyan(`ā Claude configuration generated at: .claude/settings.local.json`));
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
// Check if .gitignore was updated
|
|
418
|
-
const gitignorePath = path.join(process.cwd(), '.gitignore');
|
|
419
|
-
const gitDir = path.join(process.cwd(), '.git');
|
|
420
|
-
if (fs.existsSync(gitDir) && fs.existsSync(gitignorePath)) {
|
|
421
|
-
console.log(chalk.cyan(`ā Updated .gitignore to exclude AIS configuration files`));
|
|
422
|
-
}
|
|
423
|
-
} else {
|
|
424
|
-
console.log(chalk.red('ā Failed to set account.'));
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Show current project's account info
|
|
430
|
-
*/
|
|
431
|
-
function showInfo() {
|
|
432
|
-
const projectAccount = config.getProjectAccount();
|
|
433
|
-
|
|
434
|
-
if (!projectAccount) {
|
|
435
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
436
|
-
console.log(chalk.yellow(`Project: ${process.cwd()}`));
|
|
437
|
-
console.log(chalk.cyan('\nUse "ais use <account>" to set an account for this project.'));
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
console.log(chalk.bold('\nš Current Project Account Info:\n'));
|
|
442
|
-
console.log(`${chalk.cyan('Account Name:')} ${chalk.green.bold(projectAccount.name)}`);
|
|
443
|
-
console.log(`${chalk.cyan('Type:')} ${projectAccount.type}`);
|
|
444
|
-
console.log(`${chalk.cyan('API Key:')} ${maskApiKey(projectAccount.apiKey)}`);
|
|
445
|
-
if (projectAccount.apiUrl) console.log(`${chalk.cyan('API URL:')} ${projectAccount.apiUrl}`);
|
|
446
|
-
if (projectAccount.email) console.log(`${chalk.cyan('Email:')} ${projectAccount.email}`);
|
|
447
|
-
if (projectAccount.description) console.log(`${chalk.cyan('Description:')} ${projectAccount.description}`);
|
|
448
|
-
if (projectAccount.customEnv && Object.keys(projectAccount.customEnv).length > 0) {
|
|
449
|
-
console.log(`${chalk.cyan('Custom Environment Variables:')}`);
|
|
450
|
-
Object.entries(projectAccount.customEnv).forEach(([key, value]) => {
|
|
451
|
-
console.log(` ${chalk.gray('ā¢')} ${key}: ${value}`);
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
// Display model groups
|
|
455
|
-
if (projectAccount.modelGroups && Object.keys(projectAccount.modelGroups).length > 0) {
|
|
456
|
-
console.log(`${chalk.cyan('Model Groups:')}`);
|
|
457
|
-
Object.keys(projectAccount.modelGroups).forEach(groupName => {
|
|
458
|
-
const isActive = projectAccount.activeModelGroup === groupName;
|
|
459
|
-
const marker = isActive ? chalk.green('ā ') : ' ';
|
|
460
|
-
console.log(`${marker}${isActive ? chalk.green.bold(groupName) : groupName}`);
|
|
461
|
-
});
|
|
462
|
-
if (projectAccount.activeModelGroup) {
|
|
463
|
-
console.log(`${chalk.cyan('Active Model Group:')} ${chalk.green.bold(projectAccount.activeModelGroup)}`);
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
console.log(`${chalk.cyan('Set At:')} ${new Date(projectAccount.setAt).toLocaleString()}`);
|
|
467
|
-
console.log(`${chalk.cyan('Project Root:')} ${projectAccount.projectRoot}`);
|
|
468
|
-
console.log(`${chalk.cyan('Current Directory:')} ${process.cwd()}\n`);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
/**
|
|
472
|
-
* Remove an account
|
|
473
|
-
*/
|
|
474
|
-
async function removeAccount(name) {
|
|
475
|
-
if (!name) {
|
|
476
|
-
const accounts = config.getAllAccounts();
|
|
477
|
-
const accountNames = Object.keys(accounts);
|
|
478
|
-
|
|
479
|
-
if (accountNames.length === 0) {
|
|
480
|
-
console.log(chalk.yellow('No accounts found.'));
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const answers = await inquirer.prompt([
|
|
485
|
-
{
|
|
486
|
-
type: 'list',
|
|
487
|
-
name: 'accountName',
|
|
488
|
-
message: 'Select an account to remove:',
|
|
489
|
-
choices: accountNames
|
|
490
|
-
}
|
|
491
|
-
]);
|
|
492
|
-
|
|
493
|
-
name = answers.accountName;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
if (!config.accountExists(name)) {
|
|
497
|
-
console.log(chalk.red(`ā Account '${name}' not found.`));
|
|
498
|
-
return;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const { confirm } = await inquirer.prompt([
|
|
502
|
-
{
|
|
503
|
-
type: 'confirm',
|
|
504
|
-
name: 'confirm',
|
|
505
|
-
message: `Are you sure you want to remove account '${name}'?`,
|
|
506
|
-
default: false
|
|
507
|
-
}
|
|
508
|
-
]);
|
|
509
|
-
|
|
510
|
-
if (!confirm) {
|
|
511
|
-
console.log(chalk.yellow('Operation cancelled.'));
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
const success = config.removeAccount(name);
|
|
516
|
-
if (success) {
|
|
517
|
-
console.log(chalk.green(`ā Account '${name}' removed successfully.`));
|
|
518
|
-
} else {
|
|
519
|
-
console.log(chalk.red('ā Failed to remove account.'));
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
/**
|
|
524
|
-
* Show current account for current project
|
|
525
|
-
*/
|
|
526
|
-
function showCurrent() {
|
|
527
|
-
const projectAccount = config.getProjectAccount();
|
|
528
|
-
|
|
529
|
-
if (!projectAccount) {
|
|
530
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
531
|
-
return;
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
console.log(chalk.green(`Current account: ${chalk.bold(projectAccount.name)}`));
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Show configuration paths
|
|
539
|
-
*/
|
|
540
|
-
function showPaths() {
|
|
541
|
-
const paths = config.getConfigPaths();
|
|
542
|
-
const projectRoot = config.findProjectRoot();
|
|
543
|
-
|
|
544
|
-
console.log(chalk.bold('\nš Configuration Paths:\n'));
|
|
545
|
-
console.log(`${chalk.cyan('Global config file:')} ${paths.global}`);
|
|
546
|
-
console.log(`${chalk.cyan('Global config directory:')} ${paths.globalDir}`);
|
|
547
|
-
console.log(`${chalk.cyan('Project config file:')} ${paths.project}`);
|
|
548
|
-
|
|
549
|
-
if (projectRoot) {
|
|
550
|
-
const claudeConfigPath = require('path').join(projectRoot, '.claude', 'settings.local.json');
|
|
551
|
-
const fs = require('fs');
|
|
552
|
-
console.log(`${chalk.cyan('Claude config file:')} ${claudeConfigPath}`);
|
|
553
|
-
console.log(`${chalk.cyan('Claude config exists:')} ${fs.existsSync(claudeConfigPath) ? chalk.green('ā Yes') : chalk.red('ā No')}`);
|
|
554
|
-
console.log(`${chalk.cyan('Project root:')} ${projectRoot}`);
|
|
555
|
-
console.log(`${chalk.cyan('Current directory:')} ${process.cwd()}`);
|
|
556
|
-
} else {
|
|
557
|
-
console.log(chalk.yellow('\nā Not in a configured project directory'));
|
|
558
|
-
}
|
|
559
|
-
console.log('');
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* Export account configuration
|
|
564
|
-
*/
|
|
565
|
-
function exportAccount(name) {
|
|
566
|
-
if (!name) {
|
|
567
|
-
console.log(chalk.red('Please specify an account name.'));
|
|
568
|
-
console.log(chalk.cyan('Usage: ais export <account-name>'));
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
const account = config.getAccount(name);
|
|
573
|
-
if (!account) {
|
|
574
|
-
console.log(chalk.red(`ā Account '${name}' not found.`));
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
console.log(chalk.bold(`\nš¤ Export for account '${name}':\n`));
|
|
579
|
-
console.log(JSON.stringify({ [name]: account }, null, 2));
|
|
580
|
-
console.log('');
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
/**
|
|
584
|
-
* Utility function to mask API key
|
|
585
|
-
*/
|
|
586
|
-
function maskApiKey(apiKey) {
|
|
587
|
-
if (!apiKey || apiKey.length < 8) return '****';
|
|
588
|
-
return apiKey.substring(0, 4) + '****' + apiKey.substring(apiKey.length - 4);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
/**
|
|
592
|
-
* Validate account API availability
|
|
593
|
-
*/
|
|
594
|
-
async function validateAccount(apiKey, apiUrl) {
|
|
595
|
-
const https = require('https');
|
|
596
|
-
const http = require('http');
|
|
597
|
-
|
|
598
|
-
return new Promise((resolve) => {
|
|
599
|
-
const url = apiUrl || 'https://api.anthropic.com';
|
|
600
|
-
const urlObj = new URL(url);
|
|
601
|
-
const client = urlObj.protocol === 'https:' ? https : http;
|
|
602
|
-
|
|
603
|
-
const options = {
|
|
604
|
-
hostname: urlObj.hostname,
|
|
605
|
-
port: urlObj.port,
|
|
606
|
-
path: '/v1/messages',
|
|
607
|
-
method: 'POST',
|
|
608
|
-
headers: {
|
|
609
|
-
'Content-Type': 'application/json',
|
|
610
|
-
'x-api-key': apiKey,
|
|
611
|
-
'anthropic-version': '2023-06-01'
|
|
612
|
-
},
|
|
613
|
-
timeout: 5000
|
|
614
|
-
};
|
|
615
|
-
|
|
616
|
-
const req = client.request(options, (res) => {
|
|
617
|
-
resolve({ valid: res.statusCode !== 401 && res.statusCode !== 403, statusCode: res.statusCode });
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
req.on('error', () => resolve({ valid: false, error: 'Network error' }));
|
|
621
|
-
req.on('timeout', () => {
|
|
622
|
-
req.destroy();
|
|
623
|
-
resolve({ valid: false, error: 'Timeout' });
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
req.write(JSON.stringify({ model: 'claude-3-haiku-20240307', max_tokens: 1, messages: [{ role: 'user', content: 'test' }] }));
|
|
627
|
-
req.end();
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
/**
|
|
632
|
-
* Diagnose Claude Code configuration issues
|
|
633
|
-
*/
|
|
634
|
-
async function doctor() {
|
|
635
|
-
const path = require('path');
|
|
636
|
-
const fs = require('fs');
|
|
637
|
-
const os = require('os');
|
|
638
|
-
|
|
639
|
-
console.log(chalk.bold.cyan('\nš Claude Code Configuration Diagnostics\n'));
|
|
640
|
-
|
|
641
|
-
// Check current directory
|
|
642
|
-
console.log(chalk.bold('1. Current Directory:'));
|
|
643
|
-
console.log(` ${process.cwd()}\n`);
|
|
644
|
-
|
|
645
|
-
// Check project root
|
|
646
|
-
const projectRoot = config.findProjectRoot();
|
|
647
|
-
console.log(chalk.bold('2. Project Root Detection:'));
|
|
648
|
-
if (projectRoot) {
|
|
649
|
-
console.log(chalk.green(` ā Found project root: ${projectRoot}`));
|
|
650
|
-
} else {
|
|
651
|
-
console.log(chalk.yellow(' ā No project root found (not in a configured project)'));
|
|
652
|
-
console.log(chalk.gray(' Run "ais use <account>" in your project root first\n'));
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Check ais project config
|
|
657
|
-
const aisConfigPath = path.join(projectRoot, '.ais-project-config');
|
|
658
|
-
console.log(chalk.bold('\n3. AIS Project Configuration:'));
|
|
659
|
-
if (fs.existsSync(aisConfigPath)) {
|
|
660
|
-
console.log(chalk.green(` ā Config exists: ${aisConfigPath}`));
|
|
661
|
-
try {
|
|
662
|
-
const aisConfig = JSON.parse(fs.readFileSync(aisConfigPath, 'utf8'));
|
|
663
|
-
console.log(` Account: ${chalk.cyan(aisConfig.activeAccount)}`);
|
|
664
|
-
} catch (e) {
|
|
665
|
-
console.log(chalk.red(` ā Error reading config: ${e.message}`));
|
|
666
|
-
}
|
|
667
|
-
} else {
|
|
668
|
-
console.log(chalk.red(` ā Config not found: ${aisConfigPath}`));
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Check Claude config
|
|
672
|
-
const claudeDir = path.join(projectRoot, '.claude');
|
|
673
|
-
const claudeConfigPath = path.join(claudeDir, 'settings.local.json');
|
|
674
|
-
|
|
675
|
-
console.log(chalk.bold('\n4. Claude Code Configuration:'));
|
|
676
|
-
console.log(` Expected location: ${claudeConfigPath}`);
|
|
677
|
-
|
|
678
|
-
if (fs.existsSync(claudeConfigPath)) {
|
|
679
|
-
console.log(chalk.green(' ā Claude config exists'));
|
|
680
|
-
try {
|
|
681
|
-
const claudeConfig = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf8'));
|
|
682
|
-
|
|
683
|
-
if (claudeConfig.env && claudeConfig.env.ANTHROPIC_AUTH_TOKEN) {
|
|
684
|
-
const token = claudeConfig.env.ANTHROPIC_AUTH_TOKEN;
|
|
685
|
-
const masked = token.substring(0, 6) + '****' + token.substring(token.length - 4);
|
|
686
|
-
console.log(` API Token: ${masked}`);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
if (claudeConfig.env && claudeConfig.env.ANTHROPIC_BASE_URL) {
|
|
690
|
-
console.log(` API URL: ${claudeConfig.env.ANTHROPIC_BASE_URL}`);
|
|
691
|
-
}
|
|
692
|
-
} catch (e) {
|
|
693
|
-
console.log(chalk.red(` ā Error reading Claude config: ${e.message}`));
|
|
694
|
-
}
|
|
695
|
-
} else {
|
|
696
|
-
console.log(chalk.red(' ā Claude config not found'));
|
|
697
|
-
console.log(chalk.yellow(' Run "ais use <account>" to generate it'));
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
// Check Codex profile
|
|
701
|
-
const codexProfilePath = path.join(projectRoot, '.codex-profile');
|
|
702
|
-
const globalCodexConfig = path.join(os.homedir(), '.codex', 'config.toml');
|
|
703
|
-
|
|
704
|
-
console.log(chalk.bold('\n5. Codex Configuration:'));
|
|
705
|
-
console.log(` Profile file: ${codexProfilePath}`);
|
|
706
|
-
|
|
707
|
-
if (fs.existsSync(codexProfilePath)) {
|
|
708
|
-
const profileName = fs.readFileSync(codexProfilePath, 'utf8').trim();
|
|
709
|
-
console.log(chalk.green(` ā Codex profile exists: ${profileName}`));
|
|
710
|
-
console.log(chalk.cyan(` Usage: codex --profile ${profileName} [prompt]`));
|
|
711
|
-
|
|
712
|
-
// Check if profile exists in global config
|
|
713
|
-
if (fs.existsSync(globalCodexConfig)) {
|
|
714
|
-
try {
|
|
715
|
-
const globalConfig = fs.readFileSync(globalCodexConfig, 'utf8');
|
|
716
|
-
const profilePattern = new RegExp(`\\[profiles\\.${profileName}\\]`);
|
|
717
|
-
|
|
718
|
-
if (profilePattern.test(globalConfig)) {
|
|
719
|
-
console.log(chalk.green(` ā Profile configured in ~/.codex/config.toml`));
|
|
720
|
-
|
|
721
|
-
// Parse profile info
|
|
722
|
-
const providerMatch = globalConfig.match(new RegExp(`\\[profiles\\.${profileName}\\][\\s\\S]*?model_provider\\s*=\\s*"([^"]+)"`));
|
|
723
|
-
const modelMatch = globalConfig.match(new RegExp(`\\[profiles\\.${profileName}\\][\\s\\S]*?model\\s*=\\s*"([^"]+)"`));
|
|
724
|
-
|
|
725
|
-
if (providerMatch) {
|
|
726
|
-
console.log(` Model Provider: ${providerMatch[1]}`);
|
|
727
|
-
|
|
728
|
-
// Find provider details
|
|
729
|
-
const providerName = providerMatch[1];
|
|
730
|
-
const baseUrlMatch = globalConfig.match(new RegExp(`\\[model_providers\\.${providerName}\\][\\s\\S]*?base_url\\s*=\\s*"([^"]+)"`));
|
|
731
|
-
if (baseUrlMatch) {
|
|
732
|
-
console.log(` API URL: ${baseUrlMatch[1]}`);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
if (modelMatch) {
|
|
736
|
-
console.log(` Model: ${modelMatch[1]}`);
|
|
737
|
-
}
|
|
738
|
-
} else {
|
|
739
|
-
console.log(chalk.yellow(` ā Profile not found in global config`));
|
|
740
|
-
console.log(chalk.yellow(` Run "ais use <account>" to regenerate it`));
|
|
741
|
-
}
|
|
742
|
-
} catch (e) {
|
|
743
|
-
console.log(chalk.red(` ā Error reading global Codex config: ${e.message}`));
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
} else {
|
|
747
|
-
console.log(chalk.yellow(' ā No Codex profile configured'));
|
|
748
|
-
console.log(chalk.yellow(' Run "ais use <codex-account>" to create one'));
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
// Check global Claude config
|
|
752
|
-
const globalClaudeConfig = path.join(os.homedir(), '.claude', 'settings.json');
|
|
753
|
-
console.log(chalk.bold('\n6. Global Claude Configuration:'));
|
|
754
|
-
console.log(` Location: ${globalClaudeConfig}`);
|
|
755
|
-
|
|
756
|
-
if (fs.existsSync(globalClaudeConfig)) {
|
|
757
|
-
console.log(chalk.yellow(' ā Global config exists (may override project config in some cases)'));
|
|
758
|
-
try {
|
|
759
|
-
const globalConfig = JSON.parse(fs.readFileSync(globalClaudeConfig, 'utf8'));
|
|
760
|
-
if (globalConfig.env && globalConfig.env.ANTHROPIC_AUTH_TOKEN) {
|
|
761
|
-
const token = globalConfig.env.ANTHROPIC_AUTH_TOKEN;
|
|
762
|
-
const masked = token.substring(0, 6) + '****' + token.substring(token.length - 4);
|
|
763
|
-
console.log(` Global API Token: ${masked}`);
|
|
764
|
-
}
|
|
765
|
-
if (globalConfig.env && globalConfig.env.ANTHROPIC_BASE_URL) {
|
|
766
|
-
console.log(` Global API URL: ${globalConfig.env.ANTHROPIC_BASE_URL}`);
|
|
767
|
-
}
|
|
768
|
-
} catch (e) {
|
|
769
|
-
console.log(chalk.red(` ā Error reading global config: ${e.message}`));
|
|
770
|
-
}
|
|
771
|
-
} else {
|
|
772
|
-
console.log(chalk.green(' ā No global config (good - project config will be used)'));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Check current account availability
|
|
776
|
-
console.log(chalk.bold('\n7. Current Account Availability:'));
|
|
777
|
-
const projectAccount = config.getProjectAccount();
|
|
778
|
-
|
|
779
|
-
if (projectAccount && projectAccount.apiKey) {
|
|
780
|
-
console.log(` Testing account: ${chalk.cyan(projectAccount.name)}`);
|
|
781
|
-
console.log(` Account type: ${chalk.cyan(projectAccount.type)}`);
|
|
782
|
-
|
|
783
|
-
if (projectAccount.type === 'Claude') {
|
|
784
|
-
console.log(' Testing with Claude CLI...');
|
|
785
|
-
const { execSync } = require('child_process');
|
|
786
|
-
try {
|
|
787
|
-
execSync('claude --version', { stdio: 'pipe', timeout: 5000 });
|
|
788
|
-
console.log(chalk.green(' ā Claude CLI is available'));
|
|
789
|
-
|
|
790
|
-
// Interactive CLI test
|
|
791
|
-
console.log(' Running interactive test...');
|
|
792
|
-
try {
|
|
793
|
-
const testResult = execSync('echo "test" | claude', {
|
|
794
|
-
encoding: 'utf8',
|
|
795
|
-
timeout: 10000,
|
|
796
|
-
env: { ...process.env, ANTHROPIC_API_KEY: projectAccount.apiKey }
|
|
797
|
-
});
|
|
798
|
-
console.log(chalk.green(' ā Claude CLI interactive test passed'));
|
|
799
|
-
} catch (e) {
|
|
800
|
-
console.log(chalk.yellow(' ā Claude CLI interactive test failed'));
|
|
801
|
-
console.log(chalk.gray(` Error: ${e.message}`));
|
|
802
|
-
}
|
|
803
|
-
} catch (e) {
|
|
804
|
-
console.log(chalk.yellow(' ā Claude CLI not found, using API validation'));
|
|
805
|
-
}
|
|
806
|
-
} else if (projectAccount.type === 'Codex') {
|
|
807
|
-
console.log(' Testing with Codex CLI...');
|
|
808
|
-
const { execSync } = require('child_process');
|
|
809
|
-
try {
|
|
810
|
-
execSync('codex --version', { stdio: 'pipe', timeout: 5000 });
|
|
811
|
-
console.log(chalk.green(' ā Codex CLI is available'));
|
|
812
|
-
} catch (e) {
|
|
813
|
-
console.log(chalk.yellow(' ā Codex CLI not found'));
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
console.log(` API URL: ${projectAccount.apiUrl || 'https://api.anthropic.com'}`);
|
|
818
|
-
console.log(' Validating API key...');
|
|
819
|
-
|
|
820
|
-
const result = await validateAccount(projectAccount.apiKey, projectAccount.apiUrl);
|
|
821
|
-
|
|
822
|
-
if (result.valid) {
|
|
823
|
-
console.log(chalk.green(' ā Account is valid and accessible'));
|
|
824
|
-
if (result.statusCode) {
|
|
825
|
-
console.log(chalk.gray(` Response status: ${result.statusCode}`));
|
|
826
|
-
}
|
|
827
|
-
} else {
|
|
828
|
-
console.log(chalk.red(' ā Account validation failed'));
|
|
829
|
-
if (result.error) {
|
|
830
|
-
console.log(chalk.red(` Error: ${result.error}`));
|
|
831
|
-
} else if (result.statusCode) {
|
|
832
|
-
console.log(chalk.red(` Status code: ${result.statusCode}`));
|
|
833
|
-
if (result.statusCode === 401 || result.statusCode === 403) {
|
|
834
|
-
console.log(chalk.yellow(' ā API key appears to be invalid or expired'));
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
} else {
|
|
839
|
-
console.log(chalk.yellow(' ā No account configured or API key missing'));
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
// Recommendations
|
|
843
|
-
console.log(chalk.bold('\n8. Recommendations:'));
|
|
844
|
-
|
|
845
|
-
if (projectRoot && process.cwd() !== projectRoot) {
|
|
846
|
-
console.log(chalk.yellow(` ā You are in a subdirectory (${path.relative(projectRoot, process.cwd())})`));
|
|
847
|
-
console.log(chalk.cyan(' ⢠Claude Code should still find the project config'));
|
|
848
|
-
console.log(chalk.cyan(' ⢠Make sure to start Claude Code from this directory or parent directories'));
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
if (fs.existsSync(globalClaudeConfig)) {
|
|
852
|
-
console.log(chalk.yellow(' ā Global Claude config exists:'));
|
|
853
|
-
console.log(chalk.cyan(' ⢠Project config should take precedence'));
|
|
854
|
-
console.log(chalk.cyan(' ⢠If issues persist, consider removing global env settings'));
|
|
855
|
-
console.log(chalk.gray(` ⢠File: ${globalClaudeConfig}`));
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
console.log(chalk.bold('\n9. Next Steps:'));
|
|
859
|
-
console.log(chalk.cyan(' ⢠Start Claude Code from your project directory or subdirectory'));
|
|
860
|
-
console.log(chalk.cyan(' ⢠Check which account Claude Code is using'));
|
|
861
|
-
console.log(chalk.cyan(' ⢠If wrong account is used, run: ais use <correct-account>'));
|
|
862
|
-
console.log('');
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/**
|
|
866
|
-
* Start Web UI server
|
|
867
|
-
*/
|
|
868
|
-
function startUI() {
|
|
869
|
-
const UIServer = require('./ui-server');
|
|
870
|
-
const server = new UIServer();
|
|
871
|
-
|
|
872
|
-
console.log(chalk.cyan('\nš Starting AIS Web UI...\n'));
|
|
873
|
-
server.start();
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
/**
|
|
877
|
-
* List all model groups for current account
|
|
878
|
-
*/
|
|
879
|
-
function listModelGroups() {
|
|
880
|
-
const projectAccount = config.getProjectAccount();
|
|
881
|
-
|
|
882
|
-
if (!projectAccount) {
|
|
883
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
884
|
-
console.log(chalk.cyan('Use "ais use <account>" to set an account first.\n'));
|
|
885
|
-
return;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
if (!projectAccount.modelGroups || Object.keys(projectAccount.modelGroups).length === 0) {
|
|
889
|
-
console.log(chalk.yellow(`ā No model groups configured for account '${projectAccount.name}'.`));
|
|
890
|
-
console.log(chalk.cyan('Use "ais model add" to create a model group.\n'));
|
|
891
|
-
return;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
console.log(chalk.bold(`\nš Model Groups for '${projectAccount.name}':\n`));
|
|
895
|
-
|
|
896
|
-
Object.entries(projectAccount.modelGroups).forEach(([groupName, groupConfig]) => {
|
|
897
|
-
const isActive = projectAccount.activeModelGroup === groupName;
|
|
898
|
-
const marker = isActive ? chalk.green('ā ') : ' ';
|
|
899
|
-
const nameDisplay = isActive ? chalk.green.bold(groupName) : chalk.cyan(groupName);
|
|
900
|
-
|
|
901
|
-
console.log(`${marker}${nameDisplay}`);
|
|
902
|
-
if (Object.keys(groupConfig).length > 0) {
|
|
903
|
-
Object.entries(groupConfig).forEach(([key, value]) => {
|
|
904
|
-
console.log(` ${chalk.cyan(key + ':')} ${value}`);
|
|
905
|
-
});
|
|
906
|
-
} else {
|
|
907
|
-
console.log(` ${chalk.gray('(empty configuration)')}`);
|
|
908
|
-
}
|
|
909
|
-
console.log('');
|
|
910
|
-
});
|
|
911
|
-
|
|
912
|
-
if (projectAccount.activeModelGroup) {
|
|
913
|
-
console.log(chalk.green(`ā Active model group: ${projectAccount.activeModelGroup}\n`));
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
/**
|
|
918
|
-
* Add a new model group
|
|
919
|
-
*/
|
|
920
|
-
async function addModelGroup(name) {
|
|
921
|
-
const projectAccount = config.getProjectAccount();
|
|
922
|
-
|
|
923
|
-
if (!projectAccount) {
|
|
924
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
925
|
-
console.log(chalk.cyan('Use "ais use <account>" to set an account first.\n'));
|
|
926
|
-
return;
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
// Prompt for group name if not provided
|
|
930
|
-
if (!name) {
|
|
931
|
-
const answers = await inquirer.prompt([
|
|
932
|
-
{
|
|
933
|
-
type: 'input',
|
|
934
|
-
name: 'groupName',
|
|
935
|
-
message: 'Enter model group name:',
|
|
936
|
-
validate: (input) => input.trim() !== '' || 'Group name is required'
|
|
937
|
-
}
|
|
938
|
-
]);
|
|
939
|
-
name = answers.groupName;
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// Check if group already exists
|
|
943
|
-
if (projectAccount.modelGroups && projectAccount.modelGroups[name]) {
|
|
944
|
-
const { overwrite } = await inquirer.prompt([
|
|
945
|
-
{
|
|
946
|
-
type: 'confirm',
|
|
947
|
-
name: 'overwrite',
|
|
948
|
-
message: `Model group '${name}' already exists. Overwrite?`,
|
|
949
|
-
default: false
|
|
950
|
-
}
|
|
951
|
-
]);
|
|
952
|
-
|
|
953
|
-
if (!overwrite) {
|
|
954
|
-
console.log(chalk.yellow('Operation cancelled.'));
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
|
|
959
|
-
// Prompt for model configuration
|
|
960
|
-
const modelGroupConfig = await promptForModelGroup();
|
|
961
|
-
|
|
962
|
-
if (Object.keys(modelGroupConfig).length === 0) {
|
|
963
|
-
console.log(chalk.yellow('ā No configuration provided. Model group not created.'));
|
|
964
|
-
return;
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
// Add the model group to the account
|
|
968
|
-
const account = config.getAccount(projectAccount.name);
|
|
969
|
-
if (!account.modelGroups) {
|
|
970
|
-
account.modelGroups = {};
|
|
971
|
-
}
|
|
972
|
-
account.modelGroups[name] = modelGroupConfig;
|
|
973
|
-
|
|
974
|
-
// Set as active if it's the first group
|
|
975
|
-
if (!account.activeModelGroup) {
|
|
976
|
-
account.activeModelGroup = name;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
config.addAccount(projectAccount.name, account);
|
|
980
|
-
console.log(chalk.green(`ā Model group '${name}' added successfully!`));
|
|
981
|
-
|
|
982
|
-
// Ask if user wants to activate this group
|
|
983
|
-
if (account.activeModelGroup !== name) {
|
|
984
|
-
const { activate } = await inquirer.prompt([
|
|
985
|
-
{
|
|
986
|
-
type: 'confirm',
|
|
987
|
-
name: 'activate',
|
|
988
|
-
message: `Set '${name}' as active model group?`,
|
|
989
|
-
default: false
|
|
990
|
-
}
|
|
991
|
-
]);
|
|
992
|
-
|
|
993
|
-
if (activate) {
|
|
994
|
-
account.activeModelGroup = name;
|
|
995
|
-
config.addAccount(projectAccount.name, account);
|
|
996
|
-
|
|
997
|
-
// Regenerate Claude config with new active group
|
|
998
|
-
config.setProjectAccount(projectAccount.name);
|
|
999
|
-
console.log(chalk.green(`ā Switched to model group '${name}' and updated Claude configuration.`));
|
|
1000
|
-
}
|
|
1001
|
-
} else {
|
|
1002
|
-
// Regenerate Claude config
|
|
1003
|
-
config.setProjectAccount(projectAccount.name);
|
|
1004
|
-
console.log(chalk.green(`ā Updated Claude configuration with active group '${name}'.`));
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
|
|
1008
|
-
/**
|
|
1009
|
-
* Switch to a different model group
|
|
1010
|
-
*/
|
|
1011
|
-
async function useModelGroup(name) {
|
|
1012
|
-
const projectAccount = config.getProjectAccount();
|
|
1013
|
-
|
|
1014
|
-
if (!projectAccount) {
|
|
1015
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
1016
|
-
console.log(chalk.cyan('Use "ais use <account>" to set an account first.\n'));
|
|
1017
|
-
return;
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
if (!projectAccount.modelGroups || Object.keys(projectAccount.modelGroups).length === 0) {
|
|
1021
|
-
console.log(chalk.yellow(`ā No model groups configured for account '${projectAccount.name}'.`));
|
|
1022
|
-
console.log(chalk.cyan('Use "ais model add" to create a model group first.\n'));
|
|
1023
|
-
return;
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
if (!projectAccount.modelGroups[name]) {
|
|
1027
|
-
console.log(chalk.red(`ā Model group '${name}' not found.`));
|
|
1028
|
-
console.log(chalk.yellow('Available groups:'), Object.keys(projectAccount.modelGroups).join(', '));
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// Update active model group
|
|
1033
|
-
const account = config.getAccount(projectAccount.name);
|
|
1034
|
-
account.activeModelGroup = name;
|
|
1035
|
-
config.addAccount(projectAccount.name, account);
|
|
1036
|
-
|
|
1037
|
-
// Regenerate Claude config with new active group
|
|
1038
|
-
config.setProjectAccount(projectAccount.name);
|
|
1039
|
-
|
|
1040
|
-
console.log(chalk.green(`ā Switched to model group '${name}'.`));
|
|
1041
|
-
console.log(chalk.cyan('ā Claude configuration updated.\n'));
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
/**
|
|
1045
|
-
* Remove a model group
|
|
1046
|
-
*/
|
|
1047
|
-
async function removeModelGroup(name) {
|
|
1048
|
-
const projectAccount = config.getProjectAccount();
|
|
1049
|
-
|
|
1050
|
-
if (!projectAccount) {
|
|
1051
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
1052
|
-
console.log(chalk.cyan('Use "ais use <account>" to set an account first.\n'));
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
if (!projectAccount.modelGroups || Object.keys(projectAccount.modelGroups).length === 0) {
|
|
1057
|
-
console.log(chalk.yellow(`ā No model groups configured for account '${projectAccount.name}'.`));
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
// Prompt for group name if not provided
|
|
1062
|
-
if (!name) {
|
|
1063
|
-
const groupNames = Object.keys(projectAccount.modelGroups);
|
|
1064
|
-
const answers = await inquirer.prompt([
|
|
1065
|
-
{
|
|
1066
|
-
type: 'list',
|
|
1067
|
-
name: 'groupName',
|
|
1068
|
-
message: 'Select a model group to remove:',
|
|
1069
|
-
choices: groupNames
|
|
1070
|
-
}
|
|
1071
|
-
]);
|
|
1072
|
-
name = answers.groupName;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
if (!projectAccount.modelGroups[name]) {
|
|
1076
|
-
console.log(chalk.red(`ā Model group '${name}' not found.`));
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
const { confirm } = await inquirer.prompt([
|
|
1081
|
-
{
|
|
1082
|
-
type: 'confirm',
|
|
1083
|
-
name: 'confirm',
|
|
1084
|
-
message: `Are you sure you want to remove model group '${name}'?`,
|
|
1085
|
-
default: false
|
|
1086
|
-
}
|
|
1087
|
-
]);
|
|
1088
|
-
|
|
1089
|
-
if (!confirm) {
|
|
1090
|
-
console.log(chalk.yellow('Operation cancelled.'));
|
|
1091
|
-
return;
|
|
1092
|
-
}
|
|
1093
|
-
|
|
1094
|
-
// Remove the model group
|
|
1095
|
-
const account = config.getAccount(projectAccount.name);
|
|
1096
|
-
delete account.modelGroups[name];
|
|
1097
|
-
|
|
1098
|
-
// Update active group if needed
|
|
1099
|
-
if (account.activeModelGroup === name) {
|
|
1100
|
-
const remainingGroups = Object.keys(account.modelGroups);
|
|
1101
|
-
account.activeModelGroup = remainingGroups.length > 0 ? remainingGroups[0] : null;
|
|
1102
|
-
|
|
1103
|
-
if (account.activeModelGroup) {
|
|
1104
|
-
console.log(chalk.cyan(`ā Switched active group to '${account.activeModelGroup}'.`));
|
|
1105
|
-
} else {
|
|
1106
|
-
console.log(chalk.yellow('ā No model groups remaining.'));
|
|
1107
|
-
}
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
config.addAccount(projectAccount.name, account);
|
|
1111
|
-
|
|
1112
|
-
// Regenerate Claude config
|
|
1113
|
-
config.setProjectAccount(projectAccount.name);
|
|
1114
|
-
|
|
1115
|
-
console.log(chalk.green(`ā Model group '${name}' removed successfully.`));
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
/**
|
|
1119
|
-
* Show model group configuration
|
|
1120
|
-
*/
|
|
1121
|
-
function showModelGroup(name) {
|
|
1122
|
-
const projectAccount = config.getProjectAccount();
|
|
1123
|
-
|
|
1124
|
-
if (!projectAccount) {
|
|
1125
|
-
console.log(chalk.yellow('ā No account set for current project.'));
|
|
1126
|
-
console.log(chalk.cyan('Use "ais use <account>" to set an account first.\n'));
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
if (!projectAccount.modelGroups || Object.keys(projectAccount.modelGroups).length === 0) {
|
|
1131
|
-
console.log(chalk.yellow(`ā No model groups configured for account '${projectAccount.name}'.`));
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// Show active group if no name provided
|
|
1136
|
-
if (!name) {
|
|
1137
|
-
if (!projectAccount.activeModelGroup) {
|
|
1138
|
-
console.log(chalk.yellow('ā No active model group.'));
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
name = projectAccount.activeModelGroup;
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
if (!projectAccount.modelGroups[name]) {
|
|
1145
|
-
console.log(chalk.red(`ā Model group '${name}' not found.`));
|
|
1146
|
-
console.log(chalk.yellow('Available groups:'), Object.keys(projectAccount.modelGroups).join(', '));
|
|
1147
|
-
return;
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
const isActive = projectAccount.activeModelGroup === name;
|
|
1151
|
-
const activeMarker = isActive ? chalk.green(' (active)') : '';
|
|
1152
|
-
|
|
1153
|
-
console.log(chalk.bold(`\nš Model Group: ${chalk.cyan(name)}${activeMarker}\n`));
|
|
1154
|
-
|
|
1155
|
-
const groupConfig = projectAccount.modelGroups[name];
|
|
1156
|
-
if (Object.keys(groupConfig).length === 0) {
|
|
1157
|
-
console.log(chalk.gray(' (empty configuration)\n'));
|
|
1158
|
-
} else {
|
|
1159
|
-
Object.entries(groupConfig).forEach(([key, value]) => {
|
|
1160
|
-
console.log(` ${chalk.cyan(key)}: ${value}`);
|
|
1161
|
-
});
|
|
1162
|
-
console.log('');
|
|
1163
|
-
}
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
module.exports = {
|
|
1167
|
-
addAccount,
|
|
1168
|
-
listAccounts,
|
|
1169
|
-
useAccount,
|
|
1170
|
-
showInfo,
|
|
1171
|
-
removeAccount,
|
|
1172
|
-
showCurrent,
|
|
1173
|
-
showPaths,
|
|
1174
|
-
exportAccount,
|
|
1175
|
-
doctor,
|
|
1176
|
-
startUI,
|
|
1177
|
-
listModelGroups,
|
|
1178
|
-
addModelGroup,
|
|
1179
|
-
useModelGroup,
|
|
1180
|
-
removeModelGroup,
|
|
1181
|
-
showModelGroup
|
|
1182
|
-
};
|