claude-code-workflow 6.3.24 → 6.3.26

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 (75) hide show
  1. package/.claude/commands/issue/discover-by-prompt.md +764 -0
  2. package/.claude/skills/text-formatter/SKILL.md +196 -0
  3. package/.claude/skills/text-formatter/phases/01-input-collection.md +111 -0
  4. package/.claude/skills/text-formatter/phases/02-content-analysis.md +248 -0
  5. package/.claude/skills/text-formatter/phases/03-format-transform.md +245 -0
  6. package/.claude/skills/text-formatter/phases/04-output-preview.md +183 -0
  7. package/.claude/skills/text-formatter/specs/callout-types.md +293 -0
  8. package/.claude/skills/text-formatter/specs/element-mapping.md +226 -0
  9. package/.claude/skills/text-formatter/specs/format-rules.md +273 -0
  10. package/.claude/skills/text-formatter/templates/bbcode-template.md +350 -0
  11. package/ccw/dist/core/routes/help-routes.d.ts.map +1 -1
  12. package/ccw/dist/core/routes/help-routes.js +43 -7
  13. package/ccw/dist/core/routes/help-routes.js.map +1 -1
  14. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
  15. package/ccw/dist/core/routes/litellm-api-routes.js +31 -5
  16. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
  17. package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
  18. package/ccw/dist/core/routes/memory-routes.js +73 -0
  19. package/ccw/dist/core/routes/memory-routes.js.map +1 -1
  20. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  21. package/ccw/dist/core/routes/status-routes.js +36 -4
  22. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  23. package/ccw/dist/core/server.d.ts.map +1 -1
  24. package/ccw/dist/core/server.js +58 -0
  25. package/ccw/dist/core/server.js.map +1 -1
  26. package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
  27. package/ccw/dist/core/services/api-key-tester.js +8 -3
  28. package/ccw/dist/core/services/api-key-tester.js.map +1 -1
  29. package/ccw/dist/tools/claude-cli-tools.d.ts +7 -0
  30. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  31. package/ccw/dist/tools/claude-cli-tools.js +11 -1
  32. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  33. package/ccw/dist/tools/cli-executor-core.d.ts +11 -0
  34. package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
  35. package/ccw/dist/tools/cli-executor-core.js +89 -2
  36. package/ccw/dist/tools/cli-executor-core.js.map +1 -1
  37. package/ccw/dist/tools/codex-lens.d.ts +2 -1
  38. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  39. package/ccw/dist/tools/codex-lens.js +51 -8
  40. package/ccw/dist/tools/codex-lens.js.map +1 -1
  41. package/ccw/dist/tools/index.d.ts.map +1 -1
  42. package/ccw/dist/tools/index.js +2 -0
  43. package/ccw/dist/tools/index.js.map +1 -1
  44. package/ccw/dist/tools/litellm-client.d.ts +6 -0
  45. package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
  46. package/ccw/dist/tools/litellm-client.js +22 -1
  47. package/ccw/dist/tools/litellm-client.js.map +1 -1
  48. package/ccw/dist/tools/litellm-executor.js +2 -2
  49. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  50. package/ccw/dist/tools/memory-update-queue.d.ts +172 -0
  51. package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -0
  52. package/ccw/dist/tools/memory-update-queue.js +431 -0
  53. package/ccw/dist/tools/memory-update-queue.js.map +1 -0
  54. package/ccw/src/core/routes/help-routes.ts +46 -7
  55. package/ccw/src/core/routes/litellm-api-routes.ts +35 -4
  56. package/ccw/src/core/routes/memory-routes.ts +84 -0
  57. package/ccw/src/core/routes/status-routes.ts +39 -4
  58. package/ccw/src/core/server.ts +62 -0
  59. package/ccw/src/core/services/api-key-tester.ts +9 -3
  60. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +45 -0
  61. package/ccw/src/templates/dashboard-js/components/cli-status.js +36 -5
  62. package/ccw/src/templates/dashboard-js/components/hook-manager.js +42 -81
  63. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +170 -28
  64. package/ccw/src/templates/dashboard-js/components/notifications.js +14 -4
  65. package/ccw/src/templates/dashboard-js/i18n.js +26 -0
  66. package/ccw/src/templates/dashboard-js/views/cli-manager.js +72 -2
  67. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +11 -1
  68. package/ccw/src/tools/claude-cli-tools.ts +17 -1
  69. package/ccw/src/tools/cli-executor-core.ts +103 -2
  70. package/ccw/src/tools/codex-lens.ts +63 -8
  71. package/ccw/src/tools/index.ts +2 -0
  72. package/ccw/src/tools/litellm-client.ts +25 -3
  73. package/ccw/src/tools/litellm-executor.ts +2 -2
  74. package/ccw/src/tools/memory-update-queue.js +499 -0
  75. package/package.json +91 -91
