/** * `live` — a pinned, self-repainting terminal region, the pure widgets that * animate inside it (`spin` / `bar` / `elapsed`), and the `spinner()` one-liner. * * The model: you own the state, the region owns the screen. A region is a * `render: () => string` repainted on a timer (plus `refresh()` for instant * updates). A frame is just a string — `table()` / `kv()` / `ui` compose * inside it unchanged, and widgets are plain string functions. * * The hard problem this solves is interleaving: while a region is active, * `console.log/warn/error` — and therefore the logger, which writes through * console — are rerouted to print ABOVE the region (erase → write → repaint). * Logging never tears the UI, and there is no new logging API to learn. * * Non-TTY (pipe / CI / log file): nothing animates, nothing repaints. * `done(final)` prints the final frame once; the opt-in `heartbeat` prints * plain snapshots for long CI silences. Same calling code either way. * * Crash-safe cursor: hidden while painting, restored on done/clear, process * exit, and fatal signals. Signals are handled politely — if the app has its * own SIGINT handler (graceful shutdown), we restore the cursor and stay out * of the way; if we are the only listener, default die-on-signal is preserved. * * Exactly one region can be active: two pinned regions can't share the bottom * of one screen. Compose everything into a single `render()` — that's the * point of frames being strings. */ export interface LiveOpts { /** Target stream. Default stderr — stdout stays clean for pipes / --json. */ stream?: NodeJS.WriteStream; /** Repaints per second while active (default 12.5 — the spinner's cadence). */ fps?: number; /** Non-TTY only: print a plain snapshot every N ms so long CI runs aren't silent. Default off. */ heartbeat?: number; } export interface Live { /** Repaint now (state changed and 80ms is too long to wait). */ refresh(): void; /** Swap the render function (e.g. a new phase of the same operation). */ update(render: () => string): void; /** Stop and persist a final frame (default: the current render) into scrollback. */ done(final?: string): void; /** Stop and remove the region entirely. */ clear(): void; } /** * Start a pinned region at the bottom of the terminal, repainting * `render()` until `done()` / `clear()`. Non-TTY → inert (see module doc). */ export declare function live(render: () => string, opts?: LiveOpts): Live; /** Spinner frame for "now" — call it inside `render()` and it animates. */ export declare const spin: () => string; /** * Progress bar, `width` cells; `style` colors the filled part (default green — * pass e.g. `ui.warn` for a throttled/cooling state). `done > total` clamps to * full — live counters drift (e.g. a server-count proxy that overcounts) and * that's the caller's data, not a bug here. */ export declare function bar(done: number, total: number, width?: number, style?: (s: string) => string): string; /** Compact elapsed time since a `Date.now()` timestamp: 0.4s · 12s · 1m05s · 1h02m. */ export declare function elapsed(since: number): string; /** * The one-liner: animate `label` with a spinner + elapsed time while `fn` * runs, persist `✓ label 1.2s` on success, `⨯ label 1.2s message` on failure * (and rethrow). `fn` receives a setter for relabeling mid-flight: * `spinner('connecting 0/4', async (set) => { …; set('connecting 2/4') })`. * * Non-TTY prints just the final line. Started while a live region is active * (a helper deep in a call stack, under a dashboard), it degrades the same * way — the final line lands ABOVE the region via the console routing. Only * a second live() region screams; a nested spinner is legitimate composition. */ export declare function spinner(label: string, fn: (set: (label: string) => void) => Promise): Promise; //# sourceMappingURL=live.d.ts.map