coding-tool-x 3.4.9 → 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.9",
3
+ "version": "3.4.10",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -101,7 +101,7 @@ function generateSystemNotificationCommand(type, platformOverride = platform) {
101
101
  } else if (platformOverride === 'win32') {
102
102
  // Windows
103
103
  if (type === 'dialog') {
104
- return `powershell -NoProfile -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')" || ${buildWindowsPopupCommand()}`;
104
+ return `powershell -Command "Add-Type -AssemblyName PresentationFramework; [System.Windows.MessageBox]::Show('Claude Code 任务已完成 | 等待交互', 'Coding Tool', 'OK', 'Information')" || ${buildWindowsPopupCommand()}`;
105
105
  } else {
106
106
  return buildWindowsPopupCommand();
107
107
  }
@@ -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,11 +48,11 @@ 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
 
52
- function buildWindowsEnvBatchScript(operations = [], { includeSettingChangeBroadcast = false } = {}) {
55
+ function buildWindowsEnvBatchScript(operations = [], { includeSettingChangeBroadcast = true } = {}) {
53
56
  const normalizedOperations = Array.isArray(operations) ? operations.filter(Boolean) : [];
54
57
  const lines = normalizedOperations.map((operation) => {
55
58
  const key = powershellQuote(operation.key || '');
@@ -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) {
@@ -398,14 +406,10 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
398
406
  if (changed) {
399
407
  runWindowsEnvCommand(
400
408
  buildWindowsEnvBatchScript(operations, { includeSettingChangeBroadcast: false }),
401
- execSync
409
+ execSync,
410
+ { timeout: WINDOWS_ENV_COMMAND_TIMEOUT_MS }
402
411
  );
403
- try {
404
- // WM_SETTINGCHANGE 只是帮助已打开的应用刷新环境变量,失败不应让主流程报错。
405
- broadcastWindowsSettingChange(execSync);
406
- } catch {
407
- // ignore broadcast failures; registry writes are already durable
408
- }
412
+ broadcastWindowsSettingChange(execSync);
409
413
  }
410
414
 
411
415
  if (nextKeys.length > 0) {
@@ -430,21 +434,28 @@ function syncWindowsEnvironment(nextValues, previousState, options) {
430
434
  };
431
435
  }
432
436
 
433
- 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;
434
442
  const candidates = ['powershell', 'pwsh'];
435
443
  let lastError = null;
436
444
  for (const command of candidates) {
437
445
  try {
438
446
  execSync(command, ['-NoProfile', '-NonInteractive', '-Command', script], {
439
447
  stdio: ['ignore', 'ignore', 'ignore'],
440
- timeout: 5000,
448
+ timeout,
441
449
  windowsHide: true
442
450
  });
443
- return;
451
+ return true;
444
452
  } catch (error) {
445
453
  lastError = error;
446
454
  }
447
455
  }
456
+ if (ignoreErrors) {
457
+ return false;
458
+ }
448
459
  throw lastError || new Error('No PowerShell executable available');
449
460
  }
450
461