ccman 2.1.3 → 2.1.5

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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +201 -301
  3. package/README_en.md +294 -0
  4. package/dist/cli.js +213 -33
  5. package/dist/cli.js.map +1 -1
  6. package/dist/config/default-providers.d.ts +34 -0
  7. package/dist/config/default-providers.d.ts.map +1 -0
  8. package/dist/config/default-providers.js +96 -0
  9. package/dist/config/default-providers.js.map +1 -0
  10. package/dist/config/static-env.d.ts +1 -1
  11. package/dist/config/static-env.js +1 -1
  12. package/dist/core/ClaudeConfigManager.d.ts +1 -1
  13. package/dist/core/ClaudeConfigManager.d.ts.map +1 -1
  14. package/dist/core/ClaudeConfigManager.js +2 -4
  15. package/dist/core/ClaudeConfigManager.js.map +1 -1
  16. package/package.json +24 -3
  17. package/.editorconfig +0 -15
  18. package/.env.development +0 -3
  19. package/.env.production +0 -3
  20. package/.eslintrc.js +0 -28
  21. package/.github/workflows/release.yml +0 -99
  22. package/.prettierrc +0 -10
  23. package/CLAUDE.md +0 -279
  24. package/README_zh.md +0 -394
  25. package/dev-test.sh +0 -40
  26. package/docs/npm-publish-guide.md +0 -71
  27. package/docs/release-guide.md +0 -144
  28. package/docs/scripts-guide.md +0 -221
  29. package/docs/version-management.md +0 -64
  30. package/jest.config.js +0 -22
  31. package/release-temp/README.md +0 -394
  32. package/release-temp/package.json +0 -61
  33. package/scripts/build-env.js +0 -75
  34. package/scripts/modules/check-uncommitted.sh +0 -109
  35. package/scripts/modules/create-tag.sh +0 -279
  36. package/scripts/modules/monitor-release.sh +0 -296
  37. package/scripts/modules/version-bump.sh +0 -262
  38. package/scripts/publish-local.sh +0 -91
  39. package/scripts/quick-release.sh +0 -100
  40. package/scripts/release.sh +0 -430
  41. package/scripts/smart-release-v3.sh +0 -283
  42. package/scripts/smart-release.sh +0 -322
  43. package/src/cli.ts +0 -598
  44. package/src/commands/lang.ts +0 -105
  45. package/src/core/CCMConfigManager.ts +0 -259
  46. package/src/core/ClaudeConfigManager.ts +0 -141
  47. package/src/i18n/LanguageManager.ts +0 -169
  48. package/src/i18n/messages.ts +0 -233
  49. package/src/index.ts +0 -4
  50. package/src/providers/ProviderManager.ts +0 -412
  51. package/src/types/index.ts +0 -101
  52. package/src/utils/env-config.ts +0 -53
  53. package/src/utils/version.ts +0 -16
  54. package/tsconfig.json +0 -25
