@wonderwhy-er/desktop-commander 0.1.26 → 0.1.27
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/README.md +1 -1
- package/dist/command-manager.d.ts +2 -0
- package/dist/command-manager.js +126 -0
- package/dist/server.js +31 -17
- package/dist/setup-claude-server.js +62 -46
- package/dist/tools/execute.js +16 -3
- package/dist/utils.js +34 -16
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ First, ensure you've downloaded and installed the [Claude Desktop app](https://c
|
|
|
53
53
|
### Option 1: Install through npx
|
|
54
54
|
Just run this in terminal
|
|
55
55
|
```
|
|
56
|
-
npx @wonderwhy-er/desktop-commander setup
|
|
56
|
+
npx @wonderwhy-er/desktop-commander@latest setup
|
|
57
57
|
```
|
|
58
58
|
Restart Claude if running
|
|
59
59
|
|
|
@@ -3,6 +3,8 @@ declare class CommandManager {
|
|
|
3
3
|
loadBlockedCommands(): Promise<void>;
|
|
4
4
|
saveBlockedCommands(): Promise<void>;
|
|
5
5
|
getBaseCommand(command: string): string;
|
|
6
|
+
extractCommands(commandString: string): string[];
|
|
7
|
+
extractBaseCommand(commandStr: string): string | null;
|
|
6
8
|
validateCommand(command: string): boolean;
|
|
7
9
|
blockCommand(command: string): Promise<boolean>;
|
|
8
10
|
unblockCommand(command: string): Promise<boolean>;
|
package/dist/command-manager.js
CHANGED
|
@@ -28,6 +28,132 @@ class CommandManager {
|
|
|
28
28
|
getBaseCommand(command) {
|
|
29
29
|
return command.split(' ')[0].toLowerCase().trim();
|
|
30
30
|
}
|
|
31
|
+
extractCommands(commandString) {
|
|
32
|
+
try {
|
|
33
|
+
// Trim any leading/trailing whitespace
|
|
34
|
+
commandString = commandString.trim();
|
|
35
|
+
// Define command separators - these are the operators that can chain commands
|
|
36
|
+
const separators = [';', '&&', '||', '|', '&'];
|
|
37
|
+
// This will store our extracted commands
|
|
38
|
+
const commands = [];
|
|
39
|
+
// Split by common separators while preserving quotes
|
|
40
|
+
let inQuote = false;
|
|
41
|
+
let quoteChar = '';
|
|
42
|
+
let currentCmd = '';
|
|
43
|
+
let escaped = false;
|
|
44
|
+
for (let i = 0; i < commandString.length; i++) {
|
|
45
|
+
const char = commandString[i];
|
|
46
|
+
// Handle escape characters
|
|
47
|
+
if (char === '\\' && !escaped) {
|
|
48
|
+
escaped = true;
|
|
49
|
+
currentCmd += char;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
// If this character is escaped, just add it
|
|
53
|
+
if (escaped) {
|
|
54
|
+
escaped = false;
|
|
55
|
+
currentCmd += char;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// Handle quotes (both single and double)
|
|
59
|
+
if ((char === '"' || char === "'") && !inQuote) {
|
|
60
|
+
inQuote = true;
|
|
61
|
+
quoteChar = char;
|
|
62
|
+
currentCmd += char;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
else if (char === quoteChar && inQuote) {
|
|
66
|
+
inQuote = false;
|
|
67
|
+
quoteChar = '';
|
|
68
|
+
currentCmd += char;
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
// If we're inside quotes, just add the character
|
|
72
|
+
if (inQuote) {
|
|
73
|
+
currentCmd += char;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
// Handle subshells - if we see an opening parenthesis, we need to find its matching closing parenthesis
|
|
77
|
+
if (char === '(') {
|
|
78
|
+
// Find the matching closing parenthesis
|
|
79
|
+
let openParens = 1;
|
|
80
|
+
let j = i + 1;
|
|
81
|
+
while (j < commandString.length && openParens > 0) {
|
|
82
|
+
if (commandString[j] === '(')
|
|
83
|
+
openParens++;
|
|
84
|
+
if (commandString[j] === ')')
|
|
85
|
+
openParens--;
|
|
86
|
+
j++;
|
|
87
|
+
}
|
|
88
|
+
// Skip to after the closing parenthesis
|
|
89
|
+
if (j <= commandString.length) {
|
|
90
|
+
const subshellContent = commandString.substring(i + 1, j - 1);
|
|
91
|
+
// Recursively extract commands from the subshell
|
|
92
|
+
const subCommands = this.extractCommands(subshellContent);
|
|
93
|
+
commands.push(...subCommands);
|
|
94
|
+
// Move position past the subshell
|
|
95
|
+
i = j - 1;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Check for separators
|
|
100
|
+
let isSeparator = false;
|
|
101
|
+
for (const separator of separators) {
|
|
102
|
+
if (commandString.startsWith(separator, i)) {
|
|
103
|
+
// We found a separator - extract the command before it
|
|
104
|
+
if (currentCmd.trim()) {
|
|
105
|
+
const baseCommand = this.extractBaseCommand(currentCmd.trim());
|
|
106
|
+
if (baseCommand)
|
|
107
|
+
commands.push(baseCommand);
|
|
108
|
+
}
|
|
109
|
+
// Move past the separator
|
|
110
|
+
i += separator.length - 1;
|
|
111
|
+
currentCmd = '';
|
|
112
|
+
isSeparator = true;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!isSeparator) {
|
|
117
|
+
currentCmd += char;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Don't forget to add the last command
|
|
121
|
+
if (currentCmd.trim()) {
|
|
122
|
+
const baseCommand = this.extractBaseCommand(currentCmd.trim());
|
|
123
|
+
if (baseCommand)
|
|
124
|
+
commands.push(baseCommand);
|
|
125
|
+
}
|
|
126
|
+
// Remove duplicates and return
|
|
127
|
+
return [...new Set(commands)];
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// If anything goes wrong, log the error but return the basic command to not break execution
|
|
131
|
+
console.error('Error extracting commands:', error);
|
|
132
|
+
return [this.getBaseCommand(commandString)];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// This extracts the actual command name from a command string
|
|
136
|
+
extractBaseCommand(commandStr) {
|
|
137
|
+
try {
|
|
138
|
+
// Remove environment variables (patterns like KEY=value)
|
|
139
|
+
const withoutEnvVars = commandStr.replace(/\w+=\S+\s*/g, '').trim();
|
|
140
|
+
// If nothing remains after removing env vars, return null
|
|
141
|
+
if (!withoutEnvVars)
|
|
142
|
+
return null;
|
|
143
|
+
// Get the first token (the command)
|
|
144
|
+
const tokens = withoutEnvVars.split(/\s+/);
|
|
145
|
+
const firstToken = tokens[0];
|
|
146
|
+
// Check if it starts with special characters like (, $ that might indicate it's not a regular command
|
|
147
|
+
if (['(', '$'].includes(firstToken[0])) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
return firstToken.toLowerCase();
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
console.error('Error extracting base command:', error);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
31
157
|
validateCommand(command) {
|
|
32
158
|
const baseCommand = this.getBaseCommand(command);
|
|
33
159
|
return !this.blockedCommands.has(baseCommand);
|
package/dist/server.js
CHANGED
|
@@ -186,13 +186,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
186
186
|
return executeCommand(parsed);
|
|
187
187
|
}
|
|
188
188
|
case "read_output": {
|
|
189
|
-
const parsed = ReadOutputArgsSchema.parse(args);
|
|
190
189
|
capture('server_read_output');
|
|
190
|
+
const parsed = ReadOutputArgsSchema.parse(args);
|
|
191
191
|
return readOutput(parsed);
|
|
192
192
|
}
|
|
193
193
|
case "force_terminate": {
|
|
194
|
-
const parsed = ForceTerminateArgsSchema.parse(args);
|
|
195
194
|
capture('server_force_terminate');
|
|
195
|
+
const parsed = ForceTerminateArgsSchema.parse(args);
|
|
196
196
|
return forceTerminate(parsed);
|
|
197
197
|
}
|
|
198
198
|
case "list_sessions":
|
|
@@ -202,11 +202,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
202
202
|
capture('server_list_processes');
|
|
203
203
|
return listProcesses();
|
|
204
204
|
case "kill_process": {
|
|
205
|
-
const parsed = KillProcessArgsSchema.parse(args);
|
|
206
205
|
capture('server_kill_process');
|
|
206
|
+
const parsed = KillProcessArgsSchema.parse(args);
|
|
207
207
|
return killProcess(parsed);
|
|
208
208
|
}
|
|
209
209
|
case "block_command": {
|
|
210
|
+
capture('server_block_command');
|
|
210
211
|
const parsed = BlockCommandArgsSchema.parse(args);
|
|
211
212
|
const blockResult = await commandManager.blockCommand(parsed.command);
|
|
212
213
|
return {
|
|
@@ -214,6 +215,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
214
215
|
};
|
|
215
216
|
}
|
|
216
217
|
case "unblock_command": {
|
|
218
|
+
capture('server_unblock_command');
|
|
217
219
|
const parsed = UnblockCommandArgsSchema.parse(args);
|
|
218
220
|
const unblockResult = await commandManager.unblockCommand(parsed.command);
|
|
219
221
|
return {
|
|
@@ -221,6 +223,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
221
223
|
};
|
|
222
224
|
}
|
|
223
225
|
case "list_blocked_commands": {
|
|
226
|
+
capture('server_list_blocked_commands');
|
|
224
227
|
const blockedCommands = await commandManager.listBlockedCommands();
|
|
225
228
|
return {
|
|
226
229
|
content: [{ type: "text", text: blockedCommands.join('\n') }],
|
|
@@ -228,71 +231,80 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
228
231
|
}
|
|
229
232
|
// Filesystem tools
|
|
230
233
|
case "edit_block": {
|
|
234
|
+
capture('server_edit_block');
|
|
231
235
|
const parsed = EditBlockArgsSchema.parse(args);
|
|
232
236
|
const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
try {
|
|
238
|
+
await performSearchReplace(filePath, searchReplace);
|
|
239
|
+
return {
|
|
240
|
+
content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
245
|
+
return {
|
|
246
|
+
content: [{ type: "text", text: errorMessage }],
|
|
247
|
+
};
|
|
248
|
+
}
|
|
238
249
|
}
|
|
239
250
|
case "read_file": {
|
|
251
|
+
capture('server_read_file');
|
|
240
252
|
const parsed = ReadFileArgsSchema.parse(args);
|
|
241
253
|
const content = await readFile(parsed.path);
|
|
242
|
-
capture('server_read_file');
|
|
243
254
|
return {
|
|
244
255
|
content: [{ type: "text", text: content }],
|
|
245
256
|
};
|
|
246
257
|
}
|
|
247
258
|
case "read_multiple_files": {
|
|
259
|
+
capture('server_read_multiple_files');
|
|
248
260
|
const parsed = ReadMultipleFilesArgsSchema.parse(args);
|
|
249
261
|
const results = await readMultipleFiles(parsed.paths);
|
|
250
|
-
capture('server_read_multiple_files');
|
|
251
262
|
return {
|
|
252
263
|
content: [{ type: "text", text: results.join("\n---\n") }],
|
|
253
264
|
};
|
|
254
265
|
}
|
|
255
266
|
case "write_file": {
|
|
267
|
+
capture('server_write_file');
|
|
256
268
|
const parsed = WriteFileArgsSchema.parse(args);
|
|
257
269
|
await writeFile(parsed.path, parsed.content);
|
|
258
|
-
capture('server_write_file');
|
|
259
270
|
return {
|
|
260
271
|
content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
|
|
261
272
|
};
|
|
262
273
|
}
|
|
263
274
|
case "create_directory": {
|
|
275
|
+
capture('server_create_directory');
|
|
264
276
|
const parsed = CreateDirectoryArgsSchema.parse(args);
|
|
265
277
|
await createDirectory(parsed.path);
|
|
266
|
-
capture('server_create_directory');
|
|
267
278
|
return {
|
|
268
279
|
content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
|
|
269
280
|
};
|
|
270
281
|
}
|
|
271
282
|
case "list_directory": {
|
|
283
|
+
capture('server_list_directory');
|
|
272
284
|
const parsed = ListDirectoryArgsSchema.parse(args);
|
|
273
285
|
const entries = await listDirectory(parsed.path);
|
|
274
|
-
capture('server_list_directory');
|
|
275
286
|
return {
|
|
276
287
|
content: [{ type: "text", text: entries.join('\n') }],
|
|
277
288
|
};
|
|
278
289
|
}
|
|
279
290
|
case "move_file": {
|
|
291
|
+
capture('server_move_file');
|
|
280
292
|
const parsed = MoveFileArgsSchema.parse(args);
|
|
281
293
|
await moveFile(parsed.source, parsed.destination);
|
|
282
|
-
capture('server_move_file');
|
|
283
294
|
return {
|
|
284
295
|
content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
|
|
285
296
|
};
|
|
286
297
|
}
|
|
287
298
|
case "search_files": {
|
|
299
|
+
capture('server_search_files');
|
|
288
300
|
const parsed = SearchFilesArgsSchema.parse(args);
|
|
289
301
|
const results = await searchFiles(parsed.path, parsed.pattern);
|
|
290
|
-
capture('server_search_files');
|
|
291
302
|
return {
|
|
292
303
|
content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
|
|
293
304
|
};
|
|
294
305
|
}
|
|
295
306
|
case "search_code": {
|
|
307
|
+
capture('server_search_code');
|
|
296
308
|
const parsed = SearchCodeArgsSchema.parse(args);
|
|
297
309
|
const results = await searchTextInFiles({
|
|
298
310
|
rootPath: parsed.path,
|
|
@@ -303,7 +315,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
303
315
|
includeHidden: parsed.includeHidden,
|
|
304
316
|
contextLines: parsed.contextLines,
|
|
305
317
|
});
|
|
306
|
-
capture('server_search_code');
|
|
307
318
|
if (results.length === 0) {
|
|
308
319
|
return {
|
|
309
320
|
content: [{ type: "text", text: "No matches found" }],
|
|
@@ -324,9 +335,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
324
335
|
};
|
|
325
336
|
}
|
|
326
337
|
case "get_file_info": {
|
|
338
|
+
capture('server_get_file_info');
|
|
327
339
|
const parsed = GetFileInfoArgsSchema.parse(args);
|
|
328
340
|
const info = await getFileInfo(parsed.path);
|
|
329
|
-
capture('server_get_file_info');
|
|
330
341
|
return {
|
|
331
342
|
content: [{
|
|
332
343
|
type: "text",
|
|
@@ -347,6 +358,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
347
358
|
};
|
|
348
359
|
}
|
|
349
360
|
default:
|
|
361
|
+
capture('server_unknow_tool', {
|
|
362
|
+
name
|
|
363
|
+
});
|
|
350
364
|
throw new Error(`Unknown tool: ${name}`);
|
|
351
365
|
}
|
|
352
366
|
}
|
|
@@ -4,20 +4,30 @@ import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
|
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname } from 'path';
|
|
6
6
|
import { exec } from "node:child_process";
|
|
7
|
-
import { PostHog } from 'posthog-node';
|
|
8
|
-
import machineId from 'node-machine-id';
|
|
9
7
|
import { version as nodeVersion } from 'process';
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
// Optional analytics - will gracefully degrade if posthog-node isn't available
|
|
10
|
+
let client = null;
|
|
11
|
+
let uniqueUserId = 'unknown';
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const { PostHog } = await import('posthog-node');
|
|
15
|
+
const machineIdModule = await import('node-machine-id');
|
|
16
|
+
|
|
17
|
+
client = new PostHog(
|
|
18
|
+
'phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063',
|
|
19
|
+
{
|
|
20
|
+
host: 'https://eu.i.posthog.com',
|
|
21
|
+
flushAt: 1, // send all every time
|
|
22
|
+
flushInterval: 0 //send always
|
|
23
|
+
}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Get a unique user ID
|
|
27
|
+
uniqueUserId = machineIdModule.machineIdSync();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
//console.error('Analytics module not available - continuing without tracking');
|
|
30
|
+
}
|
|
21
31
|
|
|
22
32
|
// Function to get npm version
|
|
23
33
|
async function getNpmVersion() {
|
|
@@ -118,14 +128,24 @@ async function getTrackingProperties(additionalProps = {}) {
|
|
|
118
128
|
};
|
|
119
129
|
}
|
|
120
130
|
|
|
131
|
+
// Helper function for tracking that handles missing PostHog gracefully
|
|
132
|
+
async function trackEvent(eventName, additionalProps = {}) {
|
|
133
|
+
if (!client) return; // Skip tracking if PostHog client isn't available
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
client.capture({
|
|
137
|
+
distinctId: uniqueUserId,
|
|
138
|
+
event: eventName,
|
|
139
|
+
properties: await getTrackingProperties(additionalProps)
|
|
140
|
+
});
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// Silently fail if tracking fails - we don't want to break the setup process
|
|
143
|
+
//console.log(`Note: Event tracking unavailable for ${eventName}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
121
147
|
// Initial tracking
|
|
122
|
-
(
|
|
123
|
-
client.capture({
|
|
124
|
-
distinctId: uniqueUserId,
|
|
125
|
-
event: 'npx_setup_start',
|
|
126
|
-
properties: await getTrackingProperties()
|
|
127
|
-
});
|
|
128
|
-
})();
|
|
148
|
+
trackEvent('npx_setup_start');
|
|
129
149
|
|
|
130
150
|
// Fix for Windows ESM path resolution
|
|
131
151
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -231,11 +251,7 @@ async function restartClaude() {
|
|
|
231
251
|
|
|
232
252
|
logToFile(`Claude has been restarted.`)
|
|
233
253
|
} catch (error) {
|
|
234
|
-
|
|
235
|
-
distinctId: uniqueUserId,
|
|
236
|
-
event: 'npx_setup_restart_claude_error',
|
|
237
|
-
properties: await getTrackingProperties({ error: error.message })
|
|
238
|
-
});
|
|
254
|
+
await trackEvent('npx_setup_restart_claude_error', { error: error.message });
|
|
239
255
|
logToFile(`Failed to restart Claude: ${error}`, true)
|
|
240
256
|
}
|
|
241
257
|
}
|
|
@@ -246,11 +262,7 @@ if (!existsSync(claudeConfigPath)) {
|
|
|
246
262
|
logToFile('Creating default config file...');
|
|
247
263
|
|
|
248
264
|
// Track new installation
|
|
249
|
-
|
|
250
|
-
distinctId: uniqueUserId,
|
|
251
|
-
event: 'npx_setup_create_default_config',
|
|
252
|
-
properties: await getTrackingProperties()
|
|
253
|
-
});
|
|
265
|
+
await trackEvent('npx_setup_create_default_config');
|
|
254
266
|
|
|
255
267
|
// Create the directory if it doesn't exist
|
|
256
268
|
const configDir = dirname(claudeConfigPath);
|
|
@@ -322,31 +334,35 @@ export default async function setup() {
|
|
|
322
334
|
|
|
323
335
|
// Write the updated config back
|
|
324
336
|
writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
325
|
-
|
|
326
|
-
distinctId: uniqueUserId,
|
|
327
|
-
event: 'npx_setup_update_config',
|
|
328
|
-
properties: await getTrackingProperties()
|
|
329
|
-
});
|
|
337
|
+
await trackEvent('npx_setup_update_config');
|
|
330
338
|
logToFile('Successfully added MCP server to Claude configuration!');
|
|
331
339
|
logToFile(`Configuration location: ${claudeConfigPath}`);
|
|
332
340
|
logToFile('\nTo use the server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander" in Claude\'s MCP server list');
|
|
333
341
|
|
|
334
342
|
await restartClaude();
|
|
335
343
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
344
|
+
await trackEvent('npx_setup_complete');
|
|
345
|
+
|
|
346
|
+
// Safe shutdown
|
|
347
|
+
if (client) {
|
|
348
|
+
try {
|
|
349
|
+
await client.shutdown();
|
|
350
|
+
} catch (error) {
|
|
351
|
+
// Ignore shutdown errors
|
|
352
|
+
}
|
|
353
|
+
}
|
|
342
354
|
} catch (error) {
|
|
343
|
-
|
|
344
|
-
distinctId: uniqueUserId,
|
|
345
|
-
event: 'npx_setup_final_error',
|
|
346
|
-
properties: await getTrackingProperties({ error: error.message })
|
|
347
|
-
});
|
|
355
|
+
await trackEvent('npx_setup_final_error', { error: error.message });
|
|
348
356
|
logToFile(`Error updating Claude configuration: ${error}`, true);
|
|
349
|
-
|
|
357
|
+
|
|
358
|
+
// Safe shutdown
|
|
359
|
+
if (client) {
|
|
360
|
+
try {
|
|
361
|
+
await client.shutdown();
|
|
362
|
+
} catch (error) {
|
|
363
|
+
// Ignore shutdown errors
|
|
364
|
+
}
|
|
365
|
+
}
|
|
350
366
|
process.exit(1);
|
|
351
367
|
}
|
|
352
368
|
}
|
package/dist/tools/execute.js
CHANGED
|
@@ -8,9 +8,22 @@ export async function executeCommand(args) {
|
|
|
8
8
|
capture('server_execute_command_failed');
|
|
9
9
|
throw new Error(`Invalid arguments for execute_command: ${parsed.error}`);
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
try {
|
|
12
|
+
// Extract all commands for analytics while ensuring execution continues even if parsing fails
|
|
13
|
+
const commands = commandManager.extractCommands(parsed.data.command);
|
|
14
|
+
capture('server_execute_command', {
|
|
15
|
+
command: commandManager.getBaseCommand(parsed.data.command), // Keep original for backward compatibility
|
|
16
|
+
commands: commands // Add the array of all identified commands
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
// If anything goes wrong with command extraction, just continue with execution
|
|
21
|
+
capture('server_execute_command', {
|
|
22
|
+
command: commandManager.getBaseCommand(parsed.data.command)
|
|
23
|
+
});
|
|
24
|
+
// Log the error but continue execution
|
|
25
|
+
console.error('Error during command extraction:', error);
|
|
26
|
+
}
|
|
14
27
|
if (!commandManager.validateCommand(parsed.data.command)) {
|
|
15
28
|
throw new Error(`Command not allowed: ${parsed.data.command}`);
|
|
16
29
|
}
|
package/dist/utils.js
CHANGED
|
@@ -1,23 +1,41 @@
|
|
|
1
|
-
import { PostHog } from 'posthog-node';
|
|
2
|
-
import machineId from 'node-machine-id';
|
|
3
1
|
import { platform } from 'os';
|
|
2
|
+
// Set default tracking state
|
|
4
3
|
const isTrackingEnabled = true;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
4
|
+
let uniqueUserId = 'unknown';
|
|
5
|
+
let posthog = null;
|
|
6
|
+
// Try to load PostHog without breaking if it's not available
|
|
7
|
+
try {
|
|
8
|
+
// Dynamic imports to prevent crashing if dependencies aren't available
|
|
9
|
+
const { PostHog } = require('posthog-node');
|
|
10
|
+
const machineId = require('node-machine-id');
|
|
11
|
+
uniqueUserId = machineId.machineIdSync();
|
|
12
|
+
if (isTrackingEnabled) {
|
|
13
|
+
posthog = new PostHog('phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063', {
|
|
14
|
+
host: 'https://eu.i.posthog.com',
|
|
15
|
+
flushAt: 3, // send all every time
|
|
16
|
+
flushInterval: 5 // send always
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
//console.log('Analytics module not available - continuing without tracking');
|
|
22
|
+
}
|
|
11
23
|
export const capture = (event, properties) => {
|
|
12
24
|
if (!posthog || !isTrackingEnabled) {
|
|
13
25
|
return;
|
|
14
26
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
try {
|
|
28
|
+
properties = properties || {};
|
|
29
|
+
properties.timestamp = new Date().toISOString();
|
|
30
|
+
properties.platform = platform();
|
|
31
|
+
posthog.capture({
|
|
32
|
+
distinctId: uniqueUserId,
|
|
33
|
+
event,
|
|
34
|
+
properties
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
// Silently fail - we don't want analytics issues to break functionality
|
|
39
|
+
console.error('Analytics tracking failed:', error);
|
|
40
|
+
}
|
|
23
41
|
};
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.27";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1.
|
|
1
|
+
export const VERSION = '0.1.27';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wonderwhy-er/desktop-commander",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "MCP server for terminal operations and file editing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduards Ruzga",
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
"test:watch": "nodemon test/test.js",
|
|
34
34
|
"link:local": "npm run build && npm link",
|
|
35
35
|
"unlink:local": "npm unlink",
|
|
36
|
-
"inspector": "npx @modelcontextprotocol/inspector dist/index.js"
|
|
37
|
-
"publish": "npm publish"
|
|
36
|
+
"inspector": "npx @modelcontextprotocol/inspector dist/index.js"
|
|
38
37
|
},
|
|
39
38
|
"publishConfig": {
|
|
40
39
|
"access": "public"
|