ssh-fleet-mcp-server 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["nodeSpawn","sleep","constants","SSHClient","parseToml","Set","constants$1","Set$1","resolveAuth","options","Right","expandResult","Path","Left","expanded","Fs","err","contents","varName","keyValue","sock","PASS_THROUGH","childEnv","v","k","extra","nodeProcessRunner","child","spawn","cmd","args","env","timeoutMs","out","state","finish","timer","resolve","result","pid","Try","d","reject","code","input","listeners","cb","NO_PROMPT","PROBES","checkOpenSsh","present","proc","missing","i","sleep$1","ms","waitFor","check","deadline","alive","isStale","f","LOCK","isOwn","readPid","readFileSync","path","isLive","killIfOurs","ps","argv0","basename","program","recover","join","dir","masterPids","readdirSync","unlinkSync","prepare","mkdirSync","e","foreign","chmodSync","sinceBoot","statSync","uptime","lock","holder","writeFileSync","release","rmdirSync","VALID_CA_NAME","listKeys","r","agentSock","line","loadCa","fail","ca","step","detail","key","agentEnv","before","add","added","pubPath","stopAgent","agent","startCaCustody","ready","runtimeDir","existsSync","loaded","Either","cas","pubs","Map","name","socketPathFor","digest","createHash","host","serials","nextSerial","candidate","nowMs","certOptions","sourceAddress","cidr","mint","req","caPub","custody","keyPath","randomBytes","certPath","files","dispose","gen","at","now","serial","sign","exitLabel","TIMED_OUT_RE","isTimedOutMessage","message","SESSION_RE","validateSession","INPUT_ERROR_PREFIXES","isTmuxInputError","prefix","shellQuote","s","clampLines","n","trimTrailingWhitespace","ALLOWED_KEYS","validateKey","withTmuxPath","command","buildList","buildSend","session","ensure","send","enter","submit","buildRead","lines","buildKeys","keys","bad","TMUX_MISSING_MSG","SESSION_MISSING_RE","NO_SERVER_RE","isTmuxMissing","failure","label","interpretList","l","interpretAck","interpretRead","interpretKeys","tmuxList","runner","tmuxSend","p","tmuxRead","tmuxKeys","signalCode","Option","signal","channelResult","timedOutResult","execOutput","sleep","target","c","masterArgs","socketPath","m","knownHosts","execArgs","once","run","fn","opensshConnect","deps","readyTimeoutMs","drainTimeoutMs","setTimer","clearTimer","t","exitOf","drain","live","control","op","waitReady","minted","pidFile","closeCbs","fireClose","retireTimer","close","entry","opts","entries","defaultConnect","client","Client","rej","errb","res","stream","sshConfig","createPool","states","hosts","h","allStates","st","dropConn","ensureConn","connect","acquireSlot","wake","idx","releaseSlot","next","hostName","UNKNOWN_HOST_HINT","isUnknownHost","resultError","UserError","timeoutError","deniedError","routedError","AGENT_UNKNOWN","DEFAULT_AUDIT_PATH","reportedClient","reported","clientLabel","raw","excerpt","messageOf","qualify","rule","verdictFields","call","createAuditor","write","sink","id","newId","who","started","outcome","finished","summarize","APPEND_NOFOLLOW","constants","withAppendFd","fd","openSync","file","use","closeSync","appendLine","text","expected","written","writeSync","ensurePrivate","fstatSync","fchmodSync","openAuditLog","dirname","buildInfo","\"commit\"","\"branch\"","\"date\"","MODES","GATES","KEYS","EMPTY_TABLE","isTable","oneOf","values","error","strings","x","errorOf","msg","parsePolicyTable","where","mode","tmux","allow","needsApproval","agents","errors","nonEmpty","o","xs","compile","results","patterns","wrap","re","errorsOf","errs","resolvePolicy","defaults","allowSrc","needsSrc","ALLOW","allowBy","denyBy","READONLY_REJECT","TOO_LONG","listed","evaluateTmuxInput","policy","evaluateExec","tooLong","tripped","evaluate","DEFAULT_EXEC_TIMEOUT_MS","DEFAULT_MAX_EXEC_TIMEOUT_MS","DEFAULT_POOL","str","num","resolveHostAuth","modes","auth","MAX_TIMER_MS","poolValue","fallback","parsePool","fields","maxPerHost","idleTimeoutMs","acquireTimeoutMs","execTimeoutMs","maxExecTimeoutMs","validPrincipal","ipv4Value","addr","acc","octet","ipv6Value","head","tail","headGroups","tailGroups","fill","groups","g","hostBitsClear","value","width","validCidr","parts","bits","family","isIP","parseCa","refs","envName","principal","parseAudit","a","tomlError","summary","parseFleet","parsed","parse","toml","defaultPort","defaultUser","defaultSession","rawHosts","defaultsPolicy","defaultsTable","names","rawCas","caNames","parseHost","index","address","table","kind","audit","casResults","caErrors","pool","List","fleetWarnings","fleet","hostRows","row","stateDir","homedir","defaultRuntimeDir","xdg","defaultKnownHosts","fromFile","info","lstat","readFile","readCaKey","ref","caKeySource","selfTestCas","sshConfigFor","toSshConfig","buildFleetRunner","caHosts","sshHosts","resolved","cfg","authErrors","makeRunner","realOpenssh","socket","wiring","caEntry","sessions","logOutput","policies","caHostByName","caByName","sshPool","caPools","caPoolFor","existing","openssh","routeConnected","authErrorByName","route","startedAt","lastErrors","effectiveTimeout","requested","runOn","caStatus","pools","unknownHost","resolveConfigPath","flag","argv","home","isCode","ensureConfig","config","missingKeyFile","ensureCaKeyFiles","made","enrollLine","publicKey","hostAddress","u","KEY_SOURCE","hostReport","ctx","enroll","pub","caReport","configDrift","onDisk","setupReport","drift","nextSteps","readCheckConfig","checkRuntimeDir","outcomeOf","runCheck","addrWidth","probe","outcomes","unwrap","bytes","noSummary","makeHandlers","gated","summarizeExec","auditor","setup","timeout_ms","output","press","count","transcript","pkgVersion","createRequire","fatal","startCa","created","configured","selfTest","buildRunnerOrExit","caPublicKeysOf","parseOrExit","w","checkMain","missingKeyCas","probeFleet","randomUUID","main","configPath","content","caCreated","CLIENT_LABEL_ENV","handlers","context","server","createServer","hostParam","z","shutdown"],"sources":["../../core/src/auth.ts","../../core/src/proc.ts","../../core/src/ca.ts","../../core/src/tmux.ts","../../core/src/ssh.ts","../../core/src/openssh.ts","../../core/src/pool.ts","../src/tool-errors.ts","../src/audit.ts","../src/build-info.ts","../src/policy.ts","../src/fleet.ts","../src/runners.ts","../src/template.ts","../src/setup.ts","../src/check.ts","../src/handlers.ts","../src/index.ts"],"sourcesContent":["import type { Option } from \"functype\"\nimport { type Either, Left, Right } from \"functype\"\nimport { Fs, Path } from \"functype-os\"\nimport { type ConnectConfig } from \"ssh2\"\n\nexport type ResolveAuthOptions = Readonly<{\n password: Option<string>\n keyPath: Option<string>\n keyEnvVar: Option<string>\n useAgent: boolean\n}>\n\n// Auth precedence: password → key file → key from env var → ssh-agent → empty.\nexport const resolveAuth = async (options: ResolveAuthOptions): Promise<Either<string, Partial<ConnectConfig>>> => {\n if (options.password.isSome()) {\n return Right<string, Partial<ConnectConfig>>({ password: options.password.value })\n }\n if (options.keyPath.isSome()) {\n const expandResult = Path.expand(options.keyPath.value)\n if (expandResult.isLeft()) {\n return Left<string, Partial<ConnectConfig>>(\n `Invalid SSH key path ${options.keyPath.value}: ${expandResult.value.message}`,\n )\n }\n const expanded = expandResult.value\n const result = await Fs.readFile(expanded)\n return result.fold<Either<string, Partial<ConnectConfig>>>(\n (err) => Left<string, Partial<ConnectConfig>>(`Failed to read SSH key ${expanded}: ${err.message}`),\n (contents) => Right<string, Partial<ConnectConfig>>({ privateKey: contents }),\n )\n }\n if (options.keyEnvVar.isSome()) {\n const varName = options.keyEnvVar.value\n const keyValue = process.env[varName]\n if (!keyValue) {\n return Left<string, Partial<ConnectConfig>>(`--key-env=${varName} but environment variable is not set or empty`)\n }\n return Right<string, Partial<ConnectConfig>>({ privateKey: keyValue })\n }\n if (options.useAgent) {\n const sock = process.env.SSH_AUTH_SOCK\n if (!sock) {\n return Left<string, Partial<ConnectConfig>>(\n \"--agent set but SSH_AUTH_SOCK is not set; start ssh-agent or unlock your password manager's SSH agent\",\n )\n }\n return Right<string, Partial<ConnectConfig>>({ agent: sock })\n }\n return Right<string, Partial<ConnectConfig>>({})\n}\n","import { spawn as nodeSpawn } from \"node:child_process\"\n\nimport { Try } from \"functype\"\n\n/** `timedOut` is set when `run`'s timeout killed the child; stdout and stderr then hold what it wrote first. */\nexport type ProcResult = Readonly<{ code: number; stdout: string; stderr: string; timedOut?: true }>\n\n// The whole environment a child gets; never merged with process.env.\nexport type Env = Readonly<Record<string, string>>\n\n/**\n * A long-lived child, such as ssh-agent or an ssh ControlMaster.\n * @interop mirrors Node's child_process: the exit code is null when a signal ends the process, and pid is undefined when spawning failed.\n */\nexport type Child = Readonly<{\n onExit: (cb: (code: number | null) => void) => void\n kill: () => void\n stderr: () => string\n exited: () => boolean\n pid: () => number | undefined\n}>\n\nexport type ProcessRunner = Readonly<{\n run: (\n cmd: string,\n args: ReadonlyArray<string>,\n opts: Readonly<{ env: Env; input?: string; timeoutMs?: number }>,\n ) => Promise<ProcResult>\n spawn: (cmd: string, args: ReadonlyArray<string>, opts: Readonly<{ env: Env }>) => Child\n}>\n\n// Only these pass through from the hub's own environment. In particular no SSH_AUTH_SOCK (the user's\n// agent) and no DISPLAY, so an OpenSSH child can't reach an agent or a GUI prompt it wasn't given.\nconst PASS_THROUGH: ReadonlyArray<string> = [\"PATH\", \"HOME\", \"USER\", \"LOGNAME\", \"LANG\"]\n\nexport const childEnv = (extra: Env = {}): Env => ({\n ...Object.fromEntries(\n PASS_THROUGH.flatMap((k) => {\n const v = process.env[k]\n return v === undefined ? [] : [[k, v]]\n }),\n ),\n ...extra,\n})\n\nconst STDERR_TAIL = 4096\n\n/** @interop Node's child_process is callback- and event-based; these adapters are the boundary. */\nexport const nodeProcessRunner: ProcessRunner = {\n // Resolves with the exit code, non-zero included; rejects only when the program can't start (ENOENT).\n // With `timeoutMs`, the child runs in its own process group with no controlling terminal (so a\n // prompt fails instead of waiting), and the whole group is killed when time runs out.\n run: (cmd, args, { env, input, timeoutMs }) =>\n new Promise<ProcResult>((resolve, reject) => {\n const child = nodeSpawn(cmd, [...args], {\n env: { ...env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n detached: timeoutMs !== undefined,\n })\n const out: Buffer[] = []\n const err: Buffer[] = []\n const state = { done: false }\n const finish = (result: ProcResult): void => {\n if (state.done) return\n state.done = true\n clearTimeout(timer)\n resolve(result)\n }\n const timer = setTimeout(\n () => {\n const { pid } = child\n if (pid !== undefined) Try(() => process.kill(-pid, \"SIGKILL\"))\n finish({\n code: -1,\n stdout: Buffer.concat(out).toString(),\n stderr: Buffer.concat(err).toString(),\n timedOut: true,\n })\n },\n timeoutMs ?? 2 ** 31 - 1,\n )\n child.stdout.on(\"data\", (d: Buffer) => out.push(d))\n child.stderr.on(\"data\", (d: Buffer) => err.push(d))\n child.on(\"error\", (e) => {\n clearTimeout(timer)\n reject(e)\n })\n child.on(\"close\", (code) =>\n finish({ code: code ?? -1, stdout: Buffer.concat(out).toString(), stderr: Buffer.concat(err).toString() }),\n )\n child.stdin.on(\"error\", () => undefined)\n child.stdin.end(input ?? \"\")\n }),\n\n spawn: (cmd, args, { env }) => {\n const child = nodeSpawn(cmd, [...args], { env: { ...env }, stdio: [\"ignore\", \"ignore\", \"pipe\"] })\n const state = { tail: \"\", exited: false, code: null as number | null }\n const listeners: Array<(code: number | null) => void> = []\n const finish = (code: number | null): void => {\n if (state.exited) return\n state.exited = true\n state.code = code\n listeners.splice(0).forEach((cb) => cb(code))\n }\n child.stderr.on(\"data\", (d: Buffer) => {\n state.tail = (state.tail + d.toString()).slice(-STDERR_TAIL)\n })\n child.on(\"exit\", (code) => finish(code))\n child.on(\"error\", (e) => {\n state.tail = (state.tail + e.message).slice(-STDERR_TAIL)\n finish(null)\n })\n return {\n onExit: (cb) => {\n if (state.exited) queueMicrotask(() => cb(state.code))\n else listeners.push(cb)\n },\n kill: () => {\n if (!state.exited) child.kill()\n },\n stderr: () => state.tail,\n exited: () => state.exited,\n pid: () => child.pid,\n }\n },\n}\n","import { createHash, randomBytes } from \"node:crypto\"\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\"\nimport { uptime } from \"node:os\"\nimport { basename, join } from \"node:path\"\n\nimport type { Option } from \"functype\"\nimport { Either, Left, Map, Right, Try } from \"functype\"\n\nimport { type Child, childEnv, type Env, type ProcessRunner } from \"./proc.js\"\n\n// Where a CA's private key comes from at startup; the orchestrator builds these from config.\nexport type CaKeySource = Readonly<{ name: string; key: () => Promise<Either<string, string>> }>\n\nexport type Custody = Readonly<{\n runtimeDir: string\n agentSock: string\n pubPath: (ca: string) => Option<string>\n shutdown: () => Promise<void>\n}>\n\n// A passphrase-protected key must fail at once, with or without a terminal, never wait for input.\nconst NO_PROMPT: Env = { SSH_ASKPASS: \"/bin/false\", SSH_ASKPASS_REQUIRE: \"force\" }\n\n// `-?` makes each tool print usage and exit; a bare `ssh-agent` would start an agent.\nconst PROBES: ReadonlyArray<readonly [string, ReadonlyArray<string>]> = [\n [\"ssh\", [\"-V\"]],\n [\"ssh-keygen\", [\"-?\"]],\n [\"ssh-agent\", [\"-?\"]],\n [\"ssh-add\", [\"-?\"]],\n]\n\nexport const checkOpenSsh = async (proc: ProcessRunner): Promise<Either<string, void>> => {\n const present = await Promise.all(\n PROBES.map(([cmd, args]) =>\n proc.run(cmd, args, { env: childEnv() }).then(\n () => true,\n () => false,\n ),\n ),\n )\n const missing = PROBES.filter((_, i) => !present[i]).map(([cmd]) => cmd)\n return missing.length === 0\n ? Right<string, void>(undefined)\n : Left<string, void>(`OpenSSH binaries missing: ${missing.join(\", \")}`)\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\nconst waitFor = (check: () => boolean, deadline: number): Promise<boolean> =>\n check()\n ? Promise.resolve(true)\n : Date.now() > deadline\n ? Promise.resolve(false)\n : sleep(50).then(() => waitFor(check, deadline))\n\nconst alive = (pid: number): boolean =>\n Try(() => process.kill(pid, 0)).fold(\n () => false,\n () => true,\n )\n\nconst isStale = (f: string): boolean =>\n f === \"agent.sock\" ||\n f === \"agent.pid\" ||\n /^m-.*\\.(sock|pid)$/.test(f) ||\n f.startsWith(\"k-\") ||\n /^ca-.*\\.pub$/.test(f)\n\n// Holds the PID of the hub using the runtime dir, so a second hub can't mistake it for a crash.\nconst LOCK = \"hub.lock\"\n\n// Every file a hub creates in its runtime dir; anything else there belongs to someone else.\nconst isOwn = (f: string): boolean => f === LOCK || isStale(f)\n\nconst readPid = (path: string): number => Try(() => Number(readFileSync(path, \"utf8\").trim())).orElse(Number.NaN)\n\nconst isLive = (pid: number): boolean => Number.isInteger(pid) && pid > 0 && alive(pid)\n\n// Kills `pid` only if `ps` shows `program` running on `sock`, a path inside this hub's runtime dir.\n// A reused PID, or the operator's own ssh or ssh-agent, can't match. `args` rather than `comm`:\n// macOS prints `comm` as a full path, and OpenSSH may retitle a master `ssh: <sock> [mux]`.\nconst killIfOurs = async (pid: number, program: string, sock: string, proc: ProcessRunner): Promise<void> => {\n if (!isLive(pid)) return\n const ps = await proc\n .run(\"ps\", [\"-ww\", \"-p\", String(pid), \"-o\", \"args=\"], { env: childEnv() })\n .catch(() => ({ code: 1, stdout: \"\", stderr: \"\" }))\n const args = ps.stdout.trim()\n const argv0 = basename(args.split(\" \")[0] ?? \"\").replace(/:$/, \"\")\n if (ps.code !== 0 || argv0 !== program || !args.includes(sock)) return\n Try(() => process.kill(pid, \"SIGTERM\"))\n await waitFor(() => !alive(pid), Date.now() + 2000)\n}\n\n// A previous run that crashed leaves its agent running (holding the CA key), its masters running\n// (holding authenticated sessions to fleet hosts), and its sockets behind; ssh-agent refuses an\n// existing socket path. Kill what is still ours, then clear the files.\nconst recover = async (dir: string, proc: ProcessRunner): Promise<void> => {\n await killIfOurs(readPid(join(dir, \"agent.pid\")), \"ssh-agent\", join(dir, \"agent.sock\"), proc)\n const masterPids = readdirSync(dir).filter((f) => /^m-.*\\.pid$/.test(f))\n await Promise.all(\n masterPids.map((f) => killIfOurs(readPid(join(dir, f)), \"ssh\", join(dir, f.replace(/\\.pid$/, \".sock\")), proc)),\n )\n readdirSync(dir)\n .filter(isStale)\n .forEach((f) => Try(() => unlinkSync(join(dir, f))))\n}\n\n// Creates the runtime dir, or takes over an existing one only if it holds nothing but hub files:\n// the hub chmods it and deletes its own files from it, so it must not be anyone else's directory.\nconst prepare = (dir: string): Either<string, void> =>\n Try(() => {\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n return readdirSync(dir).filter((f) => !isOwn(f))\n }).fold(\n (e) => Left<string, void>(`runtime_dir ${dir}: ${e.message}`),\n (foreign) =>\n foreign.length > 0\n ? Left<string, void>(\n `runtime_dir ${dir} holds files the hub did not create (${foreign.slice(0, 3).join(\", \")}); give runtime_dir a directory of its own`,\n )\n : Try(() => chmodSync(dir, 0o700)).fold(\n (e) => Left<string, void>(`runtime_dir ${dir}: ${e.message}`),\n () => Right<string, void>(undefined),\n ),\n )\n\n// A lock written before the last boot is stale whatever its pid: the runtime dir can outlive a reboot\n// (`~/.local/state` on macOS), and by then the old hub's pid may belong to any live process.\nconst sinceBoot = (path: string): boolean =>\n Try(() => statSync(path).mtimeMs >= Date.now() - uptime() * 1000).orElse(false)\n\n// One hub per runtime dir: a second would take the first's agent and masters for a crash's leftovers.\nconst lock = (dir: string): Either<string, void> => {\n const path = join(dir, LOCK)\n const holder = readPid(path)\n if (isLive(holder) && sinceBoot(path))\n return Left<string, void>(\n `runtime_dir ${dir} is in use by pid ${holder}; give each hub its own runtime_dir (delete ${path} if that process is not a hub)`,\n )\n return Try(() => {\n Try(() => unlinkSync(path))\n writeFileSync(path, String(process.pid), { mode: 0o600, flag: \"wx\" })\n }).fold(\n (e) => Left<string, void>(`runtime_dir ${dir}: can't take ${LOCK}: ${e.message}`),\n () => Right<string, void>(undefined),\n )\n}\n\n// Deletes the hub's own files, lock included, and the dir itself once nothing else is left in it.\nconst release = (dir: string): void => {\n Try(() => readdirSync(dir))\n .orElse([])\n .filter(isOwn)\n .forEach((f) => Try(() => unlinkSync(join(dir, f))))\n Try(() => rmdirSync(dir))\n}\n\nconst VALID_CA_NAME = /^[\\w-]+$/\n\n// Public keys the agent holds, one per line; empty when it holds none (ssh-add -L exits 1 then).\nconst listKeys = async (agentSock: string, proc: ProcessRunner): Promise<ReadonlyArray<string>> => {\n const r = await proc.run(\"ssh-add\", [\"-L\"], { env: childEnv({ SSH_AUTH_SOCK: agentSock }) })\n return r.code === 0 ? r.stdout.split(\"\\n\").filter((line) => line.startsWith(\"ssh-\")) : []\n}\n\nconst loadCa = async (\n ca: CaKeySource,\n dir: string,\n agentSock: string,\n proc: ProcessRunner,\n): Promise<Either<string, readonly [string, string]>> => {\n const fail = (step: string, detail: string) =>\n Left<string, readonly [string, string]>(`CA \"${ca.name}\": ${step} failed: ${detail.trim()}`)\n // The name becomes a file name in the runtime dir.\n if (!VALID_CA_NAME.test(ca.name)) return fail(\"naming\", \"use letters, digits, hyphen or underscore\")\n const key = await ca.key()\n if (key.isLeft()) return fail(\"reading the key\", key.value)\n const agentEnv = childEnv({ ...NO_PROMPT, SSH_AUTH_SOCK: agentSock })\n const before = await listKeys(agentSock, proc)\n // The private key reaches OpenSSH on stdin only; it is never written to disk. (`ssh-keygen -y -f\n // /dev/stdin` can't be used: Node gives children a socket, not a pipe, and /dev/stdin won't open.)\n const add = await proc.run(\"ssh-add\", [\"-\"], { env: agentEnv, input: key.value })\n if (add.code !== 0) return fail(\"loading into the agent\", add.stderr)\n // The CA's public key is the one line the agent gained, so it always matches what the agent signs with.\n const added = (await listKeys(agentSock, proc)).filter((line) => !before.includes(line))\n if (added.length !== 1) return fail(\"loading into the agent\", \"the key is already loaded for another CA\")\n const pubPath = join(dir, `ca-${ca.name}.pub`)\n writeFileSync(pubPath, `${added[0]}\\n`, { mode: 0o600 })\n return Right<string, readonly [string, string]>([ca.name, pubPath])\n}\n\nconst stopAgent = async (agent: Child): Promise<void> => {\n agent.kill()\n await Promise.race([new Promise<void>((resolve) => agent.onExit(() => resolve())), sleep(2000)])\n}\n\n// Starts a private ssh-agent in `runtimeDir` and loads every CA key into it. On any failure the agent\n// is stopped and its files removed, so a failed start never leaves a key behind.\nexport const startCaCustody = async (\n runtimeDir: string,\n cas: ReadonlyArray<CaKeySource>,\n proc: ProcessRunner,\n): Promise<Either<string, Custody>> => {\n const ready = prepare(runtimeDir).flatMap(() => lock(runtimeDir))\n if (ready.isLeft()) return Left<string, Custody>(ready.value)\n await recover(runtimeDir, proc)\n\n const agentSock = join(runtimeDir, \"agent.sock\")\n const agent = proc.spawn(\"ssh-agent\", [\"-D\", \"-a\", agentSock], { env: childEnv() })\n const up = await waitFor(() => existsSync(agentSock) || agent.exited(), Date.now() + 5000)\n if (!up || agent.exited()) {\n await stopAgent(agent)\n release(runtimeDir)\n return Left<string, Custody>(`ssh-agent failed to start: ${agent.stderr().trim() || \"timed out\"}`)\n }\n const pid = agent.pid()\n if (pid !== undefined) writeFileSync(join(runtimeDir, \"agent.pid\"), String(pid), { mode: 0o600 })\n\n const loaded = await Either.traverseAsync(cas, (ca) => loadCa(ca, runtimeDir, agentSock, proc))\n if (loaded.isLeft()) {\n await stopAgent(agent)\n release(runtimeDir)\n return Left<string, Custody>(loaded.value)\n }\n const pubs = Map.of(...loaded.value.map(([name, path]): [string, string] => [name, path]))\n\n return Right<string, Custody>({\n runtimeDir,\n agentSock,\n pubPath: (ca) => pubs.get(ca),\n shutdown: async () => {\n await stopAgent(agent)\n release(runtimeDir)\n },\n })\n}\n\nexport type MintRequest = Readonly<{ ca: string; agent: string; principal: string; sourceAddress: Option<string> }>\n\n// An ephemeral key and the certificate the CA signed for it. `dispose` deletes both and is safe to repeat.\nexport type Minted = Readonly<{\n keyPath: string\n certPath: string\n validUntil: Date\n serial: string\n dispose: () => Promise<void>\n}>\n\nconst VALIDITY_MS = 10 * 60 * 1000\n\n// OpenSSH binds a control socket at `<path>.<16 random chars>` before renaming it, so the usable\n// length is the platform limit minus 17: 90 bytes on Linux (108), 86 on macOS (104).\nconst MAX_SOCKET_PATH = 86\n\n// Each master adds `-<4 hex>` before `.sock` (see openssh.ts), so two masters never share a path.\nconst MASTER_SUFFIX = 5\n\nexport const socketPathFor = (runtimeDir: string, agent: string, host: string): Either<string, string> => {\n const digest = createHash(\"sha256\").update(`${agent}\\0${host}`).digest(\"hex\").slice(0, 12)\n const path = join(runtimeDir, `m-${digest}.sock`)\n return Buffer.byteLength(path) + MASTER_SUFFIX <= MAX_SOCKET_PATH\n ? Right<string, string>(path)\n : Left<string, string>(\n `runtime_dir ${runtimeDir} is too long for control sockets (socket paths must be at most ${MAX_SOCKET_PATH} bytes)`,\n )\n}\n\n// Strictly increasing without stored state: microseconds since the epoch, bumped past the last\n// serial when calls land in the same millisecond. Keeps a key revocation list possible later.\nconst serials = { last: 0n }\n\nexport const nextSerial = (nowMs: number): bigint => {\n const candidate = BigInt(nowMs) * 1000n\n serials.last = candidate > serials.last ? candidate : serials.last + 1n\n return serials.last\n}\n\nconst certOptions = (sourceAddress: Option<string>): ReadonlyArray<string> => [\n \"-O\",\n \"no-port-forwarding\",\n \"-O\",\n \"no-agent-forwarding\",\n \"-O\",\n \"no-x11-forwarding\",\n \"-O\",\n \"no-user-rc\",\n ...sourceAddress.fold<string[]>(\n () => [],\n (cidr) => [\"-O\", `source-address=${cidr}`],\n ),\n]\n\n// Generates an ephemeral ed25519 key in the runtime dir and has the CA, held in the private agent,\n// sign a ten-minute certificate for it naming the agent.\nexport const mint = async (\n custody: Custody,\n req: MintRequest,\n proc: ProcessRunner,\n now: () => Date = () => new Date(),\n): Promise<Either<string, Minted>> => {\n const fail = (step: string, detail: string) =>\n Left<string, Minted>(`CA \"${req.ca}\": ${step} failed: ${detail.trim()}`)\n const caPub = custody.pubPath(req.ca)\n if (caPub.isNone()) return fail(\"signing\", `no CA named \"${req.ca}\" in custody`)\n\n const keyPath = join(custody.runtimeDir, `k-${randomBytes(8).toString(\"hex\")}`)\n const certPath = `${keyPath}-cert.pub`\n const files = [keyPath, `${keyPath}.pub`, certPath]\n const dispose = (): Promise<void> => {\n files.forEach((f) => Try(() => unlinkSync(f)))\n return Promise.resolve()\n }\n\n const gen = await proc.run(\"ssh-keygen\", [\"-q\", \"-t\", \"ed25519\", \"-N\", \"\", \"-C\", \"\", \"-f\", keyPath], {\n env: childEnv(),\n })\n if (gen.code !== 0) {\n await dispose()\n return fail(\"key generation\", gen.stderr)\n }\n\n const at = now()\n const serial = nextSerial(at.getTime())\n const sign = await proc.run(\n \"ssh-keygen\",\n [\n \"-U\",\n \"-s\",\n caPub.orElse(\"\"),\n \"-I\",\n req.agent,\n \"-n\",\n req.principal,\n \"-V\",\n \"-5m:+10m\",\n \"-z\",\n serial.toString(),\n ...certOptions(req.sourceAddress),\n `${keyPath}.pub`,\n ],\n { env: childEnv({ SSH_AUTH_SOCK: custody.agentSock }) },\n )\n if (sign.code !== 0) {\n await dispose()\n return fail(\"signing\", sign.stderr)\n }\n return Right<string, Minted>({\n keyPath,\n certPath,\n validUntil: new Date(at.getTime() + VALIDITY_MS),\n serial: serial.toString(),\n dispose,\n })\n}\n","import { type Either, Left, Right } from \"functype\"\n\n/**\n * `signal` is set when a signal ended the command; `code` is then 128 + the signal number, as a shell\n * reports it. `timedOutMs` is set when exec's timeout stopped waiting; `code` is then 124, as\n * `timeout(1)` reports it, and stdout/stderr hold what arrived first.\n */\nexport type CommandResult = Readonly<{\n stdout: string\n stderr: string\n code: number\n signal?: string\n timedOutMs?: number\n // The channel closed with no exit status (the connection dropped); code is then 255.\n noExitStatus?: true\n}>\n\n/** \"exit 1\", \"exit 137 (signal KILL)\", \"exit 255 (no exit status: channel closed)\", or \"timed out after 5000 ms\". */\nexport const exitLabel = (r: CommandResult): string => {\n if (r.timedOutMs !== undefined) return `timed out after ${r.timedOutMs} ms`\n if (r.noExitStatus === true) return `exit ${r.code} (no exit status: channel closed)`\n return r.signal === undefined ? `exit ${r.code}` : `exit ${r.code} (signal ${r.signal})`\n}\n\n// The whole message a tmux interpreter returns for a timed-out command (\"tmux_send timed out after\n// 5000 ms\"). Anchored, so a failure whose stderr merely mentions a timeout doesn't match.\nconst TIMED_OUT_RE = /^tmux[\\w -]* timed out after \\d+ ms$/\n\nexport const isTimedOutMessage = (message: string): boolean => TIMED_OUT_RE.test(message)\n\nexport type TmuxRunner = (command: string) => Promise<CommandResult>\n\nconst SESSION_RE = /^[A-Za-z0-9_-]+$/\n\nexport const validateSession = (name: string): Either<string, string> =>\n SESSION_RE.test(name)\n ? Right<string, string>(name)\n : Left<string, string>(`Invalid session name \"${name}\": only letters, digits, hyphen, and underscore are allowed`)\n\n// The three argument errors the builders below return. Callers label them as validation errors\n// (the caller sent a bad argument), not execution errors (the host failed).\nconst INPUT_ERROR_PREFIXES = [\"Invalid session name \", \"Unsupported key \", \"No keys provided\"] as const\n\nexport const isTmuxInputError = (message: string): boolean =>\n INPUT_ERROR_PREFIXES.some((prefix) => message.startsWith(prefix))\n\nexport const shellQuote = (s: string): string => `'${s.replaceAll(\"'\", \"'\\\\''\")}'`\n\nexport const clampLines = (n: number): number => Math.max(1, Math.min(2000, Math.floor(n)))\n\nexport const trimTrailingWhitespace = (s: string): string => s.replace(/\\s+$/, \"\")\n\nconst ALLOWED_KEYS: ReadonlySet<string> = new Set([\n \"Enter\",\n \"Escape\",\n \"Tab\",\n \"Space\",\n \"BSpace\",\n \"Up\",\n \"Down\",\n \"Left\",\n \"Right\",\n \"Home\",\n \"End\",\n \"PageUp\",\n \"PageDown\",\n \"C-c\",\n \"C-d\",\n \"C-z\",\n \"C-l\",\n \"C-u\",\n \"C-a\",\n \"C-e\",\n \"C-r\",\n])\n\nexport const validateKey = (key: string): Either<string, string> =>\n ALLOWED_KEYS.has(key)\n ? Right<string, string>(key)\n : Left<string, string>(`Unsupported key \"${key}\": allowed keys are ${[...ALLOWED_KEYS].join(\", \")}`)\n\n// A command sent over SSH runs in a non-login shell, so on macOS PATH is only\n// /usr/bin:/bin:/usr/sbin:/sbin and Homebrew's tmux (/opt/homebrew/bin, or /usr/local/bin on\n// Intel) isn't found. Appending keeps a tmux already on the host's PATH first. PATH is exported,\n// so the assignment reaches every tmux in the command.\nconst TMUX_PATH = 'PATH=\"$PATH:/opt/homebrew/bin:/usr/local/bin\"; '\n\nconst withTmuxPath = (command: string): string => `${TMUX_PATH}${command}`\n\nexport const buildList = (): string => withTmuxPath(\"tmux list-sessions -F '#{session_name}'\")\n\nexport const buildSend = (session: string, input: string, submit: boolean): Either<string, string> =>\n validateSession(session).map((s) => {\n // s is interpolated unquoted into -s/-t because SESSION_RE guarantees no shell metacharacters;\n // if that regex is ever relaxed, quoting would be required here.\n //\n // Ensure the session exists WITHOUT attaching: `new-session -A` would attach when the\n // session already exists, which needs a TTY and fails over an SSH exec channel\n // (\"open terminal failed: not a terminal\"). `has-session || new-session -d` only ever\n // checks-or-creates detached, so it works headlessly on both the first and later sends.\n const ensure = `{ tmux has-session -t ${s} 2>/dev/null || tmux new-session -d -s ${s}; }`\n const send = `tmux send-keys -t ${s} -l -- ${shellQuote(input)}`\n const enter = submit ? ` && tmux send-keys -t ${s} Enter` : \"\"\n return withTmuxPath(`${ensure} && ${send}${enter}`)\n })\n\nexport const buildRead = (session: string, lines: number): Either<string, string> =>\n validateSession(session).map((s) => withTmuxPath(`tmux capture-pane -t ${s} -p -J -S -${clampLines(lines)}`))\n\nexport const buildKeys = (session: string, keys: ReadonlyArray<string>): Either<string, string> =>\n validateSession(session).flatMap((s) => {\n if (keys.length === 0) return Left<string, string>(\"No keys provided\")\n const bad = keys.find((k) => validateKey(k).isLeft())\n return bad === undefined\n ? Right<string, string>(withTmuxPath(`tmux send-keys -t ${s} ${keys.join(\" \")}`))\n : validateKey(bad)\n })\n\nconst TMUX_MISSING_MSG =\n \"tmux not found on the remote host — install tmux (e.g. apt/brew install tmux) or use the exec tool instead\"\n\n// tmux phrases a missing target differently per subcommand: send-keys says\n// \"can't find session\", capture-pane says \"can't find pane\". Match both.\nconst SESSION_MISSING_RE = /can't find session|can't find pane|session not found/i\n\n// No sessions: \"no server running\" when the socket is stale, and \"error connecting to <socket>\n// (No such file or directory)\" when it doesn't exist yet (tmux 3.4 on Linux, 3.7c on macOS).\nconst NO_SERVER_RE = /no server running|error connecting to \\S+ \\(No such file or directory\\)/i\n\nexport const isTmuxMissing = (r: CommandResult): boolean =>\n r.code === 127 || /tmux: ([Cc]ommand )?not found/i.test(r.stderr)\n\nconst failure = (label: string, r: CommandResult): string =>\n r.timedOutMs === undefined ? `${label} failed: ${r.stderr.trim() || exitLabel(r)}` : `${label} ${exitLabel(r)}`\n\n// Each interpreter checks a timed-out result first, so its partial stderr can't read as \"no server\"\n// or \"no session\".\n\nexport const interpretList = (r: CommandResult): Either<string, ReadonlyArray<string>> => {\n if (r.timedOutMs !== undefined) return Left<string, ReadonlyArray<string>>(failure(\"tmux list-sessions\", r))\n if (isTmuxMissing(r)) return Left<string, ReadonlyArray<string>>(TMUX_MISSING_MSG)\n if (r.code === 0) {\n return Right<string, ReadonlyArray<string>>(\n r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0),\n )\n }\n if (NO_SERVER_RE.test(r.stderr)) return Right<string, ReadonlyArray<string>>([])\n return Left<string, ReadonlyArray<string>>(failure(\"tmux list-sessions\", r))\n}\n\nexport const interpretAck =\n (label: string) =>\n (r: CommandResult): Either<string, void> => {\n if (r.timedOutMs !== undefined) return Left<string, void>(failure(label, r))\n if (isTmuxMissing(r)) return Left<string, void>(TMUX_MISSING_MSG)\n if (r.code === 0) return Right<string, void>(undefined)\n return Left<string, void>(failure(label, r))\n }\n\nexport const interpretRead =\n (session: string) =>\n (r: CommandResult): Either<string, string> => {\n if (r.timedOutMs !== undefined) return Left<string, string>(failure(\"tmux capture-pane\", r))\n if (isTmuxMissing(r)) return Left<string, string>(TMUX_MISSING_MSG)\n if (r.code === 0) return Right<string, string>(trimTrailingWhitespace(r.stdout))\n if (SESSION_MISSING_RE.test(r.stderr)) {\n return Left<string, string>(`No tmux session \"${session}\" — list with tmux_list, or tmux_send creates one`)\n }\n return Left<string, string>(failure(\"tmux capture-pane\", r))\n }\n\nexport const interpretKeys =\n (session: string) =>\n (r: CommandResult): Either<string, void> => {\n if (r.timedOutMs !== undefined) return Left<string, void>(failure(\"tmux send-keys\", r))\n if (isTmuxMissing(r)) return Left<string, void>(TMUX_MISSING_MSG)\n if (r.code === 0) return Right<string, void>(undefined)\n if (SESSION_MISSING_RE.test(r.stderr)) {\n return Left<string, void>(`No tmux session \"${session}\" — list with tmux_list, or tmux_send creates one`)\n }\n return Left<string, void>(failure(\"tmux send-keys\", r))\n }\n\nexport const tmuxList = async (runner: TmuxRunner): Promise<Either<string, ReadonlyArray<string>>> =>\n interpretList(await runner(buildList()))\n\nexport const tmuxSend = async (\n runner: TmuxRunner,\n p: Readonly<{ session: string; input: string; submit: boolean }>,\n): Promise<Either<string, void>> => {\n const cmd = buildSend(p.session, p.input, p.submit)\n if (cmd.isLeft()) return Left<string, void>(cmd.value)\n return interpretAck(\"tmux_send\")(await runner(cmd.value))\n}\n\nexport const tmuxRead = async (\n runner: TmuxRunner,\n p: Readonly<{ session: string; lines: number }>,\n): Promise<Either<string, string>> => {\n const cmd = buildRead(p.session, p.lines)\n if (cmd.isLeft()) return Left<string, string>(cmd.value)\n return interpretRead(p.session)(await runner(cmd.value))\n}\n\nexport const tmuxKeys = async (\n runner: TmuxRunner,\n p: Readonly<{ session: string; keys: ReadonlyArray<string> }>,\n): Promise<Either<string, void>> => {\n const cmd = buildKeys(p.session, p.keys)\n if (cmd.isLeft()) return Left<string, void>(cmd.value)\n return interpretKeys(p.session)(await runner(cmd.value))\n}\n","import { constants } from \"node:os\"\n\nimport { type Either, Left, Option, Right } from \"functype\"\nimport { UserError } from \"somamcp\"\nimport { Client as SSHClient, type ConnectConfig } from \"ssh2\"\n\nimport { type CommandResult, exitLabel } from \"./tmux.js\"\n\n// A signal number differs by OS only for rarer signals; KILL, TERM, INT and HUP match everywhere.\nconst signalCode = (signal: string): number =>\n Option((constants.signals as Readonly<Record<string, number>>)[`SIG${signal}`]).fold(\n () => 255,\n (n) => 128 + n,\n )\n\n/**\n * The result of an ssh2 channel from its \"close\" arguments.\n * @interop ssh2 passes (code, signal) to \"close\" but @types/ssh2 declares none; code is null when\n * a signal ended the command, and signal is its name. ssh2 gives it as \"SIGKILL\" (observed against\n * OpenSSH on macOS); the SSH wire format says \"KILL\". Both become \"KILL\".\n */\nexport const channelResult = (stdout: string, stderr: string, code: unknown, signal: unknown): CommandResult => {\n if (typeof code === \"number\") return { stdout, stderr, code }\n // Neither: the channel closed before the host sent an exit status, e.g. the connection dropped.\n if (typeof signal !== \"string\" || signal === \"\") return { stdout, stderr, code: 255, noExitStatus: true }\n const name = signal.replace(/^SIG/, \"\")\n return { stdout, stderr, code: signalCode(name), signal: name }\n}\n\n/** Options for one command. Without `timeoutMs`, exec waits as long as the command runs. */\nexport type ExecOptions = Readonly<{ timeoutMs?: number }>\n\nexport const TIMEOUT_CODE = 124\n\nexport const timedOutResult = (stdout: string, stderr: string, timeoutMs: number): CommandResult => ({\n stdout,\n stderr,\n code: TIMEOUT_CODE,\n timedOutMs: timeoutMs,\n})\n\n/**\n * What `exec` returns for a finished command. A non-zero exit is the failure signal, not stderr:\n * many tools warn on stderr and exit 0. Neither stream is dropped. On success, stderr follows\n * stdout under a marker so warnings stay visible. A failure leads with the exit label and stderr,\n * the usual one-line reason, then stdout, which often holds the context.\n */\nexport const execOutput = (r: CommandResult): Either<string, string> => {\n if (r.code === 0) return Right(r.stderr ? `${r.stdout}\\n[stderr]\\n${r.stderr}` : r.stdout)\n const detail = r.stderr && r.stdout ? `${r.stderr}\\n[stdout]\\n${r.stdout}` : r.stderr || r.stdout\n return Left(`${exitLabel(r)}:\\n${detail}`)\n}\n\nexport const execSshResult = (sshConfig: ConnectConfig, command: string): Promise<CommandResult> =>\n new Promise((resolve, reject) => {\n const conn = new SSHClient()\n conn.on(\"ready\", () => {\n conn.exec(command, (err, stream) => {\n if (err) {\n reject(new UserError(`SSH exec error: ${err.message}`))\n conn.end()\n return\n }\n const stdoutChunks: Buffer[] = []\n const stderrChunks: Buffer[] = []\n stream.on(\"close\", (code: unknown, signal: unknown) => {\n conn.end()\n resolve(\n channelResult(Buffer.concat(stdoutChunks).toString(), Buffer.concat(stderrChunks).toString(), code, signal),\n )\n })\n stream.on(\"data\", (data: Buffer) => stdoutChunks.push(data))\n stream.stderr.on(\"data\", (data: Buffer) => stderrChunks.push(data))\n })\n })\n conn.on(\"error\", (err) => reject(new UserError(`SSH connection error: ${err.message}`)))\n conn.connect(sshConfig)\n })\n","import { randomBytes } from \"node:crypto\"\nimport { unlinkSync, writeFileSync } from \"node:fs\"\n\nimport { type Either, Left, Option, Right, Try } from \"functype\"\nimport { type ConnectConfig } from \"ssh2\"\n\nimport { type Minted } from \"./ca.js\"\nimport { type ConnectFactory, type PooledConnection } from \"./pool.js\"\nimport { type Child, childEnv, type ProcessRunner } from \"./proc.js\"\nimport { timedOutResult } from \"./ssh.js\"\n\n// One factory serves one (agent, host). `socketPath` is a base: each master gets its own socket\n// beside it (`<base>-<4 hex>.sock`), so a new master never touches one that is still draining.\nexport type OpensshDeps = Readonly<{\n proc: ProcessRunner\n mintFor: () => Promise<Either<string, Minted>>\n socketPath: string\n knownHosts: string\n readyTimeoutMs?: number\n retireAfterMs?: number\n drainTimeoutMs?: number\n setTimer?: (cb: () => void, ms: number) => unknown\n clearTimer?: (timer: unknown) => void\n}>\n\nexport type Openssh = Readonly<{ connect: ConnectFactory; shutdown: () => Promise<void> }>\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\nconst target = (c: ConnectConfig): ReadonlyArray<string> => [\n \"-p\",\n String(c.port ?? 22),\n `${c.username ?? \"\"}@${c.host ?? \"\"}`,\n]\n\n// `-F /dev/null` and `IdentityAgent=none` keep the hub user's own config and agent out, so the master\n// can offer only the minted certificate; a dead certificate can never fall back to another key.\nconst masterArgs = (c: ConnectConfig, socketPath: string, knownHosts: string, m: Minted): ReadonlyArray<string> => [\n \"-F\",\n \"/dev/null\",\n \"-M\",\n \"-N\",\n \"-S\",\n socketPath,\n \"-o\",\n \"ControlPersist=no\",\n \"-o\",\n \"IdentitiesOnly=yes\",\n \"-o\",\n \"IdentityAgent=none\",\n \"-o\",\n `IdentityFile=${m.keyPath}`,\n \"-o\",\n `CertificateFile=${m.certPath}`,\n \"-o\",\n \"BatchMode=yes\",\n \"-o\",\n \"StrictHostKeyChecking=accept-new\",\n \"-o\",\n `UserKnownHostsFile=${knownHosts}`,\n \"-o\",\n \"ConnectTimeout=10\",\n // A host that stops answering drops the master within about 45 s, instead of it lingering.\n \"-o\",\n \"ServerAliveInterval=15\",\n \"-o\",\n \"ServerAliveCountMax=3\",\n ...target(c),\n]\n\n// `ProxyCommand=false`: if the control socket is gone, the call fails instead of opening a direct\n// connection with whatever identities ssh would otherwise find.\nconst execArgs = (c: ConnectConfig, socketPath: string, command: string): ReadonlyArray<string> => [\n \"-F\",\n \"/dev/null\",\n \"-S\",\n socketPath,\n \"-o\",\n \"ControlMaster=no\",\n \"-o\",\n \"ProxyCommand=false\",\n \"-o\",\n \"BatchMode=yes\",\n ...target(c),\n \"--\",\n command,\n]\n\n// Runs `fn` on the first call; later calls get the same promise.\nconst once = (fn: () => Promise<void>): (() => Promise<void>) => {\n const state = { run: Option.none<Promise<void>>() }\n return () =>\n state.run.fold(\n () => {\n const run = fn()\n state.run = Option(run)\n return run\n },\n (run) => run,\n )\n}\n\n/** @interop ConnectFactory and PooledConnection are the pool's promise-and-callback contract. */\nexport const opensshConnect = (deps: OpensshDeps): Openssh => {\n const { proc, socketPath, knownHosts } = deps\n const readyTimeoutMs = deps.readyTimeoutMs ?? 15_000\n const drainTimeoutMs = deps.drainTimeoutMs ?? 60_000\n const setTimer = deps.setTimer ?? ((cb: () => void, ms: number): unknown => setTimeout(cb, ms))\n const clearTimer = deps.clearTimer ?? ((t: unknown) => clearTimeout(t as ReturnType<typeof setTimeout>))\n\n // Resolves when the child exits, or after `timeoutMs` if it doesn't.\n const exitOf = (child: Child, timeoutMs: number): Promise<void> =>\n Promise.race([\n new Promise<void>((resolve) => child.onExit(() => resolve())),\n new Promise<void>((resolve) => setTimer(resolve, timeoutMs)),\n ])\n\n // Waits for the master to exit (in-flight sessions finish first), then makes sure it is gone.\n const drain = (child: Child, dispose: () => Promise<void>): Promise<void> =>\n exitOf(child, drainTimeoutMs).then(() => {\n child.kill()\n return dispose()\n })\n const live = new globalThis.Set<Readonly<{ child: Child; close: () => void }>>()\n\n const control = (op: string, sock: string, c: ConnectConfig) =>\n proc.run(\"ssh\", [\"-F\", \"/dev/null\", \"-S\", sock, \"-O\", op, ...target(c)], { env: childEnv() })\n\n const waitReady = (child: Child, sock: string, c: ConnectConfig, deadline: number): Promise<Either<string, void>> => {\n if (child.exited())\n return Promise.resolve(Left<string, void>(`master for ${c.host} exited: ${child.stderr().trim()}`))\n if (Date.now() > deadline)\n return Promise.resolve(Left<string, void>(`master for ${c.host} did not come up: ${child.stderr().trim()}`))\n return control(\"check\", sock, c).then((r) =>\n r.code === 0 && !child.exited()\n ? Right<string, void>(undefined)\n : sleep(100).then(() => waitReady(child, sock, c, deadline)),\n )\n }\n\n const connect: ConnectFactory = async (c) => {\n const minted = await deps.mintFor()\n if (minted.isLeft()) throw new Error(minted.value)\n const m = minted.value\n const dispose = once(m.dispose)\n\n const sock = socketPath.replace(/\\.sock$/, `-${randomBytes(2).toString(\"hex\")}.sock`)\n const pidFile = sock.replace(/\\.sock$/, \".pid\")\n const child = proc.spawn(\"ssh\", masterArgs(c, sock, knownHosts, m), { env: childEnv() })\n // Recorded so that crash recovery can kill a master the hub no longer knows about.\n const pid = child.pid()\n if (pid !== undefined) Try(() => writeFileSync(pidFile, String(pid), { mode: 0o600 }))\n child.onExit(() => Try(() => unlinkSync(pidFile)))\n\n const ready = await waitReady(child, sock, c, Date.now() + readyTimeoutMs)\n if (ready.isLeft()) {\n child.kill()\n await dispose()\n throw new Error(ready.value)\n }\n\n const state = { closing: false, closed: false, retired: false }\n const closeCbs: Array<() => void> = []\n const fireClose = (): void => {\n if (state.closed) return\n state.closed = true\n closeCbs.splice(0).forEach((cb) => cb())\n }\n child.onExit(() => {\n fireClose()\n void dispose()\n })\n\n // sshd checks a certificate only at authentication, so a master would outlive its certificate.\n // At expiry: drop it from the pool, refuse new sessions (`-O stop`; the master exits once its\n // in-flight sessions end), then make sure it is gone. The next call mints a fresh master.\n const retire = (): void => {\n if (state.closing || state.retired) return\n state.retired = true\n fireClose()\n void control(\"stop\", sock, c)\n .catch(() => undefined)\n .then(() => drain(child, dispose))\n }\n const retireTimer = setTimer(retire, deps.retireAfterMs ?? Math.max(0, m.validUntil.getTime() - Date.now()))\n child.onExit(() => clearTimer(retireTimer))\n\n const close = (): void => {\n clearTimer(retireTimer)\n if (state.closing || state.retired) return\n state.closing = true\n // `-O exit` exits 255 once the master is gone; that is success here, not an error.\n void control(\"exit\", sock, c)\n .catch(() => undefined)\n .then(() => drain(child, dispose))\n }\n const entry = { child, close }\n live.add(entry)\n child.onExit(() => live.delete(entry))\n\n const conn: PooledConnection = {\n exec: async (command, opts = {}) => {\n if (child.exited() || state.closing || state.retired) throw new Error(`master for ${c.host} is gone`)\n const r = await proc.run(\"ssh\", execArgs(c, sock, command), { env: childEnv(), timeoutMs: opts.timeoutMs })\n return r.timedOut === true\n ? timedOutResult(r.stdout, r.stderr, opts.timeoutMs ?? 0)\n : { stdout: r.stdout, stderr: r.stderr, code: r.code }\n },\n close,\n onClose: (cb) => {\n closeCbs.push(cb)\n },\n }\n return conn\n }\n\n const shutdown = async (): Promise<void> => {\n const entries = [...live]\n entries.forEach((e) => e.close())\n await Promise.all(entries.map((e) => (e.child.exited() ? Promise.resolve() : exitOf(e.child, drainTimeoutMs))))\n }\n\n return { connect, shutdown }\n}\n","import { Map } from \"functype\"\nimport { Client as SSHClient, type ConnectConfig } from \"ssh2\"\n\nimport { channelResult, type ExecOptions, timedOutResult } from \"./ssh.js\"\nimport { type CommandResult } from \"./tmux.js\"\n\n// A timeout resolves with the partial output, never rejects: a rejection makes the pool reconnect\n// and run the command again.\nexport type PooledConnection = {\n exec: (command: string, opts?: ExecOptions) => Promise<CommandResult>\n close: () => void\n onClose: (cb: () => void) => void\n}\n\nexport type ConnectFactory = (sshConfig: ConnectConfig) => Promise<PooledConnection>\n\nexport type PoolOptions = { maxPerHost: number; idleTimeoutMs: number; acquireTimeoutMs: number }\n\nexport type HostStatus = { name: string; state: \"connected\" | \"idle\" | \"disconnected\"; inFlight: number }\n\nexport type Pool = {\n run: (hostName: string, command: string, opts?: ExecOptions) => Promise<CommandResult>\n status: () => HostStatus[]\n shutdown: () => Promise<void>\n}\n\ntype HostState = {\n name: string\n sshConfig: ConnectConfig\n conn?: PooledConnection\n connecting?: Promise<PooledConnection>\n inFlight: number\n waiters: Array<() => void>\n idleTimer?: ReturnType<typeof setTimeout>\n}\n\n// Default factory: a persistent ssh2 client; exec runs each command on its own channel.\nexport const defaultConnect: ConnectFactory = (sshConfig) =>\n new Promise<PooledConnection>((resolve, reject) => {\n const client = new SSHClient()\n const closeCbs: Array<() => void> = []\n client.on(\"ready\", () =>\n resolve({\n exec: (command, opts = {}) =>\n new Promise<CommandResult>((res, rej) => {\n client.exec(command, (err, stream) => {\n if (err) {\n rej(err)\n return\n }\n const out: Buffer[] = []\n const errb: Buffer[] = []\n const timer =\n opts.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n const ms = opts.timeoutMs ?? 0\n res(timedOutResult(Buffer.concat(out).toString(), Buffer.concat(errb).toString(), ms))\n // OpenSSH 7.9+ delivers the signal; an older sshd ignores it, and closing the\n // channel only stops the command if it then writes output.\n stream.signal(\"KILL\")\n stream.close()\n }, opts.timeoutMs)\n stream.on(\"close\", (code: unknown, signal: unknown) => {\n clearTimeout(timer)\n res(channelResult(Buffer.concat(out).toString(), Buffer.concat(errb).toString(), code, signal))\n })\n stream.on(\"data\", (d: Buffer) => out.push(d))\n stream.stderr.on(\"data\", (d: Buffer) => errb.push(d))\n })\n }),\n close: () => client.end(),\n onClose: (cb) => closeCbs.push(cb),\n }),\n )\n client.on(\"error\", (err) => {\n closeCbs.forEach((cb) => cb())\n reject(err)\n })\n client.on(\"close\", () => closeCbs.forEach((cb) => cb()))\n client.connect({ keepaliveInterval: 15000, ...sshConfig })\n })\n\nexport const createPool = (\n hosts: ReadonlyArray<{ name: string; sshConfig: ConnectConfig }>,\n opts: PoolOptions,\n connect: ConnectFactory = defaultConnect,\n): Pool => {\n const states = Map.of<string, HostState>(\n ...hosts.map((h): [string, HostState] => [\n h.name,\n { name: h.name, sshConfig: h.sshConfig, inFlight: 0, waiters: [] },\n ]),\n )\n\n const allStates = (): HostState[] => [...states].map(([, st]) => st)\n\n const dropConn = (st: HostState): void => {\n st.conn = undefined\n if (st.idleTimer) {\n clearTimeout(st.idleTimer)\n st.idleTimer = undefined\n }\n }\n\n const ensureConn = async (st: HostState): Promise<PooledConnection> => {\n if (st.conn) return st.conn\n if (!st.connecting) {\n st.connecting = connect(st.sshConfig).then((c) => {\n c.onClose(() => {\n if (st.conn === c) dropConn(st)\n })\n st.conn = c\n st.connecting = undefined\n return c\n })\n st.connecting.catch(() => {\n st.connecting = undefined\n })\n }\n return st.connecting\n }\n\n const acquireSlot = (st: HostState): Promise<void> => {\n if (st.inFlight < opts.maxPerHost) {\n st.inFlight++\n return Promise.resolve()\n }\n // Woken by releaseSlot: the slot is transferred to the waiter, so inFlight already counts it.\n return new Promise<void>((resolve, reject) => {\n const wake = (): void => {\n clearTimeout(timer)\n resolve()\n }\n const timer = setTimeout(() => {\n const idx = st.waiters.indexOf(wake)\n if (idx !== -1) st.waiters.splice(idx, 1)\n reject(new Error(`host \"${st.name}\" busy: no free connection slot after ${opts.acquireTimeoutMs}ms`))\n }, opts.acquireTimeoutMs)\n st.waiters.push(wake)\n })\n }\n\n const releaseSlot = (st: HostState): void => {\n const next = st.waiters.shift()\n if (next) {\n next() // transfer the slot to the waiter; inFlight is unchanged (count carries over)\n return\n }\n st.inFlight--\n if (st.inFlight === 0 && st.conn) {\n st.idleTimer = setTimeout(() => {\n st.conn?.close()\n dropConn(st)\n }, opts.idleTimeoutMs)\n }\n }\n\n const run = async (hostName: string, command: string, opts?: ExecOptions): Promise<CommandResult> => {\n const st = states.get(hostName).orThrow(new Error(`unknown host \"${hostName}\"`))\n if (st.idleTimer) {\n clearTimeout(st.idleTimer)\n st.idleTimer = undefined\n }\n await acquireSlot(st)\n try {\n const conn = await ensureConn(st)\n return await conn.exec(command, opts).catch(async () => {\n // connection likely dead — drop, reconnect once, retry\n dropConn(st)\n const fresh = await ensureConn(st)\n return fresh.exec(command, opts)\n })\n } finally {\n releaseSlot(st)\n }\n }\n\n const status = (): HostStatus[] =>\n allStates().map((st) => ({\n name: st.name,\n state: st.conn ? (st.inFlight > 0 ? \"connected\" : \"idle\") : \"disconnected\",\n inFlight: st.inFlight,\n }))\n\n const shutdown = (): Promise<void> => {\n allStates().forEach((st) => {\n if (st.idleTimer) clearTimeout(st.idleTimer)\n st.conn?.close()\n dropConn(st)\n })\n return Promise.resolve()\n }\n\n return { run, status, shutdown }\n}\n","import { isTimedOutMessage, isTmuxInputError } from \"@ssh-mcp/core\"\nimport { UserError } from \"somamcp\"\n\n// Every failure a host-routed tool reports carries its own label and hints. somamcp guesses a plain\n// Error's label from its words, which called an unknown host \"internal\" and told the model the\n// tool's maintainer should investigate.\n\nexport const UNKNOWN_HOST_HINT = \"Call hosts_list for the configured host names.\"\n\nconst isUnknownHost = (message: string): boolean => message.startsWith(\"unknown host\")\n\n/** A tool result that failed (a bad tmux argument, a tmux error, an exit code): the message says why, so no hints. */\nexport const resultError = (message: string): UserError => {\n if (isUnknownHost(message)) return new UserError(message, { category: \"not_found\", suggestions: [UNKNOWN_HOST_HINT] })\n if (isTmuxInputError(message)) return new UserError(message, { category: \"validation\", suggestions: [] })\n if (isTimedOutMessage(message))\n return new UserError(message, {\n category: \"timeout\",\n suggestions: [\"tmux took longer than [pool].exec_timeout_ms. Try again, or raise it.\"],\n })\n return new UserError(message, { category: \"execution\", suggestions: [] })\n}\n\n/** exec stopped waiting: the message carries the output so far. */\nexport const timeoutError = (host: string, failure: string): UserError =>\n new UserError(`[${host}] ${failure}`, {\n category: \"timeout\",\n suggestions: [\n `The command may still be running on ${host}. For a long job, start it in tmux with tmux_send and check it with tmux_read, or pass a larger timeout_ms.`,\n ],\n })\n\n/** A call the host's policy refused. */\nexport const deniedError = (message: string): UserError =>\n new UserError(message, {\n category: \"auth\",\n suggestions: [\"This host's policy doesn't allow it. Use a command or tool the policy allows, or ask the operator.\"],\n })\n\n/**\n * Labels a runner failure: an unknown host, a busy pool, or a connection that failed.\n * @interop a `.catch` handler for a tool's promise; it rethrows as UserError so the label and hints are ours, not guessed.\n */\nexport const routedError =\n (host: string) =>\n (e: unknown): never => {\n if (e instanceof UserError) throw e\n // A TypeError or RangeError is a bug in this server, not a host failure: don't send the model to fleet_setup.\n if (e instanceof TypeError || e instanceof RangeError)\n throw new UserError(e.message, { category: \"internal\", suggestions: [] })\n const message = e instanceof Error ? e.message : String(e)\n if (isUnknownHost(message))\n throw new UserError(message, { category: \"not_found\", suggestions: [UNKNOWN_HOST_HINT] })\n if (message.includes(\" busy: no free connection slot\"))\n throw new UserError(message, {\n category: \"timeout\",\n suggestions: [`Every connection to ${host} is in use. Try again, or raise [pool].max_per_host.`],\n })\n throw new UserError(`[${host}] ${message}`, {\n category: \"execution\",\n suggestions: [`Call fleet_setup to see ${host}'s connection state and what to configure.`],\n })\n }\n","import { closeSync, constants, fchmodSync, fstatSync, mkdirSync, openSync, writeSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nimport { type Either, Left, Option, Right, Try, type Wire } from \"functype\"\nimport { Path } from \"functype-os\"\nimport { UserError } from \"somamcp\"\n\nimport { type Verdict } from \"./policy.js\"\nimport { deniedError } from \"./tool-errors.js\"\n\nexport const AGENT_UNKNOWN = \"unknown\"\nexport const DEFAULT_AUDIT_PATH = \"~/.local/state/ssh-fleet/audit.jsonl\"\nexport const EXCERPT_CHARS = 1024\n\n// Tool-specific fields of an audit line, in the shape written to disk.\nexport type AuditFields = Wire<Readonly<Record<string, string | number | boolean | ReadonlyArray<string> | undefined>>>\n\nexport type AuditCall = Readonly<{\n agent: string\n tool: string\n host: Option<string>\n fields: AuditFields\n verdict: Verdict\n}>\n\n// Appends one line to the audit log; throws on failure.\nexport type AuditSink = (line: string) => void\n\nexport type Auditor = Readonly<{\n audited: <A>(call: AuditCall, run: () => Promise<A>, summarize: (a: A) => AuditFields) => Promise<A>\n}>\n\n// `client` says which client made the call: the operator's label (SSH_FLEET_CLIENT), else the name\n// and version the client sent in its handshake. It is recorded beside `agent`, never in its place:\n// neither source is a verified identity, so policy never sees it.\nexport type AuditDeps = Readonly<{ sink: AuditSink; now: () => Date; newId: () => string; client?: string }>\n\nexport const CLIENT_LABEL_ENV = \"SSH_FLEET_CLIENT\"\n\n/**\n * The client's `name/version` from its handshake, cleaned like a label. A version alone names nothing.\n * @interop takes somamcp's `context.client`, whose fields are string | undefined.\n */\nexport const reportedClient = (\n reported: Readonly<{ name?: string | undefined; version: string | undefined }>,\n): Option<string> =>\n Option(reported.name).flatMap((name) =>\n clientLabel(reported.version === undefined ? name : `${name}/${reported.version}`),\n )\n\n/**\n * A label from the environment: trimmed, control characters removed, at most 64 characters; empty means none.\n * @interop takes a process.env value, which is string | undefined.\n */\nexport const clientLabel = (raw: string | undefined): Option<string> =>\n Option(raw)\n .map((v) =>\n Array.from(v.replace(/\\p{Cc}/gu, \"\").trim())\n .slice(0, 64)\n .join(\"\"),\n )\n .filter((v) => v.length > 0)\n\n// The first EXCERPT_CHARS code points, so a surrogate pair is never split.\nexport const excerpt = (s: string): string => Array.from(s).slice(0, EXCERPT_CHARS).join(\"\")\n\nconst messageOf = (e: unknown): string => (e instanceof Error ? e.message : String(e))\n\n// Rules are host-qualified in the log (`box-a:readonly`), as the spec's examples show.\nconst qualify = (host: Option<string>, rule: string): string =>\n host.fold(\n () => rule,\n (h) => `${h}:${rule}`,\n )\n\nconst verdictFields = (call: AuditCall): Readonly<Record<string, string>> =>\n call.verdict.decision === \"allow\"\n ? {\n decision: \"allow\",\n ...call.verdict.rule.fold(\n () => ({}),\n (rule) => ({ rule: qualify(call.host, rule) }),\n ),\n }\n : { decision: \"deny\", rule: qualify(call.host, call.verdict.rule), reason: call.verdict.reason }\n\n/** @interop a thrown UserError sets the message, category and suggestions the client sees; any other throw gets a label somamcp guesses from its message. */\nexport const createAuditor = ({ sink, now, newId, client }: AuditDeps): Auditor => {\n const write = (line: Readonly<Record<string, unknown>>, failure: string): void =>\n Try(() => sink(JSON.stringify(line))).fold(\n (e) => {\n throw new UserError(`audit log write failed ${failure}: ${messageOf(e)}`, {\n category: \"internal\",\n suggestions: [\n \"The server refuses every call while it can't write its audit log. Check the [audit].path file.\",\n ],\n })\n },\n () => undefined,\n )\n\n // A decision line is written before the call runs and a result line after it, sharing `id`,\n // so a call that hangs or crashes the server is still on record. A denied call never runs, so\n // it writes only its decision line.\n const audited = async <A>(call: AuditCall, run: () => Promise<A>, summarize: (a: A) => AuditFields): Promise<A> => {\n const id = newId()\n const who = {\n agent: call.agent,\n ...(client === undefined ? {} : { client }),\n tool: call.tool,\n ...call.host.fold(\n () => ({}),\n (host) => ({ host }),\n ),\n }\n const started = now()\n write(\n { ts: started.toISOString(), id, event: \"decision\", ...who, ...call.fields, ...verdictFields(call) },\n \"— call not executed\",\n )\n if (call.verdict.decision === \"deny\") {\n const prefix = call.host.fold(\n () => \"\",\n (host) => `[${host}] `,\n )\n throw deniedError(`${prefix}denied by rule ${call.verdict.rule}: ${call.verdict.reason}`)\n }\n\n const outcome = await Promise.resolve()\n .then(run)\n .then(\n (value) => ({ ok: true as const, value }),\n (error: unknown) => ({ ok: false as const, error }),\n )\n const finished = now()\n const result = { ts: finished.toISOString(), id, event: \"result\", ...who }\n const ms = finished.getTime() - started.getTime()\n\n if (outcome.ok) {\n write({ ...result, status: \"ok\", ms, ...summarize(outcome.value) }, \"after execution\")\n return outcome.value\n }\n write({ ...result, status: \"error\", ms, error: messageOf(outcome.error) }, \"after execution\")\n throw outcome.error\n }\n\n return { audited }\n}\n\n// Every append reopens the file, so a deleted or rotated log is recreated at 0600; O_NOFOLLOW\n// refuses a symlink planted at the path instead of writing through it.\nconst APPEND_NOFOLLOW = constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW\n\nconst withAppendFd = (file: string, use: (fd: number) => void): void => {\n const fd = openSync(file, APPEND_NOFOLLOW, 0o600)\n try {\n use(fd)\n } finally {\n closeSync(fd)\n }\n}\n\n/** @interop the AuditSink contract signals failure by throwing; createAuditor turns it into a UserError. */\nconst appendLine = (file: string, line: string): void =>\n withAppendFd(file, (fd) => {\n const text = `${line}\\n`\n const expected = Buffer.byteLength(text)\n const written = writeSync(fd, text)\n if (written !== expected) throw new Error(`short write: ${written} of ${expected} bytes`)\n })\n\n// The mode is fixed through the open fd, so there is no path lookup to race, and only when it isn't\n// 0600 already: a file with the append-only flag (chattr +a, chflags uappend) refuses chmod.\nconst ensurePrivate = (fd: number): void => {\n if ((fstatSync(fd).mode & 0o777) !== 0o600) fchmodSync(fd, 0o600)\n}\n\n// Opens the audit log at `path`, creating it and its directories if needed, and tightens an\n// existing file with wider permissions to 0600. Left when the file can't be opened for append.\nexport const openAuditLog = (path: string): Either<string, AuditSink> =>\n Path.expand(path).fold(\n (e) => Left<string, AuditSink>(`invalid audit path ${path}: ${messageOf(e)}`),\n (file) =>\n Try(() => {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 })\n withAppendFd(file, ensurePrivate)\n }).fold(\n (e) => Left<string, AuditSink>(`audit log ${file} is not writable: ${messageOf(e)}`),\n () => Right<string, AuditSink>((line) => appendLine(file, line)),\n ),\n )\n","// Which build is running, for somamcp's `info` tool. tsdown.config.ts replaces __FLEET_BUILD__ with\n// the short commit (with -dirty for uncommitted changes), branch and build date. Unbuilt source (tests, tsx)\n// has no stamp, so somamcp falls back to its SOMAMCP_BUILD_* environment variables.\nexport type BuildStamp = Readonly<{ commit?: string; branch?: string; date?: string }>\n\n/** @interop tsdown's `define` replaces this identifier at build time; it is undefined in unbuilt source. */\ndeclare const __FLEET_BUILD__: BuildStamp | undefined\n\nexport const buildInfo = (): BuildStamp => (typeof __FLEET_BUILD__ === \"undefined\" ? {} : __FLEET_BUILD__)\n","import { type Either, Left, Option, Right, Try } from \"functype\"\n\nexport type PolicyMode = \"readonly\" | \"readwrite\" | \"deny\"\nexport type TmuxGate = \"approve\" | \"open\"\n\n// One parsed `[defaults.policy]` or `[hosts.policy]` table; every key optional.\nexport type PolicyTable = Readonly<{\n mode: Option<PolicyMode>\n tmux: Option<TmuxGate>\n allow: Option<ReadonlyArray<string>>\n needsApproval: Option<ReadonlyArray<string>>\n agents: Option<ReadonlyArray<string>>\n}>\n\n// A needs_approval pattern as the operator wrote it, beside its compiled form; denials quote the former.\nexport type Tripwire = Readonly<{ pattern: string; re: RegExp }>\n\n// A host's effective policy. `allow` regexes are wrapped `^(?:…)$`; `needsApproval` match anywhere.\nexport type HostPolicy = Readonly<{\n mode: PolicyMode\n tmux: TmuxGate\n allow: ReadonlyArray<RegExp>\n needsApproval: ReadonlyArray<Tripwire>\n agents: Option<ReadonlyArray<string>>\n}>\n\nconst MODES: ReadonlyArray<string> = [\"readonly\", \"readwrite\", \"deny\"]\nconst GATES: ReadonlyArray<string> = [\"approve\", \"open\"]\nconst KEYS: ReadonlyArray<string> = [\"mode\", \"tmux\", \"allow\", \"needs_approval\", \"agents\"]\n\nexport const EMPTY_TABLE: PolicyTable = {\n mode: Option.none<PolicyMode>(),\n tmux: Option.none<TmuxGate>(),\n allow: Option.none<ReadonlyArray<string>>(),\n needsApproval: Option.none<ReadonlyArray<string>>(),\n agents: Option.none<ReadonlyArray<string>>(),\n}\n\nconst isTable = (v: unknown): v is Readonly<Record<string, unknown>> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v)\n\nconst oneOf = <A extends string>(\n v: unknown,\n values: ReadonlyArray<string>,\n error: string,\n): Either<string, Option<A>> => {\n if (v === undefined) return Right<string, Option<A>>(Option.none<A>())\n return typeof v === \"string\" && values.includes(v) ? Right<string, Option<A>>(Option(v as A)) : Left(error)\n}\n\nconst strings = (v: unknown, error: string): Either<string, Option<ReadonlyArray<string>>> => {\n if (v === undefined) return Right<string, Option<ReadonlyArray<string>>>(Option.none<ReadonlyArray<string>>())\n return Array.isArray(v) && v.every((x) => typeof x === \"string\")\n ? Right<string, Option<ReadonlyArray<string>>>(Option<ReadonlyArray<string>>(v))\n : Left(error)\n}\n\nconst errorOf = (r: Either<string, unknown>): ReadonlyArray<string> =>\n r.fold(\n (msg) => [msg],\n () => [],\n )\n\nexport const parsePolicyTable = (raw: unknown, where: string): Either<ReadonlyArray<string>, PolicyTable> => {\n if (raw === undefined) return Right<ReadonlyArray<string>, PolicyTable>(EMPTY_TABLE)\n if (!isTable(raw)) return Left<ReadonlyArray<string>, PolicyTable>([`${where} must be a table`])\n\n const mode = oneOf<PolicyMode>(raw.mode, MODES, `${where}.mode must be one of readonly, readwrite, deny`)\n const tmux = oneOf<TmuxGate>(raw.tmux, GATES, `${where}.tmux must be approve or open`)\n const allow = strings(raw.allow, `${where}.allow must be an array of strings`)\n const needsApproval = strings(raw.needs_approval, `${where}.needs_approval must be an array of strings`)\n const agents = strings(raw.agents, `${where}.agents must be an array of strings`)\n\n const errors = [\n ...Object.keys(raw)\n .filter((k) => !KEYS.includes(k))\n .map((k) => `${where}: unknown key \"${k}\"`),\n ...[mode, tmux, allow, needsApproval, agents].flatMap(errorOf),\n ]\n return errors.length > 0\n ? Left<ReadonlyArray<string>, PolicyTable>(errors)\n : Right<ReadonlyArray<string>, PolicyTable>({\n mode: mode.orElse(EMPTY_TABLE.mode),\n tmux: tmux.orElse(EMPTY_TABLE.tmux),\n allow: allow.orElse(EMPTY_TABLE.allow),\n needsApproval: needsApproval.orElse(EMPTY_TABLE.needsApproval),\n agents: agents.orElse(EMPTY_TABLE.agents),\n })\n}\n\n// An empty list means \"not set\", once a host's key has replaced the default's.\nconst nonEmpty = (o: Option<ReadonlyArray<string>>): ReadonlyArray<string> => o.filter((xs) => xs.length > 0).orElse([])\n\nconst compile = (\n where: string,\n key: string,\n patterns: ReadonlyArray<string>,\n wrap: (p: string) => string,\n): Either<ReadonlyArray<string>, ReadonlyArray<RegExp>> => {\n const results = patterns.map((p) =>\n // Compile the bare pattern first: one with an unmatched `)` (e.g. `uptime)|(.*`) only compiles once\n // wrapped, and then escapes the wrap — `^(?:uptime)|(.*)$` matches anything.\n Try(() => new RegExp(wrap(new RegExp(p).source))).fold(\n (e) => Left<string, RegExp>(`${where}: invalid regex in ${key}: \"${p}\" (${(e as Error).message})`),\n (re) => Right<string, RegExp>(re),\n ),\n )\n const errors = results.flatMap(errorOf)\n return errors.length > 0\n ? Left<ReadonlyArray<string>, ReadonlyArray<RegExp>>(errors)\n : Right<ReadonlyArray<string>, ReadonlyArray<RegExp>>(\n results.flatMap((r) =>\n r.fold<RegExp[]>(\n () => [],\n (re) => [re],\n ),\n ),\n )\n}\n\nconst errorsOf = (r: Either<ReadonlyArray<string>, unknown>): ReadonlyArray<string> =>\n r.fold(\n (errs) => errs,\n () => [],\n )\n\n// Host keys replace default keys (lists are replaced, never concatenated); built-in defaults fill\n// the rest, so a config with no policy behaves as before policy existed.\nexport const resolvePolicy = (\n where: string,\n defaults: PolicyTable,\n host: PolicyTable,\n): Either<ReadonlyArray<string>, HostPolicy> => {\n const mode = host.mode.or(defaults.mode).orElse(\"readwrite\")\n const tmux = host.tmux.or(defaults.tmux).orElse(\"open\")\n const allowSrc = nonEmpty(host.allow.or(defaults.allow))\n const needsSrc = nonEmpty(host.needsApproval.or(defaults.needsApproval))\n const agents = nonEmpty(host.agents.or(defaults.agents))\n\n const allow = compile(where, \"allow\", allowSrc, (p) => `^(?:${p})$`)\n const needsApproval = compile(where, \"needs_approval\", needsSrc, (p) => p)\n const errors = [\n ...errorsOf(allow),\n ...errorsOf(needsApproval),\n ...(allowSrc.length > 0 && mode !== \"readonly\" ? [`${where}: allow applies only to mode = \"readonly\"`] : []),\n ...(needsSrc.length > 0 && mode !== \"readwrite\"\n ? [`${where}: needs_approval applies only to mode = \"readwrite\"`]\n : []),\n ]\n return errors.length > 0\n ? Left<ReadonlyArray<string>, HostPolicy>(errors)\n : Right<ReadonlyArray<string>, HostPolicy>({\n mode,\n tmux,\n allow: allow.orElse([]),\n needsApproval: needsApproval.orElse([]).map((re, i) => ({ pattern: needsSrc[i] ?? re.source, re })),\n agents: agents.length > 0 ? Option<ReadonlyArray<string>>(agents) : Option.none<ReadonlyArray<string>>(),\n })\n}\n\nexport type PolicyTool = \"exec\" | \"tmux_list\" | \"tmux_send\" | \"tmux_read\" | \"tmux_keys\"\n\n// Only exec carries a command.\nexport type PolicyRequest =\n | Readonly<{ tool: \"exec\"; agent: string; command: string }>\n | Readonly<{ tool: Exclude<PolicyTool, \"exec\">; agent: string }>\n\n// `rule` is the short rule name; the auditor qualifies it with the host.\nexport type Verdict =\n Readonly<{ decision: \"allow\"; rule: Option<string> }> | Readonly<{ decision: \"deny\"; rule: string; reason: string }>\n\n// For host-less tools and unknown hosts: no policy applies.\nexport const ALLOW: Verdict = { decision: \"allow\", rule: Option.none<string>() }\n\nconst allowBy = (rule: string): Verdict => ({ decision: \"allow\", rule: Option(rule) })\nconst denyBy = (rule: string, reason: string): Verdict => ({ decision: \"deny\", rule, reason })\n\n// Line breaks, NUL, and every character the remote `$SHELL -c` gives meaning to: control\n// operators, substitution, redirection, grouping, escapes, quotes, globs and `~`.\nconst READONLY_REJECT = /[\\n\\r\\0;&|$`()<>{}\\\\'\"*?[~]/\n\n// Policy regexes run synchronously and can backtrack badly, so commands that will be matched against\n// them are capped; a frozen regex would stall every host, not just this one.\nexport const MAX_COMMAND_CHARS = 4096\nconst TOO_LONG = `command is longer than ${MAX_COMMAND_CHARS} characters`\n\nconst listed = (agents: ReadonlyArray<string>, agent: string): boolean =>\n agents.some((e) => agent === e || agent.startsWith(`${e}@`))\n\nconst evaluateTmuxInput = (policy: HostPolicy): Verdict => {\n if (policy.mode === \"readonly\") return denyBy(\"readonly\", \"tmux input is not allowed on a readonly host\")\n return policy.tmux === \"approve\"\n ? denyBy(\"tmux\", \"tmux input requires approval, which is not available yet\")\n : allowBy(\"readwrite\")\n}\n\nconst evaluateExec = (policy: HostPolicy, command: string): Verdict => {\n // Counts characters (code points), like `excerpt`; the cheap code-unit length rules most commands out first.\n const tooLong = command.length > MAX_COMMAND_CHARS && Array.from(command).length > MAX_COMMAND_CHARS\n if (policy.mode === \"readonly\") {\n if (tooLong) return denyBy(\"readonly\", TOO_LONG)\n if (READONLY_REJECT.test(command)) return denyBy(\"readonly\", \"command contains a newline or shell metacharacter\")\n return policy.allow.some((re) => re.test(command))\n ? allowBy(\"readonly\")\n : denyBy(\"readonly\", \"command not in allow list\")\n }\n if (tooLong && policy.needsApproval.length > 0) return denyBy(\"needs_approval\", TOO_LONG)\n const tripped = policy.needsApproval.find((t) => t.re.test(command))\n return tripped === undefined\n ? allowBy(\"readwrite\")\n : denyBy(\"needs_approval\", `matches \"${tripped.pattern}\" and needs approval, which is not available yet`)\n}\n\n// First match wins: deny mode, then the agents list, then the per-tool rules.\nexport const evaluate = (policy: HostPolicy, req: PolicyRequest): Verdict => {\n if (policy.mode === \"deny\") return denyBy(\"deny\", \"host is closed by policy\")\n if (\n policy.agents.fold(\n () => false,\n (agents) => !listed(agents, req.agent),\n )\n )\n return denyBy(\"agents\", `agent \"${req.agent}\" is not in agents`)\n if (req.tool === \"exec\") return evaluateExec(policy, req.command)\n return req.tool === \"tmux_send\" || req.tool === \"tmux_keys\" ? evaluateTmuxInput(policy) : allowBy(policy.mode)\n}\n","import { isIP } from \"node:net\"\n\nimport { type ResolveAuthOptions, validateSession } from \"@ssh-mcp/core\"\nimport { type Either, Left, List, Option, Right, Try } from \"functype\"\nimport { parse as parseToml } from \"smol-toml\"\n\nimport { DEFAULT_AUDIT_PATH } from \"./audit.js\"\nimport { EMPTY_TABLE, type HostPolicy, parsePolicyTable, type PolicyTable, resolvePolicy } from \"./policy.js\"\n\nexport type HostEntry = Readonly<{\n name: string\n host: string\n port: number\n user: Option<string>\n auth: ResolveAuthOptions\n // Which auth field the host set. Kept apart from `auth`, which holds a resolved password.\n authKind: AuthKind\n passwordEnv: Option<string>\n tmuxSession: string\n logOutput: boolean\n policy: HostPolicy\n // The [[cas]] entry this host authenticates through, when it uses ca auth.\n ca: Option<string>\n principal: Option<string>\n}>\n\nexport type AuthKind = \"password_env\" | \"key\" | \"key_env\" | \"agent\" | \"ca\"\n\nexport type PoolSettings = Readonly<{\n maxPerHost: number\n idleTimeoutMs: number\n acquireTimeoutMs: number\n // exec's wait when the call gives no timeout_ms, and the most a call may ask for.\n execTimeoutMs: number\n maxExecTimeoutMs: number\n}>\n\nexport const DEFAULT_EXEC_TIMEOUT_MS = 600_000\nexport const DEFAULT_MAX_EXEC_TIMEOUT_MS = 3_600_000\n\n// Only read when parsePool succeeded; errors are reported before this is used.\nconst DEFAULT_POOL: PoolSettings = {\n maxPerHost: 2,\n idleTimeoutMs: 60000,\n acquireTimeoutMs: 30000,\n execTimeoutMs: DEFAULT_EXEC_TIMEOUT_MS,\n maxExecTimeoutMs: DEFAULT_MAX_EXEC_TIMEOUT_MS,\n}\n\nexport type AuditSettings = Readonly<{ path: string }>\n\n// Where a CA's private key comes from. Never the key itself: inline keys are rejected.\nexport type CaKeyRef =\n | Readonly<{ kind: \"cmd\"; cmd: string }>\n | Readonly<{ kind: \"env\"; name: string }>\n | Readonly<{ kind: \"file\"; path: string }>\n\nexport type CaEntry = Readonly<{ name: string; key: CaKeyRef; principal: string; sourceAddress: Option<string> }>\n\nexport type Fleet = Readonly<{\n hosts: HostEntry[]\n pool: PoolSettings\n audit: AuditSettings\n cas: ReadonlyArray<CaEntry>\n runtimeDir: Option<string>\n knownHosts: Option<string>\n}>\n\ntype RawHost = Record<string, unknown>\n\nconst str = (v: unknown): Option<string> => (typeof v === \"string\" && v.length > 0 ? Option(v) : Option.none<string>())\nconst num = (v: unknown): Option<number> => (typeof v === \"number\" ? Option(v) : Option.none<number>())\n\n// Exactly-one-auth + secrets-by-reference. Returns the auth options or an error string.\ntype HostAuth = Readonly<{ auth: ResolveAuthOptions; kind: AuthKind }>\n\nconst resolveHostAuth = (name: string, h: RawHost): Either<string, HostAuth> => {\n if (h.password !== undefined || h.private_key !== undefined) {\n return Left<string, HostAuth>(`host \"${name}\": secrets must not be inline; use key/key_env/agent/password_env/ca`)\n }\n const modes = [\n str(h.key).map((): AuthKind => \"key\"),\n str(h.key_env).map((): AuthKind => \"key_env\"),\n str(h.password_env).map((): AuthKind => \"password_env\"),\n h.agent === true ? Option<AuthKind>(\"agent\") : Option.none<AuthKind>(),\n str(h.ca).map((): AuthKind => \"ca\"),\n ].flatMap((o) => o.toArray())\n if (modes.length === 0)\n return Left<string, HostAuth>(\n `host \"${name}\": no auth mode (need exactly one of key/key_env/agent/password_env/ca)`,\n )\n if (modes.length > 1)\n return Left<string, HostAuth>(`host \"${name}\": exactly one auth mode allowed, found ${modes.length}`)\n const auth: ResolveAuthOptions = {\n password: str(h.password_env)\n .flatMap((v) => Option(process.env[v] ?? \"\"))\n .filter((v) => v.length > 0),\n keyPath: str(h.key),\n keyEnvVar: str(h.key_env),\n useAgent: h.agent === true,\n }\n return Right<string, HostAuth>({ auth, kind: modes[0] })\n}\n\n// Node's timers take at most 2^31 - 1 ms (about 24.8 days); a larger or non-positive delay fires at once.\nconst MAX_TIMER_MS = 2 ** 31 - 1\n\n// Every [pool] value is a whole number from 1 to MAX_TIMER_MS. There is no \"off\" value: 0 would\n// make every exec time out at once.\nconst poolValue = (p: Record<string, unknown>, key: string, fallback: number): Either<string, number> => {\n const v = p[key]\n if (v === undefined) return Right<string, number>(fallback)\n return typeof v === \"number\" && Number.isInteger(v) && v >= 1 && v <= MAX_TIMER_MS\n ? Right<string, number>(v)\n : Left<string, number>(`pool.${key} must be a whole number from 1 to ${MAX_TIMER_MS}`)\n}\n\nconst parsePool = (raw: Record<string, unknown>): Either<ReadonlyArray<string>, PoolSettings> => {\n const p = (raw.pool ?? {}) as Record<string, unknown>\n const fields = [\n poolValue(p, \"max_per_host\", 2),\n poolValue(p, \"idle_timeout_ms\", 60000),\n poolValue(p, \"acquire_timeout_ms\", 30000),\n poolValue(p, \"exec_timeout_ms\", DEFAULT_EXEC_TIMEOUT_MS),\n poolValue(p, \"max_exec_timeout_ms\", DEFAULT_MAX_EXEC_TIMEOUT_MS),\n ] as const\n const errors = fields.flatMap((f) =>\n f.fold(\n (e) => [e],\n () => [],\n ),\n )\n const [maxPerHost, idleTimeoutMs, acquireTimeoutMs, execTimeoutMs, maxExecTimeoutMs] = fields.map((f) => f.orElse(0))\n return errors.length > 0\n ? Left<ReadonlyArray<string>, PoolSettings>(errors)\n : Right<ReadonlyArray<string>, PoolSettings>({\n maxPerHost,\n idleTimeoutMs,\n acquireTimeoutMs,\n execTimeoutMs,\n maxExecTimeoutMs,\n })\n}\n\nconst validPrincipal = (p: string): boolean => p.length > 0 && !p.includes(\",\")\n\nconst ipv4Value = (addr: string): bigint => addr.split(\".\").reduce((acc, octet) => (acc << 8n) + BigInt(octet), 0n)\n\n// Expands `::`; an IPv6 address with an embedded dotted IPv4 tail is not accepted here.\nconst ipv6Value = (addr: string): Option<bigint> => {\n if (addr.includes(\".\")) return Option.none<bigint>()\n const [head, tail = \"\"] = addr.split(\"::\")\n const headGroups = head ? head.split(\":\") : []\n const tailGroups = addr.includes(\"::\") && tail ? tail.split(\":\") : []\n const fill = addr.includes(\"::\") ? 8 - headGroups.length - tailGroups.length : 0\n const groups = [...headGroups, ...Array.from({ length: fill }, () => \"0\"), ...tailGroups]\n return groups.length === 8\n ? Option(groups.reduce((acc, g) => (acc << 16n) + BigInt(`0x${g || \"0\"}`), 0n))\n : Option.none<bigint>()\n}\n\nconst hostBitsClear = (value: bigint, width: number, prefix: number): boolean =>\n (value & ((1n << BigInt(width - prefix)) - 1n)) === 0n\n\n// `a.b.c.d/n` (n ≤ 32) or an IPv6 address with `/n` (n ≤ 128), with no host bits set: ssh-keygen\n// rejects `100.64.0.1/24`, so it must fail here rather than at every connect.\nconst validCidr = (v: string): boolean => {\n const parts = v.split(\"/\")\n if (parts.length !== 2) return false\n const [addr, prefix] = parts\n if (!/^\\d{1,3}$/.test(prefix)) return false\n const bits = Number(prefix)\n const family = isIP(addr)\n if (family === 4) return bits <= 32 && hostBitsClear(ipv4Value(addr), 32, bits)\n if (family === 6)\n return (\n bits <= 128 &&\n ipv6Value(addr).fold(\n () => false,\n (value) => hostBitsClear(value, 128, bits),\n )\n )\n return false\n}\n\nconst parseCa = (c: RawHost): Either<string, CaEntry> => {\n const name = str(c.name)\n if (name.isNone()) return Left<string, CaEntry>(\"[[cas]] entry has no name\")\n const n = name.orElse(\"\")\n if (c.key !== undefined || c.private_key !== undefined)\n return Left<string, CaEntry>(`ca \"${n}\": keys must not be inline; use key_cmd, key_env or key_file`)\n const refs: ReadonlyArray<CaKeyRef> = [\n ...str(c.key_cmd).fold<CaKeyRef[]>(\n () => [],\n (cmd) => [{ kind: \"cmd\", cmd }],\n ),\n ...str(c.key_env).fold<CaKeyRef[]>(\n () => [],\n (envName) => [{ kind: \"env\", name: envName }],\n ),\n ...str(c.key_file).fold<CaKeyRef[]>(\n () => [],\n (path) => [{ kind: \"file\", path }],\n ),\n ]\n if (refs.length !== 1)\n return Left<string, CaEntry>(`ca \"${n}\": exactly one of key_cmd, key_env, key_file is required`)\n const key = refs[0]\n const principal = c.principal === undefined ? \"fleet\" : typeof c.principal === \"string\" ? c.principal : \"\"\n if (!validPrincipal(principal)) return Left<string, CaEntry>(`ca \"${n}\": principal must be non-empty with no commas`)\n const sourceAddress = str(c.source_address)\n if (c.source_address !== undefined && !sourceAddress.fold(() => false, validCidr))\n return Left<string, CaEntry>(`ca \"${n}\": source_address must be an IPv4 or IPv6 CIDR`)\n return Right<string, CaEntry>({ name: n, key, principal, sourceAddress })\n}\n\nconst parseAudit = (raw: Record<string, unknown>): Either<string, AuditSettings> => {\n const a = (raw.audit ?? {}) as Record<string, unknown>\n if (a.path === undefined) return Right<string, AuditSettings>({ path: DEFAULT_AUDIT_PATH })\n return str(a.path).fold(\n () => Left<string, AuditSettings>(\"audit.path must be a non-empty string\"),\n (path) => Right<string, AuditSettings>({ path }),\n )\n}\n\n// smol-toml's message quotes the offending source lines, which can hold an inline secret, and\n// fleet_setup relays this text to the MCP client. Keep the location and the first line only.\n/** @interop smol-toml throws an Error carrying line and column fields. */\nconst tomlError = (e: unknown): string => {\n const err = e as Error & { line?: number; column?: number }\n const summary = err.message.split(\"\\n\")[0]\n return err.line === undefined\n ? `Invalid TOML: ${summary}`\n : `Invalid TOML at line ${err.line}, column ${err.column}: ${summary}`\n}\n\nexport const parseFleet = (toml: string): Either<string, Fleet> => {\n const parsed = Try(() => parseToml(toml) as Record<string, unknown>)\n if (parsed.isFailure()) return Left<string, Fleet>(tomlError(parsed.error))\n const raw = parsed.orThrow()\n\n const defaults = (raw.defaults ?? {}) as Record<string, unknown>\n const defaultPort = num(defaults.port).orElse(22)\n const defaultUser = str(defaults.user)\n const defaultSession = str(defaults.tmux_session).orElse(\"agent\")\n const rawHosts = Array.isArray(raw.hosts) ? (raw.hosts as RawHost[]) : []\n // Parsed once; if it fails, hosts resolve against EMPTY_TABLE so its errors appear once.\n const defaultsPolicy = parsePolicyTable(defaults.policy, \"defaults.policy\")\n const defaultsTable: PolicyTable = defaultsPolicy.orElse(EMPTY_TABLE)\n\n const names = rawHosts.map((h) => str(h.name).orElse(\"\"))\n const rawCas = Array.isArray(raw.cas) ? (raw.cas as RawHost[]) : []\n const caNames = rawCas.map((c) => str(c.name).orElse(\"\"))\n const parseHost = (h: RawHost, index: number): Either<string, HostEntry> => {\n const name = str(h.name).orElse(\"\")\n if (!validateSession(name).isRight())\n return Left<string, HostEntry>(`invalid host name \"${name}\": only letters, digits, hyphen, underscore`)\n if (names.indexOf(name) < index) return Left<string, HostEntry>(`duplicate host name \"${name}\"`)\n const address = str(h.host)\n if (address.isNone()) return Left<string, HostEntry>(`host \"${name}\": missing host address`)\n if (h.log_output !== undefined && typeof h.log_output !== \"boolean\")\n return Left<string, HostEntry>(`host \"${name}\": log_output must be true or false`)\n const ca = str(h.ca)\n if (ca.isSome() && !caNames.includes(ca.orElse(\"\")))\n return Left<string, HostEntry>(`host \"${name}\": ca \"${ca.orElse(\"\")}\" is not defined in [[cas]]`)\n const principal = typeof h.principal === \"string\" ? Option(h.principal) : Option.none<string>()\n if (h.principal !== undefined && ca.isNone())\n return Left<string, HostEntry>(`host \"${name}\": principal applies only to ca auth`)\n if (h.principal !== undefined && !principal.fold(() => false, validPrincipal))\n return Left<string, HostEntry>(`host \"${name}\": principal must be non-empty with no commas`)\n const where = `host \"${name}\": policy`\n const policy = parsePolicyTable(h.policy, where)\n .flatMap((table) => resolvePolicy(where, defaultsTable, table))\n .mapLeft((errs) => errs.join(\"\\n\"))\n return resolveHostAuth(name, h).flatMap(({ auth, kind }) =>\n policy.map((p) => ({\n name,\n host: address.orElse(\"\"),\n port: num(h.port).orElse(defaultPort),\n user: str(h.user).or(defaultUser),\n auth,\n authKind: kind,\n passwordEnv: str(h.password_env),\n tmuxSession: str(h.tmux_session).orElse(defaultSession),\n logOutput: h.log_output === true,\n policy: p,\n ca,\n principal,\n })),\n )\n }\n\n const audit = parseAudit(raw)\n const results = rawHosts.map(parseHost)\n const casResults = rawCas.map(parseCa)\n const caErrors = [\n ...casResults.flatMap((r) =>\n r.fold<string[]>(\n (msg) => [msg],\n () => [],\n ),\n ),\n ...caNames.filter((n, i) => n !== \"\" && caNames.indexOf(n) < i).map((n) => `ca \"${n}\": duplicate name`),\n ]\n const pool = parsePool(raw)\n const errors = [\n ...caErrors,\n ...defaultsPolicy.fold(\n (errs) => [...errs],\n () => [],\n ),\n ...pool.fold(\n (errs) => [...errs],\n () => [],\n ),\n ...[audit, ...results].flatMap((r) =>\n r.fold<string[]>(\n (msg) => [msg],\n () => [],\n ),\n ),\n ]\n const hosts = results.flatMap((r) =>\n r.fold<HostEntry[]>(\n () => [],\n (entry) => [entry],\n ),\n )\n\n return errors.length > 0\n ? Left<string, Fleet>(`Fleet config errors:\\n${List(errors).toArray().join(\"\\n\")}`)\n : Right<string, Fleet>({\n hosts,\n pool: pool.orElse(DEFAULT_POOL),\n audit: audit.orElse({ path: DEFAULT_AUDIT_PATH }),\n cas: casResults.flatMap((r) =>\n r.fold<CaEntry[]>(\n () => [],\n (c) => [c],\n ),\n ),\n runtimeDir: str(raw.runtime_dir),\n knownHosts: str(raw.known_hosts),\n })\n}\n\n// Startup warnings: an empty fleet, a password variable that isn't set, and hosts that will deny\n// every call until agent identity exists (the agent is \"unknown\" until then).\nexport const fleetWarnings = (fleet: Fleet): ReadonlyArray<string> => [\n ...(fleet.hosts.length === 0 ? [\"no hosts configured; add a [[hosts]] entry and restart\"] : []),\n ...fleet.hosts.flatMap((h) =>\n h.passwordEnv\n .filter((v) => !process.env[v])\n .fold(\n () => [],\n (v) => [`host \"${h.name}\": password_env ${v} is not set`],\n ),\n ),\n ...fleet.hosts\n .filter((h) => h.policy.agents.isSome())\n .map((h) => `host \"${h.name}\": policy.agents is set, so every call is denied until agent identity is available`),\n]\n","import { lstat, readFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nimport {\n type CaKeySource,\n type CommandResult,\n type ConnectFactory,\n createPool,\n type Custody,\n type ExecOptions,\n type HostStatus,\n mint,\n nodeProcessRunner,\n type Openssh,\n opensshConnect,\n type Pool,\n type ProcessRunner,\n type ProcResult,\n resolveAuth,\n socketPathFor,\n type TmuxRunner,\n} from \"@ssh-mcp/core\"\nimport { Either, Left, Map, Option, Right, Set } from \"functype\"\nimport { Path } from \"functype-os\"\nimport { type ConnectConfig } from \"ssh2\"\n\nimport { type CaEntry, type CaKeyRef, type Fleet, type HostEntry } from \"./fleet.js\"\nimport { type HostPolicy } from \"./policy.js\"\n\nexport type FleetRunner = Readonly<{\n hostNames: string[]\n tmuxRunnerFor: (host: string, agent: string) => Either<string, TmuxRunner>\n // timeoutMs defaults to [pool].exec_timeout_ms and is capped at [pool].max_exec_timeout_ms.\n exec: (host: string, command: string, agent: string, timeoutMs?: number) => Promise<CommandResult>\n sessionFor: (host: string) => string\n logOutputFor: (host: string) => boolean\n policyFor: (host: string) => Option<HostPolicy>\n status: () => HostStatus[]\n // The last connect or transport failure on a configured host, cleared by its next success.\n lastError: (host: string) => Option<HostError>\n shutdown: () => Promise<void>\n}>\n\nexport type HostError = Readonly<{ message: string; at: string }>\n\nexport type HostRow = Readonly<{\n name: string\n state: HostStatus[\"state\"] | \"error\"\n inFlight: number\n last_error?: HostError\n}>\n\n// The rows hosts_list and fleet_setup show. \"disconnected\" only means no open connection, which is\n// normal before the first call. A host whose last call failed for a connection reason (refused,\n// auth, a busy pool's acquire timeout) reads \"error\" with the reason, until its next success. A CA\n// host's rows are named \"<host> (<agent>)\"; host names can't contain a space.\nexport const hostRows = (runner: Pick<FleetRunner, \"status\" | \"lastError\">): ReadonlyArray<HostRow> =>\n runner.status().map((row): HostRow =>\n row.state !== \"disconnected\"\n ? row\n : runner.lastError(row.name.split(\" \")[0]).fold<HostRow>(\n () => row,\n (e) => ({ ...row, state: \"error\", last_error: e }),\n ),\n )\n\n// What CA hosts need at runtime. `openFor` replaces the real OpenSSH factory, for tests.\nexport type CaWiring = Readonly<{\n custody: Custody\n proc: ProcessRunner\n knownHosts: string\n openFor?: (agent: string, host: HostEntry) => Openssh\n}>\n\nconst stateDir = (): string => join(homedir(), \".local\", \"state\", \"ssh-fleet\")\n\nexport const defaultRuntimeDir = (): string => {\n const xdg = process.env.XDG_RUNTIME_DIR\n return xdg ? join(xdg, \"ssh-fleet\") : join(stateDir(), \"run\")\n}\n\nexport const defaultKnownHosts = (): string => join(stateDir(), \"known_hosts\")\n\nconst fromFile = async (path: string): Promise<Either<string, string>> => {\n const expanded = Path.expand(path)\n if (expanded.isLeft()) return Left<string, string>(`key_file ${path}: ${expanded.value.message}`)\n const file = expanded.value\n const info = await lstat(file).catch((e: unknown) => e as Error)\n if (info instanceof Error) return Left<string, string>(`key_file ${file}: ${info.message}`)\n if (info.isSymbolicLink()) return Left<string, string>(`key_file ${file} is a symlink`)\n if (!info.isFile()) return Left<string, string>(`key_file ${file} is not a regular file`)\n if ((info.mode & 0o077) !== 0) return Left<string, string>(`key_file ${file} must be mode 0600 or stricter`)\n if (info.uid !== process.getuid?.()) return Left<string, string>(`key_file ${file} is not owned by the hub user`)\n return Right<string, string>(await readFile(file, \"utf8\"))\n}\n\nconst readCaKey = async (ref: CaKeyRef, timeoutMs: number): Promise<Either<string, string>> => {\n switch (ref.kind) {\n case \"cmd\": {\n // A secrets-manager CLI may need its own environment (a service token, a keychain session),\n // so key_cmd sees the hub's environment; it is operator config, not an OpenSSH child.\n const env = Object.fromEntries(\n Object.entries(process.env).flatMap(([k, v]) => (v === undefined ? [] : [[k, v]])),\n ) as Readonly<Record<string, string>>\n const r = await nodeProcessRunner\n .run(\"sh\", [\"-c\", ref.cmd], { env, timeoutMs })\n .catch((e: unknown): ProcResult => ({ code: -1, stdout: \"\", stderr: (e as Error).message }))\n return r.code === 0 && r.stdout.trim().length > 0\n ? Right<string, string>(`${r.stdout.trim()}\\n`)\n : Left<string, string>(\n r.timedOut === true\n ? `key_cmd timed out after ${timeoutMs} ms`\n : `key_cmd exited ${r.code}: ${r.stderr.trim() || \"no output\"}`,\n )\n }\n case \"env\": {\n const value = process.env[ref.name]\n // Read once at startup, then gone, so no later child process inherits the key.\n delete process.env[ref.name]\n return value ? Right<string, string>(value) : Left<string, string>(`environment variable ${ref.name} is not set`)\n }\n case \"file\":\n return fromFile(ref.path)\n }\n}\n\n// key_cmd gets 30 s and no terminal: a command that would prompt fails instead of blocking startup.\nexport const KEY_CMD_TIMEOUT_MS = 30_000\n\n// Options are an object, not a positional number, so `cas.map(caKeySource)` can't pass an index as a timeout.\nexport const caKeySource = (entry: CaEntry, opts: Readonly<{ timeoutMs?: number }> = {}): CaKeySource => ({\n name: entry.name,\n key: () => readCaKey(entry.key, opts.timeoutMs ?? KEY_CMD_TIMEOUT_MS),\n})\n\n// Mints and discards one certificate per CA, so a CA the agent can't sign with, or a certificate\n// option ssh-keygen rejects, stops startup instead of failing every later call.\nexport const selfTestCas = async (\n custody: Custody,\n cas: ReadonlyArray<CaEntry>,\n proc: ProcessRunner,\n): Promise<Either<string, void>> => {\n const tested = await Either.traverseAsync(cas, async (ca) => {\n const minted = await mint(\n custody,\n { ca: ca.name, agent: \"self-test\", principal: ca.principal, sourceAddress: ca.sourceAddress },\n proc,\n )\n return minted.fold(\n (msg) => Promise.resolve(Left<string, void>(msg)),\n (m) => m.dispose().then(() => Right<string, void>(undefined)),\n )\n })\n return tested.map(() => undefined)\n}\n\nconst sshConfigFor = (h: HostEntry): ConnectConfig => ({\n host: h.host,\n port: h.port,\n username: h.user.orElse(process.env.USER ?? \"root\"),\n})\n\nconst toSshConfig = async (h: HostEntry): Promise<Either<string, ConnectConfig>> => {\n const auth = await resolveAuth(h.auth)\n return auth.map((a) => ({ ...sshConfigFor(h), ...a }))\n}\n\nexport const buildFleetRunner = async (\n fleet: Fleet,\n connect?: ConnectFactory,\n ca: Option<CaWiring> = Option.none<CaWiring>(),\n): Promise<Either<string, FleetRunner>> => {\n // CA hosts never enter the ssh2 pool; they get OpenSSH pools per agent.\n const caHosts = fleet.hosts.filter((h) => h.ca.isSome())\n const sshHosts = fleet.hosts.filter((h) => h.ca.isNone())\n if (caHosts.length > 0 && ca.isNone())\n return Left<string, FleetRunner>(`host \"${caHosts[0].name}\" uses ca auth but CA custody is not running`)\n\n // A host whose auth can't resolve (a missing key file, an unset key_env, no SSH_AUTH_SOCK) doesn't\n // stop startup: it fails every call with the reason, and fleet_setup shows it from the first start.\n const resolved = await Promise.all(sshHosts.map(async (h) => ({ name: h.name, sshConfig: await toSshConfig(h) })))\n const ready = resolved.flatMap(({ name, sshConfig }) =>\n sshConfig.fold<Array<{ name: string; sshConfig: ConnectConfig }>>(\n () => [],\n (cfg) => [{ name, sshConfig: cfg }],\n ),\n )\n const authErrors = resolved.flatMap(({ name, sshConfig }) =>\n sshConfig.fold<Array<[string, string]>>(\n (e) => [[name, e]],\n () => [],\n ),\n )\n return Right<string, FleetRunner>(makeRunner(fleet, ready, authErrors, caHosts, ca, connect))\n}\n\nconst realOpenssh = (wiring: CaWiring, caEntry: CaEntry, agent: string, host: HostEntry): Openssh => {\n const socket = socketPathFor(wiring.custody.runtimeDir, agent, host.name)\n return opensshConnect({\n proc: wiring.proc,\n knownHosts: wiring.knownHosts,\n socketPath: socket.orElse(\"\"),\n mintFor: () =>\n socket.fold(\n (msg) => Promise.resolve(Left(msg)),\n () =>\n mint(\n wiring.custody,\n {\n ca: caEntry.name,\n agent,\n principal: host.principal.orElse(caEntry.principal),\n sourceAddress: caEntry.sourceAddress,\n },\n wiring.proc,\n ),\n ),\n })\n}\n\ntype CaPool = Readonly<{ host: string; agent: string; pool: Pool; openssh: Openssh }>\n\nconst makeRunner = (\n fleet: Fleet,\n resolved: ReadonlyArray<{ name: string; sshConfig: ConnectConfig }>,\n authErrors: ReadonlyArray<readonly [string, string]>,\n caHosts: ReadonlyArray<HostEntry>,\n ca: Option<CaWiring>,\n connect?: ConnectFactory,\n): FleetRunner => {\n const sessions = Map.of(...fleet.hosts.map((h): [string, string] => [h.name, h.tmuxSession]))\n const logOutput = Map.of(...fleet.hosts.map((h): [string, boolean] => [h.name, h.logOutput]))\n const policies = Map.of(...fleet.hosts.map((h): [string, HostPolicy] => [h.name, h.policy]))\n const caHostByName = Map.of(...caHosts.map((h): [string, HostEntry] => [h.name, h]))\n const caByName = Map.of(...fleet.cas.map((c): [string, CaEntry] => [c.name, c]))\n const sshPool = createPool(resolved, fleet.pool, connect)\n const names = Set.of(...fleet.hosts.map((h) => h.name))\n // One pool per (agent, CA host), created on first use: an agent never rides another's session.\n const caPools = new globalThis.Map<string, CaPool>()\n\n const caPoolFor = (host: HostEntry, agent: string, wiring: CaWiring): Pool => {\n const key = `${agent}\\0${host.name}`\n const existing = caPools.get(key)\n if (existing !== undefined) return existing.pool\n const caEntry = caByName.get(host.ca.orElse(\"\")).orThrow(new Error(`ca \"${host.ca.orElse(\"\")}\" is not defined`))\n const openssh = (wiring.openFor ?? ((a, h) => realOpenssh(wiring, caEntry, a, h)))(agent, host)\n const pool = createPool([{ name: host.name, sshConfig: sshConfigFor(host) }], fleet.pool, openssh.connect)\n caPools.set(key, { host: host.name, agent, pool, openssh })\n return pool\n }\n\n const routeConnected = (host: string, command: string, agent: string, opts: ExecOptions): Promise<CommandResult> =>\n caHostByName.get(host).fold(\n () => sshPool.run(host, command, opts),\n (h) =>\n ca.fold(\n () => Promise.reject(new Error(\"CA custody is not running, and this host uses ca auth\")),\n (wiring) => caPoolFor(h, agent, wiring).run(host, command, opts),\n ),\n )\n\n const authErrorByName = Map.of(...authErrors.map(([name, msg]): [string, string] => [name, msg]))\n\n const route = (host: string, command: string, agent: string, opts: ExecOptions): Promise<CommandResult> =>\n authErrorByName.get(host).fold(\n () => routeConnected(host, command, agent, opts),\n (msg) => Promise.reject(new Error(msg)),\n )\n\n const startedAt = new Date().toISOString()\n const lastErrors = new globalThis.Map<string, HostError>(\n authErrors.map(([name, message]): [string, HostError] => [name, { message, at: startedAt }]),\n )\n\n // A rejection is a connection-level failure (connect, auth, transport, or a busy pool's acquire\n // timeout); a non-zero exit code resolves and isn't one.\n // Only configured hosts are recorded, so unknown names can't grow the map.\n const effectiveTimeout = (requested?: number): number =>\n Math.min(requested ?? fleet.pool.execTimeoutMs, fleet.pool.maxExecTimeoutMs)\n\n const runOn = (host: string, command: string, agent: string, timeoutMs?: number): Promise<CommandResult> =>\n route(host, command, agent, { timeoutMs: effectiveTimeout(timeoutMs) }).then(\n (result) => {\n lastErrors.delete(host)\n return result\n },\n (e: unknown) => {\n if (names.has(host)) lastErrors.set(host, { message: (e as Error).message, at: new Date().toISOString() })\n return Promise.reject(e)\n },\n )\n\n const caStatus = (): HostStatus[] =>\n caHosts.flatMap((h) => {\n const pools = [...caPools.values()].filter((p) => p.host === h.name)\n return pools.length === 0\n ? [{ name: h.name, state: \"disconnected\" as const, inFlight: 0 }]\n : pools.flatMap((p) => p.pool.status().map((s) => ({ ...s, name: `${h.name} (${p.agent})` })))\n })\n\n // One message for every tool, so exec and tmux_* both list the configured names.\n const unknownHost = (host: string): string =>\n names.size === 0\n ? `unknown host \"${host}\" — no hosts are configured`\n : `unknown host \"${host}\" — configured: ${[...names].join(\", \")}`\n\n return {\n hostNames: fleet.hosts.map((h) => h.name),\n sessionFor: (host) => sessions.get(host).orElse(\"agent\"),\n logOutputFor: (host) => logOutput.get(host).orElse(false),\n policyFor: (host) => policies.get(host),\n tmuxRunnerFor: (host, agent) =>\n names.has(host)\n ? Right<string, TmuxRunner>((command) => runOn(host, command, agent))\n : Left<string, TmuxRunner>(unknownHost(host)),\n exec: (host, command, agent, timeoutMs) =>\n names.has(host) ? runOn(host, command, agent, timeoutMs) : Promise.reject(new Error(unknownHost(host))),\n status: () => [\n ...sshPool.status(),\n ...authErrors.map(([name]): HostStatus => ({ name, state: \"disconnected\", inFlight: 0 })),\n ...caStatus(),\n ],\n lastError: (host) => Option(lastErrors.get(host)),\n // Pools first (closes connections), then every master is awaited, and only then is the CA\n // agent stopped and the runtime dir removed.\n shutdown: async () => {\n const pools = [...caPools.values()]\n await Promise.all([sshPool.shutdown(), ...pools.map((p) => p.pool.shutdown())])\n await Promise.all(pools.map((p) => p.openssh.shutdown()))\n await ca.fold(\n () => Promise.resolve(),\n (wiring) => wiring.custody.shutdown(),\n )\n },\n }\n}\n","// The starter config written on first start at the default path. Every line is a comment, so it\n// parses to zero hosts. Inside an example block, TOML lines start with \"# \" and prose with \"## \", so\n// removing the leading \"# \" turns a block into valid TOML (test/template.spec.ts checks each one).\nexport const FLEET_TEMPLATE = `# ssh-fleet-mcp-server config.\n#\n# Add at least one [[hosts]] entry, then restart the server. In Claude Code, use /mcp and\n# reconnect. The fleet_setup tool shows what is configured and what is still missing.\n#\n# To use an example block, remove the leading \"# \" from each of its lines.\n#\n# Secrets never go in this file. Each host names one place its secret comes from:\n# password_env = env var holding a password key = path to a private key\n# key_env = env var holding a key (PEM) agent = true (uses SSH_AUTH_SOCK)\n# ca = a [[cas]] entry: the server signs a 10-minute certificate per connection\n#\n# Reference: https://github.com/jordanburke/ssh-client-mcp-server/tree/main/packages/orchestrator#fleet-config-schema\n\n# --- example: host with a password from the environment ---\n# [[hosts]]\n# name = \"mini\"\n# host = \"mini.local\"\n# user = \"me\"\n# password_env = \"MINI_SSH_PASSWORD\"\n# --- end ---\n\n# --- example: host with a key file ---\n# [[hosts]]\n# name = \"build\"\n# host = \"build.example.com\"\n# key = \"~/.ssh/id_ed25519\"\n# --- end ---\n\n# --- example: read-only policy for every host ---\n## Only commands matching an allow pattern run. See the README's Policy section.\n# [defaults.policy]\n# mode = \"readonly\"\n# allow = ['uptime', 'df -h']\n# --- end ---\n\n# --- example: exec timeouts ---\n## exec waits exec_timeout_ms unless a call passes timeout_ms, and never longer than\n## max_exec_timeout_ms. Run longer jobs in tmux (tmux_send, then tmux_read).\n# [pool]\n# exec_timeout_ms = 600000\n# max_exec_timeout_ms = 3600000\n# --- end ---\n\n# --- example: certificate authority ---\n## If key_file doesn't exist, the server creates it on first start. fleet_setup then shows\n## the line to add to a root-owned authorized-keys file on each host. To keep the key off\n## disk, use key_cmd (a command that prints it, e.g. a keychain read) or key_env instead.\n# [[cas]]\n# name = \"home\"\n# key_file = \"~/.local/state/ssh-fleet/ca-home\"\n#\n# [[hosts]]\n# name = \"nas\"\n# host = \"nas.local\"\n# user = \"fleet\"\n# ca = \"home\"\n# --- end ---\n`\n","import { existsSync, mkdirSync, statSync, writeFileSync } from \"node:fs\"\nimport { lstat } from \"node:fs/promises\"\nimport { dirname, join } from \"node:path\"\n\nimport { childEnv, type ProcessRunner } from \"@ssh-mcp/core\"\nimport { Either, Left, type Map, Option, Right, Try } from \"functype\"\nimport { Path } from \"functype-os\"\n\nimport { type AuthKind, type CaEntry, type Fleet, type HostEntry, parseFleet } from \"./fleet.js\"\nimport { type FleetRunner, type HostError, type HostRow, hostRows } from \"./runners.js\"\nimport { FLEET_TEMPLATE } from \"./template.js\"\n\nexport type ConfigPath = Readonly<{ path: string; explicit: boolean }>\n\n// --config= wins, then SSH_FLEET_CONFIG, then the default. Only the default is ever created.\nexport const resolveConfigPath = (argv: ReadonlyArray<string>, env: NodeJS.ProcessEnv, home: string): ConfigPath => {\n const flag = argv.find((a) => a.startsWith(\"--config=\"))\n if (flag) return { path: flag.slice(\"--config=\".length), explicit: true }\n return Option(env.SSH_FLEET_CONFIG)\n .filter((p) => p.length > 0)\n .fold<ConfigPath>(\n () => ({ path: join(home, \".config\", \"ssh-fleet\", \"fleet.toml\"), explicit: false }),\n (p) => ({ path: p, explicit: true }),\n )\n}\n\nconst isCode = (e: unknown, c: string): boolean => (e as NodeJS.ErrnoException).code === c\n\n// Writes the starter config at the default path when it is missing. An explicit path is never\n// written: a typo there must fail startup, not produce a healthy-looking server with no hosts.\nexport const ensureConfig = (config: ConfigPath): Either<string, Readonly<{ path: string; created: boolean }>> => {\n const { path } = config\n if (config.explicit)\n return Try(() => statSync(path)).fold(\n (e) =>\n Left(\n isCode(e, \"ENOENT\")\n ? `Cannot read fleet config at ${path}: no such file (only the default path is created on first start)`\n : `Cannot read fleet config at ${path}: ${(e as Error).message}`,\n ),\n () => Right({ path, created: false }),\n )\n // No existence check first: \"wx\" fails with EEXIST when the file is there, whether it always was\n // or another client created it a moment ago.\n return Try(() => {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n writeFileSync(path, FLEET_TEMPLATE, { flag: \"wx\", mode: 0o600 })\n }).fold(\n (e) =>\n isCode(e, \"EEXIST\")\n ? Right({ path, created: false })\n : Left(`Cannot create fleet config at ${path}: ${(e as Error).message}`),\n () => Right({ path, created: true }),\n )\n}\n\n// The expanded key_file path when the CA uses key_file and nothing is at that path (ENOENT only:\n// lstat, so a dangling symlink counts as present).\nexport const missingKeyFile = async (ca: CaEntry): Promise<Either<string, Option<string>>> => {\n if (ca.key.kind !== \"file\") return Right(Option.none<string>())\n const expanded = Path.expand(ca.key.path)\n if (expanded.isLeft()) return Left(`CA \"${ca.name}\": key_file ${ca.key.path}: ${expanded.value.message}`)\n const path = expanded.value\n const missing = await lstat(path).then(\n () => false,\n (e: unknown) => isCode(e, \"ENOENT\"),\n )\n return Right(missing ? Option(path) : Option.none<string>())\n}\n\n// Creates the key for each CA whose key_file is configured and missing (ENOENT only). Anything else at\n// that path, including a symlink or a file with the wrong mode, is left for the key_file checks at\n// custody start to report. key_cmd and key_env never create anything. Returns the created CAs' names.\nexport const ensureCaKeyFiles = async (\n cas: ReadonlyArray<CaEntry>,\n proc: ProcessRunner,\n): Promise<Either<string, ReadonlyArray<string>>> => {\n const created = await Either.traverseAsync(cas, async (ca): Promise<Either<string, ReadonlyArray<string>>> => {\n const missing = await missingKeyFile(ca)\n if (missing.isLeft()) return Left(missing.value)\n if (missing.value.isNone()) return Right([])\n const path = missing.value.orElse(\"\")\n const made = Try(() => mkdirSync(dirname(path), { recursive: true, mode: 0o700 }))\n if (made.isFailure()) return Left(`CA \"${ca.name}\": creating key_file ${path} failed: ${made.error.message}`)\n const r = await proc.run(\n \"ssh-keygen\",\n [\"-q\", \"-t\", \"ed25519\", \"-N\", \"\", \"-C\", `ssh-fleet-ca-${ca.name}`, \"-f\", path],\n { env: childEnv(), input: \"\" },\n )\n if (r.code === 0) return Right([ca.name])\n // ssh-keygen refuses an existing file, so a failure with the file present means another\n // process created it first.\n if (existsSync(path)) return Right([])\n return Left(`CA \"${ca.name}\": creating key_file ${path} failed: ${r.stderr.trim() || `exit ${r.code}`}`)\n })\n return created.map((names) => names.flat())\n}\n\n// What fleet_setup reports from, fixed once startup finishes.\nexport type SetupContext = Readonly<{\n configPath: string\n configCreated: boolean\n fleet: Fleet\n // The config text loaded at start, and a way to read the file now, to notice edits made after start.\n loadedConfig: string\n readConfig: () => Either<string, string>\n // CA name → its public key line, as loaded into custody.\n caPublicKeys: Map<string, string>\n caCreated: ReadonlyArray<string>\n runtimeDir: Option<string>\n knownHosts: Option<string>\n}>\n\nexport type HostReport = Readonly<{\n name: string\n address: string\n auth: AuthKind\n password_env?: string\n env_set?: boolean\n ca?: string\n principal?: string\n connections: ReadonlyArray<HostRow>\n last_error?: HostError\n enroll_line?: string\n}>\n\nexport type CaReport = Readonly<{\n name: string\n key_source: \"key_cmd\" | \"key_env\" | \"key_file\"\n key_file?: string\n created_this_start: boolean\n public_key?: string\n enroll_line?: string\n}>\n\nexport type SetupReport = Readonly<{\n config: Readonly<{\n path: string\n created_this_start: boolean\n changed_since_start: boolean\n hosts_on_disk?: number\n errors_on_disk?: string\n }>\n next_steps: ReadonlyArray<string>\n hosts: ReadonlyArray<HostReport>\n cas: ReadonlyArray<CaReport>\n audit_log: string\n runtime_dir?: string\n known_hosts?: string\n}>\n\nexport const enrollLine = (principal: string, publicKey: string): string =>\n `cert-authority,principals=\"${principal}\" ${publicKey.trim()}`\n\nexport const hostAddress = (h: HostEntry): string =>\n h.user.fold(\n () => `${h.host}:${h.port}`,\n (u) => `${u}@${h.host}:${h.port}`,\n )\n\nconst KEY_SOURCE = { cmd: \"key_cmd\", env: \"key_env\", file: \"key_file\" } as const\n\n// Built field by field: a HostEntry carries a resolved password in `auth`, so no whole entry, and no\n// key_cmd string, may reach the report.\nconst hostReport = (\n h: HostEntry,\n ctx: SetupContext,\n runner: Pick<FleetRunner, \"status\" | \"lastError\">,\n env: NodeJS.ProcessEnv,\n): HostReport => {\n const ca = h.ca.flatMap((name) => Option(ctx.fleet.cas.find((c) => c.name === name)))\n const principal = h.principal.or(ca.map((c) => c.principal))\n const enroll = ca\n .flatMap((c) => ctx.caPublicKeys.get(c.name))\n .flatMap((pub) => principal.map((p) => enrollLine(p, pub)))\n return {\n name: h.name,\n address: hostAddress(h),\n auth: h.authKind,\n ...h.passwordEnv.fold(\n () => ({}),\n (v) => ({ password_env: v, env_set: Boolean(env[v]) }),\n ),\n ...h.ca.fold(\n () => ({}),\n (name) => ({ ca: name, principal: principal.orElse(\"\") }),\n ),\n connections: hostRows(runner).filter((row) => row.name === h.name || row.name.startsWith(`${h.name} (`)),\n ...runner.lastError(h.name).fold(\n () => ({}),\n (e) => ({ last_error: e }),\n ),\n ...enroll.fold(\n () => ({}),\n (line) => ({ enroll_line: line }),\n ),\n }\n}\n\nconst caReport = (c: CaEntry, ctx: SetupContext): CaReport => {\n const pub = ctx.caPublicKeys.get(c.name)\n return {\n name: c.name,\n key_source: KEY_SOURCE[c.key.kind],\n ...(c.key.kind === \"file\" ? { key_file: c.key.path } : {}),\n created_this_start: ctx.caCreated.includes(c.name),\n ...pub.fold(\n () => ({}),\n (p) => ({ public_key: p.trim(), enroll_line: enrollLine(c.principal, p) }),\n ),\n }\n}\n\ntype Drift = Readonly<{\n changed: boolean\n fields: Readonly<{ changed_since_start: boolean; hosts_on_disk?: number; errors_on_disk?: string }>\n step: ReadonlyArray<string>\n}>\n\nconst RECONNECT = \"reconnect the server to load it (in Claude Code: /mcp, then reconnect)\"\n\n// The server reads fleet.toml once. An edit after start is invisible until a reconnect, so compare the\n// file now with the text loaded at start, and parse it to say what a reconnect would load.\nconst configDrift = (ctx: SetupContext): Drift => {\n const now = ctx.readConfig()\n if (now.isRight() && now.value === ctx.loadedConfig)\n return { changed: false, fields: { changed_since_start: false }, step: [] }\n const parsed = now.flatMap(parseFleet)\n return parsed.fold<Drift>(\n (errors) => ({\n changed: true,\n fields: { changed_since_start: true, errors_on_disk: errors },\n step: [`fleet.toml changed since the server started and now has errors; fix them before reconnecting: ${errors}`],\n }),\n (onDisk) => ({\n changed: true,\n fields: { changed_since_start: true, hosts_on_disk: onDisk.hosts.length },\n step: [\n `fleet.toml changed since the server started (${onDisk.hosts.length} hosts on disk, ${ctx.fleet.hosts.length} loaded): ${RECONNECT}.`,\n ],\n }),\n )\n}\n\n// The fleet_setup tool's answer: what is configured, its live state, and what to do next.\nexport const setupReport = (\n ctx: SetupContext,\n runner: Pick<FleetRunner, \"status\" | \"lastError\">,\n env: NodeJS.ProcessEnv = process.env,\n): SetupReport => {\n const hosts = ctx.fleet.hosts.map((h) => hostReport(h, ctx, runner, env))\n const cas = ctx.fleet.cas.map((c) => caReport(c, ctx))\n const drift = configDrift(ctx)\n const nextSteps = [\n ...drift.step,\n ...(hosts.length === 0 && !drift.changed\n ? [`Add a [[hosts]] entry to ${ctx.configPath}, then restart the server.`]\n : []),\n ...hosts\n .filter((h) => h.env_set === false)\n .map((h) => `${h.name}: set ${h.password_env} in the MCP client's env for this server, then restart.`),\n ...cas\n .filter((c) => c.created_this_start && c.enroll_line !== undefined)\n .map(\n (c) =>\n `Add CA \"${c.name}\"'s line to a root-owned authorized-keys file on each host that uses it: ${c.enroll_line}`,\n ),\n ...hosts.flatMap((h) => (h.last_error ? [`${h.name}: ${h.last_error.message}`] : [])),\n ]\n return {\n config: { path: ctx.configPath, created_this_start: ctx.configCreated, ...drift.fields },\n next_steps: nextSteps,\n hosts,\n cas,\n audit_log: ctx.fleet.audit.path,\n ...ctx.runtimeDir.fold(\n () => ({}),\n (d) => ({ runtime_dir: d }),\n ),\n ...ctx.knownHosts.fold(\n () => ({}),\n (k) => ({ known_hosts: k }),\n ),\n }\n}\n","import { readFileSync } from \"node:fs\"\n\nimport { type CommandResult } from \"@ssh-mcp/core\"\nimport { type Either, Left, type Map, Option, Right, Try } from \"functype\"\n\nimport { type Auditor } from \"./audit.js\"\nimport { type Fleet, type HostEntry } from \"./fleet.js\"\nimport { ALLOW } from \"./policy.js\"\nimport { type FleetRunner } from \"./runners.js\"\nimport { enrollLine, hostAddress } from \"./setup.js\"\n\nexport type CheckDeps = Readonly<{\n fleet: Fleet\n configPath: string\n runner: Pick<FleetRunner, \"exec\">\n auditor: Auditor\n // CAs whose key_file is missing: their hosts fail without a connection attempt.\n missingKeyCas: ReadonlyArray<string>\n caPublicKeys: Map<string, string>\n out: (line: string) => void\n}>\n\n// check never creates a config: a missing one is reported, not written.\nexport const readCheckConfig = (path: string): Either<string, string> =>\n Try(() => readFileSync(path, \"utf8\")).fold(\n (e) =>\n (e as NodeJS.ErrnoException).code === \"ENOENT\"\n ? Left(\n `No fleet config at ${path}. Start the server once to create one at the default path, or pass --config=.`,\n )\n : Left(`Cannot read fleet config at ${path}: ${(e as Error).message}`),\n (raw) => Right(raw),\n )\n\n// Its own runtime dir, so a running server's hub.lock doesn't block a check.\nexport const checkRuntimeDir = (runtimeDir: string): string => `${runtimeDir}-check`\n\ntype Outcome = Readonly<{ ok: boolean; detail: string }>\n\nconst outcomeOf = (r: CommandResult, startedAt: number): Outcome =>\n r.code === 0\n ? { ok: true, detail: `${Date.now() - startedAt} ms` }\n : { ok: false, detail: r.stderr.trim() || `exit ${r.code}` }\n\n// Runs `true` on every host as the operator and prints one line per host as each finishes. Policy is\n// not evaluated: this is a terminal probe, not an agent call, and every probe is audited with\n// bypass = \"policy\". Resolves to the process exit code.\nexport const runCheck = async (deps: CheckDeps): Promise<number> => {\n const { fleet, out } = deps\n if (fleet.hosts.length === 0) {\n out(`No hosts configured; add a [[hosts]] entry to ${deps.configPath}.`)\n return 0\n }\n const width = Math.max(...fleet.hosts.map((h) => h.name.length))\n const addrWidth = Math.max(...fleet.hosts.map((h) => hostAddress(h).length))\n const line = (h: HostEntry, o: Outcome): string =>\n [\n (o.ok ? \"ok\" : \"FAIL\").padEnd(5),\n h.name.padEnd(width),\n hostAddress(h).padEnd(addrWidth),\n h.authKind.padEnd(12),\n o.detail,\n ].join(\" \")\n\n const probe = async (h: HostEntry): Promise<Outcome> => {\n if (h.ca.exists((name) => deps.missingKeyCas.includes(name)))\n return { ok: false, detail: \"key_file missing: the server creates it on first start\" }\n const startedAt = Date.now()\n return deps.auditor\n .audited(\n { agent: \"operator\", tool: \"check\", host: Option(h.name), fields: { bypass: \"policy\" }, verdict: ALLOW },\n () => deps.runner.exec(h.name, \"true\", \"operator\"),\n (r) => ({ code: r.code }),\n )\n .then(\n (r) => outcomeOf(r, startedAt),\n (e: unknown) => ({ ok: false, detail: (e as Error).message }),\n )\n }\n\n const outcomes = await Promise.all(\n fleet.hosts.map((h) =>\n probe(h).then((o) => {\n out(line(h, o))\n return o\n }),\n ),\n )\n fleet.cas.forEach((c) =>\n deps.caPublicKeys.get(c.name).forEach((pub) => out(`CA ${c.name}: ${enrollLine(c.principal, pub)}`)),\n )\n return outcomes.every((o) => o.ok) ? 0 : 1\n}\n","import { type CommandResult, execOutput, tmuxKeys, tmuxList, tmuxRead, tmuxSend } from \"@ssh-mcp/core\"\nimport { type Either, Option } from \"functype\"\n\nimport { AGENT_UNKNOWN, type AuditCall, type AuditFields, type Auditor, excerpt } from \"./audit.js\"\nimport { ALLOW, evaluate, type PolicyRequest, type Verdict } from \"./policy.js\"\nimport { type FleetRunner, hostRows } from \"./runners.js\"\nimport { type SetupContext, setupReport } from \"./setup.js\"\nimport { resultError, routedError, timeoutError } from \"./tool-errors.js\"\n\n/** @interop a thrown UserError sets the message, category and suggestions the client sees; any other throw gets a label somamcp guesses from its message. */\nconst unwrap = <T>(result: Either<string, T>): T =>\n result.fold(\n (msg) => {\n throw resultError(msg)\n },\n (value) => value,\n )\n\nconst bytes = (s: string): number => Buffer.byteLength(s, \"utf8\")\nconst noSummary = (): AuditFields => ({})\n\n// Tool handlers, factored out so they can be unit-tested with a fake FleetRunner.\n// Every call is checked against its host's policy and goes through the auditor: a decision line\n// before it runs, a result line after. A denied call writes its decision line and never runs.\nexport const makeHandlers = (runner: FleetRunner, auditor: Auditor, setup: SetupContext) => {\n const call = (tool: string, host: Option<string>, verdict: Verdict, fields: AuditFields = {}): AuditCall => ({\n agent: AGENT_UNKNOWN,\n tool,\n host,\n fields,\n verdict,\n })\n\n // A host with no policy (an unknown host) gets ALLOW, so it reaches the runner and fails there.\n const gated = (host: string, req: PolicyRequest, fields: AuditFields = {}): AuditCall =>\n call(\n req.tool,\n Option(host),\n runner.policyFor(host).fold(\n () => ALLOW,\n (policy) => evaluate(policy, req),\n ),\n fields,\n )\n\n // Sizes only, unless the host opts in to an output excerpt with log_output.\n const summarizeExec =\n (host: string) =>\n (r: CommandResult): AuditFields => ({\n code: r.code,\n ...(r.signal === undefined ? {} : { signal: r.signal }),\n ...(r.timedOutMs === undefined ? {} : { timed_out_ms: r.timedOutMs }),\n ...(r.noExitStatus === true ? { no_exit_status: true } : {}),\n stdout_bytes: bytes(r.stdout),\n stderr_bytes: bytes(r.stderr),\n ...(runner.logOutputFor(host) ? { stdout_excerpt: excerpt(r.stdout), stderr_excerpt: excerpt(r.stderr) } : {}),\n })\n\n return {\n hostsList: (): Promise<string> =>\n auditor.audited(\n call(\"hosts_list\", Option.none<string>(), ALLOW),\n () => Promise.resolve(JSON.stringify(hostRows(runner))),\n noSummary,\n ),\n fleetSetup: (): Promise<string> =>\n auditor.audited(\n call(\"fleet_setup\", Option.none<string>(), ALLOW),\n () => Promise.resolve(JSON.stringify(setupReport(setup, runner))),\n noSummary,\n ),\n /** @interop a thrown UserError sets the message, category and suggestions the client sees; any other throw gets a label somamcp guesses from its message. */\n exec: async ({\n host,\n command,\n timeout_ms,\n }: {\n host: string\n command: string\n timeout_ms?: number\n }): Promise<string> => {\n const r = await auditor\n .audited(\n gated(\n host,\n { tool: \"exec\", agent: AGENT_UNKNOWN, command },\n { command, ...(timeout_ms === undefined ? {} : { timeout_ms }) },\n ),\n () => runner.exec(host, command, AGENT_UNKNOWN, timeout_ms),\n summarizeExec(host),\n )\n .catch(routedError(host))\n return execOutput(r).fold(\n (failure) => {\n throw r.timedOutMs === undefined ? resultError(`[${host}] ${failure}`) : timeoutError(host, failure)\n },\n (output) => output,\n )\n },\n tmuxList: ({ host }: { host: string }): Promise<string> =>\n auditor\n .audited(\n gated(host, { tool: \"tmux_list\", agent: AGENT_UNKNOWN }),\n async () => JSON.stringify(unwrap(await tmuxList(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN))))),\n noSummary,\n )\n .catch(routedError(host)),\n tmuxSend: ({\n host,\n session,\n input,\n submit,\n }: {\n host: string\n session?: string\n input: string\n submit?: boolean\n }): Promise<string> => {\n const target = session ?? runner.sessionFor(host)\n const press = submit ?? true\n return auditor\n .audited(\n gated(host, { tool: \"tmux_send\", agent: AGENT_UNKNOWN }, { session: target, input, submit: press }),\n async () => {\n unwrap(\n await tmuxSend(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN)), {\n session: target,\n input,\n submit: press,\n }),\n )\n return `[${host}] sent to tmux session \"${target}\".`\n },\n noSummary,\n )\n .catch(routedError(host))\n },\n tmuxRead: ({ host, session, lines }: { host: string; session?: string; lines?: number }): Promise<string> => {\n const target = session ?? runner.sessionFor(host)\n const count = lines ?? 200\n return auditor\n .audited(\n gated(host, { tool: \"tmux_read\", agent: AGENT_UNKNOWN }, { session: target, lines: count }),\n async () =>\n unwrap(\n await tmuxRead(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN)), { session: target, lines: count }),\n ),\n (transcript) => ({ stdout_bytes: bytes(transcript) }),\n )\n .catch(routedError(host))\n },\n tmuxKeys: ({ host, session, keys }: { host: string; session?: string; keys: string[] }): Promise<string> => {\n const target = session ?? runner.sessionFor(host)\n return auditor\n .audited(\n gated(host, { tool: \"tmux_keys\", agent: AGENT_UNKNOWN }, { session: target, keys }),\n async () => {\n unwrap(await tmuxKeys(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN)), { session: target, keys }))\n return `[${host}] sent keys [${keys.join(\", \")}] to tmux session \"${target}\".`\n },\n noSummary,\n )\n .catch(routedError(host))\n },\n }\n}\n","#!/usr/bin/env node\n\nimport { randomUUID } from \"node:crypto\"\nimport { mkdirSync, readFileSync } from \"node:fs\"\nimport { createRequire } from \"node:module\"\nimport { homedir } from \"node:os\"\nimport { dirname } from \"node:path\"\n\nimport { checkOpenSsh, nodeProcessRunner, socketPathFor, startCaCustody } from \"@ssh-mcp/core\"\nimport { Either, Left, Map, Option, Right, Try } from \"functype\"\nimport { Path } from \"functype-os\"\nimport { createServer } from \"somamcp/node\"\nimport { z } from \"zod\"\n\nimport { CLIENT_LABEL_ENV, clientLabel, createAuditor, openAuditLog, reportedClient } from \"./audit.js\"\nimport { buildInfo } from \"./build-info.js\"\nimport { checkRuntimeDir, readCheckConfig, runCheck } from \"./check.js\"\nimport { type Fleet, fleetWarnings, parseFleet } from \"./fleet.js\"\nimport { makeHandlers } from \"./handlers.js\"\nimport {\n buildFleetRunner,\n caKeySource,\n type CaWiring,\n defaultKnownHosts,\n defaultRuntimeDir,\n type FleetRunner,\n selfTestCas,\n} from \"./runners.js\"\nimport { ensureCaKeyFiles, ensureConfig, missingKeyFile, resolveConfigPath, type SetupContext } from \"./setup.js\"\n\nconst { version: pkgVersion } = createRequire(import.meta.url)(\"../package.json\") as {\n version: `${number}.${number}.${number}`\n}\n\nconst fatal = (msg: string): never => {\n console.error(msg)\n return process.exit(1)\n}\n\nconst expanded = (path: string): string =>\n Path.expand(path).fold(\n (e) => fatal(`invalid path ${path}: ${e.message}`),\n (p) => p,\n )\n\ntype CaStart = Readonly<{ wiring: Option<CaWiring>; created: ReadonlyArray<string> }>\n\n// When any [[cas]] entry exists (with or without hosts using it yet): check OpenSSH, create any missing\n// key_file, prepare the runtime dir and known_hosts, and load every CA key into a private ssh-agent.\n// Each failure stops startup. `check` creates nothing and uses its own runtime dir.\nconst startCa = async (fleet: Fleet, mode: \"serve\" | \"check\"): Promise<CaStart> => {\n if (fleet.cas.length === 0) return { wiring: Option.none<CaWiring>(), created: [] }\n ;(await checkOpenSsh(nodeProcessRunner)).fold(fatal, () => undefined)\n const created =\n mode === \"serve\" ? (await ensureCaKeyFiles(fleet.cas, nodeProcessRunner)).fold(fatal, (names) => names) : []\n fleet.cas\n .filter((c) => created.includes(c.name))\n .forEach((c) =>\n console.error(\n `created CA \"${c.name}\" key at ${c.key.kind === \"file\" ? c.key.path : \"\"}; add its line to each host (see fleet_setup)`,\n ),\n )\n const configured = fleet.runtimeDir.map(expanded).orElse(defaultRuntimeDir())\n const runtimeDir = mode === \"check\" ? checkRuntimeDir(configured) : configured\n const knownHosts = fleet.knownHosts.map(expanded).orElse(defaultKnownHosts())\n // Socket paths are fixed-length below runtimeDir, so one check covers every (agent, host).\n socketPathFor(runtimeDir, \"unknown\", \"host\").fold(fatal, () => undefined)\n mkdirSync(dirname(knownHosts), { recursive: true, mode: 0o700 })\n const custody = (\n await startCaCustody(\n runtimeDir,\n fleet.cas.map((c) => caKeySource(c)),\n nodeProcessRunner,\n )\n ).fold(fatal, (c) => c)\n // Mint and discard one certificate per CA: a CA that can't sign stops startup now, not every call.\n const selfTest = await selfTestCas(custody, fleet.cas, nodeProcessRunner)\n if (selfTest.isLeft()) {\n await custody.shutdown()\n fatal(selfTest.value)\n }\n return { wiring: Option<CaWiring>({ custody, proc: nodeProcessRunner, knownHosts }), created }\n}\n\n// Never leave a CA agent holding the key behind a failed start.\nconst buildRunnerOrExit = async (fleet: Fleet, ca: Option<CaWiring>): Promise<FleetRunner> =>\n (await buildFleetRunner(fleet, undefined, ca)).fold(\n async (msg) => {\n console.error(msg)\n await ca.fold(\n () => Promise.resolve(),\n (wiring) => wiring.custody.shutdown(),\n )\n return process.exit(1)\n },\n (r) => Promise.resolve(r),\n )\n\nconst caPublicKeysOf = (fleet: Fleet, ca: Option<CaWiring>): Map<string, string> =>\n ca.fold(\n () => Map.of<string, string>(),\n (wiring) =>\n Map.of(\n ...fleet.cas.flatMap((c): Array<[string, string]> =>\n wiring.custody\n .pubPath(c.name)\n .flatMap((path) => Try(() => readFileSync(path, \"utf8\")).toOption())\n .fold(\n () => [],\n (pub) => [[c.name, pub.trim()]],\n ),\n ),\n ),\n )\n\nconst parseOrExit = (raw: string): Fleet => {\n const fleet = parseFleet(raw).fold(fatal, (f) => f)\n fleetWarnings(fleet).forEach((w) => console.error(`warning: ${w}`))\n return fleet\n}\n\n// `ssh-fleet-mcp-server check`: connect to every host from a terminal and print one line each.\nasync function checkMain(): Promise<void> {\n const { path } = resolveConfigPath(process.argv.slice(2), process.env, homedir())\n const fleet = parseOrExit(readCheckConfig(path).fold(fatal, (raw) => raw))\n const sink = openAuditLog(fleet.audit.path).fold(fatal, (s) => s)\n const missing = await Either.traverseAsync(fleet.cas, async (c) =>\n (await missingKeyFile(c)).map((m) =>\n m.fold<string[]>(\n () => [],\n () => [c.name],\n ),\n ),\n )\n const missingKeyCas = missing.fold(fatal, (names) => names.flat())\n // Probe only what can start: a CA with no key_file, and its hosts, are reported without a connection.\n const probeFleet: Fleet = {\n ...fleet,\n cas: fleet.cas.filter((c) => !missingKeyCas.includes(c.name)),\n hosts: fleet.hosts.filter((h) => !h.ca.exists((name) => missingKeyCas.includes(name))),\n }\n const { wiring: ca } = await startCa(probeFleet, \"check\")\n const runner = await buildRunnerOrExit(probeFleet, ca)\n const code = await runCheck({\n fleet,\n configPath: path,\n runner,\n auditor: createAuditor({ sink, now: () => new Date(), newId: randomUUID }),\n missingKeyCas,\n caPublicKeys: caPublicKeysOf(probeFleet, ca),\n out: (line) => console.log(line),\n })\n await runner.shutdown()\n process.exit(code)\n}\n\nasync function main(): Promise<void> {\n const config = ensureConfig(resolveConfigPath(process.argv.slice(2), process.env, homedir())).fold(fatal, (c) => c)\n if (config.created) console.error(`created fleet config at ${config.path}`)\n const configPath = config.path\n const raw = Try(() => readFileSync(configPath, \"utf8\")).fold(\n (e) => {\n console.error(`Cannot read fleet config at ${configPath}: ${(e as Error).message}`)\n return process.exit(1)\n },\n (content) => content,\n )\n\n const fleet = parseOrExit(raw)\n\n // Fail fast: no call may run unaudited, so an unwritable audit log stops startup.\n const sink = openAuditLog(fleet.audit.path).fold(\n (msg) => {\n console.error(msg)\n process.exit(1)\n },\n (s) => s,\n )\n\n const { wiring: ca, created: caCreated } = await startCa(fleet, \"serve\")\n const runner = await buildRunnerOrExit(fleet, ca)\n fleet.hosts.forEach((host) => runner.lastError(host.name).forEach((e) => console.error(`warning: ${e.message}`)))\n\n const setup: SetupContext = {\n configPath,\n configCreated: config.created,\n fleet,\n loadedConfig: raw,\n readConfig: () =>\n Try(() => readFileSync(configPath, \"utf8\")).fold(\n (e) => Left<string, string>(`Cannot read ${configPath}: ${e.message}`),\n (text) => Right<string, string>(text),\n ),\n caPublicKeys: caPublicKeysOf(fleet, ca),\n caCreated,\n runtimeDir: ca.map((wiring) => wiring.custody.runtimeDir),\n knownHosts: ca.map((wiring) => wiring.knownHosts),\n }\n const label = clientLabel(process.env[CLIENT_LABEL_ENV])\n // The client is known per call, so each call gets handlers whose auditor records it.\n const handlers = (context: Readonly<{ client: Parameters<typeof reportedClient>[0] }>) =>\n makeHandlers(\n runner,\n createAuditor({\n sink,\n now: () => new Date(),\n newId: randomUUID,\n client: label.or(reportedClient(context.client)).orUndefined(),\n }),\n setup,\n )\n const server = createServer({\n name: \"ssh-fleet-mcp-server\",\n version: pkgVersion,\n build: buildInfo(),\n instructions:\n \"Manage a fleet of SSH hosts: pooled connections, host-routed exec and tmux tools. \" +\n \"Call fleet_setup first if hosts_list is empty or a host fails to connect; it says what to configure next.\",\n })\n\n const hostParam = z.string().describe(\"Configured host name (see hosts_list)\")\n\n server.addTool({\n name: \"hosts_list\",\n description:\n \"List configured fleet hosts and their live connection status. If a host you expect is missing, call fleet_setup: the config may have changed since the server started.\",\n parameters: z.object({}),\n execute: (_args, context) => handlers(context).hostsList(),\n })\n server.addTool({\n name: \"fleet_setup\",\n description:\n \"Show setup state: config path, each host's connection state and last error, each CA's public key and the line to add on hosts, and what to do next. Read-only.\",\n parameters: z.object({}),\n execute: (_args, context) => handlers(context).fleetSetup(),\n })\n server.addTool({\n name: \"exec\",\n description: \"Run a one-shot shell command on a fleet host over SSH.\",\n parameters: z.object({\n host: hostParam,\n command: z.string().min(1).describe(\"Shell command\"),\n timeout_ms: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n `How long to wait, in ms. Default ${fleet.pool.execTimeoutMs}; at most ${fleet.pool.maxExecTimeoutMs}, and a larger value is lowered to it. On timeout the call fails with the output so far, and the command may still be running. For longer jobs, run them in tmux (tmux_send, then tmux_read).`,\n ),\n }),\n execute: ({ host, command, timeout_ms }, context) => handlers(context).exec({ host, command, timeout_ms }),\n })\n server.addTool({\n name: \"tmux_list\",\n description: \"List tmux sessions on a fleet host. Returns a JSON array of session names.\",\n parameters: z.object({ host: hostParam }),\n execute: ({ host }, context) => handlers(context).tmuxList({ host }),\n })\n server.addTool({\n name: \"tmux_send\",\n description: \"Type text into a persistent tmux session on a fleet host (creates it if absent).\",\n parameters: z.object({\n host: hostParam,\n session: z.string().optional().describe(\"tmux session (defaults to the host's configured session)\"),\n input: z.string().min(1).describe(\"Text to type\"),\n submit: z.boolean().optional().describe(\"Press Enter after the text (default true)\"),\n }),\n execute: ({ host, session, input, submit }, context) =>\n handlers(context).tmuxSend({ host, session, input, submit }),\n })\n server.addTool({\n name: \"tmux_read\",\n description: \"Capture the recent pane transcript of a tmux session on a fleet host.\",\n parameters: z.object({\n host: hostParam,\n session: z.string().optional().describe(\"tmux session (defaults to the host's configured session)\"),\n lines: z.number().int().min(1).optional().describe(\"Lines of scrollback (default 200; capped at 2000)\"),\n }),\n execute: ({ host, session, lines }, context) => handlers(context).tmuxRead({ host, session, lines }),\n })\n server.addTool({\n name: \"tmux_keys\",\n description: \"Send control/special keys (e.g. C-c) to a tmux session on a fleet host.\",\n parameters: z.object({\n host: hostParam,\n session: z.string().optional().describe(\"tmux session (defaults to the host's configured session)\"),\n keys: z.array(z.string()).min(1).describe(\"tmux key names, e.g. ['C-c']\"),\n }),\n execute: ({ host, session, keys }, context) => handlers(context).tmuxKeys({ host, session, keys }),\n })\n\n await server.start({ transportType: \"stdio\" })\n console.error(\n `SSH Fleet MCP Server running on stdio (${runner.hostNames.length} hosts); audit log: ${fleet.audit.path}`,\n )\n\n const shutdown = async () => {\n await runner.shutdown()\n await server.stop()\n process.exit(0)\n }\n process.on(\"SIGINT\", () => void shutdown())\n process.on(\"SIGTERM\", () => void shutdown())\n}\n\nconst entry = process.argv[2] === \"check\" ? checkMain : main\nentry().catch((error) => {\n console.error(\"Fatal error in main():\", error)\n process.exit(1)\n})\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["nodeSpawn","sleep","constants","SSHClient","parseToml","Set","constants$1","Set$1","resolveAuth","options","Right","expandResult","Path","Left","expanded","Fs","err","contents","varName","keyValue","sock","PASS_THROUGH","childEnv","v","k","extra","nodeProcessRunner","child","spawn","cmd","args","env","timeoutMs","out","state","finish","timer","resolve","result","pid","Try","d","reject","code","input","listeners","cb","NO_PROMPT","PROBES","checkOpenSsh","present","proc","missing","i","sleep$1","ms","waitFor","check","deadline","alive","isStale","f","LOCK","isOwn","readPid","readFileSync","path","isLive","killIfOurs","ps","argv0","basename","program","recover","join","dir","masterPids","readdirSync","unlinkSync","prepare","mkdirSync","e","foreign","chmodSync","sinceBoot","statSync","uptime","lock","holder","writeFileSync","release","rmdirSync","VALID_CA_NAME","listKeys","r","agentSock","line","loadCa","fail","ca","step","detail","key","agentEnv","before","add","added","pubPath","stopAgent","agent","startCaCustody","ready","runtimeDir","existsSync","loaded","Either","cas","pubs","Map","name","socketPathFor","digest","createHash","host","serials","nextSerial","candidate","nowMs","certOptions","sourceAddress","cidr","mint","req","caPub","custody","keyPath","randomBytes","certPath","files","dispose","gen","at","now","serial","sign","exitLabel","TIMED_OUT_RE","isTimedOutMessage","message","SESSION_RE","validateSession","INPUT_ERROR_PREFIXES","isTmuxInputError","prefix","shellQuote","s","clampLines","n","trimTrailingWhitespace","ALLOWED_KEYS","validateKey","withTmuxPath","command","buildList","buildSend","session","ensure","send","enter","submit","buildRead","lines","buildKeys","keys","bad","TMUX_MISSING_MSG","SESSION_MISSING_RE","NO_SERVER_RE","isTmuxMissing","failure","label","interpretList","l","interpretAck","interpretRead","interpretKeys","tmuxList","runner","tmuxSend","p","tmuxRead","tmuxKeys","signalCode","Option","signal","channelResult","timedOutResult","execOutput","sleep","target","c","masterArgs","socketPath","m","knownHosts","execArgs","once","run","fn","opensshConnect","deps","readyTimeoutMs","drainTimeoutMs","setTimer","clearTimer","t","exitOf","drain","live","control","op","waitReady","minted","pidFile","closeCbs","fireClose","retireTimer","close","entry","opts","entries","defaultConnect","client","Client","rej","errb","res","stream","sshConfig","createPool","states","hosts","h","allStates","st","dropConn","ensureConn","connect","acquireSlot","wake","idx","releaseSlot","next","hostName","UNKNOWN_HOST_HINT","isUnknownHost","resultError","UserError","timeoutError","deniedError","routedError","AGENT_UNKNOWN","DEFAULT_AUDIT_PATH","reportedClient","reported","clientLabel","raw","excerpt","unfinishedFields","messageOf","qualify","rule","verdictFields","call","createAuditor","write","sink","id","newId","who","started","outcome","finished","summarize","APPEND_NOFOLLOW","constants","withAppendFd","fd","openSync","file","use","closeSync","appendLine","text","expected","written","writeSync","ensurePrivate","fstatSync","fchmodSync","openAuditLog","dirname","buildInfo","\"commit\"","\"branch\"","\"date\"","MODES","GATES","KEYS","EMPTY_TABLE","isTable","oneOf","values","error","strings","x","errorOf","msg","parsePolicyTable","where","mode","tmux","allow","needsApproval","agents","errors","nonEmpty","o","xs","compile","results","patterns","wrap","re","errorsOf","errs","resolvePolicy","defaults","allowSrc","needsSrc","ALLOW","allowBy","denyBy","READONLY_REJECT","TOO_LONG","listed","evaluateTmuxInput","policy","evaluateExec","tooLong","tripped","evaluate","DEFAULT_EXEC_TIMEOUT_MS","DEFAULT_MAX_EXEC_TIMEOUT_MS","DEFAULT_POOL","str","num","resolveHostAuth","modes","auth","MAX_TIMER_MS","poolValue","fallback","parsePool","fields","maxPerHost","idleTimeoutMs","acquireTimeoutMs","execTimeoutMs","maxExecTimeoutMs","validPrincipal","ipv4Value","addr","acc","octet","ipv6Value","head","tail","headGroups","tailGroups","fill","groups","g","hostBitsClear","value","width","validCidr","parts","bits","family","isIP","parseCa","refs","envName","principal","parseAudit","a","tomlError","summary","parseFleet","parsed","parse","toml","defaultPort","defaultUser","defaultSession","rawHosts","defaultsPolicy","defaultsTable","names","rawCas","caNames","parseHost","index","address","table","kind","audit","casResults","caErrors","pool","List","fleetWarnings","fleet","hostRows","row","stateDir","homedir","defaultRuntimeDir","xdg","defaultKnownHosts","fromFile","info","lstat","readFile","readCaKey","ref","caKeySource","selfTestCas","sshConfigFor","toSshConfig","buildFleetRunner","caHosts","sshHosts","resolved","cfg","authErrors","makeRunner","realOpenssh","socket","wiring","caEntry","sessions","logOutput","policies","caHostByName","caByName","sshPool","caPools","caPoolFor","existing","openssh","routeConnected","authErrorByName","route","startedAt","lastErrors","effectiveTimeout","requested","runOn","caStatus","pools","unknownHost","resolveConfigPath","flag","argv","home","isCode","ensureConfig","config","missingKeyFile","ensureCaKeyFiles","made","enrollLine","publicKey","hostAddress","u","KEY_SOURCE","hostReport","ctx","enroll","pub","caReport","configDrift","onDisk","setupReport","drift","nextSteps","readCheckConfig","checkRuntimeDir","outcomeOf","runCheck","addrWidth","probe","outcomes","unwrap","bytes","noSummary","makeHandlers","gated","summarizeExec","auditor","setup","timeout_ms","output","press","count","transcript","pkgVersion","createRequire","fatal","startCa","created","configured","selfTest","buildRunnerOrExit","caPublicKeysOf","parseOrExit","w","checkMain","missingKeyCas","probeFleet","randomUUID","main","configPath","content","caCreated","CLIENT_LABEL_ENV","handlers","context","server","createServer","hostParam","z","shutdown"],"sources":["../../core/src/auth.ts","../../core/src/proc.ts","../../core/src/ca.ts","../../core/src/tmux.ts","../../core/src/ssh.ts","../../core/src/openssh.ts","../../core/src/pool.ts","../src/tool-errors.ts","../src/audit.ts","../src/build-info.ts","../src/policy.ts","../src/fleet.ts","../src/runners.ts","../src/template.ts","../src/setup.ts","../src/check.ts","../src/handlers.ts","../src/index.ts"],"sourcesContent":["import type { Option } from \"functype\"\nimport { type Either, Left, Right } from \"functype\"\nimport { Fs, Path } from \"functype-os\"\nimport { type ConnectConfig } from \"ssh2\"\n\nexport type ResolveAuthOptions = Readonly<{\n password: Option<string>\n keyPath: Option<string>\n keyEnvVar: Option<string>\n useAgent: boolean\n}>\n\n// Auth precedence: password → key file → key from env var → ssh-agent → empty.\nexport const resolveAuth = async (options: ResolveAuthOptions): Promise<Either<string, Partial<ConnectConfig>>> => {\n if (options.password.isSome()) {\n return Right<string, Partial<ConnectConfig>>({ password: options.password.value })\n }\n if (options.keyPath.isSome()) {\n const expandResult = Path.expand(options.keyPath.value)\n if (expandResult.isLeft()) {\n return Left<string, Partial<ConnectConfig>>(\n `Invalid SSH key path ${options.keyPath.value}: ${expandResult.value.message}`,\n )\n }\n const expanded = expandResult.value\n const result = await Fs.readFile(expanded)\n return result.fold<Either<string, Partial<ConnectConfig>>>(\n (err) => Left<string, Partial<ConnectConfig>>(`Failed to read SSH key ${expanded}: ${err.message}`),\n (contents) => Right<string, Partial<ConnectConfig>>({ privateKey: contents }),\n )\n }\n if (options.keyEnvVar.isSome()) {\n const varName = options.keyEnvVar.value\n const keyValue = process.env[varName]\n if (!keyValue) {\n return Left<string, Partial<ConnectConfig>>(`--key-env=${varName} but environment variable is not set or empty`)\n }\n return Right<string, Partial<ConnectConfig>>({ privateKey: keyValue })\n }\n if (options.useAgent) {\n const sock = process.env.SSH_AUTH_SOCK\n if (!sock) {\n return Left<string, Partial<ConnectConfig>>(\n \"--agent set but SSH_AUTH_SOCK is not set; start ssh-agent or unlock your password manager's SSH agent\",\n )\n }\n return Right<string, Partial<ConnectConfig>>({ agent: sock })\n }\n return Right<string, Partial<ConnectConfig>>({})\n}\n","import { spawn as nodeSpawn } from \"node:child_process\"\n\nimport { Try } from \"functype\"\n\n/** `timedOut` is set when `run`'s timeout killed the child; stdout and stderr then hold what it wrote first. */\nexport type ProcResult = Readonly<{ code: number; stdout: string; stderr: string; timedOut?: true }>\n\n// The whole environment a child gets; never merged with process.env.\nexport type Env = Readonly<Record<string, string>>\n\n/**\n * A long-lived child, such as ssh-agent or an ssh ControlMaster.\n * @interop mirrors Node's child_process: the exit code is null when a signal ends the process, and pid is undefined when spawning failed.\n */\nexport type Child = Readonly<{\n onExit: (cb: (code: number | null) => void) => void\n kill: () => void\n stderr: () => string\n exited: () => boolean\n pid: () => number | undefined\n}>\n\nexport type ProcessRunner = Readonly<{\n run: (\n cmd: string,\n args: ReadonlyArray<string>,\n opts: Readonly<{ env: Env; input?: string; timeoutMs?: number }>,\n ) => Promise<ProcResult>\n spawn: (cmd: string, args: ReadonlyArray<string>, opts: Readonly<{ env: Env }>) => Child\n}>\n\n// Only these pass through from the hub's own environment. In particular no SSH_AUTH_SOCK (the user's\n// agent) and no DISPLAY, so an OpenSSH child can't reach an agent or a GUI prompt it wasn't given.\nconst PASS_THROUGH: ReadonlyArray<string> = [\"PATH\", \"HOME\", \"USER\", \"LOGNAME\", \"LANG\"]\n\nexport const childEnv = (extra: Env = {}): Env => ({\n ...Object.fromEntries(\n PASS_THROUGH.flatMap((k) => {\n const v = process.env[k]\n return v === undefined ? [] : [[k, v]]\n }),\n ),\n ...extra,\n})\n\nconst STDERR_TAIL = 4096\n\n/** @interop Node's child_process is callback- and event-based; these adapters are the boundary. */\nexport const nodeProcessRunner: ProcessRunner = {\n // Resolves with the exit code, non-zero included; rejects only when the program can't start (ENOENT).\n // With `timeoutMs`, the child runs in its own process group with no controlling terminal (so a\n // prompt fails instead of waiting), and the whole group is killed when time runs out.\n run: (cmd, args, { env, input, timeoutMs }) =>\n new Promise<ProcResult>((resolve, reject) => {\n const child = nodeSpawn(cmd, [...args], {\n env: { ...env },\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n detached: timeoutMs !== undefined,\n })\n const out: Buffer[] = []\n const err: Buffer[] = []\n const state = { done: false }\n const finish = (result: ProcResult): void => {\n if (state.done) return\n state.done = true\n clearTimeout(timer)\n resolve(result)\n }\n const timer = setTimeout(\n () => {\n const { pid } = child\n if (pid !== undefined) Try(() => process.kill(-pid, \"SIGKILL\"))\n finish({\n code: -1,\n stdout: Buffer.concat(out).toString(),\n stderr: Buffer.concat(err).toString(),\n timedOut: true,\n })\n },\n timeoutMs ?? 2 ** 31 - 1,\n )\n child.stdout.on(\"data\", (d: Buffer) => out.push(d))\n child.stderr.on(\"data\", (d: Buffer) => err.push(d))\n child.on(\"error\", (e) => {\n clearTimeout(timer)\n reject(e)\n })\n child.on(\"close\", (code) =>\n finish({ code: code ?? -1, stdout: Buffer.concat(out).toString(), stderr: Buffer.concat(err).toString() }),\n )\n child.stdin.on(\"error\", () => undefined)\n child.stdin.end(input ?? \"\")\n }),\n\n spawn: (cmd, args, { env }) => {\n const child = nodeSpawn(cmd, [...args], { env: { ...env }, stdio: [\"ignore\", \"ignore\", \"pipe\"] })\n const state = { tail: \"\", exited: false, code: null as number | null }\n const listeners: Array<(code: number | null) => void> = []\n const finish = (code: number | null): void => {\n if (state.exited) return\n state.exited = true\n state.code = code\n listeners.splice(0).forEach((cb) => cb(code))\n }\n child.stderr.on(\"data\", (d: Buffer) => {\n state.tail = (state.tail + d.toString()).slice(-STDERR_TAIL)\n })\n child.on(\"exit\", (code) => finish(code))\n child.on(\"error\", (e) => {\n state.tail = (state.tail + e.message).slice(-STDERR_TAIL)\n finish(null)\n })\n return {\n onExit: (cb) => {\n if (state.exited) queueMicrotask(() => cb(state.code))\n else listeners.push(cb)\n },\n kill: () => {\n if (!state.exited) child.kill()\n },\n stderr: () => state.tail,\n exited: () => state.exited,\n pid: () => child.pid,\n }\n },\n}\n","import { createHash, randomBytes } from \"node:crypto\"\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmdirSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\"\nimport { uptime } from \"node:os\"\nimport { basename, join } from \"node:path\"\n\nimport type { Option } from \"functype\"\nimport { Either, Left, Map, Right, Try } from \"functype\"\n\nimport { type Child, childEnv, type Env, type ProcessRunner } from \"./proc.js\"\n\n// Where a CA's private key comes from at startup; the orchestrator builds these from config.\nexport type CaKeySource = Readonly<{ name: string; key: () => Promise<Either<string, string>> }>\n\nexport type Custody = Readonly<{\n runtimeDir: string\n agentSock: string\n pubPath: (ca: string) => Option<string>\n shutdown: () => Promise<void>\n}>\n\n// A passphrase-protected key must fail at once, with or without a terminal, never wait for input.\nconst NO_PROMPT: Env = { SSH_ASKPASS: \"/bin/false\", SSH_ASKPASS_REQUIRE: \"force\" }\n\n// `-?` makes each tool print usage and exit; a bare `ssh-agent` would start an agent.\nconst PROBES: ReadonlyArray<readonly [string, ReadonlyArray<string>]> = [\n [\"ssh\", [\"-V\"]],\n [\"ssh-keygen\", [\"-?\"]],\n [\"ssh-agent\", [\"-?\"]],\n [\"ssh-add\", [\"-?\"]],\n]\n\nexport const checkOpenSsh = async (proc: ProcessRunner): Promise<Either<string, void>> => {\n const present = await Promise.all(\n PROBES.map(([cmd, args]) =>\n proc.run(cmd, args, { env: childEnv() }).then(\n () => true,\n () => false,\n ),\n ),\n )\n const missing = PROBES.filter((_, i) => !present[i]).map(([cmd]) => cmd)\n return missing.length === 0\n ? Right<string, void>(undefined)\n : Left<string, void>(`OpenSSH binaries missing: ${missing.join(\", \")}`)\n}\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\nconst waitFor = (check: () => boolean, deadline: number): Promise<boolean> =>\n check()\n ? Promise.resolve(true)\n : Date.now() > deadline\n ? Promise.resolve(false)\n : sleep(50).then(() => waitFor(check, deadline))\n\nconst alive = (pid: number): boolean =>\n Try(() => process.kill(pid, 0)).fold(\n () => false,\n () => true,\n )\n\nconst isStale = (f: string): boolean =>\n f === \"agent.sock\" ||\n f === \"agent.pid\" ||\n /^m-.*\\.(sock|pid)$/.test(f) ||\n f.startsWith(\"k-\") ||\n /^ca-.*\\.pub$/.test(f)\n\n// Holds the PID of the hub using the runtime dir, so a second hub can't mistake it for a crash.\nconst LOCK = \"hub.lock\"\n\n// Every file a hub creates in its runtime dir; anything else there belongs to someone else.\nconst isOwn = (f: string): boolean => f === LOCK || isStale(f)\n\nconst readPid = (path: string): number => Try(() => Number(readFileSync(path, \"utf8\").trim())).orElse(Number.NaN)\n\nconst isLive = (pid: number): boolean => Number.isInteger(pid) && pid > 0 && alive(pid)\n\n// Kills `pid` only if `ps` shows `program` running on `sock`, a path inside this hub's runtime dir.\n// A reused PID, or the operator's own ssh or ssh-agent, can't match. `args` rather than `comm`:\n// macOS prints `comm` as a full path, and OpenSSH may retitle a master `ssh: <sock> [mux]`.\nconst killIfOurs = async (pid: number, program: string, sock: string, proc: ProcessRunner): Promise<void> => {\n if (!isLive(pid)) return\n const ps = await proc\n .run(\"ps\", [\"-ww\", \"-p\", String(pid), \"-o\", \"args=\"], { env: childEnv() })\n .catch(() => ({ code: 1, stdout: \"\", stderr: \"\" }))\n const args = ps.stdout.trim()\n const argv0 = basename(args.split(\" \")[0] ?? \"\").replace(/:$/, \"\")\n if (ps.code !== 0 || argv0 !== program || !args.includes(sock)) return\n Try(() => process.kill(pid, \"SIGTERM\"))\n await waitFor(() => !alive(pid), Date.now() + 2000)\n}\n\n// A previous run that crashed leaves its agent running (holding the CA key), its masters running\n// (holding authenticated sessions to fleet hosts), and its sockets behind; ssh-agent refuses an\n// existing socket path. Kill what is still ours, then clear the files.\nconst recover = async (dir: string, proc: ProcessRunner): Promise<void> => {\n await killIfOurs(readPid(join(dir, \"agent.pid\")), \"ssh-agent\", join(dir, \"agent.sock\"), proc)\n const masterPids = readdirSync(dir).filter((f) => /^m-.*\\.pid$/.test(f))\n await Promise.all(\n masterPids.map((f) => killIfOurs(readPid(join(dir, f)), \"ssh\", join(dir, f.replace(/\\.pid$/, \".sock\")), proc)),\n )\n readdirSync(dir)\n .filter(isStale)\n .forEach((f) => Try(() => unlinkSync(join(dir, f))))\n}\n\n// Creates the runtime dir, or takes over an existing one only if it holds nothing but hub files:\n// the hub chmods it and deletes its own files from it, so it must not be anyone else's directory.\nconst prepare = (dir: string): Either<string, void> =>\n Try(() => {\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n return readdirSync(dir).filter((f) => !isOwn(f))\n }).fold(\n (e) => Left<string, void>(`runtime_dir ${dir}: ${e.message}`),\n (foreign) =>\n foreign.length > 0\n ? Left<string, void>(\n `runtime_dir ${dir} holds files the hub did not create (${foreign.slice(0, 3).join(\", \")}); give runtime_dir a directory of its own`,\n )\n : Try(() => chmodSync(dir, 0o700)).fold(\n (e) => Left<string, void>(`runtime_dir ${dir}: ${e.message}`),\n () => Right<string, void>(undefined),\n ),\n )\n\n// A lock written before the last boot is stale whatever its pid: the runtime dir can outlive a reboot\n// (`~/.local/state` on macOS), and by then the old hub's pid may belong to any live process.\nconst sinceBoot = (path: string): boolean =>\n Try(() => statSync(path).mtimeMs >= Date.now() - uptime() * 1000).orElse(false)\n\n// One hub per runtime dir: a second would take the first's agent and masters for a crash's leftovers.\nconst lock = (dir: string): Either<string, void> => {\n const path = join(dir, LOCK)\n const holder = readPid(path)\n if (isLive(holder) && sinceBoot(path))\n return Left<string, void>(\n `runtime_dir ${dir} is in use by pid ${holder}; give each hub its own runtime_dir (delete ${path} if that process is not a hub)`,\n )\n return Try(() => {\n Try(() => unlinkSync(path))\n writeFileSync(path, String(process.pid), { mode: 0o600, flag: \"wx\" })\n }).fold(\n (e) => Left<string, void>(`runtime_dir ${dir}: can't take ${LOCK}: ${e.message}`),\n () => Right<string, void>(undefined),\n )\n}\n\n// Deletes the hub's own files, lock included, and the dir itself once nothing else is left in it.\nconst release = (dir: string): void => {\n Try(() => readdirSync(dir))\n .orElse([])\n .filter(isOwn)\n .forEach((f) => Try(() => unlinkSync(join(dir, f))))\n Try(() => rmdirSync(dir))\n}\n\nconst VALID_CA_NAME = /^[\\w-]+$/\n\n// Public keys the agent holds, one per line; empty when it holds none (ssh-add -L exits 1 then).\nconst listKeys = async (agentSock: string, proc: ProcessRunner): Promise<ReadonlyArray<string>> => {\n const r = await proc.run(\"ssh-add\", [\"-L\"], { env: childEnv({ SSH_AUTH_SOCK: agentSock }) })\n return r.code === 0 ? r.stdout.split(\"\\n\").filter((line) => line.startsWith(\"ssh-\")) : []\n}\n\nconst loadCa = async (\n ca: CaKeySource,\n dir: string,\n agentSock: string,\n proc: ProcessRunner,\n): Promise<Either<string, readonly [string, string]>> => {\n const fail = (step: string, detail: string) =>\n Left<string, readonly [string, string]>(`CA \"${ca.name}\": ${step} failed: ${detail.trim()}`)\n // The name becomes a file name in the runtime dir.\n if (!VALID_CA_NAME.test(ca.name)) return fail(\"naming\", \"use letters, digits, hyphen or underscore\")\n const key = await ca.key()\n if (key.isLeft()) return fail(\"reading the key\", key.value)\n const agentEnv = childEnv({ ...NO_PROMPT, SSH_AUTH_SOCK: agentSock })\n const before = await listKeys(agentSock, proc)\n // The private key reaches OpenSSH on stdin only; it is never written to disk. (`ssh-keygen -y -f\n // /dev/stdin` can't be used: Node gives children a socket, not a pipe, and /dev/stdin won't open.)\n const add = await proc.run(\"ssh-add\", [\"-\"], { env: agentEnv, input: key.value })\n if (add.code !== 0) return fail(\"loading into the agent\", add.stderr)\n // The CA's public key is the one line the agent gained, so it always matches what the agent signs with.\n const added = (await listKeys(agentSock, proc)).filter((line) => !before.includes(line))\n if (added.length !== 1) return fail(\"loading into the agent\", \"the key is already loaded for another CA\")\n const pubPath = join(dir, `ca-${ca.name}.pub`)\n writeFileSync(pubPath, `${added[0]}\\n`, { mode: 0o600 })\n return Right<string, readonly [string, string]>([ca.name, pubPath])\n}\n\nconst stopAgent = async (agent: Child): Promise<void> => {\n agent.kill()\n await Promise.race([new Promise<void>((resolve) => agent.onExit(() => resolve())), sleep(2000)])\n}\n\n// Starts a private ssh-agent in `runtimeDir` and loads every CA key into it. On any failure the agent\n// is stopped and its files removed, so a failed start never leaves a key behind.\nexport const startCaCustody = async (\n runtimeDir: string,\n cas: ReadonlyArray<CaKeySource>,\n proc: ProcessRunner,\n): Promise<Either<string, Custody>> => {\n const ready = prepare(runtimeDir).flatMap(() => lock(runtimeDir))\n if (ready.isLeft()) return Left<string, Custody>(ready.value)\n await recover(runtimeDir, proc)\n\n const agentSock = join(runtimeDir, \"agent.sock\")\n const agent = proc.spawn(\"ssh-agent\", [\"-D\", \"-a\", agentSock], { env: childEnv() })\n const up = await waitFor(() => existsSync(agentSock) || agent.exited(), Date.now() + 5000)\n if (!up || agent.exited()) {\n await stopAgent(agent)\n release(runtimeDir)\n return Left<string, Custody>(`ssh-agent failed to start: ${agent.stderr().trim() || \"timed out\"}`)\n }\n const pid = agent.pid()\n if (pid !== undefined) writeFileSync(join(runtimeDir, \"agent.pid\"), String(pid), { mode: 0o600 })\n\n const loaded = await Either.traverseAsync(cas, (ca) => loadCa(ca, runtimeDir, agentSock, proc))\n if (loaded.isLeft()) {\n await stopAgent(agent)\n release(runtimeDir)\n return Left<string, Custody>(loaded.value)\n }\n const pubs = Map.of(...loaded.value.map(([name, path]): [string, string] => [name, path]))\n\n return Right<string, Custody>({\n runtimeDir,\n agentSock,\n pubPath: (ca) => pubs.get(ca),\n shutdown: async () => {\n await stopAgent(agent)\n release(runtimeDir)\n },\n })\n}\n\nexport type MintRequest = Readonly<{ ca: string; agent: string; principal: string; sourceAddress: Option<string> }>\n\n// An ephemeral key and the certificate the CA signed for it. `dispose` deletes both and is safe to repeat.\nexport type Minted = Readonly<{\n keyPath: string\n certPath: string\n validUntil: Date\n serial: string\n dispose: () => Promise<void>\n}>\n\nconst VALIDITY_MS = 10 * 60 * 1000\n\n// OpenSSH binds a control socket at `<path>.<16 random chars>` before renaming it, so the usable\n// length is the platform limit minus 17: 90 bytes on Linux (108), 86 on macOS (104).\nconst MAX_SOCKET_PATH = 86\n\n// Each master adds `-<4 hex>` before `.sock` (see openssh.ts), so two masters never share a path.\nconst MASTER_SUFFIX = 5\n\nexport const socketPathFor = (runtimeDir: string, agent: string, host: string): Either<string, string> => {\n const digest = createHash(\"sha256\").update(`${agent}\\0${host}`).digest(\"hex\").slice(0, 12)\n const path = join(runtimeDir, `m-${digest}.sock`)\n return Buffer.byteLength(path) + MASTER_SUFFIX <= MAX_SOCKET_PATH\n ? Right<string, string>(path)\n : Left<string, string>(\n `runtime_dir ${runtimeDir} is too long for control sockets (socket paths must be at most ${MAX_SOCKET_PATH} bytes)`,\n )\n}\n\n// Strictly increasing without stored state: microseconds since the epoch, bumped past the last\n// serial when calls land in the same millisecond. Keeps a key revocation list possible later.\nconst serials = { last: 0n }\n\nexport const nextSerial = (nowMs: number): bigint => {\n const candidate = BigInt(nowMs) * 1000n\n serials.last = candidate > serials.last ? candidate : serials.last + 1n\n return serials.last\n}\n\nconst certOptions = (sourceAddress: Option<string>): ReadonlyArray<string> => [\n \"-O\",\n \"no-port-forwarding\",\n \"-O\",\n \"no-agent-forwarding\",\n \"-O\",\n \"no-x11-forwarding\",\n \"-O\",\n \"no-user-rc\",\n ...sourceAddress.fold<string[]>(\n () => [],\n (cidr) => [\"-O\", `source-address=${cidr}`],\n ),\n]\n\n// Generates an ephemeral ed25519 key in the runtime dir and has the CA, held in the private agent,\n// sign a ten-minute certificate for it naming the agent.\nexport const mint = async (\n custody: Custody,\n req: MintRequest,\n proc: ProcessRunner,\n now: () => Date = () => new Date(),\n): Promise<Either<string, Minted>> => {\n const fail = (step: string, detail: string) =>\n Left<string, Minted>(`CA \"${req.ca}\": ${step} failed: ${detail.trim()}`)\n const caPub = custody.pubPath(req.ca)\n if (caPub.isNone()) return fail(\"signing\", `no CA named \"${req.ca}\" in custody`)\n\n const keyPath = join(custody.runtimeDir, `k-${randomBytes(8).toString(\"hex\")}`)\n const certPath = `${keyPath}-cert.pub`\n const files = [keyPath, `${keyPath}.pub`, certPath]\n const dispose = (): Promise<void> => {\n files.forEach((f) => Try(() => unlinkSync(f)))\n return Promise.resolve()\n }\n\n const gen = await proc.run(\"ssh-keygen\", [\"-q\", \"-t\", \"ed25519\", \"-N\", \"\", \"-C\", \"\", \"-f\", keyPath], {\n env: childEnv(),\n })\n if (gen.code !== 0) {\n await dispose()\n return fail(\"key generation\", gen.stderr)\n }\n\n const at = now()\n const serial = nextSerial(at.getTime())\n const sign = await proc.run(\n \"ssh-keygen\",\n [\n \"-U\",\n \"-s\",\n caPub.orElse(\"\"),\n \"-I\",\n req.agent,\n \"-n\",\n req.principal,\n \"-V\",\n \"-5m:+10m\",\n \"-z\",\n serial.toString(),\n ...certOptions(req.sourceAddress),\n `${keyPath}.pub`,\n ],\n { env: childEnv({ SSH_AUTH_SOCK: custody.agentSock }) },\n )\n if (sign.code !== 0) {\n await dispose()\n return fail(\"signing\", sign.stderr)\n }\n return Right<string, Minted>({\n keyPath,\n certPath,\n validUntil: new Date(at.getTime() + VALIDITY_MS),\n serial: serial.toString(),\n dispose,\n })\n}\n","import { type Either, Left, Right } from \"functype\"\n\n/**\n * `signal` is set when a signal ended the command; `code` is then 128 + the signal number, as a shell\n * reports it. `timedOutMs` is set when exec's timeout stopped waiting; `code` is then 124, as\n * `timeout(1)` reports it, and stdout/stderr hold what arrived first.\n */\nexport type CommandResult = Readonly<{\n stdout: string\n stderr: string\n code: number\n signal?: string\n timedOutMs?: number\n // The channel closed with no exit status (the connection dropped); code is then 255.\n noExitStatus?: true\n}>\n\n/** \"exit 1\", \"exit 137 (signal KILL)\", \"exit 255 (no exit status: channel closed)\", or \"timed out after 5000 ms\". */\nexport const exitLabel = (r: CommandResult): string => {\n if (r.timedOutMs !== undefined) return `timed out after ${r.timedOutMs} ms`\n if (r.noExitStatus === true) return `exit ${r.code} (no exit status: channel closed)`\n return r.signal === undefined ? `exit ${r.code}` : `exit ${r.code} (signal ${r.signal})`\n}\n\n// The whole message a tmux interpreter returns for a timed-out command (\"tmux_send timed out after\n// 5000 ms\"). Anchored, so a failure whose stderr merely mentions a timeout doesn't match.\nconst TIMED_OUT_RE = /^tmux[\\w -]* timed out after \\d+ ms$/\n\nexport const isTimedOutMessage = (message: string): boolean => TIMED_OUT_RE.test(message)\n\nexport type TmuxRunner = (command: string) => Promise<CommandResult>\n\nconst SESSION_RE = /^[A-Za-z0-9_-]+$/\n\nexport const validateSession = (name: string): Either<string, string> =>\n SESSION_RE.test(name)\n ? Right<string, string>(name)\n : Left<string, string>(`Invalid session name \"${name}\": only letters, digits, hyphen, and underscore are allowed`)\n\n// The three argument errors the builders below return. Callers label them as validation errors\n// (the caller sent a bad argument), not execution errors (the host failed).\nconst INPUT_ERROR_PREFIXES = [\"Invalid session name \", \"Unsupported key \", \"No keys provided\"] as const\n\nexport const isTmuxInputError = (message: string): boolean =>\n INPUT_ERROR_PREFIXES.some((prefix) => message.startsWith(prefix))\n\nexport const shellQuote = (s: string): string => `'${s.replaceAll(\"'\", \"'\\\\''\")}'`\n\nexport const clampLines = (n: number): number => Math.max(1, Math.min(2000, Math.floor(n)))\n\nexport const trimTrailingWhitespace = (s: string): string => s.replace(/\\s+$/, \"\")\n\nconst ALLOWED_KEYS: ReadonlySet<string> = new Set([\n \"Enter\",\n \"Escape\",\n \"Tab\",\n \"Space\",\n \"BSpace\",\n \"Up\",\n \"Down\",\n \"Left\",\n \"Right\",\n \"Home\",\n \"End\",\n \"PageUp\",\n \"PageDown\",\n \"C-c\",\n \"C-d\",\n \"C-z\",\n \"C-l\",\n \"C-u\",\n \"C-a\",\n \"C-e\",\n \"C-r\",\n])\n\nexport const validateKey = (key: string): Either<string, string> =>\n ALLOWED_KEYS.has(key)\n ? Right<string, string>(key)\n : Left<string, string>(`Unsupported key \"${key}\": allowed keys are ${[...ALLOWED_KEYS].join(\", \")}`)\n\n// A command sent over SSH runs in a non-login shell, so on macOS PATH is only\n// /usr/bin:/bin:/usr/sbin:/sbin and Homebrew's tmux (/opt/homebrew/bin, or /usr/local/bin on\n// Intel) isn't found. Appending keeps a tmux already on the host's PATH first. PATH is exported,\n// so the assignment reaches every tmux in the command.\nconst TMUX_PATH = 'PATH=\"$PATH:/opt/homebrew/bin:/usr/local/bin\"; '\n\nconst withTmuxPath = (command: string): string => `${TMUX_PATH}${command}`\n\nexport const buildList = (): string => withTmuxPath(\"tmux list-sessions -F '#{session_name}'\")\n\nexport const buildSend = (session: string, input: string, submit: boolean): Either<string, string> =>\n validateSession(session).map((s) => {\n // s is interpolated unquoted into -s/-t because SESSION_RE guarantees no shell metacharacters;\n // if that regex is ever relaxed, quoting would be required here.\n //\n // Ensure the session exists WITHOUT attaching: `new-session -A` would attach when the\n // session already exists, which needs a TTY and fails over an SSH exec channel\n // (\"open terminal failed: not a terminal\"). `has-session || new-session -d` only ever\n // checks-or-creates detached, so it works headlessly on both the first and later sends.\n const ensure = `{ tmux has-session -t ${s} 2>/dev/null || tmux new-session -d -s ${s}; }`\n const send = `tmux send-keys -t ${s} -l -- ${shellQuote(input)}`\n const enter = submit ? ` && tmux send-keys -t ${s} Enter` : \"\"\n return withTmuxPath(`${ensure} && ${send}${enter}`)\n })\n\nexport const buildRead = (session: string, lines: number): Either<string, string> =>\n validateSession(session).map((s) => withTmuxPath(`tmux capture-pane -t ${s} -p -J -S -${clampLines(lines)}`))\n\nexport const buildKeys = (session: string, keys: ReadonlyArray<string>): Either<string, string> =>\n validateSession(session).flatMap((s) => {\n if (keys.length === 0) return Left<string, string>(\"No keys provided\")\n const bad = keys.find((k) => validateKey(k).isLeft())\n return bad === undefined\n ? Right<string, string>(withTmuxPath(`tmux send-keys -t ${s} ${keys.join(\" \")}`))\n : validateKey(bad)\n })\n\nconst TMUX_MISSING_MSG =\n \"tmux not found on the remote host — install tmux (e.g. apt/brew install tmux) or use the exec tool instead\"\n\n// tmux phrases a missing target differently per subcommand: send-keys says\n// \"can't find session\", capture-pane says \"can't find pane\". Match both.\nconst SESSION_MISSING_RE = /can't find session|can't find pane|session not found/i\n\n// No sessions: \"no server running\" when the socket is stale, and \"error connecting to <socket>\n// (No such file or directory)\" when it doesn't exist yet (tmux 3.4 on Linux, 3.7c on macOS).\nconst NO_SERVER_RE = /no server running|error connecting to \\S+ \\(No such file or directory\\)/i\n\nexport const isTmuxMissing = (r: CommandResult): boolean =>\n r.code === 127 || /tmux: ([Cc]ommand )?not found/i.test(r.stderr)\n\nconst failure = (label: string, r: CommandResult): string =>\n r.timedOutMs === undefined ? `${label} failed: ${r.stderr.trim() || exitLabel(r)}` : `${label} ${exitLabel(r)}`\n\n// Each interpreter checks a timed-out result first, so its partial stderr can't read as \"no server\"\n// or \"no session\".\n\nexport const interpretList = (r: CommandResult): Either<string, ReadonlyArray<string>> => {\n if (r.timedOutMs !== undefined) return Left<string, ReadonlyArray<string>>(failure(\"tmux list-sessions\", r))\n if (isTmuxMissing(r)) return Left<string, ReadonlyArray<string>>(TMUX_MISSING_MSG)\n if (r.code === 0) {\n return Right<string, ReadonlyArray<string>>(\n r.stdout\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0),\n )\n }\n if (NO_SERVER_RE.test(r.stderr)) return Right<string, ReadonlyArray<string>>([])\n return Left<string, ReadonlyArray<string>>(failure(\"tmux list-sessions\", r))\n}\n\nexport const interpretAck =\n (label: string) =>\n (r: CommandResult): Either<string, void> => {\n if (r.timedOutMs !== undefined) return Left<string, void>(failure(label, r))\n if (isTmuxMissing(r)) return Left<string, void>(TMUX_MISSING_MSG)\n if (r.code === 0) return Right<string, void>(undefined)\n return Left<string, void>(failure(label, r))\n }\n\nexport const interpretRead =\n (session: string) =>\n (r: CommandResult): Either<string, string> => {\n if (r.timedOutMs !== undefined) return Left<string, string>(failure(\"tmux capture-pane\", r))\n if (isTmuxMissing(r)) return Left<string, string>(TMUX_MISSING_MSG)\n if (r.code === 0) return Right<string, string>(trimTrailingWhitespace(r.stdout))\n if (SESSION_MISSING_RE.test(r.stderr)) {\n return Left<string, string>(`No tmux session \"${session}\" — list with tmux_list, or tmux_send creates one`)\n }\n return Left<string, string>(failure(\"tmux capture-pane\", r))\n }\n\nexport const interpretKeys =\n (session: string) =>\n (r: CommandResult): Either<string, void> => {\n if (r.timedOutMs !== undefined) return Left<string, void>(failure(\"tmux send-keys\", r))\n if (isTmuxMissing(r)) return Left<string, void>(TMUX_MISSING_MSG)\n if (r.code === 0) return Right<string, void>(undefined)\n if (SESSION_MISSING_RE.test(r.stderr)) {\n return Left<string, void>(`No tmux session \"${session}\" — list with tmux_list, or tmux_send creates one`)\n }\n return Left<string, void>(failure(\"tmux send-keys\", r))\n }\n\nexport const tmuxList = async (runner: TmuxRunner): Promise<Either<string, ReadonlyArray<string>>> =>\n interpretList(await runner(buildList()))\n\nexport const tmuxSend = async (\n runner: TmuxRunner,\n p: Readonly<{ session: string; input: string; submit: boolean }>,\n): Promise<Either<string, void>> => {\n const cmd = buildSend(p.session, p.input, p.submit)\n if (cmd.isLeft()) return Left<string, void>(cmd.value)\n return interpretAck(\"tmux_send\")(await runner(cmd.value))\n}\n\nexport const tmuxRead = async (\n runner: TmuxRunner,\n p: Readonly<{ session: string; lines: number }>,\n): Promise<Either<string, string>> => {\n const cmd = buildRead(p.session, p.lines)\n if (cmd.isLeft()) return Left<string, string>(cmd.value)\n return interpretRead(p.session)(await runner(cmd.value))\n}\n\nexport const tmuxKeys = async (\n runner: TmuxRunner,\n p: Readonly<{ session: string; keys: ReadonlyArray<string> }>,\n): Promise<Either<string, void>> => {\n const cmd = buildKeys(p.session, p.keys)\n if (cmd.isLeft()) return Left<string, void>(cmd.value)\n return interpretKeys(p.session)(await runner(cmd.value))\n}\n","import { constants } from \"node:os\"\n\nimport { type Either, Left, Option, Right } from \"functype\"\nimport { UserError } from \"somamcp\"\nimport { Client as SSHClient, type ConnectConfig } from \"ssh2\"\n\nimport { type CommandResult, exitLabel } from \"./tmux.js\"\n\n// A signal number differs by OS only for rarer signals; KILL, TERM, INT and HUP match everywhere.\nconst signalCode = (signal: string): number =>\n Option((constants.signals as Readonly<Record<string, number>>)[`SIG${signal}`]).fold(\n () => 255,\n (n) => 128 + n,\n )\n\n/**\n * The result of an ssh2 channel from its \"close\" arguments.\n * @interop ssh2 passes (code, signal) to \"close\" but @types/ssh2 declares none; code is null when\n * a signal ended the command, and signal is its name. ssh2 gives it as \"SIGKILL\" (observed against\n * OpenSSH on macOS); the SSH wire format says \"KILL\". Both become \"KILL\".\n */\nexport const channelResult = (stdout: string, stderr: string, code: unknown, signal: unknown): CommandResult => {\n if (typeof code === \"number\") return { stdout, stderr, code }\n // Neither: the channel closed before the host sent an exit status, e.g. the connection dropped.\n if (typeof signal !== \"string\" || signal === \"\") return { stdout, stderr, code: 255, noExitStatus: true }\n const name = signal.replace(/^SIG/, \"\")\n return { stdout, stderr, code: signalCode(name), signal: name }\n}\n\n/** Options for one command. Without `timeoutMs`, exec waits as long as the command runs. */\nexport type ExecOptions = Readonly<{ timeoutMs?: number }>\n\nexport const TIMEOUT_CODE = 124\n\nexport const timedOutResult = (stdout: string, stderr: string, timeoutMs: number): CommandResult => ({\n stdout,\n stderr,\n code: TIMEOUT_CODE,\n timedOutMs: timeoutMs,\n})\n\n/**\n * What `exec` returns for a finished command. A non-zero exit is the failure signal, not stderr:\n * many tools warn on stderr and exit 0. Neither stream is dropped. On success, stderr follows\n * stdout under a marker so warnings stay visible. A failure leads with the exit label and stderr,\n * the usual one-line reason, then stdout, which often holds the context.\n */\nexport const execOutput = (r: CommandResult): Either<string, string> => {\n if (r.code === 0) return Right(r.stderr ? `${r.stdout}\\n[stderr]\\n${r.stderr}` : r.stdout)\n const detail = r.stderr && r.stdout ? `${r.stderr}\\n[stdout]\\n${r.stdout}` : r.stderr || r.stdout\n return Left(`${exitLabel(r)}:\\n${detail}`)\n}\n\nexport const execSshResult = (sshConfig: ConnectConfig, command: string): Promise<CommandResult> =>\n new Promise((resolve, reject) => {\n const conn = new SSHClient()\n conn.on(\"ready\", () => {\n conn.exec(command, (err, stream) => {\n if (err) {\n reject(new UserError(`SSH exec error: ${err.message}`))\n conn.end()\n return\n }\n const stdoutChunks: Buffer[] = []\n const stderrChunks: Buffer[] = []\n stream.on(\"close\", (code: unknown, signal: unknown) => {\n conn.end()\n resolve(\n channelResult(Buffer.concat(stdoutChunks).toString(), Buffer.concat(stderrChunks).toString(), code, signal),\n )\n })\n stream.on(\"data\", (data: Buffer) => stdoutChunks.push(data))\n stream.stderr.on(\"data\", (data: Buffer) => stderrChunks.push(data))\n })\n })\n conn.on(\"error\", (err) => reject(new UserError(`SSH connection error: ${err.message}`)))\n conn.connect(sshConfig)\n })\n","import { randomBytes } from \"node:crypto\"\nimport { unlinkSync, writeFileSync } from \"node:fs\"\n\nimport { type Either, Left, Option, Right, Try } from \"functype\"\nimport { type ConnectConfig } from \"ssh2\"\n\nimport { type Minted } from \"./ca.js\"\nimport { type ConnectFactory, type PooledConnection } from \"./pool.js\"\nimport { type Child, childEnv, type ProcessRunner } from \"./proc.js\"\nimport { timedOutResult } from \"./ssh.js\"\n\n// One factory serves one (agent, host). `socketPath` is a base: each master gets its own socket\n// beside it (`<base>-<4 hex>.sock`), so a new master never touches one that is still draining.\nexport type OpensshDeps = Readonly<{\n proc: ProcessRunner\n mintFor: () => Promise<Either<string, Minted>>\n socketPath: string\n knownHosts: string\n readyTimeoutMs?: number\n retireAfterMs?: number\n drainTimeoutMs?: number\n setTimer?: (cb: () => void, ms: number) => unknown\n clearTimer?: (timer: unknown) => void\n}>\n\nexport type Openssh = Readonly<{ connect: ConnectFactory; shutdown: () => Promise<void> }>\n\nconst sleep = (ms: number): Promise<void> => new Promise((resolve) => setTimeout(resolve, ms))\n\nconst target = (c: ConnectConfig): ReadonlyArray<string> => [\n \"-p\",\n String(c.port ?? 22),\n `${c.username ?? \"\"}@${c.host ?? \"\"}`,\n]\n\n// `-F /dev/null` and `IdentityAgent=none` keep the hub user's own config and agent out, so the master\n// can offer only the minted certificate; a dead certificate can never fall back to another key.\nconst masterArgs = (c: ConnectConfig, socketPath: string, knownHosts: string, m: Minted): ReadonlyArray<string> => [\n \"-F\",\n \"/dev/null\",\n \"-M\",\n \"-N\",\n \"-S\",\n socketPath,\n \"-o\",\n \"ControlPersist=no\",\n \"-o\",\n \"IdentitiesOnly=yes\",\n \"-o\",\n \"IdentityAgent=none\",\n \"-o\",\n `IdentityFile=${m.keyPath}`,\n \"-o\",\n `CertificateFile=${m.certPath}`,\n \"-o\",\n \"BatchMode=yes\",\n \"-o\",\n \"StrictHostKeyChecking=accept-new\",\n \"-o\",\n `UserKnownHostsFile=${knownHosts}`,\n \"-o\",\n \"ConnectTimeout=10\",\n // A host that stops answering drops the master within about 45 s, instead of it lingering.\n \"-o\",\n \"ServerAliveInterval=15\",\n \"-o\",\n \"ServerAliveCountMax=3\",\n ...target(c),\n]\n\n// `ProxyCommand=false`: if the control socket is gone, the call fails instead of opening a direct\n// connection with whatever identities ssh would otherwise find.\nconst execArgs = (c: ConnectConfig, socketPath: string, command: string): ReadonlyArray<string> => [\n \"-F\",\n \"/dev/null\",\n \"-S\",\n socketPath,\n \"-o\",\n \"ControlMaster=no\",\n \"-o\",\n \"ProxyCommand=false\",\n \"-o\",\n \"BatchMode=yes\",\n ...target(c),\n \"--\",\n command,\n]\n\n// Runs `fn` on the first call; later calls get the same promise.\nconst once = (fn: () => Promise<void>): (() => Promise<void>) => {\n const state = { run: Option.none<Promise<void>>() }\n return () =>\n state.run.fold(\n () => {\n const run = fn()\n state.run = Option(run)\n return run\n },\n (run) => run,\n )\n}\n\n/** @interop ConnectFactory and PooledConnection are the pool's promise-and-callback contract. */\nexport const opensshConnect = (deps: OpensshDeps): Openssh => {\n const { proc, socketPath, knownHosts } = deps\n const readyTimeoutMs = deps.readyTimeoutMs ?? 15_000\n const drainTimeoutMs = deps.drainTimeoutMs ?? 60_000\n const setTimer = deps.setTimer ?? ((cb: () => void, ms: number): unknown => setTimeout(cb, ms))\n const clearTimer = deps.clearTimer ?? ((t: unknown) => clearTimeout(t as ReturnType<typeof setTimeout>))\n\n // Resolves when the child exits, or after `timeoutMs` if it doesn't.\n const exitOf = (child: Child, timeoutMs: number): Promise<void> =>\n Promise.race([\n new Promise<void>((resolve) => child.onExit(() => resolve())),\n new Promise<void>((resolve) => setTimer(resolve, timeoutMs)),\n ])\n\n // Waits for the master to exit (in-flight sessions finish first), then makes sure it is gone.\n const drain = (child: Child, dispose: () => Promise<void>): Promise<void> =>\n exitOf(child, drainTimeoutMs).then(() => {\n child.kill()\n return dispose()\n })\n const live = new globalThis.Set<Readonly<{ child: Child; close: () => void }>>()\n\n const control = (op: string, sock: string, c: ConnectConfig) =>\n proc.run(\"ssh\", [\"-F\", \"/dev/null\", \"-S\", sock, \"-O\", op, ...target(c)], { env: childEnv() })\n\n const waitReady = (child: Child, sock: string, c: ConnectConfig, deadline: number): Promise<Either<string, void>> => {\n if (child.exited())\n return Promise.resolve(Left<string, void>(`master for ${c.host} exited: ${child.stderr().trim()}`))\n if (Date.now() > deadline)\n return Promise.resolve(Left<string, void>(`master for ${c.host} did not come up: ${child.stderr().trim()}`))\n return control(\"check\", sock, c).then((r) =>\n r.code === 0 && !child.exited()\n ? Right<string, void>(undefined)\n : sleep(100).then(() => waitReady(child, sock, c, deadline)),\n )\n }\n\n const connect: ConnectFactory = async (c) => {\n const minted = await deps.mintFor()\n if (minted.isLeft()) throw new Error(minted.value)\n const m = minted.value\n const dispose = once(m.dispose)\n\n const sock = socketPath.replace(/\\.sock$/, `-${randomBytes(2).toString(\"hex\")}.sock`)\n const pidFile = sock.replace(/\\.sock$/, \".pid\")\n const child = proc.spawn(\"ssh\", masterArgs(c, sock, knownHosts, m), { env: childEnv() })\n // Recorded so that crash recovery can kill a master the hub no longer knows about.\n const pid = child.pid()\n if (pid !== undefined) Try(() => writeFileSync(pidFile, String(pid), { mode: 0o600 }))\n child.onExit(() => Try(() => unlinkSync(pidFile)))\n\n const ready = await waitReady(child, sock, c, Date.now() + readyTimeoutMs)\n if (ready.isLeft()) {\n child.kill()\n await dispose()\n throw new Error(ready.value)\n }\n\n const state = { closing: false, closed: false, retired: false }\n const closeCbs: Array<() => void> = []\n const fireClose = (): void => {\n if (state.closed) return\n state.closed = true\n closeCbs.splice(0).forEach((cb) => cb())\n }\n child.onExit(() => {\n fireClose()\n void dispose()\n })\n\n // sshd checks a certificate only at authentication, so a master would outlive its certificate.\n // At expiry: drop it from the pool, refuse new sessions (`-O stop`; the master exits once its\n // in-flight sessions end), then make sure it is gone. The next call mints a fresh master.\n const retire = (): void => {\n if (state.closing || state.retired) return\n state.retired = true\n fireClose()\n void control(\"stop\", sock, c)\n .catch(() => undefined)\n .then(() => drain(child, dispose))\n }\n const retireTimer = setTimer(retire, deps.retireAfterMs ?? Math.max(0, m.validUntil.getTime() - Date.now()))\n child.onExit(() => clearTimer(retireTimer))\n\n const close = (): void => {\n clearTimer(retireTimer)\n if (state.closing || state.retired) return\n state.closing = true\n // `-O exit` exits 255 once the master is gone; that is success here, not an error.\n void control(\"exit\", sock, c)\n .catch(() => undefined)\n .then(() => drain(child, dispose))\n }\n const entry = { child, close }\n live.add(entry)\n child.onExit(() => live.delete(entry))\n\n const conn: PooledConnection = {\n exec: async (command, opts = {}) => {\n if (child.exited() || state.closing || state.retired) throw new Error(`master for ${c.host} is gone`)\n const r = await proc.run(\"ssh\", execArgs(c, sock, command), { env: childEnv(), timeoutMs: opts.timeoutMs })\n return r.timedOut === true\n ? timedOutResult(r.stdout, r.stderr, opts.timeoutMs ?? 0)\n : { stdout: r.stdout, stderr: r.stderr, code: r.code }\n },\n close,\n onClose: (cb) => {\n closeCbs.push(cb)\n },\n }\n return conn\n }\n\n const shutdown = async (): Promise<void> => {\n const entries = [...live]\n entries.forEach((e) => e.close())\n await Promise.all(entries.map((e) => (e.child.exited() ? Promise.resolve() : exitOf(e.child, drainTimeoutMs))))\n }\n\n return { connect, shutdown }\n}\n","import { Map } from \"functype\"\nimport { Client as SSHClient, type ConnectConfig } from \"ssh2\"\n\nimport { channelResult, type ExecOptions, timedOutResult } from \"./ssh.js\"\nimport { type CommandResult } from \"./tmux.js\"\n\n// A timeout resolves with the partial output, never rejects: a rejection makes the pool reconnect\n// and run the command again.\nexport type PooledConnection = {\n exec: (command: string, opts?: ExecOptions) => Promise<CommandResult>\n close: () => void\n onClose: (cb: () => void) => void\n}\n\nexport type ConnectFactory = (sshConfig: ConnectConfig) => Promise<PooledConnection>\n\nexport type PoolOptions = { maxPerHost: number; idleTimeoutMs: number; acquireTimeoutMs: number }\n\nexport type HostStatus = { name: string; state: \"connected\" | \"idle\" | \"disconnected\"; inFlight: number }\n\nexport type Pool = {\n run: (hostName: string, command: string, opts?: ExecOptions) => Promise<CommandResult>\n status: () => HostStatus[]\n shutdown: () => Promise<void>\n}\n\ntype HostState = {\n name: string\n sshConfig: ConnectConfig\n conn?: PooledConnection\n connecting?: Promise<PooledConnection>\n inFlight: number\n waiters: Array<() => void>\n idleTimer?: ReturnType<typeof setTimeout>\n}\n\n// Default factory: a persistent ssh2 client; exec runs each command on its own channel.\nexport const defaultConnect: ConnectFactory = (sshConfig) =>\n new Promise<PooledConnection>((resolve, reject) => {\n const client = new SSHClient()\n const closeCbs: Array<() => void> = []\n client.on(\"ready\", () =>\n resolve({\n exec: (command, opts = {}) =>\n new Promise<CommandResult>((res, rej) => {\n client.exec(command, (err, stream) => {\n if (err) {\n rej(err)\n return\n }\n const out: Buffer[] = []\n const errb: Buffer[] = []\n const timer =\n opts.timeoutMs === undefined\n ? undefined\n : setTimeout(() => {\n const ms = opts.timeoutMs ?? 0\n res(timedOutResult(Buffer.concat(out).toString(), Buffer.concat(errb).toString(), ms))\n // OpenSSH 7.9+ delivers the signal; an older sshd ignores it, and closing the\n // channel only stops the command if it then writes output.\n stream.signal(\"KILL\")\n stream.close()\n }, opts.timeoutMs)\n stream.on(\"close\", (code: unknown, signal: unknown) => {\n clearTimeout(timer)\n res(channelResult(Buffer.concat(out).toString(), Buffer.concat(errb).toString(), code, signal))\n })\n stream.on(\"data\", (d: Buffer) => out.push(d))\n stream.stderr.on(\"data\", (d: Buffer) => errb.push(d))\n })\n }),\n close: () => client.end(),\n onClose: (cb) => closeCbs.push(cb),\n }),\n )\n client.on(\"error\", (err) => {\n closeCbs.forEach((cb) => cb())\n reject(err)\n })\n client.on(\"close\", () => closeCbs.forEach((cb) => cb()))\n client.connect({ keepaliveInterval: 15000, ...sshConfig })\n })\n\nexport const createPool = (\n hosts: ReadonlyArray<{ name: string; sshConfig: ConnectConfig }>,\n opts: PoolOptions,\n connect: ConnectFactory = defaultConnect,\n): Pool => {\n const states = Map.of<string, HostState>(\n ...hosts.map((h): [string, HostState] => [\n h.name,\n { name: h.name, sshConfig: h.sshConfig, inFlight: 0, waiters: [] },\n ]),\n )\n\n const allStates = (): HostState[] => [...states].map(([, st]) => st)\n\n const dropConn = (st: HostState): void => {\n st.conn = undefined\n if (st.idleTimer) {\n clearTimeout(st.idleTimer)\n st.idleTimer = undefined\n }\n }\n\n const ensureConn = async (st: HostState): Promise<PooledConnection> => {\n if (st.conn) return st.conn\n if (!st.connecting) {\n st.connecting = connect(st.sshConfig).then((c) => {\n c.onClose(() => {\n if (st.conn === c) dropConn(st)\n })\n st.conn = c\n st.connecting = undefined\n return c\n })\n st.connecting.catch(() => {\n st.connecting = undefined\n })\n }\n return st.connecting\n }\n\n const acquireSlot = (st: HostState): Promise<void> => {\n if (st.inFlight < opts.maxPerHost) {\n st.inFlight++\n return Promise.resolve()\n }\n // Woken by releaseSlot: the slot is transferred to the waiter, so inFlight already counts it.\n return new Promise<void>((resolve, reject) => {\n const wake = (): void => {\n clearTimeout(timer)\n resolve()\n }\n const timer = setTimeout(() => {\n const idx = st.waiters.indexOf(wake)\n if (idx !== -1) st.waiters.splice(idx, 1)\n reject(new Error(`host \"${st.name}\" busy: no free connection slot after ${opts.acquireTimeoutMs}ms`))\n }, opts.acquireTimeoutMs)\n st.waiters.push(wake)\n })\n }\n\n const releaseSlot = (st: HostState): void => {\n const next = st.waiters.shift()\n if (next) {\n next() // transfer the slot to the waiter; inFlight is unchanged (count carries over)\n return\n }\n st.inFlight--\n if (st.inFlight === 0 && st.conn) {\n st.idleTimer = setTimeout(() => {\n st.conn?.close()\n dropConn(st)\n }, opts.idleTimeoutMs)\n }\n }\n\n const run = async (hostName: string, command: string, opts?: ExecOptions): Promise<CommandResult> => {\n const st = states.get(hostName).orThrow(new Error(`unknown host \"${hostName}\"`))\n if (st.idleTimer) {\n clearTimeout(st.idleTimer)\n st.idleTimer = undefined\n }\n await acquireSlot(st)\n try {\n const conn = await ensureConn(st)\n return await conn.exec(command, opts).catch(async () => {\n // connection likely dead — drop, reconnect once, retry\n dropConn(st)\n const fresh = await ensureConn(st)\n return fresh.exec(command, opts)\n })\n } finally {\n releaseSlot(st)\n }\n }\n\n const status = (): HostStatus[] =>\n allStates().map((st) => ({\n name: st.name,\n state: st.conn ? (st.inFlight > 0 ? \"connected\" : \"idle\") : \"disconnected\",\n inFlight: st.inFlight,\n }))\n\n const shutdown = (): Promise<void> => {\n allStates().forEach((st) => {\n if (st.idleTimer) clearTimeout(st.idleTimer)\n st.conn?.close()\n dropConn(st)\n })\n return Promise.resolve()\n }\n\n return { run, status, shutdown }\n}\n","import { isTimedOutMessage, isTmuxInputError } from \"@ssh-mcp/core\"\nimport { UserError } from \"somamcp\"\n\n// Every failure a host-routed tool reports carries its own label and hints. somamcp guesses a plain\n// Error's label from its words, which called an unknown host \"internal\" and told the model the\n// tool's maintainer should investigate.\n\nexport const UNKNOWN_HOST_HINT = \"Call hosts_list for the configured host names.\"\n\nconst isUnknownHost = (message: string): boolean => message.startsWith(\"unknown host\")\n\n/** A tool result that failed (a bad tmux argument, a tmux error, an exit code): the message says why, so no hints. */\nexport const resultError = (message: string): UserError => {\n if (isUnknownHost(message)) return new UserError(message, { category: \"not_found\", suggestions: [UNKNOWN_HOST_HINT] })\n if (isTmuxInputError(message)) return new UserError(message, { category: \"validation\", suggestions: [] })\n if (isTimedOutMessage(message))\n return new UserError(message, {\n category: \"timeout\",\n suggestions: [\"tmux took longer than [pool].exec_timeout_ms. Try again, or raise it.\"],\n })\n return new UserError(message, { category: \"execution\", suggestions: [] })\n}\n\n/** exec stopped waiting: the message carries the output so far. */\nexport const timeoutError = (host: string, failure: string): UserError =>\n new UserError(`[${host}] ${failure}`, {\n category: \"timeout\",\n suggestions: [\n `The command may still be running on ${host}. For a long job, start it in tmux with tmux_send and check it with tmux_read, or pass a larger timeout_ms.`,\n ],\n })\n\n/** A call the host's policy refused. */\nexport const deniedError = (message: string): UserError =>\n new UserError(message, {\n category: \"auth\",\n suggestions: [\"This host's policy doesn't allow it. Use a command or tool the policy allows, or ask the operator.\"],\n })\n\n/**\n * Labels a runner failure: an unknown host, a busy pool, or a connection that failed.\n * @interop a `.catch` handler for a tool's promise; it rethrows as UserError so the label and hints are ours, not guessed.\n */\nexport const routedError =\n (host: string) =>\n (e: unknown): never => {\n if (e instanceof UserError) throw e\n // A TypeError or RangeError is a bug in this server, not a host failure: don't send the model to fleet_setup.\n if (e instanceof TypeError || e instanceof RangeError)\n throw new UserError(e.message, { category: \"internal\", suggestions: [] })\n const message = e instanceof Error ? e.message : String(e)\n if (isUnknownHost(message))\n throw new UserError(message, { category: \"not_found\", suggestions: [UNKNOWN_HOST_HINT] })\n if (message.includes(\" busy: no free connection slot\"))\n throw new UserError(message, {\n category: \"timeout\",\n suggestions: [`Every connection to ${host} is in use. Try again, or raise [pool].max_per_host.`],\n })\n throw new UserError(`[${host}] ${message}`, {\n category: \"execution\",\n suggestions: [`Call fleet_setup to see ${host}'s connection state and what to configure.`],\n })\n }\n","import { closeSync, constants, fchmodSync, fstatSync, mkdirSync, openSync, writeSync } from \"node:fs\"\nimport { dirname } from \"node:path\"\n\nimport { type CommandResult, exitLabel } from \"@ssh-mcp/core\"\nimport { type Either, Left, Option, Right, Try, type Wire } from \"functype\"\nimport { Path } from \"functype-os\"\nimport { UserError } from \"somamcp\"\n\nimport { type Verdict } from \"./policy.js\"\nimport { deniedError } from \"./tool-errors.js\"\n\nexport const AGENT_UNKNOWN = \"unknown\"\nexport const DEFAULT_AUDIT_PATH = \"~/.local/state/ssh-fleet/audit.jsonl\"\nexport const EXCERPT_CHARS = 1024\n\n// Tool-specific fields of an audit line, in the shape written to disk.\nexport type AuditFields = Wire<Readonly<Record<string, string | number | boolean | ReadonlyArray<string> | undefined>>>\n\nexport type AuditCall = Readonly<{\n agent: string\n tool: string\n host: Option<string>\n fields: AuditFields\n verdict: Verdict\n}>\n\n// Appends one line to the audit log; throws on failure.\nexport type AuditSink = (line: string) => void\n\nexport type Auditor = Readonly<{\n audited: <A>(call: AuditCall, run: () => Promise<A>, summarize: (a: A) => AuditFields) => Promise<A>\n}>\n\n// `client` says which client made the call: the operator's label (SSH_FLEET_CLIENT), else the name\n// and version the client sent in its handshake. It is recorded beside `agent`, never in its place:\n// neither source is a verified identity, so policy never sees it.\nexport type AuditDeps = Readonly<{ sink: AuditSink; now: () => Date; newId: () => string; client?: string }>\n\nexport const CLIENT_LABEL_ENV = \"SSH_FLEET_CLIENT\"\n\n/**\n * The client's `name/version` from its handshake, cleaned like a label. A version alone names nothing.\n * @interop takes somamcp's `context.client`, whose fields are string | undefined.\n */\nexport const reportedClient = (\n reported: Readonly<{ name?: string | undefined; version: string | undefined }>,\n): Option<string> =>\n Option(reported.name).flatMap((name) =>\n clientLabel(reported.version === undefined ? name : `${name}/${reported.version}`),\n )\n\n/**\n * A label from the environment: trimmed, control characters removed, at most 64 characters; empty means none.\n * @interop takes a process.env value, which is string | undefined.\n */\nexport const clientLabel = (raw: string | undefined): Option<string> =>\n Option(raw)\n .map((v) =>\n Array.from(v.replace(/\\p{Cc}/gu, \"\").trim())\n .slice(0, 64)\n .join(\"\"),\n )\n .filter((v) => v.length > 0)\n\n// The first EXCERPT_CHARS code points, so a surrogate pair is never split.\nexport const excerpt = (s: string): string => Array.from(s).slice(0, EXCERPT_CHARS).join(\"\")\n\n// A command that returned without finishing: the server stopped waiting, or the connection dropped.\n// A summary spreads these in to override the \"ok\" of a call that resolved.\nexport const unfinishedFields = (r: CommandResult): AuditFields =>\n r.timedOutMs === undefined && r.noExitStatus !== true ? {} : { status: \"error\", error: exitLabel(r) }\n\nconst messageOf = (e: unknown): string => (e instanceof Error ? e.message : String(e))\n\n// Rules are host-qualified in the log (`box-a:readonly`), as the spec's examples show.\nconst qualify = (host: Option<string>, rule: string): string =>\n host.fold(\n () => rule,\n (h) => `${h}:${rule}`,\n )\n\nconst verdictFields = (call: AuditCall): Readonly<Record<string, string>> =>\n call.verdict.decision === \"allow\"\n ? {\n decision: \"allow\",\n ...call.verdict.rule.fold(\n () => ({}),\n (rule) => ({ rule: qualify(call.host, rule) }),\n ),\n }\n : { decision: \"deny\", rule: qualify(call.host, call.verdict.rule), reason: call.verdict.reason }\n\n/** @interop a thrown UserError sets the message, category and suggestions the client sees; any other throw gets a label somamcp guesses from its message. */\nexport const createAuditor = ({ sink, now, newId, client }: AuditDeps): Auditor => {\n const write = (line: Readonly<Record<string, unknown>>, failure: string): void =>\n Try(() => sink(JSON.stringify(line))).fold(\n (e) => {\n throw new UserError(`audit log write failed ${failure}: ${messageOf(e)}`, {\n category: \"internal\",\n suggestions: [\n \"The server refuses every call while it can't write its audit log. Check the [audit].path file.\",\n ],\n })\n },\n () => undefined,\n )\n\n // A decision line is written before the call runs and a result line after it, sharing `id`,\n // so a call that hangs or crashes the server is still on record. A denied call never runs, so\n // it writes only its decision line.\n const audited = async <A>(call: AuditCall, run: () => Promise<A>, summarize: (a: A) => AuditFields): Promise<A> => {\n const id = newId()\n const who = {\n agent: call.agent,\n ...(client === undefined ? {} : { client }),\n tool: call.tool,\n ...call.host.fold(\n () => ({}),\n (host) => ({ host }),\n ),\n }\n const started = now()\n write(\n { ts: started.toISOString(), id, event: \"decision\", ...who, ...call.fields, ...verdictFields(call) },\n \"— call not executed\",\n )\n if (call.verdict.decision === \"deny\") {\n const prefix = call.host.fold(\n () => \"\",\n (host) => `[${host}] `,\n )\n throw deniedError(`${prefix}denied by rule ${call.verdict.rule}: ${call.verdict.reason}`)\n }\n\n const outcome = await Promise.resolve()\n .then(run)\n .then(\n (value) => ({ ok: true as const, value }),\n (error: unknown) => ({ ok: false as const, error }),\n )\n const finished = now()\n const result = { ts: finished.toISOString(), id, event: \"result\", ...who }\n const ms = finished.getTime() - started.getTime()\n\n if (outcome.ok) {\n write({ ...result, status: \"ok\", ms, ...summarize(outcome.value) }, \"after execution\")\n return outcome.value\n }\n write({ ...result, status: \"error\", ms, error: messageOf(outcome.error) }, \"after execution\")\n throw outcome.error\n }\n\n return { audited }\n}\n\n// Every append reopens the file, so a deleted or rotated log is recreated at 0600; O_NOFOLLOW\n// refuses a symlink planted at the path instead of writing through it.\nconst APPEND_NOFOLLOW = constants.O_WRONLY | constants.O_APPEND | constants.O_CREAT | constants.O_NOFOLLOW\n\nconst withAppendFd = (file: string, use: (fd: number) => void): void => {\n const fd = openSync(file, APPEND_NOFOLLOW, 0o600)\n try {\n use(fd)\n } finally {\n closeSync(fd)\n }\n}\n\n/** @interop the AuditSink contract signals failure by throwing; createAuditor turns it into a UserError. */\nconst appendLine = (file: string, line: string): void =>\n withAppendFd(file, (fd) => {\n const text = `${line}\\n`\n const expected = Buffer.byteLength(text)\n const written = writeSync(fd, text)\n if (written !== expected) throw new Error(`short write: ${written} of ${expected} bytes`)\n })\n\n// The mode is fixed through the open fd, so there is no path lookup to race, and only when it isn't\n// 0600 already: a file with the append-only flag (chattr +a, chflags uappend) refuses chmod.\nconst ensurePrivate = (fd: number): void => {\n if ((fstatSync(fd).mode & 0o777) !== 0o600) fchmodSync(fd, 0o600)\n}\n\n// Opens the audit log at `path`, creating it and its directories if needed, and tightens an\n// existing file with wider permissions to 0600. Left when the file can't be opened for append.\nexport const openAuditLog = (path: string): Either<string, AuditSink> =>\n Path.expand(path).fold(\n (e) => Left<string, AuditSink>(`invalid audit path ${path}: ${messageOf(e)}`),\n (file) =>\n Try(() => {\n mkdirSync(dirname(file), { recursive: true, mode: 0o700 })\n withAppendFd(file, ensurePrivate)\n }).fold(\n (e) => Left<string, AuditSink>(`audit log ${file} is not writable: ${messageOf(e)}`),\n () => Right<string, AuditSink>((line) => appendLine(file, line)),\n ),\n )\n","// Which build is running, for somamcp's `info` tool. tsdown.config.ts replaces __FLEET_BUILD__ with\n// the short commit (with -dirty for uncommitted changes), branch and build date. Unbuilt source (tests, tsx)\n// has no stamp, so somamcp falls back to its SOMAMCP_BUILD_* environment variables.\nexport type BuildStamp = Readonly<{ commit?: string; branch?: string; date?: string }>\n\n/** @interop tsdown's `define` replaces this identifier at build time; it is undefined in unbuilt source. */\ndeclare const __FLEET_BUILD__: BuildStamp | undefined\n\nexport const buildInfo = (): BuildStamp => (typeof __FLEET_BUILD__ === \"undefined\" ? {} : __FLEET_BUILD__)\n","import { type Either, Left, Option, Right, Try } from \"functype\"\n\nexport type PolicyMode = \"readonly\" | \"readwrite\" | \"deny\"\nexport type TmuxGate = \"approve\" | \"open\"\n\n// One parsed `[defaults.policy]` or `[hosts.policy]` table; every key optional.\nexport type PolicyTable = Readonly<{\n mode: Option<PolicyMode>\n tmux: Option<TmuxGate>\n allow: Option<ReadonlyArray<string>>\n needsApproval: Option<ReadonlyArray<string>>\n agents: Option<ReadonlyArray<string>>\n}>\n\n// A needs_approval pattern as the operator wrote it, beside its compiled form; denials quote the former.\nexport type Tripwire = Readonly<{ pattern: string; re: RegExp }>\n\n// A host's effective policy. `allow` regexes are wrapped `^(?:…)$`; `needsApproval` match anywhere.\nexport type HostPolicy = Readonly<{\n mode: PolicyMode\n tmux: TmuxGate\n allow: ReadonlyArray<RegExp>\n needsApproval: ReadonlyArray<Tripwire>\n agents: Option<ReadonlyArray<string>>\n}>\n\nconst MODES: ReadonlyArray<string> = [\"readonly\", \"readwrite\", \"deny\"]\nconst GATES: ReadonlyArray<string> = [\"approve\", \"open\"]\nconst KEYS: ReadonlyArray<string> = [\"mode\", \"tmux\", \"allow\", \"needs_approval\", \"agents\"]\n\nexport const EMPTY_TABLE: PolicyTable = {\n mode: Option.none<PolicyMode>(),\n tmux: Option.none<TmuxGate>(),\n allow: Option.none<ReadonlyArray<string>>(),\n needsApproval: Option.none<ReadonlyArray<string>>(),\n agents: Option.none<ReadonlyArray<string>>(),\n}\n\nconst isTable = (v: unknown): v is Readonly<Record<string, unknown>> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v)\n\nconst oneOf = <A extends string>(\n v: unknown,\n values: ReadonlyArray<string>,\n error: string,\n): Either<string, Option<A>> => {\n if (v === undefined) return Right<string, Option<A>>(Option.none<A>())\n return typeof v === \"string\" && values.includes(v) ? Right<string, Option<A>>(Option(v as A)) : Left(error)\n}\n\nconst strings = (v: unknown, error: string): Either<string, Option<ReadonlyArray<string>>> => {\n if (v === undefined) return Right<string, Option<ReadonlyArray<string>>>(Option.none<ReadonlyArray<string>>())\n return Array.isArray(v) && v.every((x) => typeof x === \"string\")\n ? Right<string, Option<ReadonlyArray<string>>>(Option<ReadonlyArray<string>>(v))\n : Left(error)\n}\n\nconst errorOf = (r: Either<string, unknown>): ReadonlyArray<string> =>\n r.fold(\n (msg) => [msg],\n () => [],\n )\n\nexport const parsePolicyTable = (raw: unknown, where: string): Either<ReadonlyArray<string>, PolicyTable> => {\n if (raw === undefined) return Right<ReadonlyArray<string>, PolicyTable>(EMPTY_TABLE)\n if (!isTable(raw)) return Left<ReadonlyArray<string>, PolicyTable>([`${where} must be a table`])\n\n const mode = oneOf<PolicyMode>(raw.mode, MODES, `${where}.mode must be one of readonly, readwrite, deny`)\n const tmux = oneOf<TmuxGate>(raw.tmux, GATES, `${where}.tmux must be approve or open`)\n const allow = strings(raw.allow, `${where}.allow must be an array of strings`)\n const needsApproval = strings(raw.needs_approval, `${where}.needs_approval must be an array of strings`)\n const agents = strings(raw.agents, `${where}.agents must be an array of strings`)\n\n const errors = [\n ...Object.keys(raw)\n .filter((k) => !KEYS.includes(k))\n .map((k) => `${where}: unknown key \"${k}\"`),\n ...[mode, tmux, allow, needsApproval, agents].flatMap(errorOf),\n ]\n return errors.length > 0\n ? Left<ReadonlyArray<string>, PolicyTable>(errors)\n : Right<ReadonlyArray<string>, PolicyTable>({\n mode: mode.orElse(EMPTY_TABLE.mode),\n tmux: tmux.orElse(EMPTY_TABLE.tmux),\n allow: allow.orElse(EMPTY_TABLE.allow),\n needsApproval: needsApproval.orElse(EMPTY_TABLE.needsApproval),\n agents: agents.orElse(EMPTY_TABLE.agents),\n })\n}\n\n// An empty list means \"not set\", once a host's key has replaced the default's.\nconst nonEmpty = (o: Option<ReadonlyArray<string>>): ReadonlyArray<string> => o.filter((xs) => xs.length > 0).orElse([])\n\nconst compile = (\n where: string,\n key: string,\n patterns: ReadonlyArray<string>,\n wrap: (p: string) => string,\n): Either<ReadonlyArray<string>, ReadonlyArray<RegExp>> => {\n const results = patterns.map((p) =>\n // Compile the bare pattern first: one with an unmatched `)` (e.g. `uptime)|(.*`) only compiles once\n // wrapped, and then escapes the wrap — `^(?:uptime)|(.*)$` matches anything.\n Try(() => new RegExp(wrap(new RegExp(p).source))).fold(\n (e) => Left<string, RegExp>(`${where}: invalid regex in ${key}: \"${p}\" (${(e as Error).message})`),\n (re) => Right<string, RegExp>(re),\n ),\n )\n const errors = results.flatMap(errorOf)\n return errors.length > 0\n ? Left<ReadonlyArray<string>, ReadonlyArray<RegExp>>(errors)\n : Right<ReadonlyArray<string>, ReadonlyArray<RegExp>>(\n results.flatMap((r) =>\n r.fold<RegExp[]>(\n () => [],\n (re) => [re],\n ),\n ),\n )\n}\n\nconst errorsOf = (r: Either<ReadonlyArray<string>, unknown>): ReadonlyArray<string> =>\n r.fold(\n (errs) => errs,\n () => [],\n )\n\n// Host keys replace default keys (lists are replaced, never concatenated); built-in defaults fill\n// the rest, so a config with no policy behaves as before policy existed.\nexport const resolvePolicy = (\n where: string,\n defaults: PolicyTable,\n host: PolicyTable,\n): Either<ReadonlyArray<string>, HostPolicy> => {\n const mode = host.mode.or(defaults.mode).orElse(\"readwrite\")\n const tmux = host.tmux.or(defaults.tmux).orElse(\"open\")\n const allowSrc = nonEmpty(host.allow.or(defaults.allow))\n const needsSrc = nonEmpty(host.needsApproval.or(defaults.needsApproval))\n const agents = nonEmpty(host.agents.or(defaults.agents))\n\n const allow = compile(where, \"allow\", allowSrc, (p) => `^(?:${p})$`)\n const needsApproval = compile(where, \"needs_approval\", needsSrc, (p) => p)\n const errors = [\n ...errorsOf(allow),\n ...errorsOf(needsApproval),\n ...(allowSrc.length > 0 && mode !== \"readonly\" ? [`${where}: allow applies only to mode = \"readonly\"`] : []),\n ...(needsSrc.length > 0 && mode !== \"readwrite\"\n ? [`${where}: needs_approval applies only to mode = \"readwrite\"`]\n : []),\n ]\n return errors.length > 0\n ? Left<ReadonlyArray<string>, HostPolicy>(errors)\n : Right<ReadonlyArray<string>, HostPolicy>({\n mode,\n tmux,\n allow: allow.orElse([]),\n needsApproval: needsApproval.orElse([]).map((re, i) => ({ pattern: needsSrc[i] ?? re.source, re })),\n agents: agents.length > 0 ? Option<ReadonlyArray<string>>(agents) : Option.none<ReadonlyArray<string>>(),\n })\n}\n\nexport type PolicyTool = \"exec\" | \"tmux_list\" | \"tmux_send\" | \"tmux_read\" | \"tmux_keys\"\n\n// Only exec carries a command.\nexport type PolicyRequest =\n | Readonly<{ tool: \"exec\"; agent: string; command: string }>\n | Readonly<{ tool: Exclude<PolicyTool, \"exec\">; agent: string }>\n\n// `rule` is the short rule name; the auditor qualifies it with the host.\nexport type Verdict =\n Readonly<{ decision: \"allow\"; rule: Option<string> }> | Readonly<{ decision: \"deny\"; rule: string; reason: string }>\n\n// For host-less tools and unknown hosts: no policy applies.\nexport const ALLOW: Verdict = { decision: \"allow\", rule: Option.none<string>() }\n\nconst allowBy = (rule: string): Verdict => ({ decision: \"allow\", rule: Option(rule) })\nconst denyBy = (rule: string, reason: string): Verdict => ({ decision: \"deny\", rule, reason })\n\n// Line breaks, NUL, and every character the remote `$SHELL -c` gives meaning to: control\n// operators, substitution, redirection, grouping, escapes, quotes, globs and `~`.\nconst READONLY_REJECT = /[\\n\\r\\0;&|$`()<>{}\\\\'\"*?[~]/\n\n// Policy regexes run synchronously and can backtrack badly, so commands that will be matched against\n// them are capped; a frozen regex would stall every host, not just this one.\nexport const MAX_COMMAND_CHARS = 4096\nconst TOO_LONG = `command is longer than ${MAX_COMMAND_CHARS} characters`\n\nconst listed = (agents: ReadonlyArray<string>, agent: string): boolean =>\n agents.some((e) => agent === e || agent.startsWith(`${e}@`))\n\nconst evaluateTmuxInput = (policy: HostPolicy): Verdict => {\n if (policy.mode === \"readonly\") return denyBy(\"readonly\", \"tmux input is not allowed on a readonly host\")\n return policy.tmux === \"approve\"\n ? denyBy(\"tmux\", \"tmux input requires approval, which is not available yet\")\n : allowBy(\"readwrite\")\n}\n\nconst evaluateExec = (policy: HostPolicy, command: string): Verdict => {\n // Counts characters (code points), like `excerpt`; the cheap code-unit length rules most commands out first.\n const tooLong = command.length > MAX_COMMAND_CHARS && Array.from(command).length > MAX_COMMAND_CHARS\n if (policy.mode === \"readonly\") {\n if (tooLong) return denyBy(\"readonly\", TOO_LONG)\n if (READONLY_REJECT.test(command)) return denyBy(\"readonly\", \"command contains a newline or shell metacharacter\")\n return policy.allow.some((re) => re.test(command))\n ? allowBy(\"readonly\")\n : denyBy(\"readonly\", \"command not in allow list\")\n }\n if (tooLong && policy.needsApproval.length > 0) return denyBy(\"needs_approval\", TOO_LONG)\n const tripped = policy.needsApproval.find((t) => t.re.test(command))\n return tripped === undefined\n ? allowBy(\"readwrite\")\n : denyBy(\"needs_approval\", `matches \"${tripped.pattern}\" and needs approval, which is not available yet`)\n}\n\n// First match wins: deny mode, then the agents list, then the per-tool rules.\nexport const evaluate = (policy: HostPolicy, req: PolicyRequest): Verdict => {\n if (policy.mode === \"deny\") return denyBy(\"deny\", \"host is closed by policy\")\n if (\n policy.agents.fold(\n () => false,\n (agents) => !listed(agents, req.agent),\n )\n )\n return denyBy(\"agents\", `agent \"${req.agent}\" is not in agents`)\n if (req.tool === \"exec\") return evaluateExec(policy, req.command)\n return req.tool === \"tmux_send\" || req.tool === \"tmux_keys\" ? evaluateTmuxInput(policy) : allowBy(policy.mode)\n}\n","import { isIP } from \"node:net\"\n\nimport { type ResolveAuthOptions, validateSession } from \"@ssh-mcp/core\"\nimport { type Either, Left, List, Option, Right, Try } from \"functype\"\nimport { parse as parseToml } from \"smol-toml\"\n\nimport { DEFAULT_AUDIT_PATH } from \"./audit.js\"\nimport { EMPTY_TABLE, type HostPolicy, parsePolicyTable, type PolicyTable, resolvePolicy } from \"./policy.js\"\n\nexport type HostEntry = Readonly<{\n name: string\n host: string\n port: number\n user: Option<string>\n auth: ResolveAuthOptions\n // Which auth field the host set. Kept apart from `auth`, which holds a resolved password.\n authKind: AuthKind\n passwordEnv: Option<string>\n tmuxSession: string\n logOutput: boolean\n policy: HostPolicy\n // The [[cas]] entry this host authenticates through, when it uses ca auth.\n ca: Option<string>\n principal: Option<string>\n}>\n\nexport type AuthKind = \"password_env\" | \"key\" | \"key_env\" | \"agent\" | \"ca\"\n\nexport type PoolSettings = Readonly<{\n maxPerHost: number\n idleTimeoutMs: number\n acquireTimeoutMs: number\n // exec's wait when the call gives no timeout_ms, and the most a call may ask for.\n execTimeoutMs: number\n maxExecTimeoutMs: number\n}>\n\nexport const DEFAULT_EXEC_TIMEOUT_MS = 600_000\nexport const DEFAULT_MAX_EXEC_TIMEOUT_MS = 3_600_000\n\n// Only read when parsePool succeeded; errors are reported before this is used.\nconst DEFAULT_POOL: PoolSettings = {\n maxPerHost: 2,\n idleTimeoutMs: 60000,\n acquireTimeoutMs: 30000,\n execTimeoutMs: DEFAULT_EXEC_TIMEOUT_MS,\n maxExecTimeoutMs: DEFAULT_MAX_EXEC_TIMEOUT_MS,\n}\n\nexport type AuditSettings = Readonly<{ path: string }>\n\n// Where a CA's private key comes from. Never the key itself: inline keys are rejected.\nexport type CaKeyRef =\n | Readonly<{ kind: \"cmd\"; cmd: string }>\n | Readonly<{ kind: \"env\"; name: string }>\n | Readonly<{ kind: \"file\"; path: string }>\n\nexport type CaEntry = Readonly<{ name: string; key: CaKeyRef; principal: string; sourceAddress: Option<string> }>\n\nexport type Fleet = Readonly<{\n hosts: HostEntry[]\n pool: PoolSettings\n audit: AuditSettings\n cas: ReadonlyArray<CaEntry>\n runtimeDir: Option<string>\n knownHosts: Option<string>\n}>\n\ntype RawHost = Record<string, unknown>\n\nconst str = (v: unknown): Option<string> => (typeof v === \"string\" && v.length > 0 ? Option(v) : Option.none<string>())\nconst num = (v: unknown): Option<number> => (typeof v === \"number\" ? Option(v) : Option.none<number>())\n\n// Exactly-one-auth + secrets-by-reference. Returns the auth options or an error string.\ntype HostAuth = Readonly<{ auth: ResolveAuthOptions; kind: AuthKind }>\n\nconst resolveHostAuth = (name: string, h: RawHost): Either<string, HostAuth> => {\n if (h.password !== undefined || h.private_key !== undefined) {\n return Left<string, HostAuth>(`host \"${name}\": secrets must not be inline; use key/key_env/agent/password_env/ca`)\n }\n const modes = [\n str(h.key).map((): AuthKind => \"key\"),\n str(h.key_env).map((): AuthKind => \"key_env\"),\n str(h.password_env).map((): AuthKind => \"password_env\"),\n h.agent === true ? Option<AuthKind>(\"agent\") : Option.none<AuthKind>(),\n str(h.ca).map((): AuthKind => \"ca\"),\n ].flatMap((o) => o.toArray())\n if (modes.length === 0)\n return Left<string, HostAuth>(\n `host \"${name}\": no auth mode (need exactly one of key/key_env/agent/password_env/ca)`,\n )\n if (modes.length > 1)\n return Left<string, HostAuth>(`host \"${name}\": exactly one auth mode allowed, found ${modes.length}`)\n const auth: ResolveAuthOptions = {\n password: str(h.password_env)\n .flatMap((v) => Option(process.env[v] ?? \"\"))\n .filter((v) => v.length > 0),\n keyPath: str(h.key),\n keyEnvVar: str(h.key_env),\n useAgent: h.agent === true,\n }\n return Right<string, HostAuth>({ auth, kind: modes[0] })\n}\n\n// Node's timers take at most 2^31 - 1 ms (about 24.8 days); a larger or non-positive delay fires at once.\nconst MAX_TIMER_MS = 2 ** 31 - 1\n\n// Every [pool] value is a whole number from 1 to MAX_TIMER_MS. There is no \"off\" value: 0 would\n// make every exec time out at once.\nconst poolValue = (p: Record<string, unknown>, key: string, fallback: number): Either<string, number> => {\n const v = p[key]\n if (v === undefined) return Right<string, number>(fallback)\n return typeof v === \"number\" && Number.isInteger(v) && v >= 1 && v <= MAX_TIMER_MS\n ? Right<string, number>(v)\n : Left<string, number>(`pool.${key} must be a whole number from 1 to ${MAX_TIMER_MS}`)\n}\n\nconst parsePool = (raw: Record<string, unknown>): Either<ReadonlyArray<string>, PoolSettings> => {\n const p = (raw.pool ?? {}) as Record<string, unknown>\n const fields = [\n poolValue(p, \"max_per_host\", 2),\n poolValue(p, \"idle_timeout_ms\", 60000),\n poolValue(p, \"acquire_timeout_ms\", 30000),\n poolValue(p, \"exec_timeout_ms\", DEFAULT_EXEC_TIMEOUT_MS),\n poolValue(p, \"max_exec_timeout_ms\", DEFAULT_MAX_EXEC_TIMEOUT_MS),\n ] as const\n const errors = fields.flatMap((f) =>\n f.fold(\n (e) => [e],\n () => [],\n ),\n )\n const [maxPerHost, idleTimeoutMs, acquireTimeoutMs, execTimeoutMs, maxExecTimeoutMs] = fields.map((f) => f.orElse(0))\n return errors.length > 0\n ? Left<ReadonlyArray<string>, PoolSettings>(errors)\n : Right<ReadonlyArray<string>, PoolSettings>({\n maxPerHost,\n idleTimeoutMs,\n acquireTimeoutMs,\n execTimeoutMs,\n maxExecTimeoutMs,\n })\n}\n\nconst validPrincipal = (p: string): boolean => p.length > 0 && !p.includes(\",\")\n\nconst ipv4Value = (addr: string): bigint => addr.split(\".\").reduce((acc, octet) => (acc << 8n) + BigInt(octet), 0n)\n\n// Expands `::`; an IPv6 address with an embedded dotted IPv4 tail is not accepted here.\nconst ipv6Value = (addr: string): Option<bigint> => {\n if (addr.includes(\".\")) return Option.none<bigint>()\n const [head, tail = \"\"] = addr.split(\"::\")\n const headGroups = head ? head.split(\":\") : []\n const tailGroups = addr.includes(\"::\") && tail ? tail.split(\":\") : []\n const fill = addr.includes(\"::\") ? 8 - headGroups.length - tailGroups.length : 0\n const groups = [...headGroups, ...Array.from({ length: fill }, () => \"0\"), ...tailGroups]\n return groups.length === 8\n ? Option(groups.reduce((acc, g) => (acc << 16n) + BigInt(`0x${g || \"0\"}`), 0n))\n : Option.none<bigint>()\n}\n\nconst hostBitsClear = (value: bigint, width: number, prefix: number): boolean =>\n (value & ((1n << BigInt(width - prefix)) - 1n)) === 0n\n\n// `a.b.c.d/n` (n ≤ 32) or an IPv6 address with `/n` (n ≤ 128), with no host bits set: ssh-keygen\n// rejects `100.64.0.1/24`, so it must fail here rather than at every connect.\nconst validCidr = (v: string): boolean => {\n const parts = v.split(\"/\")\n if (parts.length !== 2) return false\n const [addr, prefix] = parts\n if (!/^\\d{1,3}$/.test(prefix)) return false\n const bits = Number(prefix)\n const family = isIP(addr)\n if (family === 4) return bits <= 32 && hostBitsClear(ipv4Value(addr), 32, bits)\n if (family === 6)\n return (\n bits <= 128 &&\n ipv6Value(addr).fold(\n () => false,\n (value) => hostBitsClear(value, 128, bits),\n )\n )\n return false\n}\n\nconst parseCa = (c: RawHost): Either<string, CaEntry> => {\n const name = str(c.name)\n if (name.isNone()) return Left<string, CaEntry>(\"[[cas]] entry has no name\")\n const n = name.orElse(\"\")\n if (c.key !== undefined || c.private_key !== undefined)\n return Left<string, CaEntry>(`ca \"${n}\": keys must not be inline; use key_cmd, key_env or key_file`)\n const refs: ReadonlyArray<CaKeyRef> = [\n ...str(c.key_cmd).fold<CaKeyRef[]>(\n () => [],\n (cmd) => [{ kind: \"cmd\", cmd }],\n ),\n ...str(c.key_env).fold<CaKeyRef[]>(\n () => [],\n (envName) => [{ kind: \"env\", name: envName }],\n ),\n ...str(c.key_file).fold<CaKeyRef[]>(\n () => [],\n (path) => [{ kind: \"file\", path }],\n ),\n ]\n if (refs.length !== 1)\n return Left<string, CaEntry>(`ca \"${n}\": exactly one of key_cmd, key_env, key_file is required`)\n const key = refs[0]\n const principal = c.principal === undefined ? \"fleet\" : typeof c.principal === \"string\" ? c.principal : \"\"\n if (!validPrincipal(principal)) return Left<string, CaEntry>(`ca \"${n}\": principal must be non-empty with no commas`)\n const sourceAddress = str(c.source_address)\n if (c.source_address !== undefined && !sourceAddress.fold(() => false, validCidr))\n return Left<string, CaEntry>(`ca \"${n}\": source_address must be an IPv4 or IPv6 CIDR`)\n return Right<string, CaEntry>({ name: n, key, principal, sourceAddress })\n}\n\nconst parseAudit = (raw: Record<string, unknown>): Either<string, AuditSettings> => {\n const a = (raw.audit ?? {}) as Record<string, unknown>\n if (a.path === undefined) return Right<string, AuditSettings>({ path: DEFAULT_AUDIT_PATH })\n return str(a.path).fold(\n () => Left<string, AuditSettings>(\"audit.path must be a non-empty string\"),\n (path) => Right<string, AuditSettings>({ path }),\n )\n}\n\n// smol-toml's message quotes the offending source lines, which can hold an inline secret, and\n// fleet_setup relays this text to the MCP client. Keep the location and the first line only.\n/** @interop smol-toml throws an Error carrying line and column fields. */\nconst tomlError = (e: unknown): string => {\n const err = e as Error & { line?: number; column?: number }\n const summary = err.message.split(\"\\n\")[0]\n return err.line === undefined\n ? `Invalid TOML: ${summary}`\n : `Invalid TOML at line ${err.line}, column ${err.column}: ${summary}`\n}\n\nexport const parseFleet = (toml: string): Either<string, Fleet> => {\n const parsed = Try(() => parseToml(toml) as Record<string, unknown>)\n if (parsed.isFailure()) return Left<string, Fleet>(tomlError(parsed.error))\n const raw = parsed.orThrow()\n\n const defaults = (raw.defaults ?? {}) as Record<string, unknown>\n const defaultPort = num(defaults.port).orElse(22)\n const defaultUser = str(defaults.user)\n const defaultSession = str(defaults.tmux_session).orElse(\"agent\")\n const rawHosts = Array.isArray(raw.hosts) ? (raw.hosts as RawHost[]) : []\n // Parsed once; if it fails, hosts resolve against EMPTY_TABLE so its errors appear once.\n const defaultsPolicy = parsePolicyTable(defaults.policy, \"defaults.policy\")\n const defaultsTable: PolicyTable = defaultsPolicy.orElse(EMPTY_TABLE)\n\n const names = rawHosts.map((h) => str(h.name).orElse(\"\"))\n const rawCas = Array.isArray(raw.cas) ? (raw.cas as RawHost[]) : []\n const caNames = rawCas.map((c) => str(c.name).orElse(\"\"))\n const parseHost = (h: RawHost, index: number): Either<string, HostEntry> => {\n const name = str(h.name).orElse(\"\")\n if (!validateSession(name).isRight())\n return Left<string, HostEntry>(`invalid host name \"${name}\": only letters, digits, hyphen, underscore`)\n if (names.indexOf(name) < index) return Left<string, HostEntry>(`duplicate host name \"${name}\"`)\n const address = str(h.host)\n if (address.isNone()) return Left<string, HostEntry>(`host \"${name}\": missing host address`)\n if (h.log_output !== undefined && typeof h.log_output !== \"boolean\")\n return Left<string, HostEntry>(`host \"${name}\": log_output must be true or false`)\n const ca = str(h.ca)\n if (ca.isSome() && !caNames.includes(ca.orElse(\"\")))\n return Left<string, HostEntry>(`host \"${name}\": ca \"${ca.orElse(\"\")}\" is not defined in [[cas]]`)\n const principal = typeof h.principal === \"string\" ? Option(h.principal) : Option.none<string>()\n if (h.principal !== undefined && ca.isNone())\n return Left<string, HostEntry>(`host \"${name}\": principal applies only to ca auth`)\n if (h.principal !== undefined && !principal.fold(() => false, validPrincipal))\n return Left<string, HostEntry>(`host \"${name}\": principal must be non-empty with no commas`)\n const where = `host \"${name}\": policy`\n const policy = parsePolicyTable(h.policy, where)\n .flatMap((table) => resolvePolicy(where, defaultsTable, table))\n .mapLeft((errs) => errs.join(\"\\n\"))\n return resolveHostAuth(name, h).flatMap(({ auth, kind }) =>\n policy.map((p) => ({\n name,\n host: address.orElse(\"\"),\n port: num(h.port).orElse(defaultPort),\n user: str(h.user).or(defaultUser),\n auth,\n authKind: kind,\n passwordEnv: str(h.password_env),\n tmuxSession: str(h.tmux_session).orElse(defaultSession),\n logOutput: h.log_output === true,\n policy: p,\n ca,\n principal,\n })),\n )\n }\n\n const audit = parseAudit(raw)\n const results = rawHosts.map(parseHost)\n const casResults = rawCas.map(parseCa)\n const caErrors = [\n ...casResults.flatMap((r) =>\n r.fold<string[]>(\n (msg) => [msg],\n () => [],\n ),\n ),\n ...caNames.filter((n, i) => n !== \"\" && caNames.indexOf(n) < i).map((n) => `ca \"${n}\": duplicate name`),\n ]\n const pool = parsePool(raw)\n const errors = [\n ...caErrors,\n ...defaultsPolicy.fold(\n (errs) => [...errs],\n () => [],\n ),\n ...pool.fold(\n (errs) => [...errs],\n () => [],\n ),\n ...[audit, ...results].flatMap((r) =>\n r.fold<string[]>(\n (msg) => [msg],\n () => [],\n ),\n ),\n ]\n const hosts = results.flatMap((r) =>\n r.fold<HostEntry[]>(\n () => [],\n (entry) => [entry],\n ),\n )\n\n return errors.length > 0\n ? Left<string, Fleet>(`Fleet config errors:\\n${List(errors).toArray().join(\"\\n\")}`)\n : Right<string, Fleet>({\n hosts,\n pool: pool.orElse(DEFAULT_POOL),\n audit: audit.orElse({ path: DEFAULT_AUDIT_PATH }),\n cas: casResults.flatMap((r) =>\n r.fold<CaEntry[]>(\n () => [],\n (c) => [c],\n ),\n ),\n runtimeDir: str(raw.runtime_dir),\n knownHosts: str(raw.known_hosts),\n })\n}\n\n// Startup warnings: an empty fleet, a password variable that isn't set, and hosts that will deny\n// every call until agent identity exists (the agent is \"unknown\" until then).\nexport const fleetWarnings = (fleet: Fleet): ReadonlyArray<string> => [\n ...(fleet.hosts.length === 0 ? [\"no hosts configured; add a [[hosts]] entry and restart\"] : []),\n ...fleet.hosts.flatMap((h) =>\n h.passwordEnv\n .filter((v) => !process.env[v])\n .fold(\n () => [],\n (v) => [`host \"${h.name}\": password_env ${v} is not set`],\n ),\n ),\n ...fleet.hosts\n .filter((h) => h.policy.agents.isSome())\n .map((h) => `host \"${h.name}\": policy.agents is set, so every call is denied until agent identity is available`),\n]\n","import { lstat, readFile } from \"node:fs/promises\"\nimport { homedir } from \"node:os\"\nimport { join } from \"node:path\"\n\nimport {\n type CaKeySource,\n type CommandResult,\n type ConnectFactory,\n createPool,\n type Custody,\n type ExecOptions,\n type HostStatus,\n mint,\n nodeProcessRunner,\n type Openssh,\n opensshConnect,\n type Pool,\n type ProcessRunner,\n type ProcResult,\n resolveAuth,\n socketPathFor,\n type TmuxRunner,\n} from \"@ssh-mcp/core\"\nimport { Either, Left, Map, Option, Right, Set } from \"functype\"\nimport { Path } from \"functype-os\"\nimport { type ConnectConfig } from \"ssh2\"\n\nimport { type CaEntry, type CaKeyRef, type Fleet, type HostEntry } from \"./fleet.js\"\nimport { type HostPolicy } from \"./policy.js\"\n\nexport type FleetRunner = Readonly<{\n hostNames: string[]\n tmuxRunnerFor: (host: string, agent: string) => Either<string, TmuxRunner>\n // timeoutMs defaults to [pool].exec_timeout_ms and is capped at [pool].max_exec_timeout_ms.\n exec: (host: string, command: string, agent: string, timeoutMs?: number) => Promise<CommandResult>\n sessionFor: (host: string) => string\n logOutputFor: (host: string) => boolean\n policyFor: (host: string) => Option<HostPolicy>\n status: () => HostStatus[]\n // The last connect or transport failure on a configured host, cleared by its next success.\n lastError: (host: string) => Option<HostError>\n shutdown: () => Promise<void>\n}>\n\nexport type HostError = Readonly<{ message: string; at: string }>\n\nexport type HostRow = Readonly<{\n name: string\n state: HostStatus[\"state\"] | \"error\"\n inFlight: number\n last_error?: HostError\n}>\n\n// The rows hosts_list and fleet_setup show. \"disconnected\" only means no open connection, which is\n// normal before the first call. A host whose last call failed for a connection reason (refused,\n// auth, a busy pool's acquire timeout) reads \"error\" with the reason, until its next success. A CA\n// host's rows are named \"<host> (<agent>)\"; host names can't contain a space.\nexport const hostRows = (runner: Pick<FleetRunner, \"status\" | \"lastError\">): ReadonlyArray<HostRow> =>\n runner.status().map((row): HostRow =>\n row.state !== \"disconnected\"\n ? row\n : runner.lastError(row.name.split(\" \")[0]).fold<HostRow>(\n () => row,\n (e) => ({ ...row, state: \"error\", last_error: e }),\n ),\n )\n\n// What CA hosts need at runtime. `openFor` replaces the real OpenSSH factory, for tests.\nexport type CaWiring = Readonly<{\n custody: Custody\n proc: ProcessRunner\n knownHosts: string\n openFor?: (agent: string, host: HostEntry) => Openssh\n}>\n\nconst stateDir = (): string => join(homedir(), \".local\", \"state\", \"ssh-fleet\")\n\nexport const defaultRuntimeDir = (): string => {\n const xdg = process.env.XDG_RUNTIME_DIR\n return xdg ? join(xdg, \"ssh-fleet\") : join(stateDir(), \"run\")\n}\n\nexport const defaultKnownHosts = (): string => join(stateDir(), \"known_hosts\")\n\nconst fromFile = async (path: string): Promise<Either<string, string>> => {\n const expanded = Path.expand(path)\n if (expanded.isLeft()) return Left<string, string>(`key_file ${path}: ${expanded.value.message}`)\n const file = expanded.value\n const info = await lstat(file).catch((e: unknown) => e as Error)\n if (info instanceof Error) return Left<string, string>(`key_file ${file}: ${info.message}`)\n if (info.isSymbolicLink()) return Left<string, string>(`key_file ${file} is a symlink`)\n if (!info.isFile()) return Left<string, string>(`key_file ${file} is not a regular file`)\n if ((info.mode & 0o077) !== 0) return Left<string, string>(`key_file ${file} must be mode 0600 or stricter`)\n if (info.uid !== process.getuid?.()) return Left<string, string>(`key_file ${file} is not owned by the hub user`)\n return Right<string, string>(await readFile(file, \"utf8\"))\n}\n\nconst readCaKey = async (ref: CaKeyRef, timeoutMs: number): Promise<Either<string, string>> => {\n switch (ref.kind) {\n case \"cmd\": {\n // A secrets-manager CLI may need its own environment (a service token, a keychain session),\n // so key_cmd sees the hub's environment; it is operator config, not an OpenSSH child.\n const env = Object.fromEntries(\n Object.entries(process.env).flatMap(([k, v]) => (v === undefined ? [] : [[k, v]])),\n ) as Readonly<Record<string, string>>\n const r = await nodeProcessRunner\n .run(\"sh\", [\"-c\", ref.cmd], { env, timeoutMs })\n .catch((e: unknown): ProcResult => ({ code: -1, stdout: \"\", stderr: (e as Error).message }))\n return r.code === 0 && r.stdout.trim().length > 0\n ? Right<string, string>(`${r.stdout.trim()}\\n`)\n : Left<string, string>(\n r.timedOut === true\n ? `key_cmd timed out after ${timeoutMs} ms`\n : `key_cmd exited ${r.code}: ${r.stderr.trim() || \"no output\"}`,\n )\n }\n case \"env\": {\n const value = process.env[ref.name]\n // Read once at startup, then gone, so no later child process inherits the key.\n delete process.env[ref.name]\n return value ? Right<string, string>(value) : Left<string, string>(`environment variable ${ref.name} is not set`)\n }\n case \"file\":\n return fromFile(ref.path)\n }\n}\n\n// key_cmd gets 30 s and no terminal: a command that would prompt fails instead of blocking startup.\nexport const KEY_CMD_TIMEOUT_MS = 30_000\n\n// Options are an object, not a positional number, so `cas.map(caKeySource)` can't pass an index as a timeout.\nexport const caKeySource = (entry: CaEntry, opts: Readonly<{ timeoutMs?: number }> = {}): CaKeySource => ({\n name: entry.name,\n key: () => readCaKey(entry.key, opts.timeoutMs ?? KEY_CMD_TIMEOUT_MS),\n})\n\n// Mints and discards one certificate per CA, so a CA the agent can't sign with, or a certificate\n// option ssh-keygen rejects, stops startup instead of failing every later call.\nexport const selfTestCas = async (\n custody: Custody,\n cas: ReadonlyArray<CaEntry>,\n proc: ProcessRunner,\n): Promise<Either<string, void>> => {\n const tested = await Either.traverseAsync(cas, async (ca) => {\n const minted = await mint(\n custody,\n { ca: ca.name, agent: \"self-test\", principal: ca.principal, sourceAddress: ca.sourceAddress },\n proc,\n )\n return minted.fold(\n (msg) => Promise.resolve(Left<string, void>(msg)),\n (m) => m.dispose().then(() => Right<string, void>(undefined)),\n )\n })\n return tested.map(() => undefined)\n}\n\nconst sshConfigFor = (h: HostEntry): ConnectConfig => ({\n host: h.host,\n port: h.port,\n username: h.user.orElse(process.env.USER ?? \"root\"),\n})\n\nconst toSshConfig = async (h: HostEntry): Promise<Either<string, ConnectConfig>> => {\n const auth = await resolveAuth(h.auth)\n return auth.map((a) => ({ ...sshConfigFor(h), ...a }))\n}\n\nexport const buildFleetRunner = async (\n fleet: Fleet,\n connect?: ConnectFactory,\n ca: Option<CaWiring> = Option.none<CaWiring>(),\n): Promise<Either<string, FleetRunner>> => {\n // CA hosts never enter the ssh2 pool; they get OpenSSH pools per agent.\n const caHosts = fleet.hosts.filter((h) => h.ca.isSome())\n const sshHosts = fleet.hosts.filter((h) => h.ca.isNone())\n if (caHosts.length > 0 && ca.isNone())\n return Left<string, FleetRunner>(`host \"${caHosts[0].name}\" uses ca auth but CA custody is not running`)\n\n // A host whose auth can't resolve (a missing key file, an unset key_env, no SSH_AUTH_SOCK) doesn't\n // stop startup: it fails every call with the reason, and fleet_setup shows it from the first start.\n const resolved = await Promise.all(sshHosts.map(async (h) => ({ name: h.name, sshConfig: await toSshConfig(h) })))\n const ready = resolved.flatMap(({ name, sshConfig }) =>\n sshConfig.fold<Array<{ name: string; sshConfig: ConnectConfig }>>(\n () => [],\n (cfg) => [{ name, sshConfig: cfg }],\n ),\n )\n const authErrors = resolved.flatMap(({ name, sshConfig }) =>\n sshConfig.fold<Array<[string, string]>>(\n (e) => [[name, e]],\n () => [],\n ),\n )\n return Right<string, FleetRunner>(makeRunner(fleet, ready, authErrors, caHosts, ca, connect))\n}\n\nconst realOpenssh = (wiring: CaWiring, caEntry: CaEntry, agent: string, host: HostEntry): Openssh => {\n const socket = socketPathFor(wiring.custody.runtimeDir, agent, host.name)\n return opensshConnect({\n proc: wiring.proc,\n knownHosts: wiring.knownHosts,\n socketPath: socket.orElse(\"\"),\n mintFor: () =>\n socket.fold(\n (msg) => Promise.resolve(Left(msg)),\n () =>\n mint(\n wiring.custody,\n {\n ca: caEntry.name,\n agent,\n principal: host.principal.orElse(caEntry.principal),\n sourceAddress: caEntry.sourceAddress,\n },\n wiring.proc,\n ),\n ),\n })\n}\n\ntype CaPool = Readonly<{ host: string; agent: string; pool: Pool; openssh: Openssh }>\n\nconst makeRunner = (\n fleet: Fleet,\n resolved: ReadonlyArray<{ name: string; sshConfig: ConnectConfig }>,\n authErrors: ReadonlyArray<readonly [string, string]>,\n caHosts: ReadonlyArray<HostEntry>,\n ca: Option<CaWiring>,\n connect?: ConnectFactory,\n): FleetRunner => {\n const sessions = Map.of(...fleet.hosts.map((h): [string, string] => [h.name, h.tmuxSession]))\n const logOutput = Map.of(...fleet.hosts.map((h): [string, boolean] => [h.name, h.logOutput]))\n const policies = Map.of(...fleet.hosts.map((h): [string, HostPolicy] => [h.name, h.policy]))\n const caHostByName = Map.of(...caHosts.map((h): [string, HostEntry] => [h.name, h]))\n const caByName = Map.of(...fleet.cas.map((c): [string, CaEntry] => [c.name, c]))\n const sshPool = createPool(resolved, fleet.pool, connect)\n const names = Set.of(...fleet.hosts.map((h) => h.name))\n // One pool per (agent, CA host), created on first use: an agent never rides another's session.\n const caPools = new globalThis.Map<string, CaPool>()\n\n const caPoolFor = (host: HostEntry, agent: string, wiring: CaWiring): Pool => {\n const key = `${agent}\\0${host.name}`\n const existing = caPools.get(key)\n if (existing !== undefined) return existing.pool\n const caEntry = caByName.get(host.ca.orElse(\"\")).orThrow(new Error(`ca \"${host.ca.orElse(\"\")}\" is not defined`))\n const openssh = (wiring.openFor ?? ((a, h) => realOpenssh(wiring, caEntry, a, h)))(agent, host)\n const pool = createPool([{ name: host.name, sshConfig: sshConfigFor(host) }], fleet.pool, openssh.connect)\n caPools.set(key, { host: host.name, agent, pool, openssh })\n return pool\n }\n\n const routeConnected = (host: string, command: string, agent: string, opts: ExecOptions): Promise<CommandResult> =>\n caHostByName.get(host).fold(\n () => sshPool.run(host, command, opts),\n (h) =>\n ca.fold(\n () => Promise.reject(new Error(\"CA custody is not running, and this host uses ca auth\")),\n (wiring) => caPoolFor(h, agent, wiring).run(host, command, opts),\n ),\n )\n\n const authErrorByName = Map.of(...authErrors.map(([name, msg]): [string, string] => [name, msg]))\n\n const route = (host: string, command: string, agent: string, opts: ExecOptions): Promise<CommandResult> =>\n authErrorByName.get(host).fold(\n () => routeConnected(host, command, agent, opts),\n (msg) => Promise.reject(new Error(msg)),\n )\n\n const startedAt = new Date().toISOString()\n const lastErrors = new globalThis.Map<string, HostError>(\n authErrors.map(([name, message]): [string, HostError] => [name, { message, at: startedAt }]),\n )\n\n // A rejection is a connection-level failure (connect, auth, transport, or a busy pool's acquire\n // timeout); a non-zero exit code resolves and isn't one.\n // Only configured hosts are recorded, so unknown names can't grow the map.\n const effectiveTimeout = (requested?: number): number =>\n Math.min(requested ?? fleet.pool.execTimeoutMs, fleet.pool.maxExecTimeoutMs)\n\n const runOn = (host: string, command: string, agent: string, timeoutMs?: number): Promise<CommandResult> =>\n route(host, command, agent, { timeoutMs: effectiveTimeout(timeoutMs) }).then(\n (result) => {\n lastErrors.delete(host)\n return result\n },\n (e: unknown) => {\n if (names.has(host)) lastErrors.set(host, { message: (e as Error).message, at: new Date().toISOString() })\n return Promise.reject(e)\n },\n )\n\n const caStatus = (): HostStatus[] =>\n caHosts.flatMap((h) => {\n const pools = [...caPools.values()].filter((p) => p.host === h.name)\n return pools.length === 0\n ? [{ name: h.name, state: \"disconnected\" as const, inFlight: 0 }]\n : pools.flatMap((p) => p.pool.status().map((s) => ({ ...s, name: `${h.name} (${p.agent})` })))\n })\n\n // One message for every tool, so exec and tmux_* both list the configured names.\n const unknownHost = (host: string): string =>\n names.size === 0\n ? `unknown host \"${host}\" — no hosts are configured`\n : `unknown host \"${host}\" — configured: ${[...names].join(\", \")}`\n\n return {\n hostNames: fleet.hosts.map((h) => h.name),\n sessionFor: (host) => sessions.get(host).orElse(\"agent\"),\n logOutputFor: (host) => logOutput.get(host).orElse(false),\n policyFor: (host) => policies.get(host),\n tmuxRunnerFor: (host, agent) =>\n names.has(host)\n ? Right<string, TmuxRunner>((command) => runOn(host, command, agent))\n : Left<string, TmuxRunner>(unknownHost(host)),\n exec: (host, command, agent, timeoutMs) =>\n names.has(host) ? runOn(host, command, agent, timeoutMs) : Promise.reject(new Error(unknownHost(host))),\n status: () => [\n ...sshPool.status(),\n ...authErrors.map(([name]): HostStatus => ({ name, state: \"disconnected\", inFlight: 0 })),\n ...caStatus(),\n ],\n lastError: (host) => Option(lastErrors.get(host)),\n // Pools first (closes connections), then every master is awaited, and only then is the CA\n // agent stopped and the runtime dir removed.\n shutdown: async () => {\n const pools = [...caPools.values()]\n await Promise.all([sshPool.shutdown(), ...pools.map((p) => p.pool.shutdown())])\n await Promise.all(pools.map((p) => p.openssh.shutdown()))\n await ca.fold(\n () => Promise.resolve(),\n (wiring) => wiring.custody.shutdown(),\n )\n },\n }\n}\n","// The starter config written on first start at the default path. Every line is a comment, so it\n// parses to zero hosts. Inside an example block, TOML lines start with \"# \" and prose with \"## \", so\n// removing the leading \"# \" turns a block into valid TOML (test/template.spec.ts checks each one).\nexport const FLEET_TEMPLATE = `# ssh-fleet-mcp-server config.\n#\n# Add at least one [[hosts]] entry, then restart the server. In Claude Code, use /mcp and\n# reconnect. The fleet_setup tool shows what is configured and what is still missing.\n#\n# To use an example block, remove the leading \"# \" from each of its lines.\n#\n# Secrets never go in this file. Each host names one place its secret comes from:\n# password_env = env var holding a password key = path to a private key\n# key_env = env var holding a key (PEM) agent = true (uses SSH_AUTH_SOCK)\n# ca = a [[cas]] entry: the server signs a 10-minute certificate per connection\n#\n# Reference: https://github.com/jordanburke/ssh-client-mcp-server/tree/main/packages/orchestrator#fleet-config-schema\n\n# --- example: host with a password from the environment ---\n# [[hosts]]\n# name = \"mini\"\n# host = \"mini.local\"\n# user = \"me\"\n# password_env = \"MINI_SSH_PASSWORD\"\n# --- end ---\n\n# --- example: host with a key file ---\n# [[hosts]]\n# name = \"build\"\n# host = \"build.example.com\"\n# key = \"~/.ssh/id_ed25519\"\n# --- end ---\n\n# --- example: read-only policy for every host ---\n## Only commands matching an allow pattern run. See the README's Policy section.\n# [defaults.policy]\n# mode = \"readonly\"\n# allow = ['uptime', 'df -h']\n# --- end ---\n\n# --- example: exec timeouts ---\n## exec waits exec_timeout_ms unless a call passes timeout_ms, and never longer than\n## max_exec_timeout_ms. Run longer jobs in tmux (tmux_send, then tmux_read).\n# [pool]\n# exec_timeout_ms = 600000\n# max_exec_timeout_ms = 3600000\n# --- end ---\n\n# --- example: certificate authority ---\n## If key_file doesn't exist, the server creates it on first start. fleet_setup then shows\n## the line to add to a root-owned authorized-keys file on each host. To keep the key off\n## disk, use key_cmd (a command that prints it, e.g. a keychain read) or key_env instead.\n# [[cas]]\n# name = \"home\"\n# key_file = \"~/.local/state/ssh-fleet/ca-home\"\n#\n# [[hosts]]\n# name = \"nas\"\n# host = \"nas.local\"\n# user = \"fleet\"\n# ca = \"home\"\n# --- end ---\n`\n","import { existsSync, mkdirSync, statSync, writeFileSync } from \"node:fs\"\nimport { lstat } from \"node:fs/promises\"\nimport { dirname, join } from \"node:path\"\n\nimport { childEnv, type ProcessRunner } from \"@ssh-mcp/core\"\nimport { Either, Left, type Map, Option, Right, Try } from \"functype\"\nimport { Path } from \"functype-os\"\n\nimport { type AuthKind, type CaEntry, type Fleet, type HostEntry, parseFleet } from \"./fleet.js\"\nimport { type FleetRunner, type HostError, type HostRow, hostRows } from \"./runners.js\"\nimport { FLEET_TEMPLATE } from \"./template.js\"\n\nexport type ConfigPath = Readonly<{ path: string; explicit: boolean }>\n\n// --config= wins, then SSH_FLEET_CONFIG, then the default. Only the default is ever created.\nexport const resolveConfigPath = (argv: ReadonlyArray<string>, env: NodeJS.ProcessEnv, home: string): ConfigPath => {\n const flag = argv.find((a) => a.startsWith(\"--config=\"))\n if (flag) return { path: flag.slice(\"--config=\".length), explicit: true }\n return Option(env.SSH_FLEET_CONFIG)\n .filter((p) => p.length > 0)\n .fold<ConfigPath>(\n () => ({ path: join(home, \".config\", \"ssh-fleet\", \"fleet.toml\"), explicit: false }),\n (p) => ({ path: p, explicit: true }),\n )\n}\n\nconst isCode = (e: unknown, c: string): boolean => (e as NodeJS.ErrnoException).code === c\n\n// Writes the starter config at the default path when it is missing. An explicit path is never\n// written: a typo there must fail startup, not produce a healthy-looking server with no hosts.\nexport const ensureConfig = (config: ConfigPath): Either<string, Readonly<{ path: string; created: boolean }>> => {\n const { path } = config\n if (config.explicit)\n return Try(() => statSync(path)).fold(\n (e) =>\n Left(\n isCode(e, \"ENOENT\")\n ? `Cannot read fleet config at ${path}: no such file (only the default path is created on first start)`\n : `Cannot read fleet config at ${path}: ${(e as Error).message}`,\n ),\n () => Right({ path, created: false }),\n )\n // No existence check first: \"wx\" fails with EEXIST when the file is there, whether it always was\n // or another client created it a moment ago.\n return Try(() => {\n mkdirSync(dirname(path), { recursive: true, mode: 0o700 })\n writeFileSync(path, FLEET_TEMPLATE, { flag: \"wx\", mode: 0o600 })\n }).fold(\n (e) =>\n isCode(e, \"EEXIST\")\n ? Right({ path, created: false })\n : Left(`Cannot create fleet config at ${path}: ${(e as Error).message}`),\n () => Right({ path, created: true }),\n )\n}\n\n// The expanded key_file path when the CA uses key_file and nothing is at that path (ENOENT only:\n// lstat, so a dangling symlink counts as present).\nexport const missingKeyFile = async (ca: CaEntry): Promise<Either<string, Option<string>>> => {\n if (ca.key.kind !== \"file\") return Right(Option.none<string>())\n const expanded = Path.expand(ca.key.path)\n if (expanded.isLeft()) return Left(`CA \"${ca.name}\": key_file ${ca.key.path}: ${expanded.value.message}`)\n const path = expanded.value\n const missing = await lstat(path).then(\n () => false,\n (e: unknown) => isCode(e, \"ENOENT\"),\n )\n return Right(missing ? Option(path) : Option.none<string>())\n}\n\n// Creates the key for each CA whose key_file is configured and missing (ENOENT only). Anything else at\n// that path, including a symlink or a file with the wrong mode, is left for the key_file checks at\n// custody start to report. key_cmd and key_env never create anything. Returns the created CAs' names.\nexport const ensureCaKeyFiles = async (\n cas: ReadonlyArray<CaEntry>,\n proc: ProcessRunner,\n): Promise<Either<string, ReadonlyArray<string>>> => {\n const created = await Either.traverseAsync(cas, async (ca): Promise<Either<string, ReadonlyArray<string>>> => {\n const missing = await missingKeyFile(ca)\n if (missing.isLeft()) return Left(missing.value)\n if (missing.value.isNone()) return Right([])\n const path = missing.value.orElse(\"\")\n const made = Try(() => mkdirSync(dirname(path), { recursive: true, mode: 0o700 }))\n if (made.isFailure()) return Left(`CA \"${ca.name}\": creating key_file ${path} failed: ${made.error.message}`)\n const r = await proc.run(\n \"ssh-keygen\",\n [\"-q\", \"-t\", \"ed25519\", \"-N\", \"\", \"-C\", `ssh-fleet-ca-${ca.name}`, \"-f\", path],\n { env: childEnv(), input: \"\" },\n )\n if (r.code === 0) return Right([ca.name])\n // ssh-keygen refuses an existing file, so a failure with the file present means another\n // process created it first.\n if (existsSync(path)) return Right([])\n return Left(`CA \"${ca.name}\": creating key_file ${path} failed: ${r.stderr.trim() || `exit ${r.code}`}`)\n })\n return created.map((names) => names.flat())\n}\n\n// What fleet_setup reports from, fixed once startup finishes.\nexport type SetupContext = Readonly<{\n configPath: string\n configCreated: boolean\n fleet: Fleet\n // The config text loaded at start, and a way to read the file now, to notice edits made after start.\n loadedConfig: string\n readConfig: () => Either<string, string>\n // CA name → its public key line, as loaded into custody.\n caPublicKeys: Map<string, string>\n caCreated: ReadonlyArray<string>\n runtimeDir: Option<string>\n knownHosts: Option<string>\n}>\n\nexport type HostReport = Readonly<{\n name: string\n address: string\n auth: AuthKind\n password_env?: string\n env_set?: boolean\n ca?: string\n principal?: string\n connections: ReadonlyArray<HostRow>\n last_error?: HostError\n enroll_line?: string\n}>\n\nexport type CaReport = Readonly<{\n name: string\n key_source: \"key_cmd\" | \"key_env\" | \"key_file\"\n key_file?: string\n created_this_start: boolean\n public_key?: string\n enroll_line?: string\n}>\n\nexport type SetupReport = Readonly<{\n config: Readonly<{\n path: string\n created_this_start: boolean\n changed_since_start: boolean\n hosts_on_disk?: number\n errors_on_disk?: string\n }>\n next_steps: ReadonlyArray<string>\n hosts: ReadonlyArray<HostReport>\n cas: ReadonlyArray<CaReport>\n audit_log: string\n runtime_dir?: string\n known_hosts?: string\n}>\n\nexport const enrollLine = (principal: string, publicKey: string): string =>\n `cert-authority,principals=\"${principal}\" ${publicKey.trim()}`\n\nexport const hostAddress = (h: HostEntry): string =>\n h.user.fold(\n () => `${h.host}:${h.port}`,\n (u) => `${u}@${h.host}:${h.port}`,\n )\n\nconst KEY_SOURCE = { cmd: \"key_cmd\", env: \"key_env\", file: \"key_file\" } as const\n\n// Built field by field: a HostEntry carries a resolved password in `auth`, so no whole entry, and no\n// key_cmd string, may reach the report.\nconst hostReport = (\n h: HostEntry,\n ctx: SetupContext,\n runner: Pick<FleetRunner, \"status\" | \"lastError\">,\n env: NodeJS.ProcessEnv,\n): HostReport => {\n const ca = h.ca.flatMap((name) => Option(ctx.fleet.cas.find((c) => c.name === name)))\n const principal = h.principal.or(ca.map((c) => c.principal))\n const enroll = ca\n .flatMap((c) => ctx.caPublicKeys.get(c.name))\n .flatMap((pub) => principal.map((p) => enrollLine(p, pub)))\n return {\n name: h.name,\n address: hostAddress(h),\n auth: h.authKind,\n ...h.passwordEnv.fold(\n () => ({}),\n (v) => ({ password_env: v, env_set: Boolean(env[v]) }),\n ),\n ...h.ca.fold(\n () => ({}),\n (name) => ({ ca: name, principal: principal.orElse(\"\") }),\n ),\n connections: hostRows(runner).filter((row) => row.name === h.name || row.name.startsWith(`${h.name} (`)),\n ...runner.lastError(h.name).fold(\n () => ({}),\n (e) => ({ last_error: e }),\n ),\n ...enroll.fold(\n () => ({}),\n (line) => ({ enroll_line: line }),\n ),\n }\n}\n\nconst caReport = (c: CaEntry, ctx: SetupContext): CaReport => {\n const pub = ctx.caPublicKeys.get(c.name)\n return {\n name: c.name,\n key_source: KEY_SOURCE[c.key.kind],\n ...(c.key.kind === \"file\" ? { key_file: c.key.path } : {}),\n created_this_start: ctx.caCreated.includes(c.name),\n ...pub.fold(\n () => ({}),\n (p) => ({ public_key: p.trim(), enroll_line: enrollLine(c.principal, p) }),\n ),\n }\n}\n\ntype Drift = Readonly<{\n changed: boolean\n fields: Readonly<{ changed_since_start: boolean; hosts_on_disk?: number; errors_on_disk?: string }>\n step: ReadonlyArray<string>\n}>\n\nconst RECONNECT = \"reconnect the server to load it (in Claude Code: /mcp, then reconnect)\"\n\n// The server reads fleet.toml once. An edit after start is invisible until a reconnect, so compare the\n// file now with the text loaded at start, and parse it to say what a reconnect would load.\nconst configDrift = (ctx: SetupContext): Drift => {\n const now = ctx.readConfig()\n if (now.isRight() && now.value === ctx.loadedConfig)\n return { changed: false, fields: { changed_since_start: false }, step: [] }\n const parsed = now.flatMap(parseFleet)\n return parsed.fold<Drift>(\n (errors) => ({\n changed: true,\n fields: { changed_since_start: true, errors_on_disk: errors },\n step: [`fleet.toml changed since the server started and now has errors; fix them before reconnecting: ${errors}`],\n }),\n (onDisk) => ({\n changed: true,\n fields: { changed_since_start: true, hosts_on_disk: onDisk.hosts.length },\n step: [\n `fleet.toml changed since the server started (${onDisk.hosts.length} hosts on disk, ${ctx.fleet.hosts.length} loaded): ${RECONNECT}.`,\n ],\n }),\n )\n}\n\n// The fleet_setup tool's answer: what is configured, its live state, and what to do next.\nexport const setupReport = (\n ctx: SetupContext,\n runner: Pick<FleetRunner, \"status\" | \"lastError\">,\n env: NodeJS.ProcessEnv = process.env,\n): SetupReport => {\n const hosts = ctx.fleet.hosts.map((h) => hostReport(h, ctx, runner, env))\n const cas = ctx.fleet.cas.map((c) => caReport(c, ctx))\n const drift = configDrift(ctx)\n const nextSteps = [\n ...drift.step,\n ...(hosts.length === 0 && !drift.changed\n ? [`Add a [[hosts]] entry to ${ctx.configPath}, then restart the server.`]\n : []),\n ...hosts\n .filter((h) => h.env_set === false)\n .map((h) => `${h.name}: set ${h.password_env} in the MCP client's env for this server, then restart.`),\n ...cas\n .filter((c) => c.created_this_start && c.enroll_line !== undefined)\n .map(\n (c) =>\n `Add CA \"${c.name}\"'s line to a root-owned authorized-keys file on each host that uses it: ${c.enroll_line}`,\n ),\n ...hosts.flatMap((h) => (h.last_error ? [`${h.name}: ${h.last_error.message}`] : [])),\n ]\n return {\n config: { path: ctx.configPath, created_this_start: ctx.configCreated, ...drift.fields },\n next_steps: nextSteps,\n hosts,\n cas,\n audit_log: ctx.fleet.audit.path,\n ...ctx.runtimeDir.fold(\n () => ({}),\n (d) => ({ runtime_dir: d }),\n ),\n ...ctx.knownHosts.fold(\n () => ({}),\n (k) => ({ known_hosts: k }),\n ),\n }\n}\n","import { readFileSync } from \"node:fs\"\n\nimport { type CommandResult } from \"@ssh-mcp/core\"\nimport { type Either, Left, type Map, Option, Right, Try } from \"functype\"\n\nimport { type Auditor, unfinishedFields } from \"./audit.js\"\nimport { type Fleet, type HostEntry } from \"./fleet.js\"\nimport { ALLOW } from \"./policy.js\"\nimport { type FleetRunner } from \"./runners.js\"\nimport { enrollLine, hostAddress } from \"./setup.js\"\n\nexport type CheckDeps = Readonly<{\n fleet: Fleet\n configPath: string\n runner: Pick<FleetRunner, \"exec\">\n auditor: Auditor\n // CAs whose key_file is missing: their hosts fail without a connection attempt.\n missingKeyCas: ReadonlyArray<string>\n caPublicKeys: Map<string, string>\n out: (line: string) => void\n}>\n\n// check never creates a config: a missing one is reported, not written.\nexport const readCheckConfig = (path: string): Either<string, string> =>\n Try(() => readFileSync(path, \"utf8\")).fold(\n (e) =>\n (e as NodeJS.ErrnoException).code === \"ENOENT\"\n ? Left(\n `No fleet config at ${path}. Start the server once to create one at the default path, or pass --config=.`,\n )\n : Left(`Cannot read fleet config at ${path}: ${(e as Error).message}`),\n (raw) => Right(raw),\n )\n\n// Its own runtime dir, so a running server's hub.lock doesn't block a check.\nexport const checkRuntimeDir = (runtimeDir: string): string => `${runtimeDir}-check`\n\ntype Outcome = Readonly<{ ok: boolean; detail: string }>\n\nconst outcomeOf = (r: CommandResult, startedAt: number): Outcome =>\n r.code === 0\n ? { ok: true, detail: `${Date.now() - startedAt} ms` }\n : { ok: false, detail: r.stderr.trim() || `exit ${r.code}` }\n\n// Runs `true` on every host as the operator and prints one line per host as each finishes. Policy is\n// not evaluated: this is a terminal probe, not an agent call, and every probe is audited with\n// bypass = \"policy\". Resolves to the process exit code.\nexport const runCheck = async (deps: CheckDeps): Promise<number> => {\n const { fleet, out } = deps\n if (fleet.hosts.length === 0) {\n out(`No hosts configured; add a [[hosts]] entry to ${deps.configPath}.`)\n return 0\n }\n const width = Math.max(...fleet.hosts.map((h) => h.name.length))\n const addrWidth = Math.max(...fleet.hosts.map((h) => hostAddress(h).length))\n const line = (h: HostEntry, o: Outcome): string =>\n [\n (o.ok ? \"ok\" : \"FAIL\").padEnd(5),\n h.name.padEnd(width),\n hostAddress(h).padEnd(addrWidth),\n h.authKind.padEnd(12),\n o.detail,\n ].join(\" \")\n\n const probe = async (h: HostEntry): Promise<Outcome> => {\n if (h.ca.exists((name) => deps.missingKeyCas.includes(name)))\n return { ok: false, detail: \"key_file missing: the server creates it on first start\" }\n const startedAt = Date.now()\n return deps.auditor\n .audited(\n { agent: \"operator\", tool: \"check\", host: Option(h.name), fields: { bypass: \"policy\" }, verdict: ALLOW },\n () => deps.runner.exec(h.name, \"true\", \"operator\"),\n (r) => ({ ...unfinishedFields(r), code: r.code }),\n )\n .then(\n (r) => outcomeOf(r, startedAt),\n (e: unknown) => ({ ok: false, detail: (e as Error).message }),\n )\n }\n\n const outcomes = await Promise.all(\n fleet.hosts.map((h) =>\n probe(h).then((o) => {\n out(line(h, o))\n return o\n }),\n ),\n )\n fleet.cas.forEach((c) =>\n deps.caPublicKeys.get(c.name).forEach((pub) => out(`CA ${c.name}: ${enrollLine(c.principal, pub)}`)),\n )\n return outcomes.every((o) => o.ok) ? 0 : 1\n}\n","import { type CommandResult, execOutput, tmuxKeys, tmuxList, tmuxRead, tmuxSend } from \"@ssh-mcp/core\"\nimport { type Either, Option } from \"functype\"\n\nimport { AGENT_UNKNOWN, type AuditCall, type AuditFields, type Auditor, excerpt, unfinishedFields } from \"./audit.js\"\nimport { ALLOW, evaluate, type PolicyRequest, type Verdict } from \"./policy.js\"\nimport { type FleetRunner, hostRows } from \"./runners.js\"\nimport { type SetupContext, setupReport } from \"./setup.js\"\nimport { resultError, routedError, timeoutError } from \"./tool-errors.js\"\n\n/** @interop a thrown UserError sets the message, category and suggestions the client sees; any other throw gets a label somamcp guesses from its message. */\nconst unwrap = <T>(result: Either<string, T>): T =>\n result.fold(\n (msg) => {\n throw resultError(msg)\n },\n (value) => value,\n )\n\nconst bytes = (s: string): number => Buffer.byteLength(s, \"utf8\")\nconst noSummary = (): AuditFields => ({})\n\n// Tool handlers, factored out so they can be unit-tested with a fake FleetRunner.\n// Every call is checked against its host's policy and goes through the auditor: a decision line\n// before it runs, a result line after. A denied call writes its decision line and never runs.\nexport const makeHandlers = (runner: FleetRunner, auditor: Auditor, setup: SetupContext) => {\n const call = (tool: string, host: Option<string>, verdict: Verdict, fields: AuditFields = {}): AuditCall => ({\n agent: AGENT_UNKNOWN,\n tool,\n host,\n fields,\n verdict,\n })\n\n // A host with no policy (an unknown host) gets ALLOW, so it reaches the runner and fails there.\n const gated = (host: string, req: PolicyRequest, fields: AuditFields = {}): AuditCall =>\n call(\n req.tool,\n Option(host),\n runner.policyFor(host).fold(\n () => ALLOW,\n (policy) => evaluate(policy, req),\n ),\n fields,\n )\n\n // Sizes only, unless the host opts in to an output excerpt with log_output.\n const summarizeExec =\n (host: string) =>\n (r: CommandResult): AuditFields => ({\n ...unfinishedFields(r),\n code: r.code,\n ...(r.signal === undefined ? {} : { signal: r.signal }),\n ...(r.timedOutMs === undefined ? {} : { timed_out_ms: r.timedOutMs }),\n ...(r.noExitStatus === true ? { no_exit_status: true } : {}),\n stdout_bytes: bytes(r.stdout),\n stderr_bytes: bytes(r.stderr),\n ...(runner.logOutputFor(host) ? { stdout_excerpt: excerpt(r.stdout), stderr_excerpt: excerpt(r.stderr) } : {}),\n })\n\n return {\n hostsList: (): Promise<string> =>\n auditor.audited(\n call(\"hosts_list\", Option.none<string>(), ALLOW),\n () => Promise.resolve(JSON.stringify(hostRows(runner))),\n noSummary,\n ),\n fleetSetup: (): Promise<string> =>\n auditor.audited(\n call(\"fleet_setup\", Option.none<string>(), ALLOW),\n () => Promise.resolve(JSON.stringify(setupReport(setup, runner))),\n noSummary,\n ),\n /** @interop a thrown UserError sets the message, category and suggestions the client sees; any other throw gets a label somamcp guesses from its message. */\n exec: async ({\n host,\n command,\n timeout_ms,\n }: {\n host: string\n command: string\n timeout_ms?: number\n }): Promise<string> => {\n const r = await auditor\n .audited(\n gated(\n host,\n { tool: \"exec\", agent: AGENT_UNKNOWN, command },\n { command, ...(timeout_ms === undefined ? {} : { timeout_ms }) },\n ),\n () => runner.exec(host, command, AGENT_UNKNOWN, timeout_ms),\n summarizeExec(host),\n )\n .catch(routedError(host))\n return execOutput(r).fold(\n (failure) => {\n throw r.timedOutMs === undefined ? resultError(`[${host}] ${failure}`) : timeoutError(host, failure)\n },\n (output) => output,\n )\n },\n tmuxList: ({ host }: { host: string }): Promise<string> =>\n auditor\n .audited(\n gated(host, { tool: \"tmux_list\", agent: AGENT_UNKNOWN }),\n async () => JSON.stringify(unwrap(await tmuxList(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN))))),\n noSummary,\n )\n .catch(routedError(host)),\n tmuxSend: ({\n host,\n session,\n input,\n submit,\n }: {\n host: string\n session?: string\n input: string\n submit?: boolean\n }): Promise<string> => {\n const target = session ?? runner.sessionFor(host)\n const press = submit ?? true\n return auditor\n .audited(\n gated(host, { tool: \"tmux_send\", agent: AGENT_UNKNOWN }, { session: target, input, submit: press }),\n async () => {\n unwrap(\n await tmuxSend(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN)), {\n session: target,\n input,\n submit: press,\n }),\n )\n return `[${host}] sent to tmux session \"${target}\".`\n },\n noSummary,\n )\n .catch(routedError(host))\n },\n tmuxRead: ({ host, session, lines }: { host: string; session?: string; lines?: number }): Promise<string> => {\n const target = session ?? runner.sessionFor(host)\n const count = lines ?? 200\n return auditor\n .audited(\n gated(host, { tool: \"tmux_read\", agent: AGENT_UNKNOWN }, { session: target, lines: count }),\n async () =>\n unwrap(\n await tmuxRead(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN)), { session: target, lines: count }),\n ),\n (transcript) => ({ stdout_bytes: bytes(transcript) }),\n )\n .catch(routedError(host))\n },\n tmuxKeys: ({ host, session, keys }: { host: string; session?: string; keys: string[] }): Promise<string> => {\n const target = session ?? runner.sessionFor(host)\n return auditor\n .audited(\n gated(host, { tool: \"tmux_keys\", agent: AGENT_UNKNOWN }, { session: target, keys }),\n async () => {\n unwrap(await tmuxKeys(unwrap(runner.tmuxRunnerFor(host, AGENT_UNKNOWN)), { session: target, keys }))\n return `[${host}] sent keys [${keys.join(\", \")}] to tmux session \"${target}\".`\n },\n noSummary,\n )\n .catch(routedError(host))\n },\n }\n}\n","#!/usr/bin/env node\n\nimport { randomUUID } from \"node:crypto\"\nimport { mkdirSync, readFileSync } from \"node:fs\"\nimport { createRequire } from \"node:module\"\nimport { homedir } from \"node:os\"\nimport { dirname } from \"node:path\"\n\nimport { checkOpenSsh, nodeProcessRunner, socketPathFor, startCaCustody } from \"@ssh-mcp/core\"\nimport { Either, Left, Map, Option, Right, Try } from \"functype\"\nimport { Path } from \"functype-os\"\nimport { createServer } from \"somamcp/node\"\nimport { z } from \"zod\"\n\nimport { CLIENT_LABEL_ENV, clientLabel, createAuditor, openAuditLog, reportedClient } from \"./audit.js\"\nimport { buildInfo } from \"./build-info.js\"\nimport { checkRuntimeDir, readCheckConfig, runCheck } from \"./check.js\"\nimport { type Fleet, fleetWarnings, parseFleet } from \"./fleet.js\"\nimport { makeHandlers } from \"./handlers.js\"\nimport {\n buildFleetRunner,\n caKeySource,\n type CaWiring,\n defaultKnownHosts,\n defaultRuntimeDir,\n type FleetRunner,\n selfTestCas,\n} from \"./runners.js\"\nimport { ensureCaKeyFiles, ensureConfig, missingKeyFile, resolveConfigPath, type SetupContext } from \"./setup.js\"\n\nconst { version: pkgVersion } = createRequire(import.meta.url)(\"../package.json\") as {\n version: `${number}.${number}.${number}`\n}\n\nconst fatal = (msg: string): never => {\n console.error(msg)\n return process.exit(1)\n}\n\nconst expanded = (path: string): string =>\n Path.expand(path).fold(\n (e) => fatal(`invalid path ${path}: ${e.message}`),\n (p) => p,\n )\n\ntype CaStart = Readonly<{ wiring: Option<CaWiring>; created: ReadonlyArray<string> }>\n\n// When any [[cas]] entry exists (with or without hosts using it yet): check OpenSSH, create any missing\n// key_file, prepare the runtime dir and known_hosts, and load every CA key into a private ssh-agent.\n// Each failure stops startup. `check` creates nothing and uses its own runtime dir.\nconst startCa = async (fleet: Fleet, mode: \"serve\" | \"check\"): Promise<CaStart> => {\n if (fleet.cas.length === 0) return { wiring: Option.none<CaWiring>(), created: [] }\n ;(await checkOpenSsh(nodeProcessRunner)).fold(fatal, () => undefined)\n const created =\n mode === \"serve\" ? (await ensureCaKeyFiles(fleet.cas, nodeProcessRunner)).fold(fatal, (names) => names) : []\n fleet.cas\n .filter((c) => created.includes(c.name))\n .forEach((c) =>\n console.error(\n `created CA \"${c.name}\" key at ${c.key.kind === \"file\" ? c.key.path : \"\"}; add its line to each host (see fleet_setup)`,\n ),\n )\n const configured = fleet.runtimeDir.map(expanded).orElse(defaultRuntimeDir())\n const runtimeDir = mode === \"check\" ? checkRuntimeDir(configured) : configured\n const knownHosts = fleet.knownHosts.map(expanded).orElse(defaultKnownHosts())\n // Socket paths are fixed-length below runtimeDir, so one check covers every (agent, host).\n socketPathFor(runtimeDir, \"unknown\", \"host\").fold(fatal, () => undefined)\n mkdirSync(dirname(knownHosts), { recursive: true, mode: 0o700 })\n const custody = (\n await startCaCustody(\n runtimeDir,\n fleet.cas.map((c) => caKeySource(c)),\n nodeProcessRunner,\n )\n ).fold(fatal, (c) => c)\n // Mint and discard one certificate per CA: a CA that can't sign stops startup now, not every call.\n const selfTest = await selfTestCas(custody, fleet.cas, nodeProcessRunner)\n if (selfTest.isLeft()) {\n await custody.shutdown()\n fatal(selfTest.value)\n }\n return { wiring: Option<CaWiring>({ custody, proc: nodeProcessRunner, knownHosts }), created }\n}\n\n// Never leave a CA agent holding the key behind a failed start.\nconst buildRunnerOrExit = async (fleet: Fleet, ca: Option<CaWiring>): Promise<FleetRunner> =>\n (await buildFleetRunner(fleet, undefined, ca)).fold(\n async (msg) => {\n console.error(msg)\n await ca.fold(\n () => Promise.resolve(),\n (wiring) => wiring.custody.shutdown(),\n )\n return process.exit(1)\n },\n (r) => Promise.resolve(r),\n )\n\nconst caPublicKeysOf = (fleet: Fleet, ca: Option<CaWiring>): Map<string, string> =>\n ca.fold(\n () => Map.of<string, string>(),\n (wiring) =>\n Map.of(\n ...fleet.cas.flatMap((c): Array<[string, string]> =>\n wiring.custody\n .pubPath(c.name)\n .flatMap((path) => Try(() => readFileSync(path, \"utf8\")).toOption())\n .fold(\n () => [],\n (pub) => [[c.name, pub.trim()]],\n ),\n ),\n ),\n )\n\nconst parseOrExit = (raw: string): Fleet => {\n const fleet = parseFleet(raw).fold(fatal, (f) => f)\n fleetWarnings(fleet).forEach((w) => console.error(`warning: ${w}`))\n return fleet\n}\n\n// `ssh-fleet-mcp-server check`: connect to every host from a terminal and print one line each.\nasync function checkMain(): Promise<void> {\n const { path } = resolveConfigPath(process.argv.slice(2), process.env, homedir())\n const fleet = parseOrExit(readCheckConfig(path).fold(fatal, (raw) => raw))\n const sink = openAuditLog(fleet.audit.path).fold(fatal, (s) => s)\n const missing = await Either.traverseAsync(fleet.cas, async (c) =>\n (await missingKeyFile(c)).map((m) =>\n m.fold<string[]>(\n () => [],\n () => [c.name],\n ),\n ),\n )\n const missingKeyCas = missing.fold(fatal, (names) => names.flat())\n // Probe only what can start: a CA with no key_file, and its hosts, are reported without a connection.\n const probeFleet: Fleet = {\n ...fleet,\n cas: fleet.cas.filter((c) => !missingKeyCas.includes(c.name)),\n hosts: fleet.hosts.filter((h) => !h.ca.exists((name) => missingKeyCas.includes(name))),\n }\n const { wiring: ca } = await startCa(probeFleet, \"check\")\n const runner = await buildRunnerOrExit(probeFleet, ca)\n const code = await runCheck({\n fleet,\n configPath: path,\n runner,\n auditor: createAuditor({ sink, now: () => new Date(), newId: randomUUID }),\n missingKeyCas,\n caPublicKeys: caPublicKeysOf(probeFleet, ca),\n out: (line) => console.log(line),\n })\n await runner.shutdown()\n process.exit(code)\n}\n\nasync function main(): Promise<void> {\n const config = ensureConfig(resolveConfigPath(process.argv.slice(2), process.env, homedir())).fold(fatal, (c) => c)\n if (config.created) console.error(`created fleet config at ${config.path}`)\n const configPath = config.path\n const raw = Try(() => readFileSync(configPath, \"utf8\")).fold(\n (e) => {\n console.error(`Cannot read fleet config at ${configPath}: ${(e as Error).message}`)\n return process.exit(1)\n },\n (content) => content,\n )\n\n const fleet = parseOrExit(raw)\n\n // Fail fast: no call may run unaudited, so an unwritable audit log stops startup.\n const sink = openAuditLog(fleet.audit.path).fold(\n (msg) => {\n console.error(msg)\n process.exit(1)\n },\n (s) => s,\n )\n\n const { wiring: ca, created: caCreated } = await startCa(fleet, \"serve\")\n const runner = await buildRunnerOrExit(fleet, ca)\n fleet.hosts.forEach((host) => runner.lastError(host.name).forEach((e) => console.error(`warning: ${e.message}`)))\n\n const setup: SetupContext = {\n configPath,\n configCreated: config.created,\n fleet,\n loadedConfig: raw,\n readConfig: () =>\n Try(() => readFileSync(configPath, \"utf8\")).fold(\n (e) => Left<string, string>(`Cannot read ${configPath}: ${e.message}`),\n (text) => Right<string, string>(text),\n ),\n caPublicKeys: caPublicKeysOf(fleet, ca),\n caCreated,\n runtimeDir: ca.map((wiring) => wiring.custody.runtimeDir),\n knownHosts: ca.map((wiring) => wiring.knownHosts),\n }\n const label = clientLabel(process.env[CLIENT_LABEL_ENV])\n // The client is known per call, so each call gets handlers whose auditor records it.\n const handlers = (context: Readonly<{ client: Parameters<typeof reportedClient>[0] }>) =>\n makeHandlers(\n runner,\n createAuditor({\n sink,\n now: () => new Date(),\n newId: randomUUID,\n client: label.or(reportedClient(context.client)).orUndefined(),\n }),\n setup,\n )\n const server = createServer({\n name: \"ssh-fleet-mcp-server\",\n version: pkgVersion,\n build: buildInfo(),\n instructions:\n \"Manage a fleet of SSH hosts: pooled connections, host-routed exec and tmux tools. \" +\n \"Call fleet_setup first if hosts_list is empty or a host fails to connect; it says what to configure next.\",\n })\n\n const hostParam = z.string().describe(\"Configured host name (see hosts_list)\")\n\n server.addTool({\n name: \"hosts_list\",\n description:\n \"List configured fleet hosts and their live connection status. If a host you expect is missing, call fleet_setup: the config may have changed since the server started.\",\n parameters: z.object({}),\n execute: (_args, context) => handlers(context).hostsList(),\n })\n server.addTool({\n name: \"fleet_setup\",\n description:\n \"Show setup state: config path, each host's connection state and last error, each CA's public key and the line to add on hosts, and what to do next. Read-only.\",\n parameters: z.object({}),\n execute: (_args, context) => handlers(context).fleetSetup(),\n })\n server.addTool({\n name: \"exec\",\n description: \"Run a one-shot shell command on a fleet host over SSH.\",\n parameters: z.object({\n host: hostParam,\n command: z.string().min(1).describe(\"Shell command\"),\n timeout_ms: z\n .number()\n .int()\n .positive()\n .optional()\n .describe(\n `How long to wait, in ms. Default ${fleet.pool.execTimeoutMs}; at most ${fleet.pool.maxExecTimeoutMs}, and a larger value is lowered to it. On timeout the call fails with the output so far, and the command may still be running. For longer jobs, run them in tmux (tmux_send, then tmux_read).`,\n ),\n }),\n execute: ({ host, command, timeout_ms }, context) => handlers(context).exec({ host, command, timeout_ms }),\n })\n server.addTool({\n name: \"tmux_list\",\n description: \"List tmux sessions on a fleet host. Returns a JSON array of session names.\",\n parameters: z.object({ host: hostParam }),\n execute: ({ host }, context) => handlers(context).tmuxList({ host }),\n })\n server.addTool({\n name: \"tmux_send\",\n description: \"Type text into a persistent tmux session on a fleet host (creates it if absent).\",\n parameters: z.object({\n host: hostParam,\n session: z.string().optional().describe(\"tmux session (defaults to the host's configured session)\"),\n input: z.string().min(1).describe(\"Text to type\"),\n submit: z.boolean().optional().describe(\"Press Enter after the text (default true)\"),\n }),\n execute: ({ host, session, input, submit }, context) =>\n handlers(context).tmuxSend({ host, session, input, submit }),\n })\n server.addTool({\n name: \"tmux_read\",\n description: \"Capture the recent pane transcript of a tmux session on a fleet host.\",\n parameters: z.object({\n host: hostParam,\n session: z.string().optional().describe(\"tmux session (defaults to the host's configured session)\"),\n lines: z.number().int().min(1).optional().describe(\"Lines of scrollback (default 200; capped at 2000)\"),\n }),\n execute: ({ host, session, lines }, context) => handlers(context).tmuxRead({ host, session, lines }),\n })\n server.addTool({\n name: \"tmux_keys\",\n description: \"Send control/special keys (e.g. C-c) to a tmux session on a fleet host.\",\n parameters: z.object({\n host: hostParam,\n session: z.string().optional().describe(\"tmux session (defaults to the host's configured session)\"),\n keys: z.array(z.string()).min(1).describe(\"tmux key names, e.g. ['C-c']\"),\n }),\n execute: ({ host, session, keys }, context) => handlers(context).tmuxKeys({ host, session, keys }),\n })\n\n await server.start({ transportType: \"stdio\" })\n console.error(\n `SSH Fleet MCP Server running on stdio (${runner.hostNames.length} hosts); audit log: ${fleet.audit.path}`,\n )\n\n const shutdown = async () => {\n await runner.shutdown()\n await server.stop()\n process.exit(0)\n }\n process.on(\"SIGINT\", () => void shutdown())\n process.on(\"SIGTERM\", () => void shutdown())\n}\n\nconst entry = process.argv[2] === \"check\" ? checkMain : main\nentry().catch((error) => {\n console.error(\"Fatal error in main():\", error)\n process.exit(1)\n})\n"],"mappings":"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"}