@wonderwhy-er/desktop-commander 0.1.23 → 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 +31 -14
- package/dist/command-manager.d.ts +1 -0
- package/dist/command-manager.js +4 -1
- package/dist/custom-stdio.d.ts +8 -0
- package/dist/custom-stdio.js +22 -0
- package/dist/index.js +62 -7
- package/dist/server.d.ts +19 -15
- package/dist/server.js +24 -4
- package/dist/setup-claude-server.js +294 -48
- 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 +8 -3
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,15 +65,13 @@ 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
|
-
Add this entry to your claude_desktop_config.json
|
|
69
|
+
Add this entry to your claude_desktop_config.json:
|
|
70
|
+
|
|
71
|
+
- On Mac: `~/Library/Application\ Support/Claude/claude_desktop_config.json`
|
|
72
|
+
- On Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
73
|
+
- On Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
74
|
+
|
|
71
75
|
```json
|
|
72
76
|
{
|
|
73
77
|
"mcpServers": {
|
|
@@ -117,7 +121,7 @@ The server provides these tool categories:
|
|
|
117
121
|
- `move_file`: Move/rename files
|
|
118
122
|
- `search_files`: Pattern-based file search
|
|
119
123
|
- `get_file_info`: File metadata
|
|
120
|
-
- `
|
|
124
|
+
- `search_code`: Recursive ripgrep based text and code search
|
|
121
125
|
|
|
122
126
|
### Edit Tools
|
|
123
127
|
- `edit_block`: Apply surgical text replacements (best for changes <20% of file size)
|
|
@@ -127,9 +131,9 @@ Search/Replace Block Format:
|
|
|
127
131
|
```
|
|
128
132
|
filepath.ext
|
|
129
133
|
<<<<<<< SEARCH
|
|
130
|
-
|
|
134
|
+
content to find
|
|
131
135
|
=======
|
|
132
|
-
new
|
|
136
|
+
new content
|
|
133
137
|
>>>>>>> REPLACE
|
|
134
138
|
```
|
|
135
139
|
|
|
@@ -164,6 +168,7 @@ This project extends the MCP Filesystem Server to enable:
|
|
|
164
168
|
Created as part of exploring Claude MCPs: https://youtube.com/live/TlbjFDbl5Us
|
|
165
169
|
|
|
166
170
|
## DONE
|
|
171
|
+
- **28-03-2025 Fixed "Watching /" JSON error** - Implemented custom stdio transport to handle non-JSON messages and prevent server crashes
|
|
167
172
|
- **25-03-2025 Better code search** ([merged](https://github.com/wonderwhy-er/ClaudeDesktopCommander/pull/17)) - Enhanced code exploration with context-aware results
|
|
168
173
|
|
|
169
174
|
## Work in Progress and TODOs
|
|
@@ -195,7 +200,7 @@ Learn more about this project through these resources:
|
|
|
195
200
|
This Developer Ditched Windsurf, Cursor Using Claude with MCPs](https://analyticsindiamag.com/ai-features/this-developer-ditched-windsurf-cursor-using-claude-with-mcps/)
|
|
196
201
|
|
|
197
202
|
### Community
|
|
198
|
-
Join our [Discord server](https://discord.gg/
|
|
203
|
+
Join our [Discord server](https://discord.gg/kQ27sNnZr7) to get help, share feedback, and connect with other users.
|
|
199
204
|
|
|
200
205
|
## Testimonials
|
|
201
206
|
|
|
@@ -258,6 +263,18 @@ No. This tool works with Claude Desktop's standard Pro subscription ($20/month),
|
|
|
258
263
|
### I'm having trouble installing or using the tool. Where can I get help?
|
|
259
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.
|
|
260
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
|
+
|
|
261
278
|
## License
|
|
262
279
|
|
|
263
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) {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
/**
|
|
3
|
+
* Extended StdioServerTransport that filters out non-JSON messages.
|
|
4
|
+
* This prevents the "Watching /" error from crashing the server.
|
|
5
|
+
*/
|
|
6
|
+
export declare class FilteredStdioServerTransport extends StdioServerTransport {
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
/**
|
|
4
|
+
* Extended StdioServerTransport that filters out non-JSON messages.
|
|
5
|
+
* This prevents the "Watching /" error from crashing the server.
|
|
6
|
+
*/
|
|
7
|
+
export class FilteredStdioServerTransport extends StdioServerTransport {
|
|
8
|
+
constructor() {
|
|
9
|
+
// Create a proxy for stdout that only allows valid JSON to pass through
|
|
10
|
+
const originalStdoutWrite = process.stdout.write;
|
|
11
|
+
process.stdout.write = function (buffer) {
|
|
12
|
+
// Only intercept string output that doesn't look like JSON
|
|
13
|
+
if (typeof buffer === 'string' && !buffer.trim().startsWith('{')) {
|
|
14
|
+
return true; //process.stderr.write(buffer);
|
|
15
|
+
}
|
|
16
|
+
return originalStdoutWrite.apply(process.stdout, arguments);
|
|
17
|
+
};
|
|
18
|
+
super();
|
|
19
|
+
// Log initialization to stderr to avoid polluting the JSON stream
|
|
20
|
+
process.stderr.write(`[desktop-commander] Initialized FilteredStdioServerTransport\n`);
|
|
21
|
+
}
|
|
22
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { FilteredStdioServerTransport } from './custom-stdio.js';
|
|
3
3
|
import { server } from './server.js';
|
|
4
4
|
import { commandManager } from './command-manager.js';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
7
|
+
import { platform } from 'os';
|
|
8
|
+
import { capture } from './utils.js';
|
|
7
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
10
|
const __dirname = dirname(__filename);
|
|
11
|
+
const isWindows = platform() === 'win32';
|
|
12
|
+
// Helper function to properly convert file paths to URLs, especially for Windows
|
|
13
|
+
function createFileURL(filePath) {
|
|
14
|
+
if (isWindows) {
|
|
15
|
+
// Ensure path uses forward slashes for URL format
|
|
16
|
+
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
17
|
+
// Ensure path has proper file:// prefix
|
|
18
|
+
if (normalizedPath.startsWith('/')) {
|
|
19
|
+
return new URL(`file://${normalizedPath}`);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
return new URL(`file:///${normalizedPath}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
// For non-Windows, we can use the built-in function
|
|
27
|
+
return pathToFileURL(filePath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
9
30
|
async function runSetup() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
31
|
+
try {
|
|
32
|
+
// Fix for Windows ESM path issue
|
|
33
|
+
const setupScriptPath = join(__dirname, 'setup-claude-server.js');
|
|
34
|
+
const setupScriptUrl = createFileURL(setupScriptPath);
|
|
35
|
+
// Now import using the URL format
|
|
36
|
+
const { default: setupModule } = await import(setupScriptUrl.href);
|
|
37
|
+
if (typeof setupModule === 'function') {
|
|
38
|
+
await setupModule();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error('Error running setup:', error);
|
|
43
|
+
process.exit(1);
|
|
14
44
|
}
|
|
15
45
|
}
|
|
16
46
|
async function runServer() {
|
|
17
47
|
try {
|
|
48
|
+
const transport = new FilteredStdioServerTransport();
|
|
49
|
+
console.log("start");
|
|
18
50
|
// Check if first argument is "setup"
|
|
19
51
|
if (process.argv[2] === 'setup') {
|
|
20
52
|
await runSetup();
|
|
@@ -23,14 +55,31 @@ async function runServer() {
|
|
|
23
55
|
// Handle uncaught exceptions
|
|
24
56
|
process.on('uncaughtException', async (error) => {
|
|
25
57
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
58
|
+
// If this is a JSON parsing error, log it to stderr but don't crash
|
|
59
|
+
if (errorMessage.includes('JSON') && errorMessage.includes('Unexpected token')) {
|
|
60
|
+
process.stderr.write(`[desktop-commander] JSON parsing error: ${errorMessage}\n`);
|
|
61
|
+
return; // Don't exit on JSON parsing errors
|
|
62
|
+
}
|
|
63
|
+
capture('run_server_uncaught_exception', {
|
|
64
|
+
error: errorMessage
|
|
65
|
+
});
|
|
66
|
+
process.stderr.write(`[desktop-commander] Uncaught exception: ${errorMessage}\n`);
|
|
26
67
|
process.exit(1);
|
|
27
68
|
});
|
|
28
69
|
// Handle unhandled rejections
|
|
29
70
|
process.on('unhandledRejection', async (reason) => {
|
|
30
71
|
const errorMessage = reason instanceof Error ? reason.message : String(reason);
|
|
72
|
+
// If this is a JSON parsing error, log it to stderr but don't crash
|
|
73
|
+
if (errorMessage.includes('JSON') && errorMessage.includes('Unexpected token')) {
|
|
74
|
+
process.stderr.write(`[desktop-commander] JSON parsing rejection: ${errorMessage}\n`);
|
|
75
|
+
return; // Don't exit on JSON parsing errors
|
|
76
|
+
}
|
|
77
|
+
capture('run_server_unhandled_rejection', {
|
|
78
|
+
error: errorMessage
|
|
79
|
+
});
|
|
80
|
+
process.stderr.write(`[desktop-commander] Unhandled rejection: ${errorMessage}\n`);
|
|
31
81
|
process.exit(1);
|
|
32
82
|
});
|
|
33
|
-
const transport = new StdioServerTransport();
|
|
34
83
|
// Load blocked commands from config file
|
|
35
84
|
await commandManager.loadBlockedCommands();
|
|
36
85
|
await server.connect(transport);
|
|
@@ -42,6 +91,9 @@ async function runServer() {
|
|
|
42
91
|
timestamp: new Date().toISOString(),
|
|
43
92
|
message: `Failed to start server: ${errorMessage}`
|
|
44
93
|
}) + '\n');
|
|
94
|
+
capture('run_server_failed_start_error', {
|
|
95
|
+
error: errorMessage
|
|
96
|
+
});
|
|
45
97
|
process.exit(1);
|
|
46
98
|
}
|
|
47
99
|
}
|
|
@@ -52,5 +104,8 @@ runServer().catch(async (error) => {
|
|
|
52
104
|
timestamp: new Date().toISOString(),
|
|
53
105
|
message: `Fatal error running server: ${errorMessage}`
|
|
54
106
|
}) + '\n');
|
|
107
|
+
capture('run_server_fatal_error', {
|
|
108
|
+
error: errorMessage
|
|
109
|
+
});
|
|
55
110
|
process.exit(1);
|
|
56
111
|
});
|
package/dist/server.d.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
export declare const server: Server<{
|
|
3
3
|
method: string;
|
|
4
|
-
params?:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
progressToken
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
}, import("zod").ZodTypeAny, "passthrough">>>;
|
|
12
|
-
}, import("zod").ZodTypeAny, "passthrough"> | undefined;
|
|
4
|
+
params?: {
|
|
5
|
+
[x: string]: unknown;
|
|
6
|
+
_meta?: {
|
|
7
|
+
[x: string]: unknown;
|
|
8
|
+
progressToken?: string | number | undefined;
|
|
9
|
+
} | undefined;
|
|
10
|
+
} | undefined;
|
|
13
11
|
}, {
|
|
14
12
|
method: string;
|
|
15
|
-
params?:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
13
|
+
params?: {
|
|
14
|
+
[x: string]: unknown;
|
|
15
|
+
_meta?: {
|
|
16
|
+
[x: string]: unknown;
|
|
17
|
+
} | undefined;
|
|
18
|
+
} | undefined;
|
|
19
|
+
}, {
|
|
20
|
+
[x: string]: unknown;
|
|
21
|
+
_meta?: {
|
|
22
|
+
[x: string]: unknown;
|
|
23
|
+
} | undefined;
|
|
24
|
+
}>;
|
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,
|
|
@@ -3,15 +3,153 @@ import { join } from 'path';
|
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
5
|
import { dirname } from 'path';
|
|
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';
|
|
6
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
|
+
})();
|
|
129
|
+
|
|
130
|
+
// Fix for Windows ESM path resolution
|
|
7
131
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
132
|
const __dirname = dirname(__filename);
|
|
9
133
|
|
|
10
|
-
// Determine OS and set appropriate config path
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
134
|
+
// Determine OS and set appropriate config path
|
|
135
|
+
const os = platform();
|
|
136
|
+
const isWindows = os === 'win32'; // Define isWindows variable
|
|
137
|
+
let claudeConfigPath;
|
|
138
|
+
|
|
139
|
+
switch (os) {
|
|
140
|
+
case 'win32':
|
|
141
|
+
claudeConfigPath = join(process.env.APPDATA, 'Claude', 'claude_desktop_config.json');
|
|
142
|
+
break;
|
|
143
|
+
case 'darwin':
|
|
144
|
+
claudeConfigPath = join(homedir(), 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');
|
|
145
|
+
break;
|
|
146
|
+
case 'linux':
|
|
147
|
+
claudeConfigPath = join(homedir(), '.config', 'Claude', 'claude_desktop_config.json');
|
|
148
|
+
break;
|
|
149
|
+
default:
|
|
150
|
+
// Fallback for other platforms
|
|
151
|
+
claudeConfigPath = join(homedir(), '.claude_desktop_config.json');
|
|
152
|
+
}
|
|
15
153
|
|
|
16
154
|
// Setup logging
|
|
17
155
|
const LOG_FILE = join(__dirname, 'setup.log');
|
|
@@ -38,18 +176,89 @@ function logToFile(message, isError = false) {
|
|
|
38
176
|
}
|
|
39
177
|
}
|
|
40
178
|
|
|
179
|
+
async function execAsync(command) {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
// Use PowerShell on Windows for better Unicode support and consistency
|
|
182
|
+
const actualCommand = isWindows
|
|
183
|
+
? `cmd.exe /c ${command}`
|
|
184
|
+
: command;
|
|
185
|
+
|
|
186
|
+
exec(actualCommand, (error, stdout, stderr) => {
|
|
187
|
+
if (error) {
|
|
188
|
+
reject(error);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
resolve({ stdout, stderr });
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function restartClaude() {
|
|
197
|
+
try {
|
|
198
|
+
const platform = process.platform
|
|
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
|
+
|
|
205
|
+
await execAsync(
|
|
206
|
+
`taskkill /F /IM "Claude.exe"`,
|
|
207
|
+
)
|
|
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 {}
|
|
221
|
+
await new Promise((resolve) => setTimeout(resolve, 3000))
|
|
222
|
+
|
|
223
|
+
if (platform === "win32") {
|
|
224
|
+
// it will never start claude
|
|
225
|
+
// await execAsync(`start "" "Claude.exe"`)
|
|
226
|
+
} else if (platform === "darwin") {
|
|
227
|
+
await execAsync(`open -a "Claude"`)
|
|
228
|
+
} else if (platform === "linux") {
|
|
229
|
+
await execAsync(`claude`)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
logToFile(`Claude has been restarted.`)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
client.capture({
|
|
235
|
+
distinctId: uniqueUserId,
|
|
236
|
+
event: 'npx_setup_restart_claude_error',
|
|
237
|
+
properties: await getTrackingProperties({ error: error.message })
|
|
238
|
+
});
|
|
239
|
+
logToFile(`Failed to restart Claude: ${error}`, true)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
41
243
|
// Check if config file exists and create default if not
|
|
42
244
|
if (!existsSync(claudeConfigPath)) {
|
|
43
245
|
logToFile(`Claude config file not found at: ${claudeConfigPath}`);
|
|
44
246
|
logToFile('Creating default config file...');
|
|
45
247
|
|
|
248
|
+
// Track new installation
|
|
249
|
+
client.capture({
|
|
250
|
+
distinctId: uniqueUserId,
|
|
251
|
+
event: 'npx_setup_create_default_config',
|
|
252
|
+
properties: await getTrackingProperties()
|
|
253
|
+
});
|
|
254
|
+
|
|
46
255
|
// Create the directory if it doesn't exist
|
|
47
256
|
const configDir = dirname(claudeConfigPath);
|
|
48
257
|
if (!existsSync(configDir)) {
|
|
49
258
|
import('fs').then(fs => fs.mkdirSync(configDir, { recursive: true }));
|
|
50
259
|
}
|
|
51
260
|
|
|
52
|
-
// Create default config
|
|
261
|
+
// Create default config with shell based on platform
|
|
53
262
|
const defaultConfig = {
|
|
54
263
|
"serverConfig": isWindows
|
|
55
264
|
? {
|
|
@@ -66,49 +275,86 @@ if (!existsSync(claudeConfigPath)) {
|
|
|
66
275
|
logToFile('Default config file created. Please update it with your Claude API credentials.');
|
|
67
276
|
}
|
|
68
277
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
// Determine if running through npx or locally
|
|
76
|
-
const isNpx = import.meta.url.endsWith('dist/setup-claude-server.js');
|
|
77
|
-
|
|
78
|
-
const serverConfig = isNpx ? {
|
|
79
|
-
"command": "npx",
|
|
80
|
-
"args": [
|
|
81
|
-
"@wonderwhy-er/desktop-commander"
|
|
82
|
-
]
|
|
83
|
-
} : {
|
|
84
|
-
"command": "node",
|
|
85
|
-
"args": [
|
|
86
|
-
join(__dirname, 'dist', 'index.js')
|
|
87
|
-
]
|
|
88
|
-
};
|
|
278
|
+
// Main function to export for ESM compatibility
|
|
279
|
+
export default async function setup() {
|
|
280
|
+
try {
|
|
281
|
+
// Read existing config
|
|
282
|
+
const configData = readFileSync(claudeConfigPath, 'utf8');
|
|
283
|
+
const config = JSON.parse(configData);
|
|
89
284
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
285
|
+
// Prepare the new server config based on OS
|
|
286
|
+
// Determine if running through npx or locally
|
|
287
|
+
const isNpx = import.meta.url.includes('node_modules');
|
|
288
|
+
|
|
289
|
+
// Fix Windows path handling for npx execution
|
|
290
|
+
let serverConfig;
|
|
291
|
+
if (isNpx) {
|
|
292
|
+
serverConfig = {
|
|
293
|
+
"command": isWindows ? "npx.cmd" : "npx",
|
|
294
|
+
"args": [
|
|
295
|
+
"@wonderwhy-er/desktop-commander"
|
|
296
|
+
]
|
|
297
|
+
};
|
|
298
|
+
} else {
|
|
299
|
+
// For local installation, use absolute path to handle Windows properly
|
|
300
|
+
const indexPath = join(__dirname, 'dist', 'index.js');
|
|
301
|
+
serverConfig = {
|
|
302
|
+
"command": "node",
|
|
303
|
+
"args": [
|
|
304
|
+
indexPath.replace(/\\/g, '\\\\') // Double escape backslashes for JSON
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Initialize mcpServers if it doesn't exist
|
|
310
|
+
if (!config.mcpServers) {
|
|
311
|
+
config.mcpServers = {};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Check if the old "desktopCommander" exists and remove it
|
|
315
|
+
if (config.mcpServers.desktopCommander) {
|
|
316
|
+
logToFile('Found old "desktopCommander" installation. Removing it...');
|
|
317
|
+
delete config.mcpServers.desktopCommander;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Add or update the terminal server config with the proper name "desktop-commander"
|
|
321
|
+
config.mcpServers["desktop-commander"] = serverConfig;
|
|
322
|
+
|
|
323
|
+
// Write the updated config back
|
|
324
|
+
writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
|
|
325
|
+
client.capture({
|
|
326
|
+
distinctId: uniqueUserId,
|
|
327
|
+
event: 'npx_setup_update_config',
|
|
328
|
+
properties: await getTrackingProperties()
|
|
329
|
+
});
|
|
330
|
+
logToFile('Successfully added MCP server to Claude configuration!');
|
|
331
|
+
logToFile(`Configuration location: ${claudeConfigPath}`);
|
|
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');
|
|
333
|
+
|
|
334
|
+
await restartClaude();
|
|
335
|
+
|
|
336
|
+
client.capture({
|
|
337
|
+
distinctId: uniqueUserId,
|
|
338
|
+
event: 'npx_setup_complete',
|
|
339
|
+
properties: await getTrackingProperties()
|
|
340
|
+
});
|
|
341
|
+
await client.shutdown()
|
|
342
|
+
} catch (error) {
|
|
343
|
+
client.capture({
|
|
344
|
+
distinctId: uniqueUserId,
|
|
345
|
+
event: 'npx_setup_final_error',
|
|
346
|
+
properties: await getTrackingProperties({ error: error.message })
|
|
347
|
+
});
|
|
348
|
+
logToFile(`Error updating Claude configuration: ${error}`, true);
|
|
349
|
+
await client.shutdown()
|
|
350
|
+
process.exit(1);
|
|
99
351
|
}
|
|
100
|
-
|
|
101
|
-
// Add or update the terminal server config with the proper name "desktop-commander"
|
|
102
|
-
config.mcpServers["desktop-commander"] = serverConfig;
|
|
352
|
+
}
|
|
103
353
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
} catch (error) {
|
|
112
|
-
logToFile(`Error updating Claude configuration: ${error}`, true);
|
|
113
|
-
process.exit(1);
|
|
354
|
+
// Allow direct execution
|
|
355
|
+
if (process.argv.length >= 2 && process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
356
|
+
setup().catch(error => {
|
|
357
|
+
logToFile(`Fatal error: ${error}`, true);
|
|
358
|
+
process.exit(1);
|
|
359
|
+
});
|
|
114
360
|
}
|
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,12 +1,15 @@
|
|
|
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",
|
|
7
7
|
"homepage": "https://github.com/wonderwhy-er/DesktopCommanderMCP",
|
|
8
8
|
"bugs": "https://github.com/wonderwhy-er/DesktopCommanderMCP/issues",
|
|
9
9
|
"type": "module",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=18.0.0"
|
|
12
|
+
},
|
|
10
13
|
"bin": {
|
|
11
14
|
"desktop-commander": "dist/index.js",
|
|
12
15
|
"setup": "dist/setup-claude-server.js"
|
|
@@ -56,14 +59,16 @@
|
|
|
56
59
|
"file-operations"
|
|
57
60
|
],
|
|
58
61
|
"dependencies": {
|
|
59
|
-
"@modelcontextprotocol/sdk": "1.0
|
|
62
|
+
"@modelcontextprotocol/sdk": "^1.8.0",
|
|
60
63
|
"@vscode/ripgrep": "^1.15.9",
|
|
61
64
|
"glob": "^10.3.10",
|
|
65
|
+
"node-machine-id": "^1.1.12",
|
|
66
|
+
"posthog-node": "^4.11.1",
|
|
62
67
|
"zod": "^3.24.1",
|
|
63
68
|
"zod-to-json-schema": "^3.23.5"
|
|
64
69
|
},
|
|
65
70
|
"devDependencies": {
|
|
66
|
-
"@types/node": "^20.
|
|
71
|
+
"@types/node": "^20.17.24",
|
|
67
72
|
"nodemon": "^3.0.2",
|
|
68
73
|
"shx": "^0.3.4",
|
|
69
74
|
"typescript": "^5.3.3"
|