UNPKG

@wonderwhy-er/desktop-commander

Version:

MCP server for terminal operations and file editing

201 lines (200 loc) 6.62 kB
/** * Client-side utility for SSH interactions */ export class SSHClient { constructor(apiClient) { this.apiClient = apiClient; this.activeSessions = {}; } /** * Connect to an SSH server * @param host - SSH host to connect to * @param options - Connection options * @returns Connection information */ async connect(host, options = {}) { try { // Check if we already have an active session for this host for (const pid in this.activeSessions) { const session = this.activeSessions[pid]; if (session.host === host && session.username === options.username) { return { success: true, pid: parseInt(pid), host, username: options.username, message: `Reusing existing SSH session to ${host} (PID: ${pid})` }; } } // Create new session const response = await this.apiClient.executeCommand('ssh.create', { host, ...options }); if (response.success && response.pid) { this.activeSessions[response.pid] = { host, username: options.username, pid: response.pid, createdAt: new Date(), lastActivity: new Date() }; } return response; } catch (error) { console.error('SSH connection failed:', error); return { success: false, error: error.message || 'SSH connection failed' }; } } /** * Execute a command on the remote server * @param pid - Process ID of the SSH session * @param command - Command to execute * @param options - Execution options * @returns Command results */ async executeCommand(pid, command, options = {}) { try { // Update last activity time const pidNum = typeof pid === 'string' ? parseInt(pid) : pid; if (this.activeSessions[pidNum]) { this.activeSessions[pidNum].lastActivity = new Date(); } return await this.apiClient.executeCommand('ssh.execute', { pid, command, options }); } catch (error) { console.error('SSH command execution failed:', error); return { success: false, error: error.message || 'SSH command execution failed' }; } } /** * Execute a command on a specific host (creates session if needed) * @param host - SSH host * @param command - Command to execute * @param options - Connection and execution options * @returns Command results */ async executeCommandOnHost(host, command, options = {}) { try { // First connect (or reuse connection) const connResult = await this.connect(host, options); if (!connResult.success) { return connResult; } // Then execute the command const result = await this.executeCommand(connResult.pid, command, options); // Include host info in the result return { ...result, host, pid: connResult.pid }; } catch (error) { console.error('SSH command execution failed:', error); return { success: false, error: error.message || 'SSH command execution failed' }; } } /** * Execute multiple commands in sequence * @param pid - Process ID of the SSH session * @param commands - List of commands to execute * @param options - Execution options * @returns Results for each command */ async executeSequence(pid, commands, options = {}) { const results = []; for (const command of commands) { const result = await this.executeCommand(pid, command, options); results.push(result); // Stop on first error if requested if (options.stopOnError && !result.success) { break; } } return { success: results.every(r => r.success), results, completedCommands: results.length }; } /** * Execute a multi-line script as separate commands * @param pid - Process ID of the SSH session * @param script - Multi-line script * @param options - Execution options * @returns Script execution results */ async executeScript(pid, script, options = {}) { const commands = script .split('\n') .map(line => line.trim()) .filter(line => line && !line.startsWith('#')); return await this.executeSequence(pid, commands, options); } /** * Close an SSH session * @param pid - Process ID to close * @returns Result indicating success */ async disconnect(pid) { try { const response = await this.apiClient.executeCommand('repl.close', { pid }); const pidNum = typeof pid === 'string' ? parseInt(pid) : pid; if (response.success) { delete this.activeSessions[pidNum]; } return response; } catch (error) { console.error('Failed to close SSH session:', error); return { success: false, error: error.message || 'Failed to close SSH session' }; } } /** * List all active SSH sessions * @returns List of active sessions */ listSessions() { return Object.values(this.activeSessions); } /** * Close all SSH sessions * @returns Result with number of closed sessions */ async disconnectAll() { const pids = Object.keys(this.activeSessions); let closedCount = 0; for (const pid of pids) { const result = await this.disconnect(pid); if (result.success) { closedCount++; } } return { success: true, closedCount, message: `Closed ${closedCount} SSH session(s)` }; } }