@adversity/coding-tool-x 3.1.0 → 3.1.2

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 (142) hide show
  1. package/CHANGELOG.md +39 -18
  2. package/README.md +8 -8
  3. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  4. package/dist/web/assets/ConfigTemplates-DvcbKKdS.js +1 -0
  5. package/dist/web/assets/Home-BJKPCBuk.css +1 -0
  6. package/dist/web/assets/Home-Cw-F_Wnu.js +1 -0
  7. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  8. package/dist/web/assets/PluginManager-jy_4GVxI.js +1 -0
  9. package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
  10. package/dist/web/assets/ProjectList-Df1-NcNr.js +1 -0
  11. package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
  12. package/dist/web/assets/SessionList-UWcZtC2r.js +1 -0
  13. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  14. package/dist/web/assets/SkillManager-IRdseMKB.js +1 -0
  15. package/dist/web/assets/Terminal-BasTyDut.js +1 -0
  16. package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
  17. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  18. package/dist/web/assets/WorkspaceManager-D-D2kK1V.js +1 -0
  19. package/dist/web/assets/icons-kcfLIMBB.js +1 -0
  20. package/dist/web/assets/index-CoB3zF0K.css +1 -0
  21. package/dist/web/assets/index-CryrSLv8.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 +41 -2
  47. package/src/config/loader.js +74 -8
  48. package/src/config/model-metadata.js +415 -0
  49. package/src/config/model-pricing.js +23 -93
  50. package/src/config/paths.js +105 -33
  51. package/src/index.js +64 -3
  52. package/src/plugins/constants.js +3 -2
  53. package/src/plugins/plugin-api.js +1 -1
  54. package/src/reset-config.js +4 -2
  55. package/src/server/api/agents.js +57 -14
  56. package/src/server/api/channels.js +112 -33
  57. package/src/server/api/codex-channels.js +111 -18
  58. package/src/server/api/codex-proxy.js +14 -8
  59. package/src/server/api/commands.js +71 -18
  60. package/src/server/api/config-export.js +0 -6
  61. package/src/server/api/config-registry.js +11 -3
  62. package/src/server/api/config.js +376 -5
  63. package/src/server/api/convert.js +133 -0
  64. package/src/server/api/dashboard.js +22 -6
  65. package/src/server/api/gemini-channels.js +107 -18
  66. package/src/server/api/gemini-proxy.js +14 -8
  67. package/src/server/api/gemini-sessions.js +1 -1
  68. package/src/server/api/health-check.js +4 -3
  69. package/src/server/api/mcp.js +3 -3
  70. package/src/server/api/opencode-channels.js +497 -0
  71. package/src/server/api/opencode-projects.js +99 -0
  72. package/src/server/api/opencode-proxy.js +207 -0
  73. package/src/server/api/opencode-sessions.js +345 -0
  74. package/src/server/api/opencode-statistics.js +57 -0
  75. package/src/server/api/plugins.js +66 -19
  76. package/src/server/api/prompts.js +2 -2
  77. package/src/server/api/proxy.js +7 -4
  78. package/src/server/api/sessions.js +3 -0
  79. package/src/server/api/settings.js +111 -0
  80. package/src/server/api/skills.js +69 -18
  81. package/src/server/api/workspaces.js +78 -6
  82. package/src/server/codex-proxy-server.js +36 -22
  83. package/src/server/dev-server.js +1 -1
  84. package/src/server/gemini-proxy-server.js +21 -7
  85. package/src/server/index.js +174 -58
  86. package/src/server/opencode-proxy-server.js +5486 -0
  87. package/src/server/proxy-server.js +33 -22
  88. package/src/server/services/agents-service.js +61 -24
  89. package/src/server/services/channel-scheduler.js +9 -5
  90. package/src/server/services/channels.js +64 -37
  91. package/src/server/services/codex-channels.js +56 -43
  92. package/src/server/services/codex-sessions.js +105 -6
  93. package/src/server/services/codex-settings-manager.js +271 -49
  94. package/src/server/services/codex-statistics-service.js +2 -2
  95. package/src/server/services/commands-service.js +84 -25
  96. package/src/server/services/config-export-service.js +7 -45
  97. package/src/server/services/config-registry-service.js +63 -17
  98. package/src/server/services/config-sync-manager.js +160 -7
  99. package/src/server/services/config-templates-service.js +204 -51
  100. package/src/server/services/env-checker.js +50 -13
  101. package/src/server/services/env-manager.js +155 -19
  102. package/src/server/services/favorites.js +5 -3
  103. package/src/server/services/gemini-channels.js +33 -44
  104. package/src/server/services/gemini-statistics-service.js +2 -2
  105. package/src/server/services/mcp-service.js +350 -9
  106. package/src/server/services/model-detector.js +707 -221
  107. package/src/server/services/network-access.js +80 -0
  108. package/src/server/services/opencode-channels.js +208 -0
  109. package/src/server/services/opencode-gateway-converter.js +639 -0
  110. package/src/server/services/opencode-sessions.js +931 -0
  111. package/src/server/services/opencode-settings-manager.js +478 -0
  112. package/src/server/services/opencode-statistics-service.js +255 -0
  113. package/src/server/services/plugins-service.js +479 -22
  114. package/src/server/services/prompts-service.js +53 -11
  115. package/src/server/services/proxy-runtime.js +1 -1
  116. package/src/server/services/repo-scanner-base.js +1 -1
  117. package/src/server/services/response-decoder.js +21 -0
  118. package/src/server/services/security-config.js +1 -1
  119. package/src/server/services/session-cache.js +1 -1
  120. package/src/server/services/skill-service.js +300 -46
  121. package/src/server/services/speed-test.js +464 -186
  122. package/src/server/services/statistics-service.js +2 -2
  123. package/src/server/services/terminal-commands.js +10 -3
  124. package/src/server/services/terminal-config.js +1 -1
  125. package/src/server/services/ui-config.js +1 -1
  126. package/src/server/services/workspace-service.js +57 -100
  127. package/src/server/websocket-server.js +156 -8
  128. package/src/ui/menu.js +49 -40
  129. package/src/utils/port-helper.js +22 -8
  130. package/src/utils/session.js +5 -4
  131. package/dist/web/assets/icons-CO_2OFES.js +0 -1
  132. package/dist/web/assets/index-DI8QOi-E.js +0 -14
  133. package/dist/web/assets/index-uLHGdeZh.css +0 -41
  134. package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
  135. package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
  136. package/src/server/api/oauth.js +0 -294
  137. package/src/server/api/permissions.js +0 -385
  138. package/src/server/config/oauth-providers.js +0 -68
  139. package/src/server/services/oauth-callback-server.js +0 -284
  140. package/src/server/services/oauth-service.js +0 -378
  141. package/src/server/services/oauth-token-storage.js +0 -135
  142. package/src/server/services/permission-templates-service.js +0 -308
