import { execFileSync } from "node:child_process"; import { existsSync, readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; export type EffectiveSshConfig = Map; export function parseSshG(text: string): EffectiveSshConfig { const values: EffectiveSshConfig = new Map(); for (const line of text.split(/\r?\n/)) { const separator = line.indexOf(" "); if (separator < 1) continue; const key = line.slice(0, separator).trim().toLowerCase(); const value = line.slice(separator + 1).trim(); if (!key || !value) continue; const current = values.get(key) ?? []; current.push(value); values.set(key, current); } return values; } export function resolveOpenSshAlias(alias: string): EffectiveSshConfig { if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(alias)) throw new Error(`invalid SSH alias: ${alias}`); const output = execFileSync("ssh", ["-G", "--", alias], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }); return parseSshG(output); } export function effectiveValue(config: EffectiveSshConfig, key: string): string | undefined { return config.get(key.toLowerCase())?.at(-1); } export function effectiveValues(config: EffectiveSshConfig, key: string): string[] { return config.get(key.toLowerCase()) ?? []; } export function effectiveIdentityAgent( config: EffectiveSshConfig, env: NodeJS.ProcessEnv = process.env, ): string | undefined { const configured = effectiveValue(config, "identityagent")?.replace(/^"|"$/g, ""); if (!configured || configured.toLowerCase() === "none") return undefined; if (configured === "SSH_AUTH_SOCK" || configured === "$SSH_AUTH_SOCK" || configured === "${SSH_AUTH_SOCK}") { return env.SSH_AUTH_SOCK; } return configured; } export function listDirectSshAliases(configPath = join(homedir(), ".ssh", "config")): string[] { if (!existsSync(configPath)) return []; const aliases: string[] = []; const text = readFileSync(configPath, "utf8"); for (const line of text.split(/\r?\n/)) { const match = /^\s*Host\s+(.+?)\s*$/i.exec(line); if (!match) continue; for (const alias of match[1].split(/\s+/)) { if (!alias || alias.includes("*") || alias.includes("?") || alias.startsWith("!")) continue; if (!aliases.includes(alias)) aliases.push(alias); } } return aliases; }