@pikecode/api-key-manager 1.1.0 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikecode/api-key-manager",
3
- "version": "1.1.00",
3
+ "version": "1.1.1",
4
4
  "description": "A CLI tool for managing and switching multiple API provider configurations",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -390,7 +390,7 @@ class ProviderAdder extends BaseCommand {
390
390
 
391
391
  async importCodexConfig() {
392
392
  try {
393
- const { readCodexFiles } = require('../utils/codex-files');
393
+ const { readCodexFiles, extractBaseUrlFromConfigToml } = require('../utils/codex-files');
394
394
  const codexFiles = await readCodexFiles();
395
395
 
396
396
  if (!codexFiles.authJson) {
@@ -405,19 +405,15 @@ class ProviderAdder extends BaseCommand {
405
405
  return null;
406
406
  }
407
407
 
408
- // 尝试从 config.toml 获取 base URL
409
- // 支持 api_base_url(akm 写入的格式)和 api_base(某些旧配置可能使用)
408
+ // config.toml 中读取当前激活 provider 的 base_url
409
+ // 优先从 [model_providers.<key>] section 读取,兼容旧的顶层 api_base_url 格式
410
410
  let baseUrl = null;
411
411
  if (codexFiles.configToml) {
412
- const baseUrlMatch = codexFiles.configToml.match(/api_base_url\s*=\s*["']([^"']+)["']/);
413
- if (baseUrlMatch) {
414
- baseUrl = baseUrlMatch[1];
415
- } else {
416
- // 兼容旧格式
417
- const legacyMatch = codexFiles.configToml.match(/api_base\s*=\s*["']([^"']+)["']/);
418
- if (legacyMatch) {
419
- baseUrl = legacyMatch[1];
420
- }
412
+ baseUrl = extractBaseUrlFromConfigToml(codexFiles.configToml);
413
+ if (!baseUrl) {
414
+ // 兼容旧格式(akm 之前错误写入的顶层字段)
415
+ const legacyMatch = codexFiles.configToml.match(/^api_base_url\s*=\s*["']([^"']+)["']/m);
416
+ if (legacyMatch) baseUrl = legacyMatch[1];
421
417
  }
422
418
  }
423
419
 
@@ -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
- * 确保 config.toml 中设置了 preferred_auth_method = "apikey"
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 ensureApiKeyAuthMethod(configToml) {
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
- cleanedConfig = cleanedConfig.replace(/^[{}]+\s*\n?/gm, '');
130
- cleanedConfig = cleanedConfig.replace(/^\n{3,}/gm, '\n\n');
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
- // 在文件开头添加 preferred_auth_method
133
- const newLine = 'preferred_auth_method = "apikey"\n';
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
- * 更新 config.toml 中的 api_base_url
145
- * @param {string} configToml - 现有的 config.toml 内容
146
- * @param {string|null} baseUrl - API base URL,null 时移除该配置
147
- * @returns {string} 更新后的 config.toml 内容
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 updateApiBaseUrl(configToml, baseUrl) {
150
- if (!configToml) {
151
- configToml = '';
152
- }
153
-
154
- // 匹配 api_base_url 配置行
155
- const baseUrlRegex = /^api_base_url\s*=\s*["']?[^"'\n]*["']?\s*\n?/m;
156
-
157
- if (baseUrl) {
158
- // 需要设置 api_base_url
159
- const newLine = `api_base_url = "${baseUrl}"`;
160
-
161
- if (configToml.match(baseUrlRegex)) {
162
- // 替换现有的
163
- return configToml.replace(baseUrlRegex, newLine + '\n');
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 和 auth.json)
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, backupDir: string|null}>}
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
- return { codexHome, backupDir };
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
- ensureApiKeyAuthMethod,
246
- updateApiBaseUrl,
182
+ removeTopLevelApiBaseUrl,
183
+ extractBaseUrlFromConfigToml,
247
184
  buildAuthJson
248
185
  };
249
-