coding-tool-x 3.4.0 → 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.
Files changed (41) hide show
  1. package/dist/web/assets/{Analytics-DEjfL5Jx.js → Analytics-CbGxotgz.js} +1 -1
  2. package/dist/web/assets/{ConfigTemplates-DkRL_-tf.js → ConfigTemplates-oP6nrFEb.js} +1 -1
  3. package/dist/web/assets/{Home-CF-L640I.js → Home-DMntmEvh.js} +1 -1
  4. package/dist/web/assets/{PluginManager-BzNYTdNB.js → PluginManager-BUC_c7nH.js} +1 -1
  5. package/dist/web/assets/{ProjectList-C0-JgHMM.js → ProjectList-CW8J49n7.js} +1 -1
  6. package/dist/web/assets/{SessionList-CkZUdX5N.js → SessionList-7lYnF92v.js} +1 -1
  7. package/dist/web/assets/{SkillManager-Cak0-4d4.js → SkillManager-Cs08216i.js} +1 -1
  8. package/dist/web/assets/{WorkspaceManager-CGDJzwEr.js → WorkspaceManager-CY-oGtyB.js} +1 -1
  9. package/dist/web/assets/{index-Dz7v9OM0.css → index-5qy5NMIP.css} +1 -1
  10. package/dist/web/assets/index-ClCqKpvX.js +2 -0
  11. package/dist/web/index.html +2 -2
  12. package/package.json +6 -2
  13. package/src/commands/doctor.js +2 -2
  14. package/src/commands/resume.js +1 -0
  15. package/src/commands/update.js +2 -1
  16. package/src/plugins/plugin-installer.js +1 -0
  17. package/src/server/api/claude-hooks.js +2 -3
  18. package/src/server/api/workspaces.js +2 -1
  19. package/src/server/codex-proxy-server.js +4 -92
  20. package/src/server/gemini-proxy-server.js +5 -28
  21. package/src/server/opencode-proxy-server.js +3 -93
  22. package/src/server/proxy-server.js +2 -57
  23. package/src/server/services/base/base-channel-service.js +247 -0
  24. package/src/server/services/base/proxy-utils.js +152 -0
  25. package/src/server/services/channel-health.js +30 -19
  26. package/src/server/services/channels.js +125 -293
  27. package/src/server/services/codex-channels.js +148 -513
  28. package/src/server/services/codex-env-manager.js +81 -21
  29. package/src/server/services/codex-settings-manager.js +20 -5
  30. package/src/server/services/gemini-channels.js +2 -7
  31. package/src/server/services/mcp-client.js +2 -1
  32. package/src/server/services/notification-hooks.js +9 -8
  33. package/src/server/services/oauth-credentials-service.js +12 -2
  34. package/src/server/services/opencode-channels.js +7 -9
  35. package/src/server/services/opencode-sessions.js +4 -2
  36. package/src/server/services/plugins-service.js +2 -1
  37. package/src/server/services/repo-scanner-base.js +1 -0
  38. package/src/server/services/skill-service.js +4 -2
  39. package/src/server/services/workspace-service.js +1 -0
  40. package/src/utils/port-helper.js +5 -5
  41. package/dist/web/assets/index-D_WItvHE.js +0 -2
@@ -77,6 +77,22 @@ function upsertManagedBlock(content, snippet) {
77
77
  return `${stripped.trimEnd()}\n\n${snippet}\n`;
78
78
  }
79
79
 
