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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.4.1",
3
+ "version": "3.4.2",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -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+/);
@@ -106,6 +106,7 @@ async function resumeSession(config, sessionId, fork = false) {
106
106
  // 此时 ct 进程只是等待,不处理任何输入
107
107
  execSync(command, {
108
108
  stdio: 'inherit', // 完全继承 stdio,让 claude 控制终端
109
+ windowsHide: true,
109
110
  });
110
111
 
111
112
  // 恢复目录
@@ -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) => {
@@ -66,6 +66,7 @@ function execCommand(command, options = {}) {
66
66
  const output = execSync(command, {
67
67
  encoding: 'utf8',
68
68
  stdio: ['pipe', 'pipe', 'pipe'],
69
+ windowsHide: true,
69
70
  ...options
70
71
  });
71
72
  return { success: true, output: output.trim() };
@@ -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);
@@ -24,7 +24,8 @@ function validateBranchName(branchName) {
24
24
 
25
25
  try {
26
26
  execFileSync('git', ['check-ref-format', '--branch', normalized], {
27
- stdio: 'ignore'
27
+ stdio: 'ignore',
28
+ windowsHide: true
28
29
  });
29
30
  return { valid: true, normalized };
30
31
  } catch (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
- 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
  }, {
@@ -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/)
@@ -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) {