UNPKG

@vigilbase/cli

Version:

Vigilbase CLI — security scanners, agent tools, and Vigilbase One from the terminal.

309 lines (308 loc) • 12.8 kB
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[];