80
+ function buildExportCommand(envFilePath, homeDir) {
81
+ return `. "${buildHomeRelativeShellPath(envFilePath, homeDir)}"`;
82
+ }
83
+
84
+ function buildPosixEnvFileContent(nextValues) {
85
+ const keys = Object.keys(nextValues).sort();
86
+ if (!keys.length) {
87
+ return '';
88
+ }
89
+ return [
90
+ '# Managed by Coding-Tool',
91
+ ...keys.map((key) => `export ${key}=${shellQuote(nextValues[key])}`),
92
+ ''
93
+ ].join('\n');
94
+ }
95
+
80
96
  function readJsonFile(filePath, fallbackValue) {
81
97
  try {
82
98
  if (!fs.existsSync(filePath)) {
@@ -181,29 +197,43 @@ function syncPosixEnvironment(nextValues, previousState, options) {
181
197
  const {
182
198
  runtime,
183
199
  homeDir,
200
+ envFilePath,
184
201
  stateFilePath,
185
202
  shellEnv,
186
203
  execSync
187
204
  } = options;
188
205
  const nextKeys = Object.keys(nextValues).sort();
189
206
  let changed = false;
207
+ const { preferred, candidates } = getPosixProfileCandidates(homeDir, shellEnv);
208
+ const sourceSnippet = buildSourceSnippet(envFilePath, homeDir);
209
+ const sourceCommand = buildExportCommand(envFilePath, homeDir);
190
210
 
191
211
  // 清理旧版本遗留的 shell profile 注入(迁移兼容)
192
212
  const previousProfiles = Array.isArray(previousState.profiles) ? previousState.profiles : [];
193
- if (previousProfiles.length > 0) {
194
- const { candidates } = getPosixProfileCandidates(homeDir, shellEnv);
195
- const cleanupTargets = new Set([
196
- ...previousProfiles,
197
- ...candidates.filter(filePath => fs.existsSync(filePath))
198
- ]);
199
- for (const profilePath of cleanupTargets) {
200
- if (!fs.existsSync(profilePath)) continue;
201
- const currentContent = fs.readFileSync(profilePath, 'utf8');
202
- if (!currentContent.includes(PROFILE_MARKER_START)) continue;
203
- const nextContent = stripManagedBlock(currentContent);
204
- const finalContent = nextContent ? `${nextContent}\n` : '';
205
- changed = writeTextFileIfChanged(profilePath, finalContent) || changed;
206
- }
213
+ const cleanupTargets = new Set([
214
+ ...previousProfiles,
215
+ ...candidates.filter((filePath) => fs.existsSync(filePath))
216
+ ]);
217
+ if (!nextKeys.length) {
218
+ cleanupTargets.add(preferred);
219
+ }
220
+ for (const profilePath of cleanupTargets) {
221
+ if (!fs.existsSync(profilePath)) continue;
222
+ const currentContent = fs.readFileSync(profilePath, 'utf8');
223
+ if (!currentContent.includes(PROFILE_MARKER_START)) continue;
224
+ const nextContent = stripManagedBlock(currentContent);
225
+ const finalContent = nextContent ? `${nextContent}\n` : '';
226
+ changed = writeTextFileIfChanged(profilePath, finalContent) || changed;
227
+ }
228
+
229
+ if (nextKeys.length > 0) {
230
+ changed = writeTextFileIfChanged(envFilePath, buildPosixEnvFileContent(nextValues)) || changed;
231
+ const currentProfileContent = fs.existsSync(preferred) ? fs.readFileSync(preferred, 'utf8') : '';
232
+ const nextProfileContent = upsertManagedBlock(currentProfileContent, sourceSnippet);
233
+ changed = writeTextFileIfChanged(preferred, nextProfileContent) || changed;
234
+ } else if (fs.existsSync(envFilePath)) {
235
+ fs.unlinkSync(envFilePath);
236
+ changed = true;
207
237
  }
208
238
 
209
239
  // macOS:用 launchctl 写入全局环境变量,新开终端/进程即生效
@@ -225,7 +255,7 @@ function syncPosixEnvironment(nextValues, previousState, options) {
225
255
  writeJsonFile(stateFilePath, {
226
256
  version: 1,
227
257
  values: nextValues,
228
- profiles: []
258
+ profiles: [preferred]
229
259
  });
230
260
  } else if (fs.existsSync(stateFilePath)) {
231
261
  fs.unlinkSync(stateFilePath);
@@ -235,10 +265,10 @@ function syncPosixEnvironment(nextValues, previousState, options) {
235
265
  changed,
236
266
  reloadRequired: changed,
237
267
  isFirstTime: Object.keys(previousState.values || {}).length === 0 && nextKeys.length > 0,
238
- sourceCommand: null,
239
- shellConfigPath: null,
240
- shellConfigPaths: [],
241
- envFilePath: null,
268
+ sourceCommand: nextKeys.length > 0 ? sourceCommand : null,
269
+ shellConfigPath: nextKeys.length > 0 ? preferred : null,
270
+ shellConfigPaths: nextKeys.length > 0 ? [preferred] : [],
271
+ envFilePath: nextKeys.length > 0 ? envFilePath : null,
242
272
  managedKeys: nextKeys
243
273
  };
244
274
  }
@@ -302,13 +332,32 @@ function runLaunchctlCommand(args, execSync) {
302
332
  try {
303
333
  execSync('launchctl', args, {
304
334
  stdio: ['ignore', 'ignore', 'ignore'],
305
- timeout: 3000
335
+ timeout: 3000,
336
+ windowsHide: true
306
337
  });
307
338
  } catch {
308
339
  // ignore launchctl failures; shell profile remains the durable source
309
340
  }
310
341
  }
311
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
+
312
361
  function syncWindowsEnvironment(nextValues, previousState, options) {
313
362
  const { stateFilePath, execSync } = options;
314
363
  const nextKeys = Object.keys(nextValues).sort();
@@ -327,6 +376,15 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
327
376
  changed = true;
328
377
  }
329
378
 
379
+ // 广播 WM_SETTINGCHANGE,通知已打开的应用(如 VSCode)刷新环境变量
380
+ if (changed) {
381
+ try {
382
+ broadcastWindowsSettingChange(execSync);
383
+ } catch {
384
+ // 广播失败不影响主流程,环境变量已写入注册表
385
+ }
386
+ }
387
+
330
388
  if (nextKeys.length > 0) {
331
389
  writeJsonFile(stateFilePath, {
332
390
  version: 1,
@@ -356,7 +414,8 @@ function runWindowsEnvCommand(script, execSync) {
356
414
  try {
357
415
  execSync(command, ['-NoProfile', '-NonInteractive', '-Command', script], {
358
416
  stdio: ['ignore', 'ignore', 'ignore'],
359
- timeout: 5000
417
+ timeout: 5000,
418
+ windowsHide: true
360
419
  });
361
420
  return;
362
421
  } catch (error) {
@@ -413,6 +472,7 @@ function syncCodexUserEnvironment(envMap = {}, options = {}) {
413
472
  module.exports = {
414
473
  syncCodexUserEnvironment,
415
474
  _test: {
475
+ broadcastWindowsSettingChange,
416
476
  buildHomeRelativeShellPath,
417
477
  buildNextEnvValues,
418
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
- backupSettings();
212
+ // 先备份(config.toml 不存在时跳过备份)
213
+ if (configExists()) {
214
+ backupSettings();
215
+ }
211
216
 
212
- // 读取当前配置
213
- const config = readConfig();
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
  }, {
@@ -3,6 +3,7 @@ const path = require('path');
3
3
  const crypto = require('crypto');
4
4
  const { PATHS, NATIVE_PATHS } = require('../../config/paths');
5
5
  const { clearNativeOAuth } = require('./native-oauth-adapters');
6
+ const { normalizeGatewaySourceType } = require('./base/proxy-utils');
6
7
 
7
8
  /**
8
9
  * Gemini 渠道管理服务(多渠道架构)
@@ -17,13 +18,7 @@ const { clearNativeOAuth } = require('./native-oauth-adapters');
17
18
  * - 使用 weight 和 maxConcurrency 控制负载均衡
18
19
  */
19
20
 
20
- function normalizeGatewaySourceType(value, fallback = 'gemini') {
21
- const normalized = String(value || '').trim().toLowerCase();
22
- if (normalized === 'claude') return 'claude';
23
- if (normalized === 'codex') return 'codex';
24
- if (normalized === 'gemini') return 'gemini';
25
- return fallback;
26
- }
21
+ // normalizeGatewaySourceType imported from base/proxy-utils
27
22
 
28
23
  // 获取 Gemini 配置目录
29
24
  function getGeminiDir() {
@@ -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 = {
@@ -380,12 +380,21 @@ function stableFingerprintValue(tool, metadata) {
380
380
  return stableId;
381
381
  }
382
382
 
383
+ function resolveFingerprintValue(tool, metadata, options = {}) {
384
+ if (options.fingerprintMode === 'primary-token') {
385
+ return metadata.primaryToken
386
+ || metadata.accessToken
387
+ || stableFingerprintValue(tool, metadata);
388
+ }
389
+ return stableFingerprintValue(tool, metadata);
390
+ }
391
+
383
392
  function upsertCredential(tool, metadata, options = {}) {
384
393
  const store = readStore();
385
394
  const toolStore = getToolStore(store, tool);
386
395
  const now = Date.now();
387
396
  const primaryToken = metadata.primaryToken || metadata.accessToken || '';
388
- const fingerprint = fingerprintFor(tool, stableFingerprintValue(tool, metadata));
397
+ const fingerprint = fingerprintFor(tool, resolveFingerprintValue(tool, metadata, options));
389
398
  const existingIndex = toolStore.credentials.findIndex((item) => item.fingerprint === fingerprint);
390
399
  const existing = existingIndex >= 0 ? toolStore.credentials[existingIndex] : null;
391
400
 
@@ -456,7 +465,8 @@ function syncLocalCredential(tool) {
456
465
  }
457
466
 
458
467
  const credentials = nativeCredentials.map((metadata) => upsertCredential(tool, metadata, {
459
- source: 'synced-local'
468
+ source: 'synced-local',
469
+ fingerprintMode: 'primary-token'
460
470
  }));
461
471
 
462
472
  return {
@@ -4,18 +4,15 @@ const crypto = require('crypto');
4
4
  const { PATHS } = require('../../config/paths');
5
5
  const { clearNativeOAuth } = require('./native-oauth-adapters');
6
6
  const { setChannelConfig } = require('./opencode-settings-manager');
7
+ const { normalizeGatewaySourceType } = require('./base/proxy-utils');
7
8
 
8
9
  /**
9
10
  * OpenCode 渠道管理服务
10
11
  * 存储位置: ~/.cc-tool/opencode-channels.json
11
12
  */
12
13
 
13
- function normalizeGatewaySourceType(value) {
14
- const normalized = String(value || '').trim().toLowerCase();
15
- if (normalized === 'claude') return 'claude';
16
- if (normalized === 'gemini') return 'gemini';
17
- return 'codex';
18
- }
14
+ // normalizeGatewaySourceType imported from base/proxy-utils
15
+ // OpenCode default fallback is 'codex'
19
16
 
20
17
  function normalizeApiKey(value) {
21
18
  if (typeof value !== 'string') return '';
@@ -80,7 +77,7 @@ function loadChannels() {
80
77
  modelRedirects: ch.modelRedirects || [],
81
78
  speedTestModel: ch.speedTestModel || null,
82
79
  wireApi: ch.wireApi || 'openai', // OpenCode 默认使用 OpenAI 兼容格式
83
- gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType),
80
+ gatewaySourceType: normalizeGatewaySourceType(ch.gatewaySourceType, 'codex'),
84
81
  allowedModels: ch.allowedModels || []
85
82
  };
86
83
  normalized.providerKey = deriveProviderKey(normalized);
@@ -132,7 +129,7 @@ function createChannel(name, baseUrl, apiKey, extraConfig = {}) {
132
129
  modelRedirects: extraConfig.modelRedirects || [],
133
130
  speedTestModel: extraConfig.speedTestModel || null,
134
131
  model: extraConfig.model || null,
135
- gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType),
132
+ gatewaySourceType: normalizeGatewaySourceType(extraConfig.gatewaySourceType, 'codex'),
136
133
  providerKey: extraConfig.providerKey || null,
137
134
  presetId: extraConfig.presetId || null,
138
135
  websiteUrl: extraConfig.websiteUrl || '',
@@ -169,7 +166,8 @@ function updateChannel(channelId, updates) {
169
166
  gatewaySourceType: normalizeGatewaySourceType(
170
167
  updates.gatewaySourceType !== undefined
171
168
  ? updates.gatewaySourceType
172
- : oldChannel.gatewaySourceType
169
+ : oldChannel.gatewaySourceType,
170
+ 'codex'
173
171
  ),
174
172
  updatedAt: Date.now()
175
173
  };
@@ -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) {
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ const os = require('os');
10
11
  const https = require('https');
11
12
  const http = require('http');
12
13
  const { createWriteStream } = require('fs');
@@ -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/)
@@ -30,6 +30,7 @@ function runGitCommand(args, options = {}) {
30
30
  const execOptions = {
31
31
  encoding: 'utf8',
32
32
  stdio: ['ignore', 'pipe', 'pipe'],
33
+ windowsHide: true,
33
34
  ...options
34
35
  };
35
36
  return execFileSync('git', args, execOptions);
@@ -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) {