ccman 1.0.1 → 2.0.1

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.
Files changed (91) hide show
  1. package/.env.development +3 -0
  2. package/.env.production +3 -0
  3. package/.github/workflows/release.yml +5 -5
  4. package/CLAUDE.md +246 -185
  5. package/README.md +282 -249
  6. package/README_zh.md +283 -250
  7. package/dev-test.sh +40 -0
  8. package/dist/cli.js +425 -369
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/lang.d.ts +3 -0
  11. package/dist/commands/lang.d.ts.map +1 -0
  12. package/dist/commands/lang.js +99 -0
  13. package/dist/commands/lang.js.map +1 -0
  14. package/dist/config/static-env.d.ts +14 -0
  15. package/dist/config/static-env.d.ts.map +1 -0
  16. package/dist/config/static-env.js +17 -0
  17. package/dist/config/static-env.js.map +1 -0
  18. package/dist/core/CCMConfigManager.d.ts +52 -0
  19. package/dist/core/CCMConfigManager.d.ts.map +1 -0
  20. package/dist/core/CCMConfigManager.js +203 -0
  21. package/dist/core/CCMConfigManager.js.map +1 -0
  22. package/dist/core/ClaudeConfigManager.d.ts +35 -0
  23. package/dist/core/ClaudeConfigManager.d.ts.map +1 -0
  24. package/dist/core/ClaudeConfigManager.js +149 -0
  25. package/dist/core/ClaudeConfigManager.js.map +1 -0
  26. package/dist/i18n/LanguageManager.d.ts +43 -0
  27. package/dist/i18n/LanguageManager.d.ts.map +1 -0
  28. package/dist/i18n/LanguageManager.js +157 -0
  29. package/dist/i18n/LanguageManager.js.map +1 -0
  30. package/dist/i18n/messages.d.ts +65 -0
  31. package/dist/i18n/messages.d.ts.map +1 -0
  32. package/dist/i18n/messages.js +144 -0
  33. package/dist/i18n/messages.js.map +1 -0
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +3 -8
  37. package/dist/index.js.map +1 -1
  38. package/dist/providers/ProviderManager.d.ts +58 -0
  39. package/dist/providers/ProviderManager.d.ts.map +1 -0
  40. package/dist/providers/ProviderManager.js +335 -0
  41. package/dist/providers/ProviderManager.js.map +1 -0
  42. package/dist/types/index.d.ts +78 -38
  43. package/dist/types/index.d.ts.map +1 -1
  44. package/dist/types/index.js +1 -0
  45. package/dist/types/index.js.map +1 -1
  46. package/dist/utils/env-config.d.ts +27 -0
  47. package/dist/utils/env-config.d.ts.map +1 -0
  48. package/dist/{config/constants.js → utils/env-config.js} +36 -50
  49. package/dist/utils/env-config.js.map +1 -0
  50. package/dist/utils/version.d.ts +2 -64
  51. package/dist/utils/version.d.ts.map +1 -1
  52. package/dist/utils/version.js +12 -158
  53. package/dist/utils/version.js.map +1 -1
  54. package/package.json +17 -16
  55. package/release-temp/README.md +282 -249
  56. package/release-temp/package.json +17 -16
  57. package/scripts/build-env.js +75 -0
  58. package/scripts/modules/create-tag.sh +53 -10
  59. package/scripts/modules/monitor-release.sh +40 -12
  60. package/scripts/modules/version-bump.sh +14 -17
  61. package/scripts/smart-release-v3.sh +20 -26
  62. package/src/cli.ts +462 -394
  63. package/src/commands/lang.ts +105 -0
  64. package/src/core/CCMConfigManager.ts +185 -0
  65. package/src/core/ClaudeConfigManager.ts +125 -0
  66. package/src/i18n/LanguageManager.ts +169 -0
  67. package/src/i18n/messages.ts +233 -0
  68. package/src/index.ts +4 -5
  69. package/src/providers/ProviderManager.ts +393 -0
  70. package/src/types/index.ts +80 -39
  71. package/src/utils/env-config.ts +53 -0
  72. package/src/utils/version.ts +11 -184
  73. package/dist/config/ConfigManager.d.ts +0 -67
  74. package/dist/config/ConfigManager.d.ts.map +0 -1
  75. package/dist/config/ConfigManager.js +0 -226
  76. package/dist/config/ConfigManager.js.map +0 -1
  77. package/dist/config/EnvironmentManager.d.ts +0 -83
  78. package/dist/config/EnvironmentManager.d.ts.map +0 -1
  79. package/dist/config/EnvironmentManager.js +0 -280
  80. package/dist/config/EnvironmentManager.js.map +0 -1
  81. package/dist/config/constants.d.ts +0 -40
  82. package/dist/config/constants.d.ts.map +0 -1
  83. package/dist/config/constants.js.map +0 -1
  84. package/dist/shell/ShellManager.d.ts +0 -81
  85. package/dist/shell/ShellManager.d.ts.map +0 -1
  86. package/dist/shell/ShellManager.js +0 -490
  87. package/dist/shell/ShellManager.js.map +0 -1
  88. package/src/config/ConfigManager.ts +0 -227
  89. package/src/config/EnvironmentManager.ts +0 -327
  90. package/src/config/constants.ts +0 -64
  91. package/src/shell/ShellManager.ts +0 -526
package/src/cli.ts CHANGED
@@ -3,120 +3,415 @@
3
3
  import { Command } from 'commander';
