@velum-labs/routekit-cli-ui 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/plain.js ADDED
@@ -0,0 +1,291 @@
1
+ /**
2
+ * The plain-text presenter: ordered, deterministic line logs for CI, pipes,
3
+ * `ROUTEKIT_NO_TUI=1`, and the `node --test` subprocess suites. Live surfaces
4
+ * print one line per state transition instead of animating in place.
5
+ */
6
+ import { contentWidth, formatBytes, wrapText } from "./format.js";
7
+ import { uiStream } from "./runtime.js";
8
+ import { bold, box, brandBanner, brandHeader, cyan, dim, glyph, gray, green, red, stripAnsi, yellow } from "./theme.js";
9
+ function statusGlyph(kind) {
10
+ switch (kind) {
11
+ case "ok":
12
+ return green(glyph.tick());
13
+ case "warn":
14
+ return yellow(glyph.warn());
15
+ case "fail":
16
+ return red(glyph.cross());
17
+ case "info":
18
+ return cyan(glyph.bullet());
19
+ case "pending":
20
+ return gray(glyph.bullet());
21
+ default: {
22
+ const exhaustive = kind;
23
+ throw new Error(`unknown status kind: ${String(exhaustive)}`);
24
+ }
25
+ }
26
+ }
27
+ function stepGlyph(status) {
28
+ switch (status) {
29
+ case "pending":
30
+ return gray(glyph.pending());
31
+ case "active":
32
+ return dim(glyph.arrow());
33
+ case "done":
34
+ return green(glyph.tick());
35
+ case "failed":
36
+ return red(glyph.cross());
37
+ case "skipped":
38
+ return yellow(glyph.bullet());
39
+ default: {
40
+ const exhaustive = status;
41
+ throw new Error(`unknown step status: ${String(exhaustive)}`);
42
+ }
43
+ }
44
+ }
45
+ /** Pad table cells against their visible (ANSI-stripped) width. */
46
+ function padCell(text, width, align = "left") {
47
+ const pad = " ".repeat(Math.max(0, width - stripAnsi(text).length));
48
+ return align === "right" ? pad + text : text + pad;
49
+ }
50
+ export function renderTableLines(rows, options = {}) {
51
+ const all = options.head !== undefined ? [options.head.map((cell) => dim(cell)), ...rows] : [...rows];
52
+ if (all.length === 0)
53
+ return [];
54
+ const columns = Math.max(...all.map((row) => row.length));
55
+ const widths = Array.from({ length: columns }, (_, index) => Math.max(...all.map((row) => stripAnsi(row[index] ?? "").length)));
56
+ const indent = " ".repeat(options.indent ?? 0);
57
+ return all.map((row) => (indent +
58
+ row.map((cell, index) => padCell(cell, widths[index] ?? 0, options.align?.[index] ?? "left")).join(" ")).trimEnd());
59
+ }
60
+ /**
61
+ * Render the failure panel as styled lines: a red-framed box with the message,
62
+ * dim evidence lines, the hint, and a `try:` next command. Shared by the plain
63
+ * and Ink presenters (identical settled output) and reused by the top-level
64
+ * error handler.
65
+ */
66
+ export function renderErrorPanelLines(input) {
67
+ const width = contentWidth();
68
+ const body = wrapText(input.message, width).map((line) => red(line));
69
+ if (input.details !== undefined && input.details.length > 0) {
70
+ body.push("");
71
+ for (const detail of input.details) {
72
+ for (const line of wrapText(detail, width - 2))
73
+ body.push(dim(` ${line}`));
74
+ }
75
+ }
76
+ if (input.hint !== undefined) {
77
+ body.push("");
78
+ for (const line of wrapText(input.hint, width))
79
+ body.push(line);
80
+ }
81
+ if (input.tryCommand !== undefined) {
82
+ body.push("");
83
+ body.push(`${dim("try:")} ${cyan(input.tryCommand)}`);
84
+ }
85
+ if (input.docs !== undefined) {
86
+ if (input.tryCommand === undefined)
87
+ body.push("");
88
+ body.push(`${dim("docs:")} ${dim(input.docs)}`);
89
+ }
90
+ return box(input.title ?? "error", body, { tone: "error" }).split("\n");
91
+ }
92
+ export function renderKeyValueLines(rows) {
93
+ const labelWidth = Math.max(0, ...rows.map((row) => row.label.length));
94
+ const valueWidth = Math.max(0, ...rows.map((row) => stripAnsi(row.value).length));
95
+ return rows.map((row) => {
96
+ const indent = " ".repeat(2 + (row.indent ?? 0) * 2);
97
+ const label = dim(row.label.padEnd(labelWidth));
98
+ const value = row.tag !== undefined ? padCell(row.value, valueWidth) : row.value;
99
+ const tag = row.tag !== undefined ? ` ${dim(row.tag)}` : "";
100
+ return `${indent}${label} ${value}${tag}`.trimEnd();
101
+ });
102
+ }
103
+ class PlainChecklist {
104
+ steps;
105
+ write;
106
+ constructor(steps, title, write) {
107
+ this.steps = new Map(steps.map((step) => [step.id, { label: step.label }]));
108
+ this.write = write;
109
+ if (title !== undefined)
110
+ write(title);
111
+ }
112
+ transition(id, status, detail) {
113
+ const step = this.steps.get(id);
114
+ if (step === undefined)
115
+ throw new Error(`unknown step id: ${id}`);
116
+ if (detail !== undefined)
117
+ step.detail = detail;
118
+ const suffix = step.detail !== undefined ? ` ${dim(step.detail)}` : "";
119
+ this.write(`${stepGlyph(status)} ${step.label}${suffix}`);
120
+ }
121
+ setActive(id, detail) {
122
+ this.transition(id, "active", detail);
123
+ }
124
+ setDone(id, detail) {
125
+ this.transition(id, "done", detail);
126
+ }
127
+ setFailed(id, detail) {
128
+ this.transition(id, "failed", detail);
129
+ }
130
+ setSkipped(id, detail) {
131
+ this.transition(id, "skipped", detail);
132
+ }
133
+ setDetail(id, detail) {
134
+ const step = this.steps.get(id);
135
+ if (step === undefined)
136
+ throw new Error(`unknown step id: ${id}`);
137
+ step.detail = detail;
138
+ }
139
+ stop() {
140
+ // line-per-transition output needs no settling
141
+ }
142
+ }
143
+ class PlainTask {
144
+ text;
145
+ write;
146
+ constructor(text, write) {
147
+ this.text = text;
148
+ this.write = write;
149
+ write(`${dim(glyph.arrow())} ${text}`);
150
+ }
151
+ update(text) {
152
+ this.text = text;
153
+ this.write(`${dim(glyph.arrow())} ${text}`);
154
+ }
155
+ succeed(text) {
156
+ this.write(`${green(glyph.tick())} ${text ?? this.text}`);
157
+ }
158
+ fail(text) {
159
+ this.write(`${red(glyph.cross())} ${text ?? this.text}`);
160
+ }
161
+ warn(text) {
162
+ this.write(`${yellow(glyph.warn())} ${text ?? this.text}`);
163
+ }
164
+ info(text) {
165
+ this.write(`${cyan(glyph.bullet())} ${text ?? this.text}`);
166
+ }
167
+ stop() {
168
+ // nothing to settle
169
+ }
170
+ }
171
+ class PlainProgress {
172
+ label;
173
+ write;
174
+ downloaded = 0;
175
+ total;
176
+ /** Last milestone (in 10% steps) printed, so logs stay short. */
177
+ lastMilestone = -1;
178
+ constructor(label, write) {
179
+ this.label = label;
180
+ this.write = write;
181
+ write(`${dim(glyph.arrow())} ${label}`);
182
+ }
183
+ update(progress) {
184
+ this.downloaded = progress.downloaded;
185
+ if (progress.total !== undefined && progress.total > 0)
186
+ this.total = progress.total;
187
+ if (this.total === undefined)
188
+ return;
189
+ const pct = Math.floor((this.downloaded / this.total) * 10) * 10;
190
+ if (pct > this.lastMilestone && pct < 100) {
191
+ this.lastMilestone = pct;
192
+ this.write(` ${dim(`${pct}% — ${formatBytes(this.downloaded)} / ${formatBytes(this.total)}`)}`);
193
+ }
194
+ }
195
+ succeed(text) {
196
+ this.write(`${green(glyph.tick())} ${text ?? `${this.label} ${dim(`(${formatBytes(this.downloaded)})`)}`}`);
197
+ }
198
+ fail(text) {
199
+ this.write(`${red(glyph.cross())} ${text ?? `${this.label} ${gray("(failed)")}`}`);
200
+ }
201
+ stop() {
202
+ // nothing to settle
203
+ }
204
+ }
205
+ class PlainLiveFrame {
206
+ write;
207
+ stopped = false;
208
+ constructor(write) {
209
+ this.write = write;
210
+ }
211
+ render(content) {
212
+ if (this.stopped)
213
+ return;
214
+ this.write(dim(`[${new Date().toISOString()}]`));
215
+ for (const line of typeof content === "function" ? content() : content)
216
+ this.write(line);
217
+ }
218
+ stop() {
219
+ this.stopped = true;
220
+ }
221
+ }
222
+ export class PlainPresenter {
223
+ interactive = false;
224
+ stream;
225
+ constructor(stream = uiStream()) {
226
+ this.stream = stream;
227
+ }
228
+ writeLine = (line) => {
229
+ this.stream.write(`${line}\n`);
230
+ };
231
+ banner(subtitle) {
232
+ this.writeLine(brandBanner(subtitle));
233
+ }
234
+ header(subtitle) {
235
+ this.writeLine(brandHeader(subtitle));
236
+ }
237
+ heading(text) {
238
+ this.writeLine(bold(text));
239
+ }
240
+ line(text) {
241
+ this.writeLine(text);
242
+ }
243
+ blank() {
244
+ this.stream.write("\n");
245
+ }
246
+ note(text) {
247
+ this.writeLine(`${gray(glyph.arrow())} ${text}`);
248
+ }
249
+ success(text) {
250
+ this.writeLine(`${green(glyph.tick())} ${text}`);
251
+ }
252
+ warn(text) {
253
+ this.writeLine(`${yellow(glyph.warn())} ${text}`);
254
+ }
255
+ error(text) {
256
+ this.writeLine(`${red(glyph.cross())} ${text}`);
257
+ }
258
+ status(kind, label, detail, hint) {
259
+ const suffix = detail !== undefined ? ` ${dim(detail)}` : "";
260
+ this.writeLine(` ${statusGlyph(kind)} ${label}${suffix}`);
261
+ if (hint !== undefined)
262
+ this.writeLine(` ${yellow(glyph.arrow())} ${hint}`);
263
+ }
264
+ keyValue(rows) {
265
+ for (const line of renderKeyValueLines(rows))
266
+ this.writeLine(line);
267
+ }
268
+ table(rows, options) {
269
+ for (const line of renderTableLines(rows, options))
270
+ this.writeLine(line);
271
+ }
272
+ box(title, lines) {
273
+ this.writeLine(box(title, [...lines]));
274
+ }
275
+ errorPanel(input) {
276
+ for (const line of renderErrorPanelLines(input))
277
+ this.writeLine(line);
278
+ }
279
+ checklist(steps, options = {}) {
280
+ return new PlainChecklist(steps, options.title, this.writeLine);
281
+ }
282
+ task(text) {
283
+ return new PlainTask(text, this.writeLine);
284
+ }
285
+ progress(label) {
286
+ return new PlainProgress(label, this.writeLine);
287
+ }
288
+ liveFrame() {
289
+ return new PlainLiveFrame(this.writeLine);
290
+ }
291
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The presenter contract every command renders through. Two
3
+ * implementations exist:
4
+ *
5
+ * - `InkPresenter` — rich Ink (React) rendering on an interactive TTY.
6
+ * - `PlainPresenter` — ordered line logs for CI, pipes, and `ROUTEKIT_NO_TUI`.
7
+ *
8
+ * Both write exclusively to stderr (`uiStream()`); stdout stays reserved for
9
+ * machine payloads (`--json`, `config path`, `export-yaml`) and tool output.
10
+ * Live surfaces (checklist / task / progress) return controllers; a command
11
+ * must settle a live surface before printing static lines so output never
12
+ * interleaves with an active Ink render.
13
+ */
14
+ export type StepStatus = "pending" | "active" | "done" | "failed" | "skipped";
15
+ export type StepInput = {
16
+ id: string;
17
+ label: string;
18
+ };
19
+ export type ChecklistController = {
20
+ setActive(id: string, detail?: string): void;
21
+ setDone(id: string, detail?: string): void;
22
+ setFailed(id: string, detail?: string): void;
23
+ setSkipped(id: string, detail?: string): void;
24
+ setDetail(id: string, detail: string): void;
25
+ /** Settle the checklist and leave the final frame in place. */
26
+ stop(): void;
27
+ };
28
+ export type TaskController = {
29
+ update(text: string): void;
30
+ succeed(text?: string): void;
31
+ fail(text?: string): void;
32
+ warn(text?: string): void;
33
+ info(text?: string): void;
34
+ stop(): void;
35
+ };
36
+ export type ProgressUpdate = {
37
+ downloaded: number;
38
+ total?: number;
39
+ file?: string;
40
+ };
41
+ export type ProgressController = {
42
+ update(progress: ProgressUpdate): void;
43
+ succeed(text?: string): void;
44
+ fail(text?: string): void;
45
+ stop(): void;
46
+ };
47
+ export type LiveFrameContent = readonly string[] | (() => readonly string[]);
48
+ export type LiveFrameController = {
49
+ /** Replace the current frame. Plain output appends a timestamped snapshot. */
50
+ render(content: LiveFrameContent): void;
51
+ /** Render a failed refresh. Quiet presenters may surface only this frame. */
52
+ renderError?(content: LiveFrameContent): void;
53
+ /** Settle the frame and release any terminal resources. Idempotent. */
54
+ stop(): void;
55
+ };
56
+ /** One row of a `keyValue` block: label, rendered value, optional dim tag. */
57
+ export type KeyValueRow = {
58
+ label: string;
59
+ value: string;
60
+ tag?: string;
61
+ indent?: number;
62
+ };
63
+ export type TableOptions = {
64
+ /** Column headers (rendered dim). */
65
+ head?: string[];
66
+ /** Indentation (spaces) applied to every row. */
67
+ indent?: number;
68
+ /** Per-column alignment (numbers read best right-aligned). Defaults to left. */
69
+ align?: readonly ("left" | "right")[];
70
+ };
71
+ /**
72
+ * One error, three renderings: a red-framed panel on rich/plain UI, prefixed
73
+ * lines when boxes would be noise, and the same fields in `--json` payloads.
74
+ */
75
+ export type ErrorPanelInput = {
76
+ /** Panel title (defaults to "error"). */
77
+ title?: string;
78
+ message: string;
79
+ /** Supporting evidence, e.g. a distilled log tail (rendered dim). */
80
+ details?: readonly string[];
81
+ /** A human explanation of what likely went wrong / what to check. */
82
+ hint?: string;
83
+ /** A copy-pasteable next command, rendered as `→ try: <command>`. */
84
+ tryCommand?: string;
85
+ /** A docs URL for the failure area. */
86
+ docs?: string;
87
+ };
88
+ export type StatusKind = "ok" | "warn" | "fail" | "info" | "pending";
89
+ export interface Presenter {
90
+ /** True when this presenter renders rich (Ink) output. */
91
+ readonly interactive: boolean;
92
+ /** The full-dress brand banner (degrades to a one-line header when plain). */
93
+ banner(subtitle?: string): void;
94
+ /** The compact one-line brand header. */
95
+ header(subtitle?: string): void;
96
+ /** A bold section heading. */
97
+ heading(text: string): void;
98
+ /** A raw styled line. */
99
+ line(text: string): void;
100
+ /** An empty spacer line. */
101
+ blank(): void;
102
+ /** A dim informational note (arrow-prefixed). */
103
+ note(text: string): void;
104
+ /** A green tick line. */
105
+ success(text: string): void;
106
+ /** A yellow warning line. */
107
+ warn(text: string): void;
108
+ /** A red error line. */
109
+ error(text: string): void;
110
+ /** A status row: glyph + label + optional dim detail + optional hint line. */
111
+ status(kind: StatusKind, label: string, detail?: string, hint?: string): void;
112
+ /** Aligned label/value rows with optional provenance tags. */
113
+ keyValue(rows: readonly KeyValueRow[]): void;
114
+ /** A simple aligned table. */
115
+ table(rows: readonly (readonly string[])[], options?: TableOptions): void;
116
+ /** A titled rounded box. */
117
+ box(title: string, lines: readonly string[]): void;
118
+ /** A red-framed failure panel: message, evidence, hint, and the next command. */
119
+ errorPanel(input: ErrorPanelInput): void;
120
+ /** A live multi-step checklist. */
121
+ checklist(steps: readonly StepInput[], options?: {
122
+ title?: string;
123
+ }): ChecklistController;
124
+ /** A single live spinner task. */
125
+ task(text: string): TaskController;
126
+ /** A live byte-download progress bar. */
127
+ progress(label: string): ProgressController;
128
+ /** A generic replaceable region for status dashboards and watch commands. */
129
+ liveFrame(): LiveFrameController;
130
+ }
131
+ /** Run `work` under a task spinner, settling to success/failure automatically. */
132
+ export declare function withTask<T>(presenter: Presenter, text: string, work: () => Promise<T>, options?: {
133
+ success?: (value: T) => string;
134
+ failure?: (error: unknown) => string;
135
+ }): Promise<T>;
136
+ export type WatchOptions = {
137
+ signal?: AbortSignal;
138
+ errorFrame?: (error: unknown) => readonly string[];
139
+ };
140
+ /**
141
+ * Poll and redraw a live frame until Ctrl+C or an AbortSignal stops it.
142
+ * Fetch failures become frames and are retried on the next interval.
143
+ */
144
+ export declare function watch(presenter: Presenter, intervalSeconds: number, fetchAndRender: (signal: AbortSignal) => Promise<LiveFrameContent> | LiveFrameContent, options?: WatchOptions): Promise<void>;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * The presenter contract every command renders through. Two
3
+ * implementations exist:
4
+ *
5
+ * - `InkPresenter` — rich Ink (React) rendering on an interactive TTY.
6
+ * - `PlainPresenter` — ordered line logs for CI, pipes, and `ROUTEKIT_NO_TUI`.
7
+ *
8
+ * Both write exclusively to stderr (`uiStream()`); stdout stays reserved for
9
+ * machine payloads (`--json`, `config path`, `export-yaml`) and tool output.
10
+ * Live surfaces (checklist / task / progress) return controllers; a command
11
+ * must settle a live surface before printing static lines so output never
12
+ * interleaves with an active Ink render.
13
+ */
14
+ /** Run `work` under a task spinner, settling to success/failure automatically. */
15
+ export async function withTask(presenter, text, work, options = {}) {
16
+ const task = presenter.task(text);
17
+ try {
18
+ const value = await work();
19
+ task.succeed(options.success ? options.success(value) : text);
20
+ return value;
21
+ }
22
+ catch (error) {
23
+ task.fail(options.failure ? options.failure(error) : `${text} (failed)`);
24
+ throw error;
25
+ }
26
+ }
27
+ function waitForInterval(milliseconds, signal) {
28
+ return new Promise((resolve) => {
29
+ let settled = false;
30
+ let timer;
31
+ const finish = () => {
32
+ if (settled)
33
+ return;
34
+ settled = true;
35
+ if (timer !== undefined)
36
+ clearTimeout(timer);
37
+ signal.removeEventListener("abort", finish);
38
+ resolve();
39
+ };
40
+ timer = setTimeout(finish, milliseconds);
41
+ signal.addEventListener("abort", finish, { once: true });
42
+ if (signal.aborted)
43
+ finish();
44
+ });
45
+ }
46
+ function fetchFrame(fetchAndRender, signal) {
47
+ return new Promise((resolve, reject) => {
48
+ let settled = false;
49
+ const cleanup = () => signal.removeEventListener("abort", onAbort);
50
+ const succeed = (content) => {
51
+ if (settled)
52
+ return;
53
+ settled = true;
54
+ cleanup();
55
+ resolve(content);
56
+ };
57
+ const fail = (error) => {
58
+ if (settled)
59
+ return;
60
+ settled = true;
61
+ cleanup();
62
+ reject(error);
63
+ };
64
+ const onAbort = () => succeed(undefined);
65
+ signal.addEventListener("abort", onAbort, { once: true });
66
+ if (signal.aborted) {
67
+ onAbort();
68
+ return;
69
+ }
70
+ Promise.resolve()
71
+ .then(() => {
72
+ if (settled || signal.aborted)
73
+ return undefined;
74
+ return fetchAndRender(signal);
75
+ })
76
+ .then((content) => {
77
+ if (content !== undefined)
78
+ succeed(content);
79
+ }, fail);
80
+ });
81
+ }
82
+ /**
83
+ * Poll and redraw a live frame until Ctrl+C or an AbortSignal stops it.
84
+ * Fetch failures become frames and are retried on the next interval.
85
+ */
86
+ export async function watch(presenter, intervalSeconds, fetchAndRender, options = {}) {
87
+ const frame = presenter.liveFrame();
88
+ const localAbort = new AbortController();
89
+ const onSigint = () => localAbort.abort();
90
+ const onExternalAbort = () => localAbort.abort();
91
+ process.once("SIGINT", onSigint);
92
+ options.signal?.addEventListener("abort", onExternalAbort, { once: true });
93
+ if (options.signal?.aborted === true)
94
+ localAbort.abort();
95
+ const milliseconds = Math.max(0.1, intervalSeconds) * 1000;
96
+ try {
97
+ while (!localAbort.signal.aborted) {
98
+ try {
99
+ const content = await fetchFrame(fetchAndRender, localAbort.signal);
100
+ if (content === undefined || localAbort.signal.aborted)
101
+ break;
102
+ frame.render(content);
103
+ }
104
+ catch (error) {
105
+ if (localAbort.signal.aborted)
106
+ break;
107
+ const content = options.errorFrame?.(error) ?? [
108
+ `error: ${error instanceof Error ? error.message : String(error)}`,
109
+ `retrying in ${intervalSeconds}s`
110
+ ];
111
+ if (frame.renderError !== undefined)
112
+ frame.renderError(content);
113
+ else
114
+ frame.render(content);
115
+ }
116
+ await waitForInterval(milliseconds, localAbort.signal);
117
+ }
118
+ }
119
+ finally {
120
+ process.removeListener("SIGINT", onSigint);
121
+ options.signal?.removeEventListener("abort", onExternalAbort);
122
+ frame.stop();
123
+ }
124
+ }
@@ -0,0 +1,95 @@
1
+ export type SelectOption<T> = {
2
+ value: T;
3
+ label: string;
4
+ hint?: string;
5
+ };
6
+ /** Returned by prompts with `allowBack: true` when the user presses Esc. */
7
+ export declare const BACK: unique symbol;
8
+ export type Back = typeof BACK;
9
+ /**
10
+ * Single-choice selection. On a raw-capable TTY this is an Ink arrow-key
11
+ * picker with a live highlighted cursor; otherwise it falls back to a numbered
12
+ * prompt read from stdin (so piped input and non-raw terminals still work).
13
+ * Returns the default when input is empty or unparseable.
14
+ */
15
+ export declare function select<T>(input: {
16
+ message: string;
17
+ options: ReadonlyArray<SelectOption<T>>;
18
+ defaultIndex?: number;
19
+ allowBack: true;
20
+ }): Promise<T | Back>;
21
+ export declare function select<T>(input: {
22
+ message: string;
23
+ options: ReadonlyArray<SelectOption<T>>;
24
+ defaultIndex?: number;
25
+ }): Promise<T>;
26
+ /**
27
+ * Type-to-filter selection over a (possibly large or live-fetched) option
28
+ * list: fuzzy subsequence filtering with highlighted matches, arrow keys to
29
+ * move, enter to pick. `refresh` (when given) runs in the background while the
30
+ * picker is open — the list starts on the cached `options` and live-updates
31
+ * when fresh data lands (stale-while-revalidate). Falls back to the numbered
32
+ * prompt off-TTY, awaiting `refresh` first only when no cached options exist.
33
+ */
34
+ export declare function fuzzySelect<T>(input: {
35
+ message: string;
36
+ options: ReadonlyArray<SelectOption<T>>;
37
+ refresh?: () => Promise<ReadonlyArray<SelectOption<T>>>;
38
+ refreshNote?: string;
39
+ placeholder?: string;
40
+ allowBack: true;
41
+ }): Promise<T | Back>;
42
+ export declare function fuzzySelect<T>(input: {
43
+ message: string;
44
+ options: ReadonlyArray<SelectOption<T>>;
45
+ refresh?: () => Promise<ReadonlyArray<SelectOption<T>>>;
46
+ refreshNote?: string;
47
+ placeholder?: string;
48
+ }): Promise<T>;
49
+ /**
50
+ * Free text with an inline ghost suggestion completed from `suggestions`
51
+ * (Tab or → accepts). Falls back to the plain text prompt off-TTY.
52
+ */
53
+ export declare function autocompleteText(input: {
54
+ message: string;
55
+ suggestions: ReadonlyArray<string>;
56
+ defaultValue?: string;
57
+ placeholder?: string;
58
+ allowBack?: boolean;
59
+ }): Promise<string | Back>;
60
+ /**
61
+ * Multi-choice selection. On a raw-capable TTY this is an Ink checkbox list;
62
+ * otherwise it reads comma-separated numbers from stdin (empty input keeps the
63
+ * default selection).
64
+ */
65
+ export declare function multiselect<T>(input: {
66
+ message: string;
67
+ options: ReadonlyArray<SelectOption<T>>;
68
+ defaultSelected?: readonly number[];
69
+ }): Promise<T[]>;
70
+ /** Yes/no confirmation. Returns `defaultValue` on empty input. */
71
+ export declare function confirm(input: {
72
+ message: string;
73
+ defaultValue?: boolean;
74
+ allowBack: true;
75
+ }): Promise<boolean | Back>;
76
+ export declare function confirm(input: {
77
+ message: string;
78
+ defaultValue?: boolean;
79
+ }): Promise<boolean>;
80
+ /** Free-text prompt. Returns `defaultValue` (or "") on empty input. */
81
+ export declare function text(input: {
82
+ message: string;
83
+ defaultValue?: string;
84
+ placeholder?: string;
85
+ allowBack: true;
86
+ }): Promise<string | Back>;
87
+ export declare function text(input: {
88
+ message: string;
89
+ defaultValue?: string;
90
+ placeholder?: string;
91
+ }): Promise<string>;
92
+ /** A success line for the end of a wizard. */
93
+ export declare function done(message: string): void;
94
+ /** A neutral note line. */
95
+ export declare function note(message: string): void;