import { type ChannelSetupAwaitChoice } from "#setup/cli/index.js"; import { type RailSpinner } from "#setup/cli/index.js"; /** * Clack constrains option values to readonly primitives. Our scaffold flow * only ever uses string-valued options (`ChannelKind`, `SetupMode`, * `ProviderConnection`, model id), so the tightened bound matches usage. */ export type PrompterValue = string | number | boolean; export interface SelectOption { value: T; label: string; hint?: string; /** Short inline annotation shown dimmed only while the cursor is on this row. */ focusHint?: string; /** * Longer, display-only explanation shown dimmed alongside the option while it * is highlighted during navigation. Hidden once a choice is submitted. */ description?: string; /** Cursor-pointer/active-label accent; "warning" turns them yellow for an attention row. */ accent?: "warning"; disabled?: boolean; /** Parenthetical shown after a disabled option's label explaining why. */ disabledReason?: string; /** * "warning" renders the disabled reason in yellow with a dimmed (not struck) * label: the row is unavailable here but actionable elsewhere (e.g. a channel * that needs a Vercel account points at /model), unlike the default disabled * styling, which marks a hard conflict. */ disabledReasonTone?: "warning"; /** * Completed work: renders with a check and remains cursor-addressable for * contextual feedback, but cannot be selected or toggled. */ completed?: boolean; /** * Marks a mandatory row that is always selected and cannot be toggled off; the * cursor skips it and it renders a dimmed check. Mutually exclusive with * `disabled`. */ locked?: boolean; /** Parenthetical shown after a locked option's label, e.g. "always available". */ lockedReason?: 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; } /** * An outcome line from an earlier lap of a looping menu, shown with the * repainted question: the TUI panel renders it beneath the options, the CLI * prints it to scrollback before the prompt. */ export interface SelectNotice { tone: "success" | "info" | "warning" | "error"; text: string; } /** Options common to every {@link Prompter.select} call. */ export interface SelectCommonOptions { message: string; options: SelectOption[]; /** * Add a type-ahead filter line. The filter is a case-insensitive substring * match against each option's label, value, and hint. */ search?: boolean; /** Placeholder shown in the filter line while it is empty (with `search`). */ placeholder?: string; /** * Require a selection before enter can confirm. For multi-select this blocks * an empty submission; single-select always resolves to the highlighted row, * so it is inherently satisfied. */ required?: boolean; /** * How option hints are laid out in the dev TUI panel (the CLI prompter ignores * it and keeps its default inline, unnumbered rendering). "stacked" renders * each hint on its own line below the label with a blank line between options — * for small action menus whose hints carry current values. "inline" keeps hints * on the label row, suppresses numeric shortcuts, and separates the trailing * completion action (e.g. the `/channels` task list). */ hintLayout?: "stacked" | "inline"; /** Outcome lines from earlier laps of a looping menu. */ notices?: readonly SelectNotice[]; } /** Single-select form: navigate, then enter picks the highlighted option. */ export interface SingleSelectOptions extends SelectCommonOptions { multiple?: false; /** Pre-position the cursor on the option whose value matches. */ initialValue?: T; } /** Multi-select form: space or enter toggles rows; enter on the trailing Submit row confirms the marked set. */ export interface MultiSelectOptions extends SelectCommonOptions { multiple: true; /** Pre-mark these values as selected. */ initialValues?: T[]; } /** Result of a single-select whose one row can be edited inline. */ export type EditableSelectResult = { kind: "selected"; value: T; } | { kind: "edited"; value: T; text: string; }; /** Inline-edit behavior for one row in an otherwise ordinary single-select. */ export interface EditableSelectOptions extends SingleSelectOptions { editable: { value: T; defaultValue: string; formatHint: (value: string) => string; validate?: (value: string) => string | undefined; }; } /** Color intent for {@link Prompter.note}: red warning (default) or green success. */ export type NoteTone = "warning" | "success"; /** Input for {@link Prompter.acknowledge}: a heading plus optional body lines. */ export interface AcknowledgeOptions { message: string; lines?: readonly string[]; } export interface Prompter { text(opts: { message: string; placeholder?: string; defaultValue?: string; validate?: (value: string) => string | undefined; /** * Context lines shown with the question (e.g. why it is being re-asked). * They live and die with the question: the TUI paints them inside the * question panel so they vanish on submit, the CLI prints them to * scrollback above the prompt. */ notices?: readonly SelectNotice[]; }): Promise; password(opts: { message: string; validate?: (value: string) => string | undefined; }): Promise; /** * Unified picker. Defaults to single-select (enter picks the highlighted row); * pass `multiple: true` for a checklist (space or enter toggles, enter on * the trailing Submit row confirms). Add * `search: true` for a type-ahead filter. `required` gates an empty * multi-select submission. */ select(opts: SingleSelectOptions): Promise; select(opts: MultiSelectOptions): Promise; /** * TUI enhancement for a select row whose secondary value becomes an inline * editor while focused. Typing and editing keys update it directly. Optional * so non-repainting prompt implementations can keep the ordinary * select-then-text fallback. */ selectEditable?(opts: EditableSelectOptions): Promise>; /** * Static instructions the user dismisses before the flow moves on. The TUI * renders them in the question slot of the flow panel (the text takes the * place of option rows) until enter; the CLI prints them to scrollback and * resolves immediately, since printed text persists there. Optional so * lightweight test fakes can omit it — flows fall back to {@link note}. */ acknowledge?(opts: AcknowledgeOptions): Promise; /** * Presents actions while a setup operation continues in the background. * The TUI implements it; plain and headless prompters omit it, so callers * fall back to waiting without concurrent controls. */ awaitChoice?: ChannelSetupAwaitChoice; /** * Rail-attached notice, no bullet — reads as a follow-up to the previous * step. Red by default (warnings, collisions); pass `tone: "success"` for a * green closing note like the one-shot next steps. */ note(message: string, title?: string, options?: { tone?: NoteTone; }): void; /** Prints the Vercel ▲ logo + title + subtitle banner. */ intro(title: string, subtitle?: string): void; /** Prints a final green ● end-cap with the message. */ outro(message: string): void; log: { message(text: string): void; info(text: string): void; success(text: string): void; warning(text: string): void; error(text: string): void; commandOutput(text: string): void; section?(title: string, lines: readonly string[]): void; /** * Starts a section-like braille spinner for a network or other async wait, * returning a handle whose `stop()` clears it. Present on the real prompter; * optional so lightweight test fakes can omit it. */ spinner?(message: string): RailSpinner; }; } /** * Builds a prompter with Vercel-branded styling — white-then-green vertical * rails, open/filled triangle bullets per step, and a colored corner * below the active prompt. Built on `@clack/core` so we own every glyph. * * The resolved-prompt count is scoped to the returned prompter so the * first prompt's leader rail is white and subsequent ones are green. */ export declare function createPrompter(): Prompter;