caique 0.2.0 → 0.3.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.
@@ -0,0 +1,31 @@
1
+ /** What `node:readline` hands a keypress listener. Widened to what the predicates read. */
2
+ export interface KeypressEvent {
3
+ name: string;
4
+ ctrl?: boolean;
5
+ shift?: boolean;
6
+ meta?: boolean;
7
+ sequence?: string;
8
+ }
9
+ /** The two named keybinding sets the incumbent understands. Anything else is ignored. */
10
+ export type Keybinding = 'emacs' | 'vim';
11
+ /**
12
+ * `INQUIRER_KEYBINDINGS` split on whitespace or commas, lowercased, unknown names dropped
13
+ * and duplicates collapsed — so `'vim emacs unknown vim'` is `['vim', 'emacs']`.
14
+ */
15
+ export declare function getDefaultKeybindings(): Keybinding[];
16
+ /** The up arrow always; `k` under vim; `Ctrl-P` under emacs. Nothing else, ever. */
17
+ export declare const isUpKey: (key: KeypressEvent, keybindings?: readonly Keybinding[]) => boolean;
18
+ /** The down arrow always; `j` under vim; `Ctrl-N` under emacs. */
19
+ export declare const isDownKey: (key: KeypressEvent, keybindings?: readonly Keybinding[]) => boolean;
20
+ /** The space bar. */
21
+ export declare const isSpaceKey: (key: KeypressEvent) => boolean;
22
+ /** Backspace. */
23
+ export declare const isBackspaceKey: (key: KeypressEvent) => boolean;
24
+ /** Tab. */
25
+ export declare const isTabKey: (key: KeypressEvent) => boolean;
26
+ /** A digit. `name` is a single character for these, so a substring test is the whole test. */
27
+ export declare const isNumberKey: (key: KeypressEvent) => boolean;
28
+ /** Enter, under either name readline gives it. */
29
+ export declare const isEnterKey: (key: KeypressEvent) => boolean;
30
+ /** Whether shift was held. Returns the flag itself, so an absent flag reads as `false`. */
31
+ export declare const isShiftKey: (key: KeypressEvent) => boolean;
@@ -0,0 +1,22 @@
1
+ import { processRuntime } from './runtime.js';
2
+ const KEYBINDINGS = new Set(['emacs', 'vim']);
3
+ const isKeybinding = (value) => KEYBINDINGS.has(value);
4
+ export function getDefaultKeybindings() {
5
+ const env = processRuntime().env['INQUIRER_KEYBINDINGS'];
6
+ if (env === undefined || env === '')
7
+ return [];
8
+ return [
9
+ ...new Set(env
10
+ .toLowerCase()
11
+ .split(/[\s,]+/)
12
+ .filter(isKeybinding)),
13
+ ];
14
+ }
15
+ export const isUpKey = (key, keybindings = []) => key.name === 'up' || (keybindings.includes('vim') && key.name === 'k') || (keybindings.includes('emacs') && key.ctrl === true && key.name === 'p');
16
+ export const isDownKey = (key, keybindings = []) => key.name === 'down' || (keybindings.includes('vim') && key.name === 'j') || (keybindings.includes('emacs') && key.ctrl === true && key.name === 'n');
17
+ export const isSpaceKey = (key) => key.name === 'space';
18
+ export const isBackspaceKey = (key) => key.name === 'backspace';
19
+ export const isTabKey = (key) => key.name === 'tab';
20
+ export const isNumberKey = (key) => '1234567890'.includes(key.name);
21
+ export const isEnterKey = (key) => key.name === 'enter' || key.name === 'return';
22
+ export const isShiftKey = (key) => key.shift === true;
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Everything between a rendered string and the bytes a terminal receives: the escape
3
+ * sequences, the muteable output stream, and the screen manager that erases what it drew
4
+ * last time before it draws again.
5
+ *
6
+ * ## Why the escape sequences are written out here
7
+ *
8
+ * `@inquirer/core` takes them from `@inquirer/ansi`, and the suite imports `cursorShow`,
9
+ * `cursorLeft` and `eraseLines` from that package to assert against our output — so these
10
+ * are not ours to choose. The one case that compares a raw frame byte for byte (`allow
11
+ * cleaning the prompt after completion`) is what proves they are right.
12
+ *
13
+ * The cursor-visibility sequence is the exception and comes from `closeout`, which owns it
14
+ * across the family. The rest are CSI, which `paratext` states as out of scope, so there is
15
+ * no family subpath to take them from and they are stated here.
16
+ *
17
+ * ## Why there is a stream class here
18
+ *
19
+ * The incumbent wraps a caller's output in `mute-stream` so `readline` can write its own
20
+ * terminal setup and then be silenced, leaving the screen manager as the only thing that
21
+ * draws. That is a real behaviour rather than a dependency detail — muting is what stops
22
+ * readline echoing a password — and caique reaches nothing outside this repository, so the
23
+ * twenty lines of it live here. It is an old-style `Stream` on purpose: `create-prompt`
24
+ * branches on whether the *input* has `readableFlowing`, and a caller who passes a classic
25
+ * stream must get the synchronous first render the incumbent gives them.
26
+ */
27
+ import { Stream } from 'node:stream';
28
+ /** Move the cursor to the first column. */
29
+ export declare const cursorLeft = "\u001B[G";
30
+ /**
31
+ * Show the cursor — taken from `closeout`, not spelled again here.
32
+ *
33
+ * `inline-implementation-lock.test.ts` is what insists on that, and it is right to: this
34
+ * package already carried a hand-written copy of `?25l`/`?25h` once, and removing it found
35
+ * that caique never restored the cursor on a signal at all. A third copy for a façade would
36
+ * be the same defect wearing a compatibility badge.
37
+ */
38
+ export declare const cursorShow = "\u001B[?25h";
39
+ /** Move the cursor up `rows` rows. Zero is no sequence at all, not a zero-length move. */
40
+ export declare const cursorUp: (rows?: number) => string;
41
+ /** Move the cursor down `rows` rows. */
42
+ export declare const cursorDown: (rows?: number) => string;
43
+ /** Move the cursor to column `x` (zero-based), or to `(x, y)` when a row is given. */
44
+ export declare const cursorTo: (x: number, y?: number) => string;
45
+ /** Erase `lines` lines, ending on the first column of the topmost one. */
46
+ export declare const eraseLines: (lines: number) => string;
47
+ /**
48
+ * A writable stream that can be told to stop writing, and to start again.
49
+ *
50
+ * Declared as the four members the screen manager actually touches rather than as
51
+ * `NodeJS.WritableStream`: `end()`'s full overload set is three signatures wide and none of
52
+ * them is what a muted stream means by ending.
53
+ */
54
+ export interface Muteable {
55
+ write: (chunk: string | Uint8Array) => boolean;
56
+ mute: () => void;
57
+ unmute: () => void;
58
+ columns?: number | undefined;
59
+ rows?: number | undefined;
60
+ }
61
+ /**
62
+ * The incumbent's `mute-stream`, reduced to what a prompt uses.
63
+ *
64
+ * Classic `Stream`, emitting `data`, so `pipe()` reaches the caller's real output and so
65
+ * `'readableFlowing' in stream` is false — which is the test `create-prompt` uses to decide
66
+ * whether stale buffered input needs a tick to drain before the first render.
67
+ *
68
+ * `columns` and `rows` read through to whatever it is piped to: a terminal's size belongs
69
+ * to the terminal, and a test double that declares ten thousand columns must be believed.
70
+ */
71
+ export declare class MuteStream extends Stream implements Muteable {
72
+ #private;
73
+ muted: boolean;
74
+ writable: boolean;
75
+ readable: boolean;
76
+ get columns(): number | undefined;
77
+ get rows(): number | undefined;
78
+ get isTTY(): boolean;
79
+ mute(): void;
80
+ unmute(): void;
81
+ pipe<T extends NodeJS.WritableStream>(dest: T, options?: {
82
+ end?: boolean;
83
+ }): T;
84
+ write(chunk: string | Uint8Array): boolean;
85
+ end(chunk?: string | Uint8Array): this;
86
+ }
87
+ /**
88
+ * The width to wrap at: what the output says, then `COLUMNS`, then eighty.
89
+ *
90
+ * `cli-width`'s order, minus the `tty.getWindowSize` fallback it keeps for a stream that
91
+ * reports no `columns` at all — a stream that knows its width reports it, and one that does
92
+ * not is not necessarily the process's own terminal.
93
+ */
94
+ export declare function outputWidth(output: {
95
+ columns?: number | undefined;
96
+ } | undefined): number;
97
+ /**
98
+ * Hard-wrap every line at `width`, keeping escape sequences intact and trailing whitespace
99
+ * off. `linegauge/wrap` is this repository's port of `wrap-ansi`, graded against it.
100
+ */
101
+ export declare function breakLines(content: string, width: number): string;
102
+ /** The readline interface the screen manager drives, narrowed to what it touches. */
103
+ export interface ScreenReadline {
104
+ line: string;
105
+ output: Muteable;
106
+ setPrompt: (prompt: string) => void;
107
+ getCursorPos: () => {
108
+ rows: number;
109
+ cols: number;
110
+ };
111
+ close: () => void;
112
+ }
113
+ /**
114
+ * Draws a prompt and erases what it drew last time.
115
+ *
116
+ * The arithmetic is the incumbent's, and it has to be: `getScreen({ raw: true })` compares a
117
+ * whole frame against `eraseLines(1) + cursorLeft + cursorShow`, so an extra `cursorTo` or a
118
+ * missing newline is a failing case rather than a cosmetic difference.
119
+ */
120
+ export declare class ScreenManager {
121
+ #private;
122
+ constructor(rl: ScreenReadline);
123
+ /** Draw `content`, with `bottomContent` below the cursor line. */
124
+ render(content: string, bottomContent?: string): void;
125
+ /** Re-issue the horizontal move when readline has moved the cursor under a muted stream. */
126
+ checkCursorPos(): void;
127
+ /** Settle: leave the answer on screen, or erase it, and give the cursor back either way. */
128
+ done({ clearContent }: {
129
+ clearContent: boolean;
130
+ }): void;
131
+ }
@@ -0,0 +1,122 @@
1
+ import { Stream } from 'node:stream';
2
+ import { stripVTControlCharacters } from 'node:util';
3
+ import { SHOW_CURSOR } from 'closeout/cursor';
4
+ import { wrap } from 'linegauge/wrap';
5
+ import { processRuntime } from './runtime.js';
6
+ const ESC = '[';
7
+ export const cursorLeft = `${ESC}G`;
8
+ export const cursorShow = SHOW_CURSOR;
9
+ export const cursorUp = (rows = 1) => (rows > 0 ? `${ESC}${String(rows)}A` : '');
10
+ export const cursorDown = (rows = 1) => (rows > 0 ? `${ESC}${String(rows)}B` : '');
11
+ export const cursorTo = (x, y) => (typeof y === 'number' && !Number.isNaN(y) ? `${ESC}${String(y + 1)};${String(x + 1)}H` : `${ESC}${String(x + 1)}G`);
12
+ const eraseLine = `${ESC}2K`;
13
+ export const eraseLines = (lines) => (lines > 0 ? (eraseLine + cursorUp(1)).repeat(lines - 1) + eraseLine + cursorLeft : '');
14
+ const DEFAULT_WIDTH = 80;
15
+ const DECIMAL = 10;
16
+ export class MuteStream extends Stream {
17
+ muted = false;
18
+ writable = true;
19
+ readable = true;
20
+ #dest;
21
+ get columns() {
22
+ return this.#dest?.columns;
23
+ }
24
+ get rows() {
25
+ return this.#dest?.rows;
26
+ }
27
+ get isTTY() {
28
+ return this.#dest?.isTTY ?? false;
29
+ }
30
+ mute() {
31
+ this.muted = true;
32
+ }
33
+ unmute() {
34
+ this.muted = false;
35
+ }
36
+ pipe(dest, options) {
37
+ this.#dest = dest;
38
+ return super.pipe(dest, options);
39
+ }
40
+ write(chunk) {
41
+ if (this.muted)
42
+ return true;
43
+ this.emit('data', chunk);
44
+ return true;
45
+ }
46
+ end(chunk) {
47
+ if (chunk !== undefined && !this.muted)
48
+ this.emit('data', chunk);
49
+ this.emit('end');
50
+ return this;
51
+ }
52
+ }
53
+ export function outputWidth(output) {
54
+ const declared = output?.columns;
55
+ if (typeof declared === 'number' && declared > 0)
56
+ return declared;
57
+ const fromEnv = Number.parseInt(processRuntime().env['COLUMNS'] ?? '', DECIMAL);
58
+ return Number.isNaN(fromEnv) || fromEnv <= 0 ? DEFAULT_WIDTH : fromEnv;
59
+ }
60
+ export function breakLines(content, width) {
61
+ return content
62
+ .split('\n')
63
+ .flatMap((line) => wrap(line, width, { trim: false, wordWrap: false })
64
+ .split('\n')
65
+ .map((row) => row.trimEnd()))
66
+ .join('\n');
67
+ }
68
+ const height = (content) => content.split('\n').length;
69
+ const lastLine = (content) => content.split('\n').pop() ?? '';
70
+ export class ScreenManager {
71
+ #height = 0;
72
+ #extraLinesUnderPrompt = 0;
73
+ #cursorPos;
74
+ #rl;
75
+ constructor(rl) {
76
+ this.#rl = rl;
77
+ this.#cursorPos = rl.getCursorPos();
78
+ }
79
+ #write(content) {
80
+ this.#rl.output.unmute();
81
+ this.#rl.output.write(content);
82
+ this.#rl.output.mute();
83
+ }
84
+ render(content, bottomContent = '') {
85
+ const promptLine = lastLine(content);
86
+ const rawPromptLine = stripVTControlCharacters(promptLine);
87
+ let prompt = rawPromptLine;
88
+ if (this.#rl.line.length > 0)
89
+ prompt = prompt.slice(0, -this.#rl.line.length);
90
+ this.#rl.setPrompt(prompt);
91
+ this.#cursorPos = this.#rl.getCursorPos();
92
+ const width = outputWidth(this.#rl.output);
93
+ const wrapped = breakLines(content, width);
94
+ const wrappedBottom = breakLines(bottomContent, width);
95
+ const body = rawPromptLine.length % width === 0 ? `${wrapped}\n` : wrapped;
96
+ let output = body + (wrappedBottom ? `\n${wrappedBottom}` : '');
97
+ const promptLineUpDiff = Math.floor(rawPromptLine.length / width) - this.#cursorPos.rows;
98
+ const bottomContentHeight = promptLineUpDiff + (wrappedBottom ? height(wrappedBottom) : 0);
99
+ if (bottomContentHeight > 0)
100
+ output += cursorUp(bottomContentHeight);
101
+ output += cursorTo(this.#cursorPos.cols);
102
+ this.#write(cursorDown(this.#extraLinesUnderPrompt) + eraseLines(this.#height) + output);
103
+ this.#extraLinesUnderPrompt = bottomContentHeight;
104
+ this.#height = height(output);
105
+ }
106
+ checkCursorPos() {
107
+ const cursorPos = this.#rl.getCursorPos();
108
+ if (cursorPos.cols !== this.#cursorPos.cols) {
109
+ this.#write(cursorTo(cursorPos.cols));
110
+ this.#cursorPos = cursorPos;
111
+ }
112
+ }
113
+ done({ clearContent }) {
114
+ this.#rl.setPrompt('');
115
+ let output = cursorDown(this.#extraLinesUnderPrompt);
116
+ output += clearContent ? eraseLines(this.#height) : '\n';
117
+ output += cursorLeft;
118
+ output += cursorShow;
119
+ this.#write(output);
120
+ this.#rl.close();
121
+ }
122
+ }
@@ -0,0 +1,58 @@
1
+ import { type Keybinding } from './inquirer-keys.js';
2
+ /** `figures.tick`. */
3
+ export declare const TICK: string;
4
+ /**
5
+ * `figures.line`, the box-drawing dash a `Separator` is made of.
6
+ *
7
+ * Not conditional, and that is not an oversight: `figures` replaces a fixed list of symbols
8
+ * on a console without unicode, and `line` is not on it. Only `tick` above is.
9
+ */
10
+ export declare const LINE = "\u2500";
11
+ /** The lifecycle a prompt reports through `theme.prefix`. Open, so a caller may add states. */
12
+ export type Status = 'loading' | 'idle' | 'done' | (string & {});
13
+ /** The prefix a prompt draws: one string for every state, or one per state. */
14
+ export type ThemePrefix = string | Record<string, string>;
15
+ /** The spinner `usePrefix` runs while a prompt reports `loading`. */
16
+ export interface ThemeSpinner {
17
+ interval: number;
18
+ frames: string[];
19
+ }
20
+ /** The style functions a prompt reaches for. Named exactly as the incumbent names them. */
21
+ export interface ThemeStyle {
22
+ answer: (text: string) => string;
23
+ message: (text: string, status: Status) => string;
24
+ error: (text: string) => string;
25
+ defaultAnswer: (text: string) => string;
26
+ help: (text: string) => string;
27
+ highlight: (text: string) => string;
28
+ key: (text: string) => string;
29
+ }
30
+ /** The whole theme, as a prompt sees it after `makeTheme` has merged the defaults in. */
31
+ export interface Theme {
32
+ prefix: ThemePrefix;
33
+ spinner: ThemeSpinner;
34
+ keybindings: Keybinding[];
35
+ style: ThemeStyle;
36
+ }
37
+ /** What a caller may hand `makeTheme`: any subset, nested. */
38
+ export type PartialTheme<Extension = object> = Partial<Omit<Theme, 'style' | 'spinner'>> & {
39
+ spinner?: Partial<ThemeSpinner>;
40
+ style?: Partial<ThemeStyle>;
41
+ } & Partial<Extension>;
42
+ /**
43
+ * The incumbent's defaults, byte for byte where a byte is observable.
44
+ *
45
+ * `styleText` is `node:util`'s, which is the whole colour dependency: roundel owns colour
46
+ * for caique's own widgets, and reaching for it here would put a second policy on a subpath
47
+ * whose job is to behave like somebody else's package.
48
+ */
49
+ export declare const defaultTheme: Theme;
50
+ /** The defaults with the environment's keybindings folded in, read fresh on every call. */
51
+ export declare function getDefaultTheme(): Theme;
52
+ /**
53
+ * Merge any number of partial themes over the defaults, last one winning per leaf.
54
+ *
55
+ * `null` and `undefined` entries are dropped rather than merged, because a prompt passes
56
+ * `config.theme` straight through and a caller who set no theme passes `undefined`.
57
+ */
58
+ export declare function makeTheme<Extension = object>(...themes: readonly (PartialTheme<Extension> | undefined)[]): Theme & Extension;
@@ -0,0 +1,51 @@
1
+ import { styleText } from 'node:util';
2
+ import { getDefaultKeybindings } from './inquirer-keys.js';
3
+ import { processRuntime } from './runtime.js';
4
+ const env = processRuntime().env;
5
+ const UNICODE = process.platform !== 'win32' || env['WT_SESSION'] !== undefined || env['TERM_PROGRAM'] === 'vscode' || env['TERM'] === 'xterm-256color';
6
+ export const TICK = UNICODE ? '✔' : '√';
7
+ export const LINE = '─';
8
+ const SPINNER_INTERVAL_MS = 80;
9
+ const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
10
+ export const defaultTheme = {
11
+ prefix: { idle: styleText('blue', '?'), done: styleText('green', TICK) },
12
+ spinner: { interval: SPINNER_INTERVAL_MS, frames: FRAMES.map((frame) => styleText('yellow', frame)) },
13
+ keybindings: [],
14
+ style: {
15
+ answer: (text) => styleText('cyan', text),
16
+ message: (text) => styleText('bold', text),
17
+ error: (text) => styleText('red', `> ${text}`),
18
+ defaultAnswer: (text) => styleText('dim', `(${text})`),
19
+ help: (text) => styleText('dim', text),
20
+ highlight: (text) => styleText('cyan', text),
21
+ key: (text) => styleText('cyan', styleText('bold', `<${text}>`)),
22
+ },
23
+ };
24
+ export function getDefaultTheme() {
25
+ return { ...defaultTheme, keybindings: getDefaultKeybindings() };
26
+ }
27
+ function isPlainObject(value) {
28
+ if (typeof value !== 'object' || value === null)
29
+ return false;
30
+ let proto = value;
31
+ let next = Object.getPrototypeOf(proto);
32
+ while (next !== null) {
33
+ proto = next;
34
+ next = Object.getPrototypeOf(proto);
35
+ }
36
+ return Object.getPrototypeOf(value) === proto;
37
+ }
38
+ function deepMerge(...objects) {
39
+ const output = {};
40
+ for (const object of objects) {
41
+ for (const [key, value] of Object.entries(object)) {
42
+ const previous = output[key];
43
+ output[key] = isPlainObject(previous) && isPlainObject(value) ? deepMerge(previous, value) : value;
44
+ }
45
+ }
46
+ return output;
47
+ }
48
+ export function makeTheme(...themes) {
49
+ const toMerge = [getDefaultTheme(), ...themes.filter((theme) => theme != null)];
50
+ return deepMerge(...toMerge);
51
+ }
@@ -0,0 +1,72 @@
1
+ import { type PromptReadline } from './inquirer-hooks.js';
2
+ import { type KeypressEvent } from './inquirer-keys.js';
3
+ import { type PartialTheme, type Status } from './inquirer-theme.js';
4
+ /**
5
+ * A non-choice in a list of choices.
6
+ *
7
+ * `isSeparator` is a duck type rather than an `instanceof` check, and deliberately so: a
8
+ * caller who built `{ type: 'separator', separator: '----' }` by hand — or who has two
9
+ * copies of the package in their tree — must still have it recognised. One of the three
10
+ * graded cases is exactly that object literal.
11
+ */
12
+ export declare class Separator {
13
+ readonly separator: string;
14
+ readonly type = "separator";
15
+ constructor(separator?: string);
16
+ /** Whether `choice` is a separator — by shape, never by class. */
17
+ static isSeparator(choice: unknown): choice is Separator;
18
+ }
19
+ /**
20
+ * The prefix a prompt draws, and the spinner that replaces it while the prompt is loading.
21
+ *
22
+ * The 300 ms delay before the spinner appears is not decoration: it is what stops a prompt
23
+ * that resolves quickly from flickering a frame of spinner on its way past, and the graded
24
+ * case advances fake timers by exactly `delay + interval` to see the first frame.
25
+ */
26
+ export declare function usePrefix({ status, theme }: {
27
+ status?: Status;
28
+ theme?: PartialTheme;
29
+ }): string;
30
+ /**
31
+ * Register a keypress handler for as long as the prompt is rendering.
32
+ *
33
+ * `ignore` rather than only `removeListener`, because readline emits every keypress in one
34
+ * `data` event synchronously: a handler removed during that burst would still be called for
35
+ * the keys already queued behind the one that settled the prompt. Two graded cases — "after
36
+ * the prompt is done" and "after the prompt is aborted" — are that exact race.
37
+ */
38
+ export declare function useKeypress(userHandler: (event: KeypressEvent, rl: PromptReadline) => void | Promise<void>): void;
39
+ /** What a render function returns: one string, or a body and a line below the cursor. */
40
+ export type ViewFunction<Value, Config> = (config: Config, done: (value: Value) => void) => string | [string, string | undefined];
41
+ /** The streams and lifetime a caller may hand a prompt. */
42
+ export interface Context {
43
+ input?: NodeJS.ReadableStream;
44
+ output?: NodeJS.WritableStream;
45
+ clearPromptOnDone?: boolean;
46
+ signal?: AbortSignal;
47
+ }
48
+ /** The promise a prompt returns: cancellable, so a caller can take the question back. */
49
+ export type CancelablePromise<Value> = Promise<Value> & {
50
+ cancel: () => void;
51
+ };
52
+ /** A prompt, ready to be called with its config. */
53
+ export type Prompt<Value, Config> = (config: Config, context?: Context) => CancelablePromise<Value>;
54
+ /**
55
+ * Turn a render function into a prompt.
56
+ *
57
+ * Two lines, because the interesting half is `runPrompt` and the only thing this one adds
58
+ * is the call site — captured now rather than at the throw, since the error a render
59
+ * function gets for returning nothing names the file `createPrompt` was called from.
60
+ */
61
+ export declare function createPrompt<Value, Config>(view: ViewFunction<Value, Config>): Prompt<Value, Config>;
62
+ /**
63
+ * The rest of `@inquirer/core`'s surface, re-exported from the modules that hold it.
64
+ *
65
+ * At the end of the file rather than the top because `import-next/exports-last` says so,
66
+ * and the rule is right about this one: a reader arriving at `caique/inquirer` wants the
67
+ * prompt loop first and the vocabulary list last.
68
+ */
69
+ export { AbortPromptError, CancelPromptError, ExitPromptError, HookError, ValidationError } from './inquirer-errors.js';
70
+ export { useEffect, useMemo, useRef, useState, type SetState } from './inquirer-hooks.js';
71
+ export { getDefaultKeybindings, isBackspaceKey, isDownKey, isEnterKey, isNumberKey, isShiftKey, isSpaceKey, isTabKey, isUpKey, type Keybinding, type KeypressEvent, } from './inquirer-keys.js';
72
+ export { defaultTheme, getDefaultTheme, makeTheme, type PartialTheme, type Status, type Theme } from './inquirer-theme.js';