@@ -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
  }
@@ -62,11 +124,15 @@ function deleteEnvVars(conflicts) {
62
124
  }
63
125
  }
64
126
 
127
+ // 同步清理当前服务进程中的同名变量,避免“删除后立即复检仍提示冲突”
128
+ const clearedProcessVars = clearProcessEnvVars(conflicts);
129
+
65
130
  return {
66
131
  backupPath: backupInfo.backupPath,
67
132
  timestamp: backupInfo.timestamp,
68
133
  results,
69
- processConflictsSkipped: processConflicts.length
134
+ processConflictsSkipped: processConflicts.length,
135
+ clearedProcessVars
70
136
  };
71
137
  }
72
138
 
@@ -112,9 +178,14 @@ function getOriginalValue(conflict) {
112
178
 
113
179
  if (line) {
114
180
  const match = line.match(/^(?:export\s+)?[A-Z_][A-Z0-9_]*=(.*)$/);
115
- if (match) {
181
+ if (match && match[1] !== undefined) {
116
182
  return cleanValue(match[1]);
117
183
  }
184
+
185
+ const psMatch = line.match(/^\s*\$env:[A-Z_][A-Z0-9_]*\s*=\s*(.*)$/i);
186
+ if (psMatch && psMatch[1] !== undefined) {
187
+ return cleanValue(psMatch[1]);
188
+ }
118
189
  }
119
190
  } catch (err) {
120
191
  // 忽略
@@ -153,6 +224,30 @@ function groupByFile(conflicts) {
153
224
  return groups;
154
225
  }
155
226
 
227
+ /**
228
+ * 清理当前 Node 进程中的环境变量
229
+ */
230
+ function clearProcessEnvVars(conflicts) {
231
+ const names = new Set();
232
+
233
+ for (const conflict of conflicts || []) {
234
+ const varName = String(conflict?.varName || '').trim();
235
+ if (varName) {
236
+ names.add(varName);
237
+ }
238
+ }
239
+
240
+ const cleared = [];
241
+ for (const varName of names) {
242
+ if (Object.prototype.hasOwnProperty.call(process.env, varName)) {
243
+ delete process.env[varName];
244
+ cleared.push(varName);
245
+ }
246
+ }
247
+
248
+ return cleared;
249
+ }
250
+
156
251
  /**
157
252
  * 从文件中移除环境变量
158
253
  */
@@ -162,18 +257,44 @@ function removeVarsFromFile(filePath, vars) {
162
257
  }
163
258
 
164
259
  const content = fs.readFileSync(filePath, 'utf-8');
165
- const lines = content.split('\n');
260
+ const lines = content.split(/\r?\n/);
261
+ const usePowerShell = isPowerShellProfile(filePath);
262
+
263
+ // 收集要删除的行号(优先使用行号,行号不匹配时按变量名回退)
264
+ const lineNumbersToRemove = new Set();
265
+ for (const item of vars) {
266
+ const varName = String(item.varName || '').trim();
267
+ if (!varName) continue;
268
+
269
+ const lineIndex = Number(item.lineNumber) - 1;
270
+ if (
271
+ Number.isInteger(lineIndex) &&
272
+ lineIndex >= 0 &&
273
+ lineIndex < lines.length &&
274
+ matchesVarAssignment(lines[lineIndex], varName, usePowerShell)
275
+ ) {
276
+ lineNumbersToRemove.add(lineIndex);
277
+ continue;
278
+ }
166
279
 
167
- // 收集要删除的行号
168
- const lineNumbersToRemove = new Set(vars.map(v => v.lineNumber));
280
+ const fallbackIndex = lines.findIndex(line => matchesVarAssignment(line, varName, usePowerShell));
281
+ if (fallbackIndex >= 0) {
282
+ lineNumbersToRemove.add(fallbackIndex);
283
+ }
284
+ }
169
285
 
170
- // 过滤掉要删除的行
171
- const newLines = lines.filter((_, index) => !lineNumbersToRemove.has(index + 1));
286
+ if (lineNumbersToRemove.size === 0) {
287
+ console.log(`[EnvManager] No matching vars found in ${filePath}, skip writing`);
288
+ return;
289
+ }
172
290
 
173
- // 写回文件
174
- fs.writeFileSync(filePath, newLines.join('\n'), 'utf-8');
291
+ const newLines = lines.filter((_, index) => !lineNumbersToRemove.has(index));
292
+ const nextContent = newLines.join('\n');
293
+ if (nextContent !== content) {
294
+ writeFileAtomic(filePath, nextContent);
295
+ }
175
296
 
176
- console.log(`[EnvManager] Removed ${vars.length} var(s) from ${filePath}`);
297
+ console.log(`[EnvManager] Removed ${lineNumbersToRemove.size} var(s) from ${filePath}`);
177
298
  }
178
299
 
179
300
  /**
@@ -255,21 +376,36 @@ function restoreFromBackup(backupPath) {
255
376
  * 恢复环境变量到文件
256
377
  */
257
378
  function restoreVarToFile(filePath, varName, value) {
379
+ const usePowerShell = isPowerShellProfile(filePath);
380
+ const exportLine = formatExportLine(varName, value, usePowerShell);
258
381
  let content = '';
259
382
 
260
383
  if (fs.existsSync(filePath)) {
261
384
  content = fs.readFileSync(filePath, 'utf-8');
262
- // 确保末尾有换行
263
- if (!content.endsWith('\n')) {
264
- content += '\n';
385
+ }
386
+
387
+ const lines = content ? content.split(/\r?\n/) : [];
388
+ let replaced = false;
389
+
390
+ for (let i = 0; i < lines.length; i++) {
391
+ if (matchesVarAssignment(lines[i], varName, usePowerShell)) {
392
+ lines[i] = exportLine;
393
+ replaced = true;
265
394
  }
266
395
  }
267
396
 
268
- // 添加环境变量
269
- const exportLine = `export ${varName}="${value}"`;
270
- content += exportLine + '\n';
397
+ if (!replaced) {
398
+ while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
399
+ lines.pop();
400
+ }
401
+ if (lines.length > 0) {
402
+ lines.push('');
403
+ }
404
+ lines.push(exportLine);
405
+ }
271
406
 
272
- fs.writeFileSync(filePath, content, 'utf-8');
407
+ const nextContent = `${lines.join('\n')}\n`;
408
+ writeFileAtomic(filePath, nextContent);
273
409
 
274
410
  console.log(`[EnvManager] Restored ${varName} to ${filePath}`);
275
411
  }
@@ -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
  }