@wonderwhy-er/desktop-commander 0.1.25 → 0.1.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/README.md +23 -12
- package/dist/command-manager.d.ts +1 -0
- package/dist/command-manager.js +4 -1
- package/dist/index.js +13 -0
- package/dist/server.js +24 -4
- package/dist/setup-claude-server.js +171 -19
- package/dist/tools/execute.js +5 -0
- package/dist/utils.d.ts +1 -0
- package/dist/utils.js +23 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# Desktop Commander MCP
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
[](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
|
|
5
4
|
[](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
|
|
6
5
|
[](https://www.buymeacoffee.com/wonderwhyer)
|
|
@@ -51,7 +50,14 @@ This is server that allows Claude desktop app to execute long-running terminal c
|
|
|
51
50
|
## Installation
|
|
52
51
|
First, ensure you've downloaded and installed the [Claude Desktop app](https://claude.ai/download) and you have [npm installed](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm).
|
|
53
52
|
|
|
54
|
-
### Option 1:
|
|
53
|
+
### Option 1: Install through npx
|
|
54
|
+
Just run this in terminal
|
|
55
|
+
```
|
|
56
|
+
npx @wonderwhy-er/desktop-commander setup
|
|
57
|
+
```
|
|
58
|
+
Restart Claude if running
|
|
59
|
+
|
|
60
|
+
### Option 2: Installing via Smithery
|
|
55
61
|
|
|
56
62
|
To install Desktop Commander for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@wonderwhy-er/desktop-commander):
|
|
57
63
|
|
|
@@ -59,13 +65,6 @@ To install Desktop Commander for Claude Desktop automatically via [Smithery](htt
|
|
|
59
65
|
npx -y @smithery/cli install @wonderwhy-er/desktop-commander --client claude
|
|
60
66
|
```
|
|
61
67
|
|
|
62
|
-
### Option 2: Install trough npx
|
|
63
|
-
Just run this in terminal
|
|
64
|
-
```
|
|
65
|
-
npx @wonderwhy-er/desktop-commander setup
|
|
66
|
-
```
|
|
67
|
-
Restart Claude if running
|
|
68
|
-
|
|
69
68
|
### Option 3: Add to claude_desktop_config by hand
|
|
70
69
|
Add this entry to your claude_desktop_config.json:
|
|
71
70
|
|
|
@@ -122,7 +121,7 @@ The server provides these tool categories:
|
|
|
122
121
|
- `move_file`: Move/rename files
|
|
123
122
|
- `search_files`: Pattern-based file search
|
|
124
123
|
- `get_file_info`: File metadata
|
|
125
|
-
- `
|
|
124
|
+
- `search_code`: Recursive ripgrep based text and code search
|
|
126
125
|
|
|
127
126
|
### Edit Tools
|
|
128
127
|
- `edit_block`: Apply surgical text replacements (best for changes <20% of file size)
|
|
@@ -132,9 +131,9 @@ Search/Replace Block Format:
|
|
|
132
131
|
```
|
|
133
132
|
filepath.ext
|
|
134
133
|
<<<<<<< SEARCH
|
|
135
|
-
|
|
134
|
+
content to find
|
|
136
135
|
=======
|
|
137
|
-
new
|
|
136
|
+
new content
|
|
138
137
|
>>>>>>> REPLACE
|
|
139
138
|
```
|
|
140
139
|
|
|
@@ -264,6 +263,18 @@ No. This tool works with Claude Desktop's standard Pro subscription ($20/month),
|
|
|
264
263
|
### I'm having trouble installing or using the tool. Where can I get help?
|
|
265
264
|
Join our [Discord server](https://discord.gg/kQ27sNnZr7) for community support, check the [GitHub issues](https://github.com/wonderwhy-er/ClaudeComputerCommander/issues) for known problems, or review the [full FAQ](FAQ.md) for troubleshooting tips. You can also visit our [website FAQ section](https://desktopcommander.app#faq) for a more user-friendly experience. If you encounter a new issue, please consider [opening a GitHub issue](https://github.com/wonderwhy-er/ClaudeComputerCommander/issues/new) with details about your problem.
|
|
266
265
|
|
|
266
|
+
## Data Collection
|
|
267
|
+
|
|
268
|
+
During installation and setup, Desktop Commander collects anonymous usage data to help improve the tool. This includes:
|
|
269
|
+
- Operating system information
|
|
270
|
+
- Node.js and NPM versions
|
|
271
|
+
- Installation method and shell environment
|
|
272
|
+
- Error messages (if any occur during setup)
|
|
273
|
+
|
|
274
|
+
This data is collected using PostHog analytics and is associated with a machine-generated unique ID. No personal information is collected. This helps us understand how the tool is being used and identify common issues.
|
|
275
|
+
|
|
276
|
+
We are currently working on adding a built-in opt-out option for this data collection in an upcoming release. For now, if you wish to opt out, you can block network connections to `eu.i.posthog.com` in your firewall settings.
|
|
277
|
+
|
|
267
278
|
## License
|
|
268
279
|
|
|
269
280
|
MIT
|
|
@@ -2,6 +2,7 @@ declare class CommandManager {
|
|
|
2
2
|
private blockedCommands;
|
|
3
3
|
loadBlockedCommands(): Promise<void>;
|
|
4
4
|
saveBlockedCommands(): Promise<void>;
|
|
5
|
+
getBaseCommand(command: string): string;
|
|
5
6
|
validateCommand(command: string): boolean;
|
|
6
7
|
blockCommand(command: string): Promise<boolean>;
|
|
7
8
|
unblockCommand(command: string): Promise<boolean>;
|
package/dist/command-manager.js
CHANGED
|
@@ -25,8 +25,11 @@ class CommandManager {
|
|
|
25
25
|
// Handle error if needed
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
getBaseCommand(command) {
|
|
29
|
+
return command.split(' ')[0].toLowerCase().trim();
|
|
30
|
+
}
|
|
28
31
|
validateCommand(command) {
|
|
29
|
-
const baseCommand =
|
|
32
|
+
const baseCommand = this.getBaseCommand(command);
|
|
30
33
|
return !this.blockedCommands.has(baseCommand);
|
|
31
34
|
}
|
|
32
35
|
async blockCommand(command) {
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { commandManager } from './command-manager.js';
|
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
7
|
import { platform } from 'os';
|
|
8
|
+
import { capture } from './utils.js';
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = dirname(__filename);
|
|
10
11
|
const isWindows = platform() === 'win32';
|
|
@@ -59,6 +60,9 @@ async function runServer() {
|
|
|
59
60
|
process.stderr.write(`[desktop-commander] JSON parsing error: ${errorMessage}\n`);
|
|
60
61
|
return; // Don't exit on JSON parsing errors
|
|
61
62
|
}
|
|
63
|
+
capture('run_server_uncaught_exception', {
|
|
64
|
+
error: errorMessage
|
|
65
|
+
});
|
|
62
66
|
process.stderr.write(`[desktop-commander] Uncaught exception: ${errorMessage}\n`);
|
|
63
67
|
process.exit(1);
|
|
64
68
|
});
|
|
@@ -70,6 +74,9 @@ async function runServer() {
|
|
|
70
74
|
process.stderr.write(`[desktop-commander] JSON parsing rejection: ${errorMessage}\n`);
|
|
71
75
|
return; // Don't exit on JSON parsing errors
|
|
72
76
|
}
|
|
77
|
+
capture('run_server_unhandled_rejection', {
|
|
78
|
+
error: errorMessage
|
|
79
|
+
});
|
|
73
80
|
process.stderr.write(`[desktop-commander] Unhandled rejection: ${errorMessage}\n`);
|
|
74
81
|
process.exit(1);
|
|
75
82
|
});
|
|
@@ -84,6 +91,9 @@ async function runServer() {
|
|
|
84
91
|
timestamp: new Date().toISOString(),
|
|
85
92
|
message: `Failed to start server: ${errorMessage}`
|
|
86
93
|
}) + '\n');
|
|
94
|
+
capture('run_server_failed_start_error', {
|
|
95
|
+
error: errorMessage
|
|
96
|
+
});
|
|
87
97
|
process.exit(1);
|
|
88
98
|
}
|
|
89
99
|
}
|
|
@@ -94,5 +104,8 @@ runServer().catch(async (error) => {
|
|
|
94
104
|
timestamp: new Date().toISOString(),
|
|
95
105
|
message: `Fatal error running server: ${errorMessage}`
|
|
96
106
|
}) + '\n');
|
|
107
|
+
capture('run_server_fatal_error', {
|
|
108
|
+
error: errorMessage
|
|
109
|
+
});
|
|
97
110
|
process.exit(1);
|
|
98
111
|
});
|
package/dist/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory,
|
|
|
9
9
|
import { parseEditBlock, performSearchReplace } from './tools/edit.js';
|
|
10
10
|
import { searchTextInFiles } from './tools/search.js';
|
|
11
11
|
import { VERSION } from './version.js';
|
|
12
|
+
import { capture } from "./utils.js";
|
|
12
13
|
export const server = new Server({
|
|
13
14
|
name: "desktop-commander",
|
|
14
15
|
version: VERSION,
|
|
@@ -96,7 +97,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
96
97
|
{
|
|
97
98
|
name: "read_file",
|
|
98
99
|
description: "Read the complete contents of a file from the file system. " +
|
|
99
|
-
"
|
|
100
|
+
"Reads UTF-8 text and provides detailed error messages " +
|
|
100
101
|
"if the file cannot be read. Only works within allowed directories.",
|
|
101
102
|
inputSchema: zodToJsonSchema(ReadFileArgsSchema),
|
|
102
103
|
},
|
|
@@ -136,7 +137,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
136
137
|
},
|
|
137
138
|
{
|
|
138
139
|
name: "search_files",
|
|
139
|
-
description: "
|
|
140
|
+
description: "Finds files by name using a case-insensitive substring matching. " +
|
|
140
141
|
"Searches through all subdirectories from the starting path. " +
|
|
141
142
|
"Only searches within allowed directories.",
|
|
142
143
|
inputSchema: zodToJsonSchema(SearchFilesArgsSchema),
|
|
@@ -168,8 +169,8 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
168
169
|
{
|
|
169
170
|
name: "edit_block",
|
|
170
171
|
description: "Apply surgical text replacements to files. Best for small changes (<20% of file size). " +
|
|
171
|
-
"
|
|
172
|
-
"Format
|
|
172
|
+
"Call repeatedly to change multiple blocks. Will verify changes after application. " +
|
|
173
|
+
"Format:\nfilepath\n<<<<<<< SEARCH\ncontent to find\n=======\nnew content\n>>>>>>> REPLACE",
|
|
173
174
|
inputSchema: zodToJsonSchema(EditBlockArgsSchema),
|
|
174
175
|
},
|
|
175
176
|
],
|
|
@@ -186,18 +187,23 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
186
187
|
}
|
|
187
188
|
case "read_output": {
|
|
188
189
|
const parsed = ReadOutputArgsSchema.parse(args);
|
|
190
|
+
capture('server_read_output');
|
|
189
191
|
return readOutput(parsed);
|
|
190
192
|
}
|
|
191
193
|
case "force_terminate": {
|
|
192
194
|
const parsed = ForceTerminateArgsSchema.parse(args);
|
|
195
|
+
capture('server_force_terminate');
|
|
193
196
|
return forceTerminate(parsed);
|
|
194
197
|
}
|
|
195
198
|
case "list_sessions":
|
|
199
|
+
capture('server_list_sessions');
|
|
196
200
|
return listSessions();
|
|
197
201
|
case "list_processes":
|
|
202
|
+
capture('server_list_processes');
|
|
198
203
|
return listProcesses();
|
|
199
204
|
case "kill_process": {
|
|
200
205
|
const parsed = KillProcessArgsSchema.parse(args);
|
|
206
|
+
capture('server_kill_process');
|
|
201
207
|
return killProcess(parsed);
|
|
202
208
|
}
|
|
203
209
|
case "block_command": {
|
|
@@ -225,6 +231,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
225
231
|
const parsed = EditBlockArgsSchema.parse(args);
|
|
226
232
|
const { filePath, searchReplace } = await parseEditBlock(parsed.blockContent);
|
|
227
233
|
await performSearchReplace(filePath, searchReplace);
|
|
234
|
+
capture('server_edit_block');
|
|
228
235
|
return {
|
|
229
236
|
content: [{ type: "text", text: `Successfully applied edit to ${filePath}` }],
|
|
230
237
|
};
|
|
@@ -232,6 +239,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
232
239
|
case "read_file": {
|
|
233
240
|
const parsed = ReadFileArgsSchema.parse(args);
|
|
234
241
|
const content = await readFile(parsed.path);
|
|
242
|
+
capture('server_read_file');
|
|
235
243
|
return {
|
|
236
244
|
content: [{ type: "text", text: content }],
|
|
237
245
|
};
|
|
@@ -239,6 +247,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
239
247
|
case "read_multiple_files": {
|
|
240
248
|
const parsed = ReadMultipleFilesArgsSchema.parse(args);
|
|
241
249
|
const results = await readMultipleFiles(parsed.paths);
|
|
250
|
+
capture('server_read_multiple_files');
|
|
242
251
|
return {
|
|
243
252
|
content: [{ type: "text", text: results.join("\n---\n") }],
|
|
244
253
|
};
|
|
@@ -246,6 +255,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
246
255
|
case "write_file": {
|
|
247
256
|
const parsed = WriteFileArgsSchema.parse(args);
|
|
248
257
|
await writeFile(parsed.path, parsed.content);
|
|
258
|
+
capture('server_write_file');
|
|
249
259
|
return {
|
|
250
260
|
content: [{ type: "text", text: `Successfully wrote to ${parsed.path}` }],
|
|
251
261
|
};
|
|
@@ -253,6 +263,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
253
263
|
case "create_directory": {
|
|
254
264
|
const parsed = CreateDirectoryArgsSchema.parse(args);
|
|
255
265
|
await createDirectory(parsed.path);
|
|
266
|
+
capture('server_create_directory');
|
|
256
267
|
return {
|
|
257
268
|
content: [{ type: "text", text: `Successfully created directory ${parsed.path}` }],
|
|
258
269
|
};
|
|
@@ -260,6 +271,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
260
271
|
case "list_directory": {
|
|
261
272
|
const parsed = ListDirectoryArgsSchema.parse(args);
|
|
262
273
|
const entries = await listDirectory(parsed.path);
|
|
274
|
+
capture('server_list_directory');
|
|
263
275
|
return {
|
|
264
276
|
content: [{ type: "text", text: entries.join('\n') }],
|
|
265
277
|
};
|
|
@@ -267,6 +279,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
267
279
|
case "move_file": {
|
|
268
280
|
const parsed = MoveFileArgsSchema.parse(args);
|
|
269
281
|
await moveFile(parsed.source, parsed.destination);
|
|
282
|
+
capture('server_move_file');
|
|
270
283
|
return {
|
|
271
284
|
content: [{ type: "text", text: `Successfully moved ${parsed.source} to ${parsed.destination}` }],
|
|
272
285
|
};
|
|
@@ -274,6 +287,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
274
287
|
case "search_files": {
|
|
275
288
|
const parsed = SearchFilesArgsSchema.parse(args);
|
|
276
289
|
const results = await searchFiles(parsed.path, parsed.pattern);
|
|
290
|
+
capture('server_search_files');
|
|
277
291
|
return {
|
|
278
292
|
content: [{ type: "text", text: results.length > 0 ? results.join('\n') : "No matches found" }],
|
|
279
293
|
};
|
|
@@ -289,6 +303,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
289
303
|
includeHidden: parsed.includeHidden,
|
|
290
304
|
contextLines: parsed.contextLines,
|
|
291
305
|
});
|
|
306
|
+
capture('server_search_code');
|
|
292
307
|
if (results.length === 0) {
|
|
293
308
|
return {
|
|
294
309
|
content: [{ type: "text", text: "No matches found" }],
|
|
@@ -311,6 +326,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
311
326
|
case "get_file_info": {
|
|
312
327
|
const parsed = GetFileInfoArgsSchema.parse(args);
|
|
313
328
|
const info = await getFileInfo(parsed.path);
|
|
329
|
+
capture('server_get_file_info');
|
|
314
330
|
return {
|
|
315
331
|
content: [{
|
|
316
332
|
type: "text",
|
|
@@ -322,6 +338,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
322
338
|
}
|
|
323
339
|
case "list_allowed_directories": {
|
|
324
340
|
const directories = listAllowedDirectories();
|
|
341
|
+
capture('server_list_allowed_directories');
|
|
325
342
|
return {
|
|
326
343
|
content: [{
|
|
327
344
|
type: "text",
|
|
@@ -335,6 +352,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
335
352
|
}
|
|
336
353
|
catch (error) {
|
|
337
354
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
355
|
+
capture('server_request_error', {
|
|
356
|
+
error: errorMessage
|
|
357
|
+
});
|
|
338
358
|
return {
|
|
339
359
|
content: [{ type: "text", text: `Error: ${errorMessage}` }],
|
|
340
360
|
isError: true,
|
|
@@ -4,6 +4,128 @@ 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
|
+
import { version as nodeVersion } from 'process';
|
|
10
|
+
|
|
11
|
+
const client = new PostHog(
|
|
12
|
+
'phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063',
|
|
13
|
+
{
|
|
14
|
+
host: 'https://eu.i.posthog.com',
|
|
15
|
+
flushAt: 1, // send all every time
|
|
16
|
+
flushInterval: 0 //send always
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
// Get a unique user ID
|
|
20
|
+
const uniqueUserId = machineId.machineIdSync();
|
|
21
|
+
|
|
22
|
+
// Function to get npm version
|
|
23
|
+
async function getNpmVersion() {
|
|
24
|
+
try {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
exec('npm --version', (error, stdout, stderr) => {
|
|
27
|
+
if (error) {
|
|
28
|
+
resolve('unknown');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
resolve(stdout.trim());
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
return 'unknown';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Function to detect shell environment
|
|
40
|
+
function detectShell() {
|
|
41
|
+
// Check for Windows shells
|
|
42
|
+
if (process.platform === 'win32') {
|
|
43
|
+
if (process.env.TERM_PROGRAM === 'vscode') return 'vscode-terminal';
|
|
44
|
+
if (process.env.WT_SESSION) return 'windows-terminal';
|
|
45
|
+
if (process.env.SHELL?.includes('bash')) return 'git-bash';
|
|
46
|
+
if (process.env.TERM?.includes('xterm')) return 'xterm-on-windows';
|
|
47
|
+
if (process.env.ComSpec?.toLowerCase().includes('powershell')) return 'powershell';
|
|
48
|
+
if (process.env.PROMPT) return 'cmd';
|
|
49
|
+
|
|
50
|
+
// WSL detection
|
|
51
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
|
|
52
|
+
return `wsl-${process.env.WSL_DISTRO_NAME || 'unknown'}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return 'windows-unknown';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Unix-based shells
|
|
59
|
+
if (process.env.SHELL) {
|
|
60
|
+
const shellPath = process.env.SHELL.toLowerCase();
|
|
61
|
+
if (shellPath.includes('bash')) return 'bash';
|
|
62
|
+
if (shellPath.includes('zsh')) return 'zsh';
|
|
63
|
+
if (shellPath.includes('fish')) return 'fish';
|
|
64
|
+
if (shellPath.includes('ksh')) return 'ksh';
|
|
65
|
+
if (shellPath.includes('csh')) return 'csh';
|
|
66
|
+
if (shellPath.includes('dash')) return 'dash';
|
|
67
|
+
return `other-unix-${shellPath.split('/').pop()}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Terminal emulators and IDE terminals
|
|
71
|
+
if (process.env.TERM_PROGRAM) {
|
|
72
|
+
return process.env.TERM_PROGRAM.toLowerCase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return 'unknown-shell';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Function to determine execution context
|
|
79
|
+
function getExecutionContext() {
|
|
80
|
+
// Check if running from npx
|
|
81
|
+
const isNpx = process.env.npm_lifecycle_event === 'npx' ||
|
|
82
|
+
process.env.npm_execpath?.includes('npx') ||
|
|
83
|
+
process.env._?.includes('npx') ||
|
|
84
|
+
import.meta.url.includes('node_modules');
|
|
85
|
+
|
|
86
|
+
// Check if installed globally
|
|
87
|
+
const isGlobal = process.env.npm_config_global === 'true' ||
|
|
88
|
+
process.argv[1]?.includes('node_modules/.bin');
|
|
89
|
+
|
|
90
|
+
// Check if it's run from a script in package.json
|
|
91
|
+
const isNpmScript = !!process.env.npm_lifecycle_script;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
runMethod: isNpx ? 'npx' : (isGlobal ? 'global' : (isNpmScript ? 'npm_script' : 'direct')),
|
|
95
|
+
isCI: !!process.env.CI || !!process.env.GITHUB_ACTIONS || !!process.env.TRAVIS || !!process.env.CIRCLECI,
|
|
96
|
+
shell: detectShell()
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Helper function to get standard environment properties for tracking
|
|
101
|
+
let npmVersionCache = null;
|
|
102
|
+
async function getTrackingProperties(additionalProps = {}) {
|
|
103
|
+
if (npmVersionCache === null) {
|
|
104
|
+
npmVersionCache = await getNpmVersion();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const context = getExecutionContext();
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
platform: platform(),
|
|
111
|
+
nodeVersion: nodeVersion,
|
|
112
|
+
npmVersion: npmVersionCache,
|
|
113
|
+
executionContext: context.runMethod,
|
|
114
|
+
isCI: context.isCI,
|
|
115
|
+
shell: context.shell,
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
...additionalProps
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Initial tracking
|
|
122
|
+
(async () => {
|
|
123
|
+
client.capture({
|
|
124
|
+
distinctId: uniqueUserId,
|
|
125
|
+
event: 'npx_setup_start',
|
|
126
|
+
properties: await getTrackingProperties()
|
|
127
|
+
});
|
|
128
|
+
})();
|
|
7
129
|
|
|
8
130
|
// Fix for Windows ESM path resolution
|
|
9
131
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -74,27 +196,28 @@ async function execAsync(command) {
|
|
|
74
196
|
async function restartClaude() {
|
|
75
197
|
try {
|
|
76
198
|
const platform = process.platform
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
199
|
+
// ignore errors on windows when claude is not running.
|
|
200
|
+
// just silently kill the process
|
|
201
|
+
try {
|
|
202
|
+
switch (platform) {
|
|
203
|
+
case "win32":
|
|
204
|
+
|
|
82
205
|
await execAsync(
|
|
83
206
|
`taskkill /F /IM "Claude.exe"`,
|
|
84
207
|
)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
208
|
+
break;
|
|
209
|
+
case "darwin":
|
|
210
|
+
await execAsync(
|
|
211
|
+
`killall "Claude"`,
|
|
212
|
+
)
|
|
213
|
+
break;
|
|
214
|
+
case "linux":
|
|
215
|
+
await execAsync(
|
|
216
|
+
`pkill -f "claude"`,
|
|
217
|
+
)
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
} catch {}
|
|
98
221
|
await new Promise((resolve) => setTimeout(resolve, 3000))
|
|
99
222
|
|
|
100
223
|
if (platform === "win32") {
|
|
@@ -108,6 +231,11 @@ async function restartClaude() {
|
|
|
108
231
|
|
|
109
232
|
logToFile(`Claude has been restarted.`)
|
|
110
233
|
} catch (error) {
|
|
234
|
+
client.capture({
|
|
235
|
+
distinctId: uniqueUserId,
|
|
236
|
+
event: 'npx_setup_restart_claude_error',
|
|
237
|
+
properties: await getTrackingProperties({ error: error.message })
|
|
238
|
+
});
|
|
111
239
|
logToFile(`Failed to restart Claude: ${error}`, true)
|
|
112
240
|
}
|
|
113
241
|
}
|
|
@@ -117,6 +245,13 @@ if (!existsSync(claudeConfigPath)) {
|
|
|
117
245
|
logToFile(`Claude config file not found at: ${claudeConfigPath}`);
|
|
118
246
|
logToFile('Creating default config file...');
|
|
119
247
|
|
|
248
|
+
// Track new installation
|
|
249
|
+
client.capture({
|
|
250
|
+
distinctId: uniqueUserId,
|
|
251
|
+
event: 'npx_setup_create_default_config',
|
|
252
|
+
properties: await getTrackingProperties()
|
|
253
|
+
});
|
|
254
|
+
|
|
120
255
|
// Create the directory if it doesn't exist
|
|
121
256
|
const configDir = dirname(claudeConfigPath);
|
|
122
257
|
if (!existsSync(configDir)) {
|
|
@@ -187,14 +322,31 @@ export default async function setup() {
|
|
|
187
322
|
|
|
188
323
|
// Write the updated config back
|
|
189
324
|
writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
190
|
-
|
|
325
|
+
client.capture({
|
|
326
|
+
distinctId: uniqueUserId,
|
|
327
|
+
event: 'npx_setup_update_config',
|
|
328
|
+
properties: await getTrackingProperties()
|
|
329
|
+
});
|
|
191
330
|
logToFile('Successfully added MCP server to Claude configuration!');
|
|
192
331
|
logToFile(`Configuration location: ${claudeConfigPath}`);
|
|
193
332
|
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');
|
|
194
333
|
|
|
195
334
|
await restartClaude();
|
|
335
|
+
|
|
336
|
+
client.capture({
|
|
337
|
+
distinctId: uniqueUserId,
|
|
338
|
+
event: 'npx_setup_complete',
|
|
339
|
+
properties: await getTrackingProperties()
|
|
340
|
+
});
|
|
341
|
+
await client.shutdown()
|
|
196
342
|
} catch (error) {
|
|
343
|
+
client.capture({
|
|
344
|
+
distinctId: uniqueUserId,
|
|
345
|
+
event: 'npx_setup_final_error',
|
|
346
|
+
properties: await getTrackingProperties({ error: error.message })
|
|
347
|
+
});
|
|
197
348
|
logToFile(`Error updating Claude configuration: ${error}`, true);
|
|
349
|
+
await client.shutdown()
|
|
198
350
|
process.exit(1);
|
|
199
351
|
}
|
|
200
352
|
}
|
package/dist/tools/execute.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { terminalManager } from '../terminal-manager.js';
|
|
2
2
|
import { commandManager } from '../command-manager.js';
|
|
3
3
|
import { ExecuteCommandArgsSchema, ReadOutputArgsSchema, ForceTerminateArgsSchema } from './schemas.js';
|
|
4
|
+
import { capture } from "../utils.js";
|
|
4
5
|
export async function executeCommand(args) {
|
|
5
6
|
const parsed = ExecuteCommandArgsSchema.safeParse(args);
|
|
6
7
|
if (!parsed.success) {
|
|
8
|
+
capture('server_execute_command_failed');
|
|
7
9
|
throw new Error(`Invalid arguments for execute_command: ${parsed.error}`);
|
|
8
10
|
}
|
|
11
|
+
capture('server_execute_command', {
|
|
12
|
+
command: commandManager.getBaseCommand(parsed.data.command)
|
|
13
|
+
});
|
|
9
14
|
if (!commandManager.validateCommand(parsed.data.command)) {
|
|
10
15
|
throw new Error(`Command not allowed: ${parsed.data.command}`);
|
|
11
16
|
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const capture: (event: string, properties?: any) => void;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PostHog } from 'posthog-node';
|
|
2
|
+
import machineId from 'node-machine-id';
|
|
3
|
+
import { platform } from 'os';
|
|
4
|
+
const isTrackingEnabled = true;
|
|
5
|
+
const uniqueUserId = machineId.machineIdSync();
|
|
6
|
+
const posthog = isTrackingEnabled ? new PostHog('phc_TFQqTkCwtFGxlwkXDY3gSs7uvJJcJu8GurfXd6mV063', {
|
|
7
|
+
host: 'https://eu.i.posthog.com',
|
|
8
|
+
flushAt: 3, // send all every time
|
|
9
|
+
flushInterval: 5 //send always
|
|
10
|
+
}) : null;
|
|
11
|
+
export const capture = (event, properties) => {
|
|
12
|
+
if (!posthog || !isTrackingEnabled) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
properties = properties || {};
|
|
16
|
+
properties.timestamp = new Date().toISOString();
|
|
17
|
+
properties.platform = platform();
|
|
18
|
+
posthog.capture({
|
|
19
|
+
distinctId: uniqueUserId,
|
|
20
|
+
event,
|
|
21
|
+
properties
|
|
22
|
+
});
|
|
23
|
+
};
|
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.1.
|
|
1
|
+
export declare const VERSION = "0.1.26";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.1.
|
|
1
|
+
export const VERSION = '0.1.26';
|
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.26",
|
|
4
4
|
"description": "MCP server for terminal operations and file editing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eduards Ruzga",
|
|
@@ -62,6 +62,8 @@
|
|
|
62
62
|
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
63
63
|
"@vscode/ripgrep": "^1.15.9",
|
|
64
64
|
"glob": "^10.3.10",
|
|
65
|
+
"node-machine-id": "^1.1.12",
|
|
66
|
+
"posthog-node": "^4.11.1",
|
|
65
67
|
"zod": "^3.24.1",
|
|
66
68
|
"zod-to-json-schema": "^3.23.5"
|
|
67
69
|
},
|