@@ -84,6 +84,147 @@ function getCliMode() {
84
84
  return currentCliMode;
85
85
  }
86
86
 
87
+ // ========== Cross-Platform MCP Helpers ==========
88
+
89
+ /**
90
+ * Build cross-platform MCP server configuration
91
+ * On Windows, wraps npx/node/python commands with cmd /c for proper execution
92
+ * @param {string} command - The command to run (e.g., 'npx', 'node', 'python')
93
+ * @param {string[]} args - Command arguments
94
+ * @param {object} [options] - Additional options (env, type, etc.)
95
+ * @returns {object} MCP server configuration
96
+ */
97
+ function buildCrossPlatformMcpConfig(command, args = [], options = {}) {
98
+ const { env, type, ...rest } = options;
99
+
100
+ // Commands that need cmd /c wrapper on Windows
101
+ const windowsWrappedCommands = ['npx', 'npm', 'node', 'python', 'python3', 'pip', 'pip3', 'pnpm', 'yarn', 'bun'];
102
+ const needsWindowsWrapper = isWindowsPlatform && windowsWrappedCommands.includes(command.toLowerCase());
103
+
104
+ const config = needsWindowsWrapper
105
+ ? { command: 'cmd', args: ['/c', command, ...args] }
106
+ : { command, args };
107
+
108
+ // Add optional fields
109
+ if (type) config.type = type;
110
+ if (env && Object.keys(env).length > 0) config.env = env;
111
+ Object.assign(config, rest);
112
+
113
+ return config;
114
+ }
115
+
116
+ /**
117
+ * Check if MCP config needs Windows cmd /c wrapper
118
+ * @param {object} serverConfig - MCP server configuration
119
+ * @returns {object} { needsWrapper: boolean, command: string }
120
+ */
121
+ function checkWindowsMcpCompatibility(serverConfig) {
122
+ if (!isWindowsPlatform) return { needsWrapper: false };
123
+
124
+ const command = serverConfig.command?.toLowerCase() || '';
125
+ const windowsWrappedCommands = ['npx', 'npm', 'node', 'python', 'python3', 'pip', 'pip3', 'pnpm', 'yarn', 'bun'];
126
+
127
+ // Already wrapped with cmd
128
+ if (command === 'cmd') return { needsWrapper: false };
129
+
130
+ const needsWrapper = windowsWrappedCommands.includes(command);
131
+ return { needsWrapper, command: serverConfig.command };
132
+ }
133
+
134
+ /**
135
+ * Auto-fix MCP config for Windows platform
136
+ * @param {object} serverConfig - Original MCP server configuration
137
+ * @returns {object} Fixed configuration (or original if no fix needed)
138
+ */
139
+ function autoFixWindowsMcpConfig(serverConfig) {
140
+ const { needsWrapper, command } = checkWindowsMcpCompatibility(serverConfig);
141
+
142
+ if (!needsWrapper) return serverConfig;
143
+
144
+ // Create new config with cmd /c wrapper
145
+ const fixedConfig = {
146
+ ...serverConfig,
147
+ command: 'cmd',
148
+ args: ['/c', command, ...(serverConfig.args || [])]
149
+ };
150
+
151
+ return fixedConfig;
152
+ }
153
+
154
+ /**
155
+ * Show Windows compatibility warning for MCP config
156
+ * @param {string} serverName - Name of the MCP server
157
+ * @param {object} serverConfig - MCP server configuration
158
+ * @returns {Promise<boolean>} True if user confirms auto-fix, false to keep original
159
+ */
160
+ async function showWindowsMcpCompatibilityWarning(serverName, serverConfig) {
161
+ const { needsWrapper, command } = checkWindowsMcpCompatibility(serverConfig);
162
+
163
+ if (!needsWrapper) return false;
164
+
165
+ // Show warning toast with auto-fix option
166
+ const message = t('mcp.windows.compatibilityWarning', {
167
+ name: serverName,
168
+ command: command
169
+ });
170
+
171
+ return new Promise((resolve) => {
172
+ // Create custom toast with action buttons
173
+ const toastContainer = document.getElementById('refreshToast') || createToastContainer();
174
+ const toastId = `windows-mcp-warning-${Date.now()}`;
175
+
176
+ const toastHtml = `
177
+ <div id="${toastId}" class="fixed bottom-4 right-4 bg-warning text-warning-foreground p-4 rounded-lg shadow-lg max-w-md z-50 animate-slide-up">
178
+ <div class="flex items-start gap-3">
179
+ <i data-lucide="alert-triangle" class="w-5 h-5 flex-shrink-0 mt-0.5"></i>
180
+ <div class="flex-1">
181
+ <p class="font-medium mb-2">${t('mcp.windows.title')}</p>
182
+ <p class="text-sm opacity-90 mb-3">${message}</p>
183
+ <div class="flex gap-2">
184
+ <button class="px-3 py-1.5 text-sm bg-background text-foreground rounded hover:opacity-90"
185
+ onclick="document.getElementById('${toastId}').remove(); window._mcpWindowsResolve && window._mcpWindowsResolve(true)">
186
+ ${t('mcp.windows.autoFix')}
187
+ </button>
188
+ <button class="px-3 py-1.5 text-sm border border-current rounded hover:opacity-90"
189
+ onclick="document.getElementById('${toastId}').remove(); window._mcpWindowsResolve && window._mcpWindowsResolve(false)">
190
+ ${t('mcp.windows.keepOriginal')}
191
+ </button>
192
+ </div>
193
+ </div>
194
+ <button onclick="document.getElementById('${toastId}').remove(); window._mcpWindowsResolve && window._mcpWindowsResolve(false)"
195
+ class="text-current opacity-70 hover:opacity-100">
196
+ <i data-lucide="x" class="w-4 h-4"></i>
197
+ </button>
198
+ </div>
199
+ </div>
200
+ `;
201
+
202
+ // Store resolve function globally for button clicks
203
+ window._mcpWindowsResolve = (result) => {
204
+ delete window._mcpWindowsResolve;
205
+ resolve(result);
206
+ };
207
+
208
+ document.body.insertAdjacentHTML('beforeend', toastHtml);
209
+
210
+ // Initialize icons
211
+ if (typeof lucide !== 'undefined') {
212
+ lucide.createIcons();
213
+ }
214
+
215
+ // Auto-dismiss after 15 seconds (keep original)
216
+ setTimeout(() => {
217
+ const toast = document.getElementById(toastId);
218
+ if (toast) {
219
+ toast.remove();
220
+ if (window._mcpWindowsResolve) {
221
+ window._mcpWindowsResolve(false);
222
+ }
223
+ }
224
+ }, 15000);
225
+ });
226
+ }
227
+
87
228
  // ========== Codex MCP Functions ==========
