ccman 0.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 (58) hide show
  1. package/.editorconfig +15 -0
  2. package/.eslintrc.js +28 -0
  3. package/.github/workflows/release.yml +213 -0
  4. package/.prettierrc +10 -0
  5. package/CLAUDE.md +215 -0
  6. package/README.md +361 -0
  7. package/README_zh.md +361 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +476 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/config/ConfigManager.d.ts +67 -0
  13. package/dist/config/ConfigManager.d.ts.map +1 -0
  14. package/dist/config/ConfigManager.js +226 -0
  15. package/dist/config/ConfigManager.js.map +1 -0
  16. package/dist/config/EnvironmentManager.d.ts +83 -0
  17. package/dist/config/EnvironmentManager.d.ts.map +1 -0
  18. package/dist/config/EnvironmentManager.js +280 -0
  19. package/dist/config/EnvironmentManager.js.map +1 -0
  20. package/dist/config/constants.d.ts +40 -0
  21. package/dist/config/constants.d.ts.map +1 -0
  22. package/dist/config/constants.js +97 -0
  23. package/dist/config/constants.js.map +1 -0
  24. package/dist/index.d.ts +5 -0
  25. package/dist/index.d.ts.map +1 -0
  26. package/dist/index.js +26 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/shell/ShellManager.d.ts +73 -0
  29. package/dist/shell/ShellManager.d.ts.map +1 -0
  30. package/dist/shell/ShellManager.js +391 -0
  31. package/dist/shell/ShellManager.js.map +1 -0
  32. package/dist/types/index.d.ts +55 -0
  33. package/dist/types/index.d.ts.map +1 -0
  34. package/dist/types/index.js +6 -0
  35. package/dist/types/index.js.map +1 -0
  36. package/dist/utils/version.d.ts +67 -0
  37. package/dist/utils/version.d.ts.map +1 -0
  38. package/dist/utils/version.js +199 -0
  39. package/dist/utils/version.js.map +1 -0
  40. package/docs/npm-publish-guide.md +71 -0
  41. package/docs/release-guide.md +86 -0
  42. package/docs/version-management.md +64 -0
  43. package/jest.config.js +22 -0
  44. package/package.json +57 -0
  45. package/release-temp/README.md +361 -0
  46. package/release-temp/package.json +57 -0
  47. package/scripts/publish-local.sh +91 -0
  48. package/scripts/quick-release.sh +100 -0
  49. package/scripts/release.sh +430 -0
  50. package/src/cli.ts +510 -0
  51. package/src/config/ConfigManager.ts +227 -0
  52. package/src/config/EnvironmentManager.ts +327 -0
  53. package/src/config/constants.ts +64 -0
  54. package/src/index.ts +5 -0
  55. package/src/shell/ShellManager.ts +416 -0
  56. package/src/types/index.ts +60 -0
  57. package/src/utils/version.ts +189 -0
  58. package/tsconfig.json +25 -0
