/**
 * Shared SSH exec primitive — the single hardened choke point for running a
 * command on a remote host over the system `ssh`.
 *
 * `agents run --device` dispatch and the browser driver both go through here so the
 * connection hardening (`BatchMode`, `accept-new`, `ConnectTimeout`) and the
 * target-injection guard live in exactly one place. Target validation is the
 * canonical definition; `commands/secrets.ts` re-exports it.
 */
/**
 * SSH target: a bare ssh-config host alias (e.g. `yosemite-s0`) or `user@host`.
 * The strict allowlist blocks shell metacharacters so a target can't be
 * smuggled in as part of a remote command, and `sshExec` additionally rejects a
 * leading `-` so it can never be parsed as an ssh argv flag.
 */
/**
 * ssh's own connection-layer failure code — the transport dropped or never came
 * up, as opposed to the remote command choosing an exit status. Defined here,
 * in the layer that owns the ssh invocation, and re-exported by
 * `lib/hosts/reconnect.ts` as `SSH_CONN_FAILURE` so there is one constant for
 * the concept rather than two that can drift apart.
 */
export declare const SSH_CONN_FAILURE_CODE = 255;
export declare const SSH_TARGET_RE: RegExp;
export declare function assertValidSshTarget(host: string): void;
/** POSIX single-quote a string for safe interpolation into a remote shell command. */
export declare function shellQuote(s: string): string;
/**
 * Hardened ssh options applied to every connection — the single baseline every
 * `ssh` in the codebase composes from (directly here, or as `[...SSH_OPTS, …]`
 * in the few callers that need extra flags like `-L`/`-N`/`ProxyCommand`).
 *
 * `ServerAliveInterval`/`ServerAliveCountMax` add in-connection keepalive: a
 * silently-dropped link (laptop sleeps, Wi-Fi flips) is detected and the ssh
 * process exits within ~45s instead of hanging forever — so a followed run or a
 * long-lived `-N` tunnel can't leave a zombie ssh + socket pinned on the laptop.
 */
export declare const SSH_OPTS: readonly string[];
/**
 * Hard ceiling on the stdout one peer may return before its capture is aborted.
 * A cross-machine fan-out streams every peer's `agents … --json` into memory in
 * parallel, so an unbounded buffer means one runaway peer (a corrupt or
 * pathologically large payload) can exhaust the caller's heap and take the whole
 * sweep down with it — RUSH-2065 observed ~170MB retained across a single
 * `--active` gather. 16 MiB is far above any legitimate JSON listing yet small
 * enough that N peers in flight stay bounded. A peer that overflows is treated as
 * unreachable rather than trusted with partial output.
 */
export declare const REMOTE_STDOUT_MAX_BYTES: number;
/**
 * Accumulate SSH stdout as UTF-8 without corrupting a multi-byte code point that
 * a chunk boundary splits — the streaming `on('data')` callbacks hand us raw
 * `Buffer`s, and a naive `buf.toString()` per chunk mangles any character the
 * kernel cut in half. `StringDecoder` holds the trailing partial bytes until the
 * next chunk completes them.
 */
export declare class RemoteUtf8Accumulator {
    private readonly decoder;
    private value;
    write(chunk: Buffer): void;
    end(): string;
    current(): string;
}
/**
 * How long OpenSSH keeps a multiplex master alive after its last client exits
 * (`ControlPersist`, in seconds).
 *
 * The master only survives while it stays IDLE under this window, so it helps
 * exactly a *repeated* touch of the same host that arrives within the window; a
 * touch that arrives after the master has expired pays the full cold TCP+auth
 * handshake again (~100-300ms direct, up to ~500ms relayed; measured on the live
 * fleet 2026-08-10, PHNX-2582).
 *
 * The repeated same-host touches are the ad-hoc `--device` and fleet fan-out
 * calls — `sessions --active`, `fleet ping`/`status`, `doctor`, `teams`, a
 * `--device <box>` command run a few times while working — which arrive in
 * BURSTS spread over minutes, not on a fixed clock. (The one truly high-frequency
 * caller, `followHostTask`'s ~1.5s follow poll, was already warm even at 60s and
 * is not the target here.) At the old 60s window any two touches of the same box
 * more than a minute apart were both cold, so a burst paid a fresh handshake
 * almost every time.
 *
 * 10 minutes keeps a multi-minute burst warm (3-5ms per reuse) while still
 * bounding how long an idle master lingers — an important limit, because a reused
 * master to a box that has since slept costs a ~45s ServerAlive teardown on the
 * first touch (`ServerAliveInterval=15 × ServerAliveCountMax=3`), and a wider
 * window would only widen the chance of hitting that. The daemon's periodic
 * SSH-touching services (`usage-sync`/`auth-sync`) tick every 15 minutes, longer
 * than this window ON PURPOSE: warming them would need a wider window for a
 * handshake that is negligible at that cadence. Fan-outs stay bounded regardless
 * by their own per-peer `timeoutMs`.
 */
