Socket
Book a DemoInstallSign in
Socket

@wonderwhy-er/desktop-commander

Package Overview
Dependencies
Maintainers
1
Versions
53
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@wonderwhy-er/desktop-commander - npm Package Compare versions

Comparing version

to
0.2.2

dist/command-manager.js.map

1

dist/server.js

@@ -297,2 +297,3 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";

Read new output from a running terminal session.
Set timeout_ms for long running commands.

@@ -299,0 +300,0 @@ ${CMD_PREFIX_DESCRIPTION}`,

80

dist/setup-claude-server.js

@@ -49,3 +49,2 @@ import { homedir, platform } from 'os';

}
const getVersion = async () => {

@@ -55,32 +54,24 @@ try {

return process.env.npm_package_version;
} else {
const packageJsonPath = join(__dirname, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJsonContent = readFileSync(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonContent);
if (packageJson.version) {
return packageJson.version;
}
}
// Check if version.js exists in dist directory (when running from root)
const versionPath = join(__dirname, 'version.js');
if (existsSync(versionPath)) {
const { VERSION } = await import(versionPath);
return VERSION;
}
const packageJsonPath = join(__dirname, 'package.json');
if (existsSync(packageJsonPath)) {
const packageJsonContent = readFileSync(packageJsonPath, 'utf8');
const packageJson = JSON.parse(packageJsonContent);
if (packageJson.version) {
return packageJson.version;
}
}
throw new Error('Version not found in environment variable or package.json');
return 'unknown';
} catch (error) {
try {
const packageJson = await import('./package.json', { with: { type: 'json' } });
if (packageJson.default?.version) {
return packageJson.default.version;
}
} catch (importError) {
// Try older syntax as fallback
try {
const packageJson = await import('./package.json', { assert: { type: 'json' } });
if (packageJson.default?.version) {
return packageJson.default.version;
}
} catch (legacyImportError) {
// Log the error for debugging
logToFile(`Failed to import package.json: ${legacyImportError.message}`, true);
}
}
return 'unknown';
}

@@ -325,10 +316,10 @@ };

};
process.stdout.write(JSON.stringify(jsonOutput) + '\n');
process.stdout.write(`${message}\n`);
} catch (err) {
// Last resort error handling
process.stderr.write(JSON.stringify({
process.stderr.write(`${JSON.stringify({
type: 'error',
timestamp: new Date().toISOString(),
message: `Failed to write to log file: ${err.message}`
}) + '\n');
})}\n`);
}

@@ -488,11 +479,18 @@ }

updateSetupStep(startStep, 'completed');
logToFile(`Claude has been restarted.`);
logToFile("\nβœ… Claude has been restarted automatically!");
await trackEvent('npx_setup_start_claude_success', { platform });
} else if (platform === "linux") {
await execAsync(`claude`);
logToFile(`Claude has been restarted.`);
logToFile("\nβœ… Claude has been restarted automatically!");
updateSetupStep(startStep, 'completed');
await trackEvent('npx_setup_start_claude_success', { platform });
} else {
logToFile('\nTo use the server restart Claude if it\'s currently running\n');
}
logToFile("\nβœ… Installation successfully completed! Thank you for using Desktop Commander!\n");
logToFile('\nThe server is available as "desktop-commander" in Claude\'s MCP server list');
logToFile("Future updates will install automatically β€” no need to run this setup again.\n\n");
logToFile("πŸ’¬ Need help or found an issue? Join our community: https://discord.com/invite/kQ27sNnZr7\n\n")
updateSetupStep(restartStep, 'completed');

@@ -525,2 +523,12 @@ await trackEvent('npx_setup_restart_claude_success', { platform });

// Print ASCII art for DESKTOP COMMANDER
console.log('\n');
console.log('β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— ');
console.log('β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—');
console.log('β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ–ˆβ–ˆβ•”β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•');
console.log('β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β• β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—');
console.log('β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ•β• β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘');
console.log('β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β• β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•β•β•šβ•β•β•β•β•β• β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•');
console.log('\n');
if (debugMode) {

@@ -713,3 +721,3 @@ logToFile('Debug mode enabled. Will configure with Node.js inspector options.');

const appVersion = await getVersion()
logToFile(`Successfully added Desktop Commander MCP v${appVersion} server to Claude configuration!`);
logToFile(`βœ… Desktop Commander MCP v${appVersion} successfully added to Claude’s configuration.`);
logToFile(`Configuration location: ${claudeConfigPath}`);

@@ -719,4 +727,2 @@

logToFile('\nTo use the debug server:\n1. Restart Claude if it\'s currently running\n2. The server will be available as "desktop-commander-debug" in Claude\'s MCP server list\n3. Connect your debugger to port 9229');
} else {
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');
}

@@ -736,2 +742,4 @@

return true;

@@ -738,0 +746,0 @@ } catch (error) {

@@ -1,2 +0,2 @@

import { CommandExecutionResult, ActiveSession } from './types.js';
import { TerminalSession, CommandExecutionResult, ActiveSession } from './types.js';
interface CompletedSession {

@@ -14,2 +14,8 @@ pid: number;

getNewOutput(pid: number): string | null;
/**
* Get a session by PID
* @param pid Process ID
* @returns The session or undefined if not found
*/
getSession(pid: number): TerminalSession | undefined;
forceTerminate(pid: number): boolean;

@@ -16,0 +22,0 @@ listActiveSessions(): ActiveSession[];

@@ -16,3 +16,3 @@ import { spawn } from 'child_process';

const config = await configManager.getConfig();
shellToUse = config.shell || true;
shellToUse = config.defaultShell || true;
}

@@ -107,2 +107,10 @@ catch (error) {

}
/**
* Get a session by PID
* @param pid Process ID
* @returns The session or undefined if not found
*/
getSession(pid) {
return this.sessions.get(pid);
}
forceTerminate(pid) {

@@ -109,0 +117,0 @@ const session = this.sessions.get(pid);

@@ -67,11 +67,2 @@ import { readFile, writeFile } from './filesystem.js';

export async function performSearchReplace(filePath, block, expectedReplacements = 1) {
// Check for empty search string to prevent infinite loops
if (block.search === "") {
return {
content: [{
type: "text",
text: "Empty search strings are not allowed. Please provide a non-empty string to search for."
}],
};
}
// Get file extension for telemetry using path module

@@ -88,2 +79,13 @@ const fileExtension = path.extname(filePath).toLowerCase();

});
// Check for empty search string to prevent infinite loops
if (block.search === "") {
// Capture file extension in telemetry without capturing the file path
capture('server_edit_block_empty_search', { fileExtension: fileExtension, expectedReplacements });
return {
content: [{
type: "text",
text: "Empty search strings are not allowed. Please provide a non-empty string to search for."
}],
};
}
// Read file as plain string

@@ -93,2 +95,3 @@ const { content } = await readFile(filePath, false, 0, Number.MAX_SAFE_INTEGER);

if (typeof content !== 'string') {
capture('server_edit_block_content_not_string', { fileExtension: fileExtension, expectedReplacements });
throw new Error('Wrong content for file ' + filePath);

@@ -139,2 +142,3 @@ }

await writeFile(filePath, newContent);
capture('server_edit_block_exact_success', { fileExtension: fileExtension, expectedReplacements, hasWarning: warningMessage !== "" });
return {

@@ -149,2 +153,3 @@ content: [{

if (count > 0 && count !== expectedReplacements) {
capture('server_edit_block_unexpected_count', { fileExtension: fileExtension, expectedReplacements, expectedReplacementsCount: count });
return {

@@ -151,0 +156,0 @@ content: [{

@@ -59,9 +59,70 @@ import { terminalManager } from '../terminal-manager.js';

}
const output = terminalManager.getNewOutput(parsed.data.pid);
const { pid, timeout_ms = 5000 } = parsed.data;
// Check if the process exists
const session = terminalManager.getSession(pid);
if (!session) {
return {
content: [{ type: "text", text: `No session found for PID ${pid}` }],
isError: true,
};
}
// Wait for output with timeout
let output = "";
let timeoutReached = false;
try {
// Create a promise that resolves when new output is available or when timeout is reached
const outputPromise = new Promise((resolve) => {
// Check for initial output
const initialOutput = terminalManager.getNewOutput(pid);
if (initialOutput && initialOutput.length > 0) {
resolve(initialOutput);
return;
}
let resolved = false;
let interval = null;
let timeout = null;
const cleanup = () => {
if (interval) {
clearInterval(interval);
interval = null;
}
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
};
const resolveOnce = (value, isTimeout = false) => {
if (resolved)
return;
resolved = true;
cleanup();
if (isTimeout)
timeoutReached = true;
resolve(value);
};
// Setup an interval to poll for output
interval = setInterval(() => {
const newOutput = terminalManager.getNewOutput(pid);
if (newOutput && newOutput.length > 0) {
resolveOnce(newOutput);
}
}, 300); // Check every 300ms
// Set a timeout to stop waiting
timeout = setTimeout(() => {
const finalOutput = terminalManager.getNewOutput(pid) || "";
resolveOnce(finalOutput, true);
}, timeout_ms);
});
output = await outputPromise;
}
catch (error) {
return {
content: [{ type: "text", text: `Error reading output: ${error}` }],
isError: true,
};
}
return {
content: [{
type: "text",
text: output === null
? `No session found for PID ${parsed.data.pid}`
: output || 'No new output available'
text: output || 'No new output available' + (timeoutReached ? ' (timeout reached)' : '')
}],

@@ -68,0 +129,0 @@ };

@@ -392,8 +392,2 @@ import fs from "fs/promises";

catch (error) {
// Only sanitize for telemetry, not for the returned error
capture('server_search_read_dir_error', {
errorType: error instanceof Error ? error.name : 'Unknown',
error: 'Error reading directory',
isReadDirError: true
});
return; // Skip this directory on error

@@ -400,0 +394,0 @@ }

@@ -16,11 +16,11 @@ import { z } from "zod";

command: z.ZodString;
timeout_ms: z.ZodOptional<z.ZodNumber>;
timeout_ms: z.ZodNumber;
shell: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
command: string;
timeout_ms?: number | undefined;
timeout_ms: number;
shell?: string | undefined;
}, {
command: string;
timeout_ms?: number | undefined;
timeout_ms: number;
shell?: string | undefined;

@@ -30,6 +30,9 @@ }>;

pid: z.ZodNumber;
timeout_ms: z.ZodOptional<z.ZodNumber>;
}, "strip", z.ZodTypeAny, {
pid: number;
timeout_ms?: number | undefined;
}, {
pid: number;
timeout_ms?: number | undefined;
}>;

@@ -36,0 +39,0 @@ export declare const ForceTerminateArgsSchema: z.ZodObject<{

@@ -14,3 +14,3 @@ import { z } from "zod";

command: z.string(),
timeout_ms: z.number().optional(),
timeout_ms: z.number(),
shell: z.string().optional(),

@@ -20,2 +20,3 @@ });

pid: z.number(),
timeout_ms: z.number().optional(),
});

@@ -22,0 +23,0 @@ export const ForceTerminateArgsSchema = z.object({

@@ -1,1 +0,1 @@

export declare const VERSION = "0.2.1";
export declare const VERSION = "0.2.2";

@@ -1,1 +0,1 @@

export const VERSION = '0.2.1';
export const VERSION = '0.2.2';
{
"name": "@wonderwhy-er/desktop-commander",
"version": "0.2.1",
"version": "0.2.2",
"description": "MCP server for terminal operations and file editing",

@@ -5,0 +5,0 @@ "license": "MIT",

@@ -69,3 +69,5 @@ # Desktop Commander MCP

### Option 1: Install through npx
> **πŸ“‹ Update & Uninstall Information:** Before choosing an installation option, note that **only Options 1 and 3 have automatic updates**. Options 2, 4, and 5 require manual updates. See the sections below for update and uninstall instructions for each option.
### Option 1: Install through npx ⭐ **Auto-Updates**
Just run this in terminal:

@@ -82,3 +84,7 @@ ```

