coding-tool-x 3.3.6 → 3.3.8
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/CHANGELOG.md +14 -0
- package/dist/web/assets/{Analytics-TtaduRqL.js → Analytics-DLpoDZ2M.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-BP2lLBMN.js → ConfigTemplates-D_hRb55W.js} +1 -1
- package/dist/web/assets/Home-BMoFdAwy.css +1 -0
- package/dist/web/assets/Home-DNwp-0J-.js +1 -0
- package/dist/web/assets/{PluginManager-HmISlyMK.js → PluginManager-JXsyym1s.js} +1 -1
- package/dist/web/assets/{ProjectList-DoN8Hjbu.js → ProjectList-DZWSeb-q.js} +1 -1
- package/dist/web/assets/{SessionList-Da8BYzNi.js → SessionList-Cs624DR3.js} +1 -1
- package/dist/web/assets/{SkillManager-DqLAXh9o.js → SkillManager-bEliz7qz.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-B_TxOgPW.js → WorkspaceManager-J3RecFGn.js} +1 -1
- package/dist/web/assets/{icons-B29onFfZ.js → icons-Cuc23WS7.js} +1 -1
- package/dist/web/assets/index-BXeSvAwU.js +2 -0
- package/dist/web/assets/index-DWAC3Tdv.css +1 -0
- package/dist/web/index.html +3 -3
- package/package.json +3 -2
- package/src/commands/daemon.js +44 -6
- package/src/commands/toggle-proxy.js +100 -5
- package/src/config/default.js +1 -1
- package/src/config/model-metadata.js +2 -2
- package/src/config/model-metadata.json +7 -2
- package/src/config/paths.js +102 -19
- package/src/server/api/channels.js +9 -0
- package/src/server/api/codex-channels.js +9 -0
- package/src/server/api/codex-proxy.js +22 -11
- package/src/server/api/gemini-proxy.js +22 -11
- package/src/server/api/mcp.js +26 -4
- package/src/server/api/oauth-credentials.js +163 -0
- package/src/server/api/opencode-proxy.js +22 -10
- package/src/server/api/plugins.js +3 -1
- package/src/server/api/proxy.js +39 -44
- package/src/server/api/skills.js +91 -13
- package/src/server/codex-proxy-server.js +1 -11
- package/src/server/index.js +26 -2
- package/src/server/services/channels.js +18 -22
- package/src/server/services/codex-channels.js +124 -175
- package/src/server/services/codex-config.js +2 -5
- package/src/server/services/codex-settings-manager.js +12 -348
- package/src/server/services/config-export-service.js +572 -117
- package/src/server/services/gemini-channels.js +11 -9
- package/src/server/services/mcp-client.js +70 -13
- package/src/server/services/mcp-service.js +74 -29
- package/src/server/services/model-detector.js +1 -0
- package/src/server/services/native-keychain.js +243 -0
- package/src/server/services/native-oauth-adapters.js +890 -0
- package/src/server/services/oauth-credentials-service.js +786 -0
- package/src/server/services/oauth-utils.js +49 -0
- package/src/server/services/opencode-channels.js +13 -9
- package/src/server/services/opencode-settings-manager.js +169 -16
- package/src/server/services/plugins-service.js +22 -1
- package/src/server/services/settings-manager.js +13 -0
- package/src/server/services/skill-service.js +712 -332
- package/src/utils/port-helper.js +87 -2
- package/dist/web/assets/Home-BsSioaaB.css +0 -1
- package/dist/web/assets/Home-CbbyopS-.js +0 -1
- package/dist/web/assets/index-By3mDEvx.js +0 -2
- package/dist/web/assets/index-CsWInMQV.css +0 -1
|
@@ -6,7 +6,8 @@ const toml = require('toml');
|
|
|
6
6
|
const tomlStringify = require('@iarna/toml').stringify;
|
|
7
7
|
const { resolvePreferredHomeDir } = require('../../utils/home-dir');
|
|
8
8
|
const { getCodexDir } = require('./codex-config');
|
|
9
|
-
const {
|
|
9
|
+
const { isProxyConfig } = require('./codex-settings-manager');
|
|
10
|
+
const { clearNativeOAuth } = require('./native-oauth-adapters');
|
|
10
11
|
|
|
11
12
|
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
12
13
|
|
|
@@ -129,13 +130,7 @@ function initializeFromConfig() {
|
|
|
129
130
|
updatedAt: Date.now()
|
|
130
131
|
});
|
|
131
132
|
|
|
132
|
-
//
|
|
133
|
-
if (apiKey && envKey) {
|
|
134
|
-
const injectResult = injectEnvToShell(envKey, apiKey);
|
|
135
|
-
if (injectResult.success) {
|
|
136
|
-
console.log(`[Codex Channels] Environment variable ${envKey} injected during initialization`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
133
|
+
// auth.json 已写入 API Key,Codex 启动时优先读取 auth.json,无需注入 shell
|
|
139
134
|
}
|
|
140
135
|
}
|
|
141
136
|
|
|
@@ -158,6 +153,86 @@ function saveChannels(data) {
|
|
|
158
153
|
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf8');
|
|
159
154
|
}
|
|
160
155
|
|
|
156
|
+
function getDefaultCodexConfig() {
|
|
157
|
+
return {
|
|
158
|
+
model: 'gpt-4',
|
|
159
|
+
model_reasoning_effort: 'high',
|
|
160
|
+
model_reasoning_summary_format: 'experimental',
|
|
161
|
+
network_access: 'enabled',
|
|
162
|
+
disable_response_storage: false,
|
|
163
|
+
show_raw_agent_reasoning: true
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function cloneConfigValue(value) {
|
|
168
|
+
return JSON.parse(JSON.stringify(value));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function readCodexConfigOrThrow(configPath, fallbackConfig = {}) {
|
|
172
|
+
if (!fs.existsSync(configPath)) {
|
|
173
|
+
return cloneConfigValue(fallbackConfig);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
return toml.parse(content);
|
|
180
|
+
} catch (err) {
|
|
181
|
+
throw new Error(`Failed to parse existing config.toml: ${err.message}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function writeTextAtomic(filePath, content) {
|
|
186
|
+
const dirPath = path.dirname(filePath);
|
|
187
|
+
if (!fs.existsSync(dirPath)) {
|
|
188
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
192
|
+
|
|
193
|
+
try {
|
|
194
|
+
fs.writeFileSync(tempPath, content, 'utf8');
|
|
195
|
+
fs.renameSync(tempPath, filePath);
|
|
196
|
+
} finally {
|
|
197
|
+
if (fs.existsSync(tempPath)) {
|
|
198
|
+
try {
|
|
199
|
+
fs.unlinkSync(tempPath);
|
|
200
|
+
} catch {
|
|
201
|
+
// ignore temp cleanup errors
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function writeAnnotatedCodexConfig(configPath, config, headerLines = []) {
|
|
208
|
+
const tomlContent = tomlStringify(config);
|
|
209
|
+
const prefix = headerLines.length > 0 ? `${headerLines.join('\n')}\n\n` : '';
|
|
210
|
+
writeTextAtomic(configPath, `${prefix}${tomlContent}`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function getManagedProviderKeys(channels = []) {
|
|
214
|
+
const keys = new Set(['cc-proxy']);
|
|
215
|
+
for (const channel of channels) {
|
|
216
|
+
if (channel?.providerKey) {
|
|
217
|
+
keys.add(channel.providerKey);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return keys;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function pruneManagedProviders(existingProviders = {}, currentProviderKey, channels = []) {
|
|
224
|
+
const managedProviderKeys = getManagedProviderKeys(channels);
|
|
225
|
+
const preservedProviders = {};
|
|
226
|
+
|
|
227
|
+
for (const [providerKey, providerConfig] of Object.entries(existingProviders)) {
|
|
228
|
+
if (!managedProviderKeys.has(providerKey) || providerKey === currentProviderKey) {
|
|
229
|
+
preservedProviders[providerKey] = providerConfig;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return preservedProviders;
|
|
234
|
+
}
|
|
235
|
+
|
|
161
236
|
// 获取所有渠道
|
|
162
237
|
function getChannels() {
|
|
163
238
|
const data = loadChannels();
|
|
@@ -202,15 +277,7 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi = 'responses'
|
|
|
202
277
|
data.channels.push(newChannel);
|
|
203
278
|
saveChannels(data);
|
|
204
279
|
|
|
205
|
-
//
|
|
206
|
-
if (newChannel.enabled !== false && newChannel.apiKey && envKey) {
|
|
207
|
-
const injectResult = injectEnvToShell(envKey, newChannel.apiKey);
|
|
208
|
-
if (injectResult.success) {
|
|
209
|
-
console.log(`[Codex Channels] Environment variable ${envKey} injected for new channel`);
|
|
210
|
-
} else {
|
|
211
|
-
console.warn(`[Codex Channels] Failed to inject ${envKey}: ${injectResult.error}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
280
|
+
// auth.json 已写入 API Key(通过 writeCodexConfigForMultiChannel),Codex 优先读取 auth.json
|
|
214
281
|
|
|
215
282
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
216
283
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -266,14 +333,6 @@ function updateChannel(channelId, updates) {
|
|
|
266
333
|
console.log(`[Codex Single-channel mode] Enabled "${newChannel.name}", disabled all others`);
|
|
267
334
|
}
|
|
268
335
|
|
|
269
|
-
// Prevent disabling last enabled channel when proxy is OFF
|
|
270
|
-
if (!isProxyRunning && !newChannel.enabled && oldChannel.enabled) {
|
|
271
|
-
const enabledCount = data.channels.filter(ch => ch.enabled).length;
|
|
272
|
-
if (enabledCount === 0) {
|
|
273
|
-
throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
336
|
saveChannels(data);
|
|
278
337
|
|
|
279
338
|
// Sync config.toml only when proxy is OFF.
|
|
@@ -295,20 +354,7 @@ function updateChannel(channelId, updates) {
|
|
|
295
354
|
newChannel.enabled === false
|
|
296
355
|
);
|
|
297
356
|
|
|
298
|
-
//
|
|
299
|
-
if (shouldRemoveOldEnv) {
|
|
300
|
-
const removeResult = removeEnvFromShell(oldEnvKey);
|
|
301
|
-
if (removeResult.success) {
|
|
302
|
-
console.log(`[Codex Channels] Old environment variable ${oldEnvKey} removed`);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
if (newChannel.enabled !== false && newApiKey && newEnvKey) {
|
|
307
|
-
const injectResult = injectEnvToShell(newEnvKey, newApiKey);
|
|
308
|
-
if (injectResult.success) {
|
|
309
|
-
console.log(`[Codex Channels] Environment variable ${newEnvKey} updated`);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
357
|
+
// auth.json 由 applyChannelToSettings/writeCodexConfigForMultiChannel 维护,Codex 优先读取 auth.json
|
|
312
358
|
|
|
313
359
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
314
360
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -329,15 +375,7 @@ async function deleteChannel(channelId) {
|
|
|
329
375
|
data.channels.splice(index, 1);
|
|
330
376
|
saveChannels(data);
|
|
331
377
|
|
|
332
|
-
//
|
|
333
|
-
if (deletedChannel.envKey) {
|
|
334
|
-
const removeResult = removeEnvFromShell(deletedChannel.envKey);
|
|
335
|
-
if (removeResult.success) {
|
|
336
|
-
console.log(`[Codex Channels] Environment variable ${deletedChannel.envKey} removed`);
|
|
337
|
-
} else {
|
|
338
|
-
console.warn(`[Codex Channels] Failed to remove ${deletedChannel.envKey}: ${removeResult.error}`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
378
|
+
// auth.json 中的 key 由 writeCodexConfigForMultiChannel 管理,删除渠道后下次写入时自动清理
|
|
341
379
|
|
|
342
380
|
// 注意:不再自动写入 config.toml,只在开启代理控制时才同步
|
|
343
381
|
// writeCodexConfigForMultiChannel(data.channels);
|
|
@@ -364,38 +402,18 @@ function writeCodexConfigForMultiChannel(allChannels) {
|
|
|
364
402
|
const authPath = path.join(codexDir, 'auth.json');
|
|
365
403
|
|
|
366
404
|
// 读取现有配置,保留所有现有字段(特别是 mcp_servers, projects 等)
|
|
405
|
+
const defaultConfig = getDefaultCodexConfig();
|
|
406
|
+
const parsedConfig = readCodexConfigOrThrow(configPath, defaultConfig);
|
|
367
407
|
let config = {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
408
|
+
...parsedConfig,
|
|
409
|
+
model: parsedConfig.model || defaultConfig.model,
|
|
410
|
+
model_reasoning_effort: parsedConfig.model_reasoning_effort || defaultConfig.model_reasoning_effort,
|
|
411
|
+
model_reasoning_summary_format: parsedConfig.model_reasoning_summary_format || defaultConfig.model_reasoning_summary_format,
|
|
412
|
+
network_access: parsedConfig.network_access || defaultConfig.network_access,
|
|
413
|
+
disable_response_storage: parsedConfig.disable_response_storage !== undefined ? parsedConfig.disable_response_storage : defaultConfig.disable_response_storage,
|
|
414
|
+
show_raw_agent_reasoning: parsedConfig.show_raw_agent_reasoning !== undefined ? parsedConfig.show_raw_agent_reasoning : defaultConfig.show_raw_agent_reasoning
|
|
374
415
|
};
|
|
375
416
|
|
|
376
|
-
if (fs.existsSync(configPath)) {
|
|
377
|
-
try {
|
|
378
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
379
|
-
const parsedConfig = toml.parse(content);
|
|
380
|
-
|
|
381
|
-
// 深度合并,保留原有的所有配置
|
|
382
|
-
config = {
|
|
383
|
-
...parsedConfig,
|
|
384
|
-
// 只覆盖这些字段
|
|
385
|
-
model: parsedConfig.model || config.model,
|
|
386
|
-
model_reasoning_effort: parsedConfig.model_reasoning_effort || config.model_reasoning_effort,
|
|
387
|
-
model_reasoning_summary_format: parsedConfig.model_reasoning_summary_format || config.model_reasoning_summary_format,
|
|
388
|
-
network_access: parsedConfig.network_access || config.network_access,
|
|
389
|
-
disable_response_storage: parsedConfig.disable_response_storage !== undefined ? parsedConfig.disable_response_storage : config.disable_response_storage,
|
|
390
|
-
show_raw_agent_reasoning: parsedConfig.show_raw_agent_reasoning !== undefined ? parsedConfig.show_raw_agent_reasoning : config.show_raw_agent_reasoning,
|
|
391
|
-
// mcp_servers 和 projects 会从 parsedConfig 自动继承
|
|
392
|
-
// model_provider 会根据动态切换情况决定是否更新
|
|
393
|
-
};
|
|
394
|
-
} catch (err) {
|
|
395
|
-
// ignore read error, use defaults
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
|
|
399
417
|
// 判断是否已启用动态切换
|
|
400
418
|
const isProxyMode = config.model_provider === 'cc-proxy';
|
|
401
419
|
const existingProviders = (config && typeof config.model_providers === 'object') ? config.model_providers : {};
|
|
@@ -441,23 +459,11 @@ function writeCodexConfigForMultiChannel(allChannels) {
|
|
|
441
459
|
}
|
|
442
460
|
}
|
|
443
461
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
# Managed by Coding-Tool
|
|
450
|
-
# WARNING: MCP servers and projects are preserved automatically
|
|
451
|
-
|
|
452
|
-
${tomlContent}`;
|
|
453
|
-
|
|
454
|
-
fs.writeFileSync(configPath, annotatedContent, 'utf8');
|
|
455
|
-
} catch (err) {
|
|
456
|
-
console.error('[Codex Channels] Failed to write config with TOML stringify:', err);
|
|
457
|
-
// 降级处理:如果 tomlStringify 失败,使用手工拼接(但这样会丢失注释)
|
|
458
|
-
const fallbackContent = JSON.stringify(config, null, 2);
|
|
459
|
-
fs.writeFileSync(configPath, fallbackContent, 'utf8');
|
|
460
|
-
}
|
|
462
|
+
writeAnnotatedCodexConfig(configPath, config, [
|
|
463
|
+
'# Codex Configuration',
|
|
464
|
+
'# Managed by Coding-Tool',
|
|
465
|
+
'# WARNING: MCP servers and projects are preserved automatically'
|
|
466
|
+
]);
|
|
461
467
|
|
|
462
468
|
// 更新 auth.json
|
|
463
469
|
let auth = {};
|
|
@@ -524,42 +530,8 @@ function saveChannelOrder(order) {
|
|
|
524
530
|
* 这个函数会在服务启动时自动调用
|
|
525
531
|
*/
|
|
526
532
|
function syncAllChannelEnvVars() {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
const channels = data.channels || [];
|
|
530
|
-
|
|
531
|
-
if (channels.length === 0) {
|
|
532
|
-
return { success: true, synced: 0 };
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
let syncedCount = 0;
|
|
536
|
-
const results = [];
|
|
537
|
-
|
|
538
|
-
for (const channel of channels) {
|
|
539
|
-
if (!channel.envKey) continue;
|
|
540
|
-
|
|
541
|
-
const shouldInject = channel.enabled !== false && !!channel.apiKey;
|
|
542
|
-
if (shouldInject) {
|
|
543
|
-
const injectResult = injectEnvToShell(channel.envKey, channel.apiKey);
|
|
544
|
-
if (injectResult.success) {
|
|
545
|
-
syncedCount++;
|
|
546
|
-
results.push({ envKey: channel.envKey, success: true });
|
|
547
|
-
} else {
|
|
548
|
-
results.push({ envKey: channel.envKey, success: false, error: injectResult.error });
|
|
549
|
-
}
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// 清理已停用或缺失 key 的渠道环境变量,避免残留
|
|
554
|
-
removeEnvFromShell(channel.envKey);
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
console.log(`[Codex Channels] Synced ${syncedCount} environment variables`);
|
|
558
|
-
return { success: true, synced: syncedCount, results };
|
|
559
|
-
} catch (err) {
|
|
560
|
-
console.error('[Codex Channels] Failed to sync env vars:', err);
|
|
561
|
-
return { success: false, error: err.message };
|
|
562
|
-
}
|
|
533
|
+
// Codex 优先从 auth.json 读取 API Key,无需注入 shell 配置文件
|
|
534
|
+
return { success: true, synced: 0 };
|
|
563
535
|
}
|
|
564
536
|
|
|
565
537
|
/**
|
|
@@ -584,6 +556,7 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
584
556
|
ch.enabled = ch.id === channelId;
|
|
585
557
|
});
|
|
586
558
|
saveChannels(data);
|
|
559
|
+
clearNativeOAuth('codex');
|
|
587
560
|
|
|
588
561
|
const codexDir = getCodexDir();
|
|
589
562
|
|
|
@@ -595,33 +568,18 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
595
568
|
const authPath = path.join(codexDir, 'auth.json');
|
|
596
569
|
|
|
597
570
|
// 读取现有配置,保留 mcp_servers, projects 等
|
|
598
|
-
let config =
|
|
599
|
-
model: 'gpt-4',
|
|
600
|
-
model_reasoning_effort: 'high',
|
|
601
|
-
model_reasoning_summary_format: 'experimental',
|
|
602
|
-
network_access: 'enabled',
|
|
603
|
-
disable_response_storage: false,
|
|
604
|
-
show_raw_agent_reasoning: true
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
if (fs.existsSync(configPath)) {
|
|
608
|
-
try {
|
|
609
|
-
const content = fs.readFileSync(configPath, 'utf8');
|
|
610
|
-
const parsedConfig = toml.parse(content);
|
|
611
|
-
// 深度合并,保留原有的所有配置
|
|
612
|
-
config = { ...parsedConfig };
|
|
613
|
-
} catch (err) {
|
|
614
|
-
console.warn('[Codex Channels] Failed to read existing config, using defaults');
|
|
615
|
-
}
|
|
616
|
-
}
|
|
571
|
+
let config = readCodexConfigOrThrow(configPath, getDefaultCodexConfig());
|
|
617
572
|
|
|
618
573
|
// 设置当前渠道为 model_provider
|
|
619
574
|
config.model_provider = channel.providerKey;
|
|
620
575
|
|
|
621
576
|
// 可选:清理 provider,关闭动态切换后只保留当前渠道配置
|
|
622
577
|
if (options.pruneProviders === true) {
|
|
623
|
-
config.model_providers
|
|
624
|
-
|
|
578
|
+
const existingProviders = (config.model_providers && typeof config.model_providers === 'object')
|
|
579
|
+
? config.model_providers
|
|
580
|
+
: {};
|
|
581
|
+
config.model_providers = pruneManagedProviders(existingProviders, channel.providerKey, data.channels);
|
|
582
|
+
} else if (!config.model_providers || typeof config.model_providers !== 'object') {
|
|
625
583
|
// 默认兼容历史行为:保留已有 provider
|
|
626
584
|
config.model_providers = {};
|
|
627
585
|
}
|
|
@@ -640,21 +598,12 @@ function applyChannelToSettings(channelId, options = {}) {
|
|
|
640
598
|
config.model_providers[channel.providerKey].query_params = channel.queryParams;
|
|
641
599
|
}
|
|
642
600
|
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
${tomlContent}`;
|
|
651
|
-
|
|
652
|
-
fs.writeFileSync(configPath, annotatedContent, 'utf8');
|
|
653
|
-
console.log(`[Codex Channels] Applied channel ${channel.name} to config.toml`);
|
|
654
|
-
} catch (err) {
|
|
655
|
-
console.error('[Codex Channels] Failed to write config with TOML stringify:', err);
|
|
656
|
-
throw new Error('Failed to write config.toml: ' + err.message);
|
|
657
|
-
}
|
|
601
|
+
writeAnnotatedCodexConfig(configPath, config, [
|
|
602
|
+
'# Codex Configuration',
|
|
603
|
+
'# Managed by Coding-Tool',
|
|
604
|
+
`# Current provider: ${channel.name}`
|
|
605
|
+
]);
|
|
606
|
+
console.log(`[Codex Channels] Applied channel ${channel.name} to config.toml`);
|
|
658
607
|
|
|
659
608
|
// 更新 auth.json
|
|
660
609
|
let auth = {};
|
|
@@ -678,14 +627,7 @@ ${tomlContent}`;
|
|
|
678
627
|
|
|
679
628
|
fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
|
|
680
629
|
|
|
681
|
-
|
|
682
|
-
const injectResult = injectEnvToShell(channel.envKey, channel.apiKey);
|
|
683
|
-
if (injectResult.success) {
|
|
684
|
-
console.log(`[Codex Channels] Environment variable ${channel.envKey} injected`);
|
|
685
|
-
}
|
|
686
|
-
} else if (channel.envKey) {
|
|
687
|
-
removeEnvFromShell(channel.envKey);
|
|
688
|
-
}
|
|
630
|
+
// auth.json 已在上方写入 API Key,Codex 优先读取 auth.json,无需注入 shell
|
|
689
631
|
|
|
690
632
|
return channel;
|
|
691
633
|
}
|
|
@@ -705,6 +647,12 @@ function getEffectiveApiKey(channel) {
|
|
|
705
647
|
return channel.apiKey || null;
|
|
706
648
|
}
|
|
707
649
|
|
|
650
|
+
function disableAllChannels() {
|
|
651
|
+
const data = loadChannels();
|
|
652
|
+
data.channels.forEach(ch => { ch.enabled = false; });
|
|
653
|
+
saveChannels(data);
|
|
654
|
+
}
|
|
655
|
+
|
|
708
656
|
module.exports = {
|
|
709
657
|
getChannels,
|
|
710
658
|
createChannel,
|
|
@@ -715,5 +663,6 @@ module.exports = {
|
|
|
715
663
|
syncAllChannelEnvVars,
|
|
716
664
|
writeCodexConfigForMultiChannel,
|
|
717
665
|
applyChannelToSettings,
|
|
718
|
-
getEffectiveApiKey
|
|
666
|
+
getEffectiveApiKey,
|
|
667
|
+
disableAllChannels
|
|
719
668
|
};
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const toml = require('toml');
|
|
5
|
-
const {
|
|
6
|
-
|
|
7
|
-
const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
|
|
4
|
+
const { NATIVE_PATHS } = require('../../config/paths');
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* 获取 Codex 配置目录
|
|
11
8
|
*/
|
|
12
9
|
function getCodexDir() {
|
|
13
|
-
return
|
|
10
|
+
return NATIVE_PATHS.codex.dir;
|
|
14
11
|
}
|
|
15
12
|
|
|
16
13
|
/**
|