@wonderwhy-er/desktop-commander
Version:
MCP server for terminal operations and file editing
201 lines (200 loc) • 6.62 kB
JavaScript
/**
* 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)`
};
}
}