coding-tool-x 3.3.8 → 3.3.9

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.
Files changed (75) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/README.md +253 -326
  3. package/dist/web/assets/{Analytics-DLpoDZ2M.js → Analytics-D6LzK9hk.js} +1 -1
  4. package/dist/web/assets/{ConfigTemplates-D_hRb55W.js → ConfigTemplates-BUDYuxRi.js} +1 -1
  5. package/dist/web/assets/Home-BQxQ1LhR.css +1 -0
  6. package/dist/web/assets/Home-D7KX7iF8.js +1 -0
  7. package/dist/web/assets/{PluginManager-JXsyym1s.js → PluginManager-DTgQ--vB.js} +1 -1
  8. package/dist/web/assets/{ProjectList-DZWSeb-q.js → ProjectList-DMCiGmCT.js} +1 -1
  9. package/dist/web/assets/{SessionList-Cs624DR3.js → SessionList-CRBsdVRe.js} +1 -1
  10. package/dist/web/assets/{SkillManager-bEliz7qz.js → SkillManager-DMwx2Q4k.js} +1 -1
  11. package/dist/web/assets/{WorkspaceManager-J3RecFGn.js → WorkspaceManager-DapB4ljL.js} +1 -1
  12. package/dist/web/assets/{icons-Cuc23WS7.js → icons-B5Pl4lrD.js} +1 -1
  13. package/dist/web/assets/index-CL-qpoJ_.js +2 -0
  14. package/dist/web/assets/index-D_5dRFOL.css +1 -0
  15. package/dist/web/assets/{markdown-C9MYpaSi.js → markdown-DyTJGI4N.js} +1 -1
  16. package/dist/web/assets/{naive-ui-CxpuzdjU.js → naive-ui-Bdxp09n2.js} +1 -1
  17. package/dist/web/assets/{vendors-DMjSfzlv.js → vendors-CKPV1OAU.js} +2 -2
  18. package/dist/web/assets/{vue-vendor-DET08QYg.js → vue-vendor-3bf-fPGP.js} +1 -1
  19. package/dist/web/index.html +7 -7
  20. package/docs/home.png +0 -0
  21. package/package.json +13 -5
  22. package/src/commands/daemon.js +3 -2
  23. package/src/commands/security.js +1 -2
  24. package/src/config/paths.js +638 -93
  25. package/src/server/api/agents.js +1 -1
  26. package/src/server/api/claude-hooks.js +13 -8
  27. package/src/server/api/codex-proxy.js +5 -4
  28. package/src/server/api/hooks.js +45 -0
  29. package/src/server/api/plugins.js +0 -1
  30. package/src/server/api/ui-config.js +5 -0
  31. package/src/server/codex-proxy-server.js +89 -59
  32. package/src/server/gemini-proxy-server.js +107 -88
  33. package/src/server/index.js +1 -0
  34. package/src/server/opencode-proxy-server.js +381 -225
  35. package/src/server/proxy-server.js +86 -60
  36. package/src/server/services/alias.js +3 -3
  37. package/src/server/services/channels.js +3 -2
  38. package/src/server/services/codex-channels.js +41 -87
  39. package/src/server/services/codex-env-manager.js +423 -0
  40. package/src/server/services/codex-settings-manager.js +15 -15
  41. package/src/server/services/codex-statistics-service.js +3 -27
  42. package/src/server/services/config-export-service.js +20 -7
  43. package/src/server/services/config-registry-service.js +3 -2
  44. package/src/server/services/config-sync-manager.js +1 -1
  45. package/src/server/services/favorites.js +4 -3
  46. package/src/server/services/gemini-channels.js +3 -3
  47. package/src/server/services/gemini-statistics-service.js +3 -25
  48. package/src/server/services/mcp-service.js +2 -3
  49. package/src/server/services/model-detector.js +4 -3
  50. package/src/server/services/native-oauth-adapters.js +2 -1
  51. package/src/server/services/network-access.js +39 -1
  52. package/src/server/services/notification-hooks.js +951 -0
  53. package/src/server/services/opencode-channels.js +6 -6
  54. package/src/server/services/opencode-sessions.js +2 -2
  55. package/src/server/services/opencode-statistics-service.js +3 -27
  56. package/src/server/services/plugins-service.js +110 -31
  57. package/src/server/services/prompts-service.js +2 -3
  58. package/src/server/services/proxy-log-helper.js +242 -0
  59. package/src/server/services/proxy-runtime.js +6 -4
  60. package/src/server/services/repo-scanner-base.js +12 -4
  61. package/src/server/services/request-logger.js +7 -7
  62. package/src/server/services/security-config.js +4 -4
  63. package/src/server/services/session-cache.js +2 -2
  64. package/src/server/services/sessions.js +2 -2
  65. package/src/server/services/skill-service.js +174 -55
  66. package/src/server/services/statistics-service.js +5 -5
  67. package/src/server/services/ui-config.js +4 -3
  68. package/src/server/services/workspace-service.js +1 -1
  69. package/src/server/websocket-server.js +5 -4
  70. package/dist/web/assets/Home-BMoFdAwy.css +0 -1
  71. package/dist/web/assets/Home-DNwp-0J-.js +0 -1
  72. package/dist/web/assets/index-BXeSvAwU.js +0 -2
  73. package/dist/web/assets/index-DWAC3Tdv.css +0 -1
  74. package/docs/bannel.png +0 -0
  75. package/docs/model-redirection.md +0 -251