### Option 2: Using bash script installer (macOS)
**βœ… Auto-Updates:** Yes - automatically updates when you restart Claude
**πŸ”„ Manual Update:** Run the setup command again
**πŸ—‘οΈ Uninstall:** Run `npx @wonderwhy-er/desktop-commander@latest setup --uninstall`
### Option 2: Using bash script installer (macOS) ⭐ **Auto-Updates**
For macOS users, you can use our automated bash installer which will check your Node.js version, install it if needed, and automatically configure Desktop Commander:

@@ -90,4 +96,8 @@ ```

### Option 3: Installing via Smithery
**βœ… Auto-Updates:** Yes - requires manual updates
**πŸ”„ Manual Update:** Re-run the bash installer command above
**πŸ—‘οΈ Uninstall:** Remove the MCP server entry from your Claude config file and delete the cloned repository if it exists
### Option 3: Installing via Smithery ⭐ **Auto-Updates**
To install Desktop Commander for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@wonderwhy-er/desktop-commander):

@@ -99,3 +109,7 @@

### Option 4: Add to claude_desktop_config manually
**βœ… Auto-Updates:** Yes - automatically updates when you restart Claude
**πŸ”„ Manual Update:** Re-run the Smithery install command
**πŸ—‘οΈ Uninstall:** `npx -y @smithery/cli uninstall @wonderwhy-er/desktop-commander --client claude`
### Option 4: Add to claude_desktop_config manually ❌ **Manual Updates**
Add this entry to your claude_desktop_config.json:

@@ -122,3 +136,7 @@

### Option 5: Checkout locally
**❌ Auto-Updates:** No - uses npx but config might not update automatically
**πŸ”„ Manual Update:** Usually automatic via npx, but if issues occur, update your config file or re-add the entry
**πŸ—‘οΈ Uninstall:** Remove the "desktop-commander" entry from your claude_desktop_config.json file
### Option 5: Checkout locally ❌ **Manual Updates**
1. Clone and build:

@@ -138,8 +156,25 @@ ```bash

