coding-tool-x 3.4.8 → 3.4.10

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.8",
3
+ "version": "3.4.10",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -103,7 +103,7 @@ function generateSystemNotificationCommand(type, platformOverride = platform) {
103
103
  if (type === 'dialog') {
104
104
  return `powershell -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')" || ${buildWindowsPopupCommand()}`;
105
105
  } else {
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()}`;
106
+ return buildWindowsPopupCommand();
107
107
  }
108
108
  } else {
109
109
  // Linux
@@ -11,7 +11,7 @@ const {
11
11
  hasBackup,
12
12
  readSettings
13
13
  } = require('../services/settings-manager');
14
- const { getAllChannels } = require('../services/channels');
14
+ const { getAllChannels, extractApiKeyFromHelper } = require('../services/channels');
15
15
  const { clearNativeOAuth } = require('../services/native-oauth-adapters');
16
16
  const { clearAllLogs } = require('../websocket-server');
17
17
  const { PATHS, NATIVE_PATHS, ensureStorageDirMigrated } = require('../../config/paths');
@@ -101,10 +101,7 @@ function findActiveChannelFromSettings() {
101
101
 
102
102
  // 如果 apiKey 仍为空,尝试从 apiKeyHelper 提取
103
103
  if (!apiKey && settings?.apiKeyHelper) {
104
- const match = settings.apiKeyHelper.match(/['\"]([^'\"]+)['\"]/)
105
- if (match && match[1]) {
106
- apiKey = match[1];
107
- }
104
+ apiKey = extractApiKeyFromHelper(settings.apiKeyHelper);
108
105
  }
109
106
 
110
107
  const channels = getAllChannels();
@@ -44,18 +44,22 @@ function extractApiKeyFromHelper(apiKeyHelper) {
44
44
  return '';
45
45
  }
46
46
  const helper = apiKeyHelper.trim();
47
- let match = helper.match(/^echo\s+["']([^"']+)["']$/);
47
+ let match = helper.match(/^echo\s+["']([^"']+)["']$/i);
48
48
  if (match && match[1]) return match[1];
49
- match = helper.match(/^printf\s+["'][^"']*["']\s+["']([^"']+)["']$/);
49
+ match = helper.match(/^echo\s+([^\s].*)$/i);
50
+ if (match && match[1]) return match[1].trim();
51
+ match = helper.match(/^cmd(?:\.exe)?\s*\/c\s+echo\s+([^\s].*)$/i);
52
+ if (match && match[1]) return match[1].trim();
53
+ match = helper.match(/^printf\s+["'][^"']*["']\s+["']([^"']+)["']$/i);
50
54
  if (match && match[1]) return match[1];
51
55
  return '';
52
56
  }
53
57
 
54
- function buildApiKeyHelperCommand() {
58
+ function buildApiKeyHelperCommand(value) {
55
59
  if (isWindowsLikePlatform(process.platform, process.env)) {
56
- return 'cmd /c echo ctx-managed';
60
+ return `cmd /c echo ${value}`;
57
61
  }
58
- return "echo 'ctx-managed'";
62
+ return `echo '${value}'`;
59
63
  }
60
64
 
61
65
  // ── Claude 原生设置写入 ──
@@ -114,7 +118,7 @@ function updateClaudeSettingsWithModelConfig(channel) {
114
118
  delete settings.env;
115
119
  }
116
120
 
117
- settings.apiKeyHelper = buildApiKeyHelperCommand();
121
+ settings.apiKeyHelper = buildApiKeyHelperCommand(apiKey);
118
122
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
119
123
  }
120
124
 
@@ -147,7 +151,7 @@ function updateClaudeSettings(baseUrl, apiKey) {
147
151
  delete settings.env;
148
152
  }
149
153
 
150
- settings.apiKeyHelper = buildApiKeyHelperCommand();
154
+ settings.apiKeyHelper = buildApiKeyHelperCommand(apiKey);
151
155
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf8');
152
156
  }
153
157
 
@@ -326,4 +330,5 @@ module.exports = {
326
330
  updateClaudeSettingsWithModelConfig,
327
331
  getEffectiveApiKey,
328
332
  disableAllChannels,
333
+ extractApiKeyFromHelper,
329
334
  };
@@ -5,6 +5,9 @@ const { PATHS, HOME_DIR } = require('../../config/paths');
5
5
 
6
6
  const PROFILE_MARKER_START = '# >>> coding-tool codex env >>>';
7
7
  const PROFILE_MARKER_END = '# <<< coding-tool codex env <<<';
8
+ const WINDOWS_ENV_COMMAND_TIMEOUT_MS = 15000;
9
+ const WINDOWS_SETTING_CHANGE_TIMEOUT_MS = 1000;
10
+ const WINDOWS_SETTING_CHANGE_COMMAND_TIMEOUT_MS = 7000;
8
11
 
9
12
  function defaultEnvFilePath(configDir) {
10
13
  return path.join(configDir, 'codex-env.sh');
@@ -32,7 +35,7 @@ function powershellQuote(value) {
32
35
  return `'${String(value).replace(/'/g, "''")}'`;
33
36
  }
34
37
 
