claude-code-workflow 6.3.25 → 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 (66) hide show
  1. package/.claude/commands/issue/discover-by-prompt.md +764 -0
  2. package/ccw/dist/core/routes/help-routes.d.ts.map +1 -1
  3. package/ccw/dist/core/routes/help-routes.js +43 -7
  4. package/ccw/dist/core/routes/help-routes.js.map +1 -1
  5. package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
  6. package/ccw/dist/core/routes/litellm-api-routes.js +31 -5
  7. package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
  8. package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
  9. package/ccw/dist/core/routes/memory-routes.js +73 -0
  10. package/ccw/dist/core/routes/memory-routes.js.map +1 -1
  11. package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
  12. package/ccw/dist/core/routes/status-routes.js +36 -4
  13. package/ccw/dist/core/routes/status-routes.js.map +1 -1
  14. package/ccw/dist/core/server.d.ts.map +1 -1
  15. package/ccw/dist/core/server.js +58 -0
  16. package/ccw/dist/core/server.js.map +1 -1
  17. package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
  18. package/ccw/dist/core/services/api-key-tester.js +8 -3
  19. package/ccw/dist/core/services/api-key-tester.js.map +1 -1
  20. package/ccw/dist/tools/claude-cli-tools.d.ts +7 -0
  21. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  22. package/ccw/dist/tools/claude-cli-tools.js +11 -1
  23. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  24. package/ccw/dist/tools/cli-executor-core.d.ts +11 -0
  25. package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
  26. package/ccw/dist/tools/cli-executor-core.js +89 -2
  27. package/ccw/dist/tools/cli-executor-core.js.map +1 -1
  28. package/ccw/dist/tools/codex-lens.d.ts +2 -1
  29. package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
  30. package/ccw/dist/tools/codex-lens.js +51 -8
  31. package/ccw/dist/tools/codex-lens.js.map +1 -1
  32. package/ccw/dist/tools/index.d.ts.map +1 -1
  33. package/ccw/dist/tools/index.js +2 -0
  34. package/ccw/dist/tools/index.js.map +1 -1
  35. package/ccw/dist/tools/litellm-client.d.ts +6 -0
  36. package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
  37. package/ccw/dist/tools/litellm-client.js +22 -1
  38. package/ccw/dist/tools/litellm-client.js.map +1 -1
  39. package/ccw/dist/tools/litellm-executor.js +2 -2
  40. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  41. package/ccw/dist/tools/memory-update-queue.d.ts +172 -0
  42. package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -0
  43. package/ccw/dist/tools/memory-update-queue.js +431 -0
  44. package/ccw/dist/tools/memory-update-queue.js.map +1 -0
  45. package/ccw/src/core/routes/help-routes.ts +46 -7
  46. package/ccw/src/core/routes/litellm-api-routes.ts +35 -4
  47. package/ccw/src/core/routes/memory-routes.ts +84 -0
  48. package/ccw/src/core/routes/status-routes.ts +39 -4
  49. package/ccw/src/core/server.ts +62 -0
  50. package/ccw/src/core/services/api-key-tester.ts +9 -3
  51. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +45 -0
  52. package/ccw/src/templates/dashboard-js/components/cli-status.js +36 -5
  53. package/ccw/src/templates/dashboard-js/components/hook-manager.js +42 -81
  54. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +170 -28
  55. package/ccw/src/templates/dashboard-js/components/notifications.js +14 -4
  56. package/ccw/src/templates/dashboard-js/i18n.js +26 -0
  57. package/ccw/src/templates/dashboard-js/views/cli-manager.js +72 -2
  58. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +11 -1
  59. package/ccw/src/tools/claude-cli-tools.ts +17 -1
  60. package/ccw/src/tools/cli-executor-core.ts +103 -2
  61. package/ccw/src/tools/codex-lens.ts +63 -8
  62. package/ccw/src/tools/index.ts +2 -0
  63. package/ccw/src/tools/litellm-client.ts +25 -3
  64. package/ccw/src/tools/litellm-executor.ts +2 -2
  65. package/ccw/src/tools/memory-update-queue.js +499 -0
  66. package/package.json +1 -1
@@ -49,43 +49,17 @@ const HOOK_TEMPLATES = {
49
49
  description: 'Auto-update code index when files are written or edited',
50
50
  category: 'indexing'
51
51
  },