### Updating Desktop Commander
**❌ Auto-Updates:** No - requires manual git updates
**πŸ”„ Manual Update:** `cd DesktopCommanderMCP && git pull && npm run setup`
**πŸ—‘οΈ Uninstall:** Remove the cloned directory and remove MCP server entry from Claude config
When installed through npx (Option 1) or Smithery (Option 3), Desktop Commander will automatically update to the latest version whenever you restart Claude. No manual update process is needed.
## Updating & Uninstalling Desktop Commander
For manual installations, you can update by running the setup command again.
### Automatic Updates (Options 1 & 3 only)
**Options 1 (npx) and 3 (Smithery)** automatically update to the latest version whenever you restart Claude. No manual intervention needed.
### Manual Updates (Options 2, 4 & 5)
- **Option 2 (bash installer):** Re-run the curl command
- **Option 4 (manual config):** Usually automatic via npx, but re-add config entry if issues occur
- **Option 5 (local checkout):** `cd DesktopCommanderMCP && git pull && npm run setup`
### Uninstalling Desktop Commander
- **Option 1:** `npx @wonderwhy-er/desktop-commander@latest setup --uninstall`
- **Option 2:** Remove MCP server entry from Claude config and delete any cloned repositories
- **Option 3:** `npx -y @smithery/cli uninstall @wonderwhy-er/desktop-commander --client claude`
- **Option 4:** Remove the "desktop-commander" entry from your claude_desktop_config.json file
- **Option 5:** Delete the cloned directory and remove MCP server entry from Claude config
After uninstalling, restart Claude Desktop to complete the removal.
## Usage

@@ -307,2 +342,26 @@

#### Understanding fileWriteLineLimit
The `fileWriteLineLimit` setting controls how many lines can be written in a single `write_file` operation (default: 50 lines). This limit exists for several important reasons:
**Why the limit exists:**
- **AIs are wasteful with tokens**: Instead of doing two small edits in a file, AIs may decide to rewrite the whole thing. We're trying to force AIs to do things in smaller changes as it saves time and tokens
- **Claude UX message limits**: There are limits within one message and hitting "Continue" does not really work. What we're trying here is to make AI work in smaller chunks so when you hit that limit, multiple chunks have succeeded and that work is not lost - it just needs to restart from the last chunk
**Setting the limit:**
```javascript
// You can set it to thousands if you want
set_config_value({ "key": "fileWriteLineLimit", "value": 1000 })
// Or keep it smaller to force more efficient behavior
set_config_value({ "key": "fileWriteLineLimit", "value": 25 })
```
**Maximum value**: You can set it to thousands if you want - there's no technical restriction.
**Best practices**:
- Keep the default (50) to encourage efficient AI behavior and avoid token waste
- The system automatically suggests chunking when limits are exceeded
- Smaller chunks mean less work lost when Claude hits message limits
### Best Practices

@@ -309,0 +368,0 @@