@pikecode/api-key-manager 1.0.45 → 1.1.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 +267 -1174
- package/bin/akm.js +125 -1
- package/package.json +1 -1
- package/src/CommandRegistry.js +15 -0
- package/src/commands/add.js +14 -189
- package/src/commands/backup.js +3 -0
- package/src/commands/claude-clean.js +253 -0
- package/src/commands/clone.js +265 -0
- package/src/commands/current.js +4 -18
- package/src/commands/edit.js +10 -39
- package/src/commands/list.js +17 -20
- package/src/commands/mcp.js +467 -0
- package/src/commands/switch.js +7 -39
- package/src/config.js +0 -6
- package/src/constants/index.js +6 -18
- package/src/utils/claude-settings.js +0 -1
- package/src/utils/env-launcher.js +8 -16
- package/src/utils/provider-status-checker.js +0 -4
package/src/commands/list.js
CHANGED
|
@@ -9,7 +9,7 @@ const { configManager } = require('../config');
|
|
|
9
9
|
const { Logger } = require('../utils/logger');
|
|
10
10
|
const { ProviderStatusChecker } = require('../utils/provider-status-checker');
|
|
11
11
|
const { maybeMaskToken } = require('../utils/secrets');
|
|
12
|
-
const { AUTH_MODE_DISPLAY,
|
|
12
|
+
const { AUTH_MODE_DISPLAY, BASE_URL } = require('../constants');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
15
|
* 供应商列表显示类
|
|
@@ -29,12 +29,14 @@ class ProviderLister {
|
|
|
29
29
|
* @param {string|null} filter - 过滤器 ('codex', 'claude', 或 null 表示全部)
|
|
30
30
|
* @param {Object} options - 显示选项
|
|
31
31
|
* @param {boolean} [options.showToken=true] - 是否显示完整 token
|
|
32
|
+
* @param {boolean} [options.checkStatus=false] - 是否检测在线状态
|
|
32
33
|
* @returns {Promise<void>}
|
|
33
34
|
*/
|
|
34
35
|
async list(filter = null, options = {}) {
|
|
35
36
|
try {
|
|
36
37
|
// 默认显示完整 token,不再加密
|
|
37
38
|
const showToken = options.showToken !== false;
|
|
39
|
+
const checkStatus = options.checkStatus === true;
|
|
38
40
|
await this.configManager.ensureLoaded();
|
|
39
41
|
let providers = this.configManager.listProviders();
|
|
40
42
|
const currentProvider = this.configManager.getCurrentProvider();
|
|
@@ -46,7 +48,7 @@ class ProviderLister {
|
|
|
46
48
|
providers = providers.filter(p => p.ideName !== 'codex');
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
const statusMap = await this.statusChecker.checkAll(providers);
|
|
51
|
+
const statusMap = checkStatus ? await this.statusChecker.checkAll(providers) : {};
|
|
50
52
|
|
|
51
53
|
if (providers.length === 0) {
|
|
52
54
|
if (filter) {
|
|
@@ -66,9 +68,6 @@ class ProviderLister {
|
|
|
66
68
|
providers.forEach((provider, index) => {
|
|
67
69
|
const isCurrent = provider.name === currentProvider?.name;
|
|
68
70
|
const status = isCurrent ? '✅' : '🔹';
|
|
69
|
-
const availability = statusMap[provider.name] || { state: 'unknown', label: '未知', latency: null };
|
|
70
|
-
const availabilityIcon = this._iconForState(availability.state);
|
|
71
|
-
const availabilityText = this._formatAvailability(availability);
|
|
72
71
|
const nameColor = isCurrent ? chalk.green : chalk.white;
|
|
73
72
|
|
|
74
73
|
// IDE 类型标签
|
|
@@ -78,7 +77,15 @@ class ProviderLister {
|
|
|
78
77
|
|
|
79
78
|
// 如果有别名,显示别名
|
|
80
79
|
const aliasText = provider.alias ? chalk.yellow(` [别名: ${provider.alias}]`) : '';
|
|
81
|
-
|
|
80
|
+
|
|
81
|
+
if (checkStatus) {
|
|
82
|
+
const availability = statusMap[provider.name] || { state: 'unknown', label: '未知', latency: null };
|
|
83
|
+
const availabilityIcon = this._iconForState(availability.state);
|
|
84
|
+
const availabilityText = this._formatAvailability(availability);
|
|
85
|
+
console.log(`${status} ${availabilityIcon} ${ideTag} ${nameColor(provider.name)} (${provider.displayName})${aliasText} - ${availabilityText}`);
|
|
86
|
+
} else {
|
|
87
|
+
console.log(`${status} ${ideTag} ${nameColor(provider.name)} (${provider.displayName})${aliasText}`);
|
|
88
|
+
}
|
|
82
89
|
|
|
83
90
|
if (provider.ideName === 'codex') {
|
|
84
91
|
console.log(chalk.gray(' IDE: Codex CLI'));
|
|
@@ -93,30 +100,20 @@ class ProviderLister {
|
|
|
93
100
|
console.log(chalk.gray(` 认证模式: ${AUTH_MODE_DISPLAY[provider.authMode] || provider.authMode}`));
|
|
94
101
|
|
|
95
102
|
// 根据不同模式显示对应的环境变量名称
|
|
96
|
-
if (provider.authMode === '
|
|
97
|
-
// OAuth 模式
|
|
98
|
-
if (provider.authToken) {
|
|
99
|
-
console.log(chalk.gray(` CLAUDE_CODE_OAUTH_TOKEN: ${maybeMaskToken(provider.authToken, showToken)}`));
|
|
100
|
-
}
|
|
101
|
-
if (provider.baseUrl) {
|
|
102
|
-
console.log(chalk.gray(` ANTHROPIC_BASE_URL: ${provider.baseUrl}`));
|
|
103
|
-
}
|
|
104
|
-
} else if (provider.authMode === 'api_key') {
|
|
105
|
-
// API Key 模式
|
|
103
|
+
if (provider.authMode === 'auth_token') {
|
|
106
104
|
if (provider.baseUrl) {
|
|
107
105
|
console.log(chalk.gray(` ANTHROPIC_BASE_URL: ${provider.baseUrl}`));
|
|
108
106
|
}
|
|
109
107
|
if (provider.authToken) {
|
|
110
|
-
|
|
111
|
-
console.log(chalk.gray(` ${tokenEnvName}: ${maybeMaskToken(provider.authToken, showToken)}`));
|
|
108
|
+
console.log(chalk.gray(` ANTHROPIC_AUTH_TOKEN: ${maybeMaskToken(provider.authToken, showToken)}`));
|
|
112
109
|
}
|
|
113
110
|
} else {
|
|
114
|
-
//
|
|
111
|
+
// API Key 模式(默认)
|
|
115
112
|
if (provider.baseUrl) {
|
|
116
113
|
console.log(chalk.gray(` ANTHROPIC_BASE_URL: ${provider.baseUrl}`));
|
|
117
114
|
}
|
|
118
115
|
if (provider.authToken) {
|
|
119
|
-
console.log(chalk.gray(`
|
|
116
|
+
console.log(chalk.gray(` ANTHROPIC_API_KEY: ${maybeMaskToken(provider.authToken, showToken)}`));
|
|
120
117
|
}
|
|
121
118
|
}
|
|
122
119
|
}
|
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Management Command
|
|
3
|
+
* 管理 ~/.claude.json 中的 MCP 服务器配置
|
|
4
|
+
* @module commands/mcp
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const inquirer = require('inquirer');
|
|
12
|
+
const { Logger } = require('../utils/logger');
|
|
13
|
+
const { UIHelper } = require('../utils/ui-helper');
|
|
14
|
+
const { BaseCommand } = require('./BaseCommand');
|
|
15
|
+
|
|
16
|
+
const CLAUDE_JSON_PATH = path.join(os.homedir(), '.claude.json');
|
|
17
|
+
|
|
18
|
+
// 预设 MCP 服务器模板
|
|
19
|
+
const MCP_PRESETS = [
|
|
20
|
+
{
|
|
21
|
+
name: 'Playwright',
|
|
22
|
+
description: '浏览器自动化测试',
|
|
23
|
+
config: {
|
|
24
|
+
command: 'npx',
|
|
25
|
+
args: ['-y', '@playwright/mcp@latest'],
|
|
26
|
+
type: 'stdio'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'filesystem',
|
|
31
|
+
description: '文件系统访问',
|
|
32
|
+
config: {
|
|
33
|
+
command: 'npx',
|
|
34
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', os.homedir()],
|
|
35
|
+
type: 'stdio'
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: 'memory',
|
|
40
|
+
description: '知识图谱记忆',
|
|
41
|
+
config: {
|
|
42
|
+
command: 'npx',
|
|
43
|
+
args: ['-y', '@modelcontextprotocol/server-memory'],
|
|
44
|
+
type: 'stdio'
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: 'fetch',
|
|
49
|
+
description: '网页内容获取',
|
|
50
|
+
config: {
|
|
51
|
+
command: 'npx',
|
|
52
|
+
args: ['-y', '@modelcontextprotocol/server-fetch'],
|
|
53
|
+
type: 'stdio'
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
class McpManager extends BaseCommand {
|
|
59
|
+
constructor() {
|
|
60
|
+
super();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 读取 ~/.claude.json
|
|
65
|
+
*/
|
|
66
|
+
async _readClaudeJson() {
|
|
67
|
+
if (!await fs.pathExists(CLAUDE_JSON_PATH)) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
return await fs.readJson(CLAUDE_JSON_PATH);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 写入 ~/.claude.json
|
|
75
|
+
*/
|
|
76
|
+
async _writeClaudeJson(data) {
|
|
77
|
+
await fs.writeJson(CLAUDE_JSON_PATH, data, { spaces: 2 });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* 获取 mcpServers 对象
|
|
82
|
+
*/
|
|
83
|
+
_getMcpServers(data) {
|
|
84
|
+
return data.mcpServers || {};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 列出所有 MCP 服务器
|
|
89
|
+
*/
|
|
90
|
+
async list() {
|
|
91
|
+
const data = await this._readClaudeJson();
|
|
92
|
+
const servers = this._getMcpServers(data);
|
|
93
|
+
const names = Object.keys(servers);
|
|
94
|
+
|
|
95
|
+
console.log(UIHelper.createTitle('MCP 服务器列表', '🔌'));
|
|
96
|
+
console.log();
|
|
97
|
+
|
|
98
|
+
if (names.length === 0) {
|
|
99
|
+
Logger.info('暂无 MCP 服务器配置。使用 akm mcp add 添加。');
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
names.forEach(name => {
|
|
104
|
+
const server = servers[name];
|
|
105
|
+
const cmd = server.command || '(unknown)';
|
|
106
|
+
const args = (server.args || []).join(' ');
|
|
107
|
+
const envKeys = Object.keys(server.env || {});
|
|
108
|
+
const envInfo = envKeys.length > 0 ? chalk.gray(` env: ${envKeys.join(', ')}`) : '';
|
|
109
|
+
|
|
110
|
+
console.log(` ${chalk.cyan('●')} ${chalk.bold(name)}`);
|
|
111
|
+
console.log(` ${chalk.gray('命令:')} ${cmd} ${args}`);
|
|
112
|
+
if (server.type) {
|
|
113
|
+
console.log(` ${chalk.gray('类型:')} ${server.type}`);
|
|
114
|
+
}
|
|
115
|
+
if (envInfo) {
|
|
116
|
+
console.log(` ${envInfo}`);
|
|
117
|
+
}
|
|
118
|
+
console.log();
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
console.log(chalk.gray(` 共 ${names.length} 个 MCP 服务器`));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 添加 MCP 服务器
|
|
126
|
+
*/
|
|
127
|
+
async add() {
|
|
128
|
+
console.log(UIHelper.createTitle('添加 MCP 服务器', '➕'));
|
|
129
|
+
console.log();
|
|
130
|
+
|
|
131
|
+
let sourceChoice;
|
|
132
|
+
try {
|
|
133
|
+
sourceChoice = await this.prompt([
|
|
134
|
+
{
|
|
135
|
+
type: 'list',
|
|
136
|
+
name: 'source',
|
|
137
|
+
message: '选择添加方式:',
|
|
138
|
+
choices: [
|
|
139
|
+
{ name: '📦 从预设模板选择', value: 'preset' },
|
|
140
|
+
{ name: '✏️ 手动配置', value: 'manual' },
|
|
141
|
+
new inquirer.Separator(),
|
|
142
|
+
{ name: '取消', value: null }
|
|
143
|
+
]
|
|
144
|
+
}
|
|
145
|
+
]);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (this.isEscCancelled(error)) return;
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (!sourceChoice.source) {
|
|
152
|
+
Logger.info('操作已取消。');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const data = await this._readClaudeJson();
|
|
157
|
+
const servers = this._getMcpServers(data);
|
|
158
|
+
|
|
159
|
+
if (sourceChoice.source === 'preset') {
|
|
160
|
+
await this._addFromPreset(data, servers);
|
|
161
|
+
} else {
|
|
162
|
+
await this._addManual(data, servers);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async _addFromPreset(data, servers) {
|
|
167
|
+
// 过滤掉已存在的预设
|
|
168
|
+
const available = MCP_PRESETS.filter(p => !servers[p.name]);
|
|
169
|
+
|
|
170
|
+
if (available.length === 0) {
|
|
171
|
+
Logger.info('所有预设模板已添加。可使用手动配置添加自定义服务器。');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
let selection;
|
|
176
|
+
try {
|
|
177
|
+
selection = await this.prompt([
|
|
178
|
+
{
|
|
179
|
+
type: 'checkbox',
|
|
180
|
+
name: 'presets',
|
|
181
|
+
message: '选择要添加的 MCP 服务器:',
|
|
182
|
+
choices: available.map(p => ({
|
|
183
|
+
name: `${p.name} - ${p.description}`,
|
|
184
|
+
value: p.name
|
|
185
|
+
})),
|
|
186
|
+
validate: input => input.length > 0 ? true : '请至少选择一个'
|
|
187
|
+
}
|
|
188
|
+
]);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (this.isEscCancelled(error)) return;
|
|
191
|
+
throw error;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!data.mcpServers) {
|
|
195
|
+
data.mcpServers = {};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
selection.presets.forEach(name => {
|
|
199
|
+
const preset = MCP_PRESETS.find(p => p.name === name);
|
|
200
|
+
data.mcpServers[name] = { ...preset.config, env: {} };
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
await this._writeClaudeJson(data);
|
|
204
|
+
Logger.success(`已添加 ${selection.presets.length} 个 MCP 服务器: ${selection.presets.join(', ')}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async _addManual(data, servers) {
|
|
208
|
+
let config;
|
|
209
|
+
try {
|
|
210
|
+
config = await this.prompt([
|
|
211
|
+
{
|
|
212
|
+
type: 'input',
|
|
213
|
+
name: 'name',
|
|
214
|
+
message: '服务器名称:',
|
|
215
|
+
validate: input => {
|
|
216
|
+
if (!input || !input.trim()) return '名称不能为空';
|
|
217
|
+
if (servers[input.trim()]) return `'${input.trim()}' 已存在`;
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
type: 'input',
|
|
223
|
+
name: 'command',
|
|
224
|
+
message: '启动命令 (如 npx, node, python):',
|
|
225
|
+
validate: input => input && input.trim() ? true : '命令不能为空'
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
type: 'input',
|
|
229
|
+
name: 'args',
|
|
230
|
+
message: '命令参数 (空格分隔):',
|
|
231
|
+
default: ''
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
type: 'list',
|
|
235
|
+
name: 'type',
|
|
236
|
+
message: '通信类型:',
|
|
237
|
+
choices: [
|
|
238
|
+
{ name: 'stdio (标准输入输出)', value: 'stdio' },
|
|
239
|
+
{ name: 'sse (Server-Sent Events)', value: 'sse' }
|
|
240
|
+
],
|
|
241
|
+
default: 'stdio'
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
type: 'input',
|
|
245
|
+
name: 'env',
|
|
246
|
+
message: '环境变量 (格式: KEY=VALUE, 多个用逗号分隔, 留空跳过):',
|
|
247
|
+
default: ''
|
|
248
|
+
}
|
|
249
|
+
]);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (this.isEscCancelled(error)) return;
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const name = config.name.trim();
|
|
256
|
+
const args = config.args.trim() ? config.args.trim().split(/\s+/) : [];
|
|
257
|
+
const env = {};
|
|
258
|
+
if (config.env.trim()) {
|
|
259
|
+
config.env.split(',').forEach(pair => {
|
|
260
|
+
const [key, ...valueParts] = pair.trim().split('=');
|
|
261
|
+
if (key) {
|
|
262
|
+
env[key.trim()] = valueParts.join('=').trim();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!data.mcpServers) {
|
|
268
|
+
data.mcpServers = {};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
data.mcpServers[name] = {
|
|
272
|
+
command: config.command.trim(),
|
|
273
|
+
args,
|
|
274
|
+
env,
|
|
275
|
+
type: config.type
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
await this._writeClaudeJson(data);
|
|
279
|
+
Logger.success(`MCP 服务器 '${name}' 已添加。`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* 编辑 MCP 服务器
|
|
284
|
+
*/
|
|
285
|
+
async edit() {
|
|
286
|
+
const data = await this._readClaudeJson();
|
|
287
|
+
const servers = this._getMcpServers(data);
|
|
288
|
+
const names = Object.keys(servers);
|
|
289
|
+
|
|
290
|
+
if (names.length === 0) {
|
|
291
|
+
Logger.warning('暂无 MCP 服务器配置。');
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
let selection;
|
|
296
|
+
try {
|
|
297
|
+
selection = await this.prompt([
|
|
298
|
+
{
|
|
299
|
+
type: 'list',
|
|
300
|
+
name: 'name',
|
|
301
|
+
message: '选择要编辑的 MCP 服务器:',
|
|
302
|
+
choices: [
|
|
303
|
+
...names.map(n => ({
|
|
304
|
+
name: `${n} ${chalk.gray('(' + (servers[n].command || '') + ')')}`,
|
|
305
|
+
value: n
|
|
306
|
+
})),
|
|
307
|
+
new inquirer.Separator(),
|
|
308
|
+
{ name: '取消', value: null }
|
|
309
|
+
]
|
|
310
|
+
}
|
|
311
|
+
]);
|
|
312
|
+
} catch (error) {
|
|
313
|
+
if (this.isEscCancelled(error)) return;
|
|
314
|
+
throw error;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
if (!selection.name) {
|
|
318
|
+
Logger.info('操作已取消。');
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const name = selection.name;
|
|
323
|
+
const server = servers[name];
|
|
324
|
+
|
|
325
|
+
let updates;
|
|
326
|
+
try {
|
|
327
|
+
updates = await this.prompt([
|
|
328
|
+
{
|
|
329
|
+
type: 'input',
|
|
330
|
+
name: 'command',
|
|
331
|
+
message: '启动命令:',
|
|
332
|
+
default: server.command || '',
|
|
333
|
+
validate: input => input && input.trim() ? true : '命令不能为空'
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
type: 'input',
|
|
337
|
+
name: 'args',
|
|
338
|
+
message: '命令参数 (空格分隔):',
|
|
339
|
+
default: (server.args || []).join(' ')
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
type: 'list',
|
|
343
|
+
name: 'type',
|
|
344
|
+
message: '通信类型:',
|
|
345
|
+
choices: [
|
|
346
|
+
{ name: 'stdio (标准输入输出)', value: 'stdio' },
|
|
347
|
+
{ name: 'sse (Server-Sent Events)', value: 'sse' }
|
|
348
|
+
],
|
|
349
|
+
default: server.type || 'stdio'
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
type: 'input',
|
|
353
|
+
name: 'env',
|
|
354
|
+
message: '环境变量 (KEY=VALUE, 逗号分隔):',
|
|
355
|
+
default: Object.entries(server.env || {}).map(([k, v]) => `${k}=${v}`).join(', ')
|
|
356
|
+
}
|
|
357
|
+
]);
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (this.isEscCancelled(error)) return;
|
|
360
|
+
throw error;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const args = updates.args.trim() ? updates.args.trim().split(/\s+/) : [];
|
|
364
|
+
const env = {};
|
|
365
|
+
if (updates.env.trim()) {
|
|
366
|
+
updates.env.split(',').forEach(pair => {
|
|
367
|
+
const [key, ...valueParts] = pair.trim().split('=');
|
|
368
|
+
if (key) {
|
|
369
|
+
env[key.trim()] = valueParts.join('=').trim();
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
data.mcpServers[name] = {
|
|
375
|
+
command: updates.command.trim(),
|
|
376
|
+
args,
|
|
377
|
+
env,
|
|
378
|
+
type: updates.type
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
await this._writeClaudeJson(data);
|
|
382
|
+
Logger.success(`MCP 服务器 '${name}' 已更新。`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* 删除 MCP 服务器
|
|
387
|
+
*/
|
|
388
|
+
async remove() {
|
|
389
|
+
const data = await this._readClaudeJson();
|
|
390
|
+
const servers = this._getMcpServers(data);
|
|
391
|
+
const names = Object.keys(servers);
|
|
392
|
+
|
|
393
|
+
if (names.length === 0) {
|
|
394
|
+
Logger.warning('暂无 MCP 服务器配置。');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
let selection;
|
|
399
|
+
try {
|
|
400
|
+
selection = await this.prompt([
|
|
401
|
+
{
|
|
402
|
+
type: 'checkbox',
|
|
403
|
+
name: 'names',
|
|
404
|
+
message: '选择要删除的 MCP 服务器:',
|
|
405
|
+
choices: names.map(n => ({
|
|
406
|
+
name: `${n} ${chalk.gray('(' + (servers[n].command || '') + ' ' + (servers[n].args || []).join(' ') + ')')}`,
|
|
407
|
+
value: n
|
|
408
|
+
})),
|
|
409
|
+
validate: input => input.length > 0 ? true : '请至少选择一个'
|
|
410
|
+
}
|
|
411
|
+
]);
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (this.isEscCancelled(error)) return;
|
|
414
|
+
throw error;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// 二次确认
|
|
418
|
+
let confirm;
|
|
419
|
+
try {
|
|
420
|
+
confirm = await this.prompt([
|
|
421
|
+
{
|
|
422
|
+
type: 'confirm',
|
|
423
|
+
name: 'ok',
|
|
424
|
+
message: `确认删除 ${selection.names.length} 个 MCP 服务器: ${selection.names.join(', ')}?`,
|
|
425
|
+
default: false
|
|
426
|
+
}
|
|
427
|
+
]);
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (this.isEscCancelled(error)) return;
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (!confirm.ok) {
|
|
434
|
+
Logger.info('操作已取消。');
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
selection.names.forEach(name => {
|
|
439
|
+
delete data.mcpServers[name];
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
await this._writeClaudeJson(data);
|
|
443
|
+
Logger.success(`已删除 ${selection.names.length} 个 MCP 服务器: ${selection.names.join(', ')}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const VALID_SUBCOMMANDS = ['list', 'add', 'edit', 'remove'];
|
|
448
|
+
|
|
449
|
+
async function mcpCommand(subcommand) {
|
|
450
|
+
const manager = new McpManager();
|
|
451
|
+
try {
|
|
452
|
+
if (!VALID_SUBCOMMANDS.includes(subcommand)) {
|
|
453
|
+
Logger.error(`未知子命令: ${subcommand}`);
|
|
454
|
+
console.log(chalk.gray(` 可用子命令: ${VALID_SUBCOMMANDS.join(', ')}`));
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
await manager[subcommand]();
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (!manager.isEscCancelled(error)) {
|
|
460
|
+
Logger.error(`MCP 操作失败: ${error.message}`);
|
|
461
|
+
}
|
|
462
|
+
} finally {
|
|
463
|
+
manager.destroy();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
module.exports = { mcpCommand, McpManager, MCP_PRESETS };
|
package/src/commands/switch.js
CHANGED
|
@@ -17,7 +17,7 @@ const { findSettingsConflict, backupSettingsFile, clearConflictKeys, saveSetting
|
|
|
17
17
|
const { BaseCommand } = require('./BaseCommand');
|
|
18
18
|
const { validator } = require('../utils/validator');
|
|
19
19
|
const { ProviderStatusChecker } = require('../utils/provider-status-checker');
|
|
20
|
-
const { AUTH_MODE_DISPLAY,
|
|
20
|
+
const { AUTH_MODE_DISPLAY, BASE_URL } = require('../constants');
|
|
21
21
|
const { LaunchArgsHelper } = require('./switch/launch-args-helper');
|
|
22
22
|
const { StatusHelper } = require('./switch/status-helper');
|
|
23
23
|
|
|
@@ -1238,15 +1238,9 @@ class EnvSwitcher extends BaseCommand {
|
|
|
1238
1238
|
['认证模式', AUTH_MODE_DISPLAY[provider.authMode] || provider.authMode]
|
|
1239
1239
|
];
|
|
1240
1240
|
|
|
1241
|
-
// 如果是 api_key 模式,添加 tokenType 信息
|
|
1242
|
-
if (provider.authMode === 'api_key' && provider.tokenType) {
|
|
1243
|
-
const tokenTypeDisplay = TOKEN_TYPE_DISPLAY[provider.tokenType];
|
|
1244
|
-
details.push(['Token类型', tokenTypeDisplay]);
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
1241
|
// 继续添加其他信息
|
|
1248
1242
|
const baseUrlDisplay = provider.baseUrl
|
|
1249
|
-
|| (
|
|
1243
|
+
|| (provider.authMode === 'auth_token'
|
|
1250
1244
|
? BASE_URL.OFFICIAL_DEFAULT
|
|
1251
1245
|
: '⚠️ 未设置');
|
|
1252
1246
|
details.push(
|
|
@@ -1385,23 +1379,11 @@ class EnvSwitcher extends BaseCommand {
|
|
|
1385
1379
|
name: 'authMode',
|
|
1386
1380
|
message: '认证模式:',
|
|
1387
1381
|
choices: [
|
|
1388
|
-
{ name: '🔑
|
|
1389
|
-
{ name: '🔐
|
|
1390
|
-
{ name: '🌐 OAuth令牌模式 (CLAUDE_CODE_OAUTH_TOKEN) - 适用于官方Claude Code', value: 'oauth_token' }
|
|
1382
|
+
{ name: '🔑 ANTHROPIC_API_KEY - 大多数第三方代理使用', value: 'api_key' },
|
|
1383
|
+
{ name: '🔐 ANTHROPIC_AUTH_TOKEN - 部分服务商使用', value: 'auth_token' }
|
|
1391
1384
|
],
|
|
1392
1385
|
default: provider.authMode || 'api_key'
|
|
1393
1386
|
},
|
|
1394
|
-
{
|
|
1395
|
-
type: 'list',
|
|
1396
|
-
name: 'tokenType',
|
|
1397
|
-
message: 'Token类型:',
|
|
1398
|
-
choices: [
|
|
1399
|
-
{ name: '🔑 ANTHROPIC_API_KEY - 通用API密钥', value: 'api_key' },
|
|
1400
|
-
{ name: '🔐 ANTHROPIC_AUTH_TOKEN - 认证令牌', value: 'auth_token' }
|
|
1401
|
-
],
|
|
1402
|
-
default: provider.tokenType || 'api_key',
|
|
1403
|
-
when: (answers) => answers.authMode === 'api_key'
|
|
1404
|
-
},
|
|
1405
1387
|
{
|
|
1406
1388
|
type: 'input',
|
|
1407
1389
|
name: 'primaryModel',
|
|
@@ -1447,17 +1429,8 @@ class EnvSwitcher extends BaseCommand {
|
|
|
1447
1429
|
if (isCodex) {
|
|
1448
1430
|
return 'API Key (OPENAI_API_KEY):';
|
|
1449
1431
|
}
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
const tokenTypeLabel = answers.tokenType === 'auth_token' ? 'ANTHROPIC_AUTH_TOKEN' : 'ANTHROPIC_API_KEY';
|
|
1453
|
-
return `Token (${tokenTypeLabel}):`;
|
|
1454
|
-
case 'auth_token':
|
|
1455
|
-
return '认证令牌 (ANTHROPIC_AUTH_TOKEN):';
|
|
1456
|
-
case 'oauth_token':
|
|
1457
|
-
return 'OAuth令牌 (CLAUDE_CODE_OAUTH_TOKEN):';
|
|
1458
|
-
default:
|
|
1459
|
-
return '认证令牌:';
|
|
1460
|
-
}
|
|
1432
|
+
const envVar = answers.authMode === 'auth_token' ? 'ANTHROPIC_AUTH_TOKEN' : 'ANTHROPIC_API_KEY';
|
|
1433
|
+
return `Token (${envVar}):`;
|
|
1461
1434
|
},
|
|
1462
1435
|
default: provider.authToken,
|
|
1463
1436
|
prefillDefault: true
|
|
@@ -1506,16 +1479,12 @@ class EnvSwitcher extends BaseCommand {
|
|
|
1506
1479
|
// 更新供应商配置
|
|
1507
1480
|
provider.displayName = answers.displayName || newName;
|
|
1508
1481
|
provider.alias = answers.alias || null;
|
|
1509
|
-
|
|
1510
|
-
provider.baseUrl = answers.authMode === 'oauth_token' ? null : answers.baseUrl;
|
|
1482
|
+
provider.baseUrl = answers.baseUrl;
|
|
1511
1483
|
provider.authToken = answers.authToken;
|
|
1512
1484
|
|
|
1513
1485
|
// Claude Code 特定的更新
|
|
1514
1486
|
if (!isCodex) {
|
|
1515
1487
|
provider.authMode = answers.authMode;
|
|
1516
|
-
if (answers.tokenType) {
|
|
1517
|
-
provider.tokenType = answers.tokenType; // 仅在 authMode 为 'api_key' 时使用
|
|
1518
|
-
}
|
|
1519
1488
|
|
|
1520
1489
|
// 更新模型配置
|
|
1521
1490
|
if (!provider.models) {
|
|
@@ -1526,7 +1495,6 @@ class EnvSwitcher extends BaseCommand {
|
|
|
1526
1495
|
} else {
|
|
1527
1496
|
// 确保 Codex 配置不包含 Claude 特定字段
|
|
1528
1497
|
provider.authMode = null;
|
|
1529
|
-
provider.tokenType = null;
|
|
1530
1498
|
provider.models = null;
|
|
1531
1499
|
}
|
|
1532
1500
|
|
package/src/config.js
CHANGED
|
@@ -11,7 +11,6 @@ const chalk = require('chalk');
|
|
|
11
11
|
* @property {string} authMode - 认证模式
|
|
12
12
|
* @property {string} authToken - 认证令牌
|
|
13
13
|
* @property {string|null} baseUrl - API 基础 URL
|
|
14
|
-
* @property {string|null} tokenType - Token 类型
|
|
15
14
|
* @property {Object|null} models - 模型配置
|
|
16
15
|
* @property {string[]} launchArgs - 启动参数
|
|
17
16
|
* @property {boolean} current - 是否为当前供应商
|
|
@@ -320,9 +319,6 @@ class ConfigManager {
|
|
|
320
319
|
// Claude Code 特定字段
|
|
321
320
|
if (!isCodex) {
|
|
322
321
|
const authMode = providerConfig.authMode || existing?.authMode || 'api_key';
|
|
323
|
-
const tokenType = authMode === 'api_key'
|
|
324
|
-
? (providerConfig.tokenType ?? existing?.tokenType ?? 'api_key')
|
|
325
|
-
: null;
|
|
326
322
|
const primaryModel = providerConfig.primaryModel !== undefined
|
|
327
323
|
? providerConfig.primaryModel
|
|
328
324
|
: (existing?.models?.primary ?? null);
|
|
@@ -331,7 +327,6 @@ class ConfigManager {
|
|
|
331
327
|
: (existing?.models?.smallFast ?? null);
|
|
332
328
|
|
|
333
329
|
this.config.providers[name].authMode = authMode;
|
|
334
|
-
this.config.providers[name].tokenType = tokenType;
|
|
335
330
|
this.config.providers[name].models = {
|
|
336
331
|
primary: primaryModel,
|
|
337
332
|
smallFast: smallFastModel
|
|
@@ -339,7 +334,6 @@ class ConfigManager {
|
|
|
339
334
|
} else {
|
|
340
335
|
// Codex 不需要这些字段,设置为 null 以保持向后兼容
|
|
341
336
|
this.config.providers[name].authMode = null;
|
|
342
|
-
this.config.providers[name].tokenType = null;
|
|
343
337
|
this.config.providers[name].models = null;
|
|
344
338
|
}
|
|
345
339
|
|