coding-tool-x 3.4.1 → 3.4.2
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 +1 -1
- package/src/commands/doctor.js +2 -2
- package/src/commands/resume.js +1 -0
- package/src/commands/update.js +2 -1
- package/src/plugins/plugin-installer.js +1 -0
- package/src/server/api/claude-hooks.js +2 -3
- package/src/server/api/workspaces.js +2 -1
- package/src/server/services/codex-env-manager.js +32 -2
- package/src/server/services/codex-settings-manager.js +20 -5
- package/src/server/services/mcp-client.js +2 -1
- package/src/server/services/notification-hooks.js +9 -8
- package/src/server/services/opencode-sessions.js +4 -2
- package/src/server/services/plugins-service.js +2 -1
- package/src/server/services/skill-service.js +4 -2
- package/src/server/services/workspace-service.js +1 -0
- package/src/utils/port-helper.js +5 -5
package/package.json
CHANGED
package/src/commands/doctor.js
CHANGED
|
@@ -262,7 +262,7 @@ async function checkProcessStatus() {
|
|
|
262
262
|
|
|
263
263
|
// 检查是否有 PM2 进程
|
|
264
264
|
try {
|
|
265
|
-
const { stdout } = await execAsync('pm2 list');
|
|
265
|
+
const { stdout } = await execAsync('pm2 list', { windowsHide: true });
|
|
266
266
|
if (stdout.includes('cc-tool')) {
|
|
267
267
|
return {
|
|
268
268
|
name: '进程状态',
|
|
@@ -295,7 +295,7 @@ async function checkProcessStatus() {
|
|
|
295
295
|
*/
|
|
296
296
|
async function checkDiskSpace() {
|
|
297
297
|
try {
|
|
298
|
-
const { stdout } = await execAsync('df -h ~');
|
|
298
|
+
const { stdout } = await execAsync('df -h ~', { windowsHide: true });
|
|
299
299
|
const lines = stdout.trim().split('\n');
|
|
300
300
|
if (lines.length > 1) {
|
|
301
301
|
const parts = lines[1].split(/\s+/);
|
package/src/commands/resume.js
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -26,7 +26,8 @@ function runNpmInstall(packageName, version) {
|
|
|
26
26
|
return new Promise((resolve, reject) => {
|
|
27
27
|
const npmCommand = resolveNpmCommand();
|
|
28
28
|
const child = spawn(npmCommand, ['install', '-g', `${packageName}@${version}`], {
|
|
29
|
-
stdio: 'inherit'
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
windowsHide: true
|
|
30
31
|
});
|
|
31
32
|
|
|
32
33
|
child.on('error', (err) => {
|
|
@@ -132,7 +132,7 @@ const timestamp = new Date().toLocaleString('zh-CN');
|
|
|
132
132
|
const cmd = generateSystemNotificationCommand(systemNotification.type);
|
|
133
133
|
script += `// 系统通知
|
|
134
134
|
try {
|
|
135
|
-
execSync(${JSON.stringify(cmd)}, { stdio: 'ignore' });
|
|
135
|
+
execSync(${JSON.stringify(cmd)}, { stdio: 'ignore', windowsHide: true });
|
|
136
136
|
} catch (e) {
|
|
137
137
|
console.error('系统通知失败:', e.message);
|
|
138
138
|
}
|
|
@@ -542,8 +542,7 @@ router.post('/test', (req, res) => {
|
|
|
542
542
|
// 测试系统通知
|
|
543
543
|
const command = generateSystemNotificationCommand(type || 'notification');
|
|
544
544
|
const { execSync } = require('child_process');
|
|
545
|
-
execSync(command, { stdio: 'ignore' });
|
|
546
|
-
res.json({ success: true, message: '系统测试通知已发送' });
|
|
545
|
+
execSync(command, { stdio: 'ignore', windowsHide: true });
|
|
547
546
|
}
|
|
548
547
|
} catch (error) {
|
|
549
548
|
console.error('Error testing notification:', error);
|
|
@@ -332,13 +332,32 @@ function runLaunchctlCommand(args, execSync) {
|
|
|
332
332
|
try {
|
|
333
333
|
execSync('launchctl', args, {
|
|
334
334
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
335
|
-
timeout: 3000
|
|
335
|
+
timeout: 3000,
|
|
336
|
+
windowsHide: true
|
|
336
337
|
});
|
|
337
338
|
} catch {
|
|
338
339
|
// ignore launchctl failures; shell profile remains the durable source
|
|
339
340
|
}
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
function broadcastWindowsSettingChange(execSync) {
|
|
344
|
+
const script = [
|
|
345
|
+
'Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"',
|
|
346
|
+
'[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]',
|
|
347
|
+
'public static extern IntPtr SendMessageTimeout(',
|
|
348
|
+
' IntPtr hWnd, uint Msg, UIntPtr wParam, string lParam,',
|
|
349
|
+
' uint fuFlags, uint uTimeout, out UIntPtr lpdwResult);',
|
|
350
|
+
'"@',
|
|
351
|
+
'$HWND_BROADCAST = [IntPtr]0xffff',
|
|
352
|
+
'$WM_SETTINGCHANGE = 0x1a',
|
|
353
|
+
'$SMTO_ABORTIFHUNG = 0x0002',
|
|
354
|
+
'$result = [UIntPtr]::Zero',
|
|
355
|
+
'[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,',
|
|
356
|
+
' [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result) | Out-Null'
|
|
357
|
+
].join('\n');
|
|
358
|
+
runWindowsEnvCommand(script, execSync);
|
|
359
|
+
}
|
|
360
|
+
|
|
342
361
|
function syncWindowsEnvironment(nextValues, previousState, options) {
|
|
343
362
|
const { stateFilePath, execSync } = options;
|
|
344
363
|
const nextKeys = Object.keys(nextValues).sort();
|
|
@@ -357,6 +376,15 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
|
|
|
357
376
|
changed = true;
|
|
358
377
|
}
|
|
359
378
|
|
|
379
|
+
// 广播 WM_SETTINGCHANGE,通知已打开的应用(如 VSCode)刷新环境变量
|
|
380
|
+
if (changed) {
|
|
381
|
+
try {
|
|
382
|
+
broadcastWindowsSettingChange(execSync);
|
|
383
|
+
} catch {
|
|
384
|
+
// 广播失败不影响主流程,环境变量已写入注册表
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
360
388
|
if (nextKeys.length > 0) {
|
|
361
389
|
writeJsonFile(stateFilePath, {
|
|
362
390
|
version: 1,
|
|
@@ -386,7 +414,8 @@ function runWindowsEnvCommand(script, execSync) {
|
|
|
386
414
|
try {
|
|
387
415
|
execSync(command, ['-NoProfile', '-NonInteractive', '-Command', script], {
|
|
388
416
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
389
|
-
timeout: 5000
|
|
417
|
+
timeout: 5000,
|
|
418
|
+
windowsHide: true
|
|
390
419
|
});
|
|
391
420
|
return;
|
|
392
421
|
} catch (error) {
|
|
@@ -443,6 +472,7 @@ function syncCodexUserEnvironment(envMap = {}, options = {}) {
|
|
|
443
472
|
module.exports = {
|
|
444
473
|
syncCodexUserEnvironment,
|
|
445
474
|
_test: {
|
|
475
|
+
broadcastWindowsSettingChange,
|
|
446
476
|
buildHomeRelativeShellPath,
|
|
447
477
|
buildNextEnvValues,
|
|
448
478
|
buildSourceSnippet,
|
|
@@ -196,6 +196,9 @@ function restoreSettings() {
|
|
|
196
196
|
removeKeys: ['CC_PROXY_KEY']
|
|
197
197
|
});
|
|
198
198
|
|
|
199
|
+
// 清理 process.env 中的代理 key(与 setProxyConfig 中的设置对应)
|
|
200
|
+
delete process.env.CC_PROXY_KEY;
|
|
201
|
+
|
|
199
202
|
console.log('Codex settings restored from backup');
|
|
200
203
|
return { success: true };
|
|
201
204
|
} catch (err) {
|
|
@@ -206,11 +209,18 @@ function restoreSettings() {
|
|
|
206
209
|
// 设置代理配置
|
|
207
210
|
function setProxyConfig(proxyPort) {
|
|
208
211
|
try {
|
|
209
|
-
//
|
|
210
|
-
|
|
212
|
+
// 先备份(config.toml 不存在时跳过备份)
|
|
213
|
+
if (configExists()) {
|
|
214
|
+
backupSettings();
|
|
215
|
+
}
|
|
211
216
|
|
|
212
|
-
//
|
|
213
|
-
|
|
217
|
+
// 读取当前配置(不存在时使用空配置)
|
|
218
|
+
let config;
|
|
219
|
+
try {
|
|
220
|
+
config = readConfig();
|
|
221
|
+
} catch (err) {
|
|
222
|
+
config = {};
|
|
223
|
+
}
|
|
214
224
|
|
|
215
225
|
// 设置 model_provider 为 proxy
|
|
216
226
|
config.model_provider = 'cc-proxy';
|
|
@@ -225,12 +235,17 @@ function setProxyConfig(proxyPort) {
|
|
|
225
235
|
name: 'cc-proxy',
|
|
226
236
|
base_url: `http://127.0.0.1:${proxyPort}/v1`,
|
|
227
237
|
wire_api: 'responses',
|
|
228
|
-
env_key: 'CC_PROXY_KEY'
|
|
238
|
+
env_key: 'CC_PROXY_KEY',
|
|
239
|
+
requires_openai_auth: false
|
|
229
240
|
};
|
|
230
241
|
|
|
231
242
|
// 写入配置
|
|
232
243
|
writeConfig(config);
|
|
233
244
|
|
|
245
|
+
// Windows: 注册表写入后当前进程和已打开的终端不会自动刷新环境变量
|
|
246
|
+
// 直接设置 process.env 确保从本进程派生的 Codex CLI 能读到 CC_PROXY_KEY
|
|
247
|
+
process.env.CC_PROXY_KEY = 'PROXY_KEY';
|
|
248
|
+
|
|
234
249
|
const envResult = syncCodexUserEnvironment({
|
|
235
250
|
CC_PROXY_KEY: 'PROXY_KEY'
|
|
236
251
|
}, {
|
|
@@ -389,7 +389,8 @@ class McpClient extends EventEmitter {
|
|
|
389
389
|
this._child = spawn(resolvedCommand, args, {
|
|
390
390
|
env: mergedEnv,
|
|
391
391
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
392
|
-
cwd: finalCwd
|
|
392
|
+
cwd: finalCwd,
|
|
393
|
+
windowsHide: true
|
|
393
394
|
});
|
|
394
395
|
} catch (err) {
|
|
395
396
|
clearTimeout(timer);
|
|
@@ -200,7 +200,8 @@ function fire(eventType) {
|
|
|
200
200
|
\`--event-type=\${eventType}\`
|
|
201
201
|
], {
|
|
202
202
|
detached: true,
|
|
203
|
-
stdio: 'ignore'
|
|
203
|
+
stdio: 'ignore',
|
|
204
|
+
windowsHide: true
|
|
204
205
|
})
|
|
205
206
|
child.unref()
|
|
206
207
|
} catch (error) {
|
|
@@ -320,7 +321,7 @@ function notify(mode, message) {
|
|
|
320
321
|
const appleScript = 'display dialog "' + escapeForAppleScript(message) +
|
|
321
322
|
'" with title "' + escapeForAppleScript(title) +
|
|
322
323
|
'" buttons {"好的"} default button 1 with icon note'
|
|
323
|
-
execSync('osascript -e ' + JSON.stringify(appleScript), { stdio: 'ignore' })
|
|
324
|
+
execSync('osascript -e ' + JSON.stringify(appleScript), { stdio: 'ignore', windowsHide: true })
|
|
324
325
|
} else {
|
|
325
326
|
const fallbackScript = 'display notification "' + escapeForAppleScript(message) +
|
|
326
327
|
'" with title "' + escapeForAppleScript(title) + '" sound name "Glass"'
|
|
@@ -329,7 +330,7 @@ function notify(mode, message) {
|
|
|
329
330
|
' -message ' + JSON.stringify(message) +
|
|
330
331
|
' -sound Glass -activate com.apple.Terminal; ' +
|
|
331
332
|
'else osascript -e ' + JSON.stringify(fallbackScript) + '; fi'
|
|
332
|
-
execSync(command, { stdio: 'ignore' })
|
|
333
|
+
execSync(command, { stdio: 'ignore', windowsHide: true })
|
|
333
334
|
}
|
|
334
335
|
return
|
|
335
336
|
}
|
|
@@ -339,7 +340,7 @@ function notify(mode, message) {
|
|
|
339
340
|
const ps = "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('" +
|
|
340
341
|
escapeForPowerShellSingleQuote(message) + "', '" +
|
|
341
342
|
escapeForPowerShellSingleQuote(title) + "', 'OK', 'Information')"
|
|
342
|
-
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore' })
|
|
343
|
+
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
|
|
343
344
|
} else {
|
|
344
345
|
const toastXml = '<toast><visual><binding template="ToastGeneric"><text>' +
|
|
345
346
|
escapeForXml(title) + '</text><text>' + escapeForXml(message) +
|
|
@@ -353,7 +354,7 @@ function notify(mode, message) {
|
|
|
353
354
|
"[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) " +
|
|
354
355
|
"} catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('" +
|
|
355
356
|
escapeForPowerShellSingleQuote(message) + "', 5, '" + escapeForPowerShellSingleQuote(title) + "', 0x40) }"
|
|
356
|
-
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore' })
|
|
357
|
+
execSync('powershell -NoProfile -Command ' + JSON.stringify(ps), { stdio: 'ignore', windowsHide: true })
|
|
357
358
|
}
|
|
358
359
|
return
|
|
359
360
|
}
|
|
@@ -364,10 +365,10 @@ function notify(mode, message) {
|
|
|
364
365
|
execSync(
|
|
365
366
|
'zenity --info --title="' + escapedTitle + '" --text="' + escapedMessage +
|
|
366
367
|
'" 2>/dev/null || notify-send "' + escapedTitle + '" "' + escapedMessage + '"',
|
|
367
|
-
{ stdio: 'ignore' }
|
|
368
|
+
{ stdio: 'ignore', windowsHide: true }
|
|
368
369
|
)
|
|
369
370
|
} else {
|
|
370
|
-
execSync('notify-send "' + escapedTitle + '" "' + escapedMessage + '"', { stdio: 'ignore' })
|
|
371
|
+
execSync('notify-send "' + escapedTitle + '" "' + escapedMessage + '"', { stdio: 'ignore', windowsHide: true })
|
|
371
372
|
}
|
|
372
373
|
} catch (error) {
|
|
373
374
|
// ignore system notification failures
|
|
@@ -916,7 +917,7 @@ function testNotification({ type, testFeishu, webhookUrl } = {}) {
|
|
|
916
917
|
return sendFeishuTest(webhookUrl);
|
|
917
918
|
}
|
|
918
919
|
|
|
919
|
-
execSync(generateSystemNotificationCommand(type || 'notification', '这是一条测试通知'), { stdio: 'ignore' });
|
|
920
|
+
execSync(generateSystemNotificationCommand(type || 'notification', '这是一条测试通知'), { stdio: 'ignore', windowsHide: true });
|
|
920
921
|
}
|
|
921
922
|
|
|
922
923
|
module.exports = {
|
|
@@ -168,7 +168,8 @@ function runSqliteQuery(sql) {
|
|
|
168
168
|
const output = execFileSync('sqlite3', ['-json', OPENCODE_DB_PATH, sql], {
|
|
169
169
|
encoding: 'utf8',
|
|
170
170
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
171
|
-
maxBuffer: 10 * 1024 * 1024
|
|
171
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
172
|
+
windowsHide: true
|
|
172
173
|
}).trim();
|
|
173
174
|
|
|
174
175
|
if (!output) {
|
|
@@ -191,7 +192,8 @@ function runSqliteExec(sql) {
|
|
|
191
192
|
execFileSync('sqlite3', [OPENCODE_DB_PATH, sql], {
|
|
192
193
|
encoding: 'utf8',
|
|
193
194
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
194
|
-
maxBuffer: 10 * 1024 * 1024
|
|
195
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
196
|
+
windowsHide: true
|
|
195
197
|
});
|
|
196
198
|
}
|
|
197
199
|
|
|
@@ -1055,7 +1055,8 @@ class PluginsService {
|
|
|
1055
1055
|
execSync(`claude plugin marketplace add ${repo.url}`, {
|
|
1056
1056
|
encoding: 'utf8',
|
|
1057
1057
|
timeout: 30000,
|
|
1058
|
-
stdio: 'pipe'
|
|
1058
|
+
stdio: 'pipe',
|
|
1059
|
+
windowsHide: true
|
|
1059
1060
|
});
|
|
1060
1061
|
results.push({ repo: repo.url, success: true });
|
|
1061
1062
|
} catch (err) {
|
|
@@ -849,7 +849,8 @@ class SkillService {
|
|
|
849
849
|
const output = execFileSync(command, args, {
|
|
850
850
|
encoding: 'utf-8',
|
|
851
851
|
timeout: 3000,
|
|
852
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
852
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
853
|
+
windowsHide: true
|
|
853
854
|
}).trim();
|
|
854
855
|
return output || null;
|
|
855
856
|
} catch {
|
|
@@ -866,7 +867,8 @@ class SkillService {
|
|
|
866
867
|
input: `protocol=https\nhost=${hostname}\n\n`,
|
|
867
868
|
encoding: 'utf-8',
|
|
868
869
|
timeout: 3000,
|
|
869
|
-
stdio: ['pipe', 'pipe', 'ignore']
|
|
870
|
+
stdio: ['pipe', 'pipe', 'ignore'],
|
|
871
|
+
windowsHide: true
|
|
870
872
|
});
|
|
871
873
|
const passwordLine = output
|
|
872
874
|
.split(/\r?\n/)
|
package/src/utils/port-helper.js
CHANGED
|
@@ -138,7 +138,7 @@ function findProcessByPort(port) {
|
|
|
138
138
|
if (isWindows) {
|
|
139
139
|
try {
|
|
140
140
|
// Windows: 直接解析 netstat 输出,避免依赖 findstr/lsof
|
|
141
|
-
const result = execSync('netstat -ano', { encoding: 'utf-8' });
|
|
141
|
+
const result = execSync('netstat -ano', { encoding: 'utf-8', windowsHide: true });
|
|
142
142
|
return parsePidsFromNetstatOutput(result, port);
|
|
143
143
|
} catch (e) {
|
|
144
144
|
if (isMissingCommandError(e)) {
|
|
@@ -151,13 +151,13 @@ function findProcessByPort(port) {
|
|
|
151
151
|
let lsofMissing = false;
|
|
152
152
|
try {
|
|
153
153
|
// macOS/Linux 使用 lsof
|
|
154
|
-
const result = execSync(`lsof -ti :${port}`, { encoding: 'utf-8' }).trim();
|
|
154
|
+
const result = execSync(`lsof -ti :${port}`, { encoding: 'utf-8', windowsHide: true }).trim();
|
|
155
155
|
return result.split('\n').filter(pid => pid);
|
|
156
156
|
} catch (err) {
|
|
157
157
|
lsofMissing = isMissingCommandError(err);
|
|
158
158
|
// 如果 lsof 失败,尝试使用 fuser(某些 Linux 系统)
|
|
159
159
|
try {
|
|
160
|
-
const result = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: 'utf-8' }).trim();
|
|
160
|
+
const result = execSync(`fuser ${port}/tcp 2>/dev/null`, { encoding: 'utf-8', windowsHide: true }).trim();
|
|
161
161
|
return result.split(/\s+/).filter(pid => pid);
|
|
162
162
|
} catch (e) {
|
|
163
163
|
if (lsofMissing && isMissingCommandError(e)) {
|
|
@@ -183,9 +183,9 @@ function killProcessByPort(port) {
|
|
|
183
183
|
pids.forEach(pid => {
|
|
184
184
|
try {
|
|
185
185
|
if (isWindows) {
|
|
186
|
-
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore' });
|
|
186
|
+
execSync(`taskkill /F /PID ${pid}`, { stdio: 'ignore', windowsHide: true });
|
|
187
187
|
} else {
|
|
188
|
-
execSync(`kill -9 ${pid}`, { stdio: 'ignore' });
|
|
188
|
+
execSync(`kill -9 ${pid}`, { stdio: 'ignore', windowsHide: true });
|
|
189
189
|
}
|
|
190
190
|
killedAny = true;
|
|
191
191
|
} catch (err) {
|