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