coding-tool-x 3.2.2 → 3.3.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 +20 -0
- package/dist/web/assets/{Analytics-COVBIlMT.js → Analytics-BskCbia_.js} +1 -1
- package/dist/web/assets/{ConfigTemplates-CwCbgetE.js → ConfigTemplates-B4X3rgfY.js} +1 -1
- package/dist/web/assets/{Home-CgMMTGxS.js → Home-DHYMMKOU.js} +1 -1
- package/dist/web/assets/{PluginManager-DQ4B002M.js → PluginManager-D_LoULGH.js} +1 -1
- package/dist/web/assets/{ProjectList-BT99XzrL.js → ProjectList-DiV4Qwa1.js} +1 -1
- package/dist/web/assets/{SessionList-ButOecT4.js → SessionList-B24o0wiX.js} +1 -1
- package/dist/web/assets/{SkillManager-e2C5kuhp.js → SkillManager-B9Rnuaig.js} +1 -1
- package/dist/web/assets/{WorkspaceManager-Dh5Rzjkr.js → WorkspaceManager-BkL2l5J9.js} +1 -1
- package/dist/web/assets/icons-B29onFfZ.js +1 -0
- package/dist/web/assets/index-C5j22icm.css +1 -0
- package/dist/web/assets/index-ZttxvTKw.js +2 -0
- package/dist/web/assets/{naive-ui-DlpKk-8M.js → naive-ui-CxpuzdjU.js} +1 -1
- package/dist/web/index.html +4 -4
- package/package.json +1 -1
- package/src/server/api/opencode-channels.js +30 -2
- package/src/server/opencode-proxy-server.js +16 -116
- package/src/server/proxy-server.js +2 -10
- package/src/server/services/channels.js +7 -5
- package/src/server/services/codex-channels.js +7 -5
- package/src/server/services/codex-settings-manager.js +13 -0
- package/src/server/services/config-templates-service.js +28 -22
- package/src/server/services/gemini-channels.js +7 -5
- package/src/server/services/mcp-service.js +22 -1
- package/src/server/services/request-logger.js +190 -0
- package/src/server/services/speed-test.js +17 -108
- package/src/utils/port-helper.js +26 -5
- package/dist/web/assets/icons-DRrXwWZi.js +0 -1
- package/dist/web/assets/index-CwGg4bbn.css +0 -1
- package/dist/web/assets/index-j56-PHWL.js +0 -2
package/dist/web/index.html
CHANGED
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
<link rel="icon" href="/favicon.ico">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>CC-TOOL - ClaudeCode增强工作助手</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-ZttxvTKw.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/markdown-C9MYpaSi.js">
|
|
10
10
|
<link rel="modulepreload" crossorigin href="/assets/vue-vendor-DET08QYg.js">
|
|
11
11
|
<link rel="modulepreload" crossorigin href="/assets/vendors-DMjSfzlv.js">
|
|
12
|
-
<link rel="modulepreload" crossorigin href="/assets/naive-ui-
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/assets/icons-
|
|
12
|
+
<link rel="modulepreload" crossorigin href="/assets/naive-ui-CxpuzdjU.js">
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/assets/icons-B29onFfZ.js">
|
|
14
14
|
<link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
|
|
15
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C5j22icm.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
18
18
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -67,7 +67,8 @@ module.exports = (config) => {
|
|
|
67
67
|
const value = String(channel?.gatewaySourceType || '').trim().toLowerCase();
|
|
68
68
|
if (value === 'claude') return 'claude';
|
|
69
69
|
if (value === 'gemini') return 'gemini';
|
|
70
|
-
return 'codex';
|
|
70
|
+
if (value === 'codex') return 'codex';
|
|
71
|
+
return 'openai_compatible';
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
function mapGatewaySourceTypeToSpeedTestType(channel) {
|
|
@@ -130,6 +131,32 @@ module.exports = (config) => {
|
|
|
130
131
|
}
|
|
131
132
|
});
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* POST /api/opencode/channels/probe-models
|
|
136
|
+
* 用临时配置(新建渠道时)获取模型列表,无需 channelId
|
|
137
|
+
*/
|
|
138
|
+
router.post('/probe-models', async (req, res) => {
|
|
139
|
+
try {
|
|
140
|
+
const { baseUrl, apiKey, gatewaySourceType } = req.body;
|
|
141
|
+
if (!baseUrl) {
|
|
142
|
+
return res.status(400).json({ error: 'baseUrl is required' });
|
|
143
|
+
}
|
|
144
|
+
const tempChannel = { baseUrl, apiKey: apiKey || '', gatewaySourceType: gatewaySourceType || 'codex' };
|
|
145
|
+
const gst = resolveGatewaySourceType(tempChannel);
|
|
146
|
+
const listResult = await fetchModelsFromProvider(tempChannel, gst, { useV1ModelsEndpoint: true, forceRefresh: true });
|
|
147
|
+
const listedModels = Array.isArray(listResult.models) ? uniqueModels(listResult.models) : [];
|
|
148
|
+
res.json({
|
|
149
|
+
models: listedModels,
|
|
150
|
+
supported: listedModels.length > 0,
|
|
151
|
+
error: listedModels.length > 0 ? null : (listResult.error || '未返回可用模型列表'),
|
|
152
|
+
errorHint: listedModels.length > 0 ? null : (listResult.errorHint || '请手动填写模型名称')
|
|
153
|
+
});
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.error('[OpenCode Channels API] Error probing models:', error);
|
|
156
|
+
res.status(500).json({ error: 'Failed to probe models' });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
|
|
133
160
|
/**
|
|
134
161
|
* GET /api/opencode/channels/:channelId/models
|
|
135
162
|
* 获取渠道可用模型列表
|
|
@@ -144,9 +171,10 @@ module.exports = (config) => {
|
|
|
144
171
|
return res.status(404).json({ error: 'Channel not found' });
|
|
145
172
|
}
|
|
146
173
|
|
|
174
|
+
const forceRefresh = req.query.forceRefresh === 'true';
|
|
147
175
|
const gatewaySourceType = resolveGatewaySourceType(channel);
|
|
148
176
|
const preferredModels = collectChannelPreferredModels(channel);
|
|
149
|
-
const listResult = await fetchModelsFromProvider(channel,
|
|
177
|
+
const listResult = await fetchModelsFromProvider(channel, gatewaySourceType, { useV1ModelsEndpoint: true, forceRefresh });
|
|
150
178
|
const listedModels = Array.isArray(listResult.models) ? uniqueModels(listResult.models) : [];
|
|
151
179
|
const shouldProbeByDefault = !!listResult.disabledByConfig;
|
|
152
180
|
let result;
|
|
@@ -18,7 +18,7 @@ const { resolvePricing } = require('./utils/pricing');
|
|
|
18
18
|
const { recordRequest: recordOpenCodeRequest } = require('./services/opencode-statistics-service');
|
|
19
19
|
const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRuntime } = require('./services/proxy-runtime');
|
|
20
20
|
const { getEnabledChannels, getEffectiveApiKey } = require('./services/opencode-channels');
|
|
21
|
-
const { persistProxyRequestSnapshot } = require('./services/request-logger');
|
|
21
|
+
const { persistProxyRequestSnapshot, loadClaudeRequestTemplate } = require('./services/request-logger');
|
|
22
22
|
const { probeModelAvailability, fetchModelsFromProvider } = require('./services/model-detector');
|
|
23
23
|
const { CLAUDE_MODEL_PRICING } = require('../config/model-pricing');
|
|
24
24
|
|
|
@@ -279,69 +279,24 @@ function resolveClaudeAccountIdFromUserId(userId = '') {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
function resolveClaudeAccountIdFromLogs() {
|
|
282
|
-
const logsPath = path.join(os.homedir(), '.cc-tool', 'claude-requests.jsonl');
|
|
283
|
-
if (!fs.existsSync(logsPath)) return '';
|
|
284
|
-
|
|
285
282
|
try {
|
|
286
|
-
const
|
|
287
|
-
const
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
291
|
-
const line = lines[index].trim();
|
|
292
|
-
if (!line) continue;
|
|
293
|
-
try {
|
|
294
|
-
const parsed = JSON.parse(line);
|
|
295
|
-
const userId = parsed?.request?.body?.metadata?.user_id;
|
|
296
|
-
const accountId = resolveClaudeAccountIdFromUserId(userId);
|
|
297
|
-
if (accountId) {
|
|
298
|
-
accountIdCount.set(accountId, (accountIdCount.get(accountId) || 0) + 1);
|
|
299
|
-
}
|
|
300
|
-
} catch {
|
|
301
|
-
// ignore malformed line
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const ranked = Array.from(accountIdCount.entries())
|
|
306
|
-
.filter(([accountId]) => accountId !== '0'.repeat(64))
|
|
307
|
-
.sort((left, right) => right[1] - left[1]);
|
|
308
|
-
|
|
309
|
-
if (ranked.length > 0) {
|
|
310
|
-
return ranked[0][0];
|
|
311
|
-
}
|
|
283
|
+
const template = loadClaudeRequestTemplate();
|
|
284
|
+
const userId = normalizeSessionKeyValue(template?.userId || '');
|
|
285
|
+
const accountId = resolveClaudeAccountIdFromUserId(userId);
|
|
286
|
+
return (accountId && accountId !== '0'.repeat(64)) ? accountId : '';
|
|
312
287
|
} catch {
|
|
313
|
-
|
|
288
|
+
return '';
|
|
314
289
|
}
|
|
315
|
-
|
|
316
|
-
return '';
|
|
317
290
|
}
|
|
318
291
|
|
|
319
292
|
function resolveClaudeUserIdFromLogs() {
|
|
320
|
-
const logsPath = path.join(os.homedir(), '.cc-tool', 'claude-requests.jsonl');
|
|
321
|
-
if (!fs.existsSync(logsPath)) return '';
|
|
322
|
-
|
|
323
293
|
try {
|
|
324
|
-
const
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (!line) continue;
|
|
331
|
-
try {
|
|
332
|
-
const parsed = JSON.parse(line);
|
|
333
|
-
const userId = normalizeSessionKeyValue(parsed?.request?.body?.metadata?.user_id);
|
|
334
|
-
if (!CLAUDE_USER_ID_FULL_RE.test(userId)) continue;
|
|
335
|
-
const accountId = resolveClaudeAccountIdFromUserId(userId);
|
|
336
|
-
if (!accountId || accountId === '0'.repeat(64)) continue;
|
|
337
|
-
userIdCount.set(userId, (userIdCount.get(userId) || 0) + 1);
|
|
338
|
-
} catch {
|
|
339
|
-
// ignore malformed line
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const ranked = Array.from(userIdCount.entries()).sort((left, right) => right[1] - left[1]);
|
|
344
|
-
return ranked.length > 0 ? ranked[0][0] : '';
|
|
294
|
+
const template = loadClaudeRequestTemplate();
|
|
295
|
+
const userId = normalizeSessionKeyValue(template?.userId || '');
|
|
296
|
+
if (!CLAUDE_USER_ID_FULL_RE.test(userId)) return '';
|
|
297
|
+
const accountId = resolveClaudeAccountIdFromUserId(userId);
|
|
298
|
+
if (!accountId || accountId === '0'.repeat(64)) return '';
|
|
299
|
+
return userId;
|
|
345
300
|
} catch {
|
|
346
301
|
return '';
|
|
347
302
|
}
|
|
@@ -540,17 +495,6 @@ function buildClaudeBetaHeader(options = {}) {
|
|
|
540
495
|
return betaFlags.join(',');
|
|
541
496
|
}
|
|
542
497
|
|
|
543
|
-
function buildDefaultClaudeCodeTools() {
|
|
544
|
-
return DEFAULT_CLAUDE_CODE_TOOL_NAMES.map(name => ({
|
|
545
|
-
name,
|
|
546
|
-
description: `${name} tool`,
|
|
547
|
-
input_schema: {
|
|
548
|
-
type: 'object',
|
|
549
|
-
properties: {},
|
|
550
|
-
additionalProperties: true
|
|
551
|
-
}
|
|
552
|
-
}));
|
|
553
|
-
}
|
|
554
498
|
|
|
555
499
|
function hasExpectedClaudeToolSet(tools = []) {
|
|
556
500
|
if (!Array.isArray(tools) || tools.length < DEFAULT_CLAUDE_CODE_TOOL_NAMES.length) {
|
|
@@ -582,52 +526,12 @@ function cloneJson(value) {
|
|
|
582
526
|
}
|
|
583
527
|
}
|
|
584
528
|
|
|
585
|
-
function loadClaudeRequestTemplateFromLogs() {
|
|
586
|
-
const logsPath = path.join(os.homedir(), '.cc-tool', 'claude-requests.jsonl');
|
|
587
|
-
if (!fs.existsSync(logsPath)) return null;
|
|
588
|
-
|
|
589
|
-
try {
|
|
590
|
-
const content = fs.readFileSync(logsPath, 'utf8');
|
|
591
|
-
const lines = content.trim().split('\n');
|
|
592
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
593
|
-
const line = lines[index].trim();
|
|
594
|
-
if (!line) continue;
|
|
595
|
-
try {
|
|
596
|
-
const parsed = JSON.parse(line);
|
|
597
|
-
const body = parsed?.request?.body;
|
|
598
|
-
if (!body || typeof body !== 'object') continue;
|
|
599
|
-
|
|
600
|
-
const userId = normalizeSessionKeyValue(body?.metadata?.user_id);
|
|
601
|
-
const accountId = resolveClaudeAccountIdFromUserId(userId);
|
|
602
|
-
if (!CLAUDE_USER_ID_FULL_RE.test(userId) || !accountId || accountId === '0'.repeat(64)) continue;
|
|
603
|
-
|
|
604
|
-
const tools = Array.isArray(body.tools) ? body.tools : [];
|
|
605
|
-
const system = Array.isArray(body.system) ? body.system : [];
|
|
606
|
-
if (!hasExpectedClaudeToolSet(tools)) continue;
|
|
607
|
-
if (extractClaudeSystemCharCount(system) < CLAUDE_TEMPLATE_SYSTEM_MIN_CHARS) continue;
|
|
608
|
-
|
|
609
|
-
return {
|
|
610
|
-
userId,
|
|
611
|
-
tools: cloneJson(tools),
|
|
612
|
-
system: cloneJson(system)
|
|
613
|
-
};
|
|
614
|
-
} catch {
|
|
615
|
-
// ignore malformed line
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
} catch {
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return null;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
529
|
function resolveClaudeRequestTemplate() {
|
|
626
530
|
const now = Date.now();
|
|
627
531
|
if (cachedClaudeRequestTemplate && now - cachedClaudeRequestTemplateAt < CLAUDE_TEMPLATE_CACHE_TTL_MS) {
|
|
628
532
|
return cachedClaudeRequestTemplate;
|
|
629
533
|
}
|
|
630
|
-
cachedClaudeRequestTemplate =
|
|
534
|
+
cachedClaudeRequestTemplate = loadClaudeRequestTemplate();
|
|
631
535
|
cachedClaudeRequestTemplateAt = now;
|
|
632
536
|
return cachedClaudeRequestTemplate;
|
|
633
537
|
}
|
|
@@ -1060,14 +964,10 @@ function convertOpenCodePayloadToClaude(pathname, payload = {}, fallbackModel =
|
|
|
1060
964
|
|
|
1061
965
|
const template = resolveClaudeRequestTemplate();
|
|
1062
966
|
|
|
1063
|
-
converted.system = buildClaudeSystemBlocks(normalized.system, template
|
|
967
|
+
converted.system = buildClaudeSystemBlocks(normalized.system, template.system);
|
|
1064
968
|
|
|
1065
969
|
const tools = normalizeOpenAiToolsToClaude(payload.tools || []);
|
|
1066
|
-
|
|
1067
|
-
converted.tools = tools;
|
|
1068
|
-
} else {
|
|
1069
|
-
converted.tools = template?.tools || buildDefaultClaudeCodeTools();
|
|
1070
|
-
}
|
|
970
|
+
converted.tools = tools.length > 0 ? tools : template.tools;
|
|
1071
971
|
|
|
1072
972
|
const toolChoice = normalizeToolChoiceToClaude(payload.tool_choice);
|
|
1073
973
|
if (toolChoice) {
|
|
@@ -1083,7 +983,7 @@ function convertOpenCodePayloadToClaude(pathname, payload = {}, fallbackModel =
|
|
|
1083
983
|
}
|
|
1084
984
|
|
|
1085
985
|
// 某些 Claude relay 会校验 metadata.user_id 以识别 Claude Code 请求
|
|
1086
|
-
converted.metadata = normalizeClaudeMetadata(payload.metadata, options.sessionUserId || template
|
|
986
|
+
converted.metadata = normalizeClaudeMetadata(payload.metadata, options.sessionUserId || template.userId || '');
|
|
1087
987
|
|
|
1088
988
|
return converted;
|
|
1089
989
|
}
|
|
@@ -17,7 +17,7 @@ const { saveProxyStartTime, clearProxyStartTime, getProxyStartTime, getProxyRunt
|
|
|
17
17
|
const { createDecodedStream } = require('./services/response-decoder');
|
|
18
18
|
const eventBus = require('../plugins/event-bus');
|
|
19
19
|
const { getEffectiveApiKey } = require('./services/channels');
|
|
20
|
-
const { persistProxyRequestSnapshot } = require('./services/request-logger');
|
|
20
|
+
const { persistProxyRequestSnapshot, persistClaudeRequestTemplate } = require('./services/request-logger');
|
|
21
21
|
|
|
22
22
|
let proxyServer = null;
|
|
23
23
|
let proxyApp = null;
|
|
@@ -328,15 +328,6 @@ async function startProxyServer(options = {}) {
|
|
|
328
328
|
second: '2-digit'
|
|
329
329
|
});
|
|
330
330
|
const requestSnapshot = serializeFullClaudeRequest(req);
|
|
331
|
-
broadcastLog({
|
|
332
|
-
type: 'action',
|
|
333
|
-
action: 'claude_request_received',
|
|
334
|
-
message: '收到 Claude Code 请求',
|
|
335
|
-
time,
|
|
336
|
-
channel: channel.name,
|
|
337
|
-
source: 'claude',
|
|
338
|
-
requestSummary: buildClaudeRequestSummary(req, sessionId)
|
|
339
|
-
});
|
|
340
331
|
persistClaudeRequestSnapshot({
|
|
341
332
|
timestamp: Date.now(),
|
|
342
333
|
source: 'claude',
|
|
@@ -344,6 +335,7 @@ async function startProxyServer(options = {}) {
|
|
|
344
335
|
sessionId: sessionId || null,
|
|
345
336
|
request: requestSnapshot
|
|
346
337
|
});
|
|
338
|
+
persistClaudeRequestTemplate(req.body);
|
|
347
339
|
|
|
348
340
|
// 应用模型重定向(当 proxy 开启时)
|
|
349
341
|
if (req.body && req.body.model) {
|
|
@@ -278,8 +278,9 @@ function updateChannel(id, updates) {
|
|
|
278
278
|
const proxyStatus = getProxyStatus();
|
|
279
279
|
const isProxyRunning = proxyStatus.running;
|
|
280
280
|
|
|
281
|
-
// Single-channel enforcement
|
|
282
|
-
|
|
281
|
+
// Single-channel enforcement: enabling a channel disables all others
|
|
282
|
+
// (applies regardless of proxy state — user intent is to switch to this channel)
|
|
283
|
+
if (nextChannel.enabled && !oldChannel.enabled) {
|
|
283
284
|
data.channels.forEach((ch, i) => {
|
|
284
285
|
if (i !== index && ch.enabled) {
|
|
285
286
|
ch.enabled = false;
|
|
@@ -298,9 +299,10 @@ function updateChannel(id, updates) {
|
|
|
298
299
|
|
|
299
300
|
saveChannels(data);
|
|
300
301
|
|
|
301
|
-
// Sync settings.json
|
|
302
|
-
|
|
303
|
-
|
|
302
|
+
// Sync settings.json whenever a channel becomes enabled (proxy OFF: immediate switch;
|
|
303
|
+
// proxy ON: pre-configures for when proxy stops)
|
|
304
|
+
if (nextChannel.enabled) {
|
|
305
|
+
console.log(`[Settings-sync] Channel "${nextChannel.name}" enabled, syncing settings.json...`);
|
|
304
306
|
updateClaudeSettingsWithModelConfig(nextChannel);
|
|
305
307
|
}
|
|
306
308
|
|
|
@@ -252,8 +252,9 @@ function updateChannel(channelId, updates) {
|
|
|
252
252
|
const proxyStatus = getCodexProxyStatus();
|
|
253
253
|
const isProxyRunning = proxyStatus.running;
|
|
254
254
|
|
|
255
|
-
// Single-channel enforcement
|
|
256
|
-
|
|
255
|
+
// Single-channel enforcement: enabling a channel disables all others
|
|
256
|
+
// (applies regardless of proxy state — user intent is to switch to this channel)
|
|
257
|
+
if (newChannel.enabled && !oldChannel.enabled) {
|
|
257
258
|
data.channels.forEach((ch, i) => {
|
|
258
259
|
if (i !== index && ch.enabled) {
|
|
259
260
|
ch.enabled = false;
|
|
@@ -272,9 +273,10 @@ function updateChannel(channelId, updates) {
|
|
|
272
273
|
|
|
273
274
|
saveChannels(data);
|
|
274
275
|
|
|
275
|
-
// Sync config.toml
|
|
276
|
-
|
|
277
|
-
|
|
276
|
+
// Sync config.toml whenever a channel becomes enabled (proxy OFF: immediate switch;
|
|
277
|
+
// proxy ON: pre-configures for when proxy stops)
|
|
278
|
+
if (newChannel.enabled) {
|
|
279
|
+
console.log(`[Codex Settings-sync] Channel "${newChannel.name}" enabled, syncing config.toml...`);
|
|
278
280
|
applyChannelToSettings(channelId);
|
|
279
281
|
}
|
|
280
282
|
|
|
@@ -336,6 +336,9 @@ function restoreSettings() {
|
|
|
336
336
|
// 清理 shell 配置文件中的环境变量(可选,不影响恢复结果)
|
|
337
337
|
removeEnvFromShell('CC_PROXY_KEY');
|
|
338
338
|
|
|
339
|
+
// 同步删除当前进程的环境变量,使恢复立即生效(无需新开终端)
|
|
340
|
+
delete process.env.CC_PROXY_KEY;
|
|
341
|
+
|
|
339
342
|
console.log('Codex settings restored from backup');
|
|
340
343
|
return { success: true };
|
|
341
344
|
} catch (err) {
|
|
@@ -419,6 +422,9 @@ function injectEnvToShell(envName, envValue) {
|
|
|
419
422
|
writeFileAtomic(configPath, nextContent);
|
|
420
423
|
}
|
|
421
424
|
|
|
425
|
+
// 同步更新当前进程的环境变量,使变更立即生效(无需新开终端)
|
|
426
|
+
process.env[normalizedEnvName] = String(envValue ?? '');
|
|
427
|
+
|
|
422
428
|
return { success: true, path: configPath, isFirstTime: !existed };
|
|
423
429
|
} catch (err) {
|
|
424
430
|
// 不抛出错误,只是警告,因为这不是致命问题
|
|
@@ -484,6 +490,10 @@ function removeEnvFromShell(envName) {
|
|
|
484
490
|
const normalized = compactBlankLines(cleanedLines);
|
|
485
491
|
const nextContent = normalized.length > 0 ? `${normalized.join('\n')}\n` : '';
|
|
486
492
|
writeFileAtomic(configPath, nextContent);
|
|
493
|
+
|
|
494
|
+
// 同步删除当前进程的环境变量,使变更立即生效(无需新开终端)
|
|
495
|
+
delete process.env[normalizedEnvName];
|
|
496
|
+
|
|
487
497
|
return { success: true };
|
|
488
498
|
} catch (err) {
|
|
489
499
|
console.warn(`[Codex] Failed to remove env from shell config: ${err.message}`);
|
|
@@ -527,6 +537,9 @@ function setProxyConfig(proxyPort) {
|
|
|
527
537
|
// 注入环境变量到 shell 配置文件(解决某些系统环境变量优先级问题)
|
|
528
538
|
const shellInjectResult = injectEnvToShell('CC_PROXY_KEY', 'PROXY_KEY');
|
|
529
539
|
|
|
540
|
+
// 同步更新当前进程的环境变量,使代理立即生效(无需新开终端)
|
|
541
|
+
process.env.CC_PROXY_KEY = 'PROXY_KEY';
|
|
542
|
+
|
|
530
543
|
// 获取 shell 配置文件路径用于提示信息
|
|
531
544
|
const shellConfigPath = shellInjectResult.path || getShellConfigPath();
|
|
532
545
|
const sourceCommand = getShellReloadCommand(shellConfigPath);
|
|
@@ -14,6 +14,7 @@ const { SkillService } = require('./skill-service');
|
|
|
14
14
|
const { PluginsService } = require('./plugins-service');
|
|
15
15
|
const { convertCommandToCodex } = require('./format-converter');
|
|
16
16
|
const mcpService = require('./mcp-service');
|
|
17
|
+
const promptsService = require('./prompts-service');
|
|
17
18
|
const pluginsService = new PluginsService();
|
|
18
19
|
|
|
19
20
|
// 配置模板文件路径
|
|
@@ -368,13 +369,10 @@ function readCurrentConfig(targetDir) {
|
|
|
368
369
|
* 返回用户级的 agents, commands, plugins + MCP 服务器列表
|
|
369
370
|
*/
|
|
370
371
|
function getAvailableConfigs() {
|
|
371
|
-
const agentServices = [
|
|
372
|
-
const commandServices = [
|
|
373
|
-
const skillServices = ['claude', 'codex', 'gemini', 'opencode'].map(platform => new SkillService(platform));
|
|
374
|
-
|
|
372
|
+
const agentServices = [new AgentsService('claude')];
|
|
373
|
+
const commandServices = [new CommandsService('claude')];
|
|
375
374
|
const agentMap = new Map();
|
|
376
375
|
const commandMap = new Map();
|
|
377
|
-
const skillMap = new Map();
|
|
378
376
|
|
|
379
377
|
for (const service of agentServices) {
|
|
380
378
|
const { agents } = service.listAgents();
|
|
@@ -398,14 +396,18 @@ function getAvailableConfigs() {
|
|
|
398
396
|
}
|
|
399
397
|
}
|
|
400
398
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
399
|
+
// 按平台分别获取 skills(每个平台有独立的安装目录)
|
|
400
|
+
const skillsByPlatform = {};
|
|
401
|
+
for (const platform of ['claude', 'codex', 'gemini', 'opencode']) {
|
|
402
|
+
const service = new SkillService(platform);
|
|
403
|
+
skillsByPlatform[platform] = service.getInstalledSkills().map(skill => ({
|
|
404
|
+
directory: skill.directory,
|
|
405
|
+
name: skill.name || skill.directory,
|
|
406
|
+
description: skill.description || '',
|
|
407
|
+
repoOwner: skill.repoOwner || null,
|
|
408
|
+
repoName: skill.repoName || null,
|
|
409
|
+
repoBranch: skill.repoBranch || null
|
|
410
|
+
}));
|
|
409
411
|
}
|
|
410
412
|
|
|
411
413
|
// 获取已安装的插件和市场插件
|
|
@@ -426,15 +428,18 @@ function getAvailableConfigs() {
|
|
|
426
428
|
description: p.description
|
|
427
429
|
}));
|
|
428
430
|
|
|
431
|
+
// 获取 Prompts 预设(用于 CLAUDE.md 内容选择)
|
|
432
|
+
const { presets: promptPresets } = promptsService.getAllPresets();
|
|
433
|
+
const promptsList = Object.values(promptPresets).map(p => ({
|
|
434
|
+
id: p.id,
|
|
435
|
+
name: p.name,
|
|
436
|
+
description: p.description || '',
|
|
437
|
+
content: p.content || '',
|
|
438
|
+
isBuiltin: p.isBuiltin || false
|
|
439
|
+
}));
|
|
440
|
+
|
|
429
441
|
return {
|
|
430
|
-
|
|
431
|
-
directory: skill.directory,
|
|
432
|
-
name: skill.name || skill.directory,
|
|
433
|
-
description: skill.description || '',
|
|
434
|
-
repoOwner: skill.repoOwner || null,
|
|
435
|
-
repoName: skill.repoName || null,
|
|
436
|
-
repoBranch: skill.repoBranch || null
|
|
437
|
-
})),
|
|
442
|
+
skillsByPlatform,
|
|
438
443
|
agents: Array.from(agentMap.values()).map(a => ({
|
|
439
444
|
fileName: a.fileName,
|
|
440
445
|
name: a.name,
|
|
@@ -462,7 +467,8 @@ function getAvailableConfigs() {
|
|
|
462
467
|
repoUrl: p.repoUrl || null
|
|
463
468
|
})),
|
|
464
469
|
mcpServers: mcpServerList,
|
|
465
|
-
mcpPresets
|
|
470
|
+
mcpPresets,
|
|
471
|
+
prompts: promptsList
|
|
466
472
|
};
|
|
467
473
|
}
|
|
468
474
|
|
|
@@ -237,8 +237,9 @@ function updateChannel(channelId, updates) {
|
|
|
237
237
|
const proxyStatus = getGeminiProxyStatus();
|
|
238
238
|
const isProxyRunning = proxyStatus.running;
|
|
239
239
|
|
|
240
|
-
// Single-channel enforcement
|
|
241
|
-
|
|
240
|
+
// Single-channel enforcement: enabling a channel disables all others
|
|
241
|
+
// (applies regardless of proxy state — user intent is to switch to this channel)
|
|
242
|
+
if (nextChannel.enabled && !oldChannel.enabled) {
|
|
242
243
|
data.channels.forEach((ch, i) => {
|
|
243
244
|
if (i !== index && ch.enabled) {
|
|
244
245
|
ch.enabled = false;
|
|
@@ -257,9 +258,10 @@ function updateChannel(channelId, updates) {
|
|
|
257
258
|
|
|
258
259
|
saveChannels(data);
|
|
259
260
|
|
|
260
|
-
// Sync .env
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
// Sync .env whenever a channel becomes enabled (proxy OFF: immediate switch;
|
|
262
|
+
// proxy ON: pre-configures for when proxy stops)
|
|
263
|
+
if (nextChannel.enabled) {
|
|
264
|
+
console.log(`[Gemini Settings-sync] Channel "${nextChannel.name}" enabled, syncing .env...`);
|
|
263
265
|
applyChannelToSettings(channelId, data.channels);
|
|
264
266
|
} else {
|
|
265
267
|
// 更新 Gemini 配置文件 (full rewrite for non-active-channel changes)
|
|
@@ -1620,7 +1620,7 @@ function updateServerOrder(serverIds) {
|
|
|
1620
1620
|
|
|
1621
1621
|
/**
|
|
1622
1622
|
* 导出所有 MCP 配置
|
|
1623
|
-
* @param {string} format - 导出格式: 'json' | 'claude' | 'codex' | 'opencode'
|
|
1623
|
+
* @param {string} format - 导出格式: 'json' | 'claude' | 'codex' | 'opencode' | 'gemini'
|
|
1624
1624
|
*/
|
|
1625
1625
|
function exportServers(format = 'json') {
|
|
1626
1626
|
const servers = getAllServers();
|
|
@@ -1632,6 +1632,8 @@ function exportServers(format = 'json') {
|
|
|
1632
1632
|
return exportForCodex(servers);
|
|
1633
1633
|
case 'opencode':
|
|
1634
1634
|
return exportForOpenCode(servers);
|
|
1635
|
+
case 'gemini':
|
|
1636
|
+
return exportForGemini(servers);
|
|
1635
1637
|
case 'json':
|
|
1636
1638
|
default:
|
|
1637
1639
|
return exportAsJson(servers);
|
|
@@ -1712,6 +1714,25 @@ function exportForOpenCode(servers) {
|
|
|
1712
1714
|
};
|
|
1713
1715
|
}
|
|
1714
1716
|
|
|
1717
|
+
/**
|
|
1718
|
+
* 导出为 Gemini 格式
|
|
1719
|
+
*/
|
|
1720
|
+
function exportForGemini(servers) {
|
|
1721
|
+
const mcpServers = {};
|
|
1722
|
+
|
|
1723
|
+
for (const [id, server] of Object.entries(servers)) {
|
|
1724
|
+
if (server.apps?.gemini) {
|
|
1725
|
+
mcpServers[id] = extractServerSpec(server.server);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
return {
|
|
1730
|
+
format: 'gemini',
|
|
1731
|
+
content: JSON.stringify({ mcpServers }, null, 2),
|
|
1732
|
+
filename: 'gemini-mcp-config.json'
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1715
1736
|
module.exports = {
|
|
1716
1737
|
getAllServers,
|
|
1717
1738
|
getServer,
|