@orkestrel/terminal 0.0.12 → 0.0.14

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.
@@ -1,50 +1,43 @@
1
- import { TerminalError, checkboxReduce, confirmReduce, createCheckboxState, createConfirmState, createEditorState, createInputState, createPasswordState, createPromptTheme, createSelectState, editorReduce, errorLine, hintedHeader, inputReduce, parseKey, passwordReduce, promptHeader, sanitizeDisplayText, selectReduce } from "../core/index.js";
1
+ import { CSI, createStyler } from "@orkestrel/console";
2
+ import { NEWLINE, RETURN, TerminalError, createCheckboxState, createConfirmState, createEditorState, createInputState, createPasswordState, createPromptTheme, createSelectState, parseKey, reduceCheckbox, reduceConfirm, reduceEditor, reduceInput, reducePassword, reduceSelect, renderErrorLine, renderHintedHeader, renderPromptHeader, sanitizeDisplayText } from "../core/index.js";
2
3
  import { matchesAnswer, parseValue } from "@orkestrel/form";
3
- import { createStyler } from "@orkestrel/console";
4
+ import { isStreamTarget } from "@orkestrel/console/server";
4
5
  import { createInterface } from "node:readline";
5
6
  import { stdin, stdout } from "node:process";
6
7
  //#region src/server/constants.ts
7
- /** The Escape byte (ESC, U+001B) — the lead byte of every CSI cursor-control sequence below. */
8
- var ESCAPE = String.fromCharCode(27);
9
- /** The Control Sequence Introducer (`ESC[`) — the prefix of every cursor / erase sequence. */
10
- var CSI = `[`;
11
8
  /**
12
- * The cursor-UP sequence TEMPLATE (`ESC[{count}A`) — {@link import('./helpers.js').moveUp}
9
+ * Holds the cursor-UP sequence TEMPLATE (`ESC[{count}A`) — {@link import('./helpers.js').renderCursorUp}
13
10
  * interpolates the `{count}` placeholder with the number of lines to climb. Kept as a template so
14
11
  * the count stays out of the constant.
15
12
  */
16
13
  var CSI_UP = `${CSI}{count}A`;
17
14
  /**
18
- * Hide the cursor (`ESC[?25l`) — written before the driver starts redrawing a prompt so the cursor
15
+ * Hides the cursor (`ESC[?25l`) — written before the driver starts redrawing a prompt so the cursor
19
16
  * does not flicker across the view during an in-place re-render; paired with {@link CURSOR_SHOW}.
20
17
  */
21
18
  var CURSOR_HIDE = `${CSI}?25l`;
22
- /** Show the cursor (`ESC[?25h`) — restores the cursor after a prompt resolves / cancels (the {@link CURSOR_HIDE} pair). */
19
+ /** Shows the cursor (`ESC[?25h`) — restores the cursor after a prompt resolves / cancels (the {@link CURSOR_HIDE} pair). */
23
20
  var CURSOR_SHOW = `${CSI}?25h`;
24
21
  /**
25
- * Erase from the cursor down to the end of the screen (`ESC[J`) — wipes the WHOLE previous (possibly
22
+ * Erases from the cursor down to the end of the screen (`ESC[J`) — wipes the WHOLE previous (possibly
26
23
  * multi-line `select` / `checkbox`) view in one write before the new view is rendered, so a redraw
27
24
  * never leaves orphaned rows below.
28
25
  */
29
26
  var CLEAR_DOWN = `${CSI}J`;
30
- /** A carriage return (`\r`, U+000D) — returns the cursor to column 0 so a redraw starts at the line's left edge. */
31
- var CARRIAGE_RETURN = String.fromCharCode(13);
32
- /** A line feed (`\n`, U+000A) — the line terminator the driver writes after the final committed prompt view. */
33
- var LINE_FEED = String.fromCharCode(10);
34
27
  /**
35
- * The numbered-list prompt the non-TTY {@link import('./Terminal.js').Terminal} `select` fallback
28
+ * Holds the numbered-list prompt the non-TTY {@link import('./Terminal.js').Terminal} `select` fallback
36
29
  * appends — a piped (non-terminal) stream cannot navigate with arrow keys, so the choices are
37
30
  * printed numbered and the user types one number on a single readline line.
38
31
  */
39
32
  var FALLBACK_SELECT_HINT = "Enter a number";
40
- /** The comma-separated multi-select hint the non-TTY `checkbox` fallback shows (the user types one or more numbers). */
33
+ /** Holds the comma-separated multi-select hint the non-TTY `checkbox` fallback shows (the user types one or more numbers). */
41
34
  var FALLBACK_CHECKBOX_HINT = "Enter numbers separated by commas";
42
- /** The hint the non-TTY `editor` fallback shows — a piped stream has no ctrl-d, so end of input finishes the block. */
35
+ /** Holds the hint the non-TTY `editor` fallback shows — a piped stream has no ctrl-d, so end of input finishes the block. */
43
36
  var FALLBACK_EDITOR_HINT = "(end of input finishes)";
44
- /** The hint the non-TTY `confirm` fallback shows — a piped stream sends a whole line, so the answer is typed rather than pressed. */
37
+ /** Holds the hint the non-TTY `confirm` fallback shows — a piped stream sends a whole line, so the answer is typed rather than pressed. */
45
38
  var FALLBACK_CONFIRM_HINT = "(y/n)";
46
39
  /**
47
- * The format cue appended to a field's label for each control the walk reads as a line of text —
40
+ * Holds the format cue appended to a field's label for each control the walk reads as a line of text —
48
41
  * the terminal has no date picker, no color well, and no file chooser, so the accepted shape is
49
42
  * stated instead. A control with no entry needs none: `text` and `editor` accept any line,
50
43
  * `password` masks one, and `confirm`, `select`, and `checkbox` are answered by key rather than by
@@ -58,16 +51,16 @@ var CONTROL_HINTS = Object.freeze({
58
51
  color: "(#rrggbb)",
59
52
  file: "(path)"
60
53
  });
61
- /** The instruction a `file` field with `multiple` shows before its entries — one path per line, and a blank line ends the list. */
54
+ /** Holds the instruction a `file` field with `multiple` shows before its entries — one path per line, and a blank line ends the list. */
62
55
  var FILE_HINT = "One path per line, blank to finish";
63
- /** The lead on the line listing an open `select`'s offered values, which a typed answer may ignore. */
56
+ /** Holds the lead on the line listing an open `select`'s offered values, which a typed answer may ignore. */
64
57
  var SUGGESTION_LEAD = "Suggestions";
