@x-all-in-one/coding-helper 0.0.2 → 0.0.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/README.md +0 -2
- package/dist/commands/auth.js +10 -10
- package/dist/commands/config.js +17 -17
- package/dist/commands/doctor.js +14 -14
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +2 -2
- package/dist/commands/lang.js +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/lib/api-validator.js +7 -7
- package/dist/lib/claude-code-manager.d.ts +2 -0
- package/dist/lib/claude-code-manager.js +15 -14
- package/dist/lib/command.js +7 -7
- package/dist/lib/config.d.ts +10 -0
- package/dist/lib/config.js +14 -5
- package/dist/lib/i18n.js +4 -4
- package/dist/lib/opencode-manager.d.ts +93 -0
- package/dist/lib/opencode-manager.js +238 -0
- package/dist/lib/tool-manager.d.ts +2 -2
- package/dist/lib/tool-manager.js +65 -43
- package/dist/lib/wizard.d.ts +5 -1
- package/dist/lib/wizard.js +318 -167
- package/dist/locales/en_US.json +7 -0
- package/dist/locales/zh_CN.json +7 -0
- package/dist/utils/string-width.js +27 -27
- package/package.json +17 -16
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
// X-AIO Provider ID
|
|
5
|
+
const XAIO_PROVIDER_ID = 'xaio';
|
|
6
|
+
// Default configuration
|
|
7
|
+
export const OPENCODE_DEFAULT_CONFIG = {
|
|
8
|
+
BASE_URL: 'https://code-api.x-aio.com/v1',
|
|
9
|
+
PROVIDER_NAME: 'Coding Plan By X-AIO',
|
|
10
|
+
DEFAULT_MODEL: 'MiniMax-M2',
|
|
11
|
+
DEFAULT_SMALL_MODEL: 'Qwen3-Coder-30B-A3B-Instruct',
|
|
12
|
+
// Default limits (hardcoded for v1, will be fetched from API in future)
|
|
13
|
+
DEFAULT_CONTEXT: 128000,
|
|
14
|
+
DEFAULT_OUTPUT: 64000,
|
|
15
|
+
// Vision model limits
|
|
16
|
+
VISION_CONTEXT: 64000,
|
|
17
|
+
VISION_OUTPUT: 32000,
|
|
18
|
+
};
|
|
19
|
+
export class OpenCodeManager {
|
|
20
|
+
static instance;
|
|
21
|
+
configPath;
|
|
22
|
+
cachedModels = [];
|
|
23
|
+
constructor() {
|
|
24
|
+
// OpenCode config file paths (cross-platform support)
|
|
25
|
+
// - macOS/Linux: ~/.config/opencode/opencode.json
|
|
26
|
+
// - Windows: %USERPROFILE%\.config\opencode\opencode.json
|
|
27
|
+
this.configPath = join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
28
|
+
}
|
|
29
|
+
static getInstance() {
|
|
30
|
+
if (!OpenCodeManager.instance) {
|
|
31
|
+
OpenCodeManager.instance = new OpenCodeManager();
|
|
32
|
+
}
|
|
33
|
+
return OpenCodeManager.instance;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Ensure config directory exists
|
|
37
|
+
*/
|
|
38
|
+
ensureDir(filePath) {
|
|
39
|
+
const dir = dirname(filePath);
|
|
40
|
+
if (!existsSync(dir)) {
|
|
41
|
+
mkdirSync(dir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Read opencode.json config
|
|
46
|
+
*/
|
|
47
|
+
getConfig() {
|
|
48
|
+
try {
|
|
49
|
+
if (existsSync(this.configPath)) {
|
|
50
|
+
const content = readFileSync(this.configPath, 'utf-8');
|
|
51
|
+
return JSON.parse(content);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.warn('Failed to read OpenCode config:', error);
|
|
56
|
+
}
|
|
57
|
+
return {};
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Save opencode.json config
|
|
61
|
+
*/
|
|
62
|
+
saveConfig(config) {
|
|
63
|
+
try {
|
|
64
|
+
this.ensureDir(this.configPath);
|
|
65
|
+
writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
throw new Error(`Failed to save OpenCode config: ${error}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Fetch available models from API
|
|
73
|
+
*/
|
|
74
|
+
async fetchAvailableModels(apiKey) {
|
|
75
|
+
try {
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
78
|
+
const response = await fetch(`${OPENCODE_DEFAULT_CONFIG.BASE_URL}/models`, {
|
|
79
|
+
method: 'GET',
|
|
80
|
+
headers: {
|
|
81
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
82
|
+
'Content-Type': 'application/json',
|
|
83
|
+
},
|
|
84
|
+
signal: controller.signal,
|
|
85
|
+
});
|
|
86
|
+
clearTimeout(timeoutId);
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
if (data && data.data && Array.isArray(data.data)) {
|
|
92
|
+
this.cachedModels = data.data.map((model) => ({
|
|
93
|
+
id: model.id,
|
|
94
|
+
name: model.name || model.id,
|
|
95
|
+
context_length: model.context_length,
|
|
96
|
+
max_output_tokens: model.max_output_tokens,
|
|
97
|
+
}));
|
|
98
|
+
return this.cachedModels;
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
console.warn('Failed to fetch available models:', error);
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if model is a vision model (contains "VL" or ends with "V")
|
|
109
|
+
*/
|
|
110
|
+
isVisionModel(modelId) {
|
|
111
|
+
const upperCaseId = modelId.toUpperCase();
|
|
112
|
+
return upperCaseId.includes('VL') || upperCaseId.endsWith('V');
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Build models object from fetched model list
|
|
116
|
+
*/
|
|
117
|
+
buildModelsConfig(modelInfos) {
|
|
118
|
+
const models = {};
|
|
119
|
+
for (const info of modelInfos) {
|
|
120
|
+
const entry = {};
|
|
121
|
+
const isVision = this.isVisionModel(info.id);
|
|
122
|
+
// Set display name
|
|
123
|
+
if (info.name && info.name !== info.id) {
|
|
124
|
+
entry.name = info.name;
|
|
125
|
+
}
|
|
126
|
+
// Set limits (use API values if available, otherwise use defaults)
|
|
127
|
+
// TODO: In future versions, prioritize context_length/max_output_tokens from API
|
|
128
|
+
if (isVision) {
|
|
129
|
+
entry.limit = {
|
|
130
|
+
context: info.context_length || OPENCODE_DEFAULT_CONFIG.VISION_CONTEXT,
|
|
131
|
+
output: info.max_output_tokens || OPENCODE_DEFAULT_CONFIG.VISION_OUTPUT,
|
|
132
|
+
};
|
|
133
|
+
// Add modalities for vision models
|
|
134
|
+
entry.modalities = {
|
|
135
|
+
input: ['text', 'image', 'video'],
|
|
136
|
+
output: ['text'],
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
entry.limit = {
|
|
141
|
+
context: info.context_length || OPENCODE_DEFAULT_CONFIG.DEFAULT_CONTEXT,
|
|
142
|
+
output: info.max_output_tokens || OPENCODE_DEFAULT_CONFIG.DEFAULT_OUTPUT,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
models[info.id] = entry;
|
|
146
|
+
}
|
|
147
|
+
return models;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Save model config to OpenCode (merge, not overwrite)
|
|
151
|
+
*/
|
|
152
|
+
async saveModelConfig(config) {
|
|
153
|
+
// Fetch all available models from API
|
|
154
|
+
let modelInfos = this.cachedModels;
|
|
155
|
+
if (modelInfos.length === 0) {
|
|
156
|
+
modelInfos = await this.fetchAvailableModels(config.apiKey);
|
|
157
|
+
}
|
|
158
|
+
// Build models config from API response
|
|
159
|
+
const modelsConfig = this.buildModelsConfig(modelInfos);
|
|
160
|
+
// If no models fetched, at least include the selected ones
|
|
161
|
+
if (Object.keys(modelsConfig).length === 0) {
|
|
162
|
+
modelsConfig[config.model] = {};
|
|
163
|
+
if (config.smallModel && config.smallModel !== config.model) {
|
|
164
|
+
modelsConfig[config.smallModel] = {};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Read current config (preserve user's other settings)
|
|
168
|
+
const currentConfig = this.getConfig();
|
|
169
|
+
// Build xaio provider config
|
|
170
|
+
const xaioProvider = {
|
|
171
|
+
npm: '@ai-sdk/openai-compatible',
|
|
172
|
+
name: OPENCODE_DEFAULT_CONFIG.PROVIDER_NAME,
|
|
173
|
+
options: {
|
|
174
|
+
baseURL: config.baseUrl || OPENCODE_DEFAULT_CONFIG.BASE_URL,
|
|
175
|
+
apiKey: config.apiKey,
|
|
176
|
+
},
|
|
177
|
+
models: modelsConfig,
|
|
178
|
+
};
|
|
179
|
+
// Merge config: only update xaio provider, preserve other providers
|
|
180
|
+
const newConfig = {
|
|
181
|
+
...currentConfig,
|
|
182
|
+
$schema: 'https://opencode.ai/config.json',
|
|
183
|
+
model: `${XAIO_PROVIDER_ID}/${config.model}`,
|
|
184
|
+
small_model: `${XAIO_PROVIDER_ID}/${config.smallModel}`,
|
|
185
|
+
provider: {
|
|
186
|
+
...currentConfig.provider,
|
|
187
|
+
[XAIO_PROVIDER_ID]: xaioProvider,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
this.saveConfig(newConfig);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Get current model config
|
|
194
|
+
*/
|
|
195
|
+
getModelConfig() {
|
|
196
|
+
const config = this.getConfig();
|
|
197
|
+
// Check if xaio provider exists
|
|
198
|
+
const xaioProvider = config.provider?.[XAIO_PROVIDER_ID];
|
|
199
|
+
if (!xaioProvider) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const apiKey = xaioProvider.options?.apiKey;
|
|
203
|
+
if (!apiKey) {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
// Extract model names from "provider/model" format
|
|
207
|
+
const model = config.model?.replace(`${XAIO_PROVIDER_ID}/`, '') || '';
|
|
208
|
+
const smallModel = config.small_model?.replace(`${XAIO_PROVIDER_ID}/`, '') || '';
|
|
209
|
+
return {
|
|
210
|
+
apiKey,
|
|
211
|
+
model,
|
|
212
|
+
smallModel,
|
|
213
|
+
baseUrl: xaioProvider.options?.baseURL || '',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Clear model config (only remove xaio provider)
|
|
218
|
+
*/
|
|
219
|
+
clearModelConfig() {
|
|
220
|
+
const currentConfig = this.getConfig();
|
|
221
|
+
// Remove xaio provider from config
|
|
222
|
+
if (currentConfig.provider) {
|
|
223
|
+
delete currentConfig.provider[XAIO_PROVIDER_ID];
|
|
224
|
+
if (Object.keys(currentConfig.provider).length === 0) {
|
|
225
|
+
delete currentConfig.provider;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Clear model settings if they use xaio
|
|
229
|
+
if (currentConfig.model?.startsWith(`${XAIO_PROVIDER_ID}/`)) {
|
|
230
|
+
delete currentConfig.model;
|
|
231
|
+
}
|
|
232
|
+
if (currentConfig.small_model?.startsWith(`${XAIO_PROVIDER_ID}/`)) {
|
|
233
|
+
delete currentConfig.small_model;
|
|
234
|
+
}
|
|
235
|
+
this.saveConfig(currentConfig);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
export const openCodeManager = OpenCodeManager.getInstance();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ModelConfig } from './claude-code-manager.js';
|
|
1
|
+
import type { ModelConfig } from './claude-code-manager.js';
|
|
2
2
|
export interface ToolInfo {
|
|
3
3
|
name: string;
|
|
4
4
|
command: string;
|
|
@@ -16,7 +16,7 @@ export declare class ToolManager {
|
|
|
16
16
|
installTool(toolName: string): Promise<void>;
|
|
17
17
|
getToolConfig(toolName: string): any;
|
|
18
18
|
updateToolConfig(toolName: string, config: any): void;
|
|
19
|
-
loadModelConfig(toolName: string, config: ModelConfig): void
|
|
19
|
+
loadModelConfig(toolName: string, config: ModelConfig): Promise<void>;
|
|
20
20
|
getInstalledTools(): string[];
|
|
21
21
|
getSupportedTools(): ToolInfo[];
|
|
22
22
|
isGitInstalled(): boolean;
|
package/dist/lib/tool-manager.js
CHANGED
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { claudeCodeManager } from './claude-code-manager.js';
|
|
6
|
-
import { i18n } from './i18n.js';
|
|
7
|
-
import inquirer from 'inquirer';
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
8
5
|
import chalk from 'chalk';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import ora from 'ora';
|
|
9
8
|
import terminalLink from 'terminal-link';
|
|
10
|
-
import
|
|
9
|
+
import { claudeCodeManager } from './claude-code-manager.js';
|
|
10
|
+
import { i18n } from './i18n.js';
|
|
11
|
+
import { openCodeManager } from './opencode-manager.js';
|
|
11
12
|
export const SUPPORTED_TOOLS = {
|
|
12
13
|
'claude-code': {
|
|
13
14
|
name: 'claude-code',
|
|
14
15
|
command: 'claude',
|
|
15
16
|
installCommand: 'npm install -g @anthropic-ai/claude-code',
|
|
16
17
|
configPath: join(homedir(), '.claude', 'settings.json'),
|
|
17
|
-
displayName: 'Claude Code'
|
|
18
|
+
displayName: 'Claude Code',
|
|
18
19
|
},
|
|
19
20
|
'opencode': {
|
|
20
21
|
name: 'opencode',
|
|
21
22
|
command: 'opencode',
|
|
22
23
|
installCommand: 'npm install -g opencode',
|
|
23
|
-
configPath: join(homedir(), '.config', 'opencode', '
|
|
24
|
+
configPath: join(homedir(), '.config', 'opencode', 'opencode.json'),
|
|
24
25
|
displayName: 'OpenCode',
|
|
25
|
-
|
|
26
|
-
}
|
|
26
|
+
},
|
|
27
27
|
};
|
|
28
28
|
export class ToolManager {
|
|
29
29
|
static instance;
|
|
@@ -66,41 +66,41 @@ export class ToolManager {
|
|
|
66
66
|
// 检查是否是权限错误 (EACCES)
|
|
67
67
|
// execSync 的错误信息可能在 stderr 中,需要检查多个来源
|
|
68
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 权限错误的退出码
|
|
69
|
+
const isPermissionError = errorMessage.includes('EACCES')
|
|
70
|
+
|| errorMessage.includes('permission denied')
|
|
71
|
+
|| errorMessage.includes('EPERM')
|
|
72
|
+
|| error.status === 243; // npm 权限错误的退出码
|
|
73
73
|
if (!isPermissionError) {
|
|
74
74
|
// 如果不是权限错误,直接抛出
|
|
75
75
|
throw new Error(`Failed to install ${tool.displayName}: ${error}`);
|
|
76
76
|
}
|
|
77
|
-
console.log(
|
|
77
|
+
console.log(`\n⚠️ ${i18n.t('install.permission_detected')}\n`);
|
|
78
78
|
// Windows 平台处理
|
|
79
79
|
if (process.platform === 'win32') {
|
|
80
80
|
try {
|
|
81
81
|
// Windows: 尝试使用用户级安装(不需要管理员权限)
|
|
82
82
|
const userInstallCommand = tool.installCommand.replace('npm install -g', 'npm install -g --force');
|
|
83
|
-
console.log(
|
|
83
|
+
console.log(`🔧 ${i18n.t('install.trying_solution', { num: '1', desc: i18n.t('install.using_force') })}`);
|
|
84
84
|
execSync(userInstallCommand, { stdio: 'inherit' });
|
|
85
|
-
console.log(
|
|
85
|
+
console.log(`\n✅ ${i18n.t('install.permission_fixed')}`);
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
88
88
|
catch (retryError) {
|
|
89
89
|
// 如果还是失败,显示解决方案并询问用户
|
|
90
90
|
console.log(`Retry install error ${retryError}`);
|
|
91
|
-
console.log(
|
|
92
|
-
console.log(chalk.yellow(
|
|
91
|
+
console.log(`\n❌ ${i18n.t('install.auto_fix_failed')}`);
|
|
92
|
+
console.log(chalk.yellow(`\n📌 ${i18n.t('install.windows_solutions')}`));
|
|
93
93
|
console.log('');
|
|
94
94
|
// 方案 1: 以管理员身份运行
|
|
95
95
|
console.log(chalk.cyan.bold(i18n.t('install.windows_solution_1_title')));
|
|
96
|
-
console.log(chalk.gray(
|
|
97
|
-
console.log(chalk.gray(
|
|
98
|
-
console.log(chalk.gray(
|
|
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
99
|
console.log(chalk.white(` ${tool.installCommand}`));
|
|
100
100
|
console.log('');
|
|
101
101
|
// 方案 2: 用户级安装
|
|
102
102
|
console.log(chalk.cyan.bold(i18n.t('install.windows_solution_2_title')));
|
|
103
|
-
console.log(chalk.gray(
|
|
103
|
+
console.log(chalk.gray(` ${i18n.t('install.windows_solution_2_command')}`));
|
|
104
104
|
console.log(chalk.white(` ${tool.installCommand.replace('npm install -g', 'npm install -g --prefix=%APPDATA%\\npm')}`));
|
|
105
105
|
console.log('');
|
|
106
106
|
// 询问用户是否已完成安装
|
|
@@ -111,36 +111,36 @@ export class ToolManager {
|
|
|
111
111
|
message: i18n.t('install.what_next'),
|
|
112
112
|
choices: [
|
|
113
113
|
{ name: i18n.t('install.installed_continue'), value: 'continue' },
|
|
114
|
-
{ name: i18n.t('install.cancel_install'), value: 'cancel' }
|
|
115
|
-
]
|
|
116
|
-
}
|
|
114
|
+
{ name: i18n.t('install.cancel_install'), value: 'cancel' },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
117
|
]);
|
|
118
118
|
if (action === 'cancel') {
|
|
119
119
|
throw new Error(i18n.t('install.user_cancelled'));
|
|
120
120
|
}
|
|
121
121
|
// 验证是否真的安装成功
|
|
122
122
|
if (!this.isToolInstalled(toolName)) {
|
|
123
|
-
console.log(chalk.red(
|
|
123
|
+
console.log(chalk.red(`\n❌ ${i18n.t('install.still_not_installed', { tool: tool.displayName })}`));
|
|
124
124
|
throw new Error(`${tool.displayName} is not installed`);
|
|
125
125
|
}
|
|
126
|
-
console.log(chalk.green(
|
|
126
|
+
console.log(chalk.green(`\n✅ ${i18n.t('install.verified_success', { tool: tool.displayName })}`));
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
// macOS 和 Linux 平台处理 - 显示建议而不是自动执行
|
|
131
|
-
console.log(chalk.yellow(
|
|
131
|
+
console.log(chalk.yellow(`\n📌 ${i18n.t('install.unix_solutions')}`));
|
|
132
132
|
console.log('');
|
|
133
133
|
// 方案 1: 使用 sudo
|
|
134
134
|
console.log(chalk.cyan.bold(i18n.t('install.unix_solution_1_title')));
|
|
135
|
-
console.log(chalk.gray(
|
|
135
|
+
console.log(chalk.gray(` ${i18n.t('install.unix_solution_1_desc')}`));
|
|
136
136
|
console.log(chalk.white(` sudo ${tool.installCommand}`));
|
|
137
137
|
console.log('');
|
|
138
138
|
// 方案 2: 使用 nvm (推荐)
|
|
139
139
|
const npmDocsUrl = 'https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally';
|
|
140
140
|
const clickableLink = terminalLink(npmDocsUrl, npmDocsUrl, { fallback: () => npmDocsUrl });
|
|
141
141
|
console.log(chalk.cyan.bold(i18n.t('install.unix_solution_2_title')));
|
|
142
|
-
console.log(chalk.gray(
|
|
143
|
-
console.log(chalk.blue(
|
|
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
144
|
console.log('');
|
|
145
145
|
// 询问用户是否已完成安装
|
|
146
146
|
const { action } = await inquirer.prompt([
|
|
@@ -150,20 +150,19 @@ export class ToolManager {
|
|
|
150
150
|
message: i18n.t('install.what_next'),
|
|
151
151
|
choices: [
|
|
152
152
|
{ name: i18n.t('install.installed_continue'), value: 'continue' },
|
|
153
|
-
{ name: i18n.t('install.cancel_install'), value: 'cancel' }
|
|
154
|
-
]
|
|
155
|
-
}
|
|
153
|
+
{ name: i18n.t('install.cancel_install'), value: 'cancel' },
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
156
|
]);
|
|
157
157
|
if (action === 'cancel') {
|
|
158
158
|
throw new Error(i18n.t('install.user_cancelled'));
|
|
159
159
|
}
|
|
160
160
|
// 验证是否真的安装成功
|
|
161
161
|
if (!this.isToolInstalled(toolName)) {
|
|
162
|
-
console.log(chalk.red(
|
|
162
|
+
console.log(chalk.red(`\n❌ ${i18n.t('install.still_not_installed', { tool: tool.displayName })}`));
|
|
163
163
|
throw new Error(`${tool.displayName} is not installed`);
|
|
164
164
|
}
|
|
165
|
-
console.log(chalk.green(
|
|
166
|
-
return;
|
|
165
|
+
console.log(chalk.green(`\n✅ ${i18n.t('install.verified_success', { tool: tool.displayName })}`));
|
|
167
166
|
}
|
|
168
167
|
}
|
|
169
168
|
getToolConfig(toolName) {
|
|
@@ -174,7 +173,13 @@ export class ToolManager {
|
|
|
174
173
|
// Claude Code 使用专门的管理器
|
|
175
174
|
if (toolName === 'claude-code') {
|
|
176
175
|
return {
|
|
177
|
-
settings: claudeCodeManager.getSettings()
|
|
176
|
+
settings: claudeCodeManager.getSettings(),
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
// OpenCode 使用专门的管理器
|
|
180
|
+
if (toolName === 'opencode') {
|
|
181
|
+
return {
|
|
182
|
+
config: openCodeManager.getConfig(),
|
|
178
183
|
};
|
|
179
184
|
}
|
|
180
185
|
try {
|
|
@@ -200,6 +205,13 @@ export class ToolManager {
|
|
|
200
205
|
}
|
|
201
206
|
return;
|
|
202
207
|
}
|
|
208
|
+
// OpenCode 使用专门的管理器
|
|
209
|
+
if (toolName === 'opencode') {
|
|
210
|
+
if (config.config) {
|
|
211
|
+
openCodeManager.saveConfig(config.config);
|
|
212
|
+
}
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
203
215
|
try {
|
|
204
216
|
// 使用 dirname 获取目录路径,确保跨平台兼容(Windows/macOS/Linux)
|
|
205
217
|
const configDir = dirname(tool.configPath);
|
|
@@ -212,7 +224,7 @@ export class ToolManager {
|
|
|
212
224
|
throw new Error(`Failed to update config for ${toolName}: ${error}`);
|
|
213
225
|
}
|
|
214
226
|
}
|
|
215
|
-
loadModelConfig(toolName, config) {
|
|
227
|
+
async loadModelConfig(toolName, config) {
|
|
216
228
|
const tool = SUPPORTED_TOOLS[toolName];
|
|
217
229
|
if (!tool) {
|
|
218
230
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
@@ -222,6 +234,16 @@ export class ToolManager {
|
|
|
222
234
|
claudeCodeManager.saveModelConfig(config);
|
|
223
235
|
return;
|
|
224
236
|
}
|
|
237
|
+
// OpenCode 使用专门的管理器和独立的配置键
|
|
238
|
+
if (toolName === 'opencode') {
|
|
239
|
+
const openCodeConfig = {
|
|
240
|
+
apiKey: config.apiKey,
|
|
241
|
+
model: config.openCodeModel || '',
|
|
242
|
+
smallModel: config.openCodeSmallModel || '',
|
|
243
|
+
};
|
|
244
|
+
await openCodeManager.saveModelConfig(openCodeConfig);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
225
247
|
// 其他工具的配置
|
|
226
248
|
let existingConfig = this.getToolConfig(toolName) || {};
|
|
227
249
|
existingConfig = {
|
|
@@ -229,7 +251,7 @@ export class ToolManager {
|
|
|
229
251
|
apiKey: config.apiKey,
|
|
230
252
|
haikuModel: config.haikuModel,
|
|
231
253
|
sonnetModel: config.sonnetModel,
|
|
232
|
-
opusModel: config.opusModel
|
|
254
|
+
opusModel: config.opusModel,
|
|
233
255
|
};
|
|
234
256
|
this.updateToolConfig(toolName, existingConfig);
|
|
235
257
|
}
|
package/dist/lib/wizard.d.ts
CHANGED
|
@@ -32,7 +32,11 @@ export declare class Wizard {
|
|
|
32
32
|
/**
|
|
33
33
|
* Step-by-step model selection with back navigation
|
|
34
34
|
*/
|
|
35
|
-
selectModels(): Promise<void>;
|
|
35
|
+
selectModels(toolName?: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* OpenCode 专用的模型选择流程(2 步:主模型 + 小模型)
|
|
38
|
+
*/
|
|
39
|
+
private selectModelsForOpenCode;
|
|
36
40
|
selectAndConfigureTool(): Promise<void>;
|
|
37
41
|
configureTool(toolName: any): Promise<void>;
|
|
38
42
|
showMainMenu(): Promise<void>;
|