@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.
- package/CHANGELOG.md +15 -18
- package/README.md +8 -8
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-ZrK_s7ma.js +1 -0
- package/dist/web/assets/Home-B8YfhZ3c.js +1 -0
- package/dist/web/assets/Home-Di2qsylF.css +1 -0
- package/dist/web/assets/PluginManager-BD7QUZbU.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1fQb9OW.css +1 -0
- package/dist/web/assets/ProjectList-DRb1DuHV.js +1 -0
- package/dist/web/assets/SessionList-BGJWyneI.css +1 -0
- package/dist/web/assets/SessionList-lZ0LKzfT.js +1 -0
- package/dist/web/assets/SkillManager-C1xG5B4Q.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/Terminal-DGNJeVtc.css +1 -0
- package/dist/web/assets/Terminal-DksBo_lM.js +1 -0
- package/dist/web/assets/WorkspaceManager-Burx7XOo.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/icons-kcfLIMBB.js +1 -0
- package/dist/web/assets/index-Ufv5rCa5.css +1 -0
- package/dist/web/assets/index-lAkrRC3h.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-CSrLusZZ.js +1 -0
- package/dist/web/assets/{vendors-D2HHw_aW.js → vendors-CO3Upi1d.js} +2 -2
- package/dist/web/assets/vue-vendor-DqyWIXEb.js +45 -0
- package/dist/web/assets/xterm-6GBZ9nXN.css +32 -0
- package/dist/web/assets/xterm-BJzAjXCH.js +13 -0
- package/dist/web/index.html +8 -6
- package/package.json +4 -2
- package/src/commands/channels.js +48 -1
- package/src/commands/cli-type.js +4 -2
- package/src/commands/daemon.js +81 -12
- package/src/commands/doctor.js +10 -9
- package/src/commands/list.js +1 -1
- package/src/commands/logs.js +6 -4
- package/src/commands/port-config.js +24 -4
- package/src/commands/proxy-control.js +12 -6
- package/src/commands/search.js +1 -1
- package/src/commands/security.js +3 -2
- package/src/commands/stats.js +226 -52
- package/src/commands/switch.js +1 -1
- package/src/commands/toggle-proxy.js +31 -6
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +1 -1
- package/src/config/default.js +39 -2
- package/src/config/loader.js +74 -8
- package/src/config/paths.js +105 -33
- package/src/index.js +64 -3
- package/src/plugins/constants.js +3 -2
- package/src/plugins/plugin-api.js +1 -1
- package/src/reset-config.js +4 -2
- package/src/server/api/agents.js +57 -14
- package/src/server/api/channels.js +112 -33
- package/src/server/api/codex-channels.js +111 -18
- package/src/server/api/codex-proxy.js +14 -8
- package/src/server/api/commands.js +71 -18
- package/src/server/api/config-export.js +0 -6
- package/src/server/api/config-registry.js +11 -3
- package/src/server/api/config.js +376 -5
- package/src/server/api/convert.js +133 -0
- package/src/server/api/dashboard.js +22 -6
- package/src/server/api/gemini-channels.js +107 -18
- package/src/server/api/gemini-proxy.js +14 -8
- package/src/server/api/gemini-sessions.js +1 -1
- package/src/server/api/health-check.js +4 -3
- package/src/server/api/mcp.js +3 -3
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +198 -0
- package/src/server/api/opencode-sessions.js +403 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +66 -19
- package/src/server/api/prompts.js +2 -2
- package/src/server/api/proxy.js +7 -4
- package/src/server/api/sessions.js +3 -0
- package/src/server/api/skills.js +69 -18
- package/src/server/api/workspaces.js +78 -6
- package/src/server/codex-proxy-server.js +30 -18
- package/src/server/dev-server.js +1 -1
- package/src/server/gemini-proxy-server.js +15 -3
- package/src/server/index.js +165 -58
- package/src/server/opencode-proxy-server.js +4375 -0
- package/src/server/proxy-server.js +27 -18
- package/src/server/services/agents-service.js +61 -24
- package/src/server/services/channel-scheduler.js +9 -5
- package/src/server/services/channels.js +64 -37
- package/src/server/services/codex-channels.js +56 -43
- package/src/server/services/codex-settings-manager.js +271 -49
- package/src/server/services/codex-statistics-service.js +2 -2
- package/src/server/services/commands-service.js +84 -25
- package/src/server/services/config-export-service.js +7 -45
- package/src/server/services/config-registry-service.js +63 -17
- package/src/server/services/config-sync-manager.js +160 -7
- package/src/server/services/config-templates-service.js +204 -51
- package/src/server/services/env-checker.js +26 -12
- package/src/server/services/env-manager.js +126 -18
- package/src/server/services/favorites.js +5 -3
- package/src/server/services/gemini-channels.js +33 -44
- package/src/server/services/gemini-statistics-service.js +2 -2
- package/src/server/services/mcp-service.js +350 -9
- package/src/server/services/model-detector.js +707 -221
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +206 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +663 -0
- package/src/server/services/opencode-settings-manager.js +342 -0
- package/src/server/services/opencode-statistics-service.js +255 -0
- package/src/server/services/plugins-service.js +479 -22
- package/src/server/services/prompts-service.js +53 -11
- package/src/server/services/proxy-runtime.js +1 -1
- package/src/server/services/repo-scanner-base.js +1 -1
- package/src/server/services/security-config.js +1 -1
- package/src/server/services/session-cache.js +1 -1
- package/src/server/services/skill-service.js +300 -46
- package/src/server/services/speed-test.js +464 -186
- package/src/server/services/statistics-service.js +2 -2
- package/src/server/services/terminal-commands.js +10 -3
- package/src/server/services/terminal-config.js +1 -1
- package/src/server/services/ui-config.js +1 -1
- package/src/server/services/workspace-service.js +57 -100
- package/src/server/websocket-server.js +132 -3
- package/src/ui/menu.js +49 -40
- package/src/utils/port-helper.js +22 -8
- package/src/utils/session.js +5 -4
- package/dist/web/assets/icons-CO_2OFES.js +0 -1
- package/dist/web/assets/index-DI8QOi-E.js +0 -14
- package/dist/web/assets/index-uLHGdeZh.css +0 -41
- package/dist/web/assets/naive-ui-B1re3c-e.js +0 -1
- package/dist/web/assets/vue-vendor-6JaYHOiI.js +0 -44
- package/src/server/api/oauth.js +0 -294
- package/src/server/api/permissions.js +0 -385
- package/src/server/config/oauth-providers.js +0 -68
- package/src/server/services/oauth-callback-server.js +0 -284
- package/src/server/services/oauth-service.js +0 -378
- package/src/server/services/oauth-token-storage.js +0 -135
- 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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
171
|
-
|
|
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
|
|
9
|
+
const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
|
|
10
10
|
|
|
11
11
|
// 备份目录
|
|
12
|
-
const BACKUP_DIR =
|
|
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(
|
|
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
|
-
|
|
252
|
+
const fallbackIndex = lines.findIndex(line => matchesVarAssignment(line, varName, usePowerShell));
|
|
253
|
+
if (fallbackIndex >= 0) {
|
|
254
|
+
lineNumbersToRemove.add(fallbackIndex);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
169
257
|
|
|
170
|
-
|
|
171
|
-
|
|
258
|
+
if (lineNumbersToRemove.size === 0) {
|
|
259
|
+
console.log(`[EnvManager] No matching vars found in ${filePath}, skip writing`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
172
262
|
|
|
173
|
-
|
|
174
|
-
|
|
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 ${
|
|
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
|
-
|
|
264
|
-
|
|
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
|
-
|
|
270
|
-
|
|
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
|
-
|
|
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(), '.
|
|
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(), '.
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
...
|
|
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=${
|
|
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=${
|
|
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
|
-
|
|
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
|
-
* ~/.
|
|
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(), '.
|
|
18
|
+
const dir = path.join(os.homedir(), '.cc-tool');
|
|
19
19
|
if (!fs.existsSync(dir)) {
|
|
20
20
|
fs.mkdirSync(dir, { recursive: true });
|
|
21
21
|
}
|