export type TtyInput = NodeJS.ReadableStream & {
    isTTY?: boolean;
    setRawMode?: (mode: boolean) => unknown;
};
export type TtyOutput = NodeJS.WritableStream & {
    isTTY?: boolean;
    columns?: number;
    rows?: number;
    on?(event: 'resize', listener: () => void): unknown;
    removeListener?(event: 'resize', listener: () => void): unknown;
};
export type TerminalFeatures = {
    /** ANSI colours and text styles; NO_COLOR and FORCE_COLOR=0 turn them all off. */
    color: boolean;
    /** ◆ ◇ │ …, or the ASCII fallback for legacy Windows consoles and the Linux console. */
    unicode: boolean;
    /** OSC 8 hyperlinks. */
    hyperlinks: boolean;
};
/**
 * The rich UI's features, or null for the plain line output: `--json`, stdin
 * or stderr not a terminal (pipes, files), TERM=dumb, or CI set.
 */
export declare function terminalFeatures(options: {
    mode: 'text' | 'json';
    stdin: TtyInput | undefined;
    stderr: TtyOutput | undefined;
    env: NodeJS.ProcessEnv;
    platform?: NodeJS.Platform;
}): TerminalFeatures | null;
/** Legacy Windows consoles (no Windows Terminal, no terminal program) and the Linux console get ASCII. */
export declare function supportsUnicode(env: NodeJS.ProcessEnv, platform?: NodeJS.Platform): boolean;
/** Terminals known to render OSC 8 links; FORCE_HYPERLINK overrides. Multiplexers are left out. */
export declare function supportsHyperlinks(env: NodeJS.ProcessEnv): boolean;
export type Symbols = {
    active: string;
    done: string;
    rail: string;
    end: string;
    warning: string;
    error: string;
    check: string;
    radioOn: string;
    radioOff: string;
    pointer: string;
    /** Text cut short, an activity in progress (`Checking…`) and a masked ID (`0123…cdef`). */
    ellipsis: string;
    /** Between the items of a summary line (`84 reads · 72 ok`); see `Tui.join`. */
    separator: string;
    mask: string;
    placeholder: string;
    barFull: string;
    barEmpty: string;
    up: string;
    down: string;
    spinner: readonly string[];
};
export declare const UNICODE_SYMBOLS: Symbols;
export declare const ASCII_SYMBOLS: Symbols;
export type Style = (text: string) => string;
export type Styles = Record<'bold' | 'dim' | 'underline' | 'inverse' | 'green' | 'yellow' | 'red' | 'cyan' | 'brand', Style>;
/** SGR styles; with colour off, every style is the identity (no escape sequences at all). */
export declare function createStyles(color: boolean): Styles;
export declare function stripAnsi(text: string): string;
/** Terminal columns of one character: 0 for marks and controls, 2 for CJK and emoji, else 1. */
export declare function charWidth(char: string): number;
/** Terminal columns of a line, ignoring escape sequences. */
export declare function displayWidth(text: string): number;
/**
 * At most `width` columns, ending in `…` when cut. Escape sequences are kept;
 * styles and an open OSC 8 link are closed after a cut.
 */
export declare function truncate(text: string, width: number, ellipsis?: string): string;
/**
 * Plain `text` (no escape sequences) in at most `width` columns, keeping its
 * start and its end around the ellipsis, so names that share a long prefix
 * (`Acme … Production`, `Acme … Staging`) still differ once cut.
 */
export declare function truncateMiddle(text: string, width: number, ellipsis?: string): string;
/** `text` padded with spaces to `width` columns. */
export declare function padEnd(text: string, width: number): string;
export type Key = {
    name: 'char';
    char: string;
} | {
    name: 'enter' | 'backspace' | 'delete' | 'up' | 'down' | 'left' | 'right' | 'home' | 'end' | 'tab' | 'escape' | 'ctrl-c' | 'ctrl-d' | 'ctrl-u' | 'ctrl-w' | 'other';
};
/** Keys in a chunk of raw-mode input; a paste arrives as many `char` keys. */
export declare function parseKeys(input: string): Key[];
/**
 * The lines at the bottom of the output that are redrawn in place: each
 * update moves the cursor back to the region's first line, clears to the end
 * of the screen and writes the new lines in a single write. Lines are cut to
 * the terminal width, so each takes one row; after a resize, the previous
 * lines are counted at the new width (terminals rewrap them).
 */
