@x-all-in-one/coding-helper 0.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.
@@ -0,0 +1,148 @@
1
+ import { readFileSync, existsSync, readdirSync } from 'fs';
2
+ import { join, dirname } from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ class I18n {
5
+ static instance;
6
+ currentLocale = 'en_US';
7
+ translations = new Map();
8
+ fallbackLocale = 'en_US';
9
+ localesDir;
10
+ constructor() {
11
+ // Determine the locales directory path
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ // Both in development (src/lib/i18n.ts) and production (dist/lib/i18n.js),
15
+ // locales is at the same relative path: lib -> parent -> locales
16
+ this.localesDir = join(__dirname, '..', 'locales');
17
+ this.loadTranslations();
18
+ this.loadSavedLocale();
19
+ }
20
+ static getInstance() {
21
+ if (!I18n.instance) {
22
+ I18n.instance = new I18n();
23
+ }
24
+ return I18n.instance;
25
+ }
26
+ loadTranslations() {
27
+ try {
28
+ // Check if locales directory exists
29
+ if (!existsSync(this.localesDir)) {
30
+ console.warn(`Locales directory not found at ${this.localesDir}`);
31
+ return;
32
+ }
33
+ // Get all JSON files in the locales directory
34
+ const files = readdirSync(this.localesDir);
35
+ const localeFiles = files.filter(file => file.endsWith('.json'));
36
+ if (localeFiles.length === 0) {
37
+ console.warn('No locale files found in locales directory');
38
+ return;
39
+ }
40
+ // Load each locale file
41
+ for (const file of localeFiles) {
42
+ const locale = file.replace('.json', '');
43
+ const filePath = join(this.localesDir, file);
44
+ try {
45
+ const fileContent = readFileSync(filePath, 'utf-8');
46
+ const translations = JSON.parse(fileContent);
47
+ this.translations.set(locale, translations);
48
+ }
49
+ catch (error) {
50
+ console.warn(`Failed to load locale file ${file}:`, error);
51
+ }
52
+ }
53
+ // Try to detect locale from environment
54
+ const envLocale = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES;
55
+ if (envLocale) {
56
+ const localeCode = envLocale.split('.')[0].replace('_', '-');
57
+ // First try exact match
58
+ if (this.translations.has(localeCode)) {
59
+ this.currentLocale = localeCode;
60
+ }
61
+ else {
62
+ // Try language-only match (e.g., 'en' from 'en-US')
63
+ const langOnly = localeCode.split('-')[0];
64
+ const matchingLocale = Array.from(this.translations.keys()).find(key => key.startsWith(langOnly + '_'));
65
+ if (matchingLocale) {
66
+ this.currentLocale = matchingLocale;
67
+ }
68
+ else if (langOnly === 'zh') {
69
+ // Fallback to zh_CN for Chinese
70
+ this.currentLocale = 'zh_CN';
71
+ }
72
+ else if (langOnly === 'en') {
73
+ // Fallback to en_US for English
74
+ this.currentLocale = 'en_US';
75
+ }
76
+ }
77
+ }
78
+ }
79
+ catch (error) {
80
+ console.warn('Failed to load translations:', error);
81
+ }
82
+ }
83
+ setLocale(locale) {
84
+ if (this.translations.has(locale)) {
85
+ this.currentLocale = locale;
86
+ this.saveLocale();
87
+ }
88
+ else {
89
+ console.warn(`Locale '${locale}' not supported, falling back to '${this.currentLocale}'`);
90
+ }
91
+ }
92
+ getLocale() {
93
+ return this.currentLocale;
94
+ }
95
+ getAvailableLocales() {
96
+ return Array.from(this.translations.keys());
97
+ }
98
+ translate(key, params) {
99
+ const keys = key.split('.');
100
+ let translation = this.translations.get(this.currentLocale);
101
+ for (const k of keys) {
102
+ if (translation && typeof translation === 'object' && k in translation) {
103
+ translation = translation[k];
104
+ }
105
+ else {
106
+ // Fallback to fallback locale
107
+ translation = this.translations.get(this.fallbackLocale);
108
+ for (const fallbackKey of keys) {
109
+ if (translation && typeof translation === 'object' && fallbackKey in translation) {
110
+ translation = translation[fallbackKey];
111
+ }
112
+ else {
113
+ return key; // Return key if translation not found
114
+ }
115
+ }
116
+ break;
117
+ }
118
+ }
119
+ if (typeof translation !== 'string') {
120
+ return key;
121
+ }
122
+ // Replace parameters in the translation string
123
+ if (params) {
124
+ return translation.replace(/\{\{(\w+)\}\}/g, (match, param) => {
125
+ return params[param] || match;
126
+ });
127
+ }
128
+ return translation;
129
+ }
130
+ t(key, params) {
131
+ return this.translate(key, params);
132
+ }
133
+ saveLocale() {
134
+ // Language is now saved via ConfigManager
135
+ // This method is kept for compatibility
136
+ }
137
+ loadSavedLocale() {
138
+ // Language is now loaded via ConfigManager
139
+ // This method is kept for compatibility
140
+ }
141
+ loadFromConfig(locale) {
142
+ if (this.translations.has(locale)) {
143
+ this.currentLocale = locale;
144
+ }
145
+ }
146
+ }
147
+ export const i18n = I18n.getInstance();
148
+ export { i18n as I18n };
@@ -0,0 +1,24 @@
1
+ import { ModelConfig } from './claude-code-manager.js';
2
+ export interface ToolInfo {
3
+ name: string;
4
+ command: string;
5
+ installCommand: string;
6
+ configPath: string;
7
+ displayName: string;
8
+ hidden?: boolean;
9
+ }
10
+ export declare const SUPPORTED_TOOLS: Record<string, ToolInfo>;
11
+ export declare class ToolManager {
12
+ private static instance;
13
+ private constructor();
14
+ static getInstance(): ToolManager;
15
+ isToolInstalled(toolName: string): boolean;
16
+ installTool(toolName: string): Promise<void>;
17
+ getToolConfig(toolName: string): any;
18
+ updateToolConfig(toolName: string, config: any): void;
19
+ loadModelConfig(toolName: string, config: ModelConfig): void;
20
+ getInstalledTools(): string[];
21
+ getSupportedTools(): ToolInfo[];
22
+ isGitInstalled(): boolean;
23
+ }
24
+ export declare const toolManager: ToolManager;
@@ -0,0 +1,256 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
3
+ import { join, dirname } from 'path';
4
+ import { homedir } from 'os';
5
+ import { claudeCodeManager } from './claude-code-manager.js';
6
+ import { i18n } from './i18n.js';
7
+ import inquirer from 'inquirer';
8
+ import chalk from 'chalk';
9
+ import terminalLink from 'terminal-link';
10
+ import ora from "ora";
11
+ export const SUPPORTED_TOOLS = {
12
+ 'claude-code': {
13
+ name: 'claude-code',
14
+ command: 'claude',
15
+ installCommand: 'npm install -g @anthropic-ai/claude-code',
16
+ configPath: join(homedir(), '.claude', 'settings.json'),
17
+ displayName: 'Claude Code'
18
+ },
19
+ 'opencode': {
20
+ name: 'opencode',
21
+ command: 'opencode',
22
+ installCommand: 'npm install -g opencode',
23
+ configPath: join(homedir(), '.config', 'opencode', 'config.json'),
24
+ displayName: 'OpenCode',
25
+ hidden: true // 暂不适配,隐藏不展示
26
+ }
27
+ };
28
+ export class ToolManager {
29
+ static instance;
30
+ constructor() { }
31
+ static getInstance() {
32
+ if (!ToolManager.instance) {
33
+ ToolManager.instance = new ToolManager();
34
+ }
35
+ return ToolManager.instance;
36
+ }
37
+ isToolInstalled(toolName) {
38
+ const tool = SUPPORTED_TOOLS[toolName];
39
+ if (!tool)
40
+ return false;
41
+ try {
42
+ // Windows 使用 where 命令,Unix 系统使用 which 命令
43
+ const checkCommand = process.platform === 'win32'
44
+ ? `where ${tool.command}`
45
+ : `which ${tool.command}`;
46
+ execSync(checkCommand, { stdio: 'pipe' });
47
+ return true;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ }
53
+ async installTool(toolName) {
54
+ const tool = SUPPORTED_TOOLS[toolName];
55
+ if (!tool) {
56
+ throw new Error(`Unknown tool: ${toolName}`);
57
+ }
58
+ const spinner = ora(i18n.t('wizard.installing_tool')).start();
59
+ try {
60
+ // 首次尝试正常安装
61
+ execSync(tool.installCommand, { stdio: 'inherit' });
62
+ spinner.succeed(i18n.t('wizard.tool_installed'));
63
+ }
64
+ catch (error) {
65
+ spinner.fail(i18n.t('wizard.install_failed'));
66
+ // 检查是否是权限错误 (EACCES)
67
+ // execSync 的错误信息可能在 stderr 中,需要检查多个来源
68
+ const errorMessage = (error.message || '') + (error.stderr?.toString() || '') + (error.stdout?.toString() || '');
69
+ const isPermissionError = errorMessage.includes('EACCES') ||
70
+ errorMessage.includes('permission denied') ||
71
+ errorMessage.includes('EPERM') ||
72
+ error.status === 243; // npm 权限错误的退出码
73
+ if (!isPermissionError) {
74
+ // 如果不是权限错误,直接抛出
75
+ throw new Error(`Failed to install ${tool.displayName}: ${error}`);
76
+ }
77
+ console.log('\n⚠️ ' + i18n.t('install.permission_detected') + '\n');
78
+ // Windows 平台处理
79
+ if (process.platform === 'win32') {
80
+ try {
81
+ // Windows: 尝试使用用户级安装(不需要管理员权限)
82
+ const userInstallCommand = tool.installCommand.replace('npm install -g', 'npm install -g --force');
83
+ console.log('🔧 ' + i18n.t('install.trying_solution', { num: '1', desc: i18n.t('install.using_force') }));
84
+ execSync(userInstallCommand, { stdio: 'inherit' });
85
+ console.log('\n✅ ' + i18n.t('install.permission_fixed'));
86
+ return;
87
+ }
88
+ catch (retryError) {
89
+ // 如果还是失败,显示解决方案并询问用户
90
+ console.log(`Retry install error ${retryError}`);
91
+ console.log('\n❌ ' + i18n.t('install.auto_fix_failed'));
92
+ console.log(chalk.yellow('\n📌 ' + i18n.t('install.windows_solutions')));
93
+ console.log('');
94
+ // 方案 1: 以管理员身份运行
95
+ console.log(chalk.cyan.bold(i18n.t('install.windows_solution_1_title')));
96
+ console.log(chalk.gray(' ' + i18n.t('install.windows_solution_1_step1')));
97
+ console.log(chalk.gray(' ' + i18n.t('install.windows_solution_1_step2')));
98
+ console.log(chalk.gray(' ' + i18n.t('install.windows_solution_1_step3')));
99
+ console.log(chalk.white(` ${tool.installCommand}`));
100
+ console.log('');
101
+ // 方案 2: 用户级安装
102
+ console.log(chalk.cyan.bold(i18n.t('install.windows_solution_2_title')));
103
+ console.log(chalk.gray(' ' + i18n.t('install.windows_solution_2_command')));
104
+ console.log(chalk.white(` ${tool.installCommand.replace('npm install -g', 'npm install -g --prefix=%APPDATA%\\npm')}`));
105
+ console.log('');
106
+ // 询问用户是否已完成安装
107
+ const { action } = await inquirer.prompt([
108
+ {
109
+ type: 'list',
110
+ name: 'action',
111
+ message: i18n.t('install.what_next'),
112
+ choices: [
113
+ { name: i18n.t('install.installed_continue'), value: 'continue' },
114
+ { name: i18n.t('install.cancel_install'), value: 'cancel' }
115
+ ]
116
+ }
117
+ ]);
118
+ if (action === 'cancel') {
119
+ throw new Error(i18n.t('install.user_cancelled'));
120
+ }
121
+ // 验证是否真的安装成功
122
+ if (!this.isToolInstalled(toolName)) {
123
+ console.log(chalk.red('\n❌ ' + i18n.t('install.still_not_installed', { tool: tool.displayName })));
124
+ throw new Error(`${tool.displayName} is not installed`);
125
+ }
126
+ console.log(chalk.green('\n✅ ' + i18n.t('install.verified_success', { tool: tool.displayName })));
127
+ return;
128
+ }
129
+ }
130
+ // macOS 和 Linux 平台处理 - 显示建议而不是自动执行
131
+ console.log(chalk.yellow('\n📌 ' + i18n.t('install.unix_solutions')));
132
+ console.log('');
133
+ // 方案 1: 使用 sudo
134
+ console.log(chalk.cyan.bold(i18n.t('install.unix_solution_1_title')));
135
+ console.log(chalk.gray(' ' + i18n.t('install.unix_solution_1_desc')));
136
+ console.log(chalk.white(` sudo ${tool.installCommand}`));
137
+ console.log('');
138
+ // 方案 2: 使用 nvm (推荐)
139
+ const npmDocsUrl = 'https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally';
140
+ const clickableLink = terminalLink(npmDocsUrl, npmDocsUrl, { fallback: () => npmDocsUrl });
141
+ console.log(chalk.cyan.bold(i18n.t('install.unix_solution_2_title')));
142
+ console.log(chalk.gray(' ' + i18n.t('install.unix_solution_2_desc')));
143
+ console.log(chalk.blue(' 📖 ' + i18n.t('install.npm_docs_link') + ': ') + clickableLink);
144
+ console.log('');
145
+ // 询问用户是否已完成安装
146
+ const { action } = await inquirer.prompt([
147
+ {
148
+ type: 'list',
149
+ name: 'action',
150
+ message: i18n.t('install.what_next'),
151
+ choices: [
152
+ { name: i18n.t('install.installed_continue'), value: 'continue' },
153
+ { name: i18n.t('install.cancel_install'), value: 'cancel' }
154
+ ]
155
+ }
156
+ ]);
157
+ if (action === 'cancel') {
158
+ throw new Error(i18n.t('install.user_cancelled'));
159
+ }
160
+ // 验证是否真的安装成功
161
+ if (!this.isToolInstalled(toolName)) {
162
+ console.log(chalk.red('\n❌ ' + i18n.t('install.still_not_installed', { tool: tool.displayName })));
163
+ throw new Error(`${tool.displayName} is not installed`);
164
+ }
165
+ console.log(chalk.green('\n✅ ' + i18n.t('install.verified_success', { tool: tool.displayName })));
166
+ return;
167
+ }
168
+ }
169
+ getToolConfig(toolName) {
170
+ const tool = SUPPORTED_TOOLS[toolName];
171
+ if (!tool) {
172
+ throw new Error(`Unknown tool: ${toolName}`);
173
+ }
174
+ // Claude Code 使用专门的管理器
175
+ if (toolName === 'claude-code') {
176
+ return {
177
+ settings: claudeCodeManager.getSettings()
178
+ };
179
+ }
180
+ try {
181
+ if (existsSync(tool.configPath)) {
182
+ const content = readFileSync(tool.configPath, 'utf-8');
183
+ return JSON.parse(content);
184
+ }
185
+ }
186
+ catch (error) {
187
+ console.warn(`Failed to read config for ${toolName}:`, error);
188
+ }
189
+ return null;
190
+ }
191
+ updateToolConfig(toolName, config) {
192
+ const tool = SUPPORTED_TOOLS[toolName];
193
+ if (!tool) {
194
+ throw new Error(`Unknown tool: ${toolName}`);
195
+ }
196
+ // Claude Code 使用专门的管理器
197
+ if (toolName === 'claude-code') {
198
+ if (config.settings) {
199
+ claudeCodeManager.saveSettings(config.settings);
200
+ }
201
+ return;
202
+ }
203
+ try {
204
+ // 使用 dirname 获取目录路径,确保跨平台兼容(Windows/macOS/Linux)
205
+ const configDir = dirname(tool.configPath);
206
+ if (!existsSync(configDir)) {
207
+ mkdirSync(configDir, { recursive: true });
208
+ }
209
+ writeFileSync(tool.configPath, JSON.stringify(config, null, 2), 'utf-8');
210
+ }
211
+ catch (error) {
212
+ throw new Error(`Failed to update config for ${toolName}: ${error}`);
213
+ }
214
+ }
215
+ loadModelConfig(toolName, config) {
216
+ const tool = SUPPORTED_TOOLS[toolName];
217
+ if (!tool) {
218
+ throw new Error(`Unknown tool: ${toolName}`);
219
+ }
220
+ // Claude Code 使用专门的管理器
221
+ if (toolName === 'claude-code') {
222
+ claudeCodeManager.saveModelConfig(config);
223
+ return;
224
+ }
225
+ // 其他工具的配置
226
+ let existingConfig = this.getToolConfig(toolName) || {};
227
+ existingConfig = {
228
+ ...existingConfig,
229
+ apiKey: config.apiKey,
230
+ haikuModel: config.haikuModel,
231
+ sonnetModel: config.sonnetModel,
232
+ opusModel: config.opusModel
233
+ };
234
+ this.updateToolConfig(toolName, existingConfig);
235
+ }
236
+ getInstalledTools() {
237
+ return Object.keys(SUPPORTED_TOOLS).filter(toolName => this.isToolInstalled(toolName));
238
+ }
239
+ getSupportedTools() {
240
+ return Object.values(SUPPORTED_TOOLS).filter(tool => !tool.hidden);
241
+ }
242
+ isGitInstalled() {
243
+ try {
244
+ // Windows 使用 where 命令,Unix 系统使用 which 命令
245
+ const checkCommand = process.platform === 'win32'
246
+ ? `where git`
247
+ : `which git`;
248
+ execSync(checkCommand, { stdio: 'pipe' });
249
+ return true;
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ }
255
+ }
256
+ export const toolManager = ToolManager.getInstance();
@@ -0,0 +1,44 @@
1
+ export declare class Wizard {
2
+ static instance: any;
3
+ BOX_WIDTH: number;
4
+ private cachedModels;
5
+ constructor();
6
+ static getInstance(): any;
7
+ /**
8
+ * Create a simple box with title using double-line border style
9
+ */
10
+ createBox(title: any): void;
11
+ /**
12
+ * Display operation hints
13
+ */
14
+ showOperationHints(): void;
15
+ /**
16
+ * Prompt wrapper that shows operation hints
17
+ */
18
+ promptWithHints(questions: any): Promise<import("inquirer").Answers>;
19
+ printBanner(): void;
20
+ resetScreen(): void;
21
+ runFirstTimeSetup(): Promise<void>;
22
+ configLanguage(): Promise<void>;
23
+ configApiKey(): Promise<void>;
24
+ /**
25
+ * Fetch and cache available models
26
+ */
27
+ fetchModels(): Promise<string[]>;
28
+ /**
29
+ * Configure models menu
30
+ */
31
+ configModels(): Promise<void>;
32
+ /**
33
+ * Step-by-step model selection
34
+ */
35
+ selectModels(): Promise<void>;
36
+ selectAndConfigureTool(): Promise<void>;
37
+ configureTool(toolName: any): Promise<void>;
38
+ showMainMenu(): Promise<void>;
39
+ showToolMenu(toolName: any): Promise<void>;
40
+ startTool(toolName: any): Promise<void>;
41
+ loadModelConfig(toolName: any): Promise<void>;
42
+ unloadModelConfig(toolName: any): Promise<void>;
43
+ }
44
+ export declare const wizard: any;