export declare const SSH_CONTROL_PERSIST_SECONDS: number;
export declare function controlOpts(): string[];
/**
 * Compose an ssh connection-option prefix.
 *
 * `hostKeyOpts` (a caller's host-key posture, e.g. a pinned
 * `StrictHostKeyChecking=yes` + managed `UserKnownHostsFile`) go FIRST, ahead of
 * the `SSH_OPTS` baseline: ssh honors the *first* value it sees for each option,
 * so an override placed after the baseline's `accept-new` would be silently
 * ignored. Pure so the ordering contract is unit-testable. See RUSH-1767.
 */
export declare function sshConnectOpts(mux: string[], hostKeyOpts?: string[]): string[];
export interface SshExecOptions {
    /** Piped to the remote command's stdin (never interpolated into the shell). */
    input?: string;
    /** Kill the ssh process after this many ms. */
    timeoutMs?: number;
    /** Extra ssh flags inserted before the target (e.g. `-tt`). */
    extraSshArgs?: string[];
    /** Reuse a persistent control socket across calls (default true; see `controlOpts`). */
    multiplex?: boolean;
    /**
     * Host-key `-o` options that OVERRIDE the accept-new baseline — prepended so
     * ssh's first-value-wins rule takes them (see {@link sshConnectOpts}). Used to
     * force strict verification against the managed known_hosts store on the
     * credential-copy path (RUSH-1767).
     */
    hostKeyOpts?: string[];
}
export interface SshExecResult {
    /** Remote exit status, or null if ssh itself failed / timed out. */
    code: number | null;
    stdout: string;
    stderr: string;
    timedOut: boolean;
}
/** Grace after a timed-out child receives SIGTERM before SIGKILL enforces the bound. */
export declare const SSH_TIMEOUT_KILL_GRACE_MS = 250;
/**
 * Run `remoteCmd` on `target` over ssh and capture stdout/stderr/exit.
 *
 * `remoteCmd` is passed as a single argv to ssh (the remote login shell parses
 * it); callers that build it from user input must `shellQuote` the pieces.
 */
export declare function sshExec(target: string, remoteCmd: string, opts?: SshExecOptions): SshExecResult;
/**
 * Async variant of {@link sshExec}. Same hardened argv composition, but uses
 * child_process.spawn so fleet fan-outs can probe multiple hosts concurrently.
 *
 * A timeout-bearing call uses a fresh ssh connection (`multiplex: false`) even
 * when the caller requests multiplexing: a control-master outlives the local
 * client, so killing the local ssh process on timeout would leave the remote
 * command running. With a direct connection, terminating the local child tears
 * down the remote side (RUSH-2114).
 */
export declare function sshExecAsync(target: string, remoteCmd: string, opts?: SshExecOptions): Promise<SshExecResult>;
export interface SshExecRawResult {
    code: number | null;
    stdout: Buffer;
    stderr: Buffer;
    timedOut: boolean;
}
/**
 * Like {@link sshExec} but returns raw stdout/stderr Buffers — no UTF-8 decode.
 *
 * Use when byte-exactness matters, e.g. offset-tracked log tailing: a multibyte
 * character split across a read boundary must stay raw bytes, not collapse to a
 * U+FFFD replacement char (which would desync a byte offset from the wire).
 */
export declare function sshExecRaw(target: string, remoteCmd: string, opts?: SshExecOptions): SshExecRawResult;
export interface SshExecRawStreamOptions extends SshExecOptions {
    onStdout: (chunk: Buffer) => void;
    onStderr?: (chunk: Buffer) => void;
    signal?: AbortSignal;
}
/**
 * Stream raw stdout from a long-lived remote command over ssh.
 *
 * Unlike {@link sshStream}, this does not inherit local stdio: callers receive
 * byte-exact stdout chunks and decide how to account for them. That matters for
 * offset-resumed log following where a UTF-8 decode boundary must not shift the
 * byte cursor.
 */
export declare function sshExecRawStream(target: string, remoteCmd: string, opts: SshExecRawStreamOptions): Promise<Omit<SshExecRawResult, 'stdout'>>;
/** True if `target` is reachable over ssh (a passwordless `true` succeeds quickly). */
export declare function sshReachable(target: string, timeoutMs?: number): boolean;
export interface SshStreamOptions {
    /**
     * Allocate a remote pseudo-terminal (`ssh -tt`) so an interactive remote
     * command (a picker, a prompt) renders live on the local terminal. Callers
     * pass this when the *local* process is itself a TTY; piped/scripted callers
     * leave it off and forward a non-interactive invocation instead.
     */
    tty?: boolean;
    /** Reuse a persistent control socket across calls (default true; see `controlOpts`). */
    multiplex?: boolean;
    /**
     * Host-key `-o` options that OVERRIDE the accept-new baseline — prepended so
     * ssh's first-value-wins rule takes them (see {@link sshConnectOpts}). Used to
     * force strict verification against the managed known_hosts store on the
     * interactive credential-copy path (RUSH-1767).
     */
    hostKeyOpts?: string[];
    /** Additional OpenSSH argv placed before the target (for example `-i <path>`). */
    extraSshArgs?: string[];
}
/**
 * The DEC private modes a full-screen remote app turns ON and, when it exits
 * cleanly, turns back off itself. A remote agent TUI killed by a dropped link
 * never gets to, so they stay enabled on the LOCAL terminal after ssh is gone:
 * focus reporting (1004) and the mode/colour-scheme reports (996/997) then make
 * the terminal emit answerback sequences at a shell that is not expecting them,
 * which is the `^[[?997;1n ^[[I ^[[O` litter seen after a drop (RUSH-3125). Also
 * resets bracketed paste (2004), the alternate screen (1049), mouse tracking
 * (1000/1002/1003/1006), and re-shows the cursor (25) — every one of which a TUI
 * leaves armed the same way.
 */
export declare const TERMINAL_MODE_RESET: string;
/**
 * Put the local terminal back the way ssh found it.
 *
 * Two independent kinds of state survive a hard ssh death, and BOTH have to be
 * undone or the next thing the user sees is corrupted:
 *
 *  - **termios** (kernel side): `-tt` puts the tty in raw mode, so a killed ssh
 *    leaves it with echo and line-editing off. `saved` is the `stty -g` snapshot
 *    taken before the spawn; restoring it is exact, unlike guessing at `sane`.
 *  - **DEC private modes** (terminal side): see {@link TERMINAL_MODE_RESET}.
 *
 * Then drain whatever the terminal already answered back into the input buffer.
 * Without the drain those bytes are not merely cosmetic — they are still queued
 * on the tty, so the NEXT attach hands them straight to the agent as if the user
 * had typed them.
 *
 * Best-effort by construction: this runs while recovering from a failure, so a
 * missing `stty`, a non-TTY stdin, or a closed stream must never turn a
 * recoverable drop into a thrown error.
 */
export declare function restoreLocalTerminal(saved: string | undefined, opts: {
    drainStdin: boolean;
}): void;
/** `stty -g` snapshot of the local tty, or undefined when there is nothing to snapshot. */
export declare function saveLocalTerminal(): string | undefined;
/**
 * Foreground counterpart to `sshExec`: run `remoteCmd` on `target` with the
 * local stdio wired straight through (`stdio: 'inherit'`), so output streams as
 * it is produced and — with `tty` — keystrokes reach a remote picker. Blocks
 * until the remote command exits and returns its exit code (255 is ssh's own
 * connection-layer failure; any other non-zero is the remote command's code).
 *
 * On a `tty` stream the local terminal state is snapshotted before the spawn and
 * restored after it. ssh restores termios itself when it exits cleanly, so this
 * is for the case that matters here: the link dying under a full-screen agent
 * TUI, which leaves the tty raw and the TUI's DEC modes armed. Doing it HERE
 * rather than in the reconnect loop means every caller that opens an interactive
 * remote stream is covered, not just the one that noticed (RUSH-3125).
 *
 * **The stdin drain is gated on an ABNORMAL exit, and the distinction is not
 * cosmetic.** Resetting termios and the DEC modes is idempotent — on a clean
 * exit ssh and the remote TUI have already done it, so re-doing it changes
 * nothing. Draining stdin is destructive: it discards whatever the user has
 * typed ahead. On a clean exit those bytes are legitimate type-ahead the
 * resuming shell should receive, and eating them would be a new bug in every
 * caller of this function (`runInteractiveOnHost`, the remote-tmux attach in
 * commands/go.ts, the remote secrets browse) rather than a fix. Only an
 * abnormal exit — ssh killed by a signal (`status === null`) or its own
 * connection-layer failure (255) — produces the answerback storm the drain
 * exists to clear, because only then did the TUI die without sending its own
 * mode resets.
 */
export declare function sshStream(target: string, remoteCmd: string, opts?: SshStreamOptions): number;
