coding-tool-x 3.4.7 → 3.4.9
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
|
@@ -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 (
|
|
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 (
|
|
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 -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')" || ${buildWindowsPopupCommand()}`;
|
|
101
105
|
} else {
|
|
102
|
-
return
|
|
106
|
+
return buildWindowsPopupCommand();
|
|
103
107
|
}
|
|
104
108
|
} else {
|
|
105
109
|
// Linux
|
|
@@ -63,6 +63,7 @@ function buildApiKeyHelperCommand() {
|
|
|
63
63
|
function updateClaudeSettingsWithModelConfig(channel) {
|
|
64
64
|
clearNativeOAuth('claude');
|
|
65
65
|
const settingsPath = getClaudeSettingsPath();
|
|
66
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
66
67
|
|
|
67
68
|
let settings = {};
|
|
68
69
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -119,6 +120,7 @@ function updateClaudeSettingsWithModelConfig(channel) {
|
|
|
119
120
|
|
|
120
121
|
function updateClaudeSettings(baseUrl, apiKey) {
|
|
121
122
|
const settingsPath = getClaudeSettingsPath();
|
|
123
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
122
124
|
|
|
123
125
|
let settings = {};
|
|
124
126
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -149,6 +151,11 @@ function updateClaudeSettings(baseUrl, apiKey) {
|
|
|
149
151
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
function resolveCurrentManagedChannel(channels = []) {
|
|
155
|
+
const allChannels = Array.isArray(channels) ? channels : [];
|
|
156
|
+
return allChannels.find(ch => ch.enabled !== false) || null;
|
|
157
|
+
}
|
|
158
|
+
|
|
152
159
|
// ── ClaudeChannelService ──
|
|
153
160
|
|
|
154
161
|
class ClaudeChannelService extends BaseChannelService {
|
|
@@ -197,6 +204,39 @@ class ClaudeChannelService extends BaseChannelService {
|
|
|
197
204
|
this._cacheInitialized = true;
|
|
198
205
|
}
|
|
199
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
|
+
|
|
200
240
|
_applyToNativeSettings(channel) {
|
|
201
241
|
updateClaudeSettingsWithModelConfig(channel);
|
|
202
242
|
}
|
|
@@ -49,7 +49,7 @@ function buildWindowsSettingChangeScript() {
|
|
|
49
49
|
].join('\n');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function buildWindowsEnvBatchScript(operations = [], { includeSettingChangeBroadcast =
|
|
52
|
+
function buildWindowsEnvBatchScript(operations = [], { includeSettingChangeBroadcast = false } = {}) {
|
|
53
53
|
const normalizedOperations = Array.isArray(operations) ? operations.filter(Boolean) : [];
|
|
54
54
|
const lines = normalizedOperations.map((operation) => {
|
|
55
55
|
const key = powershellQuote(operation.key || '');
|
|
@@ -396,7 +396,16 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
|
|
|
396
396
|
|
|
397
397
|
const changed = operations.length > 0;
|
|
398
398
|
if (changed) {
|
|
399
|
-
runWindowsEnvCommand(
|
|
399
|
+
runWindowsEnvCommand(
|
|
400
|
+
buildWindowsEnvBatchScript(operations, { includeSettingChangeBroadcast: false }),
|
|
401
|
+
execSync
|
|
402
|
+
);
|
|
403
|
+
try {
|
|
404
|
+
// WM_SETTINGCHANGE 只是帮助已打开的应用刷新环境变量,失败不应让主流程报错。
|
|
405
|
+
broadcastWindowsSettingChange(execSync);
|
|
406
|
+
} catch {
|
|
407
|
+
// ignore broadcast failures; registry writes are already durable
|
|
408
|
+
}
|
|
400
409
|
}
|
|
401
410
|
|
|
402
411
|
if (nextKeys.length > 0) {
|
|
@@ -237,6 +237,10 @@ function escapeForXml(value) {
|
|
|
237
237
|
.replace(/'/g, ''');
|
|
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,25 +340,15 @@ 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
|
-
|
|
348
|
+
const command = 'powershell -NoProfile -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
|
|
349
|
+
execSync(command, { stdio: 'ignore', windowsHide: true })
|
|
344
350
|
} else {
|
|
345
|
-
|
|
346
|
-
escapeForXml(title) + '</text><text>' + escapeForXml(message) +
|
|
347
|
-
'</text></binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>'
|
|
348
|
-
const ps = 'try { ' +
|
|
349
|
-
'[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; ' +
|
|
350
|
-
'[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null; ' +
|
|
351
|
-
'$xml = New-Object Windows.Data.Xml.Dom.XmlDocument; ' +
|
|
352
|
-
'$xml.LoadXml(\\'' + toastXml.replace(/'/g, "''") + '\\'); ' +
|
|
353
|
-
'$toast = [Windows.UI.Notifications.ToastNotification]::new($xml); ' +
|
|
354
|
-
"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) " +
|
|
355
|
-
"} catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('" +
|
|
356
|
-
escapeForPowerShellSingleQuote(message) + "', 5, '" + escapeForPowerShellSingleQuote(title) + "', 0x40) }"
|
|
357
|
-
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
|
|
351
|
+
execSync(popupCommand, { stdio: 'ignore', windowsHide: true })
|
|
358
352
|
}
|
|
359
353
|
return
|
|
360
354
|
}
|
|
@@ -468,6 +462,10 @@ function escapeForXml(value) {
|
|
|
468
462
|
.replace(/"/g, '"')
|
|
469
463
|
.replace(/'/g, ''')
|
|
470
464
|
}
|
|
465
|
+
|
|
466
|
+
function buildWindowsPopupCommand(title, message) {
|
|
467
|
+
return \`powershell -NoProfile -Command "$wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('\${escapeForPowerShellSingleQuote(message)}', 5, '\${escapeForPowerShellSingleQuote(title)}', 0x40)"\`
|
|
468
|
+
}
|
|
471
469
|
`;
|
|
472
470
|
}
|
|
473
471
|
|
|
@@ -884,10 +882,10 @@ function sendFeishuTest(webhookUrl) {
|
|
|
884
882
|
});
|
|
885
883
|
}
|
|
886
884
|
|
|
887
|
-
function generateSystemNotificationCommand(type, message) {
|
|
885
|
+
function generateSystemNotificationCommand(type, message, platformOverride = os.platform()) {
|
|
888
886
|
const normalizedType = normalizeType(type);
|
|
889
887
|
const title = 'Coding Tool';
|
|
890
|
-
const platform =
|
|
888
|
+
const platform = platformOverride;
|
|
891
889
|
|
|
892
890
|
if (platform === 'darwin') {
|
|
893
891
|
if (normalizedType === 'dialog') {
|
|
@@ -897,12 +895,11 @@ function generateSystemNotificationCommand(type, message) {
|
|
|
897
895
|
}
|
|
898
896
|
|
|
899
897
|
if (platform === 'win32') {
|
|
898
|
+
const popupCommand = buildWindowsPopupCommand(title, message);
|
|
900
899
|
if (normalizedType === 'dialog') {
|
|
901
|
-
return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')"`;
|
|
900
|
+
return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')" || ${popupCommand}`;
|
|
902
901
|
}
|
|
903
|
-
|
|
904
|
-
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) }"`;
|
|
902
|
+
return popupCommand;
|
|
906
903
|
}
|
|
907
904
|
|
|
908
905
|
if (normalizedType === 'dialog') {
|
|
@@ -947,6 +944,7 @@ module.exports = {
|
|
|
947
944
|
buildClaudeCommand,
|
|
948
945
|
buildOpenCodePluginContent,
|
|
949
946
|
getOpenCodeManagedPluginPath,
|
|
950
|
-
generateNotifyScript
|
|
947
|
+
generateNotifyScript,
|
|
948
|
+
generateSystemNotificationCommand
|
|
951
949
|
}
|
|
952
950
|
};
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
|