35
- function buildWindowsSettingChangeScript() {
38
+ function buildWindowsSettingChangeScript(timeoutMs = WINDOWS_SETTING_CHANGE_TIMEOUT_MS) {
36
39
  return [
37
40
  'Add-Type -Namespace Win32 -Name NativeMethods -MemberDefinition @"',
38
41
  '[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]',
@@ -45,7 +48,7 @@ function buildWindowsSettingChangeScript() {
45
48
  '$SMTO_ABORTIFHUNG = 0x0002',
46
49
  '$result = [UIntPtr]::Zero',
47
50
  '[Win32.NativeMethods]::SendMessageTimeout($HWND_BROADCAST, $WM_SETTINGCHANGE,',
48
- ' [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, 5000, [ref]$result) | Out-Null'
51
+ ` [UIntPtr]::Zero, "Environment", $SMTO_ABORTIFHUNG, ${timeoutMs}, [ref]$result) | Out-Null`
49
52
  ].join('\n');
50
53
  }
51
54
 
@@ -375,7 +378,12 @@ function runLaunchctlCommand(args, execSync) {
375
378
  }
376
379
 
377
380
  function broadcastWindowsSettingChange(execSync) {
378
- runWindowsEnvCommand(buildWindowsSettingChangeScript(), execSync);
381
+ // WM_SETTINGCHANGE 只是帮助已运行的 GUI/终端尽快感知环境变化。
382
+ // 用户级环境变量已经写入注册表,即使这里超时,新开进程仍然能读到。
383
+ runWindowsEnvCommand(buildWindowsSettingChangeScript(), execSync, {
384
+ timeout: WINDOWS_SETTING_CHANGE_COMMAND_TIMEOUT_MS,
385
+ ignoreErrors: true
386
+ });
379
387
  }
380
388
 
381
389
  function syncWindowsEnvironment(nextValues, previousState, options) {
@@ -396,7 +404,12 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
396
404
 
397
405
  const changed = operations.length > 0;
398
406
  if (changed) {
399
- runWindowsEnvCommand(buildWindowsEnvBatchScript(operations), execSync);
407
+ runWindowsEnvCommand(
408
+ buildWindowsEnvBatchScript(operations, { includeSettingChangeBroadcast: false }),
409
+ execSync,
410
+ { timeout: WINDOWS_ENV_COMMAND_TIMEOUT_MS }
411
+ );
412
+ broadcastWindowsSettingChange(execSync);
400
413
  }
401
414
 
402
415
  if (nextKeys.length > 0) {
@@ -421,21 +434,28 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
421
434
  };
422
435
  }
423
436
 
424
- function runWindowsEnvCommand(script, execSync) {
437
+ function runWindowsEnvCommand(script, execSync, options = {}) {
438
+ const timeout = Number(options.timeout) > 0
439
+ ? Number(options.timeout)
440
+ : WINDOWS_ENV_COMMAND_TIMEOUT_MS;
441
+ const ignoreErrors = options.ignoreErrors === true;
425
442
  const candidates = ['powershell', 'pwsh'];
426
443
  let lastError = null;
427
444
  for (const command of candidates) {
428
445
  try {
429
446
  execSync(command, ['-NoProfile', '-NonInteractive', '-Command', script], {
430
447
  stdio: ['ignore', 'ignore', 'ignore'],
431
- timeout: 5000,
448
+ timeout,
432
449
  windowsHide: true
433
450
  });
434
- return;
451
+ return true;
435
452
  } catch (error) {
436
453
  lastError = error;
437
454
  }
438
455
  }
456
+ if (ignoreErrors) {
457
+ return false;
458
+ }
439
459
  throw lastError || new Error('No PowerShell executable available');
440
460
  }
441
461
 
@@ -348,20 +348,7 @@ function notify(mode, message) {
348
348
  const command = 'powershell -NoProfile -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
349
349
  execSync(command, { stdio: 'ignore', windowsHide: true })
350
350
  } else {
351
- const toastXml = '<toast><visual><binding template="ToastGeneric"><text>' +
352
- escapeForXml(title) + '</text><text>' + escapeForXml(message) +
353
- '</text></binding></visual><audio src="ms-winsoundevent:Notification.Default"/></toast>'
354
- const ps = 'try { ' +
355
- '[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; ' +
356
- '[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] > $null; ' +
357
- '$xml = New-Object Windows.Data.Xml.Dom.XmlDocument; ' +
358
- '$xml.LoadXml(\\'' + toastXml.replace(/'/g, "''") + '\\'); ' +
359
- '$toast = [Windows.UI.Notifications.ToastNotification]::new($xml); ' +
360
- "[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Coding Tool').Show($toast) " +
361
- "} catch { $wshell = New-Object -ComObject Wscript.Shell; $wshell.Popup('" +
362
- escapeForPowerShellSingleQuote(message) + "', 5, '" + escapeForPowerShellSingleQuote(title) + "', 0x40) }"
363
- const command = 'powershell -NoProfile -Command ' + JSON.stringify(ps) + ' || ' + popupCommand
364
- execSync(command, { stdio: 'ignore', windowsHide: true })
351
+ execSync(popupCommand, { stdio: 'ignore', windowsHide: true })
365
352
  }
366
353
  return
367
354
  }
@@ -912,9 +899,7 @@ function generateSystemNotificationCommand(type, message, platformOverride = os.
912
899
  if (normalizedType === 'dialog') {
913
900
  return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('${escapeForPowerShellSingleQuote(message)}', '${escapeForPowerShellSingleQuote(title)}', 'OK', 'Information')" || ${popupCommand}`;
914
901
  }
915
-
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>`;
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}`;
902
+ return popupCommand;
918
903
  }
919
904
 
920
905
  if (normalizedType === 'dialog') {