ccman 2.1.2 → 2.1.4
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/LICENSE +21 -0
- package/README.md +308 -258
- package/README_en.md +444 -0
- package/dist/cli.js +213 -33
- package/dist/cli.js.map +1 -1
- package/dist/config/default-providers.d.ts +34 -0
- package/dist/config/default-providers.d.ts.map +1 -0
- package/dist/config/default-providers.js +96 -0
- package/dist/config/default-providers.js.map +1 -0
- package/dist/config/static-env.d.ts +1 -1
- package/dist/config/static-env.js +1 -1
- package/dist/core/ClaudeConfigManager.d.ts +5 -1
- package/dist/core/ClaudeConfigManager.d.ts.map +1 -1
- package/dist/core/ClaudeConfigManager.js +19 -3
- package/dist/core/ClaudeConfigManager.js.map +1 -1
- package/dist/providers/ProviderManager.d.ts.map +1 -1
- package/dist/providers/ProviderManager.js +7 -8
- package/dist/providers/ProviderManager.js.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +24 -3
- package/.editorconfig +0 -15
- package/.env.development +0 -3
- package/.env.production +0 -3
- package/.eslintrc.js +0 -28
- package/.github/workflows/release.yml +0 -99
- package/.prettierrc +0 -10
- package/CLAUDE.md +0 -276
- package/README_zh.md +0 -394
- package/dev-test.sh +0 -40
- package/docs/npm-publish-guide.md +0 -71
- package/docs/release-guide.md +0 -144
- package/docs/scripts-guide.md +0 -221
- package/docs/version-management.md +0 -64
- package/jest.config.js +0 -22
- package/release-temp/README.md +0 -394
- package/release-temp/package.json +0 -61
- package/scripts/build-env.js +0 -75
- package/scripts/modules/check-uncommitted.sh +0 -109
- package/scripts/modules/create-tag.sh +0 -279
- package/scripts/modules/monitor-release.sh +0 -296
- package/scripts/modules/version-bump.sh +0 -262
- package/scripts/publish-local.sh +0 -91
- package/scripts/quick-release.sh +0 -100
- package/scripts/release.sh +0 -430
- package/scripts/smart-release-v3.sh +0 -283
- package/scripts/smart-release.sh +0 -322
- package/src/cli.ts +0 -598
- package/src/commands/lang.ts +0 -105
- package/src/core/CCMConfigManager.ts +0 -259
- package/src/core/ClaudeConfigManager.ts +0 -123
- package/src/i18n/LanguageManager.ts +0 -169
- package/src/i18n/messages.ts +0 -233
- package/src/index.ts +0 -4
- package/src/providers/ProviderManager.ts +0 -414
- package/src/types/index.ts +0 -100
- package/src/utils/env-config.ts +0 -53
- package/src/utils/version.ts +0 -16
- 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,123 +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): Promise<void> {
|
|
39
|
-
try {
|
|
40
|
-
await fs.ensureDir(path.dirname(this.claudeConfigPath));
|
|
41
|
-
|
|
42
|
-
let existingConfig: any = {};
|
|
43
|
-
|
|
44
|
-
// 如果文件已存在,先读取现有配置
|
|
45
|
-
if (await fs.pathExists(this.claudeConfigPath)) {
|
|
46
|
-
const content = await fs.readFile(this.claudeConfigPath, 'utf8');
|
|
47
|
-
existingConfig = JSON.parse(content);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 选择性覆盖只有CCM管理的key
|
|
51
|
-
const mergedConfig = {
|
|
52
|
-
...existingConfig, // 保留现有配置
|
|
53
|
-
env: {
|
|
54
|
-
...existingConfig.env, // 保留现有env配置
|
|
55
|
-
// 只覆盖CCM管理的环境变量
|
|
56
|
-
ANTHROPIC_AUTH_TOKEN: config.env.ANTHROPIC_AUTH_TOKEN,
|
|
57
|
-
ANTHROPIC_BASE_URL: config.env.ANTHROPIC_BASE_URL,
|
|
58
|
-
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: config.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
|
|
59
|
-
},
|
|
60
|
-
permissions: {
|
|
61
|
-
...existingConfig.permissions, // 保留现有permissions配置
|
|
62
|
-
// 覆盖CCM管理的权限设置
|
|
63
|
-
allow: config.permissions.allow,
|
|
64
|
-
deny: config.permissions.deny
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
await fs.writeFile(this.claudeConfigPath, JSON.stringify(mergedConfig, null, 2), 'utf8');
|
|
69
|
-
} catch (error) {
|
|
70
|
-
throw new Error(`Failed to write Claude config: ${error}`);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* 备份当前Claude配置
|
|
76
|
-
*/
|
|
77
|
-
async backupClaudeConfig(): Promise<string> {
|
|
78
|
-
try {
|
|
79
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
80
|
-
const backupPath = `${this.claudeConfigPath}.backup-${timestamp}`;
|
|
81
|
-
|
|
82
|
-
if (await fs.pathExists(this.claudeConfigPath)) {
|
|
83
|
-
await fs.copy(this.claudeConfigPath, backupPath);
|
|
84
|
-
return backupPath;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return '';
|
|
88
|
-
} catch (error) {
|
|
89
|
-
throw new Error(`Failed to backup Claude config: ${error}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* 恢复Claude配置
|
|
95
|
-
*/
|
|
96
|
-
async restoreClaudeConfig(backupPath: string): Promise<void> {
|
|
97
|
-
try {
|
|
98
|
-
if (await fs.pathExists(backupPath)) {
|
|
99
|
-
await fs.copy(backupPath, this.claudeConfigPath);
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
throw new Error(`Failed to restore Claude config: ${error}`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* 验证Claude配置目录是否存在
|
|
108
|
-
*/
|
|
109
|
-
async ensureClaudeConfigDir(): Promise<void> {
|
|
110
|
-
const claudeDir = path.dirname(this.claudeConfigPath);
|
|
111
|
-
|
|
112
|
-
if (!await fs.pathExists(claudeDir)) {
|
|
113
|
-
throw new Error(`Claude config directory not found: ${claudeDir}. Please ensure Claude Code is installed and initialized.`);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* 获取Claude配置路径
|
|
119
|
-
*/
|
|
120
|
-
getClaudeConfigPath(): string {
|
|
121
|
-
return this.claudeConfigPath;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
@@ -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
|
-
}
|