@crustjs/prompts 0.0.1

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +966 -0
  2. package/dist/index.js +1002 -0
  3. package/package.json +53 -0
@@ -0,0 +1,966 @@
1
+ /**
2
+ * Result of a fuzzy match against a single candidate.
3
+ */
4
+ interface FuzzyMatchResult {
5
+ /** Whether the candidate matches the query */
6
+ readonly match: boolean;
7
+ /** Match quality score — higher is better. 0 if no match. */
8
+ readonly score: number;
9
+ /** Indices of matched characters in the candidate string */
10
+ readonly indices: readonly number[];
11
+ }
12
+ /**
13
+ * A fuzzy-filtered item with its match metadata.
14
+ */
15
+ interface FuzzyFilterResult<T> {
16
+ /** The original item */
17
+ readonly item: {
18
+ readonly label: string;
19
+ readonly value: T;
20
+ };
21
+ /** Match quality score */
22
+ readonly score: number;
23
+ /** Indices of matched characters in the label */
24
+ readonly indices: readonly number[];
25
+ }
26
+ /**
27
+ * Test whether a query fuzzy-matches a candidate string.
28
+ *
29
+ * Each character in `query` must appear in `candidate` in order (but not
30
+ * necessarily contiguously). Matching is case-insensitive.
31
+ *
32
+ * The score rewards:
33
+ * - Consecutive matched characters (contiguity bonus)
34
+ * - Matches at the start of the string
35
+ * - Matches at word boundaries (after space, `-`, `_`, `.`)
36
+ *
37
+ * @param query - The search query
38
+ * @param candidate - The string to match against
39
+ * @returns Match result with boolean, score, and matched character indices
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * fuzzyMatch("abc", "alphabet cookie"); // { match: true, score: ..., indices: [0, 9, 10] }
44
+ * fuzzyMatch("xyz", "hello"); // { match: false, score: 0, indices: [] }
45
+ * fuzzyMatch("", "anything"); // { match: true, score: 0, indices: [] }
46
+ * ```
47
+ */
48
+ declare function fuzzyMatch(query: string, candidate: string): FuzzyMatchResult;
49
+ /**
50
+ * Filter and sort a list of labeled items by fuzzy-matching against a query.
51
+ *
52
+ * Returns only items that match, sorted by score descending (best matches first).
53
+ * An empty query returns all items (each with score 0 and empty indices).
54
+ *
55
+ * @param query - The search query
56
+ * @param items - Array of items with `label` and `value` properties
57
+ * @returns Matched items sorted by score (highest first)
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const results = fuzzyFilter("ts", [
62
+ * { label: "TypeScript", value: "ts" },
63
+ * { label: "JavaScript", value: "js" },
64
+ * { label: "Rust", value: "rs" },
65
+ * ]);
66
+ * // Returns TypeScript and Rust (both contain "t" then "s")
67
+ * ```
68
+ */
69
+ declare function fuzzyFilter<T>(query: string, items: readonly {
70
+ readonly label: string;
71
+ readonly value: T;
72
+ }[]): FuzzyFilterResult<T>[];
73
+ import { StyleFn } from "@crustjs/style";
74
+ /**
75
+ * Style slots for all prompt elements.
76
+ *
77
+ * Each slot is a `StyleFn` — a `(text: string) => string` function that
78
+ * applies ANSI styling. The theme controls the visual appearance of every
79
+ * prompt component.
80
+ *
81
+ * @example
82
+ * ```ts
83
+ * const theme: PromptTheme = {
84
+ * prefix: cyan,
85
+ * message: bold,
86
+ * placeholder: dim,
87
+ * // ...
88
+ * };
89
+ * ```
90
+ */
91
+ interface PromptTheme {
92
+ /** Prefix glyph before the prompt message (e.g., "?" or "◆") */
93
+ readonly prefix: StyleFn;
94
+ /** The prompt message text */
95
+ readonly message: StyleFn;
96
+ /** Placeholder text shown when input is empty */
97
+ readonly placeholder: StyleFn;
98
+ /** Cursor indicator in selection lists */
99
+ readonly cursor: StyleFn;
100
+ /** Selected / active item styling */
101
+ readonly selected: StyleFn;
102
+ /** Unselected / inactive item styling */
103
+ readonly unselected: StyleFn;
104
+ /** Validation error messages */
105
+ readonly error: StyleFn;
106
+ /** Success / confirmed value styling */
107
+ readonly success: StyleFn;
108
+ /** Hint text (e.g., keybinding hints, choice hints) */
109
+ readonly hint: StyleFn;
110
+ /** Spinner frame characters */
111
+ readonly spinner: StyleFn;
112
+ /** Matched characters in fuzzy filter results */
113
+ readonly filterMatch: StyleFn;
114
+ }
115
+ /**
116
+ * Partial version of `PromptTheme` for user overrides.
117
+ * Users only need to specify the slots they want to customize.
118
+ */
119
+ type PartialPromptTheme = Partial<PromptTheme>;
120
+ /**
121
+ * A choice item for select, multiselect, and filter prompts.
122
+ *
123
+ * Accepts either a plain string (where `label === value`) or an object
124
+ * with explicit label, value, and optional hint.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * // String choices
129
+ * const colors: Choice<string>[] = ["red", "green", "blue"];
130
+ *
131
+ * // Object choices with typed values
132
+ * const ports: Choice<number>[] = [
133
+ * { label: "HTTP", value: 80 },
134
+ * { label: "HTTPS", value: 443, hint: "recommended" },
135
+ * ];
136
+ * ```
137
+ */
138
+ type Choice<T> = string | {
139
+ readonly label: string;
140
+ readonly value: T;
141
+ readonly hint?: string;
142
+ };
143
+ /**
144
+ * Result of a validation function.
145
+ * - `true` means valid
146
+ * - A `string` is the error message to display
147
+ */
148
+ type ValidateResult = true | string;
149
+ /**
150
+ * Validation function for prompt values.
151
+ * May be synchronous or asynchronous.
152
+ *
153
+ * @example
154
+ * ```ts
155
+ * const validateEmail: ValidateFn<string> = (value) => {
156
+ * if (!value.includes("@")) return "Must be a valid email address";
157
+ * return true;
158
+ * };
159
+ * ```
160
+ */
161
+ type ValidateFn<T> = (value: T) => ValidateResult | Promise<ValidateResult>;
162
+ /**
163
+ * Calculate the scroll offset to keep the cursor within the visible viewport.
164
+ *
165
+ * Used by select, multiselect, and filter prompts to manage scrolling
166
+ * when the number of items exceeds the visible viewport.
167
+ *
168
+ * @param cursor - Current cursor position in the list
169
+ * @param scrollOffset - Current scroll offset
170
+ * @param totalItems - Total number of items in the list
171
+ * @param maxVisible - Maximum number of visible items in the viewport
172
+ * @returns The new scroll offset
173
+ */
174
+ declare function calculateScrollOffset(cursor: number, scrollOffset: number, totalItems: number, maxVisible: number): number;
175
+ /**
176
+ * A normalized choice item with explicit label, value, and optional hint.
177
+ * This is the internal representation used by select, multiselect, and filter prompts.
178
+ */
179
+ interface NormalizedChoice<T> {
180
+ readonly label: string;
181
+ readonly value: T;
182
+ readonly hint?: string;
183
+ }
184
+ /**
185
+ * Normalize an array of choices into a consistent `{ label, value, hint? }` format.
186
+ *
187
+ * String choices are converted to `{ label: str, value: str }`.
188
+ * Object choices are passed through as-is.
189
+ *
190
+ * @param choices - Array of string or object choices
191
+ * @returns Array of normalized choice objects
192
+ *
193
+ * @example
194
+ * ```ts
195
+ * // String choices
196
+ * normalizeChoices(["red", "green", "blue"]);
197
+ * // => [{ label: "red", value: "red" }, { label: "green", value: "green" }, ...]
198
+ *
199
+ * // Object choices
200
+ * normalizeChoices([{ label: "HTTP", value: 80 }]);
201
+ * // => [{ label: "HTTP", value: 80 }]
202
+ * ```
203
+ */
204
+ declare function normalizeChoices<T>(choices: readonly Choice<T>[]): NormalizedChoice<T>[];
205
+ /**
206
+ * Default prompt theme with a polished, gum/clack-inspired aesthetic.
207
+ *
208
+ * Uses `@crustjs/style` color functions for ANSI output that respects
209
+ * terminal capability detection (NO_COLOR, non-TTY graceful degradation).
210
+ */
211
+ declare const defaultTheme: PromptTheme;
212
+ /**
213
+ * Create a complete theme by merging partial overrides onto the default theme.
214
+ *
215
+ * Use this to build a reusable theme object, then apply it globally
216
+ * with {@link setTheme}.
217
+ *
218
+ * @param overrides - Partial theme slots to override. Only specified slots
219
+ * are replaced; all others retain their default values.
220
+ * @returns A complete `PromptTheme` with all slots defined.
221
+ *
222
+ * @example
223
+ * ```ts
224
+ * import { createTheme, setTheme } from "@crustjs/prompts";
225
+ * import { magenta, cyan } from "@crustjs/style";
226
+ *
227
+ * const myTheme = createTheme({
228
+ * prefix: magenta,
229
+ * success: cyan,
230
+ * });
231
+ *
232
+ * // Apply globally so all prompts use it
233
+ * setTheme(myTheme);
234
+ * ```
235
+ */
236
+ declare function createTheme(overrides?: PartialPromptTheme): PromptTheme;
237
+ /**
238
+ * Set the global theme applied to all prompts.
239
+ *
240
+ * Accepts either a complete `PromptTheme` (e.g., from {@link createTheme})
241
+ * or a `PartialPromptTheme` with only the slots you want to override.
242
+ * Unspecified slots fall back to {@link defaultTheme}.
243
+ *
244
+ * Call with no arguments or `undefined` to clear the global theme.
245
+ *
246
+ * @param theme - Theme or partial overrides to apply globally.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * import { setTheme } from "@crustjs/prompts";
251
+ * import { magenta, cyan } from "@crustjs/style";
252
+ *
253
+ * // Set once at app bootstrap
254
+ * setTheme({ prefix: magenta, success: cyan });
255
+ *
256
+ * // Clear the global theme
257
+ * setTheme();
258
+ * ```
259
+ */
260
+ declare function setTheme(theme?: PartialPromptTheme): void;
261
+ /**
262
+ * Get the current global theme with all slots resolved.
263
+ *
264
+ * Returns {@link defaultTheme} if no global theme has been set.
265
+ *
266
+ * @returns The complete resolved global `PromptTheme`.
267
+ *
268
+ * @example
269
+ * ```ts
270
+ * import { getTheme, setTheme } from "@crustjs/prompts";
271
+ *
272
+ * setTheme({ prefix: magenta });
273
+ * const theme = getTheme();
274
+ * // theme.prefix === magenta
275
+ * // theme.message === bold (default)
276
+ * ```
277
+ */
278
+ declare function getTheme(): PromptTheme;
279
+ /**
280
+ * Structured keypress event parsed from raw terminal input.
281
+ */
282
+ interface KeypressEvent {
283
+ /** The character string for printable keys, or empty string for special keys */
284
+ readonly char: string;
285
+ /** Key name (e.g., "return", "backspace", "up", "down", "left", "right", "tab") */
286
+ readonly name: string;
287
+ /** Whether Ctrl was held */
288
+ readonly ctrl: boolean;
289
+ /** Whether Meta/Alt was held */
290
+ readonly meta: boolean;
291
+ /** Whether Shift was held */
292
+ readonly shift: boolean;
293
+ }
294
+ /**
295
+ * Symbol used internally to discriminate submit results from state updates.
296
+ * Using a symbol prevents collisions with user-defined state types.
297
+ */
298
+ declare const SUBMIT: unique symbol;
299
+ /**
300
+ * A submit action wrapping the final value.
301
+ */
302
+ interface SubmitResult<T> {
303
+ readonly [SUBMIT]: T;
304
+ }
305
+ /**
306
+ * Create a submit result to resolve the prompt with the given value.
307
+ *
308
+ * @param value - The value to resolve the prompt with
309
+ * @returns A submit action object
310
+ *
311
+ * @example
312
+ * ```ts
313
+ * handleKey: (key, state) => {
314
+ * if (key.name === "return") return submit(state.value);
315
+ * return { ...state, value: state.value + key.char };
316
+ * }
317
+ * ```
318
+ */
319
+ declare function submit<T>(value: T): SubmitResult<T>;
320
+ /**
321
+ * Result of a keypress handler.
322
+ * - Return updated state to continue the prompt
323
+ * - Return `submit(value)` to resolve the prompt with a value
324
+ */
325
+ type HandleKeyResult<
326
+ S,
327
+ T
328
+ > = S | SubmitResult<T>;
329
+ /**
330
+ * Configuration for `runPrompt` — each prompt provides these functions
331
+ * to define its behavior.
332
+ */
333
+ interface PromptConfig<
334
+ S,
335
+ T
336
+ > {
337
+ /** Render the current state to a string (may contain newlines) */
338
+ readonly render: (state: S, theme: PromptTheme) => string;
339
+ /** Handle a keypress event — return new state or `submit(value)` to resolve */
340
+ readonly handleKey: (key: KeypressEvent, state: S) => HandleKeyResult<S, T> | Promise<HandleKeyResult<S, T>>;
341
+ /** Initial state for the prompt */
342
+ readonly initialState: S;
343
+ /** Resolved theme for this prompt */
344
+ readonly theme: PromptTheme;
345
+ /**
346
+ * Optional: render the final submitted state.
347
+ * Called after submit with the final state and submitted value.
348
+ * If not provided, the last render output is left on screen.
349
+ */
350
+ readonly renderSubmitted?: (state: S, value: T, theme: PromptTheme) => string;
351
+ }
352
+ /**
353
+ * Error thrown when a prompt requires an interactive TTY but none is available.
354
+ */
355
+ declare class NonInteractiveError extends Error {
356
+ constructor(message?: string);
357
+ }
358
+ /**
359
+ * Error thrown when the user cancels a prompt with Ctrl+C.
360
+ */
361
+ declare class CancelledError extends Error {
362
+ constructor(message?: string);
363
+ }
364
+ /**
365
+ * Check that stdin is an interactive TTY.
366
+ * @throws {NonInteractiveError} when stdin is not a TTY
367
+ */
368
+ declare function assertTTY(): void;
369
+ /**
370
+ * Run an interactive prompt with the given configuration.
371
+ *
372
+ * Manages the full terminal lifecycle:
373
+ * 1. Asserts stdin is a TTY
374
+ * 2. Enables raw mode and hides cursor
375
+ * 3. Renders initial state
376
+ * 4. Listens for keypress events, delegating to `handleKey`
377
+ * 5. Re-renders on state changes
378
+ * 6. On submit, renders final state, cleans up, and resolves
379
+ *
380
+ * Output is written to `process.stderr` so prompt UI doesn't pollute
381
+ * piped stdout.
382
+ *
383
+ * @param config - Prompt configuration (render, handleKey, initialState, theme)
384
+ * @returns Promise resolving to the user's submitted value
385
+ * @throws {NonInteractiveError} when stdin is not a TTY
386
+ *
387
+ * @example
388
+ * ```ts
389
+ * const value = await runPrompt({
390
+ * initialState: { value: "", submitted: false },
391
+ * theme: resolveTheme(),
392
+ * render: (state, theme) => `${theme.prefix("?")} Enter value: ${state.value}`,
393
+ * handleKey: (key, state) => {
394
+ * if (key.name === "return") return { submit: state.value };
395
+ * return { ...state, value: state.value + key.char };
396
+ * },
397
+ * });
398
+ * ```
399
+ */
400
+ declare function runPrompt<
401
+ S,
402
+ T
403
+ >(config: PromptConfig<S, T>): Promise<T>;
404
+ /**
405
+ * Options for the {@link confirm} prompt.
406
+ *
407
+ * @example
408
+ * ```ts
409
+ * const shouldContinue = await confirm({
410
+ * message: "Do you want to continue?",
411
+ * });
412
+ * ```
413
+ *
414
+ * @example
415
+ * ```ts
416
+ * const agreed = await confirm({
417
+ * message: "Accept terms?",
418
+ * active: "Agree",
419
+ * inactive: "Decline",
420
+ * default: false,
421
+ * });
422
+ * ```
423
+ */
424
+ interface ConfirmOptions {
425
+ /** The prompt message displayed to the user */
426
+ readonly message: string;
427
+ /** Default value when the user presses Enter without toggling (defaults to `true`) */
428
+ readonly default?: boolean;
429
+ /** Initial value — if provided, the prompt is skipped and this value is returned immediately */
430
+ readonly initial?: boolean;
431
+ /** Label for the affirmative option (defaults to `"Yes"`) */
432
+ readonly active?: string;
433
+ /** Label for the negative option (defaults to `"No"`) */
434
+ readonly inactive?: string;
435
+ /** Per-prompt theme overrides */
436
+ readonly theme?: PartialPromptTheme;
437
+ }
438
+ /**
439
+ * Display an interactive yes/no confirmation prompt.
440
+ *
441
+ * Shows two side-by-side options (default: "Yes" / "No") that the user
442
+ * toggles with arrow keys, h/l, y/n, or Tab, then confirms with Enter.
443
+ *
444
+ * If `initial` is provided, the prompt is skipped and the value is returned
445
+ * immediately — useful for prefilling from CLI flags.
446
+ *
447
+ * @param options - Confirm prompt configuration
448
+ * @returns The user's boolean selection
449
+ * @throws {NonInteractiveError} when stdin is not a TTY and no `initial` is provided
450
+ *
451
+ * @example
452
+ * ```ts
453
+ * const proceed = await confirm({
454
+ * message: "Do you want to continue?",
455
+ * });
456
+ * ```
457
+ *
458
+ * @example
459
+ * ```ts
460
+ * // Custom labels with default value
461
+ * const accepted = await confirm({
462
+ * message: "Accept the license?",
463
+ * active: "Accept",
464
+ * inactive: "Decline",
465
+ * default: false,
466
+ * });
467
+ * ```
468
+ *
469
+ * @example
470
+ * ```ts
471
+ * // Skip prompt when flag is provided
472
+ * const yes = await confirm({
473
+ * message: "Continue?",
474
+ * initial: flags.yes,
475
+ * });
476
+ * ```
477
+ */
478
+ declare function confirm(options: ConfirmOptions): Promise<boolean>;
479
+ /**
480
+ * Options for the {@link filter} prompt.
481
+ *
482
+ * @example
483
+ * ```ts
484
+ * const lang = await filter({
485
+ * message: "Search for a language",
486
+ * choices: ["TypeScript", "JavaScript", "Rust", "Python", "Go"],
487
+ * });
488
+ * ```
489
+ *
490
+ * @example
491
+ * ```ts
492
+ * const pkg = await filter<{ name: string; version: string }>({
493
+ * message: "Find a package",
494
+ * choices: [
495
+ * { label: "react", value: { name: "react", version: "18.2" } },
496
+ * { label: "vue", value: { name: "vue", version: "3.3" } },
497
+ * ],
498
+ * placeholder: "Type to filter...",
499
+ * });
500
+ * ```
501
+ */
502
+ interface FilterOptions<T> {
503
+ /** The prompt message displayed to the user */
504
+ readonly message: string;
505
+ /** List of choices — strings or `{ label, value, hint? }` objects */
506
+ readonly choices: readonly Choice<T>[];
507
+ /** Initial value — if provided, the prompt is skipped and this value is returned immediately */
508
+ readonly initial?: T;
509
+ /** Placeholder text shown when the query input is empty */
510
+ readonly placeholder?: string;
511
+ /** Maximum number of visible filtered results before scrolling (defaults to 10) */
512
+ readonly maxVisible?: number;
513
+ /** Per-prompt theme overrides */
514
+ readonly theme?: PartialPromptTheme;
515
+ }
516
+ /**
517
+ * Display an interactive fuzzy-filter prompt over a list of choices.
518
+ *
519
+ * Type to filter the list using fuzzy matching. Navigate filtered results
520
+ * with Up/Down arrows, confirm with Enter. Matched characters are
521
+ * highlighted in the results.
522
+ *
523
+ * If `initial` is provided, the prompt is skipped and the value is returned
524
+ * immediately — useful for prefilling from CLI flags.
525
+ *
526
+ * @param options - Filter prompt configuration
527
+ * @returns The value of the selected choice
528
+ * @throws {NonInteractiveError} when stdin is not a TTY and no `initial` is provided
529
+ *
530
+ * @example
531
+ * ```ts
532
+ * const lang = await filter({
533
+ * message: "Search for a language",
534
+ * choices: ["TypeScript", "JavaScript", "Rust", "Python", "Go"],
535
+ * });
536
+ * ```
537
+ *
538
+ * @example
539
+ * ```ts
540
+ * const pkg = await filter<{ name: string; version: string }>({
541
+ * message: "Find a package",
542
+ * choices: [
543
+ * { label: "react", value: { name: "react", version: "18.2" } },
544
+ * { label: "vue", value: { name: "vue", version: "3.3" } },
545
+ * ],
546
+ * placeholder: "Type to filter...",
547
+ * });
548
+ * ```
549
+ *
550
+ * @example
551
+ * ```ts
552
+ * // Skip prompt when flag is provided
553
+ * const tool = await filter({
554
+ * message: "Pick a tool",
555
+ * choices: ["prettier", "eslint", "biome"],
556
+ * initial: flags.tool,
557
+ * });
558
+ * ```
559
+ */
560
+ declare function filter<T>(options: FilterOptions<T>): Promise<T>;
561
+ /**
562
+ * Options for the {@link input} prompt.
563
+ *
564
+ * @example
565
+ * ```ts
566
+ * const name = await input({
567
+ * message: "What is your name?",
568
+ * placeholder: "Enter your name",
569
+ * validate: (v) => v.length > 0 || "Name is required",
570
+ * });
571
+ * ```
572
+ */
573
+ interface InputOptions {
574
+ /** The prompt message displayed to the user */
575
+ readonly message: string;
576
+ /** Placeholder text shown when the input is empty */
577
+ readonly placeholder?: string;
578
+ /** Default value used when the user submits an empty input */
579
+ readonly default?: string;
580
+ /** Initial value — if provided, the prompt is skipped and this value is returned immediately */
581
+ readonly initial?: string;
582
+ /** Validation function — return `true` for valid, or a string error message */
583
+ readonly validate?: ValidateFn<string>;
584
+ /** Per-prompt theme overrides */
585
+ readonly theme?: PartialPromptTheme;
586
+ }
587
+ /**
588
+ * Display an interactive single-line text input prompt.
589
+ *
590
+ * Supports placeholder text, default values, validation with inline error
591
+ * display, and full cursor editing (insert, delete, arrow keys, home/end).
592
+ *
593
+ * If `initial` is provided, the prompt is skipped and the value is returned
594
+ * immediately — useful for prefilling from CLI flags.
595
+ *
596
+ * @param options - Input prompt configuration
597
+ * @returns The user's entered text
598
+ * @throws {NonInteractiveError} when stdin is not a TTY and no `initial` is provided
599
+ *
600
+ * @example
601
+ * ```ts
602
+ * const name = await input({
603
+ * message: "What is your name?",
604
+ * placeholder: "John Doe",
605
+ * validate: (v) => v.length > 0 || "Name cannot be empty",
606
+ * });
607
+ * ```
608
+ *
609
+ * @example
610
+ * ```ts
611
+ * // Skip prompt when flag is provided
612
+ * const name = await input({
613
+ * message: "Name?",
614
+ * initial: flags.name,
615
+ * });
616
+ * ```
617
+ */
618
+ declare function input(options: InputOptions): Promise<string>;
619
+ /**
620
+ * Options for the {@link multiselect} prompt.
621
+ *
622
+ * @example
623
+ * ```ts
624
+ * const toppings = await multiselect({
625
+ * message: "Select toppings",
626
+ * choices: ["cheese", "pepperoni", "mushrooms", "olives"],
627
+ * });
628
+ * ```
629
+ *
630
+ * @example
631
+ * ```ts
632
+ * const features = await multiselect<string>({
633
+ * message: "Enable features",
634
+ * choices: [
635
+ * { label: "TypeScript", value: "ts", hint: "recommended" },
636
+ * { label: "ESLint", value: "eslint" },
637
+ * { label: "Prettier", value: "prettier" },
638
+ * ],
639
+ * default: ["ts"],
640
+ * required: true,
641
+ * });
642
+ * ```
643
+ */
644
+ interface MultiselectOptions<T> {
645
+ /** The prompt message displayed to the user */
646
+ readonly message: string;
647
+ /** List of choices — strings or `{ label, value, hint? }` objects */
648
+ readonly choices: readonly Choice<T>[];
649
+ /** Default selected values — pre-selects matching choices */
650
+ readonly default?: readonly T[];
651
+ /** Initial value — if provided, the prompt is skipped and this value is returned immediately */
652
+ readonly initial?: readonly T[];
653
+ /** Whether at least one item must be selected (defaults to false) */
654
+ readonly required?: boolean;
655
+ /** Minimum number of selections required */
656
+ readonly min?: number;
657
+ /** Maximum number of selections allowed */
658
+ readonly max?: number;
659
+ /** Maximum number of visible choices before scrolling (defaults to 10) */
660
+ readonly maxVisible?: number;
661
+ /** Per-prompt theme overrides */
662
+ readonly theme?: PartialPromptTheme;
663
+ }
664
+ /**
665
+ * Display an interactive checkbox-style multi-selection prompt.
666
+ *
667
+ * Navigate with Up/Down arrows (or k/j vim keys), toggle selection with Space,
668
+ * toggle all with 'a', invert selection with 'i', and confirm with Enter.
669
+ * When the list exceeds `maxVisible` items, the viewport scrolls with
670
+ * indicators showing more items above or below.
671
+ *
672
+ * If `initial` is provided, the prompt is skipped and the value is returned
673
+ * immediately -- useful for prefilling from CLI flags.
674
+ *
675
+ * @param options - Multiselect prompt configuration
676
+ * @returns Array of selected values
677
+ * @throws {NonInteractiveError} when stdin is not a TTY and no `initial` is provided
678
+ *
679
+ * @example
680
+ * ```ts
681
+ * const toppings = await multiselect({
682
+ * message: "Select toppings",
683
+ * choices: ["cheese", "pepperoni", "mushrooms", "olives"],
684
+ * });
685
+ * ```
686
+ *
687
+ * @example
688
+ * ```ts
689
+ * const features = await multiselect<string>({
690
+ * message: "Enable features",
691
+ * choices: [
692
+ * { label: "TypeScript", value: "ts", hint: "recommended" },
693
+ * { label: "ESLint", value: "eslint" },
694
+ * { label: "Prettier", value: "prettier" },
695
+ * ],
696
+ * default: ["ts"],
697
+ * required: true,
698
+ * });
699
+ * ```
700
+ *
701
+ * @example
702
+ * ```ts
703
+ * // Skip prompt when flags are provided
704
+ * const features = await multiselect({
705
+ * message: "Features?",
706
+ * choices: ["auth", "logging", "metrics"],
707
+ * initial: flags.features,
708
+ * });
709
+ * ```
710
+ */
711
+ declare function multiselect<T>(options: MultiselectOptions<T>): Promise<T[]>;
712
+ /**
713
+ * Options for the {@link password} prompt.
714
+ *
715
+ * @example
716
+ * ```ts
717
+ * const secret = await password({
718
+ * message: "Enter your password:",
719
+ * validate: (v) => v.length >= 8 || "Password must be at least 8 characters",
720
+ * });
721
+ * ```
722
+ */
723
+ interface PasswordOptions {
724
+ /** The prompt message displayed to the user */
725
+ readonly message: string;
726
+ /** Character used to mask the input (default: `"*"`) */
727
+ readonly mask?: string;
728
+ /** Validation function — return `true` for valid, or a string error message */
729
+ readonly validate?: ValidateFn<string>;
730
+ /** Initial value — if provided, the prompt is skipped and this value is returned immediately */
731
+ readonly initial?: string;
732
+ /** Per-prompt theme overrides */
733
+ readonly theme?: PartialPromptTheme;
734
+ }
735
+ /**
736
+ * Display an interactive masked password input prompt.
737
+ *
738
+ * Characters are shown as the mask character (default `"*"`) as the user
739
+ * types. After submission, a fixed-length mask is displayed to prevent
740
+ * revealing the password length.
741
+ *
742
+ * Supports validation with inline error display and full cursor editing
743
+ * (insert, delete, arrow keys, home/end).
744
+ *
745
+ * If `initial` is provided, the prompt is skipped and the value is returned
746
+ * immediately — useful for prefilling from CLI flags.
747
+ *
748
+ * @param options - Password prompt configuration
749
+ * @returns The user's entered password
750
+ * @throws {NonInteractiveError} when stdin is not a TTY and no `initial` is provided
751
+ *
752
+ * @example
753
+ * ```ts
754
+ * const secret = await password({
755
+ * message: "Enter your password:",
756
+ * validate: (v) => v.length >= 8 || "Password must be at least 8 characters",
757
+ * });
758
+ * ```
759
+ *
760
+ * @example
761
+ * ```ts
762
+ * // Custom mask character
763
+ * const pin = await password({
764
+ * message: "Enter PIN:",
765
+ * mask: "●",
766
+ * });
767
+ * ```
768
+ */
769
+ declare function password(options: PasswordOptions): Promise<string>;
770
+ /**
771
+ * Options for the {@link select} prompt.
772
+ *
773
+ * @example
774
+ * ```ts
775
+ * const color = await select({
776
+ * message: "Pick a color",
777
+ * choices: ["red", "green", "blue"],
778
+ * });
779
+ * ```
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * const port = await select<number>({
784
+ * message: "Choose a port",
785
+ * choices: [
786
+ * { label: "HTTP", value: 80 },
787
+ * { label: "HTTPS", value: 443, hint: "recommended" },
788
+ * ],
789
+ * default: 443,
790
+ * });
791
+ * ```
792
+ */
793
+ interface SelectOptions<T> {
794
+ /** The prompt message displayed to the user */
795
+ readonly message: string;
796
+ /** List of choices — strings or `{ label, value, hint? }` objects */
797
+ readonly choices: readonly Choice<T>[];
798
+ /** Default value — sets the initial cursor position to the matching choice */
799
+ readonly default?: T;
800
+ /** Initial value — if provided, the prompt is skipped and this value is returned immediately */
801
+ readonly initial?: T;
802
+ /** Maximum number of visible choices before scrolling (defaults to 10) */
803
+ readonly maxVisible?: number;
804
+ /** Per-prompt theme overrides */
805
+ readonly theme?: PartialPromptTheme;
806
+ }
807
+ /**
808
+ * Display an interactive single-selection prompt from a list of choices.
809
+ *
810
+ * Navigate with Up/Down arrows (or k/j vim keys), confirm with Enter.
811
+ * When the list exceeds `maxVisible` items, the viewport scrolls with
812
+ * indicators showing more items above or below.
813
+ *
814
+ * If `initial` is provided, the prompt is skipped and the value is returned
815
+ * immediately -- useful for prefilling from CLI flags.
816
+ *
817
+ * @param options - Select prompt configuration
818
+ * @returns The value of the selected choice
819
+ * @throws {NonInteractiveError} when stdin is not a TTY and no `initial` is provided
820
+ *
821
+ * @example
822
+ * ```ts
823
+ * const color = await select({
824
+ * message: "Pick a color",
825
+ * choices: ["red", "green", "blue"],
826
+ * });
827
+ * ```
828
+ *
829
+ * @example
830
+ * ```ts
831
+ * const port = await select<number>({
832
+ * message: "Choose a port",
833
+ * choices: [
834
+ * { label: "HTTP", value: 80 },
835
+ * { label: "HTTPS", value: 443, hint: "recommended" },
836
+ * ],
837
+ * default: 443,
838
+ * });
839
+ * ```
840
+ *
841
+ * @example
842
+ * ```ts
843
+ * // Skip prompt when flag is provided
844
+ * const env = await select({
845
+ * message: "Environment?",
846
+ * choices: ["dev", "staging", "prod"],
847
+ * initial: flags.env,
848
+ * });
849
+ * ```
850
+ */
851
+ declare function select<T>(options: SelectOptions<T>): Promise<T>;
852
+ /**
853
+ * Built-in spinner animation names or a custom frame configuration.
854
+ *
855
+ * Built-in spinners:
856
+ * - `"dots"` — Braille dot pattern (⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏)
857
+ * - `"line"` — Line rotation (-\\|/)
858
+ * - `"arc"` — Quarter circle rotation (◐◓◑◒)
859
+ * - `"bounce"` — Bouncing braille dot (⠁⠂⠄⡀⢀⠠⠐⠈)
860
+ *
861
+ * Or provide a custom `{ frames: string[]; interval: number }` object.
862
+ */
863
+ type SpinnerType = "dots" | "line" | "arc" | "bounce" | {
864
+ readonly frames: readonly string[];
865
+ readonly interval: number;
866
+ };
867
+ /**
868
+ * Options for the {@link spinner} prompt.
869
+ *
870
+ * @example
871
+ * ```ts
872
+ * const data = await spinner({
873
+ * message: "Fetching data...",
874
+ * task: async () => {
875
+ * const res = await fetch("https://api.example.com/data");
876
+ * return res.json();
877
+ * },
878
+ * });
879
+ * ```
880
+ */
881
+ interface SpinnerOptions<T> {
882
+ /** The message displayed alongside the spinner */
883
+ readonly message: string;
884
+ /** The async task to run while the spinner is displayed */
885
+ readonly task: () => Promise<T>;
886
+ /** Spinner animation style (defaults to `"dots"`) */
887
+ readonly spinner?: SpinnerType;
888
+ /** Per-prompt theme overrides */
889
+ readonly theme?: PartialPromptTheme;
890
+ }
891
+ /**
892
+ * Display a spinner animation while running an async task.
893
+ *
894
+ * The spinner renders to stderr so it doesn't interfere with piped stdout.
895
+ * On task completion, the spinner is replaced with a success (✔) or error (✖)
896
+ * indicator. If the task throws, the error is re-thrown after cleanup.
897
+ *
898
+ * Unlike other prompts, the spinner does **not** use raw mode or keypress
899
+ * handling — it is output-only and non-interactive.
900
+ *
901
+ * @param options - Spinner prompt configuration
902
+ * @returns The result of the async task
903
+ *
904
+ * @example
905
+ * ```ts
906
+ * const data = await spinner({
907
+ * message: "Loading data...",
908
+ * task: async () => {
909
+ * const res = await fetch("https://api.example.com/data");
910
+ * return res.json();
911
+ * },
912
+ * });
913
+ * ```
914
+ *
915
+ * @example
916
+ * ```ts
917
+ * // Custom spinner animation
918
+ * await spinner({
919
+ * message: "Building project...",
920
+ * task: async () => { await buildProject(); },
921
+ * spinner: "arc",
922
+ * });
923
+ * ```
924
+ *
925
+ * @example
926
+ * ```ts
927
+ * // Custom frames and interval
928
+ * await spinner({
929
+ * message: "Processing...",
930
+ * task: async () => { await processData(); },
931
+ * spinner: { frames: ["⊶", "⊷"], interval: 150 },
932
+ * });
933
+ * ```
934
+ */
935
+ declare function spinner<T>(options: SpinnerOptions<T>): Promise<T>;
936
+ /** Thin vertical bar used as cursor indicator in text inputs */
937
+ declare const CURSOR_CHAR = "│";
938
+ /**
939
+ * The text + cursor fields that `handleTextEdit` reads and updates.
940
+ * Prompts embed these fields in their own state type.
941
+ */
942
+ interface TextEditState {
943
+ readonly text: string;
944
+ readonly cursorPos: number;
945
+ }
946
+ /**
947
+ * Result of a text-edit operation.
948
+ * `null` means the key was not a text-editing key and the caller should
949
+ * handle it (e.g., Enter for submit, arrow-up/down for list navigation).
950
+ */
951
+ type TextEditResult = TextEditState | null;
952
+ /**
953
+ * Handle common text-editing keypresses: backspace, delete, left, right,
954
+ * home, end, and printable character insertion.
955
+ *
956
+ * Returns the updated `{ text, cursorPos }` if the key was handled,
957
+ * or `null` if the key is not a text-editing key (so the caller can
958
+ * handle it for prompt-specific logic like submit or list navigation).
959
+ *
960
+ * @param key - The keypress event
961
+ * @param text - Current text content
962
+ * @param cursorPos - Current cursor position within the text
963
+ * @returns Updated text/cursor, or `null` if not a text-edit key
964
+ */
965
+ declare function handleTextEdit(key: KeypressEvent, text: string, cursorPos: number): TextEditResult;
966
+ export { submit, spinner, setTheme, select, runPrompt, password, normalizeChoices, multiselect, input, handleTextEdit, getTheme, fuzzyMatch, fuzzyFilter, filter, defaultTheme, createTheme, confirm, calculateScrollOffset, assertTTY, ValidateResult, ValidateFn, TextEditState, TextEditResult, SubmitResult, SpinnerType, SpinnerOptions, SelectOptions, PromptTheme, PromptConfig, PasswordOptions, PartialPromptTheme, NormalizedChoice, NonInteractiveError, MultiselectOptions, KeypressEvent, InputOptions, HandleKeyResult, FuzzyMatchResult, FuzzyFilterResult, FilterOptions, ConfirmOptions, Choice, CancelledError, CURSOR_CHAR };