ccman 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.github/workflows/release.yml +5 -5
  2. package/CLAUDE.md +246 -185
  3. package/README.md +282 -249
  4. package/README_zh.md +283 -250
  5. package/dev-test.sh +40 -0
  6. package/dist/cli.js +421 -369
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/lang.d.ts +3 -0
  9. package/dist/commands/lang.d.ts.map +1 -0
  10. package/dist/commands/lang.js +99 -0
  11. package/dist/commands/lang.js.map +1 -0
  12. package/dist/core/CCMConfigManager.d.ts +52 -0
  13. package/dist/core/CCMConfigManager.d.ts.map +1 -0
  14. package/dist/core/CCMConfigManager.js +203 -0
  15. package/dist/core/CCMConfigManager.js.map +1 -0
  16. package/dist/core/ClaudeConfigManager.d.ts +35 -0
  17. package/dist/core/ClaudeConfigManager.d.ts.map +1 -0
  18. package/dist/core/ClaudeConfigManager.js +151 -0
  19. package/dist/core/ClaudeConfigManager.js.map +1 -0
  20. package/dist/i18n/LanguageManager.d.ts +43 -0
  21. package/dist/i18n/LanguageManager.d.ts.map +1 -0
  22. package/dist/i18n/LanguageManager.js +157 -0
  23. package/dist/i18n/LanguageManager.js.map +1 -0
  24. package/dist/i18n/messages.d.ts +65 -0
  25. package/dist/i18n/messages.d.ts.map +1 -0
  26. package/dist/i18n/messages.js +144 -0
  27. package/dist/i18n/messages.js.map +1 -0
  28. package/dist/index.d.ts +3 -3
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -8
  31. package/dist/index.js.map +1 -1
  32. package/dist/providers/ProviderManager.d.ts +55 -0
  33. package/dist/providers/ProviderManager.d.ts.map +1 -0
  34. package/dist/providers/ProviderManager.js +326 -0
  35. package/dist/providers/ProviderManager.js.map +1 -0
  36. package/dist/types/index.d.ts +78 -38
  37. package/dist/types/index.d.ts.map +1 -1
  38. package/dist/types/index.js +1 -0
  39. package/dist/types/index.js.map +1 -1
  40. package/dist/utils/version.d.ts +2 -64
  41. package/dist/utils/version.d.ts.map +1 -1
  42. package/dist/utils/version.js +12 -158
  43. package/dist/utils/version.js.map +1 -1
  44. package/package.json +2 -2
  45. package/release-temp/README.md +282 -249
  46. package/release-temp/package.json +2 -2
  47. package/scripts/modules/create-tag.sh +53 -10
  48. package/scripts/modules/monitor-release.sh +40 -12
  49. package/scripts/modules/version-bump.sh +14 -17
  50. package/scripts/smart-release-v3.sh +20 -26
  51. package/src/cli.ts +458 -394
  52. package/src/commands/lang.ts +105 -0
  53. package/src/core/CCMConfigManager.ts +185 -0
  54. package/src/core/ClaudeConfigManager.ts +127 -0
  55. package/src/i18n/LanguageManager.ts +169 -0
  56. package/src/i18n/messages.ts +233 -0
  57. package/src/index.ts +4 -5
  58. package/src/providers/ProviderManager.ts +380 -0
  59. package/src/types/index.ts +80 -39
  60. package/src/utils/version.ts +11 -184
  61. package/dist/config/ConfigManager.d.ts +0 -67
  62. package/dist/config/ConfigManager.d.ts.map +0 -1
  63. package/dist/config/ConfigManager.js +0 -226
  64. package/dist/config/ConfigManager.js.map +0 -1
  65. package/dist/config/EnvironmentManager.d.ts +0 -83
  66. package/dist/config/EnvironmentManager.d.ts.map +0 -1
  67. package/dist/config/EnvironmentManager.js +0 -280
  68. package/dist/config/EnvironmentManager.js.map +0 -1
  69. package/dist/config/constants.d.ts +0 -40
  70. package/dist/config/constants.d.ts.map +0 -1
  71. package/dist/config/constants.js +0 -97
  72. package/dist/config/constants.js.map +0 -1
  73. package/dist/shell/ShellManager.d.ts +0 -81
  74. package/dist/shell/ShellManager.d.ts.map +0 -1
  75. package/dist/shell/ShellManager.js +0 -490
  76. package/dist/shell/ShellManager.js.map +0 -1
  77. package/src/config/ConfigManager.ts +0 -227
  78. package/src/config/EnvironmentManager.ts +0 -327
  79. package/src/config/constants.ts +0 -64
  80. package/src/shell/ShellManager.ts +0 -526