@@ -1,259 +0,0 @@
1
- import * as fs from 'fs-extra';
2
- import * as path from 'path';
3
- import { CCMConfig, ProviderConfig } from '../types';
4
- import { getPackageVersion } from '../utils/version';
5
- import { envConfig } from '../utils/env-config';
6
-
7
- /**
8
- * CCM配置管理器
9
- * 负责管理 ~/.ccman/ 目录下的配置文件
10
- */
11
- export class CCMConfigManager {
12
- private configDir: string;
13
- private configPath: string;
14
- private providersDir: string;
15
-
16
- constructor() {
17
- // 使用编译时生成的静态配置
18
- this.configDir = envConfig.getCCMConfigDir();
19
- this.configPath = path.join(this.configDir, 'config.json');
20
- this.providersDir = path.join(this.configDir, 'providers');
21
- }
22
-
23
- /**
24
- * 初始化配置目录和文件
25
- */
26
- async init(): Promise<void> {
27
- await fs.ensureDir(this.configDir);
28
- await fs.ensureDir(this.providersDir);
29
-
30
- if (!await fs.pathExists(this.configPath)) {
31
- // 使用编译时确定的Claude配置路径
32
- const defaultConfig: CCMConfig = {
33
- version: getPackageVersion(),
34
- currentProvider: '',
35
- claudeConfigPath: envConfig.getClaudeConfigPath(),
36
- providers: {},
37
- settings: {
38
- language: null,
39
- firstRun: true
40
- },
41
- metadata: {
42
- version: getPackageVersion(),
43
- createdAt: new Date().toISOString(),
44
- updatedAt: new Date().toISOString()
45
- }
46
- };
47
-
48
- await fs.writeFile(this.configPath, JSON.stringify(defaultConfig, null, 2));
49
- }
50
- }
51
-
52
- /**
53
- * 读取主配置
54
- */
55
- async readConfig(): Promise<CCMConfig> {
56
- try {
57
- if (!await fs.pathExists(this.configPath)) {
58
- await this.init();
59
- }
60
-
61
- const content = await fs.readFile(this.configPath, 'utf8');
62
- const config = JSON.parse(content);
63
-
64
- // 迁移旧版本配置
65
- const migratedConfig = await this.migrateConfig(config);
66
-
67
- // 如果配置被迁移了,立即保存新格式
68
- if (this.needsMigration(config)) {
69
- await fs.writeFile(this.configPath, JSON.stringify(migratedConfig, null, 2));
70
- }
71
-
72
- return migratedConfig;
73
- } catch (error) {
74
- throw new Error(`Failed to read CCM config: ${error}`);
75
- }
76
- }
77
-
78
- /**
79
- * 写入主配置
80
- */
81
- async writeConfig(config: CCMConfig): Promise<void> {
82
- try {
83
- // 确保配置结构完整,兼容旧版本
84
- const migratedConfig = await this.migrateConfig(config);
85
-
86
- // 更新版本信息和时间戳
87
- migratedConfig.version = getPackageVersion();
88
- migratedConfig.metadata.version = getPackageVersion();
89
- migratedConfig.metadata.updatedAt = new Date().toISOString();
90
-
91
- await fs.writeFile(this.configPath, JSON.stringify(migratedConfig, null, 2));
92
- } catch (error) {
93
- throw new Error(`Failed to write CCM config: ${error}`);
94
- }
95
- }
96
-
97
- /**
98
- * 检查是否需要迁移配置
99
- */
100
- private needsMigration(config: any): boolean {
101
- // 只检查metadata字段,因为version字段是新添加的
102
- return !config.metadata;
103
- }
104
-
105
- /**
106
- * 执行配置迁移
107
- */
108
- private async performMigration(config: any): Promise<CCMConfig> {
109
- console.log('🔄 Migrating configuration from older version...');
110
-
111
- // 备份旧配置
112
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
113
- const backupPath = `${this.configPath}.backup-v1-${timestamp}`;
114
-
115
- if (await fs.pathExists(this.configPath)) {
116
- await fs.copy(this.configPath, backupPath);
117
- console.log(`📦 Old config backed up to: ${backupPath}`);
118
- }
119
-
120
- // 迁移到新格式
121
- const migratedConfig: CCMConfig = {
122
- version: getPackageVersion(),
123
- currentProvider: config.currentProvider || '',
124
- claudeConfigPath: config.claudeConfigPath || envConfig.getClaudeConfigPath(),
125
- providers: config.providers || {},
126
- settings: {
127
- language: config.settings?.language || null,
128
- firstRun: config.settings?.firstRun ?? true
129
- },
130
- metadata: {
131
- version: getPackageVersion(),
132
- createdAt: new Date().toISOString(),
133
- updatedAt: new Date().toISOString()
134
- }
135
- };
136
-
137
- console.log('✅ Configuration migration completed');
138
- return migratedConfig;
139
- }
140
-
141
- /**
142
- * 配置迁移和兼容性处理
143
- */
144
- private async migrateConfig(config: any): Promise<CCMConfig> {
145
- // 检查是否需要迁移
146
- if (this.needsMigration(config)) {
147
- return await this.performMigration(config);
148
- }
149
-
150
- // 确保所有必需字段存在
151
- return {
152
- version: config.version || getPackageVersion(),
153
- currentProvider: config.currentProvider || '',
154
- claudeConfigPath: config.claudeConfigPath || envConfig.getClaudeConfigPath(),
155
- providers: config.providers || {},
156
- settings: {
157
- language: config.settings?.language || null,
158
- firstRun: config.settings?.firstRun ?? true
159
- },
160
- metadata: {
161
- version: config.metadata?.version || getPackageVersion(),
162
- createdAt: config.metadata?.createdAt || new Date().toISOString(),
163
- updatedAt: config.metadata?.updatedAt || new Date().toISOString()
164
- }
165
- };
166
- }
167
-
168
- /**
169
- * 读取供应商配置
170
- */
171
- async readProviderConfig(providerId: string): Promise<ProviderConfig | null> {
172
- try {
173
- const providerPath = path.join(this.providersDir, `${providerId}.json`);
174
-
175
- if (!await fs.pathExists(providerPath)) {
176
- return null;
177
- }
178
-
179
- const content = await fs.readFile(providerPath, 'utf8');
180
- return JSON.parse(content);
181
- } catch (error) {
182
- throw new Error(`Failed to read provider config: ${error}`);
183
- }
184
- }
185
-
186
- /**
187
- * 写入供应商配置
188
- */
189
- async writeProviderConfig(providerId: string, config: ProviderConfig): Promise<void> {
190
- try {
191
- const providerPath = path.join(this.providersDir, `${providerId}.json`);
192
- config.metadata.updatedAt = new Date().toISOString();
193
-
194
- await fs.writeFile(providerPath, JSON.stringify(config, null, 2));
195
- } catch (error) {
196
- throw new Error(`Failed to write provider config: ${error}`);
197
- }
198
- }
199
-
200
- /**
201
- * 删除供应商配置
202
- */
203
- async deleteProviderConfig(providerId: string): Promise<void> {
204
- try {
205
- const providerPath = path.join(this.providersDir, `${providerId}.json`);
206
-
207
- if (await fs.pathExists(providerPath)) {
208
- await fs.remove(providerPath);
209
- }
210
- } catch (error) {
211
- throw new Error(`Failed to delete provider config: ${error}`);
212
- }
213
- }
214
-
215
- /**
216
- * 列出所有供应商配置文件
217
- */
218
- async listProviderFiles(): Promise<string[]> {
219
- try {
220
- if (!await fs.pathExists(this.providersDir)) {
221
- return [];
222
- }
223
-
224
- const files = await fs.readdir(this.providersDir);
225
- return files
226
- .filter((file: string) => file.endsWith('.json'))
227
- .map((file: string) => file.replace('.json', ''));
228
- } catch (error) {
229
- throw new Error(`Failed to list provider files: ${error}`);
230
- }
231
- }
232
-
233
- /**
234
- * 清除所有配置
235
- */
236
- async clearAll(): Promise<void> {
237
- try {
238
- if (await fs.pathExists(this.configDir)) {
239
- await fs.remove(this.configDir);
240
- }
241
- } catch (error) {
242
- throw new Error(`Failed to clear all configs: ${error}`);
243
- }
244
- }
245
-
246
- /**
247
- * 获取配置目录路径
248
- */
249
- getConfigDir(): string {
250
- return this.configDir;
251
- }
252
-
253
- /**
254
- * 获取供应商配置目录路径
255
- */
256
- getProvidersDir(): string {
257
- return this.providersDir;
258
- }
259
- }
@@ -1,141 +0,0 @@
1
- import * as fs from 'fs-extra';
2
- import * as path from 'path';
3
- import { ClaudeSettings } from '../types';
4
- import { envConfig } from '../utils/env-config';
5
-
6
- /**
7
- * Claude配置管理器
8
- * 负责直接修改 ~/.claude/settings.json
9
- */
10
- export class ClaudeConfigManager {
11
- private claudeConfigPath: string;
12
-
13
- constructor(claudeConfigPath?: string) {
14
- // 优先使用传入参数,否则使用编译时确定的路径
15
- this.claudeConfigPath = claudeConfigPath || envConfig.getClaudeConfigPath();
16
- }
17
-
18
- /**
19
- * 读取Claude配置
20
- */
21
- async readClaudeConfig(): Promise<ClaudeSettings | null> {
22
- try {
23
- if (!await fs.pathExists(this.claudeConfigPath)) {
24
- return null;
25
- }
26
-
27
- const content = await fs.readFile(this.claudeConfigPath, 'utf8');
28
- return JSON.parse(content);
29
- } catch (error) {
30
- throw new Error(`Failed to read Claude config: ${error}`);
31
- }
32
- }
33
-
34
- /**
35
- * 选择性写入Claude配置
36
- * 只覆盖指定的key,保留其他用户配置
37
- */
38
- async writeClaudeConfig(config: ClaudeSettings, skipBackup: boolean = false): Promise<string | null> {
39
- try {
40
- await fs.ensureDir(path.dirname(this.claudeConfigPath));
41
-
42
- let existingConfig: any = {};
43
- let backupPath: string | null = null;
44
-
45
- // 如果文件已存在,先读取现有配置
46
- if (await fs.pathExists(this.claudeConfigPath)) {
47
- const content = await fs.readFile(this.claudeConfigPath, 'utf8');
48
- existingConfig = JSON.parse(content);
49
-
50
- // 只有在需要时才备份(首次CCM接管用户配置)
51
- if (!skipBackup && !this.isCCMManaged(existingConfig)) {
52
- backupPath = await this.backupClaudeConfig();
53
- }
54
- }
55
-
56
- // 选择性覆盖只有CCM管理的key
57
- const mergedConfig = {
58
- ...existingConfig, // 保留现有配置
59
- env: {
60
- ...existingConfig.env, // 保留现有env配置
61
- // 只覆盖CCM管理的环境变量
62
- ANTHROPIC_AUTH_TOKEN: config.env.ANTHROPIC_AUTH_TOKEN,
63
- ANTHROPIC_BASE_URL: config.env.ANTHROPIC_BASE_URL,
64
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: config.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC,
65
- CLAUDE_CODE_MAX_OUTPUT_TOKENS: config.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS
66
- },
67
- permissions: {
68
- ...existingConfig.permissions, // 保留现有permissions配置
69
- // 覆盖CCM管理的权限设置
70
- allow: config.permissions.allow,
71
- deny: config.permissions.deny
72
- },
73
- // 添加CCM管理标记(隐藏字段)
74
- '_ccm_managed': true,
75
- '_ccm_version': '2.0.0'
76
- };
77
-
78
- await fs.writeFile(this.claudeConfigPath, JSON.stringify(mergedConfig, null, 2), 'utf8');
79
- return backupPath;
80
- } catch (error) {
81
- throw new Error(`Failed to write Claude config: ${error}`);
82
- }
83
- }
84
-
85
- /**
86
- * 备份当前Claude配置
87
- */
88
- async backupClaudeConfig(): Promise<string> {
89
- try {
90
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
91
- const backupPath = `${this.claudeConfigPath}.backup-${timestamp}`;
92
-
93
- if (await fs.pathExists(this.claudeConfigPath)) {
94
- await fs.copy(this.claudeConfigPath, backupPath);
95
- return backupPath;
96
- }
97
-
98
- return '';
99
- } catch (error) {
100
- throw new Error(`Failed to backup Claude config: ${error}`);
101
- }
102
- }
103
-
104
- /**
105
- * 恢复Claude配置
106
- */
107
- async restoreClaudeConfig(backupPath: string): Promise<void> {
108
- try {
109
- if (await fs.pathExists(backupPath)) {
110
- await fs.copy(backupPath, this.claudeConfigPath);
111
- }
112
- } catch (error) {
113
- throw new Error(`Failed to restore Claude config: ${error}`);
114
- }
115
- }
116
-
117
- /**
118
- * 检查配置是否已被CCM管理
119
- */
120
- private isCCMManaged(config: any): boolean {
121
- return config && config._ccm_managed === true;
122
- }
123
-
124
- /**
125
- * 验证Claude配置目录是否存在
126
- */
127
- async ensureClaudeConfigDir(): Promise<void> {
128
- const claudeDir = path.dirname(this.claudeConfigPath);
129
-
130
- if (!await fs.pathExists(claudeDir)) {
131
- throw new Error(`Claude config directory not found: ${claudeDir}. Please ensure Claude Code is installed and initialized.`);
132
- }
133
- }
134
-
135
- /**
136
- * 获取Claude配置路径
137
- */
138
- getClaudeConfigPath(): string {
139
- return this.claudeConfigPath;
140
- }
141
- }
@@ -1,169 +0,0 @@
1
- import inquirer from 'inquirer';
2
- import { MessageBundle, chineseMessages, englishMessages } from './messages';
3
- import { CCMConfigManager } from '../core/CCMConfigManager';
4
- import { LanguageStats } from '../types';
5
-
6
- export class LanguageManager {
7
- private configManager: CCMConfigManager;
8
-
9
- constructor() {
10
- this.configManager = new CCMConfigManager();
11
- }
12
-
13
- /**
14
- * 检测系统是否为英文环境
15
- */
16
- private shouldUseEnglish(): boolean {
17
- const locale = process.env.LANG || process.env.LANGUAGE || '';
18
- return locale.toLowerCase().startsWith('en');
19
- }
20
-
21
- /**
22
- * 检查是否为首次运行
23
- */
24
- async isFirstRun(): Promise<boolean> {
25
- try {
26
- await this.configManager.init();
27
- const config = await this.configManager.readConfig();
28
- return !config.settings || config.settings.language === null || config.settings.firstRun !== false;
29
- } catch (error) {
30
- // 配置不存在,视为首次运行
31
- return true;
32
- }
33
- }
34
-
35
- /**
36
- * 获取当前语言设置
37
- */
38
- async getCurrentLanguage(): Promise<'zh' | 'en'> {
39
- try {
40
- await this.configManager.init();
41
- const config = await this.configManager.readConfig();
42
- const langSetting = config.settings?.language;
43
-
44
- switch (langSetting) {
45
- case 'zh':
46
- return 'zh';
47
- case 'en':
48
- return 'en';
49
- case 'auto':
50
- return this.shouldUseEnglish() ? 'en' : 'zh';
51
- default:
52
- // 首次运行或未设置,根据系统环境决定
53
- return this.shouldUseEnglish() ? 'en' : 'zh';
54
- }
55
- } catch (error) {
56
- // 配置读取失败,使用自动检测
57
- return this.shouldUseEnglish() ? 'en' : 'zh';
58
- }
59
- }
60
-
61
- /**
62
- * 获取当前语言的消息包
63
- */
64
- async getMessages(): Promise<MessageBundle> {
65
- const currentLang = await this.getCurrentLanguage();
66
- return currentLang === 'en' ? englishMessages : chineseMessages;
67
- }
68
-
69
- /**
70
- * 首次运行语言选择向导
71
- */
72
- async promptLanguageChoice(): Promise<'zh' | 'en' | 'auto'> {
73
- console.log('🌍 Welcome to CCM! / 欢迎使用 CCM!\n');
74
- console.log('This is your first time running CCM.');
75
- console.log('这是您首次运行 CCM。\n');
76
-
77
- const answer = await inquirer.prompt([
78
- {
79
- type: 'list',
80
- name: 'language',
81
- message: 'Please choose your preferred language:\n请选择您偏好的语言:',
82
- choices: [
83
- {
84
- name: '🇨🇳 中文 (Chinese)',
85
- value: 'zh'
86
- },
87
- {
88
- name: '🇺🇸 English',
89
- value: 'en'
90
- },
91
- {
92
- name: '🌐 自动检测 (Auto-detect based on system)',
93
- value: 'auto'
94
- }
95
- ]
96
- }
97
- ]);
98
-
99
- return answer.language;
100
- }
101
-
102
- /**
103
- * 设置语言
104
- */
105
- async setLanguage(language: 'zh' | 'en' | 'auto'): Promise<void> {
106
- await this.configManager.init();
107
- const config = await this.configManager.readConfig();
108
-
109
- // 更新配置
110
- const updatedConfig = {
111
- ...config,
112
- settings: {
113
- ...config.settings,
114
- language,
115
- firstRun: false
116
- }
117
- };
118
-
119
- await this.configManager.writeConfig(updatedConfig);
120
- }
121
-
122
- /**
123
- * 重置语言设置(恢复首次运行状态)
124
- */
125
- async resetLanguage(): Promise<void> {
126
- await this.configManager.init();
127
- const config = await this.configManager.readConfig();
128
-
129
- const updatedConfig = {
130
- ...config,
131
- settings: {
132
- ...config.settings,
133
- language: null,
134
- firstRun: true
135
- }
136
- };
137
-
138
- await this.configManager.writeConfig(updatedConfig);
139
- }
140
-
141
- /**
142
- * 获取语言统计信息
143
- */
144
- async getLanguageStats(): Promise<LanguageStats> {
145
- await this.configManager.init();
146
- const config = await this.configManager.readConfig();
147
-
148
- return {
149
- current: config.settings?.language || 'auto',
150
- isFirstRun: await this.isFirstRun(),
151
- autoDetected: this.shouldUseEnglish() ? 'en' : 'zh'
152
- };
153
- }
154
-
155
- /**
156
- * 处理首次运行流程
157
- */
158
- async handleFirstRun(): Promise<void> {
159
- if (await this.isFirstRun()) {
160
- const selectedLang = await this.promptLanguageChoice();
161
- await this.setLanguage(selectedLang);
162
-
163
- // 显示设置成功消息(双语)
164
- const messages = await this.getMessages();
165
- console.log(`\n✓ ${messages.languageSetSuccess}`);
166
- console.log(`✓ ${messages.languageChangeHint}\n`);
167
- }
168
- }
169
- }