@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 CHANGED
@@ -1,6 +1,5 @@
1
1
  # Desktop Commander MCP
2
2
 
3
-
4
3
  [![npm downloads](https://img.shields.io/npm/dw/@wonderwhy-er/desktop-commander)](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
5
4
  [![smithery badge](https://smithery.ai/badge/@wonderwhy-er/desktop-commander)](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
6
5
  [![Buy Me A Coffee](https://img.shields.io/badge/Buy%20Me%20A%20Coffee-support-yellow.svg)](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: Installing via Smithery
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
- - `code_search`: Recursive ripgrep based text and code search
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
- existing code to replace
134
+ content to find
136
135
  =======
137
- new code to insert
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>;
@@ -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 = command.split(' ')[0].toLowerCase().trim();
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
- "Handles various text encodings and provides detailed error messages " +
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: "Recursively search for files and directories matching a pattern. " +
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
- "Multiple blocks can be used for separate changes. Will verify changes after application. " +
172
- "Format: filepath, then <<<<<<< SEARCH, content to find, =======, new content, >>>>>>> REPLACE.",
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
- switch (platform) {
78
- case "win32":
79
- // ignore errors on windows when claude is not running.
80
- // just silently kill the process
81
- try {
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
- } catch {}
86
- break;
87
- case "darwin":
88
- await execAsync(
89
- `killall "Claude"`,
90
- )
91
- break;
92
- case "linux":
93
- await execAsync(
94
- `pkill -f "claude"`,
95
- )
96
- break;
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
  }
@@ -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
  }
@@ -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.25";
1
+ export declare const VERSION = "0.1.26";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.1.25';
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.25",
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
  },