import type { Client, ClientChannel } from 'ssh2';
import type { CommandResult } from '../types.js';
export interface ExecOptions {
cwd?: string;
env?: Record<string, string>;
pty?: boolean;
timeout?: number;
}
export async function executeCommand(
client: Client,
command: string,
options: ExecOptions = {}
): Promise<CommandResult> {
return new Promise((resolve, reject) => {
const { cwd, env, pty = false, timeout = 60000 } = options;
// Prepare the command with working directory if specified
let fullCommand = command;
if (cwd) {
fullCommand = `cd ${escapeShellArg(cwd)} && ${command}`;
}
const execOptions: Record<string, unknown> = {};
if (env) {
execOptions.env = env;
}
if (pty) {
execOptions.pty = {
rows: 24,
cols: 80,
height: 480,
width: 640,
term: 'xterm-256color',
};
}
let stdout = '';
let stderr = '';
let timeoutId: NodeJS.Timeout | undefined;
client.exec(fullCommand, execOptions, (err, channel) => {
if (err) {
reject(new Error(`Failed to execute command: ${err.message}`));
return;
}
// Set timeout
if (timeout > 0) {
timeoutId = setTimeout(() => {
channel.close();
reject(new Error(`Command timed out after ${timeout}ms`));
}, timeout);
}
channel.on('data', (data: Buffer) => {
stdout += data.toString();
});
channel.stderr.on('data', (data: Buffer) => {
stderr += data.toString();
});
channel.on('close', (code: number | null) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
resolve({
stdout: stdout.trim(),
stderr: stderr.trim(),
exitCode: code ?? 0,
});
});
channel.on('error', (channelErr: Error) => {
if (timeoutId) {
clearTimeout(timeoutId);
}
reject(new Error(`Channel error: ${channelErr.message}`));
});
});
});
}
export async function createShellChannel(
client: Client,
cols: number = 80,
rows: number = 24
): Promise<ClientChannel> {
return new Promise((resolve, reject) => {
client.shell(
{
rows,
cols,
height: rows * 20,
width: cols * 8,
term: 'xterm-256color',
},
(err, channel) => {
if (err) {
reject(new Error(`Failed to create shell: ${err.message}`));
return;
}
resolve(channel);
}
);
});
}
function escapeShellArg(arg: string): string {
// Escape single quotes and wrap in single quotes
return `'${arg.replace(/'/g, "'\\''")}'`;
}