@pikecode/api-key-manager 1.0.45 → 1.1.1
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 +22 -201
- 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/codex-files.js +49 -113
- package/src/utils/env-launcher.js +8 -16
- package/src/utils/provider-status-checker.js +0 -4
package/src/constants/index.js
CHANGED
|
@@ -9,32 +9,22 @@
|
|
|
9
9
|
* 认证模式的显示名称映射
|
|
10
10
|
*/
|
|
11
11
|
const AUTH_MODE_DISPLAY = {
|
|
12
|
-
api_key: '
|
|
13
|
-
auth_token: '
|
|
14
|
-
oauth_token: 'OAuth令牌模式'
|
|
12
|
+
api_key: 'API Key 模式',
|
|
13
|
+
auth_token: 'Auth Token 模式'
|
|
15
14
|
};
|
|
16
15
|
|
|
17
16
|
/**
|
|
18
17
|
* 认证模式详细说明(用于添加供应商时)
|
|
19
18
|
*/
|
|
20
19
|
const AUTH_MODE_DISPLAY_DETAILED = {
|
|
21
|
-
api_key: '
|
|
22
|
-
auth_token: '
|
|
23
|
-
oauth_token: 'OAuth令牌模式 (CLAUDE_CODE_OAUTH_TOKEN)'
|
|
20
|
+
api_key: 'API Key 模式 (ANTHROPIC_API_KEY)',
|
|
21
|
+
auth_token: 'Auth Token 模式 (ANTHROPIC_AUTH_TOKEN)'
|
|
24
22
|
};
|
|
25
23
|
|
|
26
24
|
/**
|
|
27
25
|
* 所有支持的认证模式列表
|
|
28
26
|
*/
|
|
29
|
-
const AUTH_MODES = ['api_key', 'auth_token'
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Token 类型显示映射
|
|
33
|
-
*/
|
|
34
|
-
const TOKEN_TYPE_DISPLAY = {
|
|
35
|
-
auth_token: 'ANTHROPIC_AUTH_TOKEN',
|
|
36
|
-
api_key: 'ANTHROPIC_API_KEY'
|
|
37
|
-
};
|
|
27
|
+
const AUTH_MODES = ['api_key', 'auth_token'];
|
|
38
28
|
|
|
39
29
|
// ============ ESC 返回提示常量 ============
|
|
40
30
|
|
|
@@ -202,8 +192,7 @@ const ENV_VARS = {
|
|
|
202
192
|
ANTHROPIC_AUTH_TOKEN: 'ANTHROPIC_AUTH_TOKEN',
|
|
203
193
|
ANTHROPIC_API_KEY: 'ANTHROPIC_API_KEY',
|
|
204
194
|
OPENAI_API_KEY: 'OPENAI_API_KEY',
|
|
205
|
-
OPENAI_BASE_URL: 'OPENAI_BASE_URL'
|
|
206
|
-
CLAUDE_CODE_OAUTH_TOKEN: 'CLAUDE_CODE_OAUTH_TOKEN'
|
|
195
|
+
OPENAI_BASE_URL: 'OPENAI_BASE_URL'
|
|
207
196
|
};
|
|
208
197
|
|
|
209
198
|
module.exports = {
|
|
@@ -211,7 +200,6 @@ module.exports = {
|
|
|
211
200
|
AUTH_MODE_DISPLAY,
|
|
212
201
|
AUTH_MODE_DISPLAY_DETAILED,
|
|
213
202
|
AUTH_MODES,
|
|
214
|
-
TOKEN_TYPE_DISPLAY,
|
|
215
203
|
|
|
216
204
|
// ESC 提示
|
|
217
205
|
ESC_HINTS,
|
package/src/utils/codex-files.js
CHANGED
|
@@ -87,102 +87,45 @@ async function backupCodexFiles(codexHome = resolveCodexHome()) {
|
|
|
87
87
|
return backupDir;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
async function applyCodexProfile(profile, options = {}) {
|
|
91
|
-
if (!profile || (profile.configToml == null && profile.authJson == null)) {
|
|
92
|
-
throw new Error('Codex 配置为空,无法切换');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const codexHome = await ensureCodexHome(profile.codexHome || options.codexHome);
|
|
96
|
-
const { configTomlPath, authJsonPath } = buildCodexPaths(codexHome);
|
|
97
|
-
|
|
98
|
-
const backupDir = await backupCodexFiles(codexHome);
|
|
99
|
-
|
|
100
|
-
if (profile.configToml != null) {
|
|
101
|
-
await fs.writeFile(configTomlPath, profile.configToml, 'utf8');
|
|
102
|
-
await setSecurePermissions(configTomlPath);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (profile.authJson != null) {
|
|
106
|
-
await fs.writeFile(authJsonPath, profile.authJson, 'utf8');
|
|
107
|
-
await setSecurePermissions(authJsonPath);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return { codexHome, backupDir };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
90
|
/**
|
|
114
|
-
*
|
|
91
|
+
* 移除 config.toml 顶层(第一个 [section] 之前)的 api_base_url 字段
|
|
92
|
+
* 只清理顶层,不影响 [model_providers.xxx] section 内的 base_url
|
|
115
93
|
* @param {string} configToml - 现有的 config.toml 内容
|
|
116
94
|
* @returns {string} 更新后的 config.toml 内容
|
|
117
95
|
*/
|
|
118
|
-
function
|
|
119
|
-
if (!configToml)
|
|
120
|
-
// 如果没有 config.toml,创建一个最小配置
|
|
121
|
-
return 'preferred_auth_method = "apikey"\n';
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// 移除所有已存在的 preferred_auth_method 行(防止重复)
|
|
125
|
-
const authMethodRegex = /^preferred_auth_method\s*=\s*["']?[^"'\n]*["']?\s*\n?/gm;
|
|
126
|
-
let cleanedConfig = configToml.replace(authMethodRegex, '');
|
|
96
|
+
function removeTopLevelApiBaseUrl(configToml) {
|
|
97
|
+
if (!configToml) return configToml;
|
|
127
98
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
99
|
+
const sectionStart = configToml.search(/^\[/m);
|
|
100
|
+
const topLevel = sectionStart === -1 ? configToml : configToml.slice(0, sectionStart);
|
|
101
|
+
const rest = sectionStart === -1 ? '' : configToml.slice(sectionStart);
|
|
131
102
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
// 如果清理后为空,直接返回新配置
|
|
136
|
-
if (!cleanedConfig.trim()) {
|
|
137
|
-
return newLine;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return newLine + cleanedConfig;
|
|
103
|
+
const cleaned = topLevel.replace(/^api_base_url\s*=\s*["']?[^"'\n]*["']?\s*\n?/m, '');
|
|
104
|
+
return cleaned + rest;
|
|
141
105
|
}
|
|
142
106
|
|
|
143
107
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
* @param {string
|
|
147
|
-
* @returns {string}
|
|
108
|
+
* 从 config.toml 中提取当前激活的 model_provider 的 base_url
|
|
109
|
+
* 读取 model_provider 字段,再从对应的 [model_providers.<key>] section 中取 base_url
|
|
110
|
+
* @param {string} configToml - config.toml 内容
|
|
111
|
+
* @returns {string|null} base_url 或 null
|
|
148
112
|
*/
|
|
149
|
-
function
|
|
150
|
-
if (!configToml)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
// 空配置,直接返回新行
|
|
166
|
-
if (configToml.length === 0) {
|
|
167
|
-
return newLine + '\n';
|
|
168
|
-
}
|
|
169
|
-
// 找到第一个 section [xxx],在它之前插入
|
|
170
|
-
const sectionMatch = configToml.match(/^\[/m);
|
|
171
|
-
if (sectionMatch) {
|
|
172
|
-
const insertPos = configToml.indexOf(sectionMatch[0]);
|
|
173
|
-
const before = configToml.slice(0, insertPos);
|
|
174
|
-
const after = configToml.slice(insertPos);
|
|
175
|
-
// 确保前面有换行分隔
|
|
176
|
-
const separator = before.endsWith('\n') ? '' : '\n';
|
|
177
|
-
return before + separator + newLine + '\n\n' + after;
|
|
178
|
-
}
|
|
179
|
-
// 没有 section,在文件末尾添加
|
|
180
|
-
const separator = configToml.endsWith('\n') ? '' : '\n';
|
|
181
|
-
return configToml + separator + newLine + '\n';
|
|
182
|
-
} else {
|
|
183
|
-
// 移除 api_base_url(使用官方 API)
|
|
184
|
-
return configToml.replace(baseUrlRegex, '');
|
|
185
|
-
}
|
|
113
|
+
function extractBaseUrlFromConfigToml(configToml) {
|
|
114
|
+
if (!configToml) return null;
|
|
115
|
+
|
|
116
|
+
// 读取 model_provider = "xxx"
|
|
117
|
+
const providerMatch = configToml.match(/^model_provider\s*=\s*["']([^"']+)["']/m);
|
|
118
|
+
if (!providerMatch) return null;
|
|
119
|
+
|
|
120
|
+
const providerKey = providerMatch[1];
|
|
121
|
+
|
|
122
|
+
// 在对应 section 中找 base_url
|
|
123
|
+
const sectionRegex = new RegExp(
|
|
124
|
+
`\\[model_providers\\.${providerKey}\\][^\\[]*base_url\\s*=\\s*["']([^"']+)["']`,
|
|
125
|
+
's'
|
|
126
|
+
);
|
|
127
|
+
const urlMatch = configToml.match(sectionRegex);
|
|
128
|
+
return urlMatch ? urlMatch[1] : null;
|
|
186
129
|
}
|
|
187
130
|
|
|
188
131
|
/**
|
|
@@ -195,11 +138,14 @@ function buildAuthJson(apiKey) {
|
|
|
195
138
|
}
|
|
196
139
|
|
|
197
140
|
/**
|
|
198
|
-
* 应用 Codex 配置(写入 config.toml
|
|
199
|
-
*
|
|
141
|
+
* 应用 Codex 配置(写入 auth.json,清理 config.toml 中的无效字段)
|
|
142
|
+
* config.toml 由用户自己管理,akm 只负责:
|
|
143
|
+
* 1. 写入 auth.json(API Key)
|
|
144
|
+
* 2. 清理之前错误写入的顶层 api_base_url 字段
|
|
145
|
+
* base_url 通过环境变量 OPENAI_BASE_URL 传递给 Codex 进程
|
|
200
146
|
* @param {object} config - 供应商配置
|
|
201
147
|
* @param {object} options - 选项
|
|
202
|
-
* @returns {Promise<{codexHome: string
|
|
148
|
+
* @returns {Promise<{codexHome: string}>}
|
|
203
149
|
*/
|
|
204
150
|
async function applyCodexConfig(config, options = {}) {
|
|
205
151
|
if (!config || !config.authToken) {
|
|
@@ -209,41 +155,31 @@ async function applyCodexConfig(config, options = {}) {
|
|
|
209
155
|
const codexHome = await ensureCodexHome(options.codexHome);
|
|
210
156
|
const { configTomlPath, authJsonPath } = buildCodexPaths(codexHome);
|
|
211
157
|
|
|
212
|
-
//
|
|
213
|
-
const backupDir = await backupCodexFiles(codexHome);
|
|
214
|
-
|
|
215
|
-
// 读取现有 config.toml
|
|
216
|
-
let existingConfigToml = null;
|
|
217
|
-
if (await fs.pathExists(configTomlPath)) {
|
|
218
|
-
existingConfigToml = await fs.readFile(configTomlPath, 'utf8');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// 确保设置了 preferred_auth_method = "apikey"
|
|
222
|
-
let updatedConfigToml = ensureApiKeyAuthMethod(existingConfigToml);
|
|
223
|
-
|
|
224
|
-
// 更新 api_base_url(如果有则设置,没有则移除)
|
|
225
|
-
updatedConfigToml = updateApiBaseUrl(updatedConfigToml, config.baseUrl || null);
|
|
226
|
-
|
|
227
|
-
await fs.writeFile(configTomlPath, updatedConfigToml, 'utf8');
|
|
228
|
-
await setSecurePermissions(configTomlPath);
|
|
229
|
-
|
|
230
|
-
// 写入 auth.json
|
|
158
|
+
// 写入 auth.json(API Key)
|
|
231
159
|
const authJsonContent = buildAuthJson(config.authToken);
|
|
232
160
|
await fs.writeFile(authJsonPath, authJsonContent, 'utf8');
|
|
233
161
|
await setSecurePermissions(authJsonPath);
|
|
234
162
|
|
|
235
|
-
|
|
163
|
+
// 清理 config.toml 中 akm 之前错误写入的顶层 api_base_url 字段
|
|
164
|
+
if (await fs.pathExists(configTomlPath)) {
|
|
165
|
+
const existingToml = await fs.readFile(configTomlPath, 'utf8');
|
|
166
|
+
const cleanedToml = removeTopLevelApiBaseUrl(existingToml);
|
|
167
|
+
if (cleanedToml !== existingToml) {
|
|
168
|
+
await fs.writeFile(configTomlPath, cleanedToml, 'utf8');
|
|
169
|
+
await setSecurePermissions(configTomlPath);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return { codexHome };
|
|
236
174
|
}
|
|
237
175
|
|
|
238
176
|
module.exports = {
|
|
239
177
|
resolveCodexHome,
|
|
240
178
|
buildCodexPaths,
|
|
241
179
|
readCodexFiles,
|
|
242
|
-
applyCodexProfile,
|
|
243
180
|
applyCodexConfig,
|
|
244
181
|
backupCodexFiles,
|
|
245
|
-
|
|
246
|
-
|
|
182
|
+
removeTopLevelApiBaseUrl,
|
|
183
|
+
extractBaseUrlFromConfigToml,
|
|
247
184
|
buildAuthJson
|
|
248
185
|
};
|
|
249
|
-
|
|
@@ -6,26 +6,18 @@ function buildEnvVariables(config) {
|
|
|
6
6
|
|
|
7
7
|
try {
|
|
8
8
|
// Claude Code 配置
|
|
9
|
-
if (config.authMode === '
|
|
10
|
-
env.CLAUDE_CODE_OAUTH_TOKEN = sanitizeEnvValue(config.authToken);
|
|
11
|
-
} else if (config.authMode === 'api_key') {
|
|
12
|
-
if (!config.baseUrl) {
|
|
13
|
-
throw new Error('未配置基础地址');
|
|
14
|
-
}
|
|
15
|
-
env.ANTHROPIC_BASE_URL = sanitizeEnvValue(config.baseUrl);
|
|
16
|
-
// 根据 tokenType 选择设置哪种 token
|
|
17
|
-
if (config.tokenType === 'auth_token') {
|
|
18
|
-
env.ANTHROPIC_AUTH_TOKEN = sanitizeEnvValue(config.authToken);
|
|
19
|
-
} else {
|
|
20
|
-
// 默认使用 ANTHROPIC_API_KEY
|
|
21
|
-
env.ANTHROPIC_API_KEY = sanitizeEnvValue(config.authToken);
|
|
22
|
-
}
|
|
23
|
-
} else {
|
|
24
|
-
// auth_token 模式
|
|
9
|
+
if (config.authMode === 'auth_token') {
|
|
25
10
|
if (config.baseUrl) {
|
|
26
11
|
env.ANTHROPIC_BASE_URL = sanitizeEnvValue(config.baseUrl);
|
|
27
12
|
}
|
|
28
13
|
env.ANTHROPIC_AUTH_TOKEN = sanitizeEnvValue(config.authToken);
|
|
14
|
+
} else {
|
|
15
|
+
// api_key 模式(默认)
|
|
16
|
+
if (!config.baseUrl) {
|
|
17
|
+
throw new Error('未配置基础地址');
|
|
18
|
+
}
|
|
19
|
+
env.ANTHROPIC_BASE_URL = sanitizeEnvValue(config.baseUrl);
|
|
20
|
+
env.ANTHROPIC_API_KEY = sanitizeEnvValue(config.authToken);
|
|
29
21
|
}
|
|
30
22
|
|
|
31
23
|
if (config.models && config.models.primary) {
|
|
@@ -102,10 +102,6 @@ class ProviderStatusChecker {
|
|
|
102
102
|
return result;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
if (provider.authMode === 'oauth_token') {
|
|
106
|
-
return this._result('unknown', '暂不支持 OAuth 令牌检测', null);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
105
|
// auth_token 模式可留空 baseUrl(使用官方默认 API)
|
|
110
106
|
// api_key 模式需要 baseUrl(用于自定义端点/代理)
|
|
111
107
|
if (provider.authMode === 'auth_token' && !provider.baseUrl) {
|