@@ -18,6 +18,7 @@ const { createDecodedStream } = require('./services/response-decoder');
18
18
  const eventBus = require('../plugins/event-bus');
19
19
  const { getEffectiveApiKey } = require('./services/channels');
20
20
  const { persistProxyRequestSnapshot, persistClaudeRequestTemplate } = require('./services/request-logger');
21
+ const { publishUsageLog, publishFailureLog } = require('./services/proxy-log-helper');
21
22
 
22
23
  let proxyServer = null;
23
24
  let proxyApp = null;
@@ -313,20 +314,20 @@ async function startProxyServer(options = {}) {
313
314
  const effectiveKey = getEffectiveApiKey(channel);
314
315
  if (!effectiveKey) {
315
316
  release();
317
+ publishFailureLog({
318
+ source: 'claude',
319
+ channel: channel.name,
320
+ message: 'API key not configured or expired. Please update your channel key.',
321
+ statusCode: 401,
322
+ stage: 'preflight',
323
+ broadcastLog
324
+ });
316
325
  return res.status(401).json({
317
326
  error: 'API key not configured or expired. Please update your channel key.',
318
327
  type: 'authentication_error'
319
328
  });
320
329
  }
321
330
  req.effectiveApiKey = effectiveKey;
322
-
323
- const now = new Date();
324
- const time = now.toLocaleTimeString('zh-CN', {
325
- hour12: false,
326
- hour: '2-digit',
327
- minute: '2-digit',
328
- second: '2-digit'
329
- });
330
331
  const requestSnapshot = serializeFullClaudeRequest(req);
331
332
  persistClaudeRequestSnapshot({
332
333
  timestamp: Date.now(),
@@ -373,6 +374,21 @@ async function startProxyServer(options = {}) {
373
374
  if (err) {
374
375
  // 记录请求失败
375
376
  recordFailure(channel.id, 'claude', err);
377
+ const metadata = requestMetadata.get(req) || {
378
+ id: null,
379
+ channel: channel.name,
380
+ channelId: channel.id,
381
+ startTime: Date.now()
382
+ };
383
+ publishFailureLog({
384
+ source: 'claude',
385
+ metadata,
386
+ message: err.message,
387
+ error: err,
388
+ statusCode: 502,
389
+ stage: 'proxy_web',
390
+ broadcastLog
391
+ });
376
392
  console.error('Proxy error:', err);
377
393
  if (res && !res.headersSent) {
378
394
  res.status(502).json({
@@ -384,6 +400,13 @@ async function startProxyServer(options = {}) {
384
400
  });
385
401
  } catch (error) {
386
402
  console.error('Channel allocation error:', error);
403
+ publishFailureLog({
404
+ source: 'claude',
405
+ message: error.message || '所有渠道暂时不可用',
406
+ statusCode: 503,
407
+ stage: 'allocate_channel',
408
+ broadcastLog
409
+ });
387
410
  if (!res.headersSent) {
388
411
  res.status(503).json({
389
412
  error: error.message || '所有渠道暂时不可用',
@@ -425,8 +448,34 @@ async function startProxyServer(options = {}) {
425
448
  cacheRead: 0,
426
449
  model: ''
427
450
  };
451
+ let usageRecorded = false;
428
452
  const parsedStream = createDecodedStream(proxyRes);
429
453
 
454
+ function recordUsageIfReady() {
455
+ if (usageRecorded) return false;
456
+
457
+ const result = publishUsageLog({
458
+ source: 'claude',
459
+ metadata,
460
+ model: tokenData.model,
461
+ tokens: {
462
+ input: tokenData.inputTokens,
463
+ output: tokenData.outputTokens,
464
+ cacheCreation: tokenData.cacheCreation,
465
+ cacheRead: tokenData.cacheRead
466
+ },
467
+ calculateCost,
468
+ broadcastLog,
469
+ recordRequest,
470
+ recordSuccess,
471
+ allowBroadcast: !isResponseClosed
472
+ });
473
+
474
+ if (!result) return false;
475
+ usageRecorded = true;
476
+ return true;
477
+ }
478
+
430
479
  parsedStream.on('data', (chunk) => {
431
480
  if (isResponseClosed) return;
432
481
 
@@ -474,57 +523,8 @@ async function startProxyServer(options = {}) {
474
523
  }
475
524
  }
476
525
 
477
- if (eventType === 'message_delta' && parsed.usage) {
478
- const now = new Date();
479
- const time = now.toLocaleTimeString('zh-CN', {
480
- hour12: false,
481
- hour: '2-digit',
482
- minute: '2-digit',
483
- second: '2-digit'
484
- });
485
-
486
- const tokens = {
487
- input: tokenData.inputTokens,
488
- output: tokenData.outputTokens,
489
- cacheCreation: tokenData.cacheCreation,
490
- cacheRead: tokenData.cacheRead,
491
- total: tokenData.inputTokens + tokenData.outputTokens + tokenData.cacheCreation + tokenData.cacheRead
492
- };
493
- const cost = calculateCost(tokenData.model, tokens);
494
-
495
- if (!isResponseClosed) {
496
- broadcastLog({
497
- type: 'log',
498
- id: metadata.id,
499
- time: time,
500
- channel: metadata.channel,
501
- model: tokenData.model,
502
- inputTokens: tokenData.inputTokens,
503
- outputTokens: tokenData.outputTokens,
504
- cacheCreation: tokenData.cacheCreation,
505
- cacheRead: tokenData.cacheRead,
506
- cost: cost,
507
- source: 'claude'
508
- });
509
- }
510
-
511
- const duration = Date.now() - metadata.startTime;
512
-
513
- recordRequest({
514
- id: metadata.id,
515
- timestamp: new Date(metadata.startTime).toISOString(),
516
- toolType: 'claude-code',
517
- channel: metadata.channel,
518
- channelId: metadata.channelId,
519
- model: tokenData.model,
520
- tokens: tokens,
521
- duration: duration,
522
- success: true,
523
- cost: cost
524
- });
525
-
526
- // 记录请求成功(用于健康检查)
527
- recordSuccess(metadata.channelId, 'claude');
526
+ if (eventType === 'message_stop') {
527
+ recordUsageIfReady();
528
528
  }
529
529
  } catch (err) {
530
530
  }
@@ -540,7 +540,10 @@ async function startProxyServer(options = {}) {
540
540
  }
541
541
  };
542
542
 
543
- parsedStream.on('end', finalize);
543
+ parsedStream.on('end', () => {
544
+ recordUsageIfReady();
545
+ finalize();
546
+ });
544
547
 
545
548
  parsedStream.on('error', (err) => {
546
549
  if (err.code !== 'EPIPE' && err.code !== 'ECONNRESET') {
@@ -550,6 +553,15 @@ async function startProxyServer(options = {}) {
550
553
  if (metadata && metadata.channelId) {
551
554
  recordFailure(metadata.channelId, 'claude', err);
552
555
  }
556
+ publishFailureLog({
557
+ source: 'claude',
558
+ metadata,
559
+ message: err.message,
560
+ error: err,
561
+ statusCode: proxyRes.statusCode,
562
+ stage: 'response_stream',
563
+ broadcastLog
564
+ });
553
565
  isResponseClosed = true;
554
566
  finalize();
555
567
  });
@@ -561,6 +573,20 @@ async function startProxyServer(options = {}) {
561
573
  if (req && req.selectedChannel && req.selectedChannel.id) {
562
574
  recordFailure(req.selectedChannel.id, 'claude', err);
563
575
  }
576
+ const metadata = req ? requestMetadata.get(req) : null;
577
+ publishFailureLog({
578
+ source: 'claude',
579
+ metadata: metadata || {
580
+ channel: req?.selectedChannel?.name,
581
+ channelId: req?.selectedChannel?.id,
582
+ model: req?.body?.model
583
+ },
584
+ message: err.message,
585
+ error: err,
586
+ statusCode: 502,
587
+ stage: 'proxy',
588
+ broadcastLog
589
+ });
564
590
  if (res && !res.headersSent) {
565
591
  res.status(502).json({
566
592
  error: 'Proxy error: ' + err.message,
@@ -2,13 +2,13 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { PATHS } = require('../../config/paths');
4
4
 
5
- const ALIAS_DIR = PATHS.base;
6
5
  const ALIAS_FILE = PATHS.aliases;
7
6
 
8
7
  // Ensure alias directory exists
9
8
  function ensureAliasDir() {
10
- if (!fs.existsSync(ALIAS_DIR)) {
11
- fs.mkdirSync(ALIAS_DIR, { recursive: true });
9
+ const dir = path.dirname(ALIAS_FILE);
10
+ if (!fs.existsSync(dir)) {
11
+ fs.mkdirSync(dir, { recursive: true });
12
12
  }
13
13
  }
14
14
 
@@ -1,10 +1,11 @@
1
1
  const fs = require('fs');
2
+ const path = require('path');
2
3
  const { isProxyConfig } = require('./settings-manager');
3
4
  const { PATHS, NATIVE_PATHS } = require('../../config/paths');
4
5
  const { clearNativeOAuth } = require('./native-oauth-adapters');
5
6
 
6
7
  function getChannelsFilePath() {
7
- const dir = PATHS.base;
8
+ const dir = path.dirname(PATHS.channels.claude);
8
9
  if (!fs.existsSync(dir)) {
9
10
  fs.mkdirSync(dir, { recursive: true });
10
11
  }
@@ -12,7 +13,7 @@ function getChannelsFilePath() {
12
13
  }
13
14
 
14
15
  function getActiveChannelIdPath() {
15
- const dir = PATHS.base;
16
+ const dir = path.dirname(PATHS.activeChannel.claude);
16
17
  if (!fs.existsSync(dir)) {
17
18
  fs.mkdirSync(dir, { recursive: true });
18
19
  }
@@ -1,22 +1,23 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
- const os = require('os');
4
3
  const crypto = require('crypto');
5
4
  const toml = require('toml');
6
5
  const tomlStringify = require('@iarna/toml').stringify;
7
- const { resolvePreferredHomeDir } = require('../../utils/home-dir');
6
+ const { PATHS } = require('../../config/paths');
8
7
  const { getCodexDir } = require('./codex-config');
9
8
  const { isProxyConfig } = require('./codex-settings-manager');
10
9
  const { clearNativeOAuth } = require('./native-oauth-adapters');
10
+ const { syncCodexUserEnvironment } = require('./codex-env-manager');
11
11
 
12
- const HOME_DIR = resolvePreferredHomeDir(process.platform, process.env, os.homedir());
12
+ const CODEX_PROXY_ENV_KEY = 'CC_PROXY_KEY';
13
+ const CODEX_PROXY_ENV_VALUE = 'PROXY_KEY';
13
14
 
14
15
  /**
15
16
  * Codex 渠道管理服务(多渠道架构)
16
17
  *
17
18
  * Codex 配置结构:
18
19
  * - config.toml: 主配置,包含 model_provider 和各提供商配置
19
- * - auth.json: API Key 存储
20
+ * - 用户级环境变量: env_key 对应的 API Key 存储
20
21
  * - 我们的 codex-channels.json: 完整渠道信息(用于管理)
21
22
  *
22
23
  * 多渠道模式:
@@ -32,13 +33,28 @@ function normalizeGatewaySourceType(value, fallback = 'codex') {
32
33
  return fallback;
33
34
  }
34
35
 
36
+ function buildManagedCodexEnvMap(channels = [], { includeProxyKey = false } = {}) {
37
+ const envMap = {};
38
+
39
+ for (const channel of channels) {
40
+ if (!channel?.envKey || !channel?.apiKey) continue;
41
+ envMap[channel.envKey] = channel.apiKey;
42
+ }
43
+
44
+ if (includeProxyKey) {
45
+ envMap[CODEX_PROXY_ENV_KEY] = CODEX_PROXY_ENV_VALUE;
46
+ }
47
+
48
+ return envMap;
49
+ }
50
+
35
51
  // 获取渠道存储文件路径
36
52
  function getChannelsFilePath() {
37
- const ccToolDir = path.join(HOME_DIR, '.cc-tool');
38
- if (!fs.existsSync(ccToolDir)) {
39
- fs.mkdirSync(ccToolDir, { recursive: true });
53
+ const channelsDir = path.dirname(PATHS.channels.codex);
54
+ if (!fs.existsSync(channelsDir)) {
55
+ fs.mkdirSync(channelsDir, { recursive: true });
40
56
  }
41
- return path.join(ccToolDir, 'codex-channels.json');
57
+ return PATHS.channels.codex;
42
58
  }
43
59
 
44
60
  // 读取所有渠道(从我们的存储文件)
@@ -103,11 +119,11 @@ function initializeFromConfig() {
103
119
  for (const [providerKey, providerConfig] of Object.entries(config.model_providers)) {
104
120
  // env_key 优先级:配置的 env_key > PROVIDER_API_KEY > OPENAI_API_KEY
105
121
  let envKey = providerConfig.env_key || `${providerKey.toUpperCase()}_API_KEY`;
106
- let apiKey = auth[envKey] || '';
122
+ let apiKey = process.env[envKey] || auth[envKey] || '';
107
123
 
108
124
  // 如果没找到,尝试 OPENAI_API_KEY 作为通用 fallback
109
- if (!apiKey && auth['OPENAI_API_KEY']) {
110
- apiKey = auth['OPENAI_API_KEY'];
125
+ if (!apiKey && (process.env.OPENAI_API_KEY || auth['OPENAI_API_KEY'])) {
126
+ apiKey = process.env.OPENAI_API_KEY || auth['OPENAI_API_KEY'];
111
127
  envKey = 'OPENAI_API_KEY';
112
128
  }
113
129
 
@@ -129,8 +145,6 @@ function initializeFromConfig() {
129
145
  createdAt: Date.now(),
130
146
  updatedAt: Date.now()
131
147
  });
132
-
133
- // auth.json 已写入 API Key,Codex 启动时优先读取 auth.json,无需注入 shell
134
148
  }
135
149
  }
136
150
 
@@ -276,8 +290,7 @@ function createChannel(name, providerKey, baseUrl, apiKey, wireApi = 'responses'
276
290
 
277
291
  data.channels.push(newChannel);
278
292
  saveChannels(data);
279
-
280
- // auth.json 已写入 API Key(通过 writeCodexConfigForMultiChannel),Codex 优先读取 auth.json
293
+ syncAllChannelEnvVars(data.channels);
281
294
 
282
295
  // 注意:不再自动写入 config.toml,只在开启代理控制时才同步
283
296
  // writeCodexConfigForMultiChannel(data.channels);
@@ -342,19 +355,7 @@ function updateChannel(channelId, updates) {
342
355
  applyChannelToSettings(channelId);
343
356
  }
344
357
 
345
- // 处理环境变量更新
346
- // 如果 envKey 或 apiKey 变化,需要更新环境变量
347
- const oldEnvKey = oldChannel.envKey;
348
- const newEnvKey = newChannel.envKey;
349
- const newApiKey = newChannel.apiKey;
350
- const shouldRemoveOldEnv =
351
- !!oldEnvKey && (
352
- oldEnvKey !== newEnvKey ||
353
- !newApiKey ||
354
- newChannel.enabled === false
355
- );
356
-
357
- // auth.json 由 applyChannelToSettings/writeCodexConfigForMultiChannel 维护,Codex 优先读取 auth.json
358
+ syncAllChannelEnvVars(data.channels);
358
359
 
359
360
  // 注意:不再自动写入 config.toml,只在开启代理控制时才同步
360
361
  // writeCodexConfigForMultiChannel(data.channels);
@@ -374,8 +375,7 @@ async function deleteChannel(channelId) {
374
375
  const deletedChannel = data.channels[index];
375
376
  data.channels.splice(index, 1);
376
377
  saveChannels(data);
377
-
378
- // auth.json 中的 key 由 writeCodexConfigForMultiChannel 管理,删除渠道后下次写入时自动清理
378
+ syncAllChannelEnvVars(data.channels);
379
379
 
380
380
  // 注意:不再自动写入 config.toml,只在开启代理控制时才同步
381
381
  // writeCodexConfigForMultiChannel(data.channels);
@@ -399,7 +399,6 @@ function writeCodexConfigForMultiChannel(allChannels) {
399
399
  }
400
400
 
401
401
  const configPath = path.join(codexDir, 'config.toml');
402
- const authPath = path.join(codexDir, 'auth.json');
403
402
 
404
403
  // 读取现有配置,保留所有现有字段(特别是 mcp_servers, projects 等)
405
404
  const defaultConfig = getDefaultCodexConfig();
@@ -464,34 +463,10 @@ function writeCodexConfigForMultiChannel(allChannels) {
464
463
  '# Managed by Coding-Tool',
465
464
  '# WARNING: MCP servers and projects are preserved automatically'
466
465
  ]);
467
-
468
- // 更新 auth.json
469
- let auth = {};
470
- if (fs.existsSync(authPath)) {
471
- try {
472
- auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
473
- } catch (err) {
474
- console.warn('[Codex Channels] Failed to read auth.json, creating new');
475
- }
476
- }
477
-
478
- // 更新所有渠道的 API Key
479
- for (const channel of allChannels) {
480
- if (channel.envKey && !channel.apiKey) {
481
- delete auth[channel.envKey];
482
- }
483
- }
484
-
485
- for (const channel of allChannels) {
486
- if (channel.apiKey) {
487
- auth[channel.envKey] = channel.apiKey;
488
- }
489
- }
490
-
491
- fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
492
-
493
- // 注意:环境变量注入在 createChannel 和 updateChannel 时已经处理
494
- // 这里不再重复注入,避免多次写入 shell 配置文件
466
+ syncCodexUserEnvironment(
467
+ buildManagedCodexEnvMap(allChannels, { includeProxyKey: isProxyMode }),
468
+ { replace: true }
469
+ );
495
470
  }
496
471
 
497
472
  // 获取所有启用的渠道(供调度器使用)
@@ -529,9 +504,12 @@ function saveChannelOrder(order) {
529
504
  * 确保用户可以直接使用 codex 命令而无需手动设置环境变量
530
505
  * 这个函数会在服务启动时自动调用
531
506
  */
532
- function syncAllChannelEnvVars() {
533
- // Codex 优先从 auth.json 读取 API Key,无需注入 shell 配置文件
534
- return { success: true, synced: 0 };
507
+ function syncAllChannelEnvVars(channels = null) {
508
+ const data = channels ? { channels } : loadChannels();
509
+ return syncCodexUserEnvironment(
510
+ buildManagedCodexEnvMap(data.channels || [], { includeProxyKey: isProxyConfig() }),
511
+ { replace: true }
512
+ );
535
513
  }
536
514
 
537
515
  /**
@@ -565,7 +543,6 @@ function applyChannelToSettings(channelId, options = {}) {
565
543
  }
566
544
 
567
545
  const configPath = path.join(codexDir, 'config.toml');
568
- const authPath = path.join(codexDir, 'auth.json');
569
546
 
570
547
  // 读取现有配置,保留 mcp_servers, projects 等
571
548
  let config = readCodexConfigOrThrow(configPath, getDefaultCodexConfig());
@@ -604,30 +581,7 @@ function applyChannelToSettings(channelId, options = {}) {
604
581
  `# Current provider: ${channel.name}`
605
582
  ]);
606
583
  console.log(`[Codex Channels] Applied channel ${channel.name} to config.toml`);
607
-
608
- // 更新 auth.json
609
- let auth = {};
610
- if (fs.existsSync(authPath)) {
611
- try {
612
- auth = JSON.parse(fs.readFileSync(authPath, 'utf8'));
613
- } catch (err) {
614
- console.warn('[Codex Channels] Failed to read auth.json, creating new');
615
- }
616
- }
617
-
618
- if (channel.apiKey && channel.envKey) {
619
- auth[channel.envKey] = channel.apiKey;
620
- } else if (channel.envKey) {
621
- delete auth[channel.envKey];
622
- }
623
-
624
- // 清除 chatgpt token 认证字段,避免 Codex 优先用过期 token 而报 usage limit
625
- delete auth.tokens;
626
- delete auth.auth_mode;
627
-
628
- fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
629
-
630
- // auth.json 已在上方写入 API Key,Codex 优先读取 auth.json,无需注入 shell
584
+ syncAllChannelEnvVars();
631
585
 
632
586
  return channel;
633
587
  }