package/src/cli.ts ADDED
@@ -0,0 +1,510 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import chalk from 'chalk';
5
+ import inquirer from 'inquirer';
6
+ import { EnvironmentManager } from './config/EnvironmentManager';
7
+ import { AddEnvOptions } from './types';
8
+ import { getCurrentVersion } from './utils/version';
9
+
10
+ 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
+ });
25
+
26
+ if (!options?.skipSuccessMessage) {
27
+ console.log(chalk.green(`✓ Switched to environment "${name}"`));
28
+ console.log(` Base URL: ${result.env.baseUrl}`);
29
+ }
30
+
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)'));
41
+ }
42
+ } else {
43
+ // 询问用户是否要自动 source
44
+ const sourceAnswer = await inquirer.prompt([
45
+ {
46
+ type: 'list',
47
+ name: 'sourceChoice',
48
+ message: 'How would you like to apply the environment variables?',
49
+ 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'
54
+ }
55
+ ]);
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();
60
+
61
+ if (sourceResult.success) {
62
+ console.log(chalk.green('✓ Shell configuration sourced successfully'));
63
+ } else {
64
+ console.log(chalk.red(`✗ Auto-source failed: ${sourceResult.error}`));
65
+ console.log(chalk.cyan('Please run manually: source ~/.bashrc (or ~/.zshrc)'));
66
+ }
67
+ } else {
68
+ console.log(chalk.cyan('To apply changes, restart your terminal or run:'));
69
+ console.log(chalk.cyan('source ~/.bashrc (or ~/.zshrc)'));
70
+ }
71
+ }
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)'));
78
+ }
79
+ }
80
+
81
+ program
82
+ .name('ccman')
83
+ .description('Claude Code Manager - Manage Claude Code API configurations')
84
+ .version(getCurrentVersion());
85
+
86
+ // 列出所有环境
87
+ 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
+ }
98
+
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}`);
104
+
105
+ if (env.lastUsed) {
106
+ const lastUsed = new Date(env.lastUsed).toLocaleDateString();
107
+ console.log(`${' '.repeat(17)} Last used: ${lastUsed}`);
108
+ }
109
+ });
110
+ console.log();
111
+ });
112
+
113
+ // 添加环境
114
+ 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 }) => {
119
+ try {
120
+ if (!apiKey) {
121
+ const answer = await inquirer.prompt([
122
+ {
123
+ type: 'password',
124
+ name: 'apiKey',
125
+ message: 'Enter API Key:',
126
+ mask: '*'
127
+ }
128
+ ]);
129
+ apiKey = answer.apiKey;
130
+ }
131
+
132
+ const addOptions: AddEnvOptions = {
133
+ name,
134
+ baseUrl,
135
+ apiKey: apiKey!,
136
+ autoWriteShell: options?.autoWrite
137
+ };
138
+
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()}`);
143
+
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
+ ]);
155
+
156
+ if (useAnswer.useCurrent) {
157
+ await performUseEnvironment(name, {
158
+ autoWrite: options?.autoWrite,
159
+ skipSuccessMessage: true // 因为前面已经显示了添加成功的信息
160
+ });
161
+ }
162
+ }
163
+
164
+ } catch (error) {
165
+ console.error(chalk.red(`✗ Error: ${error}`));
166
+ process.exit(1);
167
+ }
168
+ });
169
+
170
+ // 删除环境
171
+ program
172
+ .command('remove <name>')
173
+ .alias('rm')
174
+ .description('Remove an environment group')
175
+ .action(async (name: string) => {
176
+ try {
177
+ const env = envManager.getEnvironment(name);
178
+ if (!env) {
179
+ console.error(chalk.red(`✗ Environment "${name}" not found`));
180
+ process.exit(1);
181
+ }
182
+
183
+ const answer = await inquirer.prompt([
184
+ {
185
+ type: 'confirm',
186
+ name: 'confirm',
187
+ message: `Are you sure you want to remove environment "${name}"?`,
188
+ default: false
189
+ }
190
+ ]);
191
+
192
+ if (answer.confirm) {
193
+ await envManager.removeEnvironment(name);
194
+ console.log(chalk.green(`✓ Removed environment "${name}"`));
195
+ } else {
196
+ console.log(chalk.yellow('Operation cancelled'));
197
+ }
198
+ } catch (error) {
199
+ console.error(chalk.red(`✗ Error: ${error}`));
200
+ process.exit(1);
201
+ }
202
+ });
203
+
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
+ // 清除所有配置
310
+ program
311
+ .command('clear')
312
+ .alias('clearall')
313
+ .description('Clear all environments and shell integration (DESTRUCTIVE)')
314
+ .action(async () => {
315
+ try {
316
+ // 确认操作
317
+ const confirmAnswer = await inquirer.prompt([
318
+ {
319
+ type: 'confirm',
320
+ name: 'confirmed',
321
+ message: chalk.red('⚠️ This will remove ALL environments and shell integration. Are you sure?'),
322
+ default: false
323
+ }
324
+ ]);
325
+
326
+ if (!confirmAnswer.confirmed) {
327
+ console.log(chalk.yellow('Operation cancelled.'));
328
+ return;
329
+ }
330
+
331
+ // 执行清除
332
+ console.log(chalk.yellow('Clearing CCM configuration...'));
333
+ const result = await envManager.clearAll();
334
+
335
+ // 显示结果
336
+ console.log();
337
+ if (result.success) {
338
+ console.log(chalk.green(`✓ ${result.message}`));
339
+ } else {
340
+ console.log(chalk.red(`✗ ${result.message}`));
341
+ }
342
+
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
+ } catch (error) {
364
+ console.error(chalk.red(`✗ Error: ${error}`));
365
+ process.exit(1);
366
+ }
367
+ });
368
+
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
+ });
508
+
509
+ // 解析命令行参数
510
+ program.parse();