import type { AgentId } from '../types.js';
import type { DeviceProfile } from '../devices/registry.js';
import { type AuthVerdict, type AuthHealth } from '../auth-health.js';
import { type LoginFlow } from './auth-sync.js';
/** A verification URL + user code scraped from a device-code login screen. */
export interface ScrapedLogin {
    url?: string;
    code?: string;
}
/**
 * Extract the verification URL and user code from an ANSI-stripped login screen
 * using the flow's regexes. Pure — no I/O, so it is the highest-value unit test
 * target (a wrong code boundary or a missed URL is exactly the bug class this
 * whole feature turns on). Group 1 is preferred over the whole match so a
 * surrounding phrase ("enter code XXXX-XXXX") never leaks into the captured
 * value. A flow with no regex for a field yields `undefined` for that field.
 */
export declare function scrapeLogin(screenText: string, flow: LoginFlow): ScrapedLogin;
/** Why a logged-out (agent, device) pair can or cannot be driven remotely. */
export interface FlowClassification {
    flow: LoginFlow | null;
    remotable: boolean;
    reason?: string;
}
/**
 * Decide whether `agent`'s login can be driven on a box of `platform`. Only a
 * `device-code` flow is remotable; loopback (browser + 127.0.0.1 on the box),
 * api-key, unknown, or a macOS keychain-bound token are surfaced with an honest
 * reason instead of a mis-drive. Pure.
 */
export declare function classifyLoginFlow(agent: AgentId | string, platform: string | undefined): FlowClassification;
/**
 * Does a cached auth verdict mean "this (host, agent) needs an interactive
 * login"? A missing row (never signed in / credential absent) and a `revoked`
 * token do; `live`, `unverified` (signed in, no probe), `expired`, and
 * `rate_limited` are treated as already-logged-in (expired self-heals on next
 * launch — never re-login on it). Pure.
 */
export declare function isPendingVerdict(verdict: AuthVerdict | undefined): boolean;
/** A logged-out (agent, device) pair `fleet login` will target or explain. */
export interface PendingLogin {
    device: string;
    agent: AgentId | string;
    platform?: string;
    /** ssh dial target for the device. */
    target: string;
    extraSshArgs?: string[];
    flow: LoginFlow;
    /** True when a device-code flow this command can actually drive. */
    remotable: boolean;
    /** Present when `!remotable` — why it must be done on the box itself. */
    reason?: string;
}
export interface SelectLoginOptions {
    /** Include pairs already logged-in (verdict live/unverified) — for a forced re-login. */
    includeLoggedIn?: boolean;
}
/**
 * From a device list, requested agents, and the auth-health cache, compute the
 * pending logins — partitioned (via {@link classifyLoginFlow}) into remotable
 * device-code flows and non-remotable pairs with a reason. Pure: the caller
 * supplies the already-loaded devices + cache, so this is fully unit-testable.
 *
 * A pair is pending when its cached verdict {@link isPendingVerdict} (or
 * `includeLoggedIn` forces it). Agents with no defined login flow are dropped
 * silently (nothing to log into).
 */
export declare function selectLoginTargets(devices: DeviceProfile[], agents: (AgentId | string)[], cache: Record<string, AuthHealth>, opts?: SelectLoginOptions): PendingLogin[];
/**
 * Build the `ssh -tt <target> <loginCommand>` command string driven through the
 * PTY sidecar. `-tt` forces a tty so the remote CLI enters its interactive
 * device-code flow; `accept-new` learns a first-seen host key without a prompt.
 * Both the target and the login command are shell-quoted. Pure/testable.
 *
 * NOTE: this uses key-based ssh auth (the tailnet fleet's norm). Password-bundle
 * devices need the askpass shim env, which the PTY env allowlist strips — those
 * are a follow-up (TODO), surfaced as an error rather than silently mis-driven.
 */
export declare function buildRemoteLoginSshCommand(target: string, flow: LoginFlow, extraSshArgs?: string[]): string;
export type LoginMode = 'bulk' | 'interactive';
/** Per-pair status shape shared between the driver, the status endpoint, and the page. */
export type LoginStatusState = 'pending' | 'driving' | 'ready' | 'authorized' | 'error' | 'skipped';
export interface LoginStatus {
    device: string;
    agent: string;
    remotable: boolean;
    reason?: string;
    state: LoginStatusState;
    url?: string;
    code?: string;
    /** epoch ms the scraped code expires (best-effort ~15 min TTL). */
    expiresAt?: number;
    detail?: string;
}
/**
 * Render the self-contained dashboard page. Inlines all CSS/JS; the page polls
 * `/api/status` and flips rows to their live state. `bulk` shows a grid of every
 * pending login at once; `interactive` shows a one-at-a-time wizard. Pure — a
 * string in, a string out — so it renders (and is asserted) without a server.
 * Terminal-coded per the agents-cli brand (#0a0a0a bg, #a3e635 lime), with a
 * light variant via `prefers-color-scheme`.
 */
export declare function buildDashboardHtml(pending: PendingLogin[], mode: LoginMode): string;
/** The subset of the PTY sidecar `driveRemoteLogin` needs — faked in tests. */
export interface PtyDriver {
    start(opts?: {
        rows?: number;
        cols?: number;
    }): Promise<string>;
    exec(id: string, command: string): Promise<void>;
    write(id: string, input: string): Promise<void>;
    screen(id: string): Promise<{
        screen: string;
        exited: boolean;
    }>;
    stop(id: string): Promise<void>;
}
/** Real driver over the pty sidecar (`ptyRequest`). */
export declare function defaultPtyDriver(): PtyDriver;
export interface DriveOptions {
    /** Wait after launching ssh before steering / scraping (default 4000ms). */
    initialDelayMs?: number;
    /** Poll cadence for the scrape loop (default 1000ms). */
    pollMs?: number;
    /** Overall scrape deadline (default 90000ms). */
    timeoutMs?: number;
}
export interface DriveResult extends ScrapedLogin {
    /** True when the underlying ssh session already exited (login finished or failed). */
    exited: boolean;
    /** The pty session id, so the caller can keep polling / stop it. */
    sessionId: string;
}
/**
 * Drive one remote device-code login: launch `ssh -tt <target> <loginCommand>`
 * in a PTY, send the flow's `deviceCodeSelect` keystrokes (if any) to reach the
 * device-code path, then poll the screen until {@link scrapeLogin} yields both a
 * URL and a code (or the deadline / session-exit). The human completes the code
 * in the browser; the caller polls the credential file for completion. Testable
 * against a fake {@link PtyDriver}.
 */
export declare function driveRemoteLogin(target: string, flow: LoginFlow, driver: PtyDriver, opts?: DriveOptions, extraSshArgs?: string[]): Promise<DriveResult>;
export interface DetectOptions {
    agents?: (AgentId | string)[];
    devices?: string[];
    includeLoggedIn?: boolean;
}
/**
 * Resolve the online fleet devices, then compute pending logins
 * from the shared auth-health cache (populated by `agents fleet ping`). Thin I/O
 * over the pure {@link selectLoginTargets}. Devices/agents can be narrowed by the
 * caller's flags; the default agent set is every agent with a defined flow.
 */
export declare function detectPending(opts?: DetectOptions): Promise<PendingLogin[]>;
/**
 * Best-effort epoch-seconds mtime of a home-relative file on a remote box, or 0
 * when it is absent/unreadable. Used to detect the credential file appearing /
 * bumping after the human completes the browser step. POSIX `stat` only (the
 * device-code agents are all POSIX-fleet).
 */
export declare function remoteFileMtime(target: string, homeRel: string, extraSshArgs?: string[]): Promise<number>;
export interface RunFleetLoginOptions {
    agents?: (AgentId | string)[];
    devices?: string[];
    interactive?: boolean;
    json?: boolean;
    /** Target every device-code pair regardless of cached login state (cold cache / forced re-login). */
    all?: boolean;
    /** Overrides for testing / non-default cadence. */
    driver?: PtyDriver;
    drive?: DriveOptions;
    /** Port for the local dashboard (0 = ephemeral). */
    port?: number;
    /** Open the browser (default true). */
    open?: boolean;
    /** Max wall-clock to wait for all logins before giving up (default 20 min). */
    overallTimeoutMs?: number;
    /** ~15 min device-code TTL used for the countdown. */
    codeTtlMs?: number;
}
/**
 * Orchestrate `agents fleet login`: detect pending -> serve the local dashboard
 * + open the browser -> drive each box's device-code flow (concurrent in bulk,
 * sequential just-in-time in interactive) -> poll the credential file for
 * completion -> print a final matrix. The pure pieces above carry the logic;
 * this wires them to the live fleet.
 */
export declare function runFleetLogin(opts?: RunFleetLoginOptions): Promise<LoginStatus[]>;
