import type { Prompter } from "./prompter.js"; /** One choice in a select question. */ export interface SelectOption { /** Stable handle so external answers (flags, JSON) can address rich values. */ id: string; label: string; value: T; hint?: string; /** * A leading run of featured options forms a searchable picker's default * viewport: with no filter typed, only they are in view, and scrolling or * filtering reaches the rest of the list. Featured options must be sorted * to the front. Meaningless without `search`. */ featured?: boolean; } /** * One choice in a multi-select question. Row availability is part of the * question, not the renderer: an interactive picker renders the reason next to * the row, and {@link withAnswers} enforces the same semantics on external * answers (a disabled id is refused with its reason, a locked value is part of * every answer whether or not it was named). */ export interface MultiSelectOption extends SelectOption { /** Visible and explained but not pickable. Mutually exclusive with `locked`. */ disabled?: boolean; /** Why the row is disabled; shown by pickers and quoted by answer refusals. */ disabledReason?: string; /** Always selected and cannot be toggled off. */ locked?: boolean; /** Why the row is locked; shown by pickers (e.g. "always available"). */ lockedReason?: string; } /** * A keyed question a box sends through the channel. The key is the question's * stable identity: decorators match pre-supplied answers and requiredness * opt-ins against it, and resolutions are announced under it. */ export type Question = { key: string; message: string; /** Found in the world (e.g. an on-disk link). Feeds the confirm/assume rungs. */ detected?: T; /** A safe assumption (e.g. the most popular model). Used by "assume". */ recommended?: T; /** * Questions are skippable by default; required is the opt-in. Headless and * assume runs auto-skip non-required questions, and only a required question * may refuse a headless run ({@link InteractionRequired}). Set by the box * when intrinsic, or by the flow via {@link withRequired}. */ required?: boolean; /** * Synthesized by a decorator (e.g. the confirm-detected rung): interaction * mechanics, not a box value, so bases render it but do not announce it. */ internal?: boolean; } & ({ kind: "select"; options: readonly SelectOption[]; /** Offer the type-ahead filter line in interactive renderers. */ search?: boolean; /** Placeholder for the filter line while it is empty. */ placeholder?: string; } | { kind: "confirm"; } | { kind: "text"; validate?: (raw: string) => string | null; sensitive?: boolean; /** Presentation hint: ghost text shown while the input is empty. */ placeholder?: string; }); /** * A keyed multi-select question. Its answer is a set (`T[]`) while its options * carry single values (`T`), so it travels through the paired * {@link Asker.askMany} instead of being a {@link Question} kind: forcing it * through `ask` would make the channel lie about the answer type. */ export interface MultiSelectQuestion { key: string; message: string; options: readonly MultiSelectOption[]; /** Found in the world. Feeds the confirm/assume rungs and pre-marks pickers. */ detected?: readonly T[]; /** A safe assumption. Used by "assume"; pre-marks pickers without a detection. */ recommended?: readonly T[]; /** Same opt-in as {@link Question.required}: only it may refuse a headless run. */ required?: boolean; /** Same as {@link Question.internal}: rendered but never announced. */ internal?: boolean; /** * Block an empty submission in interactive pickers. Distinct from `required`, * which is the headless-refusal opt-in: a question can refuse to be skipped * headlessly while still accepting an empty interactive selection. */ requireSelection?: boolean; /** Offer the type-ahead filter line in interactive renderers. */ search?: boolean; /** Placeholder for the filter line while it is empty. */ placeholder?: string; } /** Builds a select question without spelling the discriminant at call sites. */ export declare const select: (q: Omit, { kind: "select"; }>, "kind">) => Question; /** Builds a confirm question without spelling the discriminant at call sites. */ export declare const confirm: (q: { key: string; message: string; detected?: boolean; recommended?: boolean; required?: boolean; internal?: boolean; }) => Question; /** Builds a text question without spelling the discriminant at call sites. */ export declare const text: (q: { key: string; message: string; detected?: string; recommended?: string; required?: boolean; validate?: (raw: string) => string | null; sensitive?: boolean; placeholder?: string; }) => Question; /** The capability boxes see. Pure and flow-agnostic: Prompter's successor. */ export interface Asker { ask(question: Question): Promise; /** * The multi-select channel. Paired with {@link ask} instead of being a * question kind because the answer type (`T[]`) differs from the option type * (`T`); every rung treats it with the same ladder semantics as `ask`. */ askMany(question: MultiSelectQuestion): Promise; } /** A ladder rung: wraps an asker and resolves (or rewrites) some questions. */ export type AskerDecorator = (inner: Asker) => Asker; /** Thrown when a skippable question is skipped, so the box can branch on it. */ export declare class SkippedSignal extends Error { readonly key: string; constructor(key: string); } /** Any question the channel can carry, for signals that quote one. */ export type AnyQuestion = Question | MultiSelectQuestion; /** * Headless refusal that keeps the whole question: an agent driver can relay * exactly what is missing (key, message, options) instead of a bare string. */ export declare class InteractionRequired extends Error { readonly question: AnyQuestion; constructor(question: AnyQuestion); } /** Thrown when a pre-supplied answer fails the question's own validation. */ export declare class InvalidAnswerError extends Error { readonly key: string; constructor(key: string, message: string); } /** How a question got its value, so nothing is silently assumed. */ export type ResolutionSource = "answer" | "detected" | "assumed" | "asked" | "skipped"; /** One announced question outcome. */ export interface Resolution { key: string; value: unknown; source: ResolutionSource; } /** Every resolution is announced with its source: nothing is silently assumed. */ export interface AskerEvents { onResolved?: (resolution: Resolution) => void; } /** * The interactive base: renders every question through the existing * {@link Prompter}, pre-filled with detected/recommended. A user cancel * surfaces as the prompter's own WizardCancelledError and propagates to the * runner unchanged. There is no skip gesture on non-required questions yet: * the current prompter has none, so it arrives with the prompter rework. */ export declare function interactiveAsker(prompter: Prompter, events?: AskerEvents): Asker; /** * The base for flag/agent stacks: auto-skips non-required questions (announced * as "skipped"), refuses required ones structurally with * {@link InteractionRequired}. */ export declare function headlessAsker(events?: AskerEvents): Asker; /** * Pre-answers by question key (flags, config, agent tool args), validated * against the question they answer. Unmatched questions fall through. */ export declare function withAnswers(answers: Record, events?: AskerEvents): AskerDecorator; /** * Flow-level opt-in to requiredness by key: the flow declares which questions * it cannot proceed without, boxes stay oblivious. Composes with box-intrinsic * `required` flags (whichever marks it, it is required). */ export declare function withRequired(keys: readonly string[]): AskerDecorator; /** The two detected/recommended rungs of the ladder. */ export type AnswerPolicy = "confirm-detected" | "assume"; /** * The detected/recommended rungs. "confirm-detected" turns a detected value * into a one-keystroke confirm (synthesized as an internal question through * the same channel); "assume" takes detected or recommended silently but * announced, skips the non-required unknowable, and escalates only required * unknowables to the inner asker. */ export declare function withPolicy(policy: AnswerPolicy, events?: AskerEvents): AskerDecorator;