52
- 'memory-update-related': {
52
+ 'memory-update-queue': {
53
53
  event: 'Stop',
54
54
  matcher: '',
55
55
  command: 'bash',
56
- args: ['-c', 'ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\''],
57
- description: 'Update CLAUDE.md for changed modules when session ends',
56
+ args: ['-c', 'ccw tool exec memory_queue "{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"'],
57
+ description: 'Queue CLAUDE.md update when session ends (batched by threshold/timeout)',
58
58
  category: 'memory',
59
59
  configurable: true,
60
60
  config: {
61
- tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
62
- strategy: { type: 'select', options: ['related', 'single-layer'], default: 'related', label: 'Strategy' }
63
- }
64
- },
65
- 'memory-update-periodic': {
66
- event: 'PostToolUse',
67
- matcher: 'Write|Edit',
68
- command: 'bash',
69
- args: ['-c', 'INTERVAL=300; LAST_FILE="$HOME/.claude/.last_memory_update"; mkdir -p "$HOME/.claude"; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
70
- description: 'Periodically update CLAUDE.md (default: 5 min interval)',
71
- category: 'memory',
72
- configurable: true,
73
- config: {
74
- tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
75
- interval: { type: 'number', default: 300, min: 60, max: 3600, label: 'Interval (seconds)', step: 60 }
76
- }
77
- },
78
- 'memory-update-count-based': {
79
- event: 'PostToolUse',
80
- matcher: 'Write|Edit',
81
- command: 'bash',
82
- args: ['-c', 'THRESHOLD=10; COUNT_FILE="$HOME/.claude/.memory_update_count"; mkdir -p "$HOME/.claude"; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty" 2>/dev/null); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
83
- description: 'Update CLAUDE.md when file changes reach threshold (default: 10 files)',
84
- category: 'memory',
85
- configurable: true,
86
- config: {
87
- tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
88
- threshold: { type: 'number', default: 10, min: 3, max: 50, label: 'File count threshold', step: 1 }
61
+ threshold: { type: 'number', default: 5, min: 1, max: 20, label: 'Threshold (paths)', step: 1 },
62
+ timeout: { type: 'number', default: 300, min: 60, max: 1800, label: 'Timeout (seconds)', step: 60 }
89
63
  }
90
64
  },
91
65
  // SKILL Context Loader templates
@@ -210,33 +184,19 @@ const HOOK_TEMPLATES = {
210
184
  const WIZARD_TEMPLATES = {
211
185
  'memory-update': {
212
186
  name: 'Memory Update Hook',
213
- description: 'Automatically update CLAUDE.md documentation based on code changes',
187
+ description: 'Queue-based CLAUDE.md updates with configurable threshold and timeout',
214
188
  icon: 'brain',
215
189
  options: [
216
190
  {
217
- id: 'on-stop',
218
- name: 'On Session End',
219
- description: 'Update documentation when Claude session ends',
220
- templateId: 'memory-update-related'
221
- },
222
- {
223
- id: 'periodic',
224
- name: 'Periodic Update',
225
- description: 'Update documentation at regular intervals during session',
226
- templateId: 'memory-update-periodic'
227
- },
228
- {
229
- id: 'count-based',
230
- name: 'Count-Based Update',
231
- description: 'Update documentation when file changes reach threshold',
232
- templateId: 'memory-update-count-based'
191
+ id: 'queue',
192
+ name: 'Queue-Based Update',
193
+ description: 'Batch updates when threshold reached or timeout expires',
194
+ templateId: 'memory-update-queue'
233
195
  }
234
196
  ],
235
197
  configFields: [
236
- { key: 'tool', type: 'select', label: 'CLI Tool', options: ['gemini', 'qwen', 'codex'], default: 'gemini', description: 'Tool for documentation generation' },
237
- { key: 'interval', type: 'number', label: 'Interval (seconds)', default: 300, min: 60, max: 3600, step: 60, showFor: ['periodic'], description: 'Time between updates' },
238
- { key: 'threshold', type: 'number', label: 'File Count Threshold', default: 10, min: 3, max: 50, step: 1, showFor: ['count-based'], description: 'Number of file changes to trigger update' },
239
- { key: 'strategy', type: 'select', label: 'Update Strategy', options: ['related', 'single-layer'], default: 'related', description: 'Related: changed modules, Single-layer: current directory' }
198
+ { key: 'threshold', type: 'number', label: 'Threshold (paths)', default: 5, min: 1, max: 20, step: 1, description: 'Number of paths to trigger batch update' },
199
+ { key: 'timeout', type: 'number', label: 'Timeout (seconds)', default: 300, min: 60, max: 1800, step: 60, description: 'Auto-flush queue after this time' }
240
200
  ]
241
201
  },
242
202
  'skill-context': {
@@ -730,9 +690,7 @@ function renderWizardModalContent() {
730
690
  // Helper to get translated option names
731
691
  const getOptionName = (optId) => {
732
692
  if (wizardId === 'memory-update') {
733
- if (optId === 'on-stop') return t('hook.wizard.onSessionEnd');
734
- if (optId === 'periodic') return t('hook.wizard.periodicUpdate');
735
- if (optId === 'count-based') return t('hook.wizard.countBasedUpdate');
693
+ if (optId === 'queue') return t('hook.wizard.queueBasedUpdate') || 'Queue-Based Update';
736
694
  }
737
695
  if (wizardId === 'memory-setup') {
738
696
  if (optId === 'file-read') return t('hook.wizard.fileReadTracker');
@@ -748,9 +706,7 @@ function renderWizardModalContent() {
748
706
 
749
707
  const getOptionDesc = (optId) => {
750
708
  if (wizardId === 'memory-update') {
751
- if (optId === 'on-stop') return t('hook.wizard.onSessionEndDesc');
752
- if (optId === 'periodic') return t('hook.wizard.periodicUpdateDesc');
753
- if (optId === 'count-based') return t('hook.wizard.countBasedUpdateDesc');
709
+ if (optId === 'queue') return t('hook.wizard.queueBasedUpdateDesc') || 'Batch updates when threshold reached or timeout expires';
754
710
  }
755
711
  if (wizardId === 'memory-setup') {
756
712
  if (optId === 'file-read') return t('hook.wizard.fileReadTrackerDesc');
@@ -767,20 +723,16 @@ function renderWizardModalContent() {
767
723
  // Helper to get translated field labels
768
724
  const getFieldLabel = (fieldKey) => {
769
725
  const labels = {
770
- 'tool': t('hook.wizard.cliTool'),
771
- 'interval': t('hook.wizard.intervalSeconds'),
772
- 'threshold': t('hook.wizard.fileCountThreshold'),
773
- 'strategy': t('hook.wizard.updateStrategy')
726
+ 'threshold': t('hook.wizard.thresholdPaths') || 'Threshold (paths)',
727
+ 'timeout': t('hook.wizard.timeoutSeconds') || 'Timeout (seconds)'
774
728
  };
775
729
  return labels[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.label || fieldKey;
776
730
  };
777
731
 
778
732
  const getFieldDesc = (fieldKey) => {
779
733
  const descs = {
780
- 'tool': t('hook.wizard.toolForDocGen'),
781
- 'interval': t('hook.wizard.timeBetweenUpdates'),
782
- 'threshold': t('hook.wizard.fileCountThresholdDesc'),
783
- 'strategy': t('hook.wizard.relatedStrategy')
734
+ 'threshold': t('hook.wizard.thresholdPathsDesc') || 'Number of paths to trigger batch update',
735
+ 'timeout': t('hook.wizard.timeoutSecondsDesc') || 'Auto-flush queue after this time'
784
736
  };
785
737
  return descs[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.description || '';
786
738
  };
@@ -1154,21 +1106,10 @@ function generateWizardCommand() {
1154
1106
  }
1155
1107
 
1156
1108
  // Handle memory-update wizard (default)
1157
- const tool = wizardConfig.tool || 'gemini';
1158
- const strategy = wizardConfig.strategy || 'related';
1159
- const interval = wizardConfig.interval || 300;
1160
- const threshold = wizardConfig.threshold || 10;
1161
-
1162
- // Build the ccw tool command based on configuration
1163
- const params = JSON.stringify({ strategy, tool });
1164
-
1165
- if (triggerType === 'periodic') {
1166
- return `INTERVAL=${interval}; LAST_FILE="$HOME/.claude/.last_memory_update"; mkdir -p "$HOME/.claude"; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE" 2>/dev/null || echo 0); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
1167
- } else if (triggerType === 'count-based') {
1168
- return `THRESHOLD=${threshold}; COUNT_FILE="$HOME/.claude/.memory_update_count"; mkdir -p "$HOME/.claude"; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty" 2>/dev/null); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
1169
- } else {
1170
- return `ccw tool exec update_module_claude '${params}'`;
1171
- }
1109
+ // Now uses memory_queue for batched updates with configurable threshold/timeout
1110
+ // The command adds to queue, configuration is applied separately via submitHookWizard
1111
+ const params = `"{\\"action\\":\\"add\\",\\"path\\":\\"$CLAUDE_PROJECT_DIR\\"}"`;
1112
+ return `ccw tool exec memory_queue ${params}`;
1172
1113
  }
1173
1114
 
1174
1115
  async function submitHookWizard() {
@@ -1263,6 +1204,26 @@ async function submitHookWizard() {
1263
1204
  }
1264
1205
 
1265
1206
  await saveHook(scope, baseTemplate.event, hookData);
1207
+
1208
+ // For memory-update wizard, also configure queue settings
1209
+ if (wizard.id === 'memory-update') {
1210
+ const threshold = wizardConfig.threshold || 5;
1211
+ const timeout = wizardConfig.timeout || 300;
1212
+ try {
1213
+ const configParams = JSON.stringify({ action: 'configure', threshold, timeout });
1214
+ const response = await fetch('/api/tools/execute', {
1215
+ method: 'POST',
1216
+ headers: { 'Content-Type': 'application/json' },
1217
+ body: JSON.stringify({ tool: 'memory_queue', params: configParams })
1218
+ });
1219
+ if (response.ok) {
1220
+ showRefreshToast(`Queue configured: threshold=${threshold}, timeout=${timeout}s`, 'success');
1221
+ }
1222
+ } catch (e) {
1223
+ console.warn('Failed to configure memory queue:', e);
1224
+ }
1225
+ }
1226
+
1266
1227
  closeHookWizardModal();
1267
1228
  }
1268
1229
 
@@ -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
  }