import type { CheckboxField } from '@orkestrel/form'; import type { ConfirmField } from '@orkestrel/form'; import type { DriverInterface } from '@orkestrel/database'; import type { EditorField } from '@orkestrel/form'; import type { EmitterErrorHandler } from '@orkestrel/emitter'; import type { EmitterHooks } from '@orkestrel/emitter'; import type { EmitterInterface } from '@orkestrel/emitter'; import type { FieldChoice } from '@orkestrel/form'; import type { FieldError } from '@orkestrel/form'; import type { FormInterface } from '@orkestrel/form'; import type { FormSchema } from '@orkestrel/form'; import type { FormValues } from '@orkestrel/form'; import type { Guard } from '@orkestrel/contract'; import type { JSONRecord } from '@orkestrel/contract'; import type { PasswordField } from '@orkestrel/form'; import type { Result } from '@orkestrel/contract'; import type { SelectField } from '@orkestrel/form'; import type { Style } from '@orkestrel/console'; import type { StylerInterface } from '@orkestrel/console'; import type { TableInterface } from '@orkestrel/database'; import type { TextField } from '@orkestrel/form'; /** Names the `Accept` header value that opens the broker's SSE stream — `text/event-stream`. */ export declare const ACCEPT_EVENT_STREAM = "text/event-stream"; /** * Explains why {@link PromptInterface.answer} refused — `unknown` for an id no form is parked * under, `rejected` for values the authoritative form itself refused, carrying the `FieldError` * list it reported. Names its axis with `reason`. * * @remarks * - `unknown` — no form is parked under that id, or the one that was has already settled. * - `rejected` — the authoritative form refused the values, and `errors` is exactly what it * reported. A client seeds its local form with the values it sent, applies each failure through * the form's `invalidate`, and asks again; the parked form stays parked until it accepts or * expires. This is the retry loop that makes a server-side `custom` rule enforceable, because * that rule never crossed the wire and the client could not have checked it. */ export declare type AnswerError = { readonly reason: 'unknown'; } | { readonly reason: 'rejected'; readonly errors: readonly FieldError[]; }; /** Names the backspace byte (BS, U+0008) — Ctrl+H / some terminals' Backspace. */ export declare const BACKSPACE: string; /** * Represents the immutable state a checkbox field's reducer carries — the select state plus the ticked set. * * @remarks * - `message` — the sanitized label the header renders. * - `choices` — the choices the list offers, in declared order. * - `styler` / `theme` — the console styler and the resolved {@link PromptTheme}. * - `focused` — the index the cursor sits on. * - `checked` — the ticked indices, in the order they were ticked; the reducer sorts them into * choice order when it submits. */ export declare interface CheckboxState { readonly message: string; readonly choices: readonly FieldChoice[]; readonly styler: StylerInterface; readonly theme: PromptTheme; readonly focused: number; readonly checked: readonly number[]; } /** * Represents the immutable state a confirm field's reducer carries. It holds no typed value, because the * answer is the key itself. * * @remarks * - `message` — the sanitized label the header renders. * - `default` — the answer a bare return submits, and the letter the view capitalizes. * - `styler` / `theme` — the console styler and the resolved {@link PromptTheme}. */ export declare interface ConfirmState { readonly message: string; readonly default: boolean; readonly styler: StylerInterface; readonly theme: PromptTheme; } /** * Holds the control byte (or CRLF pair) to key descriptor table * {@link import('./helpers.js').parseKey} consults for the one-byte keys and the two-byte CRLF * Enter chunk. Each entry carries the canonical `name` and whether it is a `ctrl` combination. The * source of truth for that decode; frozen. * * @remarks * `return` / `newline` / `return + newline` all map to `return` (one canonical Enter name) — a * terminal or a paste can deliver Enter as `\r`, `\n`, or the `\r\n` pair in one chunk; * `delete` / `backspace` both map to `backspace` (the Backspace bytes); the Ctrl combos * (`c` / `d` / `u` / `a` / `e`) carry `ctrl: true` so a reducer can match * `key.ctrl && key.name === 'c'`. `escape` / `tab` / `space` are plain named keys. */ export declare const CONTROL_NAMES: Readonly>; /** * Builds the initial checkbox-field reducer state — the offered choices, with every value in the * field's `default` list pre-checked. * * @param field - The checkbox field to render * @param styler - The styler used to render the view * @param theme - The optional terminal theme * @returns The initial immutable key state */ export declare function createCheckboxState(field: CheckboxField, styler?: StylerInterface, theme?: PromptThemeOptions): CheckboxState; /** * Builds the initial confirm-field reducer state — the sanitized label and the declared default * answer. * * @param field - The confirm field to render * @param styler - The styler used to render the view * @param theme - The optional terminal theme * @returns The initial immutable key state */ export declare function createConfirmState(field: ConfirmField, styler?: StylerInterface, theme?: PromptThemeOptions): ConfirmState; /** * Creates a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the * driver-pluggable twin of {@link createMemoryTerminalStore}, storing each endpoint's config * snapshot as one opaque JSON column. The default driver is an in-memory `@orkestrel/database` * driver. * * @param driver - The {@link DriverInterface} backing the table (default an in-memory driver) * @returns A {@link TerminalStoreInterface} * * @example * ```ts * import { createDatabaseTerminalStore } from '@orkestrel/terminal' * * const store = createDatabaseTerminalStore() // in-memory by default * ``` */ export declare function createDatabaseTerminalStore(driver?: DriverInterface): TerminalStoreInterface; /** * Builds the initial editor-field reducer state — the committed lines empty, and the declared * default held for a finish with nothing typed. * * @param field - The editor field to render * @param styler - The styler used to render the view * @param theme - The optional terminal theme * @returns The initial immutable key state */ export declare function createEditorState(field: EditorField, styler?: StylerInterface, theme?: PromptThemeOptions): EditorState; /** * Builds the initial text-field reducer state — the sanitized label, the declared default, the * styler, and the resolved theme. * * @param field - The text field to render * @param styler - The styler used to render the view * @param theme - The optional terminal theme * @returns The initial immutable key state */ export declare function createInputState(field: TextField, styler?: StylerInterface, theme?: PromptThemeOptions): InputState; /** * Creates the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint * config snapshots, the default store backing a {@link TerminalManagerInterface}'s `open` / `save`. * * @returns A {@link TerminalStoreInterface} * * @example * ```ts * import { createMemoryTerminalStore } from '@orkestrel/terminal' * * const store = createMemoryTerminalStore() * ``` */ export declare function createMemoryTerminalStore(): TerminalStoreInterface; /** * Builds the initial password-field reducer state — the text-field state, plus the mask glyph each * typed character renders as. * * @param field - The password field to render * @param styler - The styler used to render the view * @param theme - The optional terminal theme * @returns The initial immutable key state */ export declare function createPasswordState(field: PasswordField, styler?: StylerInterface, theme?: PromptThemeOptions): PasswordState; /** * Creates the headless {@link PromptInterface} broker. It parks live forms and applies remote * answers to the authoritative instances. * * @param options - See {@link PromptOptions} * @returns A {@link PromptInterface} * * @remarks * The caller awaits the parked form's own `answer`. Timeout, `stop`, or teardown destroys the form, * so that promise rejects with the Form package's `ABANDONED` error. Inject `options.timer` to * drive expiry without real time. * * @example * ```ts * import { createPrompt } from '@orkestrel/terminal' * import { createForm } from '@orkestrel/form' * * const prompt = createPrompt() * const form = createForm({ fields: [{ control: 'text', name: 'name' }] }) * const id = prompt.park(form) * prompt.answer(id, { name: 'Ada' }) * ``` */ export declare function createPrompt(options?: PromptOptions): PromptInterface; /** * Creates the SSE prompt {@link PromptClientInterface} bridge — it connects to a remote broker's * SSE endpoint, dispatches each received form to a local * {@link import('./types.js').TerminalInterface}, and POSTs the answer back. Universal — `fetch` * and SSE are web standards. * * @param options - See {@link PromptClientOptions} (`url` + `terminal` required) * @returns A {@link PromptClientInterface} * * @remarks * - **Connect + reconnect.** `await client.connect()` streams remote prompts until the stream * ends; it reconnects with the `delay` backoff unless `reconnect` is `false` / the client was * `destroy`ed. Inject `options.fetch` (a scripted `fetch`) and `options.timer` to drive it * deterministically in tests — no real network. * - **Wire narrowing.** Every decoded prompt is guard-narrowed before dispatch (never an `as`). * * @example * ```ts * import { createPromptClient } from '@orkestrel/terminal' * * const client = createPromptClient({ url: 'http://host/prompts', terminal }) * await client.connect() * ``` */ export declare function createPromptClient(options: PromptClientOptions): PromptClientInterface; /** * Builds a complete {@link PromptTheme} by merging a partial one over * {@link DEFAULT_PROMPT_THEME}, leaf by leaf — each supplied icon replaces that glyph, each * supplied role replaces that {@link Style}, and everything else keeps its default. Each supplied * style is snapshotted through the console module's own * {@link import('@orkestrel/console').freezeStyle}, so the result is deeply frozen and a caller * mutating its own attribute list afterwards cannot reach into a built theme. * * @param options - The partial theme to merge, or `undefined` for the defaults * @returns The resolved, deeply frozen theme every prompt state carries * * @example * ```ts * createPromptTheme() // the defaults * createPromptTheme({ * icons: { pointer: '=>' }, * roles: { message: { foreground: 'magenta', attributes: ['bold'] } }, * }) * ``` */ export declare function createPromptTheme(options?: PromptThemeOptions): PromptTheme; /** * Builds the initial select-field reducer state — the offered choices, with the focus pre-placed on * the declared default. * * @param field - The select field to render * @param styler - The styler used to render the view * @param theme - The optional terminal theme * @returns The initial immutable key state */ export declare function createSelectState(field: SelectField, styler?: StylerInterface, theme?: PromptThemeOptions): SelectState; /** * Creates the multi-endpoint {@link TerminalManager} — a named registry of {@link PromptInterface} * brokers so several parties can `ask` forms of each other by name, with a transitive cycle check * that refuses `DEADLOCK` across every in-flight ask. * * @param options - See {@link TerminalManagerOptions} * @returns A {@link TerminalManager} * * @example * ```ts * import { createTerminalManager } from '@orkestrel/terminal' * * const manager = createTerminalManager() * manager.add('agent') * ``` */ export declare function createTerminalManager(options?: TerminalManagerOptions): TerminalManagerInterface; /** Names the Ctrl+A byte (SOH, U+0001) — move to start of line. */ export declare const CTRL_A: string; /** Names the Ctrl+C byte (ETX, U+0003) — interrupt / cancel. */ export declare const CTRL_C: string; /** Names the Ctrl+D byte (EOT, U+0004) — end-of-transmission / finish (the editor's commit key). */ export declare const CTRL_D: string; /** Names the Ctrl+E byte (ENQ, U+0005) — move to end of line. */ export declare const CTRL_E: string; /** Names the Ctrl+U byte (NAK, U+0015) — clear the current line. */ export declare const CTRL_U: string; /** * Implements a {@link TerminalStoreInterface} backed by one table of the `databases` layer — an * endpoint's durable config state is a row, so persistence reduces to keyed point-access (`get` / * `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map` * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. A stored `snapshot` is narrowed * with {@link import('../validators.js').isTerminalSnapshot} on read. * * @remarks * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory, * JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite / * IndexedDB backend swaps in WITHOUT touching the manager — the same seam as * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. The driver defaults to memory * ({@link import('../factories.js').createDatabaseTerminalStore} passes `createMemoryDriver()`), so * it ALSO works in memory out of the box; you opt into the durable plumbing by passing a JSON / * SQLite / IndexedDB driver. * * The {@link TerminalSnapshot} is stored as ONE OPAQUE JSON COLUMN — the table is a row of * `{ id; snapshot }` ({@link TerminalSnapshotRow}). The snapshot is already a COMPLETE, * self-contained, pure-JSON CONFIG payload (no live broker state), so storing it whole is lossless * AND keeps the row type flat (`snapshot` reads back as `unknown`). * * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes * the row `{ id: snapshot.id, snapshot }`. * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to * a {@link TerminalSnapshot} ({@link import('../validators.js').isTerminalSnapshot} — the total * guard that narrows an untrusted storage read), or `undefined` if none is stored. * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw). * * There is NO idle-TTL / eviction — a persisted config lives until an explicit `delete`. The public * surface is EXACTLY `get` / `set` / `delete` — no extra members, so the class and * {@link TerminalStoreInterface} carry the same methods. Hydration stays a caller concern: `open` always restores an EMPTY * broker — parked Promises are process-bound and never resurrected. * * @example * ```ts * import { createDatabaseTerminalStore } from '@orkestrel/terminal' * import { createMemoryDriver } from '@orkestrel/database' * * const store = createDatabaseTerminalStore(createMemoryDriver()) // a durable driver swaps in here * await store.set({ id: 'shell', timeout: 5000 }) // persist the config (one JSON column) * const snapshot = await store.get('shell') * await store.delete('shell') // drop it * ``` */ export declare class DatabaseTerminalStore implements TerminalStoreInterface { #private; /** * Wraps a table as a terminal store. * * @param table - The {@link TableInterface} holding the snapshots — its row is the * {@link TerminalSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column) */ constructor(table: TableInterface); /** * Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a * `TerminalSnapshot`. * * @param id - The endpoint name the row is keyed by * @returns The stored snapshot, or `undefined` when none is held or the column is off-shape */ get(id: string): Promise; /** * Inserts or replaces under the snapshot's OWN `id` (no separate id param) — the row is * `{ id, snapshot }`. * * @param snapshot - The config snapshot to persist, carrying its own `id` * @returns A promise that settles once the row is written */ set(snapshot: TerminalSnapshot): Promise; /** * Drops a snapshot by id; an absent id is a no-op (no throw). * * @param id - The endpoint name to drop * @returns A promise that settles once the row is gone */ delete(id: string): Promise; } /** Names the default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */ export declare const DEFAULT_MASK = "*"; /** * Holds the {@link import('./types.js').PromptTheme} every prompt renders with unless its options supply * another — the glyph set assembled from {@link PROMPT_ICONS} plus the console * {@link import('@orkestrel/console').STATUS_ICONS} `success` / `error` marks, and the console * {@link import('@orkestrel/console').Style} each role is painted with. Deeply frozen through the * console module's own {@link import('@orkestrel/console').freezeStyle}; the baseline * {@link import('./helpers.js').createPromptTheme} merges a partial theme over. * * @remarks * The default roles reproduce the views' historical coloring exactly: `question` / `pointer` cyan, * `message` / `focus` bold, `success` / `selected` green, `error` red, `hint` / `muted` / * `description` dim, and `content` the EMPTY style — an empty style renders bare text, so primary * content keeps the bytes it had before it became themeable. * Two roles sharing a style today are still two roles — re-mapping one leaves the other alone. */ export declare const DEFAULT_PROMPT_THEME: PromptTheme; /** Holds how long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered form before it expires — 5 minutes. */ export declare const DEFAULT_PROMPT_TIMEOUT_MS = 300000; /** Holds how long (ms) the {@link import('./types.js').PromptClientInterface} waits before each reconnect attempt — 2 seconds. */ export declare const DEFAULT_RECONNECT_DELAY_MS = 2000; /** * Implements the default {@link import('./types.js').TimerHandler} — a thin host `setTimeout` / `clearTimeout` * wrapper that arms `callback` after `ms` and returns a {@link TimerCancelFunction}. The deadline seam * behind both the {@link import('./Prompt.js').Prompt} broker (its expiry) and the * {@link import('./PromptClient.js').PromptClient} (its reconnect backoff); a test injects a * deterministic timer instead, so neither entity touches real time. * * @param callback - The deadline callback to arm * @param ms - How long to wait before firing it, in milliseconds * @returns The {@link TimerCancelFunction} that clears the armed deadline */ export declare function defaultTimer(callback: () => void, ms: number): TimerCancelFunction; /** Names the delete byte (DEL, U+007F) — the usual Backspace byte on a Unix TTY. */ export declare const DELETE: string; /** * Applies a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input, * password, and editor. A printable key appends its character; `backspace` drops the last * character; `space` appends a space; ctrl-u clears the line; a key that edits nothing returns * `undefined`. * * @param value - The buffer the field holds so far * @param key - The decoded keypress to apply * @returns The new buffer, or `undefined` when the key does not edit the line, so the caller can * leave the state untouched */ export declare function editLine(value: string, key: KeyEvent): string | undefined; /** * Represents the immutable state an editor field's reducer carries — the committed lines and the line still * being typed, kept apart so a return commits one without ending the field. * * @remarks * - `message` — the sanitized label the header renders. * - `default` — the text an empty finish falls back to. * - `styler` / `theme` — the console styler and the resolved {@link PromptTheme}. * - `lines` — the lines already committed with a return. * - `current` — the line in progress. */ export declare interface EditorState { readonly message: string; readonly default: string; readonly styler: StylerInterface; readonly theme: PromptTheme; readonly lines: readonly string[]; readonly current: string; } /** * Represents a minimal `fetch` — the subset of the global `fetch` a {@link PromptClientInterface} uses: open * the SSE stream, POST an answer. Injected so a test drives the client with a scripted `Response` * instead of a real network. */ export declare type FetchHandler = (input: string, init?: FetchInit) => Promise; /** * Represents the request init a {@link PromptClientInterface} passes to its {@link FetchHandler} — the * `RequestInit` fields it actually sets. */ export declare interface FetchInit { readonly method?: string; readonly headers?: Readonly>; readonly body?: string; readonly signal?: AbortSignal; } /** * Implements the default {@link import('./types.js').FetchHandler} — the global `fetch`, adapted to * the minimal injected shape the {@link import('./PromptClient.js').PromptClient} uses. * * @param input - The request URL * @param init - The request init the client sets — method, headers, body, and abort signal * @returns The host `fetch` promise for that request */ export declare function globalFetch(input: string, init?: FetchInit): Promise; /** * Names the auth-token request header the {@link import('./types.js').PromptClientInterface} sends * when a `token` is configured — `x-orkestrel-token`. */ export declare const HEADER_TOKEN = "x-orkestrel-token"; /** * Represents the immutable state a text field's reducer carries — built by * {@link import('./helpers.js').createInputState}, rendered by * {@link import('./helpers.js').renderInputView}, and advanced by * {@link import('./helpers.js').reduceInput}. * * @remarks * - `message` — the sanitized label the header renders. * - `default` — the declared default a bare return submits. * - `styler` — the console styler every role is painted through. * - `theme` — the resolved {@link PromptTheme}. * - `value` — the characters typed so far. */ export declare interface InputState { readonly message: string; readonly default: string; readonly styler: StylerInterface; readonly theme: PromptTheme; readonly value: string; } /** * Checks whether a caught value is an `AbortError` — the {@link import('./PromptClient.js').PromptClient} * distinguishes a deliberate `disconnect` / teardown (an aborted `fetch`) from a real fault, so it * exits its connect loop quietly instead of emitting `error` / reconnecting. * * @param error - The caught value to test * @returns True if the value is a host `Error` or `DOMException` named `AbortError`; false otherwise */ export declare function isAbortError(error: unknown): boolean; /** * Checks whether `url` is an insecure remote endpoint — a plain `http://` URL whose host is not a * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input; * the {@link import('./PromptClient.js').PromptClient} warns once when a `token` would cross such * an endpoint in cleartext. * * @remarks * A loopback host (`localhost`, `127.0.0.1`, `[::1]`) over `http://` is exempt (local * development has no network hop to eavesdrop on); every other `http://` host is insecure. * An `https://` URL (or any non-`http://` scheme) is never flagged. * * @param url - The candidate endpoint URL * @returns True if `url` is a non-loopback `http://` endpoint; false otherwise * * @example * ```ts * isInsecureRemote('http://example.com') // true * isInsecureRemote('http://localhost:3000') // false * isInsecureRemote('https://example.com') // false * ``` */ export declare function isInsecureRemote(url: string): boolean; /** * Narrows an unknown wire value to a {@link PendingForm} envelope — the envelope alone, because the * form package's `parseForm` owns the schema payload. * * @remarks * This guard checks the transport record and proves only that `schema` is a record. The Form * package's `parseForm` owns the schema payload and its semantic audit. * * @param value - The decoded wire value to inspect * @returns True if the value is a complete pending-form envelope; false otherwise */ export declare function isPendingForm(value: unknown): value is PendingForm; /** * Narrows an unknown value to a {@link PendingFormStatus}. * * @param value - The candidate ticket status * @returns True if the value is one of the declared ticket statuses; false otherwise */ export declare const isPendingFormStatus: Guard; /** * Checks whether a single character is printable — the fallback test {@link parseKey} applies after * the control bytes and the escape sequences, so the C0 controls and DEL are excluded. * * @param character - The single character to test * @returns True if the character is at or above space and is not DEL; false otherwise */ export declare function isPrintable(character: string): boolean; /** * Narrows an unknown caught value to a {@link TerminalError}, so a caller can branch on its `code`. * * @param value - The value to test (typically a `catch` binding or a rejected prompt call) * @returns True if `value` is a {@link TerminalError}; false otherwise * * @example * ```ts * try { * await terminal.ask(form) * } catch (error) { * if (isTerminalError(error) && error.code === 'CANCEL') { * // the person aborted * } * } * ``` */ export declare function isTerminalError(value: unknown): value is TerminalError; /** * Narrows an unknown value to a {@link TerminalSnapshot} — a non-empty `id` and an optional numeric * `timeout`, the read boundary a store applies to an untrusted persisted row. * * @param value - The candidate snapshot read back from storage * @returns True if the value carries a non-empty `id` and an optional numeric `timeout`; false otherwise */ export declare const isTerminalSnapshot: Guard; /** * Narrows an unknown value to a transport-neutral {@link WireEvent} — the guard a consumer's own * transport applies to an inbound frame. * * @param value - The candidate wire event * @returns True if the value carries an event name, serialized data, and an optional id; false otherwise */ export declare const isWireEvent: Guard; /** * Names the Single Shift Three lead (`ESCO`) — the alternate arrow-key prefix some terminals emit * (`ESC O A`). Built from the console module's own {@link import('@orkestrel/console').ESC}; the * navigation keys' CSI lead is that module's {@link import('@orkestrel/console').CSI}, which this * package reuses rather than redeclaring. */ export declare const KEY_SS3: string; /** * Represents one decoded keypress — the TTY-agnostic representation of a single key, the output of * {@link import('./helpers.js').parseKey}. A driver reads `name` and the modifier flags to decide * its transition; `sequence` is preserved so a printable character round-trips and an unknown * escape is never lost. * * @remarks * - `name` — the canonical key name: a control or navigation key (`return`, `backspace`, `tab`, * `escape`, `up` / `down` / `left` / `right`, `space`, `home`, `end`, `delete`), a named ctrl * combo (`c` with `ctrl` true for ctrl-c, likewise `d` / `u` / `a` / `e`), or the printable * character itself (`'a'`, `'7'`, `'?'`). An unrecognized sequence carries NO `name` — absence, * never an empty string — and the decoder stays total and never throws. * - `sequence` — the exact input bytes as a string (a `Uint8Array` is decoded UTF-8). The driver * writes this verbatim for a printable key. * - `ctrl` / `meta` / `shift` — the modifier flags. `ctrl` is true for a C0 control byte, `meta` * for an ESC-prefixed (Alt) sequence, `shift` for an uppercase-letter printable. */ export declare interface KeyEvent { readonly name?: string; readonly sequence: string; readonly ctrl: boolean; readonly meta: boolean; readonly shift: boolean; } /** * Implements the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of * {@link TerminalSnapshot} records keyed by endpoint id, the default store * {@link import('../factories.js').createMemoryTerminalStore} builds and the exact twin of * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}. It carries no idle expiry and * no eviction. * * @remarks * A plain `Map` — the snapshot is already pure, self-contained CONFIG-only * JSON, so the memory tier needs no encoding. There is NO idle-TTL and NO * eviction: a persisted config lives until an explicit `delete`. A durable backend (JSON / SQLite / * IndexedDB) swaps in through the SAME interface without touching the manager — its * driver-pluggable twin is {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore} (the * snapshot as one opaque JSON column). * * - **`get` resolves the persisted snapshot for an id**, or `undefined` if none is stored. * - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param). * - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw). * * The public surface is EXACTLY `get` / `set` / `delete` — no extra members, so the class and * {@link TerminalStoreInterface} carry the same methods. Hydration is a caller concern: `open` always * restores an EMPTY broker — parked Promises are process-bound and never resurrected. * * @example * ```ts * import { createMemoryTerminalStore } from '@orkestrel/terminal' * * const store = createMemoryTerminalStore() * await store.set({ id: 'shell', timeout: 5000 }) // persist a config * const snapshot = await store.get('shell') * await store.delete('shell') // drop it * ``` */ export declare class MemoryTerminalStore implements TerminalStoreInterface { #private; /** * Resolves the persisted snapshot for `id`. * * @param id - The endpoint name the snapshot is keyed by * @returns The stored snapshot, or `undefined` when none is held */ get(id: string): Promise; /** * Inserts or replaces under the snapshot's OWN `id` (no separate id param). * * @param snapshot - The config snapshot to persist, carrying its own `id` * @returns A promise that settles once the snapshot is held */ set(snapshot: TerminalSnapshot): Promise; /** * Drops a snapshot by id; an absent id is a no-op (no throw). * * @param id - The endpoint name to drop * @returns A promise that settles once the snapshot is gone */ delete(id: string): Promise; } /** Names the line feed byte (`\n`, U+000A) — Enter on some terminals / pasted input. */ export declare const NEWLINE: string; /** * Represents one parked form's runtime state inside the broker — the live form, the wire-safe record the * broker exposes, and the cancel for its expiry timer. * * @remarks * `form` is the authoritative form the caller parked, not a copy: an answer fills and submits this * one, so a `custom` rule that never crossed the wire still decides. Expiry destroys it, which * abandons it and settles the caller's promise through the form's own lifecycle. `pending` is the * wire record, whose `status` tracks the ticket. */ export declare interface ParkedForm { readonly form: FormInterface; /** Lists every parked record (`pending()`), or looks one up by id (`pending(id)`). */ readonly pending: PendingForm; readonly cancel: TimerCancelFunction; } /** * Represents the parking envelope — everything the broker needs about a park that the form itself does not * say. * * @remarks * `from` and `to` are the attribution edge, set only by a {@link TerminalManagerInterface}: which * endpoint asked, which endpoint must answer. A direct broker caller passes no request at all. */ export declare interface ParkRequest { readonly from?: string; readonly to?: string; } /** * Decodes one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is * read as UTF-8; the resulting string is matched against the known control bytes and the CRLF pair * ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a single * printable character. An unrecognized sequence carries no `name`, with the raw `sequence` * preserved. * * @remarks * - **Single control byte.** A one-character control input (`return` / `backspace` / `tab` / * `escape` / `space`, or a Ctrl combo `c` / `d` / `u` / `a` / `e`), or the two-byte `\r\n` * CRLF pair, is looked up in {@link CONTROL_NAMES}, carrying its `ctrl` flag. * - **Escape sequence.** A multi-byte ESC sequence (`up` / `down` / `left` / `right` in both the * `ESC[A` and `ESCOA` forms, plus `home` / `end` / `delete`) is looked up in * {@link SEQUENCE_NAMES} and flagged `meta`. * - **Printable character.** A single printable character becomes `name` = that character, with * `shift` set when it is an uppercase letter. A multi-code-point printable (an emoji, a pasted * run) keeps its first code point as the name and the whole input as `sequence`. * - **Unknown.** Anything else (an unrecognized escape, an empty input) yields an event with NO * `name` — absence, never an empty string — total, so the driver never crashes on a stray byte. * * @param input - The raw keypress bytes, as a string or `Uint8Array` * @returns The decoded {@link KeyEvent} * * @example * ```ts * parseKey('\r') // { name: 'return', sequence: '\r', ctrl: false, meta: false, shift: false } * parseKey('\r\n') // { name: 'return', sequence: '\r\n', ctrl: false, meta: false, shift: false } * parseKey('\x1b[A') // { name: 'up', sequence: '\x1b[A', ctrl: false, meta: true, shift: false } * parseKey('A') // { name: 'A', sequence: 'A', ctrl: false, meta: false, shift: true } * parseKey('\x03') // { name: 'c', sequence: '\x03', ctrl: true, meta: false, shift: false } * ``` */ export declare function parseKey(input: string | Uint8Array): KeyEvent; /** * Represents the immutable state a password field's reducer carries — the text state with the mask glyph in * place of a default, because a secret is never seeded from the schema. * * @remarks * - `message` — the sanitized label the header renders. * - `mask` — the glyph each typed character renders as. * - `styler` / `theme` — the console styler and the resolved {@link PromptTheme}. * - `value` — the characters typed so far, rendered only as the mask repeated. */ export declare interface PasswordState { readonly message: string; readonly mask: string; readonly styler: StylerInterface; readonly theme: PromptTheme; readonly value: string; } /** * Represents one form parked by the broker — an id-keyed, wire-safe record of a live form awaiting * a remote answer. The value a `pending` listener receives and the broker serializes over SSE to a * {@link PromptClientInterface}. * * @remarks * - `id` — the unique id, minted with `crypto.randomUUID()`; the key for * {@link PromptInterface.answer}. * - `schema` — the parked form's schema projected to JSON by the dependency's own `serializeForm`. * Every `custom` validator is dropped on the way out, so an authoritative rule the wire cannot * carry stays server-side and is enforced when the answer comes back. * - `status` — the ticket's {@link PendingFormStatus}. * - `time` — the creation timestamp, ms since epoch. * - `from` / `to` — the attribution edge a {@link TerminalManagerInterface} stamps on a parked * form: which endpoint asked, which endpoint must answer. Both absent for a bare broker used * directly. */ export declare interface PendingForm { readonly id: string; readonly schema: JSONRecord; readonly status: PendingFormStatus; readonly time: number; readonly from?: string; readonly to?: string; } /** * Names the lifecycle status of a parked {@link PendingForm} — where the ticket stands, which is * not where the form stands. A ticket is `pending` until somebody answers it; the form it carries * has its own status, and each is a separate fact about a separate entity. * * @remarks * - `pending` — parked, awaiting {@link PromptInterface.answer}. * - `answered` — answered and accepted, so the parked form settled. * - `expired` — timed out, released by `stop`, or torn down by `destroy`, before an answer. */ export declare type PendingFormStatus = 'pending' | 'answered' | 'expired'; /** * Implements the headless form broker. It parks live forms, exposes their serialized schemas, * applies remote answers to the authoritative form, and abandons a parked form on timeout, release, * or teardown. * * @remarks * A parked record carries one call to `serializeForm`. A failed fill or submit leaves the record * parked for another answer. A successful submit settles the form once, emits `answer`, and removes * the record. Timeout, `stop`, and teardown abandon unsettled forms through their own `destroy` * method. * * @example * ```ts * const form = createForm({ fields: [{ control: 'text', name: 'name' }] }) * const prompt = createPrompt() * const id = prompt.park(form) * prompt.answer(id, { name: 'Ada' }) * await form.answer // { name: 'Ada' } * ``` */ export declare class Prompt implements PromptInterface { #private; constructor(options?: PromptOptions); get emitter(): EmitterInterface; get count(): number; pending(): readonly PendingForm[]; pending(id: string): PendingForm | undefined; park(form: FormInterface, request?: ParkRequest): string; answer(id: string, values: FormValues): Result; stop(ids: readonly string[]): boolean; stop(id: string): boolean; stop(): void; destroy(): void; } /** * Holds the terminal-owned glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from, beside * the console module's own success and error marks. Read only when the default theme is assembled; * a view reads its resolved theme and never this constant. Frozen. * * @remarks * - `question` — the leading mark on a prompt's message line. * - `pointer` — the cursor before the input / the focused choice row. * - `dot` / `selected` — an unfocused / focused row marker in a select list. * - `checked` / `unchecked` — a checked / unchecked box in a checkbox list. */ export declare const PROMPT_ICONS: Readonly<{ question: "?"; pointer: "›"; dot: "○"; selected: "●"; checked: "☑"; unchecked: "☐"; }>; /** * Holds every {@link import('./types.js').PromptRole}, in one frozen list — the role axis's source of * truth. {@link import('./helpers.js').createPromptTheme} walks it to merge a partial theme, and * a consumer building a complete role map reads it rather than retyping every name. */ export declare const PROMPT_ROLES: readonly PromptRole[]; /** * Implements the SSE form bridge. It ingests serialized forms from a remote broker without waiting * on a render, drives one form at a time through a local terminal, posts each answer back, and asks * again when the authoritative form refuses one. * * @remarks * - **Connect + reconnect.** {@link connect} opens the SSE stream and reconnects after a transport * drop with the injected backoff unless reconnect is disabled, the client was destroyed, or * {@link disconnect} deliberately stopped it. * - **Ingest + render.** Each `pending` envelope passes through `isPendingForm`, Form's `parseForm`, * and terminal's `sanitizeSchema`, then enters a serial render queue. The SSE reader never awaits * that queue, so `expire` and `destroy` remain live while a person is answering. * - **Safe local form.** The rendering copy omits every wire `pattern`, because Form compiles a * pattern during local evaluation. The broker's parked form retains it and remains authoritative. * - **Refusal retry.** A structured `rejected` response seeds a new rendering form with the values * the refused attempt submitted, applies every {@link FieldError} through `invalidate`, and asks * again. No retry counter truncates the loop; acceptance, expiry, and the broker's own teardown * are its bounds. * - **Replay safety.** A replayed id is skipped while it is queued, rendering, or posting. Once an * attempt ends, a later delivery of that id may be rendered again. * * @example * ```ts * const client = createPromptClient({ * url: 'http://localhost:3001/prompts', * terminal: createTerminal(), * }) * await client.connect() * ``` */ export declare class PromptClient implements PromptClientInterface { #private; readonly url: string; constructor(options: PromptClientOptions); get emitter(): EmitterInterface; get connected(): boolean; connect(): Promise; disconnect(): void; destroy(): void; } /** * Declares the client's event map — lean, errors `unknown`, no listener-error event. * * @remarks * - `connect` — the SSE stream opened. * - `disconnect` — the SSE stream closed, by the server or by * {@link PromptClientInterface.disconnect}. * - `expire` — the remote broker signalled that a parked form expired, carrying its id. The client * destroys the local form rendering it, or drops it from the queue if it has not started. * - `error` — a connection, render, or POST fault. */ export declare type PromptClientEventMap = { readonly connect: readonly []; readonly disconnect: readonly []; readonly expire: readonly [id: string]; readonly error: readonly [error: unknown]; }; /** * Declares the SSE form bridge — the client-side counterpart to {@link PromptInterface}. It * receives serialized {@link PendingForm} records from a remote broker, rebuilds each schema * locally, drives it through a {@link TerminalInterface}, and POSTs the answer back, so a human at * this machine answers forms a broker parked elsewhere. * * @remarks * - **Connect.** {@link connect} opens the SSE stream through the injected `fetch` and resolves * when the stream ends. It reconnects on the `delay` backoff unless `reconnect` is false or the * client was destroyed. * - **Ingest, then render.** Ingestion never waits on a render. Each decoded record is narrowed, * its schema parsed and sanitized, and queued; one form is driven at a time while the stream * keeps reading, so an unanswered form never starves the connection. * - **Never trust the wire.** Every rendered string is sanitized, and a `pattern` that arrived over * the wire is never executed locally. The authoritative form decides. * - **Refusal retries.** A rejected answer comes back with the parked form's own errors; the client * applies them to the local form and asks again until the answer is accepted or the form expires. * - **`connected`** reflects whether the stream is open. */ export declare interface PromptClientInterface { /** Holds the typed emitter every client event is published on. */ readonly emitter: EmitterInterface; /** Holds the remote broker's SSE endpoint this client reads from and answers to. */ readonly url: string; /** Reports whether the SSE stream is open. */ readonly connected: boolean; /** * Opens the stream and pumps it, queueing each received form for the local terminal; reconnects * on the `delay` backoff. */ connect(): Promise; /** * Stops the current connection and the reconnect loop. An active local render continues, and a * later `connect()` can restart the stream. */ disconnect(): void; /** * Tears the client down permanently — disconnects, drops the queue, abandons the active local * form, and destroys the emitter. */ destroy(): void; } /** * Configures {@link import('./factories.js').createPromptClient} and the * {@link PromptClientInterface}. * * @remarks * - `url` — the remote broker's SSE endpoint. A GET opens the stream; answers POST back to it. * - `terminal` — the local {@link TerminalInterface} each remote form is driven through, so a human * at this machine answers a form parked elsewhere. * - `token` — an optional auth token, sent as the {@link import('./constants.js').HEADER_TOKEN} * header on every request. * - `reconnect` — whether to reconnect after the stream drops, default true. * - `delay` — ms to wait before each reconnect attempt (default * {@link import('./constants.js').DEFAULT_RECONNECT_DELAY_MS}). * - `on` — initial {@link PromptClientEventMap} listeners. * - `error` — the emitter's listener-error handler. * - `fetch` — the injected {@link FetchHandler}, default the global `fetch`. * - `timer` — the injected {@link TimerHandler} for the reconnect backoff, default the host * `setTimeout`. */ export declare interface PromptClientOptions { readonly url: string; readonly terminal: TerminalInterface; readonly token?: string; readonly reconnect?: boolean; readonly delay?: number; readonly on?: EmitterHooks; readonly error?: EmitterErrorHandler; readonly fetch?: FetchHandler; readonly timer?: TimerHandler_2; } /** * Declares the broker's event map — lean, errors `unknown`, no listener-error event. * * @remarks * - `pending` — a form was parked; a transport forwards the wire record to remote clients. * - `answer` — a parked form was answered and accepted, carrying its id and the settled values. * - `expire` — a parked form timed out or was released unanswered, carrying its id. */ export declare type PromptEventMap = { readonly pending: readonly [form: PendingForm]; /** * Reports an accepted answer, carrying the parked form id and settled values. */ readonly answer: readonly [id: string, values: FormValues]; readonly expire: readonly [id: string]; }; /** * Names one glyph slot a rendered field draws — the icon axis of a {@link PromptTheme}. A named value * set, not a toggle, so it stays a union. * * @remarks * - `question` — the leading mark on a field's label line. * - `pointer` — the cursor before the input or the focused choice row. * - `dot` / `selected` — an unfocused / focused row marker in a choice list. * - `checked` / `unchecked` — a checked / unchecked box in a multi-choice list. * - `success` / `error` — the mark on a settled field's line / on its failure line. */ export declare type PromptIcon = 'question' | 'pointer' | 'dot' | 'selected' | 'checked' | 'unchecked' | 'success' | 'error'; /** * Declares the headless form broker — parks a live form until somebody elsewhere answers it. The * headless arm of the local-TTY, headless, and remote trio: there is no terminal here, so a * transport forwards each `pending` record to whoever can answer, and {@link answer} drives the * parked form to settlement. * * @remarks * - **The form is the unit.** {@link park} takes a live form, mints an id, emits `pending`, and * returns the id. It wraps no promise, because the caller already holds one: the form's own * `answer`. * - **The parked form is authoritative.** {@link answer} fills and submits that form, so every rule * it carries decides, including a `custom` validator the wire dropped. A refusal returns the * form's own errors and leaves the form parked. * - **Timeout abandons.** An unanswered form is destroyed after `timeout` ms; `expire` fires and * the caller's promise rejects on the form's own lifecycle. The timer is injectable. * - **Accessors.** `pending()` lists the parked records; `pending(id)` looks one up. * - **Batch stop.** The array overload is declared first: `stop(ids)` releases each listed parked * form and reports whether all ids were parked; `stop(id)` releases one; `stop()` releases every * parked form without destroying the broker. Release uses the existing expiry semantics. * * @example * ```ts * const prompt = createPrompt() * const first = prompt.park(createForm({ fields: [{ control: 'text', name: 'first' }] })) * const second = prompt.park(createForm({ fields: [{ control: 'text', name: 'second' }] })) * prompt.stop(first) // true * prompt.stop([second, 'missing']) // false; `second` was still released * prompt.stop() // release every remaining form; the broker stays usable * ``` */ export declare interface PromptInterface { /** Holds the typed emitter every broker event is published on. */ readonly emitter: EmitterInterface; /** Reports how many forms this broker holds parked. */ readonly count: number; /** * Parks a live form, mints its id, emits `pending`, and arms the expiry deadline. Returns the id; * the caller already holds the promise. */ park(form: FormInterface, request?: ParkRequest): string; /** Lists every parked record (`pending()`), or looks one up by id (`pending(id)`). */ pending(): readonly PendingForm[]; pending(id: string): PendingForm | undefined; /** * Fills and submits the authoritative parked form. Accepted, it settles and the record is * dropped; refused, the form stays parked. */ answer(id: string, values: FormValues): Result; /** * Releases a batch (`stop(ids)`, the array overload declared first), one id, or every parked * form. The broker stays usable. */ stop(ids: readonly string[]): boolean; stop(id: string): boolean; stop(): void; /** * Tears the broker down — abandons every parked form, cancels every deadline, then destroys the * emitter. Idempotent. */ destroy(): void; } /** * Configures {@link import('./factories.js').createPrompt} and every {@link PromptInterface} * broker, including one a {@link TerminalManagerInterface} mounts per endpoint. * * @remarks * - `on` — initial {@link PromptEventMap} listeners. * - `error` — the emitter's listener-error handler. * - `timeout` — ms a parked form waits before it expires and is abandoned (default * {@link import('./constants.js').DEFAULT_PROMPT_TIMEOUT_MS}). * - `timer` — the injected {@link TimerHandler}, default the host `setTimeout`; supply a * deterministic timer to drive expiry in tests without real time. * - `cap` — the maximum number of forms this broker holds parked at once, default unbounded. Once * `count` reaches `cap` a new park is refused with a * {@link import('./errors.js').TerminalError} coded `LIMIT`, without parking, minting an id, * emitting `pending`, or arming a timer. The runaway-asker memory ceiling. */ export declare interface PromptOptions { readonly on?: EmitterHooks; readonly error?: EmitterErrorHandler; readonly timeout?: number; readonly timer?: TimerHandler_2; readonly cap?: number; } /** * Names one styling slot a rendered field paints through — the semantic axis of a * {@link PromptTheme}. A role says what a fragment means; the theme decides what that meaning looks * like, so a consumer re-maps styled output by naming roles rather than reimplementing a renderer. * * @remarks * - `question` — the leading mark on an active field's line. * - `pointer` — the cursor before the input or the focused choice row. * - `message` — the field's own label text. * - `content` — the field's primary content: the typed value, the mask run, an unfocused choice * label, or a committed editor line. Its default is the EMPTY style, so unthemed content renders * as bare text. * - `success` / `error` — a settled field's mark / a failure mark and its message. * - `selected` — a chosen value: the checked box, the focused choice marker, the confirm default * letter. * - `focus` — the label of the row the cursor is on. * - `hint` — dim supplementary text: a default value, a key hint, a selection count, a committed * answer. * - `muted` — a dim off-state mark: an unfocused choice marker, an unchecked box, a fallback index. * - `description` — a choice's one-line help text. */ export declare type PromptRole = 'question' | 'pointer' | 'message' | 'content' | 'success' | 'error' | 'selected' | 'focus' | 'hint' | 'muted' | 'description'; /** * Names where one field's reducer stands after a key. `active`: keep asking, because the key was * consumed or the answer was refused. `submit`: the field resolved with its `value`. `cancel`: the * user aborted with ctrl-c. Names its axis, never `kind`. */ export declare type PromptStatus = 'active' | 'submit' | 'cancel'; /** * Represents the result of one reducer step — the next `state`, the rendered `view`, the `status`, * and, on `submit` alone, the candidate `value`. The whole contract between a pure reducer and the * impure driver: the driver applies the next `state`, writes the `view`, and reads `value` on * `submit`. * * @typeParam T - The value this field resolves to, as its control admits it. * @typeParam S - The reducer's concrete state shape, carried directly so `state` stays precisely * typed with no union narrowing and no assertion. * * @remarks * - `state` — the next immutable state; feed it to the next reduce call. On `submit` or `cancel` it * is the final state. * - `view` — the styled string to render now, possibly multi-line. On a refused `submit` it carries * the failure; the driver re-renders it each step. * - `value` — present only on a `submit` step. */ export declare interface PromptStep { readonly state: S; readonly view: string; readonly status: PromptStatus; readonly value?: T; } /** * Represents a resolved presentation — the glyph for every {@link PromptIcon} and the console * {@link Style} for every {@link PromptRole}. Plain JSON data with no functions, so it crosses the * wire with the form it decorates. Built by {@link import('./helpers.js').createPromptTheme}. * * @remarks * A role's value is the console module's own {@link Style} — the one style model the whole console * and terminal system shares — so a renderer paints a role through * {@link import('@orkestrel/console').StylerInterface} and this package holds no second style * vocabulary. A `Style` carries a foreground, a background, and an attribute list, so a role can * express a background color, which a styler's accessor chain cannot name. */ export declare interface PromptTheme { readonly icons: Readonly>; readonly roles: Readonly>; } /** * Represents the partial {@link PromptTheme} an option bag carries — every icon and every role is * optional, and {@link import('./helpers.js').createPromptTheme} merges what is supplied over * {@link import('./constants.js').DEFAULT_PROMPT_THEME} leaf by leaf. Supplying one icon or one * role leaves every other slot at its default. */ export declare interface PromptThemeOptions { readonly icons?: Readonly>>; readonly roles?: Readonly>>; } /** * Advances a checkbox prompt by one {@link KeyEvent} — the pure * `(state, key) → PromptStep` reducer. `up` / `down` (and `k` / `j`) move the * focus (wrapping); `space` toggles the focused index in the checked set; return submits the * checked values in choice order; ctrl-c cancels. The form applies selection-count rules. * * @param state - The checkbox field's current reducer state * @param key - The decoded keypress to apply * @returns The next step — the state, the rendered view, the status, and the ticked values on submit */ export declare function reduceCheckbox(state: CheckboxState, key: KeyEvent): PromptStep; /** * Advances a confirm prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep` * reducer. `y` / `Y` submits `true`, `n` / `N` submits `false`, return on an empty line submits * the `default`, ctrl-c cancels; any other key is ignored (stays active). * * @param state - The confirm field's current reducer state * @param key - The decoded keypress to apply * @returns The next step — the state, the rendered view, the status, and the answer on submit */ export declare function reduceConfirm(state: ConfirmState, key: KeyEvent): PromptStep; /** * Advances an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep` * reducer. Printable characters extend the current line; backspace shrinks it; return commits the * current line and starts a fresh one; ctrl-d finishes, joining every line and falling back to the * default when empty; ctrl-c cancels. The form validates the candidate after the driver fills it. * * @param state - The editor field's current reducer state * @param key - The decoded keypress to apply * @returns The next step — the state, the rendered view, the status, and the joined text on submit */ export declare function reduceEditor(state: EditorState, key: KeyEvent): PromptStep; /** * Advances an input prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep` * reducer. Printable characters extend the value; backspace shrinks it; ctrl-u clears it; ctrl-c * cancels; return produces the candidate value, with an empty line falling back to the default. * * @param state - The text field's current reducer state * @param key - The decoded keypress to apply * @returns The next step — the new state, the rendered view, the status, and the value on submit */ export declare function reduceInput(state: InputState, key: KeyEvent): PromptStep; /** * Advances a password prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep` * reducer. Identical line-editing to {@link reduceInput} (printable extends, backspace shrinks, * ctrl-u clears, ctrl-c cancels) but the view masks the value. Return produces the candidate value. * * @param state - The password field's current reducer state * @param key - The decoded keypress to apply * @returns The next step — the new state, the masked view, the status, and the value on submit */ export declare function reducePassword(state: PasswordState, key: KeyEvent): PromptStep; /** * Advances a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep` * reducer. `up` / `down` (and `k` / `j`) move the focus, wrapping at the ends; return submits the * focused choice's `value`; ctrl-c cancels. An empty choice list can never submit (a higher layer * guards against it); any other key is ignored. * * @param state - The select field's current reducer state * @param key - The decoded keypress to apply * @returns The next step — the state, the rendered view, the status, and the chosen value on submit */ export declare function reduceSelect(state: SelectState, key: KeyEvent): PromptStep; /** * Renders a checkbox-field reducer state as a multi-line styled view — one box per choice, and the * selected count beneath them. * * @param state - The checkbox field's current reducer state * @returns The rendered view — the header, one box per choice, and the selected count */ export declare function renderCheckboxView(state: CheckboxState): string; /** * Renders a confirm-field reducer state as a styled view — the header and the yes/no group, with * the default letter capitalized and painted by the `selected` role. * * @param state - The confirm field's current reducer state * @returns The rendered view — the header and the yes/no group with the default capitalized */ export declare function renderConfirmView(state: ConfirmState): string; /** * Renders an editor-field reducer state as a multi-line styled view — the finish hint, the * committed lines, and the line in progress. * * @param state - The editor field's current reducer state * @returns The rendered view — the hinted header, the committed lines, and the line in progress */ export declare function renderEditorView(state: EditorState): string; /** * Renders the styled failure line (`✖ message`) a form driver writes for each refused field before * it asks that field again. * * @param styler - The console styler that renders each role * @param theme - The resolved prompt theme * @param message - The failure text, normally the field's label and the reason * @returns The rendered failure line */ export declare function renderErrorLine(styler: StylerInterface, theme: PromptTheme, message: string): string; /** * Renders a question header followed by a key hint painted with the `hint` role, or the header * alone when no hint is supplied. * * @param styler - The console styler that renders each role * @param theme - The resolved prompt theme * @param message - The prompt's question text * @param hint - The optional key hint to append * @returns The rendered header with the optional hint */ export declare function renderHintedHeader(styler: StylerInterface, theme: PromptTheme, message: string, hint?: string): string; /** * Renders a text-field reducer state as a styled view — the header, the pointer, and the typed * value, or the default shown as a hint while nothing is typed. * * @param state - The text field's current reducer state * @returns The rendered single-line view — header, pointer, and the typed value or the default */ export declare function renderInputView(state: InputState): string; /** * Renders a password-field reducer state as a styled view, with the value replaced by the mask * repeated so the secret is never echoed. * * @param state - The password field's current reducer state * @returns The rendered view, with the mask repeated in place of the typed value */ export declare function renderPasswordView(state: PasswordState): string; /** * Renders the styled question header (`? message`) — the leading line every active prompt view * shares, themed by the `question` + `message` roles. * * @param styler - The console styler that renders each role * @param theme - The resolved prompt theme * @param message - The prompt's question text * @returns The rendered question header */ export declare function renderPromptHeader(styler: StylerInterface, theme: PromptTheme, message: string): string; /** * Renders a select-field reducer state as a multi-line styled view — one row per choice, with the * focused row marked and its help shown. * * @param state - The select field's current reducer state * @returns The rendered view — the header, then one row per choice with the focused row marked */ export declare function renderSelectView(state: SelectState): string; /** * Renders the styled submit line (`✔ message`) — the committed header an interactive prompt shows * after it resolves, themed by the `success` + `message` roles. * * @param styler - The console styler that renders each role * @param theme - The resolved prompt theme * @param message - The settled prompt's question text * @returns The rendered committed header */ export declare function renderSubmitHeader(styler: StylerInterface, theme: PromptTheme, message: string): string; /** Names the carriage return byte (`\r`, U+000D) — Enter on most terminals. */ export declare const RETURN: string; /** * Sanitizes text for one single-line display slot. Composes console's ANSI {@link strip} and C0 * {@link stripControls} passes with removal of tab, line feed, and carriage return. * * @param text - The text to sanitize for a glyph or hint slot * @returns The text with ANSI sequences, every C0 control character, and DEL removed * * @example * ```ts * sanitizeDisplayText('Q\rOVERWRITE\nNEXT\tX') // 'QOVERWRITENEXTX' * ``` */ export declare function sanitizeDisplayText(text: string): string; /** * Sanitizes every terminal-readable string in a parsed form schema, keeping every identity and * answer string verbatim and dropping field metadata. * * @remarks * Display strings pass through {@link sanitizeDisplayText}: labels, help, placeholders, masks, * choice labels and help, file accept entries, and pattern sources. Identity and answer strings * stay verbatim: schema, group, and field names, group references, choice values, and defaults. * Rewriting those would sever the rendering copy from the authoritative form. Field metadata is * removed because terminal neither renders nor interprets it. Pattern sources are sanitized as * text only and are never compiled or executed here. * * @param schema - A schema already accepted by the Form package's `parseForm` * @returns A new schema with terminal-readable strings sanitized and field metadata omitted * * @example * ```ts * sanitizeSchema({ fields: [{ control: 'text', name: 'na\u001bme', label: 'N\u0000ame' }] }) * // { fields: [{ control: 'text', name: 'na\u001bme', label: 'Name' }] } * ``` */ export declare function sanitizeSchema(schema: FormSchema): FormSchema; /** * Sanitizes every glyph a wire-supplied {@link PromptThemeOptions} carries for a single-line display * slot. Only the icons need it: a role is guard-narrowed to a console {@link Style}, whose colors * and attributes are fixed name sets, so no role can carry a byte a terminal would act on. * * @param theme - The narrowed theme options a remote prompt supplied * @returns The same theme with every supplied glyph sanitized for a single-line display slot */ export declare function sanitizeThemeIcons(theme: PromptThemeOptions): PromptThemeOptions; /** * Represents the immutable state a select field's reducer carries — the choices the list offers and * the index the cursor sits on. * * @remarks * - `message` — the sanitized label the header renders. * - `choices` — the choices the list offers, in declared order. * - `styler` / `theme` — the console styler and the resolved {@link PromptTheme}. * - `focused` — the index the cursor sits on, pre-placed on the declared default. */ export declare interface SelectState { readonly message: string; readonly choices: readonly FieldChoice[]; readonly styler: StylerInterface; readonly theme: PromptTheme; readonly focused: number; } /** * Holds the exact escape sequence to canonical key name table * {@link import('./helpers.js').parseKey} consults for the navigation and editing keys. Covers the * CSI form (`ESC[A`…) and the SS3 form (`ESCOA`…) of the arrows, plus the `home` / `end` / `delete` * CSI sequences with their numeric-tilde variants. The source of truth for the multi-byte key * decode; frozen. * * @remarks * Terminals disagree on these: a cursor key is `ESC[A` (normal) or `ESCOA` (application mode), * and Home / End / Delete each have a letter form (`ESC[H` / `ESC[F`) and a numeric form * (`ESC[1~` / `ESC[4~` / `ESC[3~`). Every accepted spelling maps to one name so a reducer never * sees the wire encoding. */ export declare const SEQUENCE_NAMES: Readonly>; /** * Serializes the `destroy` {@link WireEvent} a broker or manager sends when it is going away, which * carries no payload. * * @returns The `destroy` frame, whose `data` is empty because the signal carries no payload */ export declare function serializeDestroy(): WireEvent; /** * Serializes a parked form's expiry or release into an `expire` {@link WireEvent}, whose `data` is * the JSON `{ id }` payload. * * @param id - The id of the parked form that expired or was released * @returns The `expire` frame, carrying the JSON-stringified `{ id }` payload as `data` */ export declare function serializeExpire(id: string): WireEvent; /** * Serializes a parked {@link PendingForm} into a `pending` {@link WireEvent}, whose frame `id` is * the form's own id. * * @param form - The parked form's wire-safe record * @returns The `pending` frame — the JSON-stringified record as `data`, and the form's own `id` */ export declare function serializePending(form: PendingForm): WireEvent; /** Names the space byte (U+0020). */ export declare const SPACE = " "; /** * Sets the maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its * SSE parser buffer before treating the stream as hostile — 1 MiB, comfortably above any * legitimate prompt payload. Passed as the `limit` to `createSSEParser` so an unterminated * or oversized `data:` field cannot grow the buffer without bound (a memory-exhaustion guard). */ export declare const SSE_BUFFER_LIMIT = 1048576; /** * Holds the SSE `event:` names the broker emits and the * {@link import('./types.js').PromptClientInterface} dispatches on — `pending`, `expire`, and * `destroy`. Frozen; the source of truth for the wire event vocabulary. * * @remarks * - `pending` — a serialized {@link import('./types.js').PendingForm} to dispatch and answer. * - `expire` — an `{ id }` payload: the broker expired or released a parked form (the client drops it). * - `destroy` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable. */ export declare const SSE_EVENTS: Readonly<{ pending: "pending"; expire: "expire"; destroy: "destroy"; }>; /** Names the tab byte (`\t`, U+0009). */ export declare const TAB: string; /** * Explains why a {@link TerminalManagerInterface.answer} call refused — an {@link AnswerError} from the * endpoint's own broker, or `target` when no endpoint is mounted under that name. That is the same * condition {@link TerminalErrorCode}'s `TARGET` names for {@link TerminalManagerInterface.ask}, so * one word carries it on both doors. One discriminant, `reason`, across every member. */ export declare type TerminalAnswerError = AnswerError | { readonly reason: 'target'; }; /** * Represents the error the terminal surfaces for its own refusals: parking on a destroyed or full broker, an * unusable driver stream, a manager routing fault, or a ctrl-c cancellation. A parked form's own * lifecycle failures reject through the form's `answer` with the form package's error, never with * this one. * * @remarks * Carries a {@link TerminalErrorCode} and an optional `context` bag naming the offending values: * `{ cap }` on `LIMIT`, `{ to, known }` on `TARGET`, and `{ from, to, path }` on `DEADLOCK`. Narrow * a caught value with {@link isTerminalError} and branch on `error.code`. */ export declare class TerminalError extends Error { /** Holds the machine-readable condition — see {@link TerminalErrorCode}. */ readonly code: TerminalErrorCode; /** Holds an optional context bag naming the offending values — see the class {@link TerminalError remarks}. */ readonly context?: Readonly>; /** * Builds one terminal refusal. * * @param code - The machine-readable {@link TerminalErrorCode} a caller branches on * @param message - The human-readable reason * @param context - The optional bag naming the offending values — see the class {@link TerminalError remarks} */ constructor(code: TerminalErrorCode, message: string, context?: Readonly>); } /** * Names the machine-readable condition carried by a {@link import('./errors.js').TerminalError} — the * axis a `catch` branches on. Names its axis (the failure condition), never `kind`. * * @remarks * Every code here is terminal's own. A refusal that belongs to the form — a malformed schema, a * value a control cannot hold, a write to a settled form — arrives as the dependency's own * `FormError` and is never re-coded. * * - `EXPIRE` — `park` was called on an already-`destroy`ed broker: the given form is destroyed and * the call throws before minting an id. A parked form that times out is abandoned instead, not * coded `EXPIRE` — the broker destroys it and the caller's `answer` promise rejects on the * form's own lifecycle with the Form package's `ABANDONED` error. * - `CANCEL` — the user aborted at the server `Terminal` driver with ctrl-c. * - `DRIVER` — the driver could not read the terminal it was given. * - `DEADLOCK` — an endpoint was asked to answer its own question. * - `TARGET` — the named endpoint cannot be reached. * - `LIMIT` — the broker's optional `cap` on concurrently parked forms was already reached. The * new call is refused WITHOUT parking: no id, no `pending` event, no timer. * - `DESTROYED` — a call reached an already-destroyed {@link TerminalManagerInterface}. */ export declare type TerminalErrorCode = 'EXPIRE' | 'CANCEL' | 'DRIVER' | 'DEADLOCK' | 'TARGET' | 'LIMIT' | 'DESTROYED'; /** * Declares the contract for asking a form of a human at a keyboard — `ask` and nothing beside it, * because a form is one question however many fields it holds. The server `Terminal` implements it * against a real TTY; a {@link PromptClientInterface} holds one to answer forms parked elsewhere. * * @remarks * `ask` drives the form the caller passes: it walks the schema's fields in order, binds each * keystroke through the form's own `fill`, submits, and returns the settled values. The returned * promise is the form's `answer`, so a caller holding the form can await either one. Ctrl-c at an * interactive driver is the one exception: it rejects this promise with a `TerminalError` coded * `CANCEL` and leaves the form `editing`, so the form's own `answer` stays pending for its owner. * * A driver never owns the form's lifetime. To interrupt an active walk, destroy the form: it * abandons, `answer` rejects, and the driver stops rendering on the form's `abandon` event. That * is the only cancellation channel, which is why this contract needs no second method. */ export declare interface TerminalInterface { /** * Walks the given form to settlement and resolves its values. The Contract section names the * ctrl-c exception. */ ask(form: FormInterface): Promise; } /** * Registers named {@link PromptInterface} brokers, one per endpoint, so several parties can `ask` * forms of each other by name with a `from` → `to` attribution edge on every parked form, and * refuses `DEADLOCK` on a transitive cycle across every in-flight ask. * * @remarks * - **Registry.** `add(name, options?)` mints (or, if `name` is already mounted, returns the * existing broker unchanged — idempotent, never clobbers a live/parked endpoint). Every mounted * broker's `pending` / `answer` / `expire` events are re-emitted on the manager, attributed by * `name`. * - **`ask`.** The target must already be mounted through {@link add} — `ask` never auto-adds it; * rejects `TARGET` for an unknown `to` (listing the known names). Rejects `DEADLOCK` when parking * `from → to` would close a cycle over the current in-flight edge set (walked transitively); * otherwise parks the caller's live form through the target's broker and returns that form's own * `answer` promise. Edge cleanup never alters the value or rejection the caller observes. * - **Durable open / save.** `open(name)` restores an EMPTY broker from the `store` (parked * Promises are process-bound and never resurrected); `save(name)` persists the endpoint's * configured `timeout`. * - **Removal.** `remove` drops one endpoint, a batch (the array overload declared first), or every * endpoint when called without an argument. It destroys each broker, which expires every form * still parked on it. `destroy` is idempotent. * * @example * ```ts * const form = createForm({ fields: [{ control: 'text', name: 'name' }] }) * const manager = new TerminalManager() * manager.add('agent') * const answer = manager.ask('user', 'agent', form) * manager.answer('agent', manager.pending('agent')[0].id, { name: 'Ada' }) * await answer // { name: 'Ada' } * ``` */ export declare class TerminalManager implements TerminalManagerInterface { #private; constructor(options?: TerminalManagerOptions); get emitter(): EmitterInterface; get count(): number; terminal(name: string): PromptInterface | undefined; terminals(): readonly PromptInterface[]; add(name: string, options?: PromptOptions): PromptInterface; ask(from: string, to: string, form: FormInterface): Promise; pending(): readonly PendingForm[]; pending(to: string): readonly PendingForm[]; answer(to: string, id: string, values: FormValues): Result; open(name: string): Promise; save(name: string): Promise; remove(names: readonly string[]): boolean; remove(name: string): boolean; remove(): void; destroy(): void; } /** * Declares the manager's event map — the name-attributed re-emission of every mounted broker's * events, so a caller subscribes once for every endpoint instead of once per broker. * * @remarks * - `pending` — an endpoint parked a form; the record itself carries `from` and `to`. * - `answer` — an endpoint's parked form was answered; `to` names the endpoint. * - `expire` — an endpoint's parked form expired; `to` names the endpoint. */ export declare type TerminalManagerEventMap = { readonly pending: readonly [form: PendingForm]; readonly answer: readonly [to: string, id: string, values: FormValues]; readonly expire: readonly [to: string, id: string]; }; /** * Declares a registry of named {@link PromptInterface} brokers, one per endpoint, so several * parties (agents, tools, humans) can ask forms of each other by name, attributed with a `from` → * `to` edge on every parked record. * * @remarks * - **Accessors.** `terminal(name)` looks up one endpoint's broker; `terminals()` lists every * mounted broker, in insertion order. `terminals()` returns brokers, not keys; a name is an * argument the `terminal`, `add`, `ask`, `pending`, `answer`, `open`, `save`, and `remove` * methods take. * - **`add`** mints, or returns, the broker for `name`. Idempotent; it never clobbers a live * endpoint. * - **`ask`** is the attributed convenience: it parks `form` from `from` to `to` and resolves with * the settled values. It never mounts `to` — an unmounted target rejects with a * {@link import('./errors.js').TerminalError} coded `TARGET`, so `add` the endpoint first. * - **`pending()`** lists every endpoint's parked records; `pending(to)` scopes to one endpoint. * - **`answer`** routes to the named endpoint's broker. * - **`open`** restores, or returns the live, broker for `name` from the `store`. * - **`save`** persists an endpoint's config snapshot; false when there is no store, or `name` is * unknown. * - **Batch `remove`.** The array overload is declared first: `remove(names)` removes every listed * endpoint and reports true only when all of them were mounted; `remove(name)` removes one; * `remove()` removes every endpoint without destroying the manager. * - **`destroy`** tears down every broker, then the manager's own emitter. */ export declare interface TerminalManagerInterface { /** * Holds the typed emitter every mounted broker's events are re-published on, attributed by name. */ readonly emitter: EmitterInterface; /** Reports how many endpoints are mounted. */ readonly count: number; /** Looks up one endpoint's broker by name. */ terminal(name: string): PromptInterface | undefined; /** Lists every mounted broker, in insertion order. */ terminals(): readonly PromptInterface[]; /** * Mints, or returns unchanged, the broker for `name`. Idempotent; it never clobbers a live * endpoint. */ add(name: string, options?: PromptOptions): PromptInterface; /** * Parks `form` from `from` to `to` and resolves with the settled values. Rejects `TARGET` or * `DEADLOCK`. */ ask(from: string, to: string, form: FormInterface): Promise; /** * Lists every endpoint's parked records (`pending()`), or scopes to one endpoint (`pending(to)`). */ pending(): readonly PendingForm[]; pending(to: string): readonly PendingForm[]; /** * Routes an answer to the named endpoint's broker; `{ reason: 'target' }` when no endpoint * carries that name. */ answer(to: string, id: string, values: FormValues): Result; /** * Returns the live broker for `name`, or restores an empty one from the `store`. Parked forms are * never resurrected. */ open(name: string): Promise; /** Persists an endpoint's config snapshot; false with no store, or an unknown name. */ save(name: string): Promise; /** * Removes a batch (`remove(names)`, the array overload declared first, true only when every name * was mounted), one endpoint, or every endpoint. */ remove(names: readonly string[]): boolean; remove(name: string): boolean; remove(): void; /** Tears down every broker, then the manager's own emitter. */ destroy(): void; } /** * Configures {@link import('./factories.js').createTerminalManager} and the * {@link TerminalManagerInterface}. * * @remarks * - `store` — the optional {@link TerminalStoreInterface} backing `open` and `save`. * - `timeout` / `timer` / `cap` — the manager-wide default for each endpoint's broker, overridable * per {@link TerminalManagerInterface.add} call. * - `on` / `error` — the manager's own emitter hooks and listener-error handler. */ export declare interface TerminalManagerOptions { readonly store?: TerminalStoreInterface; readonly timeout?: number; readonly timer?: TimerHandler_2; readonly cap?: number; readonly on?: EmitterHooks; readonly error?: EmitterErrorHandler; } /** * Represents one endpoint's persisted config snapshot — `id` is the endpoint name and `timeout` its * configured default. Parked forms are process-bound and are never resurrected, so `open` always * restores an empty broker. */ export declare interface TerminalSnapshot { readonly id: string; readonly timeout?: number; } /** * Represents one opaque persisted row — the shape a table-backed store reads and writes. The store * is a `TableInterface`, and `snapshot` is narrowed with * {@link import('./validators.js').isTerminalSnapshot} on read. */ export declare interface TerminalSnapshotRow { readonly id: string; readonly snapshot: unknown; } /** * Declares the point-access persistence seam for a {@link TerminalManagerInterface}'s endpoint configs. * Every primitive is async; deleting an absent id is a no-op. */ export declare interface TerminalStoreInterface { /** Resolves the snapshot stored for `id`, or `undefined` when none is. */ get(id: string): Promise; /** Inserts or replaces under the snapshot's own `id`; there is no id argument. */ set(snapshot: TerminalSnapshot): Promise; /** Drops a snapshot by id. An absent id is a no-op, never a throw. */ delete(id: string): Promise; } /** Cancels a pending {@link TimerHandler} deadline — idempotent, safe to call after the timer fired. */ export declare type TimerCancelFunction = () => void; /** * Represents one injected timer — arms a deadline `callback` to fire after `ms`, returning a * {@link TimerCancelFunction} that cancels it. The broker's timeout seam: the default wraps the host * `setTimeout` and `clearTimeout`; a test injects a deterministic timer that captures the callback * and fires it on demand, with no real time and no global patching. */ declare type TimerHandler_2 = (callback: () => void, ms: number) => TimerCancelFunction; export { TimerHandler_2 as TimerHandler } /** * Toggles `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion * list; the primitive {@link reduceCheckbox} calls. * * @param indices - The ticked indices, in tick order * @param index - The index to add when absent, or drop when present * @returns A new list carrying the toggled membership; the input is never mutated */ export declare function toggleIndex(indices: readonly number[], index: number): readonly number[]; /** * Represents one SSE-shaped wire frame — the `event` name, its already-stringified `data` payload, and an * optional `id`. The transport-neutral shape {@link import('./helpers.js').serializePending}, * {@link import('./helpers.js').serializeExpire}, and * {@link import('./helpers.js').serializeDestroy} build, with no `http` dependency. */ export declare interface WireEvent { readonly event: string; readonly data: string; readonly id?: string; } export { }