coding-tool-x 3.4.6 → 3.4.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.4.6",
3
+ "version": "3.4.8",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -1,5 +1,13 @@
1
1
  const fs = require('fs');
2
2
  const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('./config/paths');
3
+ const { isWindowsLikePlatform } = require('./utils/home-dir');
4
+
5
+ function buildEchoCommand(value) {
6
+ if (isWindowsLikePlatform(process.platform, process.env)) {
7
+ return `cmd /c echo ${value}`;
8
+ }
9
+ return `echo '${value}'`;
10
+ }
3
11
 
4
12
  // 恢复配置到默认状态
5
13
  async function resetConfig() {
@@ -56,7 +64,7 @@ async function resetConfig() {
56
64
  if (!settings.env) settings.env = {};
57
65
  settings.env.ANTHROPIC_BASE_URL = activeChannel.baseUrl;
58
66
  settings.env.ANTHROPIC_API_KEY = activeChannel.apiKey;
59
- settings.apiKeyHelper = `echo '${activeChannel.apiKey}'`;
67
+ settings.apiKeyHelper = buildEchoCommand(activeChannel.apiKey);
60
68
 
61
69
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
62
70
  console.log(`[OK] 已恢复到渠道: ${activeChannel.name}`);
@@ -26,6 +26,10 @@ const UI_CONFIG_PATH = PATHS.uiConfig;
26
26
  // 通知脚本路径(用于飞书通知)
27
27
  const NOTIFY_SCRIPT_PATH = PATHS.notifyHook;
28
28
 
29
+ function buildWindowsPopupCommand() {
30
+ return `powershell -NoProfile -Command "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('任务已完成 | 等待交互', 5, 'Coding Tool', 0x40)"`;
31
+ }
32
+
29
33
  // 读取 Claude settings.json
30
34
  function readClaudeSettings() {
31
35
  try {
@@ -84,8 +88,8 @@ function writeUIConfig(config) {
84
88
  }
85
89
 
86
90
  // 生成系统通知命令(跨平台)
87
- function generateSystemNotificationCommand(type) {
88
- if (platform === 'darwin') {
91
+ function generateSystemNotificationCommand(type, platformOverride = platform) {
92
+ if (platformOverride === 'darwin') {
89
93
  // macOS
90
94
  if (type === 'dialog') {
91
95
  return `osascript -e 'display dialog "Claude Code 任务已完成 | 等待交互" with title "Coding Tool" buttons {"好的"} default button 1 with icon note'`;
@@ -94,12 +98,12 @@ function generateSystemNotificationCommand(type) {
94
98
  // terminal-notifier 需要 brew install terminal-notifier
95
99
  return `if command -v terminal-notifier &>/dev/null; then terminal-notifier -title "Coding Tool" -message "任务已完成 | 等待交互" -sound Glass -activate com.apple.Terminal; else osascript -e 'display notification "任务已完成 | 等待交互" with title "Coding Tool" sound name "Glass"'; fi`;
96
100
  }
97
- } else if (platform === 'win32') {
101
+ } else if (platformOverride === 'win32') {
98
102
  // Windows
99
103
  if (type === 'dialog') {
100
- return `powershell -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')"`;
104
+ return `powershell -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')" || ${buildWindowsPopupCommand()}`;
101
105
  } else {
102
- return `powershell -NoProfile -Command "try { [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null; $xml = New-Object Windows.Data.Xml.Dom.XmlDocument; $xml.LoadXml('<toast><visual><binding template=\\"ToastGeneric\\"><text>Coding Tool</text><text>任务已完成 | 等待交互</text></binding></visual><audio src=\\"ms-winsoundevent:Notification.Default\\"/></toast>'); $toast = [Windows.UI.Notifications.ToastNotification]::new($xml); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) } catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('任务已完成 | 等待交互', 5, 'Coding Tool', 0x40) }"`;
106
+ return `powershell -NoProfile -Command "try { [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null; $xml = New-Object Windows.Data.Xml.Dom.XmlDocument; $xml.LoadXml('<toast><visual><binding template=\\"ToastGeneric\\"><text>Coding Tool</text><text>任务已完成 | 等待交互</text></binding></visual><audio src=\\"ms-winsoundevent:Notification.Default\\"/></toast>'); $toast = [Windows.UI.Notifications.ToastNotification]::new($xml); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) } catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('任务已完成 | 等待交互', 5, 'Coding Tool', 0x40) }" || ${buildWindowsPopupCommand()}`;
103
107
  }
104
108
  } else {
105
109
  // Linux
@@ -107,34 +107,32 @@ function findActiveChannelFromSettings() {
107
107
  }
108
108
  }
109
109
 
110
- if (!baseUrl || !apiKey || baseUrl.includes('127.0.0.1')) {
111
- console.log('[Proxy] Invalid settings: empty baseUrl/apiKey or localhost detected');
112
- return null;
113
- }
114
-
115
110
  const channels = getAllChannels();
116
111
 
117
112
  // Level 1: Exact match (baseUrl + apiKey)
118
- let matchingChannel = channels.find(ch =>
119
- ch.baseUrl === baseUrl && ch.apiKey === apiKey
120
- );
121
-
122
- if (matchingChannel) {
123
- console.log(`[Proxy] Level 1 - Exact match: ${matchingChannel.name}`);
124
- return matchingChannel;
125
- }
113
+ if (baseUrl && apiKey && !baseUrl.includes('127.0.0.1')) {
114
+ let matchingChannel = channels.find(ch =>
115
+ ch.baseUrl === baseUrl && ch.apiKey === apiKey
116
+ );
117
+ if (matchingChannel) {
118
+ console.log(`[Proxy] Level 1 - Exact match: ${matchingChannel.name}`);
119
+ return matchingChannel;
120
+ }
126
121
 
127
- // Level 2: Match by baseUrl only (when apiKey differs)
128
- matchingChannel = channels.find(ch => ch.baseUrl === baseUrl);
129
- if (matchingChannel) {
130
- console.log(`[Proxy] Level 2 - Matched by baseUrl only: ${matchingChannel.name}`);
131
- return matchingChannel;
122
+ // Level 2: Match by baseUrl only (when apiKey differs)
123
+ matchingChannel = channels.find(ch => ch.baseUrl === baseUrl);
124
+ if (matchingChannel) {
125
+ console.log(`[Proxy] Level 2 - Matched by baseUrl only: ${matchingChannel.name}`);
126
+ return matchingChannel;
127
+ }
128
+ } else {
129
+ console.log('[Proxy] settings.json has no valid baseUrl/apiKey, falling back to channel list');
132
130
  }
133
131
 
134
132
  // Level 3: Use active-channel.json for last known active channel
135
133
  const activeChannelId = loadActiveChannelId();
136
134
  if (activeChannelId) {
137
- matchingChannel = channels.find(ch => ch.id === activeChannelId);
135
+ const matchingChannel = channels.find(ch => ch.id === activeChannelId);
138
136
  if (matchingChannel) {
139
137
  console.log(`[Proxy] Level 3 - Using last active channel: ${matchingChannel.name}`);
140
138
  return matchingChannel;
@@ -142,10 +140,10 @@ function findActiveChannelFromSettings() {
142
140
  }
143
141
 
144
142
  // Level 4: Return first enabled channel as last resort
145
- matchingChannel = channels.find(ch => ch.enabled !== false);
146
- if (matchingChannel) {
147
- console.log(`[Proxy] Level 4 - Using first enabled channel: ${matchingChannel.name}`);
148
- return matchingChannel;
143
+ const fallbackChannel = channels.find(ch => ch.enabled !== false);
144
+ if (fallbackChannel) {
145
+ console.log(`[Proxy] Level 4 - Using first enabled channel: ${fallbackChannel.name}`);
146
+ return fallbackChannel;
149
147
  }
150
148
 
151
149
  console.log('[Proxy] No matching channel found after all fallback levels');
@@ -4,6 +4,7 @@ const BaseChannelService = require('./base/base-channel-service');
4
4
  const { isProxyConfig } = require('./settings-manager');
5
5
  const { PATHS, NATIVE_PATHS } = require('../../config/paths');
6
6
  const { clearNativeOAuth } = require('./native-oauth-adapters');
7
+ const { isWindowsLikePlatform } = require('../../utils/home-dir');
7
8
 
8
9
  // ── Claude 特有工具函数 ──
9
10
 
@@ -51,7 +52,10 @@ function extractApiKeyFromHelper(apiKeyHelper) {
51
52
  }
52
53
 
53
54
  function buildApiKeyHelperCommand() {
54
- return 'echo \'ctx-managed\'';
55
+ if (isWindowsLikePlatform(process.platform, process.env)) {
56
+ return 'cmd /c echo ctx-managed';
57
+ }
58
+ return "echo 'ctx-managed'";
55
59
  }
56
60
 
57
61
  // ── Claude 原生设置写入 ──
@@ -59,6 +63,7 @@ function buildApiKeyHelperCommand() {
59
63
  function updateClaudeSettingsWithModelConfig(channel) {
60
64
  clearNativeOAuth('claude');
61
65
  const settingsPath = getClaudeSettingsPath();
66
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
62
67
 
63
68
  let settings = {};
64
69
  if (fs.existsSync(settingsPath)) {
@@ -105,12 +110,17 @@ function updateClaudeSettingsWithModelConfig(channel) {
105
110
  delete settings.env.NO_PROXY;
106
111
  }
107
112
 
113
+ if (settings.env && Object.keys(settings.env).length === 0) {
114
+ delete settings.env;
115
+ }
116
+
108
117
  settings.apiKeyHelper = buildApiKeyHelperCommand();
109
118
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
110
119
  }
111
120
 
112
121
  function updateClaudeSettings(baseUrl, apiKey) {
113
122
  const settingsPath = getClaudeSettingsPath();
123
+ fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
114
124
 
115
125
  let settings = {};
116
126
  if (fs.existsSync(settingsPath)) {
@@ -133,10 +143,19 @@ function updateClaudeSettings(baseUrl, apiKey) {
133
143
  settings.env.ANTHROPIC_API_KEY = apiKey;
134
144
  }
135
145
 
146
+ if (settings.env && Object.keys(settings.env).length === 0) {
147
+ delete settings.env;
148
+ }
149
+
136
150
  settings.apiKeyHelper = buildApiKeyHelperCommand();
137
151
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
138
152
  }
139
153
 
154
+ function resolveCurrentManagedChannel(channels = []) {
155
+ const allChannels = Array.isArray(channels) ? channels : [];
156
+ return allChannels.find(ch => ch.enabled !== false) || null;
157
+ }
158
+
140
159
  // ── ClaudeChannelService ──
141
160
 
142
161
  class ClaudeChannelService extends BaseChannelService {
@@ -185,6 +204,39 @@ class ClaudeChannelService extends BaseChannelService {
185
204
  this._cacheInitialized = true;
186
205
  }
187
206
 
207
+ _onAfterCreate(channel, _allChannels) {
208
+ if (!isProxyConfig() && channel.enabled !== false) {
209
+ this._applyToNativeSettings(channel);
210
+ }
211
+ }
212
+
213
+ _onAfterUpdate(oldChannel, nextChannel, allChannels) {
214
+ if (isProxyConfig()) {
215
+ return;
216
+ }
217
+
218
+ if (oldChannel.enabled === false && nextChannel.enabled !== false) {
219
+ this._applyToNativeSettings(nextChannel);
220
+ return;
221
+ }
222
+
223
+ const activeChannel = resolveCurrentManagedChannel(allChannels);
224
+ if (nextChannel.enabled !== false && activeChannel?.id === nextChannel.id) {
225
+ this._applyToNativeSettings(nextChannel);
226
+ }
227
+ }
228
+
229
+ _onAfterDelete(_channel, allChannels) {
230
+ if (isProxyConfig()) {
231
+ return;
232
+ }
233
+
234
+ const activeChannel = resolveCurrentManagedChannel(allChannels);
235
+ if (activeChannel) {
236
+ this._applyToNativeSettings(activeChannel);
237
+ }
238
+ }
239
+
188
240
  _applyToNativeSettings(channel) {
189
241
  updateClaudeSettingsWithModelConfig(channel);
190
242
  }
@@ -237,6 +237,10 @@ function escapeForXml(value) {
237
237
  .replace(/'/g, '&apos;');
238
238
  }
239
239
 
240
+ function buildWindowsPopupCommand(title, message) {
241
+ return `powershell -NoProfile -Command "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('${escapeForPowerShellSingleQuote(message)}', 5, '${escapeForPowerShellSingleQuote(title)}', 0x40)"`;
242
+ }
243
+
240
244
  function generateNotifyScript(feishu = {}) {
241
245
  const feishuEnabled = feishu.enabled === true && !!feishu.webhookUrl;
242
246
 
@@ -336,11 +340,13 @@ function notify(mode, message) {
336
340
  }
337
341
 
338
342
  if (platform === 'win32') {
343
+ const popupCommand = buildWindowsPopupCommand(title, message)
339
344
  if (mode === 'dialog') {
340
345
  const ps = "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('" +
341
346
  escapeForPowerShellSingleQuote(message) + "', '" +
342
347
  escapeForPowerShellSingleQuote(title) + "', 'OK', 'Information')"
343
- execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
348
+ const command = 'powershell -NoProfile -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
349
+ execSync(command, { stdio: 'ignore', windowsHide: true })
344
350
  } else {
345
351
  const toastXml = '<toast><visual><binding template="ToastGeneric"><text>' +
346
352
  escapeForXml(title) + '</text><text>' + escapeForXml(message) +
@@ -354,7 +360,8 @@ function notify(mode, message) {
354
360
  "[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) " +
355
361
  "} catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('" +
356
362
  escapeForPowerShellSingleQuote(message) + "', 5, '" + escapeForPowerShellSingleQuote(title) + "', 0x40) }"
357
- execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
363
+ const command = 'powershell -NoProfile -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
364
+ execSync(command, { stdio: 'ignore', windowsHide: true })
358
365
  }
359
366
  return
360
367
  }
@@ -468,6 +475,10 @@ function escapeForXml(value) {
468
475
  .replace(/"/g, '&quot;')
469
476
  .replace(/'/g, '&apos;')
470
477
  }
478
+
479
+ function buildWindowsPopupCommand(title, message) {
480
+ return \`powershell -NoProfile -Command "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('\${escapeForPowerShellSingleQuote(message)}', 5, '\${escapeForPowerShellSingleQuote(title)}', 0x40)"\`
481
+ }
471
482
  `;
472
483
  }
473
484
 
@@ -884,10 +895,10 @@ function sendFeishuTest(webhookUrl) {
884
895
  });
885
896
  }
886
897
 
887
- function generateSystemNotificationCommand(type, message) {
898
+ function generateSystemNotificationCommand(type, message, platformOverride = os.platform()) {
888
899
  const normalizedType = normalizeType(type);
889
900
  const title = 'Coding Tool';
890
- const platform = os.platform();
901
+ const platform = platformOverride;
891
902
 
892
903
  if (platform === 'darwin') {
893
904
  if (normalizedType === 'dialog') {
@@ -897,12 +908,13 @@ function generateSystemNotificationCommand(type, message) {
897
908
  }
898
909
 
899
910
  if (platform === 'win32') {
911
+ const popupCommand = buildWindowsPopupCommand(title, message);
900
912
  if (normalizedType === 'dialog') {
901
- return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')"`;
913
+ return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')" || ${popupCommand}`;
902
914
  }
903
915
 
904
916
  const toastXml = `<toast><visual><binding template="ToastGeneric"><text>${escapeForXml(title)}</text><text>${escapeForXml(message)}</text></binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>`;
905
- return `powershell -NoProfile -Command "try { [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null; $xml = New-Object Windows.Data.Xml.Dom.XmlDocument; $xml.LoadXml('${toastXml.replace(/'/g, "''")}'); $toast = [Windows.UI.Notifications.ToastNotification]::new($xml); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) } catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('${escapeForPowerShellSingleQuote(message)}', 5, '${escapeForPowerShellSingleQuote(title)}', 0x40) }"`;
917
+ return `powershell -NoProfile -Command "try { [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; [Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null; $xml = New-Object Windows.Data.Xml.Dom.XmlDocument; $xml.LoadXml('${toastXml.replace(/'/g, "''")}'); $toast = [Windows.UI.Notifications.ToastNotification]::new($xml); [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) } catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('${escapeForPowerShellSingleQuote(message)}', 5, '${escapeForPowerShellSingleQuote(title)}', 0x40) }" || ${popupCommand}`;
906
918
  }
907
919
 
908
920
  if (normalizedType === 'dialog') {
@@ -947,6 +959,7 @@ module.exports = {
947
959
  buildClaudeCommand,
948
960
  buildOpenCodePluginContent,
949
961
  getOpenCodeManagedPluginPath,
950
- generateNotifyScript
962
+ generateNotifyScript,
963
+ generateSystemNotificationCommand
951
964
  }
952
965
  };
@@ -2,7 +2,10 @@ 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 { setChannelConfig } = require('./opencode-settings-manager');
5
+ const {
6
+ setChannelConfig,
7
+ clearManagedChannelConfig
8
+ } = require('./opencode-settings-manager');
6
9
  const { normalizeGatewaySourceType } = require('./base/proxy-utils');
7
10
 
8
11
  /**
@@ -98,6 +101,30 @@ function deriveProviderKey(channel) {
98
101
  return `opencode_${base}`;
99
102
  }
100
103
 
104
+ function getOpenCodeProxyRunning() {
105
+ const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
106
+ return Boolean(getOpenCodeProxyStatus()?.running);
107
+ }
108
+
109
+ function resolveCurrentManagedChannel(channels = []) {
110
+ const allChannels = Array.isArray(channels) ? channels : [];
111
+ return allChannels.find(channel => channel.enabled !== false) || null;
112
+ }
113
+
114
+ function syncManagedChannelConfig(channels = [], preferredChannel = null) {
115
+ const targetChannel = preferredChannel && preferredChannel.enabled !== false
116
+ ? preferredChannel
117
+ : resolveCurrentManagedChannel(channels);
118
+
119
+ if (targetChannel) {
120
+ setChannelConfig(targetChannel);
121
+ return targetChannel;
122
+ }
123
+
124
+ clearManagedChannelConfig();
125
+ return null;
126
+ }
127
+
101
128
  // 保存渠道数据
102
129
  function saveChannels(data) {
103
130
  const filePath = getChannelsFilePath();
@@ -115,6 +142,7 @@ function getChannels() {
115
142
  // 添加渠道
116
143
  function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
117
144
  const data = loadChannels();
145
+ const isProxyRunning = getOpenCodeProxyRunning();
118
146
 
119
147
  const newChannel = {
120
148
  id: crypto.randomUUID(),
@@ -139,8 +167,22 @@ function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
139
167
  newChannel.providerKey = extraConfig.providerKey || deriveProviderKey(newChannel);
140
168
 
141
169
  data.channels.push(newChannel);
170
+
171
+ if (!isProxyRunning && newChannel.enabled !== false) {
172
+ data.channels.forEach((channel, index) => {
173
+ if (index !== data.channels.length - 1 && channel.enabled) {
174
+ channel.enabled = false;
175
+ }
176
+ });
177
+ console.log(`[OpenCode Single-channel mode] Enabled "${newChannel.name}", disabled all others`);
178
+ }
179
+
142
180
  saveChannels(data);
143
181
 
182
+ if (!isProxyRunning && newChannel.enabled !== false) {
183
+ syncManagedChannelConfig(data.channels, newChannel);
184
+ }
185
+
144
186
  return newChannel;
145
187
  }
146
188
 
@@ -173,10 +215,7 @@ function updateChannel(channelId, updates) {
173
215
  merged.providerKey = updates.providerKey || deriveProviderKey(merged);
174
216
  data.channels[index] = merged;
175
217
 
176
- // Get proxy status
177
- const { getOpenCodeProxyStatus } = require('../opencode-proxy-server');
178
- const proxyStatus = getOpenCodeProxyStatus();
179
- const isProxyRunning = proxyStatus.running;
218
+ const isProxyRunning = getOpenCodeProxyRunning();
180
219
 
181
220
  // Single-channel enforcement when proxy is OFF: enabling a channel disables all others
182
221
  if (!isProxyRunning && merged.enabled && !oldChannel.enabled) {
@@ -189,6 +228,18 @@ function updateChannel(channelId, updates) {
189
228
  }
190
229
 
191
230
  saveChannels(data);
231
+
232
+ if (!isProxyRunning) {
233
+ if (oldChannel.enabled === false && merged.enabled !== false) {
234
+ syncManagedChannelConfig(data.channels, merged);
235
+ } else {
236
+ const activeChannel = resolveCurrentManagedChannel(data.channels);
237
+ if (merged.enabled !== false && activeChannel?.id === merged.id) {
238
+ syncManagedChannelConfig(data.channels, merged);
239
+ }
240
+ }
241
+ }
242
+
192
243
  return data.channels[index];
193
244
  }
194
245
 
@@ -204,6 +255,10 @@ async function deleteChannel(channelId) {
204
255
  data.channels.splice(index, 1);
205
256
  saveChannels(data);
206
257
 
258
+ if (!getOpenCodeProxyRunning()) {
259
+ syncManagedChannelConfig(data.channels);
260
+ }
261
+
207
262
  return { success: true };
208
263
  }
209
264
 
@@ -1,5 +1,13 @@
1
1
  const fs = require('fs');
2
2
  const { NATIVE_PATHS } = require('../../config/paths');
3
+ const { isWindowsLikePlatform } = require('../../utils/home-dir');
4
+
5
+ function buildEchoCommand(value) {
6
+ if (isWindowsLikePlatform(process.platform, process.env)) {
7
+ return `cmd /c echo ${value}`;
8
+ }
9
+ return `echo '${value}'`;
10
+ }
3
11
 
4
12
  // Claude Code 配置文件路径
5
13
  function getSettingsPath() {
@@ -113,7 +121,7 @@ function setProxyConfig(proxyPort) {
113
121
  // 修改为代理配置(使用 Claude Code 的标准格式)
114
122
  settings.env.ANTHROPIC_BASE_URL = `http://127.0.0.1:${proxyPort}`;
115
123
  settings.env.ANTHROPIC_API_KEY = 'PROXY_KEY';
116
- settings.apiKeyHelper = `echo 'PROXY_KEY'`;
124
+ settings.apiKeyHelper = buildEchoCommand('PROXY_KEY');
117
125
 
118
126
  // 写入
119
127
  writeSettings(settings);