@@ -0,0 +1,105 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import { LanguageManager } from '../i18n/LanguageManager';
5
+
6
+ export function createLanguageCommands(): Command {
7
+ const langCommand = new Command('lang');
8
+ const languageManager = new LanguageManager();
9
+
10
+ langCommand
11
+ .description('Manage language settings / 管理语言设置');
12
+
13
+ // ccman lang - 显示当前语言设置
14
+ langCommand
15
+ .action(async () => {
16
+ try {
17
+ const stats = await languageManager.getLanguageStats();
18
+ const messages = await languageManager.getMessages();
19
+
20
+ console.log();
21
+ console.log(chalk.blue(`${messages.language.current} ${getLanguageDisplayName(stats.current)}`));
22
+
23
+ if (stats.current === 'auto') {
24
+ console.log(chalk.gray(`Auto-detected: ${getLanguageDisplayName(stats.autoDetected!)}`));
25
+ }
26
+
27
+ console.log(chalk.cyan(messages.language.availableCommands));
28
+ console.log();
29
+
30
+ } catch (error) {
31
+ console.error(chalk.red(`✗ Error: ${error}`));
32
+ process.exit(1);
33
+ }
34
+ });
35
+
36
+ // ccman lang set <language> - 设置语言
37
+ langCommand
38
+ .command('set <language>')
39
+ .description('Set language preference / 设置语言偏好')
40
+ .action(async (language: string) => {
41
+ try {
42
+ if (!['zh', 'en', 'auto'].includes(language)) {
43
+ const messages = await languageManager.getMessages();
44
+ console.error(chalk.red(`✗ ${messages.language.invalidLanguage}`));
45
+ process.exit(1);
46
+ }
47
+
48
+ await languageManager.setLanguage(language as 'zh' | 'en' | 'auto');
49
+
50
+ // 重新获取消息(可能语言已变更)
51
+ const messages = await languageManager.getMessages();
52
+ console.log(chalk.green(`✓ ${messages.language.switchSuccess}`));
53
+ console.log(chalk.cyan(`${messages.language.current} ${getLanguageDisplayName(language)}`));
54
+
55
+ } catch (error) {
56
+ console.error(chalk.red(`✗ Error: ${error}`));
57
+ process.exit(1);
58
+ }
59
+ });
60
+
61
+ // ccman lang reset - 重置语言设置
62
+ langCommand
63
+ .command('reset')
64
+ .description('Reset language setting to first-run state / 重置语言设置为首次运行状态')
65
+ .action(async () => {
66
+ try {
67
+ const messages = await languageManager.getMessages();
68
+
69
+ const answer = await inquirer.prompt([
70
+ {
71
+ type: 'confirm',
72
+ name: 'confirm',
73
+ message: messages.language.resetConfirm,
74
+ default: false
75
+ }
76
+ ]);
77
+
78
+ if (answer.confirm) {
79
+ await languageManager.resetLanguage();
80
+ console.log(chalk.green(`✓ ${messages.language.resetSuccess}`));
81
+ } else {
82
+ console.log(chalk.yellow(messages.cancelled));
83
+ }
84
+
85
+ } catch (error) {
86
+ console.error(chalk.red(`✗ Error: ${error}`));
87
+ process.exit(1);
88
+ }
89
+ });
90
+
91
+ return langCommand;
92
+ }
93
+
94
+ function getLanguageDisplayName(lang: string): string {
95
+ switch (lang) {
96
+ case 'zh':
97
+ return '中文 (Chinese)';
98
+ case 'en':
99
+ return 'English';
100
+ case 'auto':
101
+ return 'Auto-detect / 自动检测';
102
+ default:
103
+ return lang;
104
+ }
105
+ }
@@ -0,0 +1,185 @@
1
+ import * as fs from 'fs-extra';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { CCMConfig, ProviderConfig } from '../types';
5
+ import { getPackageVersion } from '../utils/version';
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
+ const configDirName = process.env.CCM_CONFIG_DIR || '.ccman-dev';
19
+ this.configDir = path.join(os.homedir(), configDirName);
20
+ this.configPath = path.join(this.configDir, 'config.json');
21
+ this.providersDir = path.join(this.configDir, 'providers');
22
+ }
23
+
24
+ /**
25
+ * 初始化配置目录和文件
26
+ */
27
+ async init(): Promise<void> {
28
+ await fs.ensureDir(this.configDir);
29
+ await fs.ensureDir(this.providersDir);
30
+
31
+ if (!await fs.pathExists(this.configPath)) {
32
+ const defaultConfig: CCMConfig = {
33
+ currentProvider: '',
34
+ claudeConfigPath: process.env.CLAUDE_CONFIG_PATH || path.join(os.homedir(), '.claude', 'settings-dev.json'),
35
+ providers: {},
36
+ settings: {
37
+ language: null,
38
+ firstRun: true
39
+ },
40
+ metadata: {
41
+ version: getPackageVersion(),
42
+ createdAt: new Date().toISOString(),
43
+ updatedAt: new Date().toISOString()
44
+ }
45
+ };
46
+
47
+ await fs.writeFile(this.configPath, JSON.stringify(defaultConfig, null, 2));
48
+ }
49
+ }
50
+
51
+ /**
52
+ * 读取主配置
53
+ */
54
+ async readConfig(): Promise<CCMConfig> {
55
+ try {
56
+ if (!await fs.pathExists(this.configPath)) {
57
+ await this.init();
58
+ }
59
+
60
+ const content = await fs.readFile(this.configPath, 'utf8');
61
+ const config = JSON.parse(content);
62
+
63
+ // 确保providers字段存在
64
+ if (!config.providers) {
65
+ config.providers = {};
66
+ }
67
+
68
+ // 确保settings字段存在(兼容旧版本)
69
+ if (!config.settings) {
70
+ config.settings = {
71
+ language: null,
72
+ firstRun: true
73
+ };
74
+ }
75
+
76
+ return config;
77
+ } catch (error) {
78
+ throw new Error(`Failed to read CCM config: ${error}`);
79
+ }
80
+ }
81
+
82
+ /**
83
+ * 写入主配置
84
+ */
85
+ async writeConfig(config: CCMConfig): Promise<void> {
86
+ try {
87
+ config.metadata.updatedAt = new Date().toISOString();
88
+ await fs.writeFile(this.configPath, JSON.stringify(config, null, 2));
89
+ } catch (error) {
90
+ throw new Error(`Failed to write CCM config: ${error}`);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 读取供应商配置
96
+ */
97
+ async readProviderConfig(providerId: string): Promise<ProviderConfig | null> {
98
+ try {
99
+ const providerPath = path.join(this.providersDir, `${providerId}.json`);
100
+
101
+ if (!await fs.pathExists(providerPath)) {
102
+ return null;
103
+ }
104
+
105
+ const content = await fs.readFile(providerPath, 'utf8');
106
+ return JSON.parse(content);
107
+ } catch (error) {
108
+ throw new Error(`Failed to read provider config: ${error}`);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * 写入供应商配置
114
+ */
115
+ async writeProviderConfig(providerId: string, config: ProviderConfig): Promise<void> {
116
+ try {
117
+ const providerPath = path.join(this.providersDir, `${providerId}.json`);
118
+ config.metadata.updatedAt = new Date().toISOString();
119
+
120
+ await fs.writeFile(providerPath, JSON.stringify(config, null, 2));
121
+ } catch (error) {
122
+ throw new Error(`Failed to write provider config: ${error}`);
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 删除供应商配置
128
+ */
129
+ async deleteProviderConfig(providerId: string): Promise<void> {
130
+ try {
131
+ const providerPath = path.join(this.providersDir, `${providerId}.json`);
132
+
133
+ if (await fs.pathExists(providerPath)) {
134
+ await fs.remove(providerPath);
135
+ }
136
+ } catch (error) {
137
+ throw new Error(`Failed to delete provider config: ${error}`);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * 列出所有供应商配置文件
143
+ */
144
+ async listProviderFiles(): Promise<string[]> {
145
+ try {
146
+ if (!await fs.pathExists(this.providersDir)) {
147
+ return [];
148
+ }
149
+
150
+ const files = await fs.readdir(this.providersDir);
151
+ return files
152
+ .filter((file: string) => file.endsWith('.json'))
153
+ .map((file: string) => file.replace('.json', ''));
154
+ } catch (error) {
155
+ throw new Error(`Failed to list provider files: ${error}`);
156
+ }
157
+ }
158
+
159
+ /**
160
+ * 清除所有配置
161
+ */
162
+ async clearAll(): Promise<void> {
163
+ try {
164
+ if (await fs.pathExists(this.configDir)) {
165
+ await fs.remove(this.configDir);
166
+ }
167
+ } catch (error) {
168
+ throw new Error(`Failed to clear all configs: ${error}`);
169
+ }
170
+ }
171
+
172
+ /**
173
+ * 获取配置目录路径
174
+ */
175
+ getConfigDir(): string {
176
+ return this.configDir;
177
+ }
178
+
179
+ /**
180
+ * 获取供应商配置目录路径
181
+ */
182
+ getProvidersDir(): string {
183
+ return this.providersDir;
184
+ }
185
+ }
@@ -0,0 +1,127 @@
1
+ import * as fs from 'fs-extra';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ import { ClaudeSettings } from '../types';
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 ||
16
+ process.env.CLAUDE_CONFIG_PATH ||
17
+ path.join(os.homedir(), '.claude', 'settings-dev.json');
18
+ }
19
+
20
+ /**
21
+ * 读取Claude配置
22
+ */
23
+ async readClaudeConfig(): Promise<ClaudeSettings | null> {
24
+ try {
25
+ if (!await fs.pathExists(this.claudeConfigPath)) {
26
+ return null;
27
+ }
28
+
29
+ const content = await fs.readFile(this.claudeConfigPath, 'utf8');
30
+ return JSON.parse(content);
31
+ } catch (error) {
32
+ throw new Error(`Failed to read Claude config: ${error}`);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * 选择性写入Claude配置
38
+ * 只覆盖指定的key,保留其他用户配置
39
+ */
40
+ async writeClaudeConfig(config: ClaudeSettings): Promise<void> {
41
+ try {
42
+ await fs.ensureDir(path.dirname(this.claudeConfigPath));
43
+
44
+ let existingConfig: any = {};
45
+
46
+ // 如果文件已存在,先读取现有配置
47
+ if (await fs.pathExists(this.claudeConfigPath)) {
48
+ const content = await fs.readFile(this.claudeConfigPath, 'utf8');
49
+ existingConfig = JSON.parse(content);
50
+ }
51
+
52
+ // 选择性覆盖只有CCM管理的key
53
+ const mergedConfig = {
54
+ ...existingConfig, // 保留现有配置
55
+ env: {
56
+ ...existingConfig.env, // 保留现有env配置
57
+ // 只覆盖CCM管理的环境变量
58
+ ANTHROPIC_AUTH_TOKEN: config.env.ANTHROPIC_AUTH_TOKEN,
59
+ ANTHROPIC_BASE_URL: config.env.ANTHROPIC_BASE_URL,
60
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: config.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC
61
+ },
62
+ permissions: {
63
+ ...existingConfig.permissions, // 保留现有permissions配置
64
+ // 覆盖CCM管理的权限设置
65
+ allow: config.permissions.allow,
66
+ deny: config.permissions.deny
67
+ },
68
+ // 覆盖apiKeyHelper
69
+ apiKeyHelper: config.apiKeyHelper
70
+ };
71
+
72
+ await fs.writeFile(this.claudeConfigPath, JSON.stringify(mergedConfig, null, 2), 'utf8');
73
+ } catch (error) {
74
+ throw new Error(`Failed to write Claude config: ${error}`);
75
+ }
76
+ }
77
+
78
+ /**
79
+ * 备份当前Claude配置
80
+ */
81
+ async backupClaudeConfig(): Promise<string> {
82
+ try {
83
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
84
+ const backupPath = `${this.claudeConfigPath}.backup-${timestamp}`;
85
+
86
+ if (await fs.pathExists(this.claudeConfigPath)) {
87
+ await fs.copy(this.claudeConfigPath, backupPath);
88
+ return backupPath;
89
+ }
90
+
91
+ return '';
92
+ } catch (error) {
93
+ throw new Error(`Failed to backup Claude config: ${error}`);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * 恢复Claude配置
99
+ */
100
+ async restoreClaudeConfig(backupPath: string): Promise<void> {
101
+ try {
102
+ if (await fs.pathExists(backupPath)) {
103
+ await fs.copy(backupPath, this.claudeConfigPath);
104
+ }
105
+ } catch (error) {
106
+ throw new Error(`Failed to restore Claude config: ${error}`);
107
+ }
108
+ }
109
+
110
+ /**
111
+ * 验证Claude配置目录是否存在
112
+ */
113
+ async ensureClaudeConfigDir(): Promise<void> {
114
+ const claudeDir = path.dirname(this.claudeConfigPath);
115
+
116
+ if (!await fs.pathExists(claudeDir)) {
117
+ throw new Error(`Claude config directory not found: ${claudeDir}. Please ensure Claude Code is installed and initialized.`);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * 获取Claude配置路径
123
+ */
124
+ getClaudeConfigPath(): string {
125
+ return this.claudeConfigPath;
126
+ }
127
+ }
@@ -0,0 +1,169 @@
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
+ }