coding-tool-x 3.4.5 → 3.4.6

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.
@@ -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-BDsmoSfO.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-B4Wl3JfR.js"></script>
9
9
  <link rel="modulepreload" crossorigin href="/assets/markdown-DyTJGI4N.js">
10
10
  <link rel="modulepreload" crossorigin href="/assets/vue-vendor-3bf-fPGP.js">
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendors-CKPV1OAU.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/naive-ui-Bdxp09n2.js">
13
13
  <link rel="modulepreload" crossorigin href="/assets/icons-B5Pl4lrD.js">
14
14
  <link rel="stylesheet" crossorigin href="/assets/markdown-BfC0goYb.css">
15
- <link rel="stylesheet" crossorigin href="/assets/index-C1pzEgmj.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-Bgt_oqoE.css">
16
16
  </head>
17
17
  <body>
18
18
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.4.5",
3
+ "version": "3.4.6",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -25,11 +25,26 @@ const { deleteBackup } = require('../services/codex-settings-manager');
25
25
  const { PATHS } = require('../../config/paths');
26
26
  const { getDefaultSpeedTestModelByToolType } = require('../../config/model-metadata');
27
27
  const CODEX_GATEWAY_SOURCE_TYPE = 'codex';
28
+ const CODEX_PROVIDER_KEY_PATTERN = /^[a-z0-9_-]+$/i;
28
29
 
29
30
  function getDefaultCodexModel() {
30
31
  return getDefaultSpeedTestModelByToolType('codex');
31
32
  }
32
33
 