4
4
  import chalk from 'chalk';
5
5
  import inquirer from 'inquirer';
6
- import { EnvironmentManager } from './config/EnvironmentManager';
7
- import { AddEnvOptions } from './types';
8
- import { getCurrentVersion } from './utils/version';
6
+ import { ProviderManager } from './providers/ProviderManager';
7
+ import { AddProviderOptions } from './types';
8
+ import { LanguageManager } from './i18n/LanguageManager';
9
+ import { createLanguageCommands } from './commands/lang';
10
+ import { getPackageVersion } from './utils/version';
9
11
 
10
12
  const program = new Command();
11
- const envManager = new EnvironmentManager();
12
-
13
- /**
14
- * 统一的 use 环境交互逻辑
15
- */
16
- async function performUseEnvironment(name: string, options?: {
17
- autoWrite?: boolean;
18
- autoSource?: boolean;
19
- skipSuccessMessage?: boolean;
20
- }): Promise<void> {
21
- const result = await envManager.useEnvironment(name, {
22
- autoWriteShell: options?.autoWrite,
23
- autoSource: options?.autoSource
24
- });
13
+ const providerManager = new ProviderManager();
14
+ const languageManager = new LanguageManager();
15
+
16
+ // 询问是否继续操作
17
+ async function askToContinue(): Promise<boolean> {
18
+ const messages = await languageManager.getMessages();
19
+ const answer = await inquirer.prompt([
20
+ {
21
+ type: 'confirm',
22
+ name: 'continue',
23
+ message: messages.forms.continueOperation,
24
+ default: true
25
+ }
26
+ ]);
27
+ return answer.continue;
28
+ }
29
+
30
+ // 交互式配置菜单
31
+ async function showInteractiveMenu(): Promise<void> {
32
+ // 处理首次运行语言设置
33
+ await languageManager.handleFirstRun();
25
34
 
26
- if (!options?.skipSuccessMessage) {
27
- console.log(chalk.green(`✓ Switched to environment "${name}"`));
28
- console.log(` Base URL: ${result.env.baseUrl}`);
29
- }
35
+ // 获取当前语言的消息
36
+ const messages = await languageManager.getMessages();
30
37
 
31
- if (result.shellWriteResult?.success) {
32
- console.log(chalk.green(`✓ Environment variables written to ${result.shellWriteResult.filePath}`));
33
-
34
- if (options?.autoSource) {
35
- if (result.sourceResult?.success) {
36
- console.log(chalk.green('✓ Shell configuration sourced automatically'));
37
- console.log(chalk.yellow('⚠️ Note: Auto-sourcing may not work in all terminal environments'));
38
- } else {
39
- console.log(chalk.red(`✗ Failed to source shell config: ${result.sourceResult?.error}`));
40
- console.log(chalk.cyan('Please run manually: source ~/.bashrc (or ~/.zshrc)'));
38
+ // 设置 Ctrl+C 优雅退出处理
39
+ process.removeAllListeners('SIGINT');
40
+ process.on('SIGINT', () => {
41
+ console.log(chalk.yellow(messages.interruptMessage));
42
+ process.exit(0);
43
+ });
44
+
45
+ try {
46
+ let shouldContinue = true;
47
+
48
+ while (shouldContinue) {
49
+ await providerManager.init();
50
+ const providers = await providerManager.listProviders();
51
+
52
+ if (providers.length === 0) {
53
+ console.log(chalk.yellow(messages.noProvidersFound));
54
+
55
+ const answers = await inquirer.prompt([
56
+ {
57
+ type: 'input',
58
+ name: 'id',
59
+ message: messages.forms.providerId,
60
+ default: 'anthropic'
61
+ },
62
+ {
63
+ type: 'input',
64
+ name: 'name',
65
+ message: messages.forms.providerName,
66
+ default: 'Anthropic Official'
67
+ },
68
+ {
69
+ type: 'input',
70
+ name: 'description',
71
+ message: messages.forms.description,
72
+ default: 'Official Anthropic API'
73
+ },
74
+ {
75
+ type: 'input',
76
+ name: 'baseUrl',
77
+ message: messages.forms.baseUrl,
78
+ default: 'https://api.anthropic.com'
79
+ },
80
+ {
81
+ type: 'password',
82
+ name: 'apiKey',
83
+ message: messages.forms.apiKey,
84
+ mask: '*'
85
+ }
86
+ ]);
87
+
88
+ const result = await providerManager.addProvider({
89
+ id: answers.id,
90
+ name: answers.name,
91
+ description: answers.description,
92
+ baseUrl: answers.baseUrl,
93
+ apiKey: answers.apiKey
94
+ });
95
+
96
+ if (result.success) {
97
+ console.log(chalk.green(`✓ ${result.message}`));
98
+ // 自动设为当前供应商
99
+ const useResult = await providerManager.useProvider(answers.id);
100
+ if (useResult.success) {
101
+ console.log(chalk.green(`✓ ${useResult.message}`));
102
+ }
103
+ } else {
104
+ console.error(chalk.red(`✗ ${result.message}`));
105
+ }
106
+
107
+ console.log();
108
+ shouldContinue = await askToContinue();
109
+ continue;
41
110
  }
42
- } else {
43
- // 询问用户是否要自动 source
44
- const sourceAnswer = await inquirer.prompt([
111
+
112
+ const choices = providers.map(provider => ({
113
+ name: `${provider.name} (${provider.id}) - ${provider.baseUrl}${provider.isCurrent ? ' [current]' : ''}`,
114
+ value: provider.id
115
+ }));
116
+
117
+ const action = await inquirer.prompt([
45
118
  {
46
119
  type: 'list',
47
- name: 'sourceChoice',
48
- message: 'How would you like to apply the environment variables?',
120
+ name: 'action',
121
+ message: messages.mainMenuTitle,
49
122
  choices: [
50
- { name: 'Manual - I will restart terminal or source manually (Recommended)', value: 'manual' },
51
- { name: 'Auto-source - Try to source automatically (May not work in all environments)', value: 'auto' }
52
- ],
53
- default: 'manual'
123
+ { name: messages.mainMenuOptions.switchProvider, value: 'switch' },
124
+ { name: messages.mainMenuOptions.addProvider, value: 'add' },
125
+ { name: messages.mainMenuOptions.updateProvider, value: 'update' },
126
+ { name: messages.mainMenuOptions.removeProvider, value: 'remove' },
127
+ { name: messages.mainMenuOptions.showStatus, value: 'status' },
128
+ { name: messages.mainMenuOptions.exit, value: 'exit' }
129
+ ]
54
130
  }
55
131
  ]);
56
-
57
- if (sourceAnswer.sourceChoice === 'auto') {
58
- console.log(chalk.yellow('⚠️ Attempting auto-source - this may not work in all terminal environments'));
59
- const sourceResult = await envManager.getShellManager().autoSourceShell();
132
+
133
+ if (action.action === 'exit') {
134
+ console.log(chalk.cyan(messages.exitMessage));
135
+ break;
136
+ }
137
+
138
+ switch (action.action) {
139
+ case 'switch': {
140
+ const switchAnswer = await inquirer.prompt([
141
+ {
142
+ type: 'list',
143
+ name: 'id',
144
+ message: 'Select provider:',
145
+ choices
146
+ }
147
+ ]);
60
148
 
61
- if (sourceResult.success) {
62
- console.log(chalk.green('✓ Shell configuration sourced successfully'));
149
+ const switchResult = await providerManager.useProvider(switchAnswer.id);
150
+ if (switchResult.success) {
151
+ console.log(chalk.green(`✓ ${switchResult.message}`));
63
152
  } else {
64
- console.log(chalk.red(`✗ Auto-source failed: ${sourceResult.error}`));
65
- console.log(chalk.cyan('Please run manually: source ~/.bashrc (or ~/.zshrc)'));
153
+ console.error(chalk.red(`✗ ${switchResult.message}`));
66
154
  }
67
- } else {
68
- console.log(chalk.cyan('To apply changes, restart your terminal or run:'));
69
- console.log(chalk.cyan('source ~/.bashrc (or ~/.zshrc)'));
155
+
156
+ console.log();
157
+ shouldContinue = await askToContinue();
158
+ break;
159
+ }
160
+
161
+ case 'add': {
162
+ const addAnswers = await inquirer.prompt([
163
+ { type: 'input', name: 'id', message: 'Provider ID:' },
164
+ { type: 'input', name: 'name', message: 'Provider name:' },
165
+ { type: 'input', name: 'description', message: 'Description:' },
166
+ { type: 'input', name: 'baseUrl', message: 'Base URL:' },
167
+ { type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' }
168
+ ]);
169
+
170
+ const addResult = await providerManager.addProvider(addAnswers);
171
+ if (addResult.success) {
172
+ console.log(chalk.green(`✓ ${addResult.message}`));
173
+ } else {
174
+ console.error(chalk.red(`✗ ${addResult.message}`));
175
+ }
176
+
177
+ console.log();
178
+ shouldContinue = await askToContinue();
179
+ break;
180
+ }
181
+
182
+ case 'update': {
183
+ const updateIdAnswer = await inquirer.prompt([
184
+ {
185
+ type: 'list',
186
+ name: 'id',
187
+ message: 'Select provider to update:',
188
+ choices
189
+ }
190
+ ]);
191
+
192
+ const currentProvider = providers.find(p => p.id === updateIdAnswer.id);
193
+ if (currentProvider) {
194
+ const updateAnswers = await inquirer.prompt([
195
+ { type: 'input', name: 'name', message: 'Provider name:', default: currentProvider.name },
196
+ { type: 'input', name: 'description', message: 'Description:', default: currentProvider.description },
197
+ { type: 'input', name: 'baseUrl', message: 'Base URL:', default: currentProvider.baseUrl },
198
+ { type: 'password', name: 'apiKey', message: 'API Key (leave empty to keep current):', mask: '*' }
199
+ ]);
200
+
201
+ const updateOptions: Partial<AddProviderOptions> = {
202
+ name: updateAnswers.name,
203
+ description: updateAnswers.description,
204
+ baseUrl: updateAnswers.baseUrl
205
+ };
206
+
207
+ if (updateAnswers.apiKey.trim()) {
208
+ updateOptions.apiKey = updateAnswers.apiKey;
209
+ }
210
+
211
+ const updateResult = await providerManager.updateProvider(updateIdAnswer.id, updateOptions);
212
+ if (updateResult.success) {
213
+ console.log(chalk.green(`✓ ${updateResult.message}`));
214
+ } else {
215
+ console.error(chalk.red(`✗ ${updateResult.message}`));
216
+ }
217
+ }
218
+
219
+ console.log();
220
+ shouldContinue = await askToContinue();
221
+ break;
222
+ }
223
+
224
+ case 'remove': {
225
+ const removeAnswer = await inquirer.prompt([
226
+ {
227
+ type: 'list',
228
+ name: 'id',
229
+ message: 'Select provider to remove:',
230
+ choices
231
+ }
232
+ ]);
233
+
234
+ const confirmRemove = await inquirer.prompt([
235
+ {
236
+ type: 'confirm',
237
+ name: 'confirm',
238
+ message: `Are you sure you want to remove this provider?`,
239
+ default: false
240
+ }
241
+ ]);
242
+
243
+ if (confirmRemove.confirm) {
244
+ const removeResult = await providerManager.removeProvider(removeAnswer.id);
245
+ if (removeResult.success) {
246
+ console.log(chalk.green(`✓ ${removeResult.message}`));
247
+ } else {
248
+ console.error(chalk.red(`✗ ${removeResult.message}`));
249
+ }
250
+ } else {
251
+ console.log(chalk.yellow('Operation cancelled'));
252
+ }
253
+
254
+ console.log();
255
+ shouldContinue = await askToContinue();
256
+ break;
257
+ }
258
+
259
+ case 'status': {
260
+ const stats = await providerManager.getStats();
261
+ console.log();
262
+ console.log(chalk.blue('CCM Status:'));
263
+ console.log(`Total providers: ${stats.totalProviders}`);
264
+ console.log(`Current provider: ${stats.currentProvider || 'None'}`);
265
+ console.log(`Claude config: ${stats.claudeConfigPath}`);
266
+ console.log(`CCM config: ${stats.ccmConfigPath}`);
267
+
268
+ if (providers.length > 0) {
269
+ console.log();
270
+ console.log(chalk.blue('Recent providers:'));
271
+ providers
272
+ .filter(p => p.lastUsed)
273
+ .slice(0, 3)
274
+ .forEach(provider => {
275
+ const marker = provider.isCurrent ? chalk.green('* ') : ' ';
276
+ const name = provider.isCurrent ? chalk.green(provider.name) : provider.name;
277
+ const lastUsed = new Date(provider.lastUsed!).toLocaleDateString();
278
+ console.log(`${marker}${name} (${lastUsed}, ${provider.usageCount} uses)`);
279
+ });
280
+ }
281
+ console.log();
282
+
283
+ console.log();
284
+ shouldContinue = await askToContinue();
285
+ break;
70
286
  }
71
287
  }
72
- } else if (options?.autoWrite !== false) {
73
- console.log(chalk.yellow('Environment variables have been set, but may not persist.'));
74
- console.log(chalk.cyan('Consider running: source <(ccman env)'));
75
- } else {
76
- console.log(chalk.yellow('To set environment variables manually, run:'));
77
- console.log(chalk.cyan('source <(ccman env)'));
288
+ } // while 循环结束
289
+ } catch (error) {
290
+ console.error(chalk.red(`✗ Error: ${error}`));
291
+ process.exit(1);
292
+ } finally {
293
+ // 恢复原始的 SIGINT 处理器
294
+ process.removeAllListeners('SIGINT');
78
295
  }
79
296
  }
80
297
 
81
298
  program
82
299
  .name('ccman')
83
- .description('Claude Code Manager - Manage Claude Code API configurations')
84
- .version(getCurrentVersion());
300
+ .description('Claude Code Manager - Manage Claude API configurations')
301
+ .version(getPackageVersion())
302
+ .hook('preAction', () => {
303
+ // 开发模式提示
304
+ if (process.env.CCM_CONFIG_DIR || process.env.CLAUDE_CONFIG_PATH) {
305
+ console.log(chalk.yellow('🔧 Development Mode:'));
306
+ if (process.env.CCM_CONFIG_DIR) {
307
+ console.log(chalk.yellow(` CCM Config: ${process.env.CCM_CONFIG_DIR}`));
308
+ }
309
+ if (process.env.CLAUDE_CONFIG_PATH) {
310
+ console.log(chalk.yellow(` Claude Config: ${process.env.CLAUDE_CONFIG_PATH}`));
311
+ }
312
+ console.log();
313
+ }
314
+ })
315
+ .action(async () => {
316
+ // 默认无参数时进入交互菜单
317
+ await showInteractiveMenu();
318
+ });
85
319
 
86
- // 列出所有环境
320
+ // 智能列表命令
87
321
  program
88
- .command('list')
89
- .alias('ls')
90
- .description('List all environment groups')
91
- .action(() => {
92
- const environments = envManager.listEnvironments();
93
-
94
- if (environments.length === 0) {
95
- console.log(chalk.yellow('No environment groups found. Use "ccman add" to create one.'));
96
- return;
97
- }
322
+ .command('ls')
323
+ .alias('list')
324
+ .description('List provider configurations')
325
+ .option('--current', 'Show only current provider details')
326
+ .option('--brief', 'Show brief summary')
327
+ .action(async (options?: { current?: boolean; brief?: boolean }) => {
328
+ try {
329
+ await providerManager.init();
330
+
331
+ if (options?.current) {
332
+ // 显示当前供应商详情
333
+ const currentProvider = await providerManager.getCurrentProvider();
334
+
335
+ if (!currentProvider) {
336
+ console.log(chalk.yellow('No provider is currently active.'));
337
+ console.log('Use "ccman use <id>" to activate a provider.');
338
+ return;
339
+ }
98
340
 
99
- console.log();
100
- environments.forEach(env => {
101
- const marker = env.isCurrent ? chalk.green('* ') : ' ';
102
- const name = env.isCurrent ? chalk.green(env.name) : env.name;
103
- console.log(`${marker}${name.padEnd(15)} ${env.baseUrl}`);
341
+ console.log();
342
+ console.log(chalk.green(`Current provider: ${currentProvider.config.name} (${currentProvider.id})`));
343
+ console.log(`Description: ${currentProvider.config.description}`);
344
+ console.log(`Base URL: ${currentProvider.config.config.env.ANTHROPIC_BASE_URL}`);
345
+ console.log(`API Key: ${'*'.repeat(Math.min(currentProvider.config.config.env.ANTHROPIC_AUTH_TOKEN.length, 20))}`);
346
+ console.log(`Usage count: ${currentProvider.config.metadata.usageCount} times`);
347
+ console.log(`Last updated: ${new Date(currentProvider.config.metadata.updatedAt).toLocaleString()}`);
348
+ console.log();
349
+ return;
350
+ }
351
+
352
+ const providers = await providerManager.listProviders();
353
+ const stats = await providerManager.getStats();
354
+
355
+ // 显示状态和配置信息,不管是否有providers
356
+ console.log();
357
+ console.log(chalk.blue('CCM Status:'));
358
+ console.log(`Total providers: ${stats.totalProviders}`);
359
+ console.log(`Current provider: ${stats.currentProvider || 'None'}`);
360
+ console.log(`Environment: ${stats.environment}`);
361
+ console.log();
104
362
 
105
- if (env.lastUsed) {
106
- const lastUsed = new Date(env.lastUsed).toLocaleDateString();
107
- console.log(`${' '.repeat(17)} Last used: ${lastUsed}`);
363
+ console.log(chalk.blue('Configuration Files:'));
364
+ console.log(`Claude config: ${chalk.cyan(stats.claudeConfigPath)}`);
365
+ console.log(`CCM config: ${chalk.cyan(stats.ccmConfigFile)}`);
366
+ console.log(`Providers dir: ${chalk.cyan(stats.providersDir)}`);
367
+ console.log();
368
+
369
+ if (providers.length === 0) {
370
+ console.log(chalk.yellow('No provider configurations found. Use "ccman add" to create one.'));
371
+ return;
372
+ }
373
+
374
+ if (options?.brief) {
375
+ // 简洁模式
376
+ providers.forEach(provider => {
377
+ const marker = provider.isCurrent ? chalk.green('* ') : ' ';
378
+ const name = provider.isCurrent ? chalk.green(provider.name) : provider.name;
379
+ console.log(`${marker}${name} (${provider.id})`);
380
+ });
381
+ console.log();
382
+ return;
108
383
  }
109
- });
110
- console.log();
384
+
385
+ // 详细显示providers信息
386
+ console.log(chalk.blue('Providers:'));
387
+ providers.forEach(provider => {
388
+ const marker = provider.isCurrent ? chalk.green('* ') : ' ';
389
+ const name = provider.isCurrent ? chalk.green(provider.name) : provider.name;
390
+ console.log(`${marker}${name.padEnd(15)} ${provider.baseUrl}`);
391
+ console.log(`${' '.repeat(17)} ${provider.description}`);
392
+
393
+ if (provider.lastUsed) {
394
+ const lastUsed = new Date(provider.lastUsed).toLocaleDateString();
395
+ console.log(`${' '.repeat(17)} Last used: ${lastUsed}, Usage: ${provider.usageCount} times`);
396
+ }
397
+ console.log();
398
+ });
399
+
400
+ } catch (error) {
401
+ console.error(chalk.red(`✗ Error: ${error}`));
402
+ process.exit(1);
403
+ }
111
404
  });
112
405
 
113
- // 添加环境
406
+ // 添加供应商
114
407
  program
115
- .command('add <name> <baseUrl> [apiKey]')
116
- .description('Add a new environment group')
117
- .option('--no-auto-write', 'Do not automatically write to shell config')
118
- .action(async (name: string, baseUrl: string, apiKey?: string, options?: { autoWrite: boolean }) => {
408
+ .command('add <id> <name> <baseUrl> [apiKey]')
409
+ .description('Add a new provider configuration')
410
+ .option('-d, --description <desc>', 'Provider description')
411
+ .action(async (id: string, name: string, baseUrl: string, apiKey?: string, options?: { description?: string }) => {
119
412
  try {
413
+ await providerManager.init();
414
+
120
415
  if (!apiKey) {
121
416
  const answer = await inquirer.prompt([
122
417
  {
@@ -129,36 +424,43 @@ program
129
424
  apiKey = answer.apiKey;
130
425
  }
131
426
 
132
- const addOptions: AddEnvOptions = {
427
+ const addOptions: AddProviderOptions = {
428
+ id,
133
429
  name,
430
+ description: options?.description,
134
431
  baseUrl,
135
- apiKey: apiKey!,
136
- autoWriteShell: options?.autoWrite
432
+ apiKey: apiKey!
137
433
  };
138
434
 
139
- const env = await envManager.addEnvironment(addOptions);
140
- console.log(chalk.green(`✓ Added environment group "${name}"`));
141
- console.log(` Base URL: ${env.baseUrl}`);
142
- console.log(` Created: ${new Date(env.createdAt).toLocaleString()}`);
435
+ const result = await providerManager.addProvider(addOptions);
143
436
 
144
- // 询问是否设为当前环境
145
- const currentEnv = envManager.getCurrentEnvironment();
146
- if (!currentEnv || currentEnv.name !== name) {
147
- const useAnswer = await inquirer.prompt([
148
- {
149
- type: 'confirm',
150
- name: 'useCurrent',
151
- message: `Set "${name}" as current environment?`,
152
- default: true
153
- }
154
- ]);
437
+ if (result.success) {
438
+ console.log(chalk.green(`✓ ${result.message}`));
155
439
 
156
- if (useAnswer.useCurrent) {
157
- await performUseEnvironment(name, {
158
- autoWrite: options?.autoWrite,
159
- skipSuccessMessage: true // 因为前面已经显示了添加成功的信息
160
- });
440
+ // 询问是否设为当前供应商
441
+ const currentProvider = await providerManager.getCurrentProvider();
442
+ if (!currentProvider || currentProvider.id !== id) {
443
+ const useAnswer = await inquirer.prompt([
444
+ {
445
+ type: 'confirm',
446
+ name: 'useCurrent',
447
+ message: `Set "${name}" as current provider?`,
448
+ default: true
449
+ }
450
+ ]);
451
+
452
+ if (useAnswer.useCurrent) {
453
+ const useResult = await providerManager.useProvider(id);
454
+ if (useResult.success) {
455
+ console.log(chalk.green(`✓ ${useResult.message}`));
456
+ } else {
457
+ console.error(chalk.red(`✗ ${useResult.message}`));
458
+ }
459
+ }
161
460
  }
461
+ } else {
462
+ console.error(chalk.red(`✗ ${result.message}`));
463
+ process.exit(1);
162
464
  }
163
465
 
164
466
  } catch (error) {
@@ -167,16 +469,41 @@ program
167
469
  }
168
470
  });
169
471
 
170
- // 删除环境
472
+ // 使用供应商
171
473
  program
172
- .command('remove <name>')
173
- .alias('rm')
174
- .description('Remove an environment group')
175
- .action(async (name: string) => {
474
+ .command('use <id>')
475
+ .description('Switch to a provider configuration')
476
+ .action(async (id: string) => {
176
477
  try {
177
- const env = envManager.getEnvironment(name);
178
- if (!env) {
179
- console.error(chalk.red(`✗ Environment "${name}" not found`));
478
+ await providerManager.init();
479
+ const result = await providerManager.useProvider(id);
480
+
481
+ if (result.success) {
482
+ console.log(chalk.green(`✓ ${result.message}`));
483
+ console.log(chalk.cyan('Claude Code configuration has been updated successfully!'));
484
+ } else {
485
+ console.error(chalk.red(`✗ ${result.message}`));
486
+ process.exit(1);
487
+ }
488
+ } catch (error) {
489
+ console.error(chalk.red(`✗ Error: ${error}`));
490
+ process.exit(1);
491
+ }
492
+ });
493
+
494
+ // 删除供应商
495
+ program
496
+ .command('rm <id>')
497
+ .alias('remove')
498
+ .description('Remove a provider configuration')
499
+ .action(async (id: string) => {
500
+ try {
501
+ await providerManager.init();
502
+ const providers = await providerManager.listProviders();
503
+ const provider = providers.find(p => p.id === id);
504
+
505
+ if (!provider) {
506
+ console.error(chalk.red(`✗ Provider '${id}' not found`));
180
507
  process.exit(1);
181
508
  }
182
509
 
@@ -184,14 +511,19 @@ program
184
511
  {
185
512
  type: 'confirm',
186
513
  name: 'confirm',
187
- message: `Are you sure you want to remove environment "${name}"?`,
514
+ message: `Are you sure you want to remove provider "${provider.name}" (${id})?`,
188
515
  default: false
189
516
  }
190
517
  ]);
191
518
 
192
519
  if (answer.confirm) {
193
- await envManager.removeEnvironment(name);
194
- console.log(chalk.green(`✓ Removed environment "${name}"`));
520
+ const result = await providerManager.removeProvider(id);
521
+
522
+ if (result.success) {
523
+ console.log(chalk.green(`✓ ${result.message}`));
524
+ } else {
525
+ console.error(chalk.red(`✗ ${result.message}`));
526
+ }
195
527
  } else {
196
528
  console.log(chalk.yellow('Operation cancelled'));
197
529
  }
@@ -201,124 +533,18 @@ program
201
533
  }
202
534
  });
203
535
 
204
- // 使用环境
205
- program
206
- .command('use <name>')
207
- .description('Switch to an environment group')
208
- .option('--no-auto-write', 'Do not automatically write to shell config')
209
- .option('--auto-source', 'Automatically source shell config after writing (risky)')
210
- .action(async (name: string, options?: { autoWrite: boolean; autoSource: boolean }) => {
211
- try {
212
- await performUseEnvironment(name, {
213
- autoWrite: options?.autoWrite,
214
- autoSource: options?.autoSource
215
- });
216
- } catch (error) {
217
- console.error(chalk.red(`✗ Error: ${error}`));
218
- process.exit(1);
219
- }
220
- });
221
-
222
- // 显示当前环境
223
- program
224
- .command('current')
225
- .description('Show current environment group')
226
- .action(() => {
227
- const currentEnv = envManager.getCurrentEnvironment();
228
-
229
- if (!currentEnv) {
230
- console.log(chalk.yellow('No environment is currently active.'));
231
- console.log('Use "ccman use <name>" to activate an environment.');
232
- return;
233
- }
234
-
235
- console.log();
236
- console.log(chalk.green(`Current environment: ${currentEnv.name}`));
237
- console.log(`Base URL: ${currentEnv.baseUrl}`);
238
- console.log(`API Key: ${'*'.repeat(Math.min(currentEnv.apiKey.length, 20))}`);
239
- console.log(`Created: ${new Date(currentEnv.createdAt).toLocaleString()}`);
240
-
241
- if (currentEnv.lastUsed) {
242
- console.log(`Last used: ${new Date(currentEnv.lastUsed).toLocaleString()}`);
243
- }
244
- console.log();
245
- });
246
-
247
- // 生成环境变量脚本
248
- program
249
- .command('env')
250
- .description('Generate shell script to set environment variables')
251
- .action(() => {
252
- try {
253
- const script = envManager.generateEnvScript();
254
- console.log(script);
255
- } catch (error) {
256
- console.error(chalk.red(`✗ Error: ${error}`));
257
- process.exit(1);
258
- }
259
- });
260
-
261
- // 测试环境
262
- program
263
- .command('test [name]')
264
- .description('Test environment configuration (defaults to current)')
265
- .action(async (name?: string) => {
266
- const result = await envManager.testEnvironment(name);
267
-
268
- if (result.success) {
269
- console.log(chalk.green(`✓ ${result.message}`));
270
- } else {
271
- console.error(chalk.red(`✗ ${result.message}`));
272
- if (result.error) {
273
- console.error(chalk.gray(`Details: ${result.error}`));
274
- }
275
- process.exit(1);
276
- }
277
- });
278
-
279
- // 显示统计信息
280
- program
281
- .command('status')
282
- .description('Show CCM status and statistics')
283
- .action(() => {
284
- const stats = envManager.getStats();
285
- const environments = envManager.listEnvironments();
286
-
287
- console.log();
288
- console.log(chalk.blue('CCM Status:'));
289
- console.log(`Total environments: ${stats.totalEnvironments}`);
290
- console.log(`Current environment: ${stats.currentEnvironment || 'None'}`);
291
- console.log(`Shell integration: ${stats.hasShellIntegration ? 'Enabled' : 'Disabled'}`);
292
-
293
- if (environments.length > 0) {
294
- console.log();
295
- console.log(chalk.blue('Recent environments:'));
296
- const sortedEnvs = environments
297
- .filter(env => env.lastUsed)
298
- .sort((a, b) => new Date(b.lastUsed!).getTime() - new Date(a.lastUsed!).getTime())
299
- .slice(0, 3);
300
-
301
- sortedEnvs.forEach(env => {
302
- const lastUsed = new Date(env.lastUsed!).toLocaleDateString();
303
- console.log(` ${env.name} (${lastUsed})`);
304
- });
305
- }
306
- console.log();
307
- });
308
-
309
536
  // 清除所有配置
310
537
  program
311
538
  .command('clear')
312
- .alias('clearall')
313
- .description('Clear all environments and shell integration (DESTRUCTIVE)')
539
+ .alias('reset')
540
+ .description('Clear all provider configurations (DESTRUCTIVE)')
314
541
  .action(async () => {
315
542
  try {
316
- // 确认操作
317
543
  const confirmAnswer = await inquirer.prompt([
318
544
  {
319
545
  type: 'confirm',
320
546
  name: 'confirmed',
321
- message: chalk.red('⚠️ This will remove ALL environments and shell integration. Are you sure?'),
547
+ message: chalk.red('⚠️ This will remove ALL provider configurations. Are you sure?'),
322
548
  default: false
323
549
  }
324
550
  ]);
@@ -328,183 +554,25 @@ program
328
554
  return;
329
555
  }
330
556
 
331
- // 执行清除
332
- console.log(chalk.yellow('Clearing CCM configuration...'));
333
- const result = await envManager.clearAll();
557
+ await providerManager.init();
558
+ const result = await providerManager.clearAll();
334
559
 
335
- // 显示结果
336
- console.log();
337
560
  if (result.success) {
338
561
  console.log(chalk.green(`✓ ${result.message}`));
562
+ console.log(chalk.cyan('CCM has been reset to initial state.'));
563
+ console.log(chalk.cyan('You can start fresh with: ccman'));
339
564
  } else {
340
- console.log(chalk.red(`✗ ${result.message}`));
565
+ console.error(chalk.red(`✗ ${result.message}`));
341
566
  }
342
567
 
343
- // 显示详细信息
344
- if (result.details.length > 0) {
345
- console.log();
346
- result.details.forEach(detail => {
347
- if (detail.startsWith('✓')) {
348
- console.log(chalk.green(detail));
349
- } else if (detail.startsWith('⚠')) {
350
- console.log(chalk.yellow(detail));
351
- } else if (detail.startsWith('✗')) {
352
- console.log(chalk.red(detail));
353
- } else {
354
- console.log(detail);
355
- }
356
- });
357
- }
358
-
359
- console.log();
360
- console.log(chalk.cyan('CCM has been reset to initial state.'));
361
- console.log(chalk.cyan('You can start fresh with: ccman config'));
362
-
363
568
  } catch (error) {
364
569
  console.error(chalk.red(`✗ Error: ${error}`));
365
570
  process.exit(1);
366
571
  }
367
572
  });
368
573
 
369
- // 交互式配置
370
- program
371
- .command('config')
372
- .description('Interactive configuration')
373
- .action(async () => {
374
- const environments = envManager.listEnvironments();
375
-
376
- if (environments.length === 0) {
377
- console.log(chalk.yellow('No environments found. Let\'s create your first one.'));
378
-
379
- const answers = await inquirer.prompt([
380
- { type: 'input', name: 'name', message: 'Environment name:', default: 'default' },
381
- { type: 'input', name: 'baseUrl', message: 'Base URL:', default: 'https://api.anthropic.com' },
382
- { type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' },
383
- { type: 'confirm', name: 'autoWrite', message: 'Automatically write to shell config?', default: true }
384
- ]);
385
-
386
- try {
387
- await envManager.addEnvironment({
388
- name: answers.name,
389
- baseUrl: answers.baseUrl,
390
- apiKey: answers.apiKey,
391
- autoWriteShell: answers.autoWrite
392
- });
393
- console.log(chalk.green(`✓ Created environment "${answers.name}"`));
394
- } catch (error) {
395
- console.error(chalk.red(`✗ Error: ${error}`));
396
- }
397
- return;
398
- }
399
-
400
- const choices = environments.map(env => ({
401
- name: `${env.name} (${env.baseUrl})${env.isCurrent ? ' [current]' : ''}`,
402
- value: env.name
403
- }));
404
-
405
- const action = await inquirer.prompt([
406
- {
407
- type: 'list',
408
- name: 'action',
409
- message: 'What would you like to do?',
410
- choices: [
411
- { name: 'Switch environment', value: 'switch' },
412
- { name: 'Add new environment', value: 'add' },
413
- { name: 'Edit environment', value: 'edit' },
414
- { name: 'Remove environment', value: 'remove' },
415
- { name: 'Show current status', value: 'status' }
416
- ]
417
- }
418
- ]);
419
-
420
- switch (action.action) {
421
- case 'switch':
422
- const switchAnswer = await inquirer.prompt([
423
- {
424
- type: 'list',
425
- name: 'name',
426
- message: 'Select environment:',
427
- choices
428
- }
429
- ]);
430
- try {
431
- await performUseEnvironment(switchAnswer.name);
432
- } catch (error) {
433
- console.error(chalk.red(`✗ Error: ${error}`));
434
- }
435
- break;
436
-
437
- case 'add':
438
- const addAnswers = await inquirer.prompt([
439
- { type: 'input', name: 'name', message: 'Environment name:' },
440
- { type: 'input', name: 'baseUrl', message: 'Base URL:' },
441
- { type: 'password', name: 'apiKey', message: 'API Key:', mask: '*' }
442
- ]);
443
- try {
444
- await envManager.addEnvironment(addAnswers);
445
- console.log(chalk.green(`✓ Added environment "${addAnswers.name}"`));
446
- } catch (error) {
447
- console.error(chalk.red(`✗ Error: ${error}`));
448
- }
449
- break;
450
-
451
- case 'edit':
452
- const editEnvAnswer = await inquirer.prompt([
453
- {
454
- type: 'list',
455
- name: 'name',
456
- message: 'Select environment to edit:',
457
- choices
458
- }
459
- ]);
460
-
461
- const currentConfig = envManager.getEnvironment(editEnvAnswer.name);
462
- if (currentConfig) {
463
- const editAnswers = await inquirer.prompt([
464
- { type: 'input', name: 'baseUrl', message: 'Base URL:', default: currentConfig.baseUrl },
465
- { type: 'password', name: 'apiKey', message: 'API Key:', mask: '*', default: currentConfig.apiKey }
466
- ]);
467
-
468
- try {
469
- await envManager.updateEnvironment(editEnvAnswer.name, {
470
- baseUrl: editAnswers.baseUrl,
471
- apiKey: editAnswers.apiKey
472
- });
473
- console.log(chalk.green(`✓ Updated environment "${editEnvAnswer.name}"`));
474
- } catch (error) {
475
- console.error(chalk.red(`✗ Error: ${error}`));
476
- }
477
- }
478
- break;
479
-
480
- case 'status':
481
- {
482
- const stats = envManager.getStats();
483
- const environments = envManager.listEnvironments();
484
-
485
- console.log();
486
- console.log(chalk.blue('CCM Status:'));
487
- console.log(`Total environments: ${stats.totalEnvironments}`);
488
- console.log(`Current environment: ${stats.currentEnvironment || 'None'}`);
489
- console.log(`Shell integration: ${stats.hasShellIntegration ? 'Enabled' : 'Disabled'}`);
490
-
491
- if (environments.length > 0) {
492
- console.log();
493
- console.log(chalk.blue('Recent environments:'));
494
- environments
495
- .sort((a, b) => new Date(b.lastUsed || b.createdAt).getTime() - new Date(a.lastUsed || a.createdAt).getTime())
496
- .slice(0, 3)
497
- .forEach(env => {
498
- const marker = env.isCurrent ? chalk.green('* ') : ' ';
499
- const name = env.isCurrent ? chalk.green(env.name) : env.name;
500
- console.log(`${marker}${name.padEnd(15)} ${env.baseUrl}`);
501
- });
502
- }
503
- console.log();
504
- }
505
- break;
506
- }
507
- });
574
+ // 添加语言管理命令
575
+ program.addCommand(createLanguageCommands());
508
576
 
509
577
  // 解析命令行参数
510
- program.parse();
578
+ program.parse(process.argv);