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.
- package/.claude/commands/issue/discover-by-prompt.md +764 -0
- package/ccw/dist/core/routes/help-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/help-routes.js +43 -7
- package/ccw/dist/core/routes/help-routes.js.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/litellm-api-routes.js +31 -5
- package/ccw/dist/core/routes/litellm-api-routes.js.map +1 -1
- package/ccw/dist/core/routes/memory-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/memory-routes.js +73 -0
- package/ccw/dist/core/routes/memory-routes.js.map +1 -1
- package/ccw/dist/core/routes/status-routes.d.ts.map +1 -1
- package/ccw/dist/core/routes/status-routes.js +36 -4
- package/ccw/dist/core/routes/status-routes.js.map +1 -1
- package/ccw/dist/core/server.d.ts.map +1 -1
- package/ccw/dist/core/server.js +58 -0
- package/ccw/dist/core/server.js.map +1 -1
- package/ccw/dist/core/services/api-key-tester.d.ts.map +1 -1
- package/ccw/dist/core/services/api-key-tester.js +8 -3
- package/ccw/dist/core/services/api-key-tester.js.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.d.ts +7 -0
- package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
- package/ccw/dist/tools/claude-cli-tools.js +11 -1
- package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
- package/ccw/dist/tools/cli-executor-core.d.ts +11 -0
- package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
- package/ccw/dist/tools/cli-executor-core.js +89 -2
- package/ccw/dist/tools/cli-executor-core.js.map +1 -1
- package/ccw/dist/tools/codex-lens.d.ts +2 -1
- package/ccw/dist/tools/codex-lens.d.ts.map +1 -1
- package/ccw/dist/tools/codex-lens.js +51 -8
- package/ccw/dist/tools/codex-lens.js.map +1 -1
- package/ccw/dist/tools/index.d.ts.map +1 -1
- package/ccw/dist/tools/index.js +2 -0
- package/ccw/dist/tools/index.js.map +1 -1
- package/ccw/dist/tools/litellm-client.d.ts +6 -0
- package/ccw/dist/tools/litellm-client.d.ts.map +1 -1
- package/ccw/dist/tools/litellm-client.js +22 -1
- package/ccw/dist/tools/litellm-client.js.map +1 -1
- package/ccw/dist/tools/litellm-executor.js +2 -2
- package/ccw/dist/tools/litellm-executor.js.map +1 -1
- package/ccw/dist/tools/memory-update-queue.d.ts +172 -0
- package/ccw/dist/tools/memory-update-queue.d.ts.map +1 -0
- package/ccw/dist/tools/memory-update-queue.js +431 -0
- package/ccw/dist/tools/memory-update-queue.js.map +1 -0
- package/ccw/src/core/routes/help-routes.ts +46 -7
- package/ccw/src/core/routes/litellm-api-routes.ts +35 -4
- package/ccw/src/core/routes/memory-routes.ts +84 -0
- package/ccw/src/core/routes/status-routes.ts +39 -4
- package/ccw/src/core/server.ts +62 -0
- package/ccw/src/core/services/api-key-tester.ts +9 -3
- package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +45 -0
- package/ccw/src/templates/dashboard-js/components/cli-status.js +36 -5
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +42 -81
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +170 -28
- package/ccw/src/templates/dashboard-js/components/notifications.js +14 -4
- package/ccw/src/templates/dashboard-js/i18n.js +26 -0
- package/ccw/src/templates/dashboard-js/views/cli-manager.js +72 -2
- package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +11 -1
- package/ccw/src/tools/claude-cli-tools.ts +17 -1
- package/ccw/src/tools/cli-executor-core.ts +103 -2
- package/ccw/src/tools/codex-lens.ts +63 -8
- package/ccw/src/tools/index.ts +2 -0
- package/ccw/src/tools/litellm-client.ts +25 -3
- package/ccw/src/tools/litellm-executor.ts +2 -2
- package/ccw/src/tools/memory-update-queue.js +499 -0
- 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-
|
|
52
|
+
'memory-update-queue': {
|
|
53
53
|
event: 'Stop',
|
|
54
54
|
matcher: '',
|
|
55
55
|
command: 'bash',
|
|
56
|
-
args: ['-c', 'ccw tool exec
|
|
57
|
-
description: '
|
|
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
|
-
|
|
62
|
-
|
|
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: '
|
|
187
|
+
description: 'Queue-based CLAUDE.md updates with configurable threshold and timeout',
|
|
214
188
|
icon: 'brain',
|
|
215
189
|
options: [
|
|
216
190
|
{
|
|
217
|
-
id: '
|
|
218
|
-
name: '
|
|
219
|
-
description: '
|
|
220
|
-
templateId: 'memory-update-
|
|
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: '
|
|
237
|
-
{ key: '
|
|
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 === '
|
|
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 === '
|
|
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
|
-
'
|
|
771
|
-
'
|
|
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
|
-
'
|
|
781
|
-
'
|
|
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
|
-
|
|
1158
|
-
|
|
1159
|
-
const
|
|
1160
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
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
|
-
|
|
1253
|
-
|
|
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
|
|
1278
|
-
|
|
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
|
|
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]
|
|
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
|
|
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]
|
|
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
|
-
|
|
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
|
}
|