@adversity/coding-tool-x 3.1.0 → 3.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.
Files changed (137) hide show
  1. package/CHANGELOG.md +15 -18
  2. package/README.md +8 -8
  3. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  4. package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
  5. package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
  6. package/dist/web/assets/Home-Di2qsylF.css +1 -0
  7. package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
  8. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  9. package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
  10. package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
  11. package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
  12. package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
  13. package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
  14. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  15. package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
  16. package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
  17. package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
  18. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  19. package/dist/web/assets/icons-kcfLIMBB.js +1 -0
  20. package/dist/web/assets/index-Ufv5rCa5.css +1 -0
  21. package/dist/web/assets/index-lAkrRC3h.js +2 -0
  22. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  23. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  24. package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
  25. package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
  26. package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
  27. package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
  28. package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
  29. package/dist/web/index.html +8 -6
  30. package/package.json +4 -2
  31. package/src/commands/channels.js +48 -1
  32. package/src/commands/cli-type.js +4 -2
  33. package/src/commands/daemon.js +81 -12
  34. package/src/commands/doctor.js +10 -9
  35. package/src/commands/list.js +1 -1
  36. package/src/commands/logs.js +6 -4
  37. package/src/commands/port-config.js +24 -4
  38. package/src/commands/proxy-control.js +12 -6
  39. package/src/commands/search.js +1 -1
  40. package/src/commands/security.js +3 -2
  41. package/src/commands/stats.js +226 -52
  42. package/src/commands/switch.js +1 -1
  43. package/src/commands/toggle-proxy.js +31 -6
  44. package/src/commands/update.js +97 -0
  45. package/src/commands/workspace.js +1 -1
  46. package/src/config/default.js +39 -2
  47. package/src/config/loader.js +74 -8
  48. package/src/config/paths.js +105 -33
  49. package/src/index.js +64 -3
  50. package/src/plugins/constants.js +3 -2
  51. package/src/plugins/plugin-api.js +1 -1
  52. package/src/reset-config.js +4 -2
  53. package/src/server/api/agents.js +57 -14
  54. package/src/server/api/channels.js +112 -33
  55. package/src/server/api/codex-channels.js +111 -18
  56. package/src/server/api/codex-proxy.js +14 -8
  57. package/src/server/api/commands.js +71 -18
  58. package/src/server/api/config-export.js +0 -6
  59. package/src/server/api/config-registry.js +11 -3
  60. package/src/server/api/config.js +376 -5
  61. package/src/server/api/convert.js +133 -0
  62. package/src/server/api/dashboard.js +22 -6
  63. package/src/server/api/gemini-channels.js +107 -18
  64. package/src/server/api/gemini-proxy.js +14 -8
  65. package/src/server/api/gemini-sessions.js +1 -1
  66. package/src/server/api/health-check.js +4 -3
  67. package/src/server/api/mcp.js +3 -3
  68. package/src/server/api/opencode-channels.js +419 -0
  69. package/src/server/api/opencode-projects.js +99 -0
  70. package/src/server/api/opencode-proxy.js +198 -0
  71. package/src/server/api/opencode-sessions.js +403 -0
  72. package/src/server/api/opencode-statistics.js +57 -0
  73. package/src/server/api/plugins.js +66 -19
  74. package/src/server/api/prompts.js +2 -2
  75. package/src/server/api/proxy.js +7 -4
  76. package/src/server/api/sessions.js +3 -0
  77. package/src/server/api/skills.js +69 -18
  78. package/src/server/api/workspaces.js +78 -6
  79. package/src/server/codex-proxy-server.js +30 -18
  80. package/src/server/dev-server.js +1 -1
  81. package/src/server/gemini-proxy-server.js +15 -3
  82. package/src/server/index.js +165 -58
  83. package/src/server/opencode-proxy-server.js +4375 -0
  84. package/src/server/proxy-server.js +27 -18
  85. package/src/server/services/agents-service.js +61 -24
  86. package/src/server/services/channel-scheduler.js +9 -5
  87. package/src/server/services/channels.js +64 -37
  88. package/src/server/services/codex-channels.js +56 -43
  89. package/src/server/services/codex-settings-manager.js +271 -49
  90. package/src/server/services/codex-statistics-service.js +2 -2
  91. package/src/server/services/commands-service.js +84 -25
  92. package/src/server/services/config-export-service.js +7 -45
  93. package/src/server/services/config-registry-service.js +63 -17
  94. package/src/server/services/config-sync-manager.js +160 -7
  95. package/src/server/services/config-templates-service.js +204 -51
  96. package/src/server/services/env-checker.js +26 -12
  97. package/src/server/services/env-manager.js +126 -18
  98. package/src/server/services/favorites.js +5 -3
  99. package/src/server/services/gemini-channels.js +33 -44
  100. package/src/server/services/gemini-statistics-service.js +2 -2
  101. package/src/server/services/mcp-service.js +350 -9
  102. package/src/server/services/model-detector.js +707 -221
  103. package/src/server/services/network-access.js +80 -0
  104. package/src/server/services/opencode-channels.js +206 -0
  105. package/src/server/services/opencode-gateway-converter.js +639 -0
  106. package/src/server/services/opencode-sessions.js +663 -0
  107. package/src/server/services/opencode-settings-manager.js +342 -0
  108. package/src/server/services/opencode-statistics-service.js +255 -0
  109. package/src/server/services/plugins-service.js +479 -22
  110. package/src/server/services/prompts-service.js +53 -11
  111. package/src/server/services/proxy-runtime.js +1 -1
  112. package/src/server/services/repo-scanner-base.js +1 -1
  113. package/src/server/services/security-config.js +1 -1
  114. package/src/server/services/session-cache.js +1 -1
  115. package/src/server/services/skill-service.js +300 -46
  116. package/src/server/services/speed-test.js +464 -186
  117. package/src/server/services/statistics-service.js +2 -2
  118. package/src/server/services/terminal-commands.js +10 -3
  119. package/src/server/services/terminal-config.js +1 -1
  120. package/src/server/services/ui-config.js +1 -1
  121. package/src/server/services/workspace-service.js +57 -100
  122. package/src/server/websocket-server.js +132 -3
  123. package/src/ui/menu.js +49 -40
  124. package/src/utils/port-helper.js +22 -8
  125. package/src/utils/session.js +5 -4
  126. package/dist/web/assets/icons-CO_2OFES.js +0 -1
  127. package/dist/web/assets/index-DI8QOi-E.js +0 -14
  128. package/dist/web/assets/index-uLHGdeZh.css +0 -41
  129. package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
  130. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  131. package/src/server/api/oauth.js +0 -294
  132. package/src/server/api/permissions.js +0 -385
  133. package/src/server/config/oauth-providers.js +0 -68
  134. package/src/server/services/oauth-callback-server.js +0 -284
  135. package/src/server/services/oauth-service.js +0 -378
  136. package/src/server/services/oauth-token-storage.js +0 -135
  137. package/src/server/services/permission-templates-service.js +0 -308
