@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.
- package/README.md +93 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +15 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +148 -0
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.js +133 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +128 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/lang.d.ts +1 -0
- package/dist/commands/lang.js +29 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/lib/api-validator.d.ts +16 -0
- package/dist/lib/api-validator.js +75 -0
- package/dist/lib/claude-code-manager.d.ts +79 -0
- package/dist/lib/claude-code-manager.js +201 -0
- package/dist/lib/command.d.ts +10 -0
- package/dist/lib/command.js +174 -0
- package/dist/lib/config.d.ts +37 -0
- package/dist/lib/config.js +92 -0
- package/dist/lib/i18n.d.ts +20 -0
- package/dist/lib/i18n.js +148 -0
- package/dist/lib/tool-manager.d.ts +24 -0
- package/dist/lib/tool-manager.js +256 -0
- package/dist/lib/wizard.d.ts +44 -0
- package/dist/lib/wizard.js +673 -0
- package/dist/locales/en_US.json +201 -0
- package/dist/locales/zh_CN.json +201 -0
- package/dist/utils/logger.d.ts +17 -0
- package/dist/utils/logger.js +41 -0
- package/dist/utils/string-width.d.ts +38 -0
- package/dist/utils/string-width.js +130 -0
- package/package.json +66 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key 和网络验证模块
|
|
3
|
+
* 通过调用模型列表 API 来验证 API Key 的有效性和网络连通性
|
|
4
|
+
*/
|
|
5
|
+
const VALIDATION_URL = 'https://code-api.x-aio.com/v1/models';
|
|
6
|
+
/**
|
|
7
|
+
* 验证 API Key 的有效性和网络连通性
|
|
8
|
+
*
|
|
9
|
+
* @param apiKey - API Key
|
|
10
|
+
* @returns 验证结果,包含 valid 状态和可选的错误信息
|
|
11
|
+
*/
|
|
12
|
+
export async function validateApiKey(apiKey) {
|
|
13
|
+
try {
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30 秒超时
|
|
16
|
+
const response = await fetch(VALIDATION_URL, {
|
|
17
|
+
method: 'GET',
|
|
18
|
+
headers: {
|
|
19
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
20
|
+
'Content-Type': 'application/json'
|
|
21
|
+
},
|
|
22
|
+
signal: controller.signal
|
|
23
|
+
});
|
|
24
|
+
clearTimeout(timeoutId);
|
|
25
|
+
if (response.status === 401) {
|
|
26
|
+
return {
|
|
27
|
+
valid: false,
|
|
28
|
+
error: 'invalid_api_key',
|
|
29
|
+
message: 'API Key is invalid or expired'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (response.ok) {
|
|
33
|
+
// 尝试解析响应以确保是有效的 JSON
|
|
34
|
+
try {
|
|
35
|
+
const data = await response.json();
|
|
36
|
+
if (data && data.object === 'list') {
|
|
37
|
+
return { valid: true };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// JSON 解析失败但响应成功,仍然视为有效
|
|
42
|
+
return { valid: true };
|
|
43
|
+
}
|
|
44
|
+
return { valid: true };
|
|
45
|
+
}
|
|
46
|
+
// 其他 HTTP 错误
|
|
47
|
+
return {
|
|
48
|
+
valid: false,
|
|
49
|
+
error: 'unknown_error',
|
|
50
|
+
message: `HTTP ${response.status}: ${response.statusText}`
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
// 网络错误或超时
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
if (error.name === 'AbortError') {
|
|
57
|
+
return {
|
|
58
|
+
valid: false,
|
|
59
|
+
error: 'network_error',
|
|
60
|
+
message: 'Request timeout'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
error: 'network_error',
|
|
66
|
+
message: error.message
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
valid: false,
|
|
71
|
+
error: 'network_error',
|
|
72
|
+
message: 'Network connection failed'
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
export declare const DEFAULT_CONFIG: {
|
|
2
|
+
ANTHROPIC_BASE_URL: string;
|
|
3
|
+
ANTHROPIC_API_KEY: string;
|
|
4
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: string;
|
|
5
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: string;
|
|
6
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: string;
|
|
7
|
+
};
|
|
8
|
+
export interface ModelConfig {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
haikuModel: string;
|
|
11
|
+
sonnetModel: string;
|
|
12
|
+
opusModel: string;
|
|
13
|
+
baseUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ClaudeCodeSettingsConfig {
|
|
16
|
+
env?: {
|
|
17
|
+
ANTHROPIC_API_KEY?: string;
|
|
18
|
+
ANTHROPIC_BASE_URL?: string;
|
|
19
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL?: string;
|
|
20
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL?: string;
|
|
21
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL?: string;
|
|
22
|
+
API_TIMEOUT_MS?: string;
|
|
23
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC?: number;
|
|
24
|
+
[key: string]: any;
|
|
25
|
+
};
|
|
26
|
+
[key: string]: any;
|
|
27
|
+
}
|
|
28
|
+
export interface ClaudeCodeOnboardingConfig {
|
|
29
|
+
hasCompletedOnboarding?: boolean;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
}
|
|
32
|
+
export declare class ClaudeCodeManager {
|
|
33
|
+
private static instance;
|
|
34
|
+
private settingsPath;
|
|
35
|
+
private onboardingConfigPath;
|
|
36
|
+
private constructor();
|
|
37
|
+
static getInstance(): ClaudeCodeManager;
|
|
38
|
+
/**
|
|
39
|
+
* 确保配置目录存在
|
|
40
|
+
*/
|
|
41
|
+
private ensureConfigDir;
|
|
42
|
+
/**
|
|
43
|
+
* 读取 settings.json 配置
|
|
44
|
+
*/
|
|
45
|
+
getSettings(): ClaudeCodeSettingsConfig;
|
|
46
|
+
/**
|
|
47
|
+
* 保存 settings.json 配置
|
|
48
|
+
*/
|
|
49
|
+
saveSettings(config: ClaudeCodeSettingsConfig): void;
|
|
50
|
+
/**
|
|
51
|
+
* 读取 onboarding 配置 (~/.claude.json)
|
|
52
|
+
*/
|
|
53
|
+
private getOnboardingConfig;
|
|
54
|
+
/**
|
|
55
|
+
* 保存 onboarding 配置 (~/.claude.json)
|
|
56
|
+
*/
|
|
57
|
+
private saveOnboardingConfig;
|
|
58
|
+
/**
|
|
59
|
+
* 确保 .claude.json 中有 hasCompletedOnboarding: true
|
|
60
|
+
*/
|
|
61
|
+
private ensureOnboardingCompleted;
|
|
62
|
+
/**
|
|
63
|
+
* 从 API 获取可用的模型列表
|
|
64
|
+
*/
|
|
65
|
+
fetchAvailableModels(apiKey: string): Promise<string[]>;
|
|
66
|
+
/**
|
|
67
|
+
* 保存模型配置到 Claude Code
|
|
68
|
+
*/
|
|
69
|
+
saveModelConfig(config: ModelConfig): void;
|
|
70
|
+
/**
|
|
71
|
+
* 获取当前模型配置
|
|
72
|
+
*/
|
|
73
|
+
getModelConfig(): ModelConfig | null;
|
|
74
|
+
/**
|
|
75
|
+
* 清除模型配置
|
|
76
|
+
*/
|
|
77
|
+
clearModelConfig(): void;
|
|
78
|
+
}
|
|
79
|
+
export declare const claudeCodeManager: ClaudeCodeManager;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
// 默认配置
|
|
5
|
+
export const DEFAULT_CONFIG = {
|
|
6
|
+
ANTHROPIC_BASE_URL: 'https://code-api.x-aio.com/anthropic',
|
|
7
|
+
ANTHROPIC_API_KEY: 'sk-xxx',
|
|
8
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: 'Qwen3-Coder-30B-A3B-Instruct',
|
|
9
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: 'MiniMax-M2',
|
|
10
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: 'Qwen3-Coder-480B-A35B-Instruct'
|
|
11
|
+
};
|
|
12
|
+
export class ClaudeCodeManager {
|
|
13
|
+
static instance;
|
|
14
|
+
settingsPath;
|
|
15
|
+
onboardingConfigPath;
|
|
16
|
+
constructor() {
|
|
17
|
+
// Claude Code 配置文件路径(跨平台支持)
|
|
18
|
+
// - macOS/Linux: ~/.claude/settings.json 和 ~/.claude.json
|
|
19
|
+
// - Windows: %USERPROFILE%\.claude\settings.json 和 %USERPROFILE%\.claude.json
|
|
20
|
+
// (例如: C:\Users\username\.claude\settings.json)
|
|
21
|
+
this.settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
22
|
+
this.onboardingConfigPath = join(homedir(), '.claude.json');
|
|
23
|
+
}
|
|
24
|
+
static getInstance() {
|
|
25
|
+
if (!ClaudeCodeManager.instance) {
|
|
26
|
+
ClaudeCodeManager.instance = new ClaudeCodeManager();
|
|
27
|
+
}
|
|
28
|
+
return ClaudeCodeManager.instance;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* 确保配置目录存在
|
|
32
|
+
*/
|
|
33
|
+
ensureConfigDir(filePath) {
|
|
34
|
+
const dir = dirname(filePath);
|
|
35
|
+
if (!existsSync(dir)) {
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* 读取 settings.json 配置
|
|
41
|
+
*/
|
|
42
|
+
getSettings() {
|
|
43
|
+
try {
|
|
44
|
+
if (existsSync(this.settingsPath)) {
|
|
45
|
+
const content = readFileSync(this.settingsPath, 'utf-8');
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn('Failed to read Claude Code settings:', error);
|
|
51
|
+
}
|
|
52
|
+
return {};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 保存 settings.json 配置
|
|
56
|
+
*/
|
|
57
|
+
saveSettings(config) {
|
|
58
|
+
try {
|
|
59
|
+
this.ensureConfigDir(this.settingsPath);
|
|
60
|
+
writeFileSync(this.settingsPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(`Failed to save Claude Code settings: ${error}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 读取 onboarding 配置 (~/.claude.json)
|
|
68
|
+
*/
|
|
69
|
+
getOnboardingConfig() {
|
|
70
|
+
try {
|
|
71
|
+
if (existsSync(this.onboardingConfigPath)) {
|
|
72
|
+
const content = readFileSync(this.onboardingConfigPath, 'utf-8');
|
|
73
|
+
return JSON.parse(content);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.warn('Failed to read Claude Code onboarding config:', error);
|
|
78
|
+
}
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 保存 onboarding 配置 (~/.claude.json)
|
|
83
|
+
*/
|
|
84
|
+
saveOnboardingConfig(config) {
|
|
85
|
+
try {
|
|
86
|
+
this.ensureConfigDir(this.onboardingConfigPath);
|
|
87
|
+
writeFileSync(this.onboardingConfigPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new Error(`Failed to save Claude Code onboarding config: ${error}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* 确保 .claude.json 中有 hasCompletedOnboarding: true
|
|
95
|
+
*/
|
|
96
|
+
ensureOnboardingCompleted() {
|
|
97
|
+
try {
|
|
98
|
+
const config = this.getOnboardingConfig();
|
|
99
|
+
if (!config.hasCompletedOnboarding) {
|
|
100
|
+
this.saveOnboardingConfig({ ...config, hasCompletedOnboarding: true });
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.warn('Failed to ensure onboarding completed:', error);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 从 API 获取可用的模型列表
|
|
109
|
+
*/
|
|
110
|
+
async fetchAvailableModels(apiKey) {
|
|
111
|
+
try {
|
|
112
|
+
const controller = new AbortController();
|
|
113
|
+
const timeoutId = setTimeout(() => controller.abort(), 30000);
|
|
114
|
+
const response = await fetch('https://code-api.x-aio.com/v1/models', {
|
|
115
|
+
method: 'GET',
|
|
116
|
+
headers: {
|
|
117
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
118
|
+
'Content-Type': 'application/json'
|
|
119
|
+
},
|
|
120
|
+
signal: controller.signal
|
|
121
|
+
});
|
|
122
|
+
clearTimeout(timeoutId);
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
125
|
+
}
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
if (data && data.data && Array.isArray(data.data)) {
|
|
128
|
+
return data.data.map((model) => model.id);
|
|
129
|
+
}
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
console.warn('Failed to fetch available models:', error);
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 保存模型配置到 Claude Code
|
|
139
|
+
*/
|
|
140
|
+
saveModelConfig(config) {
|
|
141
|
+
// 确保 onboarding 已完成
|
|
142
|
+
this.ensureOnboardingCompleted();
|
|
143
|
+
const currentSettings = this.getSettings();
|
|
144
|
+
// 移除旧的 ANTHROPIC_AUTH_TOKEN(如果存在)
|
|
145
|
+
const currentEnv = currentSettings.env || {};
|
|
146
|
+
const { ANTHROPIC_AUTH_TOKEN: _, ...cleanedEnv } = currentEnv;
|
|
147
|
+
const newSettings = {
|
|
148
|
+
...currentSettings,
|
|
149
|
+
env: {
|
|
150
|
+
...cleanedEnv,
|
|
151
|
+
ANTHROPIC_BASE_URL: config.baseUrl || DEFAULT_CONFIG.ANTHROPIC_BASE_URL,
|
|
152
|
+
ANTHROPIC_API_KEY: config.apiKey,
|
|
153
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: config.haikuModel,
|
|
154
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: config.sonnetModel,
|
|
155
|
+
ANTHROPIC_DEFAULT_OPUS_MODEL: config.opusModel,
|
|
156
|
+
API_TIMEOUT_MS: '3000000',
|
|
157
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 1
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
this.saveSettings(newSettings);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 获取当前模型配置
|
|
164
|
+
*/
|
|
165
|
+
getModelConfig() {
|
|
166
|
+
const settings = this.getSettings();
|
|
167
|
+
if (!settings.env) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const apiKey = settings.env.ANTHROPIC_API_KEY;
|
|
171
|
+
if (!apiKey) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
apiKey,
|
|
176
|
+
haikuModel: settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL || "",
|
|
177
|
+
sonnetModel: settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL || "",
|
|
178
|
+
opusModel: settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL || "",
|
|
179
|
+
baseUrl: settings.env.ANTHROPIC_BASE_URL || ""
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* 清除模型配置
|
|
184
|
+
*/
|
|
185
|
+
clearModelConfig() {
|
|
186
|
+
const currentSettings = this.getSettings();
|
|
187
|
+
if (!currentSettings.env) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const { ANTHROPIC_BASE_URL: _1, ANTHROPIC_API_KEY: _2, ANTHROPIC_DEFAULT_HAIKU_MODEL: _3, ANTHROPIC_DEFAULT_SONNET_MODEL: _4, ANTHROPIC_DEFAULT_OPUS_MODEL: _5, API_TIMEOUT_MS: _6, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: _7, ...otherEnv } = currentSettings.env;
|
|
191
|
+
const newSettings = {
|
|
192
|
+
...currentSettings,
|
|
193
|
+
env: otherEnv
|
|
194
|
+
};
|
|
195
|
+
if (newSettings.env && Object.keys(newSettings.env).length === 0) {
|
|
196
|
+
delete newSettings.env;
|
|
197
|
+
}
|
|
198
|
+
this.saveSettings(newSettings);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
export const claudeCodeManager = ClaudeCodeManager.getInstance();
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command as Commander } from 'commander';
|
|
2
|
+
export declare class Command {
|
|
3
|
+
private program;
|
|
4
|
+
constructor();
|
|
5
|
+
private getVersion;
|
|
6
|
+
private setupProgram;
|
|
7
|
+
private handleInitCommand;
|
|
8
|
+
execute(args: string[]): Promise<void>;
|
|
9
|
+
getProgram(): Commander;
|
|
10
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Command as Commander } from 'commander';
|
|
2
|
+
import { i18n } from './i18n.js';
|
|
3
|
+
import { configManager } from './config.js';
|
|
4
|
+
import { wizard } from './wizard.js';
|
|
5
|
+
import { langCommand, authCommand, doctorCommand, configCommand } from '../commands/index.js';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
export class Command {
|
|
11
|
+
program;
|
|
12
|
+
constructor() {
|
|
13
|
+
// Load language from config
|
|
14
|
+
const lang = configManager.getLang();
|
|
15
|
+
i18n.loadFromConfig(lang);
|
|
16
|
+
this.program = new Commander();
|
|
17
|
+
this.setupProgram();
|
|
18
|
+
}
|
|
19
|
+
getVersion() {
|
|
20
|
+
try {
|
|
21
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
22
|
+
const __dirname = dirname(__filename);
|
|
23
|
+
const packagePath = join(__dirname, '../../package.json');
|
|
24
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
25
|
+
return packageJson.version;
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return '1.0.0';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
setupProgram() {
|
|
32
|
+
this.program
|
|
33
|
+
.name('chelper')
|
|
34
|
+
.description(i18n.t('cli.title'))
|
|
35
|
+
.version(this.getVersion(), '-v, --version', i18n.t('commands.version'))
|
|
36
|
+
.helpOption('-h, --help', i18n.t('commands.help'));
|
|
37
|
+
// Init command - interactive wizard
|
|
38
|
+
this.program
|
|
39
|
+
.command('init')
|
|
40
|
+
.description(i18n.t('commands.init'))
|
|
41
|
+
.action(async () => {
|
|
42
|
+
await this.handleInitCommand();
|
|
43
|
+
});
|
|
44
|
+
// Lang command - language management
|
|
45
|
+
const langCmd = this.program
|
|
46
|
+
.command('lang')
|
|
47
|
+
.description(i18n.t('commands.lang'));
|
|
48
|
+
langCmd
|
|
49
|
+
.command('show')
|
|
50
|
+
.description(i18n.t('lang.show_usage'))
|
|
51
|
+
.action(async () => {
|
|
52
|
+
await langCommand(['show']);
|
|
53
|
+
});
|
|
54
|
+
langCmd
|
|
55
|
+
.command('set <locale>')
|
|
56
|
+
.description(i18n.t('lang.set_usage'))
|
|
57
|
+
.action(async (locale) => {
|
|
58
|
+
await langCommand(['set', locale]);
|
|
59
|
+
});
|
|
60
|
+
// Auth command - API key management
|
|
61
|
+
const authCmd = this.program
|
|
62
|
+
.command('auth')
|
|
63
|
+
.description(i18n.t('commands.auth'));
|
|
64
|
+
authCmd
|
|
65
|
+
.argument('[token]', 'API token')
|
|
66
|
+
.action(async (token) => {
|
|
67
|
+
const args = [];
|
|
68
|
+
if (token)
|
|
69
|
+
args.push(token);
|
|
70
|
+
await authCommand(args);
|
|
71
|
+
});
|
|
72
|
+
authCmd
|
|
73
|
+
.command('revoke')
|
|
74
|
+
.description('Revoke saved API key')
|
|
75
|
+
.action(async () => {
|
|
76
|
+
await authCommand(['revoke']);
|
|
77
|
+
});
|
|
78
|
+
authCmd
|
|
79
|
+
.command('reload <tool>')
|
|
80
|
+
.description('Reload plan configuration to the specified tool (e.g., claude)')
|
|
81
|
+
.action(async (tool) => {
|
|
82
|
+
await authCommand(['reload', tool]);
|
|
83
|
+
});
|
|
84
|
+
// Doctor command - health check
|
|
85
|
+
this.program
|
|
86
|
+
.command('doctor')
|
|
87
|
+
.description(i18n.t('commands.doctor'))
|
|
88
|
+
.action(async () => {
|
|
89
|
+
await doctorCommand();
|
|
90
|
+
});
|
|
91
|
+
// Config command - tool configuration
|
|
92
|
+
const enterCmd = this.program
|
|
93
|
+
.command('enter [option]')
|
|
94
|
+
.description(i18n.t('commands.enter'));
|
|
95
|
+
// config 子命令
|
|
96
|
+
enterCmd
|
|
97
|
+
.action(async (option) => {
|
|
98
|
+
// 如果没有参数,显示主菜单
|
|
99
|
+
if (!option) {
|
|
100
|
+
await wizard.showMainMenu();
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
// 根据参数执行对应操作
|
|
104
|
+
switch (option) {
|
|
105
|
+
case 'lang':
|
|
106
|
+
case 'language':
|
|
107
|
+
await wizard.configLanguage();
|
|
108
|
+
break;
|
|
109
|
+
case 'models':
|
|
110
|
+
await wizard.configModels();
|
|
111
|
+
break;
|
|
112
|
+
case 'apikey':
|
|
113
|
+
case 'api-key':
|
|
114
|
+
await wizard.configApiKey();
|
|
115
|
+
break;
|
|
116
|
+
default: {
|
|
117
|
+
// 尝试作为工具名处理
|
|
118
|
+
const args = [option];
|
|
119
|
+
await configCommand(args);
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
this.program.action(async () => {
|
|
125
|
+
if (configManager.isFirstRun()) {
|
|
126
|
+
console.log(chalk.cyan(i18n.t('messages.first_run')));
|
|
127
|
+
await wizard.runFirstTimeSetup();
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
await wizard.showMainMenu();
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Custom help
|
|
134
|
+
this.program.configureHelp({
|
|
135
|
+
sortSubcommands: true,
|
|
136
|
+
subcommandTerm: (cmd) => cmd.name() + ' ' + cmd.usage()
|
|
137
|
+
});
|
|
138
|
+
// Add examples to help
|
|
139
|
+
this.program.addHelpText('after', `
|
|
140
|
+
${chalk.bold(i18n.t('cli.examples'))}:
|
|
141
|
+
${chalk.gray('$ chelper # Interactive main menu')}
|
|
142
|
+
${chalk.gray('$ chelper init # Run first-time setup wizard')}
|
|
143
|
+
${chalk.gray('$ chelper enter # Interactive main menu')}
|
|
144
|
+
${chalk.gray('$ chelper enter lang # Interactive language configuration')}
|
|
145
|
+
${chalk.gray('$ chelper enter models # Interactive models configuration')}
|
|
146
|
+
${chalk.gray('$ chelper enter apikey # Interactive API key configuration')}
|
|
147
|
+
${chalk.gray('$ chelper enter claude-code # Interactive Configure Claude Code tool')}
|
|
148
|
+
${chalk.gray('$ chelper lang show # Show current language')}
|
|
149
|
+
${chalk.gray('$ chelper lang set zh_CN')}
|
|
150
|
+
${chalk.gray('$ chelper auth # Interactive auth setup')}
|
|
151
|
+
${chalk.gray('$ chelper auth <token> # Set API key directly')}
|
|
152
|
+
${chalk.gray('$ chelper auth revoke')}
|
|
153
|
+
${chalk.gray('$ chelper auth reload claude # Reload config to Claude Code')}
|
|
154
|
+
${chalk.gray('$ chelper doctor # Health check')}
|
|
155
|
+
`);
|
|
156
|
+
}
|
|
157
|
+
async handleInitCommand() {
|
|
158
|
+
await wizard.runFirstTimeSetup();
|
|
159
|
+
}
|
|
160
|
+
async execute(args) {
|
|
161
|
+
try {
|
|
162
|
+
await this.program.parseAsync(args, { from: 'user' });
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof Error) {
|
|
166
|
+
console.error(chalk.red(i18n.t('cli.error_general')), error.message);
|
|
167
|
+
}
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
getProgram() {
|
|
172
|
+
return this.program;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface ChelperConfig {
|
|
2
|
+
lang: string;
|
|
3
|
+
api_key?: string;
|
|
4
|
+
haikuModel?: string;
|
|
5
|
+
sonnetModel?: string;
|
|
6
|
+
opusModel?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare class ConfigManager {
|
|
9
|
+
private static instance;
|
|
10
|
+
private configDir;
|
|
11
|
+
private configPath;
|
|
12
|
+
private config;
|
|
13
|
+
private constructor();
|
|
14
|
+
static getInstance(): ConfigManager;
|
|
15
|
+
private ensureConfigDir;
|
|
16
|
+
private loadConfig;
|
|
17
|
+
saveConfig(config?: ChelperConfig): void;
|
|
18
|
+
getConfig(): ChelperConfig;
|
|
19
|
+
updateConfig(updates: Partial<ChelperConfig>): void;
|
|
20
|
+
isFirstRun(): boolean;
|
|
21
|
+
getLang(): string;
|
|
22
|
+
setLang(lang: string): void;
|
|
23
|
+
getApiKey(): string | undefined;
|
|
24
|
+
setApiKey(apiKey: string): void;
|
|
25
|
+
revokeApiKey(): void;
|
|
26
|
+
getModels(): {
|
|
27
|
+
haikuModel?: string;
|
|
28
|
+
sonnetModel?: string;
|
|
29
|
+
opusModel?: string;
|
|
30
|
+
};
|
|
31
|
+
setModels(models: {
|
|
32
|
+
haikuModel?: string;
|
|
33
|
+
sonnetModel?: string;
|
|
34
|
+
opusModel?: string;
|
|
35
|
+
}): void;
|
|
36
|
+
}
|
|
37
|
+
export declare const configManager: ConfigManager;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import * as yaml from 'js-yaml';
|
|
5
|
+
export class ConfigManager {
|
|
6
|
+
static instance;
|
|
7
|
+
configDir;
|
|
8
|
+
configPath;
|
|
9
|
+
config;
|
|
10
|
+
constructor() {
|
|
11
|
+
// chelper 配置文件路径(跨平台支持)
|
|
12
|
+
// - macOS/Linux: ~/.chelper/config.yaml
|
|
13
|
+
// - Windows: %USERPROFILE%\.chelper\config.yaml
|
|
14
|
+
// (例如: C:\Users\username\.chelper\config.yaml)
|
|
15
|
+
this.configDir = join(homedir(), '.chelper');
|
|
16
|
+
this.configPath = join(this.configDir, 'config.yaml');
|
|
17
|
+
this.config = this.loadConfig();
|
|
18
|
+
}
|
|
19
|
+
static getInstance() {
|
|
20
|
+
if (!ConfigManager.instance) {
|
|
21
|
+
ConfigManager.instance = new ConfigManager();
|
|
22
|
+
}
|
|
23
|
+
return ConfigManager.instance;
|
|
24
|
+
}
|
|
25
|
+
ensureConfigDir() {
|
|
26
|
+
if (!existsSync(this.configDir)) {
|
|
27
|
+
mkdirSync(this.configDir, { recursive: true });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
loadConfig() {
|
|
31
|
+
try {
|
|
32
|
+
if (existsSync(this.configPath)) {
|
|
33
|
+
const fileContent = readFileSync(this.configPath, 'utf-8');
|
|
34
|
+
const config = yaml.load(fileContent);
|
|
35
|
+
return config || { lang: 'en_US' };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn('Failed to load config, using defaults:', error);
|
|
40
|
+
}
|
|
41
|
+
return { lang: 'en_US' };
|
|
42
|
+
}
|
|
43
|
+
saveConfig(config) {
|
|
44
|
+
try {
|
|
45
|
+
this.ensureConfigDir();
|
|
46
|
+
const configToSave = config || this.config;
|
|
47
|
+
const yamlContent = yaml.dump(configToSave);
|
|
48
|
+
writeFileSync(this.configPath, yamlContent, 'utf-8');
|
|
49
|
+
this.config = configToSave;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
console.error('Failed to save config:', error);
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
getConfig() {
|
|
57
|
+
return { ...this.config };
|
|
58
|
+
}
|
|
59
|
+
updateConfig(updates) {
|
|
60
|
+
this.config = { ...this.config, ...updates };
|
|
61
|
+
this.saveConfig();
|
|
62
|
+
}
|
|
63
|
+
isFirstRun() {
|
|
64
|
+
return !existsSync(this.configPath);
|
|
65
|
+
}
|
|
66
|
+
getLang() {
|
|
67
|
+
return this.config.lang || 'en_US';
|
|
68
|
+
}
|
|
69
|
+
setLang(lang) {
|
|
70
|
+
this.updateConfig({ lang });
|
|
71
|
+
}
|
|
72
|
+
getApiKey() {
|
|
73
|
+
return this.config.api_key;
|
|
74
|
+
}
|
|
75
|
+
setApiKey(apiKey) {
|
|
76
|
+
this.updateConfig({ api_key: apiKey });
|
|
77
|
+
}
|
|
78
|
+
revokeApiKey() {
|
|
79
|
+
this.updateConfig({ api_key: undefined });
|
|
80
|
+
}
|
|
81
|
+
getModels() {
|
|
82
|
+
return {
|
|
83
|
+
haikuModel: this.config.haikuModel,
|
|
84
|
+
sonnetModel: this.config.sonnetModel,
|
|
85
|
+
opusModel: this.config.opusModel
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
setModels(models) {
|
|
89
|
+
this.updateConfig(models);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export const configManager = ConfigManager.getInstance();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
declare class I18n {
|
|
2
|
+
private static instance;
|
|
3
|
+
private currentLocale;
|
|
4
|
+
private translations;
|
|
5
|
+
private fallbackLocale;
|
|
6
|
+
private localesDir;
|
|
7
|
+
private constructor();
|
|
8
|
+
static getInstance(): I18n;
|
|
9
|
+
private loadTranslations;
|
|
10
|
+
setLocale(locale: string): void;
|
|
11
|
+
getLocale(): string;
|
|
12
|
+
getAvailableLocales(): string[];
|
|
13
|
+
translate(key: string, params?: Record<string, string>): string;
|
|
14
|
+
t(key: string, params?: Record<string, string>): string;
|
|
15
|
+
private saveLocale;
|
|
16
|
+
private loadSavedLocale;
|
|
17
|
+
loadFromConfig(locale: string): void;
|
|
18
|
+
}
|
|
19
|
+
export declare const i18n: I18n;
|
|
20
|
+
export { i18n as I18n };
|