88
229
 
89
230
  /**
@@ -847,6 +988,19 @@ async function submitMcpCreateFromJson() {
847
988
  }
848
989
 
849
990
  async function createMcpServerWithConfig(name, serverConfig, scope = 'project') {
991
+ // Check Windows compatibility and offer auto-fix if needed
992
+ const { needsWrapper } = checkWindowsMcpCompatibility(serverConfig);
993
+ let finalConfig = serverConfig;
994
+
995
+ if (needsWrapper) {
996
+ // Show warning and ask user whether to auto-fix
997
+ const shouldAutoFix = await showWindowsMcpCompatibilityWarning(name, serverConfig);
998
+ if (shouldAutoFix) {
999
+ finalConfig = autoFixWindowsMcpConfig(serverConfig);
1000
+ console.log('[MCP] Auto-fixed config for Windows:', finalConfig);
1001
+ }
1002
+ }
1003
+
850
1004
  // Submit to API
851
1005
  try {
852
1006
  let response;
@@ -859,7 +1013,7 @@ async function createMcpServerWithConfig(name, serverConfig, scope = 'project')
859
1013
  headers: { 'Content-Type': 'application/json' },
860
1014
  body: JSON.stringify({
861
1015
  serverName: name,
862
- serverConfig: serverConfig
1016
+ serverConfig: finalConfig
863
1017
  })
864
1018
  });
865
1019
  scopeLabel = 'Codex';
@@ -869,7 +1023,7 @@ async function createMcpServerWithConfig(name, serverConfig, scope = 'project')
869
1023
  headers: { 'Content-Type': 'application/json' },
870
1024
  body: JSON.stringify({
871
1025
  serverName: name,
872
- serverConfig: serverConfig
1026
+ serverConfig: finalConfig
873
1027
  })
874
1028
  });
875
1029
  scopeLabel = 'global';
@@ -880,7 +1034,7 @@ async function createMcpServerWithConfig(name, serverConfig, scope = 'project')
880
1034
  body: JSON.stringify({
881
1035
  projectPath: projectPath,
882
1036
  serverName: name,
883
- serverConfig: serverConfig
1037
+ serverConfig: finalConfig
884
1038
  })
885
1039
  });
886
1040
  scopeLabel = 'project';
@@ -1231,16 +1385,14 @@ const RECOMMENDED_MCP_SERVERS = [
1231
1385
  descKey: 'mcp.ace-tool.field.token.desc'
1232
1386
  }
1233
1387
  ],
1234
- buildConfig: (values) => ({
1235
- command: 'npx',
1236
- args: [
1237
- 'ace-tool',
1238
- '--base-url',
1239
- values.baseUrl || 'https://acemcp.heroman.wtf/relay/',
1240
- '--token',
1241
- values.token
1242
- ]
1243
- })
1388
+ // Uses buildCrossPlatformMcpConfig for automatic Windows cmd /c wrapping
1389
+ buildConfig: (values) => buildCrossPlatformMcpConfig('npx', [
1390
+ 'ace-tool',
1391
+ '--base-url',
1392
+ values.baseUrl || 'https://acemcp.heroman.wtf/relay/',
1393
+ '--token',
1394
+ values.token
1395
+ ])
1244
1396
  },
1245
1397
  {
1246
1398
  id: 'chrome-devtools',
@@ -1249,12 +1401,8 @@ const RECOMMENDED_MCP_SERVERS = [
1249
1401
  icon: 'chrome',
1250
1402
  category: 'browser',
1251
1403
  fields: [],
1252
- buildConfig: () => ({
1253
- type: 'stdio',
1254
- command: 'npx',
1255
- args: ['chrome-devtools-mcp@latest'],
1256
- env: {}
1257
- })
1404
+ // Uses buildCrossPlatformMcpConfig for automatic Windows cmd /c wrapping
1405
+ buildConfig: () => buildCrossPlatformMcpConfig('npx', ['chrome-devtools-mcp@latest'], { type: 'stdio' })
1258
1406
  },
1259
1407
  {
1260
1408
  id: 'exa',
@@ -1273,16 +1421,10 @@ const RECOMMENDED_MCP_SERVERS = [
1273
1421
  descKey: 'mcp.exa.field.apiKey.desc'
1274
1422
  }
1275
1423
  ],
1424
+ // Uses buildCrossPlatformMcpConfig for automatic Windows cmd /c wrapping
1276
1425
  buildConfig: (values) => {
1277
- const config = {
1278
- command: 'npx',
1279
- args: ['-y', 'exa-mcp-server']
1280
- };
1281
- // Only add env if API key is provided
1282
- if (values.apiKey) {
1283
- config.env = { EXA_API_KEY: values.apiKey };
1284
- }
1285
- return config;
1426
+ const env = values.apiKey ? { EXA_API_KEY: values.apiKey } : undefined;
1427
+ return buildCrossPlatformMcpConfig('npx', ['-y', 'exa-mcp-server'], { env });
1286
1428
  }
1287
1429
  }
1288
1430
  ];
@@ -415,10 +415,15 @@ function handleNotification(data) {
415
415
  'CodexLens'
416
416
  );
417
417
  }
418
- // Invalidate CodexLens page cache to ensure fresh data on next visit
418
+ // Invalidate all CodexLens related caches to ensure fresh data on refresh
419
+ // Must clear both codexlens-specific cache AND global status cache
420
+ if (window.cacheManager) {
421
+ window.cacheManager.invalidate('all-status');
422
+ window.cacheManager.invalidate('dashboard-init');
423
+ }
419
424
  if (typeof window.invalidateCodexLensCache === 'function') {
420
425
  window.invalidateCodexLensCache();
421
- console.log('[CodexLens] Cache invalidated after installation');
426
+ console.log('[CodexLens] All caches invalidated after installation');
422
427
  }
423
428
  // Refresh CLI status if active
424
429
  if (typeof loadCodexLensStatus === 'function') {
@@ -443,10 +448,15 @@ function handleNotification(data) {
443
448
  'CodexLens'
444
449
  );
445
450
  }
446
- // Invalidate CodexLens page cache to ensure fresh data on next visit
451
+ // Invalidate all CodexLens related caches to ensure fresh data on refresh
452
+ // Must clear both codexlens-specific cache AND global status cache
453
+ if (window.cacheManager) {
454
+ window.cacheManager.invalidate('all-status');
455
+ window.cacheManager.invalidate('dashboard-init');
456
+ }
447
457
  if (typeof window.invalidateCodexLensCache === 'function') {
448
458
  window.invalidateCodexLensCache();
449
- console.log('[CodexLens] Cache invalidated after uninstallation');
459
+ console.log('[CodexLens] All caches invalidated after uninstallation');
450
460
  }
451
461
  // Refresh CLI status if active
452
462
  if (typeof loadCodexLensStatus === 'function') {
@@ -261,6 +261,13 @@ const i18n = {
261
261
  'cli.wrapper': 'Wrapper',
262
262
  'cli.customClaudeSettings': 'Custom Claude CLI settings',
263
263
  'cli.updateFailed': 'Failed to update',
264
+
265
+ // CLI Tool Config - Environment File
266
+ 'cli.envFile': 'Environment File',
267
+ 'cli.envFileOptional': '(optional)',
268
+ 'cli.envFilePlaceholder': 'Path to .env file (e.g., ~/.gemini-env or C:/Users/xxx/.env)',
269
+ 'cli.envFileHint': 'Load environment variables (e.g., API keys) before CLI execution. Supports ~ for home directory.',
270
+ 'cli.envFileBrowse': 'Browse',
264
271
 
265
272
  // CodexLens Configuration
266
273
  'codexlens.config': 'CodexLens Configuration',
@@ -995,6 +1002,12 @@ const i18n = {
995
1002
  'mcp.clickToEdit': 'Click to edit',
996
1003
  'mcp.clickToViewDetails': 'Click to view details',
997
1004
 
1005
+ // Windows MCP Compatibility
1006
+ 'mcp.windows.title': 'Windows Compatibility Warning',
1007
+ 'mcp.windows.compatibilityWarning': 'The MCP server "{name}" uses "{command}" which requires "cmd /c" wrapper on Windows to work properly with Claude Code.',
1008
+ 'mcp.windows.autoFix': 'Auto-fix (Recommended)',
1009
+ 'mcp.windows.keepOriginal': 'Keep Original',
1010
+
998
1011
  // Hook Manager
999
1012
  'hook.projectHooks': 'Project Hooks',
1000
1013
  'hook.projectFile': '.claude/settings.json',
@@ -2415,6 +2428,13 @@ const i18n = {
2415
2428
  'cli.wrapper': '封装',
2416
2429
  'cli.customClaudeSettings': '自定义 Claude CLI 配置',
2417
2430
  'cli.updateFailed': '更新失败',
2431
+
2432
+ // CLI 工具配置 - 环境文件
2433
+ 'cli.envFile': '环境文件',
2434
+ 'cli.envFileOptional': '(可选)',
2435
+ 'cli.envFilePlaceholder': '.env 文件路径(如 ~/.gemini-env 或 C:/Users/xxx/.env)',
2436
+ 'cli.envFileHint': '在 CLI 执行前加载环境变量(如 API 密钥)。支持 ~ 表示用户目录。',
2437
+ 'cli.envFileBrowse': '浏览',
2418
2438
 
2419
2439
  // CodexLens 配置
2420
2440
  'codexlens.config': 'CodexLens 配置',
@@ -3128,6 +3148,12 @@ const i18n = {
3128
3148
  'mcp.clickToEdit': '点击编辑',
3129
3149
  'mcp.clickToViewDetails': '点击查看详情',
3130
3150
 
3151
+ // Windows MCP 兼容性
3152
+ 'mcp.windows.title': 'Windows 兼容性警告',
3153
+ 'mcp.windows.compatibilityWarning': 'MCP 服务器 "{name}" 使用的 "{command}" 命令需要在 Windows 上添加 "cmd /c" 包装才能与 Claude Code 正常工作。',
3154
+ 'mcp.windows.autoFix': '自动修复(推荐)',
3155
+ 'mcp.windows.keepOriginal': '保持原样',
3156
+
3131
3157
  // Hook Manager
3132
3158
  'hook.projectHooks': '项目钩子',
3133
3159
  'hook.projectFile': '.claude/settings.json',
@@ -523,6 +523,27 @@ function buildToolConfigModalContent(tool, config, models, status) {
523
523
  '</div>' +
524
524
  '</div>' +
525
525
 
526
+ // Environment File Section (only for builtin tools: gemini, qwen)
527
+ (tool === 'gemini' || tool === 'qwen' ? (
528
+ '<div class="tool-config-section">' +
529
+ '<h4><i data-lucide="file-key" class="w-3.5 h-3.5"></i> ' + t('cli.envFile') + ' <span class="text-muted">' + t('cli.envFileOptional') + '</span></h4>' +
530
+ '<div class="env-file-input-group">' +
531
+ '<div class="env-file-input-row">' +
532
+ '<input type="text" id="envFileInput" class="tool-config-input" ' +
533
+ 'placeholder="' + t('cli.envFilePlaceholder') + '" ' +
534
+ 'value="' + (config.envFile ? escapeHtml(config.envFile) : '') + '" />' +
535
+ '<button type="button" class="btn-sm btn-outline" id="envFileBrowseBtn">' +
536
+ '<i data-lucide="folder-open" class="w-3.5 h-3.5"></i> ' + t('cli.envFileBrowse') +
537
+ '</button>' +
538
+ '</div>' +
539
+ '<p class="env-file-hint">' +
540
+ '<i data-lucide="info" class="w-3 h-3"></i> ' +
541
+ t('cli.envFileHint') +
542
+ '</p>' +
543
+ '</div>' +
544
+ '</div>'
545
+ ) : '') +
546
+
526
547
  // Footer
527
548
  '<div class="tool-config-footer">' +
528
549
  '<button class="btn btn-outline" onclick="closeModal()">' + t('common.cancel') + '</button>' +
@@ -701,12 +722,23 @@ function initToolConfigModalEvents(tool, currentConfig, models) {
701
722
  return;
702
723
  }
703
724
 
725
+ // Get envFile value (only for gemini/qwen)
726
+ var envFileInput = document.getElementById('envFileInput');
727
+ var envFile = envFileInput ? envFileInput.value.trim() : '';
728
+
704
729
  try {
705
- await updateCliToolConfig(tool, {
730
+ var updateData = {
706
731
  primaryModel: primaryModel,
707
732
  secondaryModel: secondaryModel,
708
733
  tags: currentTags
709
- });
734
+ };
735
+
736
+ // Only include envFile for gemini/qwen tools
737
+ if (tool === 'gemini' || tool === 'qwen') {
738
+ updateData.envFile = envFile || null;
739
+ }
740
+
741
+ await updateCliToolConfig(tool, updateData);
710
742
  // Reload config to reflect changes
711
743
  await loadCliToolConfig();
712
744
  showRefreshToast('Configuration saved', 'success');
@@ -719,6 +751,44 @@ function initToolConfigModalEvents(tool, currentConfig, models) {
719
751
  };
720
752
  }
721
753
 
754
+ // Environment file browse button (only for gemini/qwen)
755
+ var envFileBrowseBtn = document.getElementById('envFileBrowseBtn');
756
+ if (envFileBrowseBtn) {
757
+ envFileBrowseBtn.onclick = async function() {
758
+ try {
759
+ // Use file dialog API if available
760
+ var response = await fetch('/api/dialog/open-file', {
761
+ method: 'POST',
762
+ headers: { 'Content-Type': 'application/json' },
763
+ body: JSON.stringify({
764
+ title: t('cli.envFile'),
765
+ filters: [
766
+ { name: 'Environment Files', extensions: ['env'] },
767
+ { name: 'All Files', extensions: ['*'] }
768
+ ],
769
+ defaultPath: ''
770
+ })
771
+ });
772
+
773
+ if (response.ok) {
774
+ var data = await response.json();
775
+ if (data.filePath) {
776
+ var envFileInput = document.getElementById('envFileInput');
777
+ if (envFileInput) {
778
+ envFileInput.value = data.filePath;
779
+ }
780
+ }
781
+ } else {
782
+ // Fallback: prompt user to enter path manually
783
+ showRefreshToast('File dialog not available. Please enter path manually.', 'info');
784
+ }
785
+ } catch (err) {
786
+ console.error('Failed to open file dialog:', err);
787
+ showRefreshToast('File dialog not available. Please enter path manually.', 'info');
788
+ }
789
+ };
790
+ }
791
+
722
792
  // Initialize lucide icons in modal
723
793
  if (window.lucide) lucide.createIcons();
724
794
  }
@@ -72,6 +72,10 @@ function invalidateCache(key) {
72
72
  Object.values(CACHE_KEY_MAP).forEach(function(k) {
73
73
  window.cacheManager.invalidate(k);
74
74
  });
75
+ // 重要:同时清理包含 CodexLens 状态的全局缓存
76
+ // 这些缓存在 cli-status.js 中使用,包含 codexLens.ready 状态
77
+ window.cacheManager.invalidate('all-status');
78
+ window.cacheManager.invalidate('dashboard-init');
75
79
  }
76
80
  }
77
81
 
@@ -788,6 +792,12 @@ function initCodexLensConfigEvents(currentConfig) {
788
792
 
789
793
  if (result.success) {
790
794
  showRefreshToast(t('codexlens.configSaved'), 'success');
795
+
796
+ // Invalidate config cache to ensure fresh data on next load
797
+ if (window.cacheManager) {
798
+ window.cacheManager.invalidate('codexlens-config');
799
+ }
800
+
791
801
  closeModal();
792
802
 
793
803
  // Refresh CodexLens status
@@ -5385,7 +5395,7 @@ function initCodexLensManagerPageEvents(currentConfig) {
5385
5395
  try {
5386
5396
  var response = await csrfFetch('/api/codexlens/config', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ index_dir: newIndexDir }) });
5387
5397
  var result = await response.json();
5388
- if (result.success) { showRefreshToast(t('codexlens.configSaved'), 'success'); renderCodexLensManager(); }
5398
+ if (result.success) { if (window.cacheManager) { window.cacheManager.invalidate('codexlens-config'); } showRefreshToast(t('codexlens.configSaved'), 'success'); renderCodexLensManager(); }
5389
5399
  else { showRefreshToast(t('common.saveFailed') + ': ' + result.error, 'error'); }
5390
5400
  } catch (err) { showRefreshToast(t('common.error') + ': ' + err.message, 'error'); }
5391
5401
  saveBtn.disabled = false;
@@ -34,6 +34,11 @@ export interface ClaudeCliTool {
34
34
  * Used to lookup endpoint configuration in litellm-api-config.json
35
35
  */