34
+ function validateCodexProviderKey(value) {
35
+ const normalized = String(value || '').trim();
36
+ if (!normalized) {
37
+ return 'Missing required fields: providerKey';
38
+ }
39
+ if (!CODEX_PROVIDER_KEY_PATTERN.test(normalized)) {
40
+ return 'Invalid providerKey: only letters, numbers, underscores, and hyphens are allowed';
41
+ }
42
+ if (normalized.toLowerCase() === 'openai') {
43
+ return 'Invalid providerKey: "openai" is reserved for the built-in OpenAI provider';
44
+ }
45
+ return '';
46
+ }
47
+
33
48
  module.exports = (config) => {
34
49
  /**
35
50
  * GET /api/codex/channels
@@ -137,6 +152,11 @@ module.exports = (config) => {
137
152
  return res.status(400).json({ error: 'Missing required fields: apiKey' });
138
153
  }
139
154
 
155
+ const providerKeyError = validateCodexProviderKey(providerKey);
156
+ if (providerKeyError) {
157
+ return res.status(400).json({ error: providerKeyError });
158
+ }
159
+
140
160
  // wireApi 固定为 'responses' (OpenAI Responses API 格式)
141
161
  const channel = createChannel(name, providerKey, baseUrl, apiKey, 'responses', {
142
162
  websiteUrl,
@@ -168,6 +188,12 @@ module.exports = (config) => {
168
188
 
169
189
  const { channelId } = req.params;
170
190
  const updates = req.body;
191
+ if (Object.prototype.hasOwnProperty.call(updates, 'providerKey')) {
192
+ const providerKeyError = validateCodexProviderKey(updates.providerKey);
193
+ if (providerKeyError) {
194
+ return res.status(400).json({ error: providerKeyError });
195
+ }
196
+ }
171
197
 
172
198
  const channel = updateChannel(channelId, updates);
173
199
  // 清除该渠道的模型重定向日志缓存,使下次请求时重新打印
@@ -9,6 +9,7 @@ const {
9
9
  setDefaultCredential,
10
10
  deleteCredential,
11
11
  applyStoredCredential,
12
+ disableStoredCredential,
12
13
  clearNativeOAuthState,
13
14
  fetchCredentialUsage
14
15
  } = require('../services/oauth-credentials-service');
@@ -117,10 +118,31 @@ router.post('/:tool/:credentialId/apply', async (req, res) => {
117
118
  assertTool(tool);
118
119
  const result = await applyStoredCredential(tool, credentialId);
119
120
  broadcastToolProxyState(tool);
121
+ const message = tool === 'opencode'
122
+ ? 'opencode 已应用 OAuth 凭证,并保留现有 API providers'
123
+ : `${tool} 已切换到 OAuth 凭证控制`;
120
124
  res.json({
121
125
  tool,
122
126
  ...result,
123
- message: `${tool} 已切换到 OAuth 凭证控制`
127
+ message
128
+ });
129
+ } catch (error) {
130
+ res.status(error.statusCode || 500).json({ error: error.message });
131
+ }
132
+ });
133
+
134
+ router.post('/:tool/:credentialId/disable-native', (req, res) => {
135
+ try {
136
+ const { tool, credentialId } = req.params;
137
+ assertTool(tool);
138
+ const result = disableStoredCredential(tool, credentialId);
139
+ broadcastToolProxyState(tool);
140
+ res.json({
141
+ tool,
142
+ ...result,
143
+ message: tool === 'opencode'
144
+ ? 'opencode OAuth provider 已关闭'
145
+ : `${tool} 本机 OAuth 已关闭`
124
146
  });
125
147
  } catch (error) {
126
148
  res.status(error.statusCode || 500).json({ error: error.message });
@@ -16,7 +16,6 @@ const {
16
16
  getCurrentProxyPort
17
17
  } = require('../services/opencode-settings-manager');
18
18
  const { getChannels, getEnabledChannels, applyChannelToSettings } = require('../services/opencode-channels');
19
- const { clearNativeOAuth } = require('../services/native-oauth-adapters');
20
19
  const { getSchedulerState } = require('../services/channel-scheduler');
21
20
  const { PATHS, ensureStorageDirMigrated } = require('../../config/paths');
22
21
  const fs = require('fs');
@@ -195,7 +194,6 @@ router.post('/start', async (req, res) => {
195
194
  });
196
195
 
197
196
  const activeModel = currentChannel.model || currentChannel.speedTestModel || null;
198
- clearNativeOAuth('opencode');
199
197
  setProxyConfig(proxyResult.port, { channels: channelPayloads, model: activeModel });
200
198
 
201
199
  // 5. 广播状态更新
@@ -219,7 +219,7 @@ function inspectClaudeState() {
219
219
 
220
220
  return {
221
221
  tool: 'claude',
222
- mode: proxyStatus.running ? 'proxy' : (nativeOAuth ? 'oauth' : (channelConfigured ? 'channel' : 'idle')),
222
+ mode: proxyStatus.running ? 'proxy' : (channelConfigured ? 'channel' : (nativeOAuth ? 'oauth' : 'idle')),
223
223
  proxyRunning: proxyStatus.running,
224
224
  oauthPresent: Boolean(nativeOAuth),
225
225
  channelConfigured,
@@ -395,7 +395,7 @@ function inspectCodexState() {
395
395
 
396
396
  return {
397
397
  tool: 'codex',
398
- mode: proxyStatus.running ? 'proxy' : (nativeOAuth ? 'oauth' : (channelConfigured ? 'channel' : 'idle')),
398
+ mode: proxyStatus.running ? 'proxy' : (channelConfigured ? 'channel' : (nativeOAuth ? 'oauth' : 'idle')),
399
399
  proxyRunning: proxyStatus.running,
400
400
  oauthPresent: Boolean(nativeOAuth),
401
401
  channelConfigured,
@@ -619,7 +619,7 @@ function inspectGeminiState() {
619
619
 
620
620
  return {
621
621
  tool: 'gemini',
622
- mode: proxyStatus.running ? 'proxy' : (nativeOAuth ? 'oauth' : (channelConfigured ? 'channel' : 'idle')),
622
+ mode: proxyStatus.running ? 'proxy' : (channelConfigured ? 'channel' : (nativeOAuth ? 'oauth' : 'idle')),
623
623
  proxyRunning: proxyStatus.running,
624
624
  oauthPresent: Boolean(nativeOAuth),
625
625
  channelConfigured,
@@ -737,10 +737,67 @@ function clearOpenCodeOAuth() {
737
737
  writeJsonFile(NATIVE_PATHS.opencode.auth, payload);
738
738
  }
739
739
 
740
- function applyOpenCodeOAuth(credential) {
741
- clearOpenCodeOAuth();
742
- opencodeSettingsManager.clearManagedChannelConfig();
740
+ function disableOpenCodeOAuthCredential(credential = {}) {
741
+ const providerId = String(credential.providerId || '').trim();
742
+ const accessToken = String(credential.accessToken || credential.primaryToken || '').trim();
743
+ const payload = readJsonFile(NATIVE_PATHS.opencode.auth, {});
744
+ if (!payload || typeof payload !== 'object') {
745
+ return;
746
+ }
747
+
748
+ Object.keys(payload).forEach((key) => {
749
+ const target = payload[key];
750
+ if (!target || target.type !== 'oauth') {
751
+ return;
752
+ }
753
+
754
+ const providerMatched = providerId && key === providerId;
755
+ const tokenMatched = accessToken && String(target.access || '').trim() === accessToken;
756
+ if (providerMatched || tokenMatched) {
757
+ delete payload[key];
758
+ }
759
+ });
760
+
761
+ if (Object.keys(payload).length === 0) {
762
+ removeFileIfExists(NATIVE_PATHS.opencode.auth);
763
+ return;
764
+ }
765
+
766
+ writeJsonFile(NATIVE_PATHS.opencode.auth, payload);
767
+ }
768
+
769
+ function isManagedOpenCodeProvider(provider) {
770
+ if (!provider || typeof provider !== 'object') {
771
+ return false;
772
+ }
773
+
774
+ if (provider.__ctx_managed__ === true) {
775
+ return true;
776
+ }
777
+
778
+ const apiKey = String(provider?.options?.apiKey || '').trim();
779
+ const baseUrl = String(provider?.options?.baseURL || '').trim();
780
+ return apiKey === 'PROXY_KEY' && (baseUrl.includes('127.0.0.1') || baseUrl.includes('localhost'));
781
+ }
782
+
783
+ function clearOpenCodeManagedModelSelection(config) {
784
+ const modelRef = String(config?.model || '').trim();
785
+ if (!modelRef || !modelRef.includes('/')) {
786
+ return;
787
+ }
788
+
789
+ const providerId = modelRef.split('/')[0].trim();
790
+ if (!providerId) {
791
+ return;
792
+ }
793
+
794
+ const provider = config?.provider?.[providerId];
795
+ if (isManagedOpenCodeProvider(provider)) {
796
+ delete config.model;
797
+ }
798
+ }
743
799
 
800
+ function applyOpenCodeOAuth(credential) {
744
801
  const providerId = String(credential.providerId || 'openai').trim() || 'openai';
745
802
  const payload = readJsonFile(NATIVE_PATHS.opencode.auth, {});
746
803
  payload[providerId] = {
@@ -758,9 +815,12 @@ function applyOpenCodeOAuth(credential) {
758
815
  ? opencodeSettingsManager.readConfig(configPath)
759
816
  : {};
760
817
  config.provider = config.provider && typeof config.provider === 'object' ? config.provider : {};
761
- if (!config.provider[providerId]) {
762
- config.provider[providerId] = {};
763
- }
818
+ config.provider[providerId] = config.provider[providerId] && typeof config.provider[providerId] === 'object'
819
+ ? config.provider[providerId]
820
+ : {};
821
+ // Preserve existing ctx-managed API providers for OpenCode coexistence, but
822
+ // drop the active managed selection so OAuth-backed providers become available.
823
+ clearOpenCodeManagedModelSelection(config);
764
824
  opencodeSettingsManager.writeConfig(configPath, config);
765
825
 
766
826
  return { storage: 'auth-file' };
@@ -787,7 +847,11 @@ function inspectOpenCodeState() {
787
847
 
788
848
  return {
789
849
  tool: 'opencode',
790
- mode: proxyStatus.running ? 'proxy' : (nativeOAuth ? 'oauth' : (channelConfigured ? 'channel' : 'idle')),
850
+ mode: proxyStatus.running
851
+ ? 'proxy'
852
+ : (nativeOAuth && channelConfigured
853
+ ? 'mixed'
854
+ : (nativeOAuth ? 'oauth' : (channelConfigured ? 'channel' : 'idle'))),
791
855
  proxyRunning: proxyStatus.running,
792
856
  oauthPresent: Boolean(nativeOAuth),
793
857
  channelConfigured,
@@ -865,6 +929,25 @@ function clearNativeOAuth(tool) {
865
929
  }
866
930
  }
867
931
 
932
+ function disableNativeOAuthCredential(tool, credential = {}) {
933
+ switch (tool) {
934
+ case 'claude':
935
+ clearClaudeOAuth();
936
+ return;
937
+ case 'codex':
938
+ clearCodexOAuth();
939
+ return;
940
+ case 'gemini':
941
+ clearGeminiOAuth();
942
+ return;
943
+ case 'opencode':
944
+ disableOpenCodeOAuthCredential(credential);
945
+ return;
946
+ default:
947
+ throw new Error(`Unsupported OAuth tool: ${tool}`);
948
+ }
949
+ }
950
+
868
951
  function applyOAuthCredential(tool, credential) {
869
952
  switch (tool) {
870
953
  case 'claude':
@@ -887,5 +970,6 @@ module.exports = {
887
970
  readNativeOAuth,
888
971
  readAllNativeOAuth,
889
972
  clearNativeOAuth,
973
+ disableNativeOAuthCredential,
890
974
  applyOAuthCredential
891
975
  };
@@ -12,6 +12,7 @@ const {
12
12
  inspectTool,
13
13
  readAllNativeOAuth,
14
14
  clearNativeOAuth,
15
+ disableNativeOAuthCredential,
15
16
  applyOAuthCredential
16
17
  } = require('./native-oauth-adapters');
17
18
  const { maskToken, decodeJwtPayload, removeFileIfExists } = require('./oauth-utils');
@@ -300,6 +301,23 @@ function sanitizeCredential(entry, defaultCredentialId) {
300
301
  };
301
302
  }
302
303
 
304
+ function sanitizeNativeCredential(entry = {}) {
305
+ const primaryToken = entry.primaryToken
306
+ || entry.accessToken
307
+ || entry.token
308
+ || '';
309
+
310
+ return {
311
+ providerId: entry.providerId || '',
312
+ accountId: entry.accountId || '',
313
+ accountEmail: entry.accountEmail || '',
314
+ expiresAt: entry.expiresAt || null,
315
+ lastRefresh: entry.lastRefresh || null,
316
+ storage: entry.storage || '',
317
+ tokenPreview: maskToken(primaryToken)
318
+ };
319
+ }
320
+
303
321
  function sanitizeToolSummary(tool, toolStore) {
304
322
  const credentials = (toolStore.credentials || [])
305
323
  .map((entry) => sanitizeCredential(entry, toolStore.defaultCredentialId))
@@ -309,11 +327,16 @@ function sanitizeToolSummary(tool, toolStore) {
309
327
  if (aTime !== bTime) return bTime - aTime;
310
328
  return (b.createdAt || 0) - (a.createdAt || 0);
311
329
  });
330
+ const nativeState = inspectTool(tool);
331
+ const nativeCredentials = readAllNativeOAuth(tool).map((entry) => sanitizeNativeCredential(entry));
312
332
  return {
313
333
  tool,
314
334
  defaultCredentialId: toolStore.defaultCredentialId || null,
315
335
  credentials,
316
- nativeState: inspectTool(tool)
336
+ nativeState: {
337
+ ...nativeState,
338
+ nativeCredentials
339
+ }
317
340
  };
318
341
  }
319
342
 
@@ -612,7 +635,9 @@ async function applyStoredCredential(tool, credentialId) {
612
635
  const entry = findStoredCredential(tool, credentialId);
613
636
  const proxyStopped = await stopProxyIfRunning(tool);
614
637
  cleanupManagedArtifacts(tool);
615
- disableAllChannelsForTool(tool);
638
+ if (tool !== 'opencode') {
639
+ disableAllChannelsForTool(tool);
640
+ }
616
641
  applyOAuthCredential(tool, entry.secrets);
617
642
 
618
643
  // 记录最近使用时间
@@ -631,6 +656,22 @@ async function applyStoredCredential(tool, credentialId) {
631
656
  };
632
657
  }
633
658
 
659
+ function disableStoredCredential(tool, credentialId) {
660
+ assertSupportedTool(tool);
661
+ const entry = findStoredCredential(tool, credentialId);
662
+ disableNativeOAuthCredential(tool, {
663
+ ...(entry.secrets || {}),
664
+ providerId: entry.providerId || entry.secrets?.providerId || '',
665
+ accountId: entry.accountId || entry.secrets?.accountId || ''
666
+ });
667
+
668
+ return {
669
+ credential: sanitizeCredential(entry, readStore().tools[tool]?.defaultCredentialId || null),
670
+ toolSummary: getToolSummary(tool),
671
+ nativeState: inspectTool(tool)
672
+ };
673
+ }
674
+
634
675
  function clearNativeOAuthState(tool) {
635
676
  assertSupportedTool(tool);
636
677
  clearNativeOAuth(tool);
@@ -791,6 +832,7 @@ module.exports = {
791
832
  setDefaultCredential,
792
833
  deleteCredential,
793
834
  applyStoredCredential,
835
+ disableStoredCredential,
794
836
  clearNativeOAuthState,
795
837
  fetchCredentialUsage
796
838
  };
@@ -2,7 +2,6 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { PATHS } = require('../../config/paths');
5
- const { clearNativeOAuth } = require('./native-oauth-adapters');
6
5
  const { setChannelConfig } = require('./opencode-settings-manager');
7
6
  const { normalizeGatewaySourceType } = require('./base/proxy-utils');
8
7
 
@@ -251,7 +250,6 @@ function applyChannelToSettings(channelId) {
251
250
  });
252
251
  saveChannels(data);
253
252
 
254
- clearNativeOAuth('opencode');
255
253
  setChannelConfig(channel);
256
254
 
257
255
  return channel;