diff --git a/package-lock.json b/package-lock.json index 5fae273..ab34f67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.0", + "version": "0.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@wonderwhy-er/desktop-commander", - "version": "0.2.0", + "version": "0.2.1", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.8.0", diff --git a/setup-claude-server.js b/setup-claude-server.js index 1df13e0..9143ed9 100755 --- a/setup-claude-server.js +++ b/setup-claude-server.js @@ -518,6 +518,16 @@ export default async function setup() { const setupStep = addSetupStep('main_setup'); const debugMode = isDebugMode(); + // 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) { logToFile('Debug mode enabled. Will configure with Node.js inspector options.'); await trackEvent('npx_setup_debug_mode', { enabled: true }); @@ -726,6 +736,8 @@ export default async function setup() { total_time_ms: Date.now() - setupStartTime }); + + return true; } catch (error) { updateSetupStep(setupStep, 'failed', error); diff --git a/src/server.ts b/src/server.ts index 2c1fe5f..b306db2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -335,6 +335,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { name: "read_output", description: ` Read new output from a running terminal session. + Set timeout_ms for long running commands. ${CMD_PREFIX_DESCRIPTION}`, inputSchema: zodToJsonSchema(ReadOutputArgsSchema), diff --git a/src/terminal-manager.ts b/src/terminal-manager.ts index e6c164a..ebb599d 100644 --- a/src/terminal-manager.ts +++ b/src/terminal-manager.ts @@ -126,6 +126,15 @@ export class TerminalManager { return null; } + /** + * Get a session by PID + * @param pid Process ID + * @returns The session or undefined if not found + */ + getSession(pid: number): TerminalSession | undefined { + return this.sessions.get(pid); + } + forceTerminate(pid: number): boolean { const session = this.sessions.get(pid); if (!session) { diff --git a/src/tools/execute.ts b/src/tools/execute.ts index 5d05e14..7e5d462 100644 --- a/src/tools/execute.ts +++ b/src/tools/execute.ts @@ -62,21 +62,87 @@ export async function executeCommand(args: unknown): Promise { } export async function readOutput(args: unknown): Promise { - const parsed = ReadOutputArgsSchema.safeParse(args); - if (!parsed.success) { - return { - content: [{ type: "text", text: `Error: Invalid arguments for read_output: ${parsed.error}` }], - isError: true, - }; - } + const parsed = ReadOutputArgsSchema.safeParse(args); + if (!parsed.success) { + return { + content: [{ type: "text", text: `Error: Invalid arguments for read_output: ${parsed.error}` }], + isError: true, + }; + } + + 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: Promise = 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: NodeJS.Timeout | null = null; + let timeout: NodeJS.Timeout | null = null; + + const cleanup = () => { + if (interval) { + clearInterval(interval); + interval = null; + } + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + }; + + const resolveOnce = (value: string, 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, + }; + } - const output = terminalManager.getNewOutput(parsed.data.pid); 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)' : '') }], }; } diff --git a/src/tools/schemas.ts b/src/tools/schemas.ts index c2e1c64..3c03cbd 100644 --- a/src/tools/schemas.ts +++ b/src/tools/schemas.ts @@ -16,12 +16,13 @@ export const ListProcessesArgsSchema = z.object({}); // Terminal tools schemas export const ExecuteCommandArgsSchema = z.object({ command: z.string(), - timeout_ms: z.number().optional(), + timeout_ms: z.number(), shell: z.string().optional(), }); export const ReadOutputArgsSchema = z.object({ pid: z.number(), + timeout_ms: z.number().optional(), }); export const ForceTerminateArgsSchema = z.object({