36
36
  id?: string;
37
+ /**
38
+ * Path to .env file for loading environment variables before CLI execution
39
+ * Supports both absolute paths and paths relative to home directory (e.g., ~/.my-env)
40
+ */
41
+ envFile?: string;
37
42
  }
38
43
 
39
44
  export type CliToolName = 'gemini' | 'qwen' | 'codex' | 'claude' | 'opencode' | string;
@@ -808,6 +813,7 @@ export function getToolConfig(projectDir: string, tool: string): {
808
813
  primaryModel: string;
809
814
  secondaryModel: string;
810
815
  tags?: string[];
816
+ envFile?: string;
811
817
  } {
812
818
  const config = loadClaudeCliTools(projectDir);
813
819
  const toolConfig = config.tools[tool];
@@ -826,7 +832,8 @@ export function getToolConfig(projectDir: string, tool: string): {
826
832
  enabled: toolConfig.enabled,
827
833
  primaryModel: toolConfig.primaryModel ?? '',
828
834
  secondaryModel: toolConfig.secondaryModel ?? '',
829
- tags: toolConfig.tags
835
+ tags: toolConfig.tags,
836
+ envFile: toolConfig.envFile
830
837
  };
831
838
  }
832
839
 
@@ -841,6 +848,7 @@ export function updateToolConfig(
841
848
  primaryModel: string;
842
849
  secondaryModel: string;
843
850
  tags: string[];
851
+ envFile: string | null;
844
852
  }>
845
853
  ): ClaudeCliToolsConfig {
846
854
  const config = loadClaudeCliTools(projectDir);
@@ -858,6 +866,14 @@ export function updateToolConfig(
858
866
  if (updates.tags !== undefined) {
859
867
  config.tools[tool].tags = updates.tags;
860
868
  }
869
+ // Handle envFile: set to undefined if null/empty, otherwise set value
870
+ if (updates.envFile !== undefined) {
871
+ if (updates.envFile === null || updates.envFile === '') {
872
+ delete config.tools[tool].envFile;
873
+ } else {
874
+ config.tools[tool].envFile = updates.envFile;
875
+ }
876
+ }
861
877
  saveClaudeCliTools(projectDir, config);
862
878
  }
863
879