@@ -2,7 +2,7 @@
2
2
  * 环境变量检测服务
3
3
  *
4
4
  * 检测系统中可能导致 API 配置冲突的环境变量
5
- * 支持 macOS/Linux 的 shell 配置文件检测
5
+ * 支持 macOS/Linux/Windows 的 shell 配置文件检测
6
6
  */
7
7
 
8
8
  const fs = require('fs');
@@ -43,13 +43,23 @@ const EXACT_SENSITIVE_VARS = [
43
43
  ];
44
44
 
45
45
  // 需要检测的 shell 配置文件
46
- const SHELL_CONFIG_FILES = [
47
- '.bashrc',
48
- '.bash_profile',
49
- '.zshrc',
50
- '.zprofile',
51
- '.profile'
52
- ];
46
+ const SHELL_CONFIG_FILES = process.platform === 'win32'
47
+ ? [
48
+ '.bashrc',
49
+ '.bash_profile',
50
+ '.zshrc',
51
+ '.zprofile',
52
+ '.profile',
53
+ path.join('Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
54
+ path.join('Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1')
55
+ ]
56
+ : [
57
+ '.bashrc',
58
+ '.bash_profile',
59
+ '.zshrc',
60
+ '.zprofile',
61
+ '.profile'
62
+ ];
53
63
 
54
64
  // 系统级配置文件
55
65
  const SYSTEM_CONFIG_FILES = [
@@ -120,7 +130,7 @@ function checkShellConfigs(keywords) {
120
130
  const homeDir = os.homedir();
121
131
 
122
132
  for (const fileName of SHELL_CONFIG_FILES) {
123
- const filePath = path.join(homeDir, fileName);
133
+ const filePath = path.isAbsolute(fileName) ? fileName : path.join(homeDir, fileName);
124
134
  const fileConflicts = parseConfigFile(filePath, keywords);
125
135
  conflicts.push(...fileConflicts);
126
136
  }
@@ -165,10 +175,14 @@ function parseConfigFile(filePath, keywords) {
165
175
  continue;
166
176
  }
167
177
 
168
- // 匹配 export VAR=value 或 VAR=value 格式
178
+ // 匹配 sh/bash/zsh 的 export VAR=value 或 VAR=value
169
179
  const exportMatch = trimmed.match(/^(?:export\s+)?([A-Z_][A-Z0-9_]*)=(.*)$/);
170
- if (exportMatch) {
171
- const [, varName, varValue] = exportMatch;
180
+ // 匹配 PowerShell 的 $env:VAR = value
181
+ const psMatch = trimmed.match(/^\$env:([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);
182
+ const matched = exportMatch || psMatch;
183
+
184
+ if (matched) {
185
+ const [, varName, varValue] = matched;
172
186
 
173
187
  if (matchesKeywords(varName, keywords)) {
174
188
  conflicts.push({
@@ -6,15 +6,77 @@
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
- const os = require('os');
9
+ const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
10
10
 
11
11
  // 备份目录
12
- const BACKUP_DIR = path.join(os.homedir(), '.claude', 'cc-tool', 'env-backups');
12
+ const BACKUP_DIR = PATHS.envBackups;
13
+
14
+ function ensureParentDir(filePath) {
15
+ const dirPath = path.dirname(filePath);
16
+ if (!fs.existsSync(dirPath)) {
17
+ fs.mkdirSync(dirPath, { recursive: true });
18
+ }
19
+ }
20
+
21
+ function writeFileAtomic(filePath, content) {
22
+ ensureParentDir(filePath);
23
+ const tempPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
24
+
25
+ try {
26
+ fs.writeFileSync(tempPath, content, 'utf-8');
27
+ fs.renameSync(tempPath, filePath);
28
+ } finally {
29
+ if (fs.existsSync(tempPath)) {
30
+ try {
31
+ fs.unlinkSync(tempPath);
32
+ } catch (cleanupErr) {
33
+ // ignore cleanup errors
34
+ }
35
+ }
36
+ }
37
+ }
38
+
39
+ function escapeRegex(value) {
40
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
41
+ }
42
+
43
+ function isPowerShellProfile(filePath) {
44
+ return String(filePath || '').toLowerCase().endsWith('.ps1');
45
+ }
46
+
47
+ function matchesVarAssignment(line, varName, usePowerShell) {
48
+ if (!line || !varName) {
49
+ return false;
50
+ }
51
+
52
+ const escaped = escapeRegex(varName);
53
+ const regex = usePowerShell
54
+ ? new RegExp(`^\\s*\\$env:${escaped}\\s*=`, 'i')
55
+ : new RegExp(`^\\s*(?:export\\s+)?${escaped}=`);
56
+ return regex.test(line.trim());
57
+ }
58
+
59
+ function formatExportLine(varName, value, usePowerShell) {
60
+ if (usePowerShell) {
61
+ const escapedValue = String(value ?? '')
62
+ .replace(/`/g, '``')
63
+ .replace(/"/g, '`"');
64
+ return `$env:${varName} = "${escapedValue}"`;
65
+ }
66
+
67
+ const escapedValue = String(value ?? '')
68
+ .replace(/\\/g, '\\\\')
69
+ .replace(/"/g, '\\"')
70
+ .replace(/\$/g, '\\$')
71
+ .replace(/`/g, '\\`');
72
+ return `export ${varName}="${escapedValue}"`;
73
+ }
13
74
 
14
75
  /**
15
76
  * 确保备份目录存在
16
77
  */
17
78
  function ensureBackupDir() {
79
+ ensureStorageDirMigrated();
18
80
  if (!fs.existsSync(BACKUP_DIR)) {
19
81
  fs.mkdirSync(BACKUP_DIR, { recursive: true });
20
82
  }
@@ -112,9 +174,14 @@ function getOriginalValue(conflict) {
112
174
 
113
175
  if (line) {
114
176
  const match = line.match(/^(?:export\s+)?[A-Z_][A-Z0-9_]*=(.*)$/);
115
- if (match) {
177
+ if (match && match[1] !== undefined) {
116
178
  return cleanValue(match[1]);
117
179
  }
180
+
181
+ const psMatch = line.match(/^\s*\$env:[A-Z_][A-Z0-9_]*\s*=\s*(.*)$/i);
182
+ if (psMatch && psMatch[1] !== undefined) {
183
+ return cleanValue(psMatch[1]);
184
+ }
118
185
  }
119
186
  } catch (err) {
120
187
  // 忽略
@@ -162,18 +229,44 @@ function removeVarsFromFile(filePath, vars) {
162
229
  }
163
230
 
164
231
  const content = fs.readFileSync(filePath, 'utf-8');
165
- const lines = content.split('\n');
232
+ const lines = content.split(/\r?\n/);
233
+ const usePowerShell = isPowerShellProfile(filePath);
234
+
235
+ // 收集要删除的行号(优先使用行号,行号不匹配时按变量名回退)
236
+ const lineNumbersToRemove = new Set();
237
+ for (const item of vars) {
238
+ const varName = String(item.varName || '').trim();
239
+ if (!varName) continue;
240
+
241
+ const lineIndex = Number(item.lineNumber) - 1;
242
+ if (
243
+ Number.isInteger(lineIndex) &&
244
+ lineIndex >= 0 &&
245
+ lineIndex < lines.length &&
246
+ matchesVarAssignment(lines[lineIndex], varName, usePowerShell)
247
+ ) {
248
+ lineNumbersToRemove.add(lineIndex);
249
+ continue;
250
+ }
166
251
 
167
- // 收集要删除的行号
168
- const lineNumbersToRemove = new Set(vars.map(v => v.lineNumber));
252
+ const fallbackIndex = lines.findIndex(line => matchesVarAssignment(line, varName, usePowerShell));
253
+ if (fallbackIndex >= 0) {
254
+ lineNumbersToRemove.add(fallbackIndex);
255
+ }
256
+ }
169
257
 
170
- // 过滤掉要删除的行
171
- const newLines = lines.filter((_, index) => !lineNumbersToRemove.has(index + 1));
258
+ if (lineNumbersToRemove.size === 0) {
259
+ console.log(`[EnvManager] No matching vars found in ${filePath}, skip writing`);
260
+ return;
261
+ }
172
262
 
173
- // 写回文件
174
- fs.writeFileSync(filePath, newLines.join('\n'), 'utf-8');
263
+ const newLines = lines.filter((_, index) => !lineNumbersToRemove.has(index));
264
+ const nextContent = newLines.join('\n');
265
+ if (nextContent !== content) {
266
+ writeFileAtomic(filePath, nextContent);
267
+ }
175
268
 
176
- console.log(`[EnvManager] Removed ${vars.length} var(s) from ${filePath}`);
269
+ console.log(`[EnvManager] Removed ${lineNumbersToRemove.size} var(s) from ${filePath}`);
177
270
  }
178
271
 
179
272
  /**
@@ -255,21 +348,36 @@ function restoreFromBackup(backupPath) {
255
348
  * 恢复环境变量到文件
256
349
  */
257
350
  function restoreVarToFile(filePath, varName, value) {
351
+ const usePowerShell = isPowerShellProfile(filePath);
352
+ const exportLine = formatExportLine(varName, value, usePowerShell);
258
353
  let content = '';
259
354
 
260
355
  if (fs.existsSync(filePath)) {
261
356
  content = fs.readFileSync(filePath, 'utf-8');
262
- // 确保末尾有换行
263
- if (!content.endsWith('\n')) {
264
- content += '\n';
357
+ }
358
+
359
+ const lines = content ? content.split(/\r?\n/) : [];
360
+ let replaced = false;
361
+
362
+ for (let i = 0; i < lines.length; i++) {
363
+ if (matchesVarAssignment(lines[i], varName, usePowerShell)) {
364
+ lines[i] = exportLine;
365
+ replaced = true;
265
366
  }
266
367
  }
267
368
 
268
- // 添加环境变量
269
- const exportLine = `export ${varName}="${value}"`;
270
- content += exportLine + '\n';
369
+ if (!replaced) {
370
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
371
+ lines.pop();
372
+ }
373
+ if (lines.length > 0) {
374
+ lines.push('');
375
+ }
376
+ lines.push(exportLine);
377
+ }
271
378
 
272
- fs.writeFileSync(filePath, content, 'utf-8');
379
+ const nextContent = `${lines.join('\n')}\n`;
380
+ writeFileAtomic(filePath, nextContent);
273
381
 
274
382
  console.log(`[EnvManager] Restored ${varName} to ${filePath}`);
275
383
  }
@@ -2,7 +2,7 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
4
 
5
- const FAVORITES_DIR = path.join(os.homedir(), '.claude', 'cc-tool');
5
+ const FAVORITES_DIR = path.join(os.homedir(), '.cc-tool');
6
6
  const FAVORITES_FILE = path.join(FAVORITES_DIR, 'favorites.json');
7
7
 
8
8
  // 内存缓存
@@ -12,7 +12,8 @@ let cacheInitialized = false;
12
12
  const DEFAULT_FAVORITES = {
13
13
  claude: [],
14
14
  codex: [],
15
- gemini: []
15
+ gemini: [],
16
+ opencode: []
16
17
  };
17
18
 
18
19
  // Ensure favorites directory exists
@@ -36,7 +37,8 @@ function readFavoritesFromFile() {
36
37
  return {
37
38
  claude: data.claude || [],
38
39
  codex: data.codex || [],
39
- gemini: data.gemini || []
40
+ gemini: data.gemini || [],
41
+ opencode: data.opencode || []
40
42
  };
41
43
  } catch (error) {
42
44
  console.error('Error reading favorites file:', error);
@@ -16,6 +16,14 @@ const crypto = require('crypto');
16
16
  * - 使用 weight 和 maxConcurrency 控制负载均衡
17
17
  */
18
18
 
19
+ function normalizeGatewaySourceType(value, fallback = 'gemini') {
20
+ const normalized = String(value || '').trim().toLowerCase();
21
+ if (normalized === 'claude') return 'claude';
22
+ if (normalized === 'codex') return 'codex';
23
+ if (normalized === 'gemini') return 'gemini';
24
+ return fallback;
25
+ }
26
+
19
27
  // 获取 Gemini 配置目录
20
28
  function getGeminiDir() {
21
29
  return path.join(os.homedir(), '.gemini');
@@ -23,7 +31,7 @@ function getGeminiDir() {
23
31
 
24
32
  // 获取渠道存储文件路径
25
33
  function getChannelsFilePath() {
26
- const ccToolDir = path.join(os.homedir(), '.claude', 'cc-tool');
34
+ const ccToolDir = path.join(os.homedir(), '.cc-tool');
27
35
  if (!fs.existsSync(ccToolDir)) {
28
36
  fs.mkdirSync(ccToolDir, { recursive: true });
29
37
  }
@@ -66,15 +74,17 @@ function loadChannels() {
66
74
  const data = JSON.parse(content);
67
75
  // 确保渠道有 enabled 字段(兼容旧数据)
68
76
  if (data.channels) {
69
- data.channels = data.channels.map(ch => ({
70
- ...ch,
71
- enabled: ch.enabled !== false, // 默认启用
72
- weight: ch.weight || 1,
73
- maxConcurrency: ch.maxConcurrency || null,
74
- modelRedirects: ch.modelRedirects || [],
75
- speedTestModel: ch.speedTestModel || null,
76
- authType: ch.authType || 'apiKey'
77
- }));
77
+ data.channels = data.channels.map(ch => {
78
+ return {
79
+ ...ch,
80
+ enabled: ch.enabled !== false, // 默认启用
81
+ weight: ch.weight || 1,
82
+ maxConcurrency: ch.maxConcurrency || null,
83
+ modelRedirects: ch.modelRedirects || [],
84
+ speedTestModel: ch.speedTestModel || null,
85
+ gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'gemini')
86
+ };
87
+ });
78
88
  }
79
89
  return data;
80
90
  } catch (err) {
@@ -119,7 +129,7 @@ function initializeFromEnv() {
119
129
  enabled: true,
120
130
  weight: 1,
121
131
  maxConcurrency: null,
122
- authType: 'apiKey',
132
+ gatewaySourceType: 'gemini',
123
133
  createdAt: Date.now(),
124
134
  updatedAt: Date.now()
125
135
  };
@@ -177,9 +187,7 @@ function createChannel(name, baseUrl, apiKey, model = 'gemini-2.5-pro', extraCon
177
187
  maxConcurrency: extraConfig.maxConcurrency || null,
178
188
  modelRedirects: extraConfig.modelRedirects || [],
179
189
  speedTestModel: extraConfig.speedTestModel || null,
180
- authType: extraConfig.authType || 'apiKey',
181
- oauthProvider: extraConfig.oauthProvider || null,
182
- oauthTokenId: extraConfig.oauthTokenId || null,
190
+ gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'gemini'),
183
191
  createdAt: Date.now(),
184
192
  updatedAt: Date.now()
185
193
  };
@@ -212,15 +220,17 @@ function updateChannel(channelId, updates) {
212
220
  }
213
221
  }
214
222
 
215
- data.channels[index] = {
216
- ...oldChannel,
217
- ...updates,
223
+ const merged = { ...oldChannel, ...updates };
224
+ const nextChannel = {
225
+ ...merged,
218
226
  id: channelId, // 保持 ID 不变
219
227
  createdAt: oldChannel.createdAt, // 保持创建时间
220
228
  modelRedirects: updates.modelRedirects !== undefined ? updates.modelRedirects : (oldChannel.modelRedirects || []),
221
229
  speedTestModel: updates.speedTestModel !== undefined ? updates.speedTestModel : (oldChannel.speedTestModel || null),
230
+ gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'gemini'),
222
231
  updatedAt: Date.now()
223
232
  };
233
+ data.channels[index] = nextChannel;
224
234
 
225
235
  // Get proxy status
226
236
  const { getGeminiProxyStatus } = require('../gemini-proxy-server');
@@ -284,8 +294,9 @@ function applyChannelToSettings(channelId, channels = null) {
284
294
  const envPath = path.join(geminiDir, '.env');
285
295
 
286
296
  // 构建 .env 内容
297
+ const effectiveApiKey = getEffectiveApiKey(channel) || '';
287
298
  const envContent = `GOOGLE_GEMINI_BASE_URL=${channel.baseUrl}
288
- GEMINI_API_KEY=${channel.apiKey}
299
+ GEMINI_API_KEY=${effectiveApiKey}
289
300
  GEMINI_MODEL=${channel.model}
290
301
  `;
291
302
 
@@ -320,7 +331,7 @@ GEMINI_MODEL=${channel.model}
320
331
  }
321
332
 
322
333
  // 删除渠道
323
- function deleteChannel(channelId) {
334
+ async function deleteChannel(channelId) {
324
335
  const data = loadChannels();
325
336
 
326
337
  const index = data.channels.findIndex(c => c.id === channelId);
@@ -359,8 +370,9 @@ function writeGeminiConfigForMultiChannel(allChannels) {
359
370
  }
360
371
 
361
372
  // 构建 .env 内容
373
+ const effectiveApiKey = getEffectiveApiKey(defaultChannel) || '';
362
374
  const envContent = `GOOGLE_GEMINI_BASE_URL=${defaultChannel.baseUrl}
363
- GEMINI_API_KEY=${defaultChannel.apiKey}
375
+ GEMINI_API_KEY=${effectiveApiKey}
364
376
  GEMINI_MODEL=${defaultChannel.model}
365
377
  `;
366
378
 
@@ -397,31 +409,8 @@ function getEnabledChannels() {
397
409
  return data.channels.filter(c => c.enabled !== false);
398
410
  }
399
411
 
400
- /**
401
- * 获取渠道的有效 API Key
402
- * 如果是 OAuth 认证,尝试从 token 存储获取 access token
403
- * 否则返回渠道配置的 apiKey
404
- *
405
- * @param {Object} channel - 渠道对象
406
- * @returns {string|null} 有效的 API key 或 access token,OAuth 令牌无效/过期时返回 null
407
- */
408
412
  function getEffectiveApiKey(channel) {
409
- if (channel.authType === 'oauth' && channel.oauthTokenId) {
410
- try {
411
- const { getToken, isTokenExpired } = require('./oauth-token-storage');
412
- const token = getToken(channel.oauthTokenId);
413
- if (token && !isTokenExpired(token)) {
414
- return token.accessToken;
415
- }
416
- // OAuth 令牌无效或已过期,返回 null(调用方应处理刷新或报错)
417
- console.warn(`[Gemini Channels] OAuth token expired or not found for channel ${channel.name}`);
418
- return null;
419
- } catch (err) {
420
- console.error('[Gemini Channels] Failed to get OAuth token:', err.message);
421
- return null;
422
- }
423
- }
424
- return channel.apiKey;
413
+ return channel.apiKey || null;
425
414
  }
426
415
 
427
416
  // 保存渠道顺序
@@ -6,7 +6,7 @@ const os = require('os');
6
6
  * Gemini 统计服务 - 数据采集和存储
7
7
  *
8
8
  * 文件结构:
9
- * ~/.claude/cc-tool/
9
+ * ~/.cc-tool/
10
10
  * ├── gemini-statistics.json # Gemini 总体统计
11
11
  * └── gemini-daily-stats/
12
12
  * ├── 2025-12-05.json # 每日汇总统计
@@ -15,7 +15,7 @@ const os = require('os');
15
15
 
16
16
  // 获取基础目录
17
17
  function getBaseDir() {
18
- const dir = path.join(os.homedir(), '.claude', 'cc-tool');
18
+ const dir = path.join(os.homedir(), '.cc-tool');
19
19
  if (!fs.existsSync(dir)) {
20
20
  fs.mkdirSync(dir, { recursive: true });
21
21
  }