@wonderwhy-er/desktop-commander 0.1.17 → 0.1.19

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,8 +1,12 @@
1
- # Claude Computer Commander
1
+ # Claude Desktop Commander MCP
2
+
3
+ [![npm downloads](https://img.shields.io/npm/dw/@wonderwhy-er/desktop-commander)](https://www.npmjs.com/package/@wonderwhy-er/desktop-commander)
4
+ [![smithery badge](https://smithery.ai/badge/@wonderwhy-er/desktop-commander)](https://smithery.ai/server/@wonderwhy-er/desktop-commander)
5
+
2
6
 
3
7
  Short version. Two key things. Terminal commands and diff based file editing.
4
8
 
5
- This server that allows Claude desktop app to execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP) + Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional file editing capabilities .
9
+ This is server that allows Claude desktop app to execute long-running terminal commands on your computer and manage processes through Model Context Protocol (MCP) + Built on top of [MCP Filesystem Server](https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem) to provide additional search and replace file editing capabilities .
6
10
 
7
11
  ## Features
8
12
 
@@ -25,14 +29,22 @@ This server that allows Claude desktop app to execute long-running terminal comm
25
29
  ## Installation
26
30
  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).
27
31
 
28
- ### Option 1: Install trough npx
32
+ ### Option 1: Installing via Smithery
33
+
34
+ To install Desktop Commander for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@wonderwhy-er/desktop-commander):
35
+
36
+ ```bash
37
+ npx -y @smithery/cli install @wonderwhy-er/desktop-commander --client claude
38
+ ```
39
+
40
+ ### Option 2: Install trough npx
29
41
  Just run this in terminal
30
42
  ```
31
43
  npx @wonderwhy-er/desktop-commander setup
32
44
  ```
33
45
  Restart Claude if running
34
46
 
35
- ### Option 2: Add to claude_desktop_config by hand
47
+ ### Option 3: Add to claude_desktop_config by hand
36
48
  Add this entry to your claude_desktop_config.json (on Mac, found at ~/Library/Application\ Support/Claude/claude_desktop_config.json):
37
49
  ```json
38
50
  {
@@ -49,7 +61,7 @@ Add this entry to your claude_desktop_config.json (on Mac, found at ~/Library/Ap
49
61
  ```
50
62
  Restart Claude if running
51
63
 
52
- ### Option 3: Checkout locally
64
+ ### Option 4: Checkout locally
53
65
  1. Clone and build:
54
66
  ```bash
55
67
  git clone https://github.com/wonderwhy-er/ClaudeComputerCommander.git
@@ -143,4 +155,4 @@ All contributions, big or small, are greatly appreciated!
143
155
 
144
156
  ## License
145
157
 
146
- MIT
158
+ MIT
package/dist/server.js CHANGED
@@ -7,9 +7,10 @@ import { executeCommand, readOutput, forceTerminate, listSessions } from './tool
7
7
  import { listProcesses, killProcess } from './tools/process.js';
8
8
  import { readFile, readMultipleFiles, writeFile, createDirectory, listDirectory, moveFile, searchFiles, getFileInfo, listAllowedDirectories, } from './tools/filesystem.js';
9
9
  import { parseEditBlock, performSearchReplace } from './tools/edit.js';
10
+ import { VERSION } from './version.js';
10
11
  export const server = new Server({
11
- name: "secure-terminal-server",
12
- version: "0.2.0",
12
+ name: "desktop-commander",
13
+ version: VERSION,
13
14
  }, {
14
15
  capabilities: {
15
16
  tools: {},
@@ -95,12 +95,12 @@ try {
95
95
  config.mcpServers.desktopCommander = serverConfig;
96
96
 
97
97
  // Add puppeteer server if not present
98
- if (!config.mcpServers.puppeteer) {
98
+ /*if (!config.mcpServers.puppeteer) {
99
99
  config.mcpServers.puppeteer = {
100
100
  "command": "npx",
101
101
  "args": ["-y", "@modelcontextprotocol/server-puppeteer"]
102
102
  };
103
- }
103
+ }*/
104
104
 
105
105
  // Write the updated config back
106
106
  writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2), 'utf8');
@@ -1,9 +1,19 @@
1
1
  import { CommandExecutionResult, ActiveSession } from './types.js';
2
+ interface CompletedSession {
3
+ pid: number;
4
+ output: string;
5
+ exitCode: number | null;
6
+ startTime: Date;
7
+ endTime: Date;
8
+ }
2
9
  export declare class TerminalManager {
3
10
  private sessions;
11
+ private completedSessions;
4
12
  executeCommand(command: string, timeoutMs?: number): Promise<CommandExecutionResult>;
5
13
  getNewOutput(pid: number): string | null;
6
14
  forceTerminate(pid: number): boolean;
7
15
  listActiveSessions(): ActiveSession[];
16
+ listCompletedSessions(): CompletedSession[];
8
17
  }
9
18
  export declare const terminalManager: TerminalManager;
19
+ export {};
@@ -3,6 +3,7 @@ import { DEFAULT_COMMAND_TIMEOUT } from './config.js';
3
3
  export class TerminalManager {
4
4
  constructor() {
5
5
  this.sessions = new Map();
6
+ this.completedSessions = new Map();
6
7
  }
7
8
  async executeCommand(command, timeoutMs = DEFAULT_COMMAND_TIMEOUT) {
8
9
  const process = spawn(command, [], { shell: true });
@@ -38,8 +39,21 @@ export class TerminalManager {
38
39
  isBlocked: true
39
40
  });
40
41
  }, timeoutMs);
41
- process.on('exit', () => {
42
+ process.on('exit', (code) => {
42
43
  if (process.pid) {
44
+ // Store completed session before removing active session
45
+ this.completedSessions.set(process.pid, {
46
+ pid: process.pid,
47
+ output: output + session.lastOutput, // Combine all output
48
+ exitCode: code,
49
+ startTime: session.startTime,
50
+ endTime: new Date()
51
+ });
52
+ // Keep only last 100 completed sessions
53
+ if (this.completedSessions.size > 100) {
54
+ const oldestKey = Array.from(this.completedSessions.keys())[0];
55
+ this.completedSessions.delete(oldestKey);
56
+ }
43
57
  this.sessions.delete(process.pid);
44
58
  }
45
59
  resolve({
@@ -51,13 +65,21 @@ export class TerminalManager {
51
65
  });
52
66
  }
53
67
  getNewOutput(pid) {
68
+ // First check active sessions
54
69
  const session = this.sessions.get(pid);
55
- if (!session) {
56
- return null;
70
+ if (session) {
71
+ const output = session.lastOutput;
72
+ session.lastOutput = '';
73
+ return output;
74
+ }
75
+ // Then check completed sessions
76
+ const completedSession = this.completedSessions.get(pid);
77
+ if (completedSession) {
78
+ // Format completion message with exit code and runtime
79
+ const runtime = (completedSession.endTime.getTime() - completedSession.startTime.getTime()) / 1000;
80
+ return `Process completed with exit code ${completedSession.exitCode}\nRuntime: ${runtime}s\nFinal output:\n${completedSession.output}`;
57
81
  }
58
- const output = session.lastOutput;
59
- session.lastOutput = '';
60
- return output;
82
+ return null;
61
83
  }
62
84
  forceTerminate(pid) {
63
85
  const session = this.sessions.get(pid);
@@ -86,5 +108,8 @@ export class TerminalManager {
86
108
  runtime: now.getTime() - session.startTime.getTime()
87
109
  }));
88
110
  }
111
+ listCompletedSessions() {
112
+ return Array.from(this.completedSessions.values());
113
+ }
89
114
  }
90
115
  export const terminalManager = new TerminalManager();
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ export declare const LongRunningTaskSchema: z.ZodObject<{
3
+ total_steps: z.ZodDefault<z.ZodNumber>;
4
+ step_duration: z.ZodDefault<z.ZodNumber>;
5
+ should_fail: z.ZodDefault<z.ZodBoolean>;
6
+ }, "strip", z.ZodTypeAny, {
7
+ total_steps: number;
8
+ step_duration: number;
9
+ should_fail: boolean;
10
+ }, {
11
+ total_steps?: number | undefined;
12
+ step_duration?: number | undefined;
13
+ should_fail?: boolean | undefined;
14
+ }>;
15
+ export declare function executeLongRunning(args: unknown, server: any, progressToken?: string): Promise<{
16
+ content: {
17
+ type: string;
18
+ text: string;
19
+ }[];
20
+ }>;
@@ -0,0 +1,59 @@
1
+ import { z } from "zod";
2
+ export const LongRunningTaskSchema = z.object({
3
+ total_steps: z.number().default(5).describe("Total number of steps to execute"),
4
+ step_duration: z.number().default(1000).describe("Duration of each step in milliseconds"),
5
+ should_fail: z.boolean().default(false).describe("Whether the task should fail midway through"),
6
+ });
7
+ export async function executeLongRunning(args, server, progressToken) {
8
+ const parsed = LongRunningTaskSchema.safeParse(args);
9
+ if (!parsed.success) {
10
+ throw new Error(`Invalid arguments for long_running_task: ${parsed.error}`);
11
+ }
12
+ const { total_steps, step_duration, should_fail } = parsed.data;
13
+ try {
14
+ for (let step = 1; step <= total_steps; step++) {
15
+ // Simulate work
16
+ await new Promise(resolve => setTimeout(resolve, step_duration));
17
+ // Send progress notification if we have a token
18
+ if (progressToken) {
19
+ await server.notification({
20
+ method: "notifications/progress",
21
+ params: {
22
+ progress: step,
23
+ total: total_steps,
24
+ progressToken,
25
+ detail: `Completed step ${step} of ${total_steps}${should_fail && step === Math.floor(total_steps / 2)
26
+ ? ' (about to fail)'
27
+ : ''}`
28
+ }
29
+ });
30
+ }
31
+ // If should_fail is true, fail halfway through
32
+ if (should_fail && step === Math.floor(total_steps / 2)) {
33
+ throw new Error("Task failed halfway through as requested");
34
+ }
35
+ }
36
+ return {
37
+ content: [{
38
+ type: "text",
39
+ text: `Long running task completed successfully after ${total_steps} steps`
40
+ }]
41
+ };
42
+ }
43
+ catch (error) {
44
+ // Send error notification if we have a token
45
+ if (progressToken) {
46
+ await server.notification({
47
+ method: "notifications/progress",
48
+ params: {
49
+ progress: Math.floor(total_steps / 2),
50
+ total: total_steps,
51
+ progressToken,
52
+ detail: error instanceof Error ? error.message : 'Unknown error',
53
+ error: true
54
+ }
55
+ });
56
+ }
57
+ throw error;
58
+ }
59
+ }
package/dist/types.d.ts CHANGED
@@ -22,3 +22,10 @@ export interface ActiveSession {
22
22
  isBlocked: boolean;
23
23
  runtime: number;
24
24
  }
25
+ export interface CompletedSession {
26
+ pid: number;
27
+ output: string;
28
+ exitCode: number | null;
29
+ startTime: Date;
30
+ endTime: Date;
31
+ }
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.1.19";
@@ -0,0 +1 @@
1
+ export const VERSION = '0.1.19';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wonderwhy-er/desktop-commander",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "MCP server for terminal operations and file editing",
5
5
  "license": "MIT",
6
6
  "author": "Eduards Ruzga",
@@ -15,6 +15,10 @@
15
15
  "dist"
16
16
  ],
17
17
  "scripts": {
18
+ "sync-version": "node scripts/sync-version.js",
19
+ "bump": "node scripts/sync-version.js --bump",
20
+ "bump:minor": "node scripts/sync-version.js --bump --minor",
21
+ "bump:major": "node scripts/sync-version.js --bump --major",
18
22
  "build": "tsc && shx cp setup-claude-server.js dist/ && shx chmod +x dist/*.js",
19
23
  "watch": "tsc --watch",
20
24
  "start": "node dist/index.js",
@@ -60,4 +64,4 @@
60
64
  "shx": "^0.3.4",
61
65
  "typescript": "^5.3.3"
62
66
  }
63
- }
67
+ }