export declare class LiveRegion {
    private readonly out;
    private readonly ellipsis;
    private lines;
    constructor(out: TtyOutput, ellipsis?: string);
    /** The terminal width; 80 when it reports none (0 or undefined, as some pseudo-terminals do). */
    get columns(): number;
    get rows(): number;
    private erase;
    /** Lines that fit: one column short of the width (no pending wrap) and one row short of the height. */
    private fit;
    draw(lines: readonly string[]): void;
    /** Permanent lines above the region (never cut; long ones wrap), then the region again. */
    commit(lines: readonly string[]): void;
    clear(): void;
    get height(): number;
}
/** Where the session listens for SIGINT, SIGTERM and exit (the process; a plain emitter in tests). */
export type SignalSource = {
    on(event: string, listener: (...args: unknown[]) => void): unknown;
    removeListener(event: string, listener: (...args: unknown[]) => void): unknown;
    listenerCount(event: string): number;
    emit(event: string, ...args: unknown[]): boolean;
};
export type TuiOptions = {
    stdin: TtyInput;
    stderr: TtyOutput;
    features: TerminalFeatures;
    signals?: SignalSource;
    exit?: (code: number) => void;
    /** Spinner frame interval. */
    frameMs?: number;
};
/** A prompt: its frame, and what each key does (a value ends it). */
export type PromptView<T> = {
    render(): string[];
    key(key: Key): {
        value: T;
    } | undefined;
};
export type Spinner = {
    update(label: string): void;
    stop(): void;
};
export type SelectItem<T> = {
    label: string;
    detail?: string;
    value: T;
};
export declare class Tui {
    readonly symbols: Symbols;
    readonly style: Styles;
    readonly features: TerminalFeatures;
    /** What Ctrl-C (or SIGTERM) prints before the process exits. */
    cancelMessage: string;
    private readonly stdin;
    private readonly stderr;
    private readonly region;
    private readonly signals;
    private readonly exitProcess;
    private readonly frameMs;
    private readonly decoder;
    private frame;
    private animated;
    private timer;
    private tick;
    private keyHandler;
    private reading;
    private raw;
    private suspended;
    private closed;
    private interrupted;
    constructor(options: TuiOptions);
    /** True once the user cancelled (Ctrl-C or SIGTERM); nothing is drawn after that. */
    get isInterrupted(): boolean;
    get columns(): number;
    /** Permanent lines above the live frame. */
    print(...lines: string[]): void;
    /** `│` */
    rail(): void;
    /** `◇  title  detail` */
    step(title: string, detail?: string): void;
    /** `◇  title › value` (an answered question). */
    answered(title: string, value: string): void;
    /** `│  text`, wrapped at word boundaries (`text` is plain). */
    info(text: string): void;
    /** `▲  text` */
    warn(text: string): void;
    /** `■  text`, then dim detail lines under it. */
    error(text: string, ...details: string[]): void;
    /** `│  text` (dim): a note inside the current step, wrapped at word boundaries. */
    note(text: string): void;
    /**
     * A command the user may copy, indented and on a line of its own: never
     * wrapped, cut or prefixed (a long one is soft-wrapped by the terminal and
     * still copies as one line).
     */
    command(text: string): void;
    /** Dim lines under the end of the timeline (after `└`), indented and wrapped. */
    footnote(text: string): void;
    /** Plain text in lines that fit after an `indent`-column prefix (a longer word, such as a path, stays whole). */
    private wrap;
    /** The current spinner frame. */
    get spin(): string;
    /** `███░░░` for a ratio between 0 and 1. */
    bar(ratio: number, width: number): string;
    /** The items of a summary line: `a · b · c`, or `a - b - c` in ASCII. */
    join(parts: readonly string[]): string;
    /**
     * Text written elsewhere with the Unicode symbols (an error message with a
     * masked ID, a warning) in this session's: on an ASCII-only console, `…`
     * becomes `...` and ` · ` becomes ` - `.
     */
    symbolText(text: string): string;
    /** An OSC 8 link where the terminal supports it, else the text. The URL must be printable ASCII. */
    link(url: string, text?: string): string;
    /** The live frame, replacing the previous one; animated frames are redrawn every frame (spinners). */
    show(render: () => string[], options?: {
        animated?: boolean;
    }): void;
    clearFrame(): void;
    /** Redraw the live frame now (after its state changed). */
    draw(): void;
    /** `◒  label` until stopped. */
    spinner(label: string): Spinner;
    /** `work` with a spinner. */
    busy<T>(label: string, work: () => Promise<T>): Promise<T>;
    private syncTimer;
    /** Run a prompt until a key ends it; its frame is cleared afterwards. Ctrl-C never ends it (it cancels the run). */
    prompt<T>(view: PromptView<T>): Promise<T>;
    /** The fake cursor of inputs (the real one stays hidden). */
    private get cursor();
    private head;
    /** Columns an input's first line has for its title and value together (the frame's width less `◆  ` and ` › `). */
    private get headRoom();
    /** A plain title cut (start and end kept) to at most `width` columns, so the value after it stays on screen. */
    private fitTitle;
    private noticeLines;
    /**
     * A one-line text input: printable characters, Backspace, Ctrl-U (clear) and
     * Ctrl-W (last word). Null on Ctrl-D with no text (the input ended). On a
     * narrow terminal the (plain) title is cut before the value, down to a third
     * of the line; a value wider than the rest shows its end.
     */
    text(options: {
        title: string;
        notice?: string | null;
        maxLength?: number;
    }): Promise<string | null>;
    /**
     * A numeric code input: digits are shown as typed (spaces and dashes, typed
     * or pasted, are skipped) and the input ends at the last digit. Enter with
     * fewer digits keeps it open. Null on Ctrl-D (the input ended). On a narrow
     * terminal the (plain) title is cut, never the digits.
     */
    code(options: {
        title: string;
        length: number;
        hint?: string;
        notice?: string | null;
    }): Promise<string | null>;
    /**
     * An arrow-key list (↑/↓ or j/k, Home/End, Enter). Null on Ctrl-D. Labels
     * are plain text, padded to one column; on a narrow terminal each is cut in
     * the middle so its detail (a masked ID) always stays on screen.
     */
    select<T>(options: {
        title: string;
        hint?: string;
        items: ReadonlyArray<SelectItem<T>>;
        initial?: number;
    }): Promise<T | null>;
    /** Yes / No: ←/→ (or Tab, h/l) move, y or n answers at once, Enter answers the highlighted choice. Null on Ctrl-D. */
    confirm(options: {
        title: string;
        initial: boolean;
        detail?: string;
    }): Promise<boolean | null>;
    private readonly onData;
    /** Ctrl-C in raw mode sends no signal: raise SIGINT for every listener, this session's included. */
    interrupt(): void;
    private setRaw;
    private attachInput;
    private detachInput;
    /**
     * Hand the terminal to another program (cf's interactive sign-in): the
     * frame is cleared, raw mode is off, input is released and the cursor shown.
     */
    suspend(): void;
    resume(): void;
    /** `work` with the terminal handed over (see `suspend`). */
    handOver<T>(work: () => Promise<T>): Promise<T>;
    private readonly onResize;
    /** Raw mode off, the frame cleared, the cursor shown; nothing more is drawn. */
    private restore;
    private detachSignals;
    /** Print the cancel message; exit with `code` unless another listener for `signal` will (it exits 130 after cleaning up). */
    private cancel;
    private readonly onSigint;
    private readonly onSigterm;
    private readonly onExit;
    /** End the session: the terminal is restored and the listeners removed. Safe to call twice. */
    close(): void;
}
/** The terminal UI has shown this error (■ and its hint); `runCli` does not print it again. */
export declare function markRendered(error: object): void;
export declare function wasRendered(error: unknown): boolean;
/**
 * Words in lines of at most `width` columns; a longer word gets a line of its
 * own. Leading spaces (an indented line) are kept on every line.
 */
export declare function wrapWords(text: string, width: number): string[];
