{"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../src/ssh.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,SAAS;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,eAAO,MAAM,OAAO;;oCAmDnB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa;;;;iCAwCzB,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,OAAO,6EAsCnB,CAAC","sourcesContent":["import { type SpawnOptions, spawn } from \"child_process\";\n\nexport interface SSHResult {\n\tstdout: string;\n\tstderr: string;\n\texitCode: number;\n}\n\n/**\n * Execute an SSH command and return the result\n */\nexport const sshExec = async (\n\tsshCmd: string,\n\tcommand: string,\n\toptions?: { keepAlive?: boolean },\n): Promise<SSHResult> => {\n\treturn new Promise((resolve) => {\n\t\t// Parse SSH command (e.g., \"ssh root@1.2.3.4\" or \"ssh -p 22 root@1.2.3.4\")\n\t\tconst sshParts = sshCmd.split(\" \").filter((p) => p);\n\t\tconst sshBinary = sshParts[0];\n\t\tlet sshArgs = [...sshParts.slice(1)];\n\n\t\t// Add SSH keepalive options for long-running commands\n\t\tif (options?.keepAlive) {\n\t\t\t// ServerAliveInterval=30 sends keepalive every 30 seconds\n\t\t\t// ServerAliveCountMax=120 allows up to 120 failures (60 minutes total)\n\t\t\tsshArgs = [\"-o\", \"ServerAliveInterval=30\", \"-o\", \"ServerAliveCountMax=120\", ...sshArgs];\n\t\t}\n\n\t\tsshArgs.push(command);\n\n\t\tconst proc = spawn(sshBinary, sshArgs, {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"pipe\"],\n\t\t});\n\n\t\tlet stdout = \"\";\n\t\tlet stderr = \"\";\n\n\t\tproc.stdout.on(\"data\", (data) => {\n\t\t\tstdout += data.toString();\n\t\t});\n\n\t\tproc.stderr.on(\"data\", (data) => {\n\t\t\tstderr += data.toString();\n\t\t});\n\n\t\tproc.on(\"close\", (code) => {\n\t\t\tresolve({\n\t\t\t\tstdout,\n\t\t\t\tstderr,\n\t\t\t\texitCode: code || 0,\n\t\t\t});\n\t\t});\n\n\t\tproc.on(\"error\", (err) => {\n\t\t\tresolve({\n\t\t\t\tstdout,\n\t\t\t\tstderr: err.message,\n\t\t\t\texitCode: 1,\n\t\t\t});\n\t\t});\n\t});\n};\n\n/**\n * Execute an SSH command with streaming output to console\n */\nexport const sshExecStream = async (\n\tsshCmd: string,\n\tcommand: string,\n\toptions?: { silent?: boolean; forceTTY?: boolean; keepAlive?: boolean },\n): Promise<number> => {\n\treturn new Promise((resolve) => {\n\t\tconst sshParts = sshCmd.split(\" \").filter((p) => p);\n\t\tconst sshBinary = sshParts[0];\n\n\t\t// Build SSH args\n\t\tlet sshArgs = [...sshParts.slice(1)];\n\n\t\t// Add -t flag if requested and not already present\n\t\tif (options?.forceTTY && !sshParts.includes(\"-t\")) {\n\t\t\tsshArgs = [\"-t\", ...sshArgs];\n\t\t}\n\n\t\t// Add SSH keepalive options for long-running commands\n\t\tif (options?.keepAlive) {\n\t\t\t// ServerAliveInterval=30 sends keepalive every 30 seconds\n\t\t\t// ServerAliveCountMax=120 allows up to 120 failures (60 minutes total)\n\t\t\tsshArgs = [\"-o\", \"ServerAliveInterval=30\", \"-o\", \"ServerAliveCountMax=120\", ...sshArgs];\n\t\t}\n\n\t\tsshArgs.push(command);\n\n\t\tconst spawnOptions: SpawnOptions = options?.silent\n\t\t\t? { stdio: [\"ignore\", \"ignore\", \"ignore\"] }\n\t\t\t: { stdio: \"inherit\" };\n\n\t\tconst proc = spawn(sshBinary, sshArgs, spawnOptions);\n\n\t\tproc.on(\"close\", (code) => {\n\t\t\tresolve(code || 0);\n\t\t});\n\n\t\tproc.on(\"error\", () => {\n\t\t\tresolve(1);\n\t\t});\n\t});\n};\n\n/**\n * Copy a file to remote via SCP\n */\nexport const scpFile = async (sshCmd: string, localPath: string, remotePath: string): Promise<boolean> => {\n\t// Extract host from SSH command\n\tconst sshParts = sshCmd.split(\" \").filter((p) => p);\n\tlet host = \"\";\n\tlet port = \"22\";\n\tlet i = 1; // Skip 'ssh'\n\n\twhile (i < sshParts.length) {\n\t\tif (sshParts[i] === \"-p\" && i + 1 < sshParts.length) {\n\t\t\tport = sshParts[i + 1];\n\t\t\ti += 2;\n\t\t} else if (!sshParts[i].startsWith(\"-\")) {\n\t\t\thost = sshParts[i];\n\t\t\tbreak;\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\tif (!host) {\n\t\tconsole.error(\"Could not parse host from SSH command\");\n\t\treturn false;\n\t}\n\n\t// Build SCP command\n\tconst scpArgs = [\"-P\", port, localPath, `${host}:${remotePath}`];\n\n\treturn new Promise((resolve) => {\n\t\tconst proc = spawn(\"scp\", scpArgs, { stdio: \"inherit\" });\n\n\t\tproc.on(\"close\", (code) => {\n\t\t\tresolve(code === 0);\n\t\t});\n\n\t\tproc.on(\"error\", () => {\n\t\t\tresolve(false);\n\t\t});\n\t});\n};\n"]}