coding-tool-x 3.2.0
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 +599 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
- package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
- package/dist/web/assets/Home-38JTUlYt.js +1 -0
- package/dist/web/assets/Home-CjupSEWE.css +1 -0
- package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
- package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
- package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
- package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
- package/dist/web/assets/icons-DRrXwWZi.js +1 -0
- package/dist/web/assets/index-CetESrXw.css +1 -0
- package/dist/web/assets/index-Cfvn-2Gb.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-DlpKk-8M.js +1 -0
- package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
- package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +20 -0
- package/dist/web/logo.png +0 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/model-redirection.md +251 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +80 -0
- package/src/commands/channels.js +551 -0
- package/src/commands/cli-type.js +101 -0
- package/src/commands/daemon.js +365 -0
- package/src/commands/doctor.js +333 -0
- package/src/commands/export-config.js +205 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +261 -0
- package/src/commands/plugin.js +585 -0
- package/src/commands/port-config.js +135 -0
- package/src/commands/proxy-control.js +264 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/security.js +37 -0
- package/src/commands/stats.js +398 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +247 -0
- package/src/commands/ui.js +99 -0
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +69 -0
- package/src/config/loader.js +149 -0
- package/src/config/model-metadata.js +167 -0
- package/src/config/model-metadata.json +125 -0
- package/src/config/model-pricing.js +35 -0
- package/src/config/paths.js +190 -0
- package/src/index.js +680 -0
- package/src/plugins/constants.js +15 -0
- package/src/plugins/event-bus.js +54 -0
- package/src/plugins/manifest-validator.js +129 -0
- package/src/plugins/plugin-api.js +128 -0
- package/src/plugins/plugin-installer.js +601 -0
- package/src/plugins/plugin-loader.js +229 -0
- package/src/plugins/plugin-manager.js +170 -0
- package/src/plugins/registry.js +152 -0
- package/src/plugins/schema/plugin-manifest.json +115 -0
- package/src/reset-config.js +94 -0
- package/src/server/api/agents.js +826 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +368 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +417 -0
- package/src/server/api/codex-projects.js +104 -0
- package/src/server/api/codex-proxy.js +195 -0
- package/src/server/api/codex-sessions.js +483 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +482 -0
- package/src/server/api/config-export.js +212 -0
- package/src/server/api/config-registry.js +357 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +248 -0
- package/src/server/api/config.js +521 -0
- package/src/server/api/convert.js +260 -0
- package/src/server/api/dashboard.js +142 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +366 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +173 -0
- package/src/server/api/gemini-sessions.js +376 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +31 -0
- package/src/server/api/mcp.js +399 -0
- 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 +207 -0
- package/src/server/api/opencode-sessions.js +327 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +463 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +306 -0
- package/src/server/api/security.js +53 -0
- package/src/server/api/sessions.js +514 -0
- package/src/server/api/settings.js +142 -0
- package/src/server/api/skills.js +570 -0
- package/src/server/api/statistics.js +238 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +456 -0
- package/src/server/codex-proxy-server.js +681 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +610 -0
- package/src/server/index.js +422 -0
- package/src/server/opencode-proxy-server.js +4771 -0
- package/src/server/proxy-server.js +669 -0
- package/src/server/services/agents-service.js +1137 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +240 -0
- package/src/server/services/channels.js +447 -0
- package/src/server/services/codex-channels.js +705 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +936 -0
- package/src/server/services/codex-settings-manager.js +619 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +161 -0
- package/src/server/services/commands-service.js +574 -0
- package/src/server/services/config-export-service.js +1165 -0
- package/src/server/services/config-registry-service.js +828 -0
- package/src/server/services/config-sync-manager.js +941 -0
- package/src/server/services/config-sync-service.js +504 -0
- package/src/server/services/config-templates-service.js +913 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/env-checker.js +409 -0
- package/src/server/services/env-manager.js +436 -0
- package/src/server/services/favorites.js +165 -0
- package/src/server/services/format-converter.js +620 -0
- package/src/server/services/gemini-channels.js +459 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +157 -0
- package/src/server/services/health-check.js +85 -0
- package/src/server/services/mcp-client.js +790 -0
- package/src/server/services/mcp-service.js +1732 -0
- package/src/server/services/model-detector.js +1245 -0
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +366 -0
- package/src/server/services/opencode-gateway-adapters.js +1168 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +161 -0
- package/src/server/services/plugins-service.js +1268 -0
- package/src/server/services/prompts-service.js +534 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/repo-scanner-base.js +708 -0
- package/src/server/services/request-logger.js +130 -0
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +131 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +900 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +1482 -0
- package/src/server/services/speed-test.js +1146 -0
- package/src/server/services/statistics-service.js +1043 -0
- package/src/server/services/ui-config.js +132 -0
- package/src/server/services/workspace-service.js +830 -0
- package/src/server/utils/pricing.js +73 -0
- package/src/server/websocket-server.js +513 -0
- package/src/ui/menu.js +139 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +108 -0
- package/src/utils/session.js +240 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const { isProxyConfig } = require('./settings-manager');
|
|
5
|
+
|
|
6
|
+
function getChannelsFilePath() {
|
|
7
|
+
const dir = path.join(os.homedir(), '.cc-tool');
|
|
8
|
+
if (!fs.existsSync(dir)) {
|
|
9
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
return path.join(dir, 'channels.json');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function getActiveChannelIdPath() {
|
|
15
|
+
const dir = path.join(os.homedir(), '.cc-tool');
|
|
16
|
+
if (!fs.existsSync(dir)) {
|
|
17
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
return path.join(dir, 'active-channel.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getClaudeSettingsPath() {
|
|
23
|
+
return path.join(os.homedir(), '.claude', 'settings.json');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function saveActiveChannelId(channelId) {
|
|
27
|
+
const filePath = getActiveChannelIdPath();
|
|
28
|
+
fs.writeFileSync(filePath, JSON.stringify({ activeChannelId: channelId }, null, 2), 'utf8');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadActiveChannelId() {
|
|
32
|
+
const filePath = getActiveChannelIdPath();
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(filePath)) {
|
|
35
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
36
|
+
const data = JSON.parse(content);
|
|
37
|
+
return data.activeChannelId || null;
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error('Error loading active channel ID:', error);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let channelsCache = null;
|
|
46
|
+
let channelsCacheInitialized = false;
|
|
47
|
+
const DEFAULT_CHANNELS = { channels: [] };
|
|
48
|
+
|
|
49
|
+
function normalizeNumber(value, defaultValue, max = null) {
|
|
50
|
+
const num = Number(value);
|
|
51
|
+
if (!Number.isFinite(num) || num <= 0) {
|
|
52
|
+
return defaultValue;
|
|
53
|
+
}
|
|
54
|
+
if (max !== null && num > max) {
|
|
55
|
+
return max;
|
|
56
|
+
}
|
|
57
|
+
return num;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function normalizeGatewaySourceType(value, fallback = 'claude') {
|
|
61
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
62
|
+
if (normalized === 'claude') return 'claude';
|
|
63
|
+
if (normalized === 'codex') return 'codex';
|
|
64
|
+
if (normalized === 'gemini') return 'gemini';
|
|
65
|
+
return fallback;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function extractApiKeyFromHelper(apiKeyHelper) {
|
|
69
|
+
if (typeof apiKeyHelper !== 'string' || !apiKeyHelper.trim()) {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const helper = apiKeyHelper.trim();
|
|
74
|
+
let match = helper.match(/^echo\s+["']([^"']+)["']$/);
|
|
75
|
+
if (match && match[1]) {
|
|
76
|
+
return match[1];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
match = helper.match(/^printf\s+["'][^"']*["']\s+["']([^"']+)["']$/);
|
|
80
|
+
if (match && match[1]) {
|
|
81
|
+
return match[1];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return '';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildApiKeyHelperCommand() {
|
|
88
|
+
// 避免把明文 API Key 写入可执行命令,降低注入风险
|
|
89
|
+
return 'printf "%s" "${ANTHROPIC_AUTH_TOKEN:-${ANTHROPIC_API_KEY:-}}"';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function applyChannelDefaults(channel) {
|
|
93
|
+
const normalized = { ...channel };
|
|
94
|
+
if (normalized.enabled === undefined) {
|
|
95
|
+
normalized.enabled = true;
|
|
96
|
+
} else {
|
|
97
|
+
normalized.enabled = !!normalized.enabled;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
normalized.weight = normalizeNumber(normalized.weight, 1, 100);
|
|
101
|
+
|
|
102
|
+
if (normalized.maxConcurrency === undefined ||
|
|
103
|
+
normalized.maxConcurrency === null ||
|
|
104
|
+
normalized.maxConcurrency === 0) {
|
|
105
|
+
normalized.maxConcurrency = null;
|
|
106
|
+
} else {
|
|
107
|
+
normalized.maxConcurrency = normalizeNumber(normalized.maxConcurrency, 1, 100);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
normalized.gatewaySourceType = normalizeGatewaySourceType(normalized.gatewaySourceType, 'claude');
|
|
111
|
+
|
|
112
|
+
return normalized;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function readChannelsFromFile() {
|
|
116
|
+
const filePath = getChannelsFilePath();
|
|
117
|
+
try {
|
|
118
|
+
if (fs.existsSync(filePath)) {
|
|
119
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
120
|
+
const data = JSON.parse(content);
|
|
121
|
+
data.channels = (data.channels || []).map(applyChannelDefaults);
|
|
122
|
+
return data;
|
|
123
|
+
}
|
|
124
|
+
} catch (error) {
|
|
125
|
+
console.error('Error loading channels:', error);
|
|
126
|
+
}
|
|
127
|
+
return { ...DEFAULT_CHANNELS };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function initializeChannelsCache() {
|
|
131
|
+
if (channelsCacheInitialized) return;
|
|
132
|
+
channelsCache = readChannelsFromFile();
|
|
133
|
+
channelsCacheInitialized = true;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const filePath = getChannelsFilePath();
|
|
137
|
+
fs.watchFile(filePath, { persistent: false }, () => {
|
|
138
|
+
channelsCache = readChannelsFromFile();
|
|
139
|
+
});
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error('Failed to watch channels file:', err);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function loadChannels() {
|
|
146
|
+
if (!channelsCacheInitialized) {
|
|
147
|
+
initializeChannelsCache();
|
|
148
|
+
}
|
|
149
|
+
return JSON.parse(JSON.stringify(channelsCache));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function saveChannels(data) {
|
|
153
|
+
const filePath = getChannelsFilePath();
|
|
154
|
+
const payload = {
|
|
155
|
+
...data,
|
|
156
|
+
channels: (data.channels || []).map(applyChannelDefaults)
|
|
157
|
+
};
|
|
158
|
+
fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8');
|
|
159
|
+
channelsCache = JSON.parse(JSON.stringify(payload));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getCurrentSettings() {
|
|
163
|
+
try {
|
|
164
|
+
const settingsPath = getClaudeSettingsPath();
|
|
165
|
+
if (!fs.existsSync(settingsPath)) {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
169
|
+
|
|
170
|
+
let baseUrl = settings.env?.ANTHROPIC_BASE_URL || '';
|
|
171
|
+
let apiKey = settings.env?.ANTHROPIC_API_KEY ||
|
|
172
|
+
settings.env?.ANTHROPIC_AUTH_TOKEN ||
|
|
173
|
+
'';
|
|
174
|
+
|
|
175
|
+
if (!apiKey && settings.apiKeyHelper) {
|
|
176
|
+
apiKey = extractApiKeyFromHelper(settings.apiKeyHelper);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!baseUrl && !apiKey) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { baseUrl, apiKey };
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('Error reading current settings:', error);
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function getBestChannelForRestore() {
|
|
191
|
+
const data = loadChannels();
|
|
192
|
+
const enabledChannels = data.channels.filter(ch => ch.enabled !== false);
|
|
193
|
+
|
|
194
|
+
if (enabledChannels.length === 0) {
|
|
195
|
+
return data.channels[0];
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
enabledChannels.sort((a, b) => (b.weight || 1) - (a.weight || 1));
|
|
199
|
+
return enabledChannels[0];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function getAllChannels() {
|
|
203
|
+
const data = loadChannels();
|
|
204
|
+
return data.channels;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function getCurrentChannel() {
|
|
208
|
+
const channels = getAllChannels();
|
|
209
|
+
if (!Array.isArray(channels) || channels.length === 0) {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const activeChannelId = loadActiveChannelId();
|
|
214
|
+
if (activeChannelId) {
|
|
215
|
+
const matched = channels.find(ch => ch.id === activeChannelId);
|
|
216
|
+
if (matched) {
|
|
217
|
+
return matched;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return channels.find(ch => ch.enabled !== false) || channels[0];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function createChannel(name, baseUrl, apiKey, websiteUrl, extraConfig = {}) {
|
|
225
|
+
const data = loadChannels();
|
|
226
|
+
const newChannel = applyChannelDefaults({
|
|
227
|
+
id: `channel-${Date.now()}`,
|
|
228
|
+
name,
|
|
229
|
+
baseUrl,
|
|
230
|
+
apiKey,
|
|
231
|
+
createdAt: Date.now(),
|
|
232
|
+
websiteUrl: websiteUrl || undefined,
|
|
233
|
+
enabled: extraConfig.enabled !== undefined ? !!extraConfig.enabled : true,
|
|
234
|
+
weight: extraConfig.weight,
|
|
235
|
+
maxConcurrency: extraConfig.maxConcurrency,
|
|
236
|
+
presetId: extraConfig.presetId || 'official',
|
|
237
|
+
modelConfig: extraConfig.modelConfig || null,
|
|
238
|
+
modelRedirects: extraConfig.modelRedirects || [],
|
|
239
|
+
proxyUrl: extraConfig.proxyUrl || '',
|
|
240
|
+
speedTestModel: extraConfig.speedTestModel || null,
|
|
241
|
+
gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'claude')
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
data.channels.push(newChannel);
|
|
245
|
+
saveChannels(data);
|
|
246
|
+
return newChannel;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function updateChannel(id, updates) {
|
|
250
|
+
const data = loadChannels();
|
|
251
|
+
const index = data.channels.findIndex(ch => ch.id === id);
|
|
252
|
+
|
|
253
|
+
if (index === -1) {
|
|
254
|
+
throw new Error('Channel not found');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Store old channel data before updates
|
|
258
|
+
const oldChannel = { ...data.channels[index] };
|
|
259
|
+
|
|
260
|
+
const merged = { ...data.channels[index], ...updates };
|
|
261
|
+
const nextChannel = applyChannelDefaults({
|
|
262
|
+
...merged,
|
|
263
|
+
weight: merged.weight,
|
|
264
|
+
maxConcurrency: merged.maxConcurrency,
|
|
265
|
+
enabled: merged.enabled,
|
|
266
|
+
presetId: merged.presetId,
|
|
267
|
+
modelConfig: merged.modelConfig,
|
|
268
|
+
modelRedirects: merged.modelRedirects || [],
|
|
269
|
+
proxyUrl: merged.proxyUrl,
|
|
270
|
+
speedTestModel: merged.speedTestModel,
|
|
271
|
+
gatewaySourceType: normalizeGatewaySourceType(merged.gatewaySourceType, 'claude'),
|
|
272
|
+
updatedAt: Date.now()
|
|
273
|
+
});
|
|
274
|
+
data.channels[index] = nextChannel;
|
|
275
|
+
|
|
276
|
+
// Get proxy status
|
|
277
|
+
const { getProxyStatus } = require('../proxy-server');
|
|
278
|
+
const proxyStatus = getProxyStatus();
|
|
279
|
+
const isProxyRunning = proxyStatus.running;
|
|
280
|
+
|
|
281
|
+
// Single-channel enforcement when proxy is OFF: enabling a channel disables all others
|
|
282
|
+
if (!isProxyRunning && nextChannel.enabled && !oldChannel.enabled) {
|
|
283
|
+
data.channels.forEach((ch, i) => {
|
|
284
|
+
if (i !== index && ch.enabled) {
|
|
285
|
+
ch.enabled = false;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
console.log(`[Single-channel mode] Enabled "${nextChannel.name}", disabled all others`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Prevent disabling last enabled channel when proxy is OFF
|
|
292
|
+
if (!isProxyRunning && !nextChannel.enabled && oldChannel.enabled) {
|
|
293
|
+
const enabledCount = data.channels.filter(ch => ch.enabled).length;
|
|
294
|
+
if (enabledCount === 0) {
|
|
295
|
+
throw new Error('无法禁用最后一个启用的渠道。请先启用其他渠道或启动动态切换。');
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
saveChannels(data);
|
|
300
|
+
|
|
301
|
+
// Sync settings.json when proxy is OFF and the channel is (or just became) enabled
|
|
302
|
+
if (!isProxyRunning && nextChannel.enabled) {
|
|
303
|
+
console.log(`[Settings-sync] Proxy is OFF and channel "${nextChannel.name}" is enabled, syncing settings.json...`);
|
|
304
|
+
updateClaudeSettingsWithModelConfig(nextChannel);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return data.channels[index];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function deleteChannel(id) {
|
|
311
|
+
const data = loadChannels();
|
|
312
|
+
const index = data.channels.findIndex(ch => ch.id === id);
|
|
313
|
+
|
|
314
|
+
if (index === -1) {
|
|
315
|
+
throw new Error('Channel not found');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
data.channels.splice(index, 1);
|
|
319
|
+
saveChannels(data);
|
|
320
|
+
|
|
321
|
+
return { success: true };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function applyChannelToSettings(id) {
|
|
325
|
+
const data = loadChannels();
|
|
326
|
+
const channel = data.channels.find(ch => ch.id === id);
|
|
327
|
+
|
|
328
|
+
if (!channel) {
|
|
329
|
+
throw new Error('Channel not found');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// In single-channel mode, only this channel should be enabled
|
|
333
|
+
data.channels.forEach(ch => {
|
|
334
|
+
ch.enabled = ch.id === id;
|
|
335
|
+
});
|
|
336
|
+
saveChannels(data);
|
|
337
|
+
updateClaudeSettingsWithModelConfig(channel);
|
|
338
|
+
|
|
339
|
+
return channel;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function updateClaudeSettingsWithModelConfig(channel) {
|
|
343
|
+
const settingsPath = getClaudeSettingsPath();
|
|
344
|
+
|
|
345
|
+
let settings = {};
|
|
346
|
+
if (fs.existsSync(settingsPath)) {
|
|
347
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!settings.env) {
|
|
351
|
+
settings.env = {};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
const { baseUrl, apiKey, modelConfig, presetId, proxyUrl } = channel;
|
|
355
|
+
|
|
356
|
+
const useAuthToken = settings.env.ANTHROPIC_AUTH_TOKEN !== undefined;
|
|
357
|
+
const useApiKey = settings.env.ANTHROPIC_API_KEY !== undefined;
|
|
358
|
+
|
|
359
|
+
settings.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
360
|
+
|
|
361
|
+
if (useAuthToken || (!useAuthToken && !useApiKey)) {
|
|
362
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
363
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
364
|
+
} else {
|
|
365
|
+
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (presetId && presetId !== 'official' && modelConfig) {
|
|
369
|
+
if (modelConfig.model) {
|
|
370
|
+
settings.env.ANTHROPIC_MODEL = modelConfig.model;
|
|
371
|
+
}
|
|
372
|
+
if (modelConfig.haikuModel) {
|
|
373
|
+
settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = modelConfig.haikuModel;
|
|
374
|
+
}
|
|
375
|
+
if (modelConfig.sonnetModel) {
|
|
376
|
+
settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL = modelConfig.sonnetModel;
|
|
377
|
+
}
|
|
378
|
+
if (modelConfig.opusModel) {
|
|
379
|
+
settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL = modelConfig.opusModel;
|
|
380
|
+
}
|
|
381
|
+
} else {
|
|
382
|
+
delete settings.env.ANTHROPIC_MODEL;
|
|
383
|
+
delete settings.env.ANTHROPIC_DEFAULT_HAIKU_MODEL;
|
|
384
|
+
delete settings.env.ANTHROPIC_DEFAULT_SONNET_MODEL;
|
|
385
|
+
delete settings.env.ANTHROPIC_DEFAULT_OPUS_MODEL;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (proxyUrl) {
|
|
389
|
+
settings.env.HTTPS_PROXY = proxyUrl;
|
|
390
|
+
settings.env.HTTP_PROXY = proxyUrl;
|
|
391
|
+
} else {
|
|
392
|
+
delete settings.env.HTTPS_PROXY;
|
|
393
|
+
delete settings.env.HTTP_PROXY;
|
|
394
|
+
delete settings.env.NO_PROXY;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
settings.apiKeyHelper = buildApiKeyHelperCommand();
|
|
398
|
+
|
|
399
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function updateClaudeSettings(baseUrl, apiKey) {
|
|
403
|
+
const settingsPath = getClaudeSettingsPath();
|
|
404
|
+
|
|
405
|
+
let settings = {};
|
|
406
|
+
if (fs.existsSync(settingsPath)) {
|
|
407
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!settings.env) {
|
|
411
|
+
settings.env = {};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const useAuthToken = settings.env.ANTHROPIC_AUTH_TOKEN !== undefined;
|
|
415
|
+
const useApiKey = settings.env.ANTHROPIC_API_KEY !== undefined;
|
|
416
|
+
|
|
417
|
+
settings.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
418
|
+
|
|
419
|
+
if (useAuthToken || (!useAuthToken && !useApiKey)) {
|
|
420
|
+
settings.env.ANTHROPIC_AUTH_TOKEN = apiKey;
|
|
421
|
+
delete settings.env.ANTHROPIC_API_KEY;
|
|
422
|
+
} else {
|
|
423
|
+
settings.env.ANTHROPIC_API_KEY = apiKey;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
settings.apiKeyHelper = buildApiKeyHelperCommand();
|
|
427
|
+
|
|
428
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function getEffectiveApiKey(channel) {
|
|
432
|
+
return channel.apiKey || null;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
module.exports = {
|
|
436
|
+
getAllChannels,
|
|
437
|
+
getCurrentChannel,
|
|
438
|
+
getCurrentSettings,
|
|
439
|
+
createChannel,
|
|
440
|
+
updateChannel,
|
|
441
|
+
deleteChannel,
|
|
442
|
+
applyChannelToSettings,
|
|
443
|
+
getBestChannelForRestore,
|
|
444
|
+
updateClaudeSettings,
|
|
445
|
+
updateClaudeSettingsWithModelConfig,
|
|
446
|
+
getEffectiveApiKey
|
|
447
|
+
};
|