65
- /** The lead on the line listing the choices a `select` or `checkbox` shows but refuses, so a reader sees why one is missing from the list below. */
58
+ /** Holds the lead on the line listing the choices a `select` or `checkbox` shows but refuses, so a reader sees why one is missing from the list below. */
66
59
  var UNAVAILABLE_LEAD = "Unavailable";
67
- /** The mark on a locked field's line — the walk renders its value and moves on, because the form refuses an edit there. */
60
+ /** Holds the mark on a locked field's line — the walk renders its value and moves on, because the form refuses an edit there. */
68
61
  var LOCKED_MARK = "(locked)";
69
62
  /**
70
- * What a field is told when the walk read an answer the control cannot hold — a word typed into a
63
+ * States what a field is told when the walk read an answer the control cannot hold — a word typed into a
71
64
  * `number`, an off-list value typed into an open `select` whose choice is refused. The value binds
72
65
  * as absence and this message is invalidated onto the field, so the walk re-asks it with the reason
73
66
  * on screen.
@@ -76,8 +69,8 @@ var REFUSAL_MESSAGE = "Enter a value this field accepts";
76
69
  //#endregion
77
70
  //#region src/server/helpers.ts
78
71
  /**
79
- * Whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` / `off`
80
- * `'data'` subscription methods. A total type guard (AGENTS §14): it NEVER throws and returns `false`
72
+ * Checks whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` / `off`
73
+ * `'data'` subscription methods. A total type guard: it NEVER throws and returns `false`
81
74
  * for anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`,
82
75
  * or a fake TTY a test injects) to the exact slice the driver reads — no `as`.
83
76
  *
@@ -88,25 +81,13 @@ var REFUSAL_MESSAGE = "Enter a value this field accepts";
88
81
  * fallback rather than raw mode.
89
82
  *
90
83
  * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
91
- * @returns `true` when `value` has callable `on` and `off`
84
+ * @returns True if `value` has callable `on` and `off`; false otherwise
92
85
  */
93
86
  function isInputStream(value) {
94
87
  return typeof value === "object" && value !== null && "on" in value && typeof value.on === "function" && "off" in value && typeof value.off === "function";
95
88
  }
96
89
  /**
97
- * Whether `value` is a usable {@link OutputStreamInterface} — a record with a callable `write`. A
98
- * total type guard (AGENTS §14): it NEVER throws and returns `false` for anything off-shape, so it
99
- * narrows the output boundary (the real `process.stdout`, or a recording fake a test injects) to the
100
- * one method the driver writes through — no `as`.
101
- *
102
- * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
103
- * @returns `true` when `value` has a callable `write`
104
- */
105
- function isOutputStream(value) {
106
- return typeof value === "object" && value !== null && "write" in value && typeof value.write === "function";
107
- }
108
- /**
109
- * Whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard (AGENTS §14)
90
+ * Checks whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard
110
91
  * checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface` requires
111
92
  * as its `input`. The non-TTY fallback narrows the resolved input stream through this before handing
112
93
  * it to readline (never an `as`), so a real piped `process.stdin` (or a `PassThrough` a test injects)
@@ -114,26 +95,26 @@ function isOutputStream(value) {
114
95
  * isn't a full readable.
115
96
  *
116
97
  * @param value - The resolved input stream (or any value crossing the boundary)
117
- * @returns `true` when `value` has the readable methods readline needs
98
+ * @returns True if `value` has the readable methods readline needs; false otherwise
118
99
  */
119
100
  function isReadable(value) {
120
101
  return typeof value === "object" && value !== null && "read" in value && typeof value.read === "function" && "pipe" in value && typeof value.pipe === "function";
121
102
  }
122
103
  /**
123
- * Whether an input stream can be driven in RAW mode — it both reports `isTTY === true` AND exposes a
124
- * callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to choose its path:
104
+ * Checks whether an input stream can be driven in RAW mode — it both reports `isTTY === true` AND
105
+ * exposes a callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to choose its path:
125
106
  * `true` ⇒ the interactive raw-mode prompts (arrow-key navigation, live re-render); `false` ⇒ the
126
107
  * `node:readline` line-input fallback (a piped / non-terminal stream cannot enter raw mode). Total —
127
108
  * never throws.
128
109
  *
129
110
  * @param input - The resolved {@link InputStreamInterface}
130
- * @returns `true` when the stream is a TTY with `setRawMode`
111
+ * @returns True if the stream is a TTY with `setRawMode`; false otherwise
131
112
  */
132
- function rawCapable(input) {
113
+ function supportsRawMode(input) {
133
114
  return input.isTTY === true && typeof input.setRawMode === "function";
134
115
  }
135
116
  /**
136
- * The number of terminal LINES a rendered prompt `view` occupies — one more than its newline count
117
+ * Counts the terminal LINES a rendered prompt `view` occupies — one more than its newline count
137
118
  * (a view with no newline is a single line; N newlines span N+1 lines). The basis of the in-place
138
119
  * re-render: the driver records the line count of the view it just wrote so the next redraw knows how
139
120
  * far up to move the cursor before overwriting. Total; an empty string is one (empty) line.
@@ -147,7 +128,7 @@ function lineCount(view) {
147
128
  return lines;
148
129
  }
149
130
  /**
150
- * The cursor-UP control sequence that moves the cursor up `count` lines (`ESC[{count}A`) — or the
131
+ * Returns the cursor-UP control sequence that moves the cursor up `count` lines (`ESC[{count}A`) — or the
151
132
  * empty string when `count` is zero or negative (no movement needed, and `ESC[0A` is a wasted write).
152
133
  * The pure step the in-place re-render uses to climb back over the previous view before clearing it.
153
134
  * Total.
@@ -155,12 +136,12 @@ function lineCount(view) {
155
136
  * @param count - How many lines to move the cursor up
156
137
  * @returns The `ESC[{count}A` sequence, or `''` when `count <= 0`
157
138
  */
158
- function moveUp(count) {
139
+ function renderCursorUp(count) {
159
140
  if (count <= 0) return "";
160
141
  return `${CSI_UP.replace("{count}", String(count))}`;
161
142
  }
162
143
  /**
163
- * The full reposition-and-clear prefix to write BEFORE re-rendering a prompt view in place — given
144
+ * Returns the full reposition-and-clear prefix to write BEFORE re-rendering a prompt view in place — given
164
145
  * the line count of the PREVIOUS view, it moves the cursor up over those lines, returns it to column
165
146
  * 0, and erases everything from there to the end of the screen, so the next view is drawn on a clean
166
147
  * region (no orphaned rows from a taller previous view). Pure; the driver writes this immediately
@@ -177,15 +158,15 @@ function moveUp(count) {
177
158
  * @returns The control-sequence prefix to write before the new view
178
159
  */
179
160
  function redrawPrefix(previousLines) {
180
- return `${moveUp(previousLines - 1)}\r${CLEAR_DOWN}`;
161
+ return `${renderCursorUp(previousLines - 1)}${RETURN}${CLEAR_DOWN}`;
181
162
  }
182
163
  /**
183
- * Project any field the walk reads as a LINE OF TEXT into the {@link TextField} the text reducer
184
- * takes — `text` itself, and the six controls a terminal has no widget for: `number`, `date`,
164
+ * Projects any field the walk reads as a LINE OF TEXT into the {@link TextField} the text reducer
165
+ * takes — `text` itself, and the controls a terminal has no widget for: `number`, `date`,
185
166
  * `time`, `datetime`, `color`, and one `file` entry. The label carries that control's format cue
186
167
  * from {@link CONTROL_HINTS}, and a declared `default` becomes the line a bare return submits. The
187
168
  * projection carries no rule, because the AUTHORITATIVE form still evaluates the answer this line
188
- * binds; it exists only so one reducer can render seven controls.
169
+ * binds; it exists only so one reducer covers every one of them.
189
170
  *
190
171
  * @param field - The field being read
191
172
  * @returns The text field the reducer renders for it
@@ -209,7 +190,7 @@ function fieldToText(field) {
209
190
  };
210
191
  }
211
192
  /**
212
- * Project one held answer into the text a read-only line shows — a scalar as itself, a boolean as
193
+ * Projects one held answer into the text a read-only line shows — a scalar as itself, a boolean as
213
194
  * `yes` / `no` (the word the confirm reducer commits), and a list joined by commas. Absence renders
214
195
  * as nothing, because a locked field nobody has answered has nothing to show.
215
196
  *
@@ -224,34 +205,42 @@ function valueToText(value) {
224
205
  return value.join(", ");
225
206
  }
226
207
  /**
227
- * The choices a `select` or `checkbox` field actually OFFERS — the form refuses a disabled choice's
228
- * value at every door, including a fill, so the walk never puts one in front of the cursor. Pair
229
- * with {@link disabledChoices} to tell the reader what was withheld.
208
+ * Returns the choices a `select` or `checkbox` field actually OFFERS — the form refuses a disabled
209
+ * choice's value at every door, including a fill, so the walk never puts one in front of the
210
+ * cursor. Pair with {@link filterDisabled} to tell the reader what was withheld.
230
211
  *
231
212
  * @param choices - The field's declared choices
232
213
  * @returns The choices the walk offers, in declared order
233
214
  */
234
- function enabledChoices(choices) {
215
+ function filterEnabled(choices) {
235
216
  return choices.filter((choice) => choice.disabled !== true);
236
217
  }
237
218
  /**
238
- * The choices a `select` or `checkbox` field SHOWS but refuses — the complement of
239
- * {@link enabledChoices}, rendered by {@link unavailableLine} above the list so a reader sees why a
240
- * declared choice is missing from it.
219
+ * Returns the choices a `select` or `checkbox` field SHOWS but refuses — the complement of
220
+ * {@link filterEnabled}, rendered by {@link renderUnavailableLine} above the list so a reader sees
221
+ * why a declared choice is missing from it.
241
222
  *
242
223
  * @param choices - The field's declared choices
243
224
  * @returns The refused choices, in declared order
244
225
  */
245
- function disabledChoices(choices) {
226
+ function filterDisabled(choices) {
246
227
  return choices.filter((choice) => choice.disabled === true);
247
228
  }
248
- /** The section header the walk writes when it enters a new field group, painted by the `message` role. */
249
- function groupHeader(styler, theme, label) {
229
+ /**
230
+ * Renders the section header the walk writes when it enters a new field group, painted by the
231
+ * `message` role.
232
+ *
233
+ * @param styler - The console styler that renders each role
234
+ * @param theme - The resolved prompt theme
235
+ * @param label - The group's declared label, falling back to its own name
236
+ * @returns The rendered section header
237
+ */
238
+ function renderGroupHeader(styler, theme, label) {
250
239
  return styler.render(theme.roles.message, label);
251
240
  }
252
241
  /**
253
- * The read-only line a LOCKED field renders — its label, the {@link LOCKED_MARK}, and the answer the
254
- * form already holds. The walk writes this instead of a prompt, because the field is still
242
+ * Renders the read-only line a LOCKED field shows — its label, the {@link LOCKED_MARK}, and the
243
+ * answer the form already holds. The walk writes this instead of a prompt, because the field is still
255
244
  * validated and still submitted but must not be edited here.
256
245
  *
257
246
  * @param styler - The console styler that renders each role
@@ -260,38 +249,54 @@ function groupHeader(styler, theme, label) {
260
249
  * @param value - The held answer, from {@link valueToText}
261
250
  * @returns The rendered line, with no trailing space when there is nothing to show
262
251
  */
263
- function lockedLine(styler, theme, label, value) {
252
+ function renderLockedLine(styler, theme, label, value) {
264
253
  const head = `${styler.render(theme.roles.muted, theme.icons.dot)} ${styler.render(theme.roles.message, label)} ${styler.render(theme.roles.hint, LOCKED_MARK)}`;
265
254
  return value.length === 0 ? head : `${head} ${styler.render(theme.roles.content, value)}`;
266
255
  }
267
- /** The line listing an OPEN select's offered values above its text prompt — a suggestion list, because an open select admits an answer the list does not offer. */
268
- function suggestionLine(styler, theme, choices) {
256
+ /**
257
+ * Renders the line listing an OPEN select's offered values above its text prompt — a suggestion
258
+ * list, because an open select admits an answer the list does not offer.
259
+ *
260
+ * @param styler - The console styler that renders each role
261
+ * @param theme - The resolved prompt theme
262
+ * @param choices - The choices the open select offers, from {@link filterEnabled}
263
+ * @returns The rendered suggestion line
264
+ */
265
+ function renderSuggestionLine(styler, theme, choices) {
269
266
  const values = choices.map((choice) => choice.value).join(", ");
270
267
  return styler.render(theme.roles.hint, `${SUGGESTION_LEAD}: ${values}`);
271
268
  }
272
- /** The line naming the choices a field shows but refuses, written above the list the walk drives. */
273
- function unavailableLine(styler, theme, choices) {
269
+ /**
270
+ * Renders the line naming the choices a field shows but refuses, written above the list the walk
271
+ * drives.
272
+ *
273
+ * @param styler - The console styler that renders each role
274
+ * @param theme - The resolved prompt theme
275
+ * @param choices - The refused choices, from {@link filterDisabled}
276
+ * @returns The rendered unavailable line
277
+ */
278
+ function renderUnavailableLine(styler, theme, choices) {
274
279
  const labels = choices.map((choice) => choice.label).join(", ");
275
280
  return styler.render(theme.roles.muted, `${UNAVAILABLE_LEAD}: ${labels}`);
276
281
  }
277
282
  /**
278
- * The numbered choice list the non-TTY fallback prints — a piped stream cannot navigate with arrow
279
- * keys, so each offered choice is printed with the number the reader types back. One line per
280
- * choice, with no trailing newline.
283
+ * Renders the numbered choice list the non-TTY fallback prints — a piped stream cannot navigate
284
+ * with arrow keys, so each offered choice is printed with the number the reader types back. One
285
+ * line per choice, with no trailing newline.
281
286
  *
282
287
  * @param styler - The console styler that renders each role
283
288
  * @param theme - The resolved prompt theme
284
- * @param choices - The choices the walk offers, from {@link enabledChoices}
289
+ * @param choices - The choices the walk offers, from {@link filterEnabled}
285
290
  * @returns The rendered list
286
291
  */
287
- function numberedList(styler, theme, choices) {
292
+ function renderNumberedList(styler, theme, choices) {
288
293
  return choices.map((choice, index) => ` ${styler.render(theme.roles.muted, `${String(index + 1)})`)} ${styler.render(theme.roles.content, choice.label)}`).join("\n");
289
294
  }
290
295
  //#endregion
291
296
  //#region src/server/Terminal.ts
292
297
  /**
293
- * The interactive terminal form DRIVER — the {@link TerminalInterface} implementation for a human at
294
- * this machine's keyboard, and the only impure part of the terminal stack. {@link ask} walks one
298
+ * Implements {@link TerminalInterface} for a human at this machine's keyboard — the interactive form
299
+ * DRIVER, and the only impure part of the terminal stack. {@link ask} walks one
295
300
  * form's fields in schema order, feeds raw-mode stdin bytes through `parseKey` into the matching
296
301
  * pure reducer, renders each returned view in place, and binds every answer through the form's own
297
302
  * `fill`. It owns no form logic: the schema, the rules, the values, and the settlement all belong to
@@ -300,7 +305,7 @@ function numberedList(styler, theme, choices) {
300
305
  * @remarks
301
306
  * See {@link TerminalInterface} for the driving contract. The walk itself:
302
307
  *
303
- * - **Twelve controls, seven reducers.** `text`, `number`, `date`, `time`, `datetime`, `color`, and
308
+ * - **Controls and reducers.** `text`, `number`, `date`, `time`, `datetime`, `color`, and
304
309
  * each `file` entry are read as one line of text through {@link fieldToText}, which appends that
305
310
  * control's format cue to the label. `password`, `confirm`, `editor`, `select`, and `checkbox`
306
311
  * each drive their own reducer. An open `select` is a suggestion list plus a typed line, because
@@ -334,7 +339,7 @@ var Terminal = class {
334
339
  #ended = false;
335
340
  constructor(options) {
336
341
  this.#input = isInputStream(options?.input) ? options.input : stdin;
337
- this.#output = isOutputStream(options?.output) ? options.output : stdout;
342
+ this.#output = isStreamTarget(options?.output) ? options.output : stdout;
338
343
  this.#styler = createStyler();
339
344
  this.#theme = createPromptTheme(options?.theme);
340
345
  }
@@ -347,7 +352,7 @@ var Terminal = class {
347
352
  const result = form.submit();
348
353
  if (result.success) return await form.answer;
349
354
  this.#report(form, result.error);
350
- const again = this.#editable(form, result.error);
355
+ const again = this.#collectEditable(form, result.error);
351
356
  if (again.length === 0 || this.#ended) {
352
357
  form.destroy();
353
358
  return await form.answer;
@@ -359,7 +364,7 @@ var Terminal = class {
359
364
  }
360
365
  }
361
366
  /**
362
- * Ask `fields` in the order given, skipping what the walk must not touch and rendering what it
367
+ * Asks `fields` in the order given, skipping what the walk must not touch and rendering what it
363
368
  * must not edit. Returns early the moment the form stops being editable, so an abandoned form
364
369
  * ends the walk between fields as well as under an active read.
365
370
  */
@@ -371,10 +376,10 @@ var Terminal = class {
371
376
  if (form.disabled.has(field.name)) continue;
372
377
  if (field.group !== group) {
373
378
  group = field.group;
374
- if (group !== void 0) this.#group(form, group);
379
+ if (group !== void 0) this.#writeGroup(form, group);
375
380
  }
376
381
  if (field.locked === true) {
377
- this.#locked(form, field);
382
+ this.#writeLocked(form, field);
378
383
  continue;
379
384
  }
380
385
  const raw = await this.#read(form, field);
@@ -382,31 +387,28 @@ var Terminal = class {
382
387
  this.#bind(form, field, raw);
383
388
  }
384
389
  }
385
- /** Write a group's label as a section header, resolving the schema's declared label and falling back to the group's own name. */
386
- #group(form, name) {
390
+ /** Writes a group's label as a section header, resolving the schema's declared label and falling back to the group's own name. */
391
+ #writeGroup(form, name) {
387
392
  const label = form.schema.groups?.find((group) => group.name === name)?.label ?? name;
388
- this.#output.write(`
389
- ${groupHeader(this.#styler, this.#theme, sanitizeDisplayText(label))}
390
- `);
393
+ this.#output.write(`${NEWLINE}${renderGroupHeader(this.#styler, this.#theme, sanitizeDisplayText(label))}${NEWLINE}`);
391
394
  }
392
- /** Render a locked field read-only — its label, its mark, and the answer the form already holds. */
393
- #locked(form, field) {
394
- const line = lockedLine(this.#styler, this.#theme, sanitizeDisplayText(field.label ?? field.name), sanitizeDisplayText(valueToText(form.values[field.name])));
395
- this.#output.write(`${line}
396
- `);
395
+ /** Renders a locked field read-only — its label, its mark, and the answer the form already holds. */
396
+ #writeLocked(form, field) {
397
+ const line = renderLockedLine(this.#styler, this.#theme, sanitizeDisplayText(field.label ?? field.name), sanitizeDisplayText(valueToText(form.values[field.name])));
398
+ this.#output.write(`${line}${NEWLINE}`);
397
399
  }
398
- /** Read one field through the reducer its control names, resolving the raw answer the binding projects. */
400
+ /** Reads one field through the reducer its control names, resolving the raw answer the binding projects. */
399
401
  #read(form, field) {
400
- if (field.control === "password") return this.#password(form, field);
402
+ if (field.control === "password") return this.#askPassword(form, field);
401
403
  if (field.control === "confirm") return this.#confirm(form, field);
402
- if (field.control === "editor") return this.#editor(form, field);
404
+ if (field.control === "editor") return this.#askEditor(form, field);
403
405
  if (field.control === "select") return this.#select(form, field);
404
406
  if (field.control === "checkbox") return this.#checkbox(form, field);
405
407
  if (field.control === "file") return this.#file(form, field);
406
- return this.#text(form, field);
408
+ return this.#askText(form, field);
407
409
  }
408
410
  /**
409
- * Bind one raw answer to the form — the single binding, and the only place this driver writes a
411
+ * Binds one raw answer to the form — the single binding, and the only place this driver writes a
410
412
  * value. `parseValue` coerces the raw answer to the control's own shape and `matchesAnswer`
411
413
  * projects a blank one to absence, which is what keeps `required` refusing a bare return. A raw
412
414
  * answer the control cannot hold binds as absence and invalidates the field, so the field comes
@@ -418,8 +420,8 @@ ${groupHeader(this.#styler, this.#theme, sanitizeDisplayText(label))}
418
420
  form.fill(field.name, matchesAnswer(value) ? value : void 0);
419
421
  if (value === void 0 && matchesAnswer(raw)) form.invalidate(field.name, REFUSAL_MESSAGE);
420
422
  }
421
- /** The erroring fields the walk can ask again — every field the walk skips or renders read-only is excluded, because a second pass could not change its answer. */
422
- #editable(form, errors) {
423
+ /** Collects the erroring fields the walk can ask again — every field the walk skips or renders read-only is excluded, because a second pass could not change its answer. */
424
+ #collectEditable(form, errors) {
423
425
  const fields = [];
424
426
  for (const error of errors) {
425
427
  const field = form.field(error.field);
@@ -431,37 +433,36 @@ ${groupHeader(this.#styler, this.#theme, sanitizeDisplayText(label))}
431
433
  }
432
434
  return fields;
433
435
  }
434
- /** Write every supplied failure against its field's label. */
436
+ /** Writes every supplied failure against its field's label. */
435
437
  #report(form, errors) {
436
438
  for (const error of errors) {
437
439
  const label = sanitizeDisplayText(form.field(error.field)?.label ?? error.field);
438
440
  const message = sanitizeDisplayText(error.message);
439
- const line = errorLine(this.#styler, this.#theme, `${label}: ${message}`);
440
- this.#output.write(`${line}
441
- `);
441
+ const line = renderErrorLine(this.#styler, this.#theme, `${label}: ${message}`);
442
+ this.#output.write(`${line}${NEWLINE}`);
442
443
  }
443
444
  }
444
- /** Read a field as one line of text — `text` itself and the six controls a terminal has no widget for. */
445
- #text(form, field) {
445
+ /** Reads a field as one line of text — `text` itself and the controls a terminal has no widget for. */
446
+ #askText(form, field) {
446
447
  const state = createInputState(fieldToText(field), this.#styler, this.#theme);
447
- if (rawCapable(this.#input)) return this.#drive(form, state, inputReduce);
448
+ if (supportsRawMode(this.#input)) return this.#drive(form, state, reduceInput);
448
449
  return this.#line(form, state.message, state.default);
449
450
  }
450
- /** Read a secret — masked live in raw mode, and read without echo through readline on a stream that cannot enter it. */
451
- #password(form, field) {
451
+ /** Reads a secret — masked live in raw mode, and read without echo through readline on a stream that cannot enter it. */
452
+ #askPassword(form, field) {
452
453
  const state = createPasswordState(field, this.#styler, this.#theme);
453
- if (rawCapable(this.#input)) return this.#drive(form, state, passwordReduce);
454
- return this.#prompt(form, promptHeader(this.#styler, this.#theme, state.message));
454
+ if (supportsRawMode(this.#input)) return this.#drive(form, state, reducePassword);
455
+ return this.#prompt(form, renderPromptHeader(this.#styler, this.#theme, state.message));
455
456
  }
456
457
  /**
457
- * Read a yes or no. The fallback accepts `y` / `yes` / `n` / `no` in any case and takes the
458
+ * Reads a yes or no. The fallback accepts `y` / `yes` / `n` / `no` in any case and takes the
458
459
  * field's default for a bare line; anything else is returned as typed, so the binding refuses it
459
460
  * and the walk asks again rather than silently reading it as no.
460
461
  */
461
462
  async #confirm(form, field) {
462
463
  const state = createConfirmState(field, this.#styler, this.#theme);
463
- if (rawCapable(this.#input)) return this.#drive(form, state, confirmReduce);
464
- const header = hintedHeader(this.#styler, this.#theme, state.message, FALLBACK_CONFIRM_HINT);
464
+ if (supportsRawMode(this.#input)) return this.#drive(form, state, reduceConfirm);
465
+ const header = renderHintedHeader(this.#styler, this.#theme, state.message, FALLBACK_CONFIRM_HINT);
465
466
  const line = await this.#prompt(form, header);
466
467
  const answer = line.trim().toLowerCase();
467
468
  if (answer.length === 0) return state.default;
@@ -469,96 +470,91 @@ ${groupHeader(this.#styler, this.#theme, sanitizeDisplayText(label))}
469
470
  if (answer === "n" || answer === "no") return false;
470
471
  return line.trim();
471
472
  }
472
- /** Read text over many lines — ctrl-d finishes in raw mode, and end of input finishes on a piped stream. */
473
- #editor(form, field) {
473
+ /** Reads text over many lines — ctrl-d finishes in raw mode, and end of input finishes on a piped stream. */
474
+ #askEditor(form, field) {
474
475
  const state = createEditorState(field, this.#styler, this.#theme);
475
- if (rawCapable(this.#input)) return this.#drive(form, state, editorReduce);
476
+ if (supportsRawMode(this.#input)) return this.#drive(form, state, reduceEditor);
476
477
  return this.#block(form, state.message, state.default);
477
478
  }
478
479
  /**
479
- * Read one choice. A disabled choice is named above the list and never offered, because the form
480
+ * Reads one choice. A disabled choice is named above the list and never offered, because the form
480
481
  * refuses its value at every door. An open select is a suggestion list plus a typed line, since
481
482
  * `open` admits an answer the list does not offer; a closed select with nothing left to offer
482
483
  * resolves absence and lets the form's own rules report it.
483
484
  */
484
485
  async #select(form, field) {
485
- const choices = enabledChoices(field.choices);
486
- this.#unavailable(field.choices);
486
+ const choices = filterEnabled(field.choices);
487
+ this.#writeUnavailable(field.choices);
487
488
  if (field.open === true) {
488
489
  if (choices.length > 0) {
489
490
  const display = choices.map((choice) => ({
490
491
  ...choice,
491
492
  value: sanitizeDisplayText(choice.value)
492
493
  }));
493
- this.#output.write(`${suggestionLine(this.#styler, this.#theme, display)}
494
- `);
494
+ this.#output.write(`${renderSuggestionLine(this.#styler, this.#theme, display)}${NEWLINE}`);
495
495
  }
496
- return this.#text(form, field);
496
+ return this.#askText(form, field);
497
497
  }
498
498
  if (choices.length === 0) return void 0;
499
499
  const state = createSelectState({
500
500
  ...field,
501
501
  choices
502
502
  }, this.#styler, this.#theme);
503
- if (rawCapable(this.#input)) return this.#drive(form, state, selectReduce);
504
- this.#list(state.message, choices);
505
- const line = await this.#prompt(form, this.#hint(`${FALLBACK_SELECT_HINT}:`));
503
+ if (supportsRawMode(this.#input)) return this.#drive(form, state, reduceSelect);
504
+ this.#writeList(state.message, choices);
505
+ const line = await this.#prompt(form, this.#formatHint(`${FALLBACK_SELECT_HINT}:`));
506
506
  return choices[Number.parseInt(line.trim(), 10) - 1]?.value;
507
507
  }
508
508
  /**
509
- * Read any number of choices. Disabled choices are named above the list and never offered, so a
509
+ * Reads any number of choices. Disabled choices are named above the list and never offered, so a
510
510
  * box the form would refuse can never be ticked. An empty answer is an answered "none of them",
511
511
  * which is what `matchesAnswer` says an empty list means.
512
512
  */
513
513
  async #checkbox(form, field) {
514
- const choices = enabledChoices(field.choices);
515
- this.#unavailable(field.choices);
514
+ const choices = filterEnabled(field.choices);
515
+ this.#writeUnavailable(field.choices);
516
516
  const state = createCheckboxState({
517
517
  ...field,
518
518
  choices
519
519
  }, this.#styler, this.#theme);
520
- if (rawCapable(this.#input)) return this.#drive(form, state, checkboxReduce);
521
- this.#list(state.message, choices);
522
- return (await this.#prompt(form, this.#hint(`${FALLBACK_CHECKBOX_HINT}:`))).split(",").map((part) => choices[Number.parseInt(part.trim(), 10) - 1]?.value).filter((value) => value !== void 0);
520
+ if (supportsRawMode(this.#input)) return this.#drive(form, state, reduceCheckbox);
521
+ this.#writeList(state.message, choices);
522
+ return (await this.#prompt(form, this.#formatHint(`${FALLBACK_CHECKBOX_HINT}:`))).split(",").map((part) => choices[Number.parseInt(part.trim(), 10) - 1]?.value).filter((value) => value !== void 0);
523
523
  }
524
524
  /**
525
- * Read file paths — names only, because bytes never enter a form. A `multiple` field collects one
525
+ * Reads file paths — names only, because bytes never enter a form. A `multiple` field collects one
526
526
  * path per line until a blank one, and a single field takes one. No path at all is absence rather
527
527
  * than an empty list, because the reader answered a blank line.
528
528
  */
529
529
  async #file(form, field) {
530
- if (field.multiple === true) this.#output.write(`${this.#hint(FILE_HINT)}
531
- `);
530
+ if (field.multiple === true) this.#output.write(`${this.#formatHint(FILE_HINT)}${NEWLINE}`);
532
531
  const paths = [];
533
532
  for (;;) {
534
- const entry = await this.#text(form, field);
533
+ const entry = await this.#askText(form, field);
535
534
  if (entry.length === 0) break;
536
535
  paths.push(entry);
537
536
  if (field.multiple !== true || this.#ended) break;
538
537
  }
539
538
  return paths.length > 0 ? paths : void 0;
540
539
  }
541
- /** Name the choices a field shows but refuses, above the list the walk drives. */
542
- #unavailable(choices) {
543
- const refused = disabledChoices(choices);
540
+ /** Names the choices a field shows but refuses, above the list the walk drives. */
541
+ #writeUnavailable(choices) {
542
+ const refused = filterDisabled(choices);
544
543
  if (refused.length === 0) return;
545
- this.#output.write(`${unavailableLine(this.#styler, this.#theme, refused)}
546
- `);
547
- }
548
- /** Write a field's header above its numbered choice list — the non-TTY presentation of a choice field. */
549
- #list(message, choices) {
550
- const header = promptHeader(this.#styler, this.#theme, message);
551
- const list = numberedList(this.#styler, this.#theme, choices);
552
- this.#output.write(`${header}
553
- ${list}
554
- `);
555
- }
556
- /** Paint one line of supplementary instruction through the `hint` role. */
557
- #hint(text) {
544
+ this.#output.write(`${renderUnavailableLine(this.#styler, this.#theme, refused)}${NEWLINE}`);
545
+ }
546
+ /** Writes a field's header above its numbered choice list — the non-TTY presentation of a choice field. */
547
+ #writeList(message, choices) {
548
+ const header = renderPromptHeader(this.#styler, this.#theme, message);
549
+ const list = renderNumberedList(this.#styler, this.#theme, choices);
550
+ this.#output.write(`${header}${NEWLINE}${list}${NEWLINE}`);
551
+ }
552
+ /** Paints one line of supplementary instruction through the `hint` role. */
553
+ #formatHint(text) {
558
554
  return this.#styler.render(this.#theme.roles.hint, text);
559
555
  }
560
556
  /**
561
- * The irreducible Node raw-mode primitive — the ONLY place raw mode is touched. Switches the input
557
+ * Isolates the irreducible Node raw-mode primitive — the ONLY place raw mode is touched. Switches the input
562
558
  * into raw mode (each keypress delivered immediately, no echo), resumes its flow, and subscribes
563
559
  * `handler` to `'data'`. The paired {@link #leaveRaw} operation unsubscribes the exact handler,
564
560
  * leaves raw mode, and pauses the stream on submit, cancel, abandon, or throw.
@@ -578,7 +574,7 @@ ${list}
578
574
  this.#input.pause?.();
579
575
  }
580
576
  /**
581
- * Drive ONE field over raw-mode stdin — the generic engine every interactive control shares.
577
+ * Drives ONE field over raw-mode stdin — the generic engine every interactive control shares.
582
578
  * Renders the reducer's initial view, enters raw mode once, and on each keypress runs `parseKey` →
583
579
  * `reduce` → an in-place re-render; on `submit` it cleans up and resolves the reducer's value, on
584
580
  * `cancel` (ctrl-c) it cleans up and rejects a {@link TerminalError} coded `CANCEL`. Abandoning
@@ -594,8 +590,7 @@ ${list}
594
590
  firstView = reduce(initial, parseKey("")).view;
595
591
  this.#output.write(firstView);
596
592
  } catch (error) {
597
- this.#output.write(`${CURSOR_SHOW}
598
- `);
593
+ this.#output.write(`${CURSOR_SHOW}${NEWLINE}`);
599
594
  reject(error);
600
595
  return;
601
596
  }
@@ -619,45 +614,41 @@ ${list}
619
614
  }
620
615
  this.#render(step.view, lines);
621
616
  this.#leaveRaw(token);
622
- this.#output.write(`${CURSOR_SHOW}
623
- `);
617
+ this.#output.write(`${CURSOR_SHOW}${NEWLINE}`);
624
618
  if (step.status === "cancel") reject(new TerminalError("CANCEL", "Prompt cancelled"));
625
619
  else if (step.status === "submit" && step.value !== void 0) resolve(step.value);
626
620
  else reject(new TerminalError("DRIVER", "submit produced no value"));
627
621
  } catch (error) {
628
622
  this.#leaveRaw(token);
629
- this.#output.write(`${CURSOR_SHOW}
630
- `);
623
+ this.#output.write(`${CURSOR_SHOW}${NEWLINE}`);
631
624
  reject(error);
632
625
  }
633
626
  };
634
627
  }
635
- /** End an active read that the form outlived — release raw mode exactly as a submit does, then fail the read with the form's own error. */
628
+ /** Ends an active read that the form outlived — releases raw mode exactly as a submit does, then fails the read with the form's own error. */
636
629
  #interrupt(token, reject, error) {
637
630
  if (!this.#handlers.has(token)) return;
638
631
  this.#leaveRaw(token);
639
- this.#output.write(`${CURSOR_SHOW}
640
- `);
632
+ this.#output.write(`${CURSOR_SHOW}${NEWLINE}`);
641
633
  reject(error);
642
634
  }
643
- /** Redraw a field view in place — climb over the previous view's `previousLines`, clear, and write the new view (the pure cursor-math is {@link redrawPrefix}). */
635
+ /** Redraws a field view in place — climbs over the previous view's `previousLines`, clears, and writes the new view (the pure cursor-math is {@link redrawPrefix}). */
644
636
  #render(view, previousLines) {
645
637
  this.#output.write(`${redrawPrefix(previousLines)}${view}`);
646
638
  }
647
- /** Read one line for a text-shaped field, taking the field's default when the line is bare. */
639
+ /** Reads one line for a text-shaped field, taking the field's default when the line is bare. */
648
640
  async #line(form, message, seed) {
649
- const answer = await this.#prompt(form, promptHeader(this.#styler, this.#theme, message));
641
+ const answer = await this.#prompt(form, renderPromptHeader(this.#styler, this.#theme, message));
650
642
  return answer.length > 0 ? answer : seed;
651
643
  }
652
644
  /**
653
- * Read a whole block for an `editor` field — a piped stream has no ctrl-d keypress, so end of
645
+ * Reads a whole block for an `editor` field — a piped stream has no ctrl-d keypress, so end of
654
646
  * input is the terminator. It drains every remaining line, which is why nothing after an `editor`
655
647
  * field can be asked on the same stream.
656
648
  */
657
649
  async #block(form, message, seed) {
658
- const header = hintedHeader(this.#styler, this.#theme, message, FALLBACK_EDITOR_HINT);
659
- this.#output.write(`${header}
660
- `);
650
+ const header = renderHintedHeader(this.#styler, this.#theme, message, FALLBACK_EDITOR_HINT);
651
+ this.#output.write(`${header}${NEWLINE}`);
661
652
  const collected = [];
662
653
  for (;;) {
663
654
  const line = await this.#next(form);
@@ -667,13 +658,13 @@ ${list}
667
658
  const text = collected.join("\n");
668
659
  return text.length > 0 ? text : seed;
669
660
  }
670
- /** Write one field's header and read back the line the reader answers it with, or `''` once the stream has ended. */
661
+ /** Writes one field's header and reads back the line the reader answers it with, or `''` once the stream has ended. */
671
662
  async #prompt(form, header) {
672
663
  this.#output.write(`${header} `);
673
664
  return await this.#next(form) ?? "";
674
665
  }
675
666
  /**
676
- * Take the next line the input carries — from the buffer when the reader has already read ahead,
667
+ * Takes the next line the input carries — from the buffer when the reader has already read ahead,
677
668
  * and otherwise by waiting for one. It resolves ABSENCE once the stream has ended, so a walk over
678
669
  * an exhausted stream settles instead of waiting forever, and it rejects when the form is
679
670
  * abandoned, so an unanswerable read ends with the form rather than outliving it.
@@ -682,7 +673,7 @@ ${list}
682
673
  const buffered = this.#queue.shift();
683
674
  if (buffered !== void 0) return Promise.resolve(buffered);
684
675
  if (this.#ended) return Promise.resolve(void 0);
685
- this.#reader();
676
+ this.#startReader();
686
677
  return new Promise((resolve, reject) => {
687
678
  this.#taker = resolve;
688
679
  form.answer.catch((error) => {
@@ -693,19 +684,19 @@ ${list}
693
684
  });
694
685
  }
695
686
  /**
696
- * Open the walk's ONE readline interface, or keep the open one. A whole form is many questions
687
+ * Opens the walk's ONE readline interface, or keeps the open one. A whole form is many questions
697
688
  * over one stream, and an interface reads ahead: a fresh interface per question would swallow
698
689
  * every line it read past the one it was asked for, so the walk holds a single reader and buffers
699
690
  * what arrives early. It is closed when the walk ends, so the process is free to exit.
700
691
  */
701
- #reader() {
692
+ #startReader() {
702
693
  if (this.#interface !== void 0) return;
703
- const rl = createInterface(this.#readline());
694
+ const rl = createInterface(this.#openReadline());
704
695
  rl.on("line", (line) => this.#accept(line));
705
696
  rl.on("close", () => this.#finish());
706
697
  this.#interface = rl;
707
698
  }
708
- /** Hand a line to whoever is waiting for it, or buffer it for the next question. */
699
+ /** Hands a line to whoever is waiting for it, or buffers it for the next question. */
709
700
  #accept(line) {
710
701
  const taker = this.#taker;
711
702
  if (taker === void 0) {
@@ -715,7 +706,7 @@ ${list}
715
706
  this.#taker = void 0;
716
707
  taker(line);
717
708
  }
718
- /** Record that no further line can arrive and release whoever was waiting for one. */
709
+ /** Records that no further line can arrive and releases whoever was waiting for one. */
719
710
  #finish() {
720
711
  this.#ended = true;
721
712
  const taker = this.#taker;
@@ -723,7 +714,7 @@ ${list}
723
714
  this.#taker = void 0;
724
715
  taker(void 0);
725
716
  }
726
- /** Close the walk's reader and release a waiting question — the walk is over, so the stream must stop holding the process open. */
717
+ /** Closes the walk's reader and releases a waiting question — the walk is over, so the stream must stop holding the process open. */
727
718
  #close() {
728
719
  const taker = this.#taker;
729
720
  this.#taker = void 0;
@@ -735,14 +726,14 @@ ${list}
735
726
  rl.close();
736
727
  }
737
728
  /**
738
- * Narrow the resolved input to the `node:readline` `createInterface` boundary, never an assertion.
729
+ * Narrows the resolved input to the `node:readline` `createInterface` boundary, never an assertion.
739
730
  * The fallback only runs on a real piped `process.stdin` (or a `PassThrough` a test injects), both
740
731
  * genuine readables; a minimal non-readable fake reaching here means the walk was driven with a
741
732
  * stream it cannot use, which fails loudly rather than silently. `terminal: false` leaves readline
742
733
  * as a line decoder and nothing else: the walk writes every prompt itself, through the same output
743
734
  * every other line goes to, so nothing is written twice and a secret is never echoed.
744
735
  */
745
- #readline() {
736
+ #openReadline() {
746
737
  const input = this.#input;
747
738
  if (!isReadable(input)) throw new TerminalError("DRIVER", "Terminal fallback requires a readable input stream");
748
739
  return {
@@ -754,7 +745,7 @@ ${list}
754
745
  //#endregion
755
746
  //#region src/server/factories.ts
756
747
  /**
757
- * Create the interactive terminal form driver — the local-keyboard arm of the terminal trio, beside
748
+ * Creates the interactive terminal form driver — the local-keyboard arm of the terminal trio, beside
758
749
  * the core headless `createPrompt` broker and the SSE `createPromptClient` bridge. Where the broker
759
750
  * PARKS a live form until somebody elsewhere answers it, a `Terminal` answers one HERE: it walks the
760
751
  * form's fields in schema order, drives each control's pure reducer over raw-mode stdin, binds every
@@ -767,7 +758,7 @@ ${list}
767
758
  * @remarks
768
759
  * - **The form is the unit.** `ask` takes the caller's live form and returns its settled values. The
769
760
  * returned promise is that form's own `answer`, so a caller holding the form can await either one.
770
- * - **Every control renders.** Seven controls are read as a line of text with their format cue,
761
+ * - **Every control renders.** The line-read controls are read as a line of text with their format cue,
771
762
  * `password` masks, `confirm` takes a key, `editor` takes a block, `select` and `checkbox` drive a
772
763
  * list, and an open `select` accepts a value its list does not offer.
773
764
  * - **The form decides.** A blank answer binds as absence, the form evaluates, and a refusal
@@ -800,6 +791,6 @@ function createTerminal(options) {
800
791
  return new Terminal(options);
801
792
  }
802
793
  //#endregion
803
- export { CARRIAGE_RETURN, CLEAR_DOWN, CONTROL_HINTS, CSI, CSI_UP, CURSOR_HIDE, CURSOR_SHOW, ESCAPE, FALLBACK_CHECKBOX_HINT, FALLBACK_CONFIRM_HINT, FALLBACK_EDITOR_HINT, FALLBACK_SELECT_HINT, FILE_HINT, LINE_FEED, LOCKED_MARK, REFUSAL_MESSAGE, SUGGESTION_LEAD, Terminal, UNAVAILABLE_LEAD, createTerminal, disabledChoices, enabledChoices, fieldToText, groupHeader, isInputStream, isOutputStream, isReadable, lineCount, lockedLine, moveUp, numberedList, rawCapable, redrawPrefix, suggestionLine, unavailableLine, valueToText };
794
+ export { CLEAR_DOWN, CONTROL_HINTS, CSI_UP, CURSOR_HIDE, CURSOR_SHOW, FALLBACK_CHECKBOX_HINT, FALLBACK_CONFIRM_HINT, FALLBACK_EDITOR_HINT, FALLBACK_SELECT_HINT, FILE_HINT, LOCKED_MARK, REFUSAL_MESSAGE, SUGGESTION_LEAD, Terminal, UNAVAILABLE_LEAD, createTerminal, fieldToText, filterDisabled, filterEnabled, isInputStream, isReadable, lineCount, redrawPrefix, renderCursorUp, renderGroupHeader, renderLockedLine, renderNumberedList, renderSuggestionLine, renderUnavailableLine, supportsRawMode, valueToText };
804
795
 
805
796
  //# sourceMappingURL=index.js.map