@pikecode/api-key-manager 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.
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/bin/akm.js +101 -0
- package/bin/cc.js +101 -0
- package/package.json +71 -0
- package/src/CommandRegistry.js +74 -0
- package/src/commands/BaseCommand.js +212 -0
- package/src/commands/add.js +514 -0
- package/src/commands/current.js +94 -0
- package/src/commands/edit.js +208 -0
- package/src/commands/list.js +122 -0
- package/src/commands/remove.js +150 -0
- package/src/commands/switch.js +1479 -0
- package/src/config.js +250 -0
- package/src/index.js +19 -0
- package/src/navigation/EscNavigationManager.js +213 -0
- package/src/utils/claude-settings.js +150 -0
- package/src/utils/config-opener.js +44 -0
- package/src/utils/env-launcher.js +80 -0
- package/src/utils/error-handler.js +53 -0
- package/src/utils/inquirer-setup.js +89 -0
- package/src/utils/logger.js +41 -0
- package/src/utils/provider-status-checker.js +210 -0
- package/src/utils/storage.js +55 -0
- package/src/utils/terminal-format.js +41 -0
- package/src/utils/ui-helper.js +227 -0
- package/src/utils/update-checker.js +121 -0
- package/src/utils/validator.js +157 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const spawn = require('cross-spawn');
|
|
2
|
+
|
|
3
|
+
function clearTerminal() {
|
|
4
|
+
if (!process.stdout || typeof process.stdout.write !== 'function') {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
process.stdout.write('\x1bc');
|
|
10
|
+
} catch (error) {
|
|
11
|
+
// 某些终端可能不支持 RIS 序列,忽略即可
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const sequence = process.platform === 'win32'
|
|
15
|
+
? '\x1b[3J\x1b[2J\x1b[0f'
|
|
16
|
+
: '\x1b[3J\x1b[2J\x1b[H';
|
|
17
|
+
try {
|
|
18
|
+
process.stdout.write(sequence);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
// 忽略清屏失败
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildEnvVariables(config) {
|
|
25
|
+
const env = { ...process.env };
|
|
26
|
+
|
|
27
|
+
if (config.authMode === 'oauth_token') {
|
|
28
|
+
env.CLAUDE_CODE_OAUTH_TOKEN = config.authToken;
|
|
29
|
+
} else if (config.authMode === 'api_key') {
|
|
30
|
+
env.ANTHROPIC_BASE_URL = config.baseUrl;
|
|
31
|
+
// 根据 tokenType 选择设置哪种 token
|
|
32
|
+
if (config.tokenType === 'auth_token') {
|
|
33
|
+
env.ANTHROPIC_AUTH_TOKEN = config.authToken;
|
|
34
|
+
} else {
|
|
35
|
+
// 默认使用 ANTHROPIC_API_KEY
|
|
36
|
+
env.ANTHROPIC_API_KEY = config.authToken;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
// auth_token 模式
|
|
40
|
+
env.ANTHROPIC_BASE_URL = config.baseUrl;
|
|
41
|
+
env.ANTHROPIC_AUTH_TOKEN = config.authToken;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (config.models && config.models.primary) {
|
|
45
|
+
env.ANTHROPIC_MODEL = config.models.primary;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (config.models && config.models.smallFast) {
|
|
49
|
+
env.ANTHROPIC_SMALL_FAST_MODEL = config.models.smallFast;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return env;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function executeWithEnv(config, launchArgs = []) {
|
|
56
|
+
const env = buildEnvVariables(config);
|
|
57
|
+
const args = [...launchArgs];
|
|
58
|
+
|
|
59
|
+
clearTerminal();
|
|
60
|
+
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const child = spawn('claude', args, {
|
|
63
|
+
stdio: 'inherit',
|
|
64
|
+
env,
|
|
65
|
+
shell: true
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
child.on('close', (code) => {
|
|
69
|
+
if (code === 0) {
|
|
70
|
+
resolve();
|
|
71
|
+
} else {
|
|
72
|
+
reject(new Error(`Claude Code 退出,代码: ${code}`));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
child.on('error', reject);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = { executeWithEnv };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { Logger } = require('./utils/logger');
|
|
3
|
+
|
|
4
|
+
class ErrorHandler {
|
|
5
|
+
static handle(error, context = '') {
|
|
6
|
+
const contextStr = context ? `[${context}] ` : '';
|
|
7
|
+
|
|
8
|
+
switch (error.type) {
|
|
9
|
+
case 'CONFIG_NOT_FOUND':
|
|
10
|
+
Logger.error(`${contextStr}配置文件不存在,请先添加供应商`);
|
|
11
|
+
break;
|
|
12
|
+
case 'PROVIDER_NOT_FOUND':
|
|
13
|
+
Logger.error(`${contextStr}指定的供应商不存在`);
|
|
14
|
+
break;
|
|
15
|
+
case 'INVALID_TOKEN':
|
|
16
|
+
Logger.error(`${contextStr}Token格式无效`);
|
|
17
|
+
break;
|
|
18
|
+
case 'INVALID_URL':
|
|
19
|
+
Logger.error(`${contextStr}URL格式无效`);
|
|
20
|
+
break;
|
|
21
|
+
case 'INVALID_NAME':
|
|
22
|
+
Logger.error(`${contextStr}供应商名称格式无效`);
|
|
23
|
+
break;
|
|
24
|
+
case 'FILE_PERMISSION':
|
|
25
|
+
Logger.error(`${contextStr}文件权限不足`);
|
|
26
|
+
break;
|
|
27
|
+
case 'NETWORK_ERROR':
|
|
28
|
+
Logger.error(`${contextStr}网络连接错误`);
|
|
29
|
+
break;
|
|
30
|
+
default:
|
|
31
|
+
Logger.error(`${contextStr}${error.message || '未知错误'}`);
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
static createError(type, message, originalError = null) {
|
|
37
|
+
const error = new Error(message);
|
|
38
|
+
error.type = type;
|
|
39
|
+
error.originalError = originalError;
|
|
40
|
+
return error;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static async withErrorHandling(fn, context = '') {
|
|
44
|
+
try {
|
|
45
|
+
return await fn();
|
|
46
|
+
} catch (error) {
|
|
47
|
+
this.handle(error, context);
|
|
48
|
+
throw error;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { ErrorHandler };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const CheckboxPrompt = require('inquirer/lib/prompts/checkbox');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
const figures = require('figures');
|
|
5
|
+
|
|
6
|
+
class LocalizedCheckboxPrompt extends CheckboxPrompt {
|
|
7
|
+
render(error) {
|
|
8
|
+
let message = this.getQuestion();
|
|
9
|
+
let bottomContent = '';
|
|
10
|
+
|
|
11
|
+
if (!this.dontShowHints) {
|
|
12
|
+
message += chalk.gray(' (空格切换选中,a 全选,i 反选,回车继续)');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (this.status === 'answered') {
|
|
16
|
+
message += chalk.cyan(this.selection.join(', '));
|
|
17
|
+
} else {
|
|
18
|
+
const choicesStr = renderChoices(this.opt.choices, this.pointer);
|
|
19
|
+
const indexPosition = this.opt.choices.indexOf(
|
|
20
|
+
this.opt.choices.getChoice(this.pointer)
|
|
21
|
+
);
|
|
22
|
+
const realIndexPosition =
|
|
23
|
+
this.opt.choices.reduce((acc, value, i) => {
|
|
24
|
+
if (i > indexPosition) {
|
|
25
|
+
return acc;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (value.type === 'separator') {
|
|
29
|
+
return acc + 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let line = value.name;
|
|
33
|
+
if (typeof line !== 'string') {
|
|
34
|
+
return acc + 1;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
line = line.split('\n');
|
|
38
|
+
return acc + line.length;
|
|
39
|
+
}, 0) - 1;
|
|
40
|
+
|
|
41
|
+
message +=
|
|
42
|
+
'\n' + this.paginator.paginate(choicesStr, realIndexPosition, this.opt.pageSize);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (error) {
|
|
46
|
+
bottomContent = chalk.red('>> ') + error;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.screen.render(message, bottomContent);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function renderChoices(choices, pointer) {
|
|
54
|
+
let output = '';
|
|
55
|
+
let separatorOffset = 0;
|
|
56
|
+
|
|
57
|
+
choices.forEach((choice, index) => {
|
|
58
|
+
if (choice.type === 'separator') {
|
|
59
|
+
separatorOffset++;
|
|
60
|
+
output += ' ' + choice + '\n';
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (choice.disabled) {
|
|
65
|
+
separatorOffset++;
|
|
66
|
+
output += ' - ' + choice.name;
|
|
67
|
+
output += ` (${typeof choice.disabled === 'string' ? choice.disabled : '禁用'})`;
|
|
68
|
+
} else {
|
|
69
|
+
const line = getCheckbox(choice.checked) + ' ' + choice.name;
|
|
70
|
+
if (index - separatorOffset === pointer) {
|
|
71
|
+
output += chalk.cyan(figures.pointer + line);
|
|
72
|
+
} else {
|
|
73
|
+
output += ' ' + line;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
output += '\n';
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return output.replace(/\n$/, '');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getCheckbox(checked) {
|
|
84
|
+
return checked ? chalk.green(figures.radioOn) : figures.radioOff;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
inquirer.registerPrompt('checkbox', LocalizedCheckboxPrompt);
|
|
88
|
+
|
|
89
|
+
module.exports = {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { formatMessage } = require('./terminal-format');
|
|
3
|
+
|
|
4
|
+
class Logger {
|
|
5
|
+
static info(message) {
|
|
6
|
+
console.log(formatMessage(message, 'info'));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
static success(message) {
|
|
10
|
+
console.log(formatMessage(message, 'success'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static warning(message) {
|
|
14
|
+
console.log(formatMessage(message, 'warning'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static error(message) {
|
|
18
|
+
console.error(formatMessage(message, 'error'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static debug(message) {
|
|
22
|
+
if (process.env.DEBUG) {
|
|
23
|
+
console.log(formatMessage(`[DEBUG] ${message}`, 'info'));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
static step(message) {
|
|
28
|
+
console.log(formatMessage(`🔄 ${message}`, 'info'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
static complete(message) {
|
|
32
|
+
console.log(formatMessage(`✅ ${message}`, 'success'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static fatal(message) {
|
|
36
|
+
console.error(formatMessage(`💀 ${message}`, 'error'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { Logger };
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
const { Anthropic, APIConnectionTimeoutError, APIConnectionError, APIError } = require('@anthropic-ai/sdk');
|
|
2
|
+
|
|
3
|
+
class ProviderStatusChecker {
|
|
4
|
+
constructor(options = {}) {
|
|
5
|
+
this.timeout = options.timeout ?? 5000;
|
|
6
|
+
this.testMessage = options.testMessage ?? '你好';
|
|
7
|
+
this.maxTokens = options.maxTokens ?? 32;
|
|
8
|
+
this.defaultModel = 'claude-haiku-4-5-20251001';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async check(provider) {
|
|
12
|
+
if (!provider) {
|
|
13
|
+
return this._result('unknown', '未找到配置', null);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (provider.authMode === 'oauth_token') {
|
|
17
|
+
return this._result('unknown', '暂不支持 OAuth 令牌检测', null);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (!provider.baseUrl) {
|
|
21
|
+
return this._result('unknown', '未配置基础地址', null);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!provider.authToken) {
|
|
25
|
+
return this._result('unknown', '未配置认证信息', null);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const model = this._resolveModel(provider);
|
|
29
|
+
try {
|
|
30
|
+
const client = this._createClient(provider);
|
|
31
|
+
if (!client) {
|
|
32
|
+
return this._result('unknown', '认证模式不受支持', null);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const start = process.hrtime.bigint();
|
|
36
|
+
const response = await client.messages.create({
|
|
37
|
+
model,
|
|
38
|
+
max_tokens: this.maxTokens,
|
|
39
|
+
messages: [
|
|
40
|
+
{
|
|
41
|
+
role: 'user',
|
|
42
|
+
content: this.testMessage
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
}, { timeout: this.timeout });
|
|
46
|
+
const latency = Number(process.hrtime.bigint() - start) / 1e6;
|
|
47
|
+
|
|
48
|
+
const text = this._extractText(response);
|
|
49
|
+
if (!text) {
|
|
50
|
+
return this._result('online', `可用 ${latency.toFixed(0)}ms (无文本响应)`, latency);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return this._result('online', `可用 ${latency.toFixed(0)}ms`, latency);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
return this._handleError(error);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async checkAll(providers) {
|
|
60
|
+
const entries = await Promise.all(
|
|
61
|
+
providers.map(async provider => {
|
|
62
|
+
const status = await this.check(provider);
|
|
63
|
+
return [provider.name, status];
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
return Object.fromEntries(entries);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
checkAllStreaming(providers, onUpdate) {
|
|
70
|
+
const results = {};
|
|
71
|
+
const tasks = providers.map(async provider => {
|
|
72
|
+
try {
|
|
73
|
+
const status = await this.check(provider);
|
|
74
|
+
results[provider.name] = status;
|
|
75
|
+
if (typeof onUpdate === 'function') {
|
|
76
|
+
onUpdate(provider.name, status, null);
|
|
77
|
+
}
|
|
78
|
+
} catch (error) {
|
|
79
|
+
const fallback = this._result('offline', `检测失败: ${error.message}`, null);
|
|
80
|
+
results[provider.name] = fallback;
|
|
81
|
+
if (typeof onUpdate === 'function') {
|
|
82
|
+
onUpdate(provider.name, fallback, error);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return Promise.all(tasks).then(() => results);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_createClient(provider) {
|
|
91
|
+
const clientOptions = { baseURL: provider.baseUrl };
|
|
92
|
+
|
|
93
|
+
if (provider.authMode === 'api_key') {
|
|
94
|
+
clientOptions.apiKey = provider.authToken;
|
|
95
|
+
} else if (provider.authMode === 'auth_token') {
|
|
96
|
+
clientOptions.authToken = provider.authToken;
|
|
97
|
+
} else {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return new Anthropic(clientOptions);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
_resolveModel(provider) {
|
|
105
|
+
if (provider.models?.primary) {
|
|
106
|
+
return provider.models.primary;
|
|
107
|
+
}
|
|
108
|
+
if (provider.models?.smallFast) {
|
|
109
|
+
return provider.models.smallFast;
|
|
110
|
+
}
|
|
111
|
+
return this.defaultModel;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
_extractText(response) {
|
|
115
|
+
if (!response) {
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 兼容部分供应商返回字符串内容
|
|
120
|
+
if (typeof response.content === 'string') {
|
|
121
|
+
return response.content.trim();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (Array.isArray(response.content)) {
|
|
125
|
+
const textParts = [];
|
|
126
|
+
for (const block of response.content) {
|
|
127
|
+
if (!block) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (typeof block === 'string') {
|
|
131
|
+
textParts.push(block);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (typeof block.text === 'string' && block.text.trim()) {
|
|
135
|
+
textParts.push(block.text);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (typeof block.thinking === 'string' && block.thinking.trim()) {
|
|
139
|
+
textParts.push(block.thinking);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (typeof block.output_text === 'string' && block.output_text.trim()) {
|
|
143
|
+
textParts.push(block.output_text);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
if (typeof block.argument === 'string' && block.argument.trim()) {
|
|
147
|
+
textParts.push(block.argument);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (typeof block.result === 'string' && block.result.trim()) {
|
|
151
|
+
textParts.push(block.result);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const combined = textParts
|
|
156
|
+
.map(part => part.replace(/\s+/g, ' ').trim())
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join('\n');
|
|
159
|
+
if (combined) {
|
|
160
|
+
return combined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof response.output_text === 'string' && response.output_text.trim()) {
|
|
165
|
+
return response.output_text.trim();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (typeof response.completion === 'string' && response.completion.trim()) {
|
|
169
|
+
return response.completion.trim();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return '';
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
_handleError(error) {
|
|
177
|
+
if (error instanceof APIConnectionTimeoutError) {
|
|
178
|
+
return this._result('offline', '请求超时', null);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (error instanceof APIConnectionError) {
|
|
182
|
+
return this._result('offline', '网络连接失败', null);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (error instanceof APIError) {
|
|
186
|
+
if (error.status >= 500) {
|
|
187
|
+
return this._result('degraded', `服务异常 (${error.status})`, null);
|
|
188
|
+
}
|
|
189
|
+
if (error.status === 401 || error.status === 403) {
|
|
190
|
+
return this._result('offline', `认证失败 (${error.status})`, null);
|
|
191
|
+
}
|
|
192
|
+
if (error.status === 404) {
|
|
193
|
+
return this._result('offline', '接口不存在 (404)', null);
|
|
194
|
+
}
|
|
195
|
+
return this._result('offline', `请求失败 (${error.status})`, null);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (error?.name === 'AbortError') {
|
|
199
|
+
return this._result('offline', '请求超时', null);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return this._result('offline', `检测失败: ${error?.message || '未知错误'}`, null);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_result(state, label, latency) {
|
|
206
|
+
return { state, label, latency };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = { ProviderStatusChecker };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
class Storage {
|
|
6
|
+
static async ensureConfigDir() {
|
|
7
|
+
const configDir = path.join(os.homedir(), '.akm-config');
|
|
8
|
+
await fs.ensureDir(configDir);
|
|
9
|
+
return configDir;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
static async readConfig() {
|
|
13
|
+
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
14
|
+
try {
|
|
15
|
+
if (await fs.pathExists(configPath)) {
|
|
16
|
+
return await fs.readJSON(configPath);
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
} catch (error) {
|
|
20
|
+
throw new Error(`读取配置文件失败: ${error.message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static async writeConfig(config) {
|
|
25
|
+
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
26
|
+
try {
|
|
27
|
+
await fs.writeJSON(configPath, config, { spaces: 2 });
|
|
28
|
+
return true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
throw new Error(`写入配置文件失败: ${error.message}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static async backupConfig() {
|
|
35
|
+
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
36
|
+
const backupPath = path.join(os.homedir(), '.akm-config.backup.json');
|
|
37
|
+
|
|
38
|
+
if (await fs.pathExists(configPath)) {
|
|
39
|
+
await fs.copy(configPath, backupPath);
|
|
40
|
+
return backupPath;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
static async restoreConfig(backupPath) {
|
|
46
|
+
const configPath = path.join(os.homedir(), '.akm-config.json');
|
|
47
|
+
if (await fs.pathExists(backupPath)) {
|
|
48
|
+
await fs.copy(backupPath, configPath);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { Storage };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const supportsColor = require('supports-color');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
|
|
4
|
+
function detectTerminalCapabilities() {
|
|
5
|
+
return {
|
|
6
|
+
colors: supportsColor.stdout,
|
|
7
|
+
unicode: Boolean(process.env.WT_SESSION || process.env.TERM_PROGRAM === 'vscode'),
|
|
8
|
+
colorDepth: typeof process.stdout.getColorDepth === 'function' ? process.stdout.getColorDepth() : 1
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function formatMessage(message, type = 'info') {
|
|
13
|
+
const capabilities = detectTerminalCapabilities();
|
|
14
|
+
|
|
15
|
+
if (!capabilities.colors) {
|
|
16
|
+
const symbols = {
|
|
17
|
+
success: '[OK]',
|
|
18
|
+
error: '[错误]',
|
|
19
|
+
warning: '[警告]',
|
|
20
|
+
info: '[信息]'
|
|
21
|
+
};
|
|
22
|
+
return `${symbols[type] || '[信息]'} ${message}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const colorMap = {
|
|
26
|
+
success: chalk.green,
|
|
27
|
+
error: chalk.red,
|
|
28
|
+
warning: chalk.yellow,
|
|
29
|
+
info: chalk.blue
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const symbols = capabilities.unicode
|
|
33
|
+
? { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }
|
|
34
|
+
: { success: '[OK]', error: '[ERROR]', warning: '[WARN]', info: '[INFO]' };
|
|
35
|
+
|
|
36
|
+
const formatter = colorMap[type] || colorMap.info;
|
|
37
|
+
const symbol = symbols[type] || symbols.info;
|
|
38
|
+
return formatter(`${symbol} ${message}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { formatMessage };
|