@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,47 +1,43 @@
1
- import { STATUS_ICONS, createStyler, freezeStyle, strip, stripControls } from "@orkestrel/console";
1
+ import { CSI, ESC, STATUS_ICONS, createStyler, freezeStyle, strip, stripControls } from "@orkestrel/console";
2
2
  import { arrayOf, attempt, isArray, isBoolean, isNonEmptyString, isNumber, isRecord, isString, literalOf, parseJSON, rawShape, recordOf, stringShape } from "@orkestrel/contract";
3
3
  import { Emitter } from "@orkestrel/emitter";
4
4
  import { createForm, isFieldError, isFormError, parseForm, serializeForm } from "@orkestrel/form";
5
5
  import { createSSEParser } from "@orkestrel/sse";
6
6
  import { createDatabase, createMemoryDriver } from "@orkestrel/database";
7
7
  //#region src/core/constants.ts
8
- /** Carriage return (`\r`, U+000D) — Enter on most terminals. */
8
+ /** Names the carriage return byte (`\r`, U+000D) — Enter on most terminals. */
9
9
  var RETURN = String.fromCharCode(13);
10
- /** Line feed (`\n`, U+000A) — Enter on some terminals / pasted input. */
10
+ /** Names the line feed byte (`\n`, U+000A) — Enter on some terminals / pasted input. */
11
11
  var NEWLINE = String.fromCharCode(10);
12
- /** Tab (`\t`, U+0009). */
12
+ /** Names the tab byte (`\t`, U+0009). */
13
13
  var TAB = String.fromCharCode(9);
14
- /** Escape (ESC, U+001B) — the lone byte, and the lead byte of every CSI / SS3 sequence. */
15
- var ESCAPE = String.fromCharCode(27);
16
- /** Backspace (BS, U+0008) — Ctrl+H / some terminals' Backspace. */
14
+ /** Names the backspace byte (BS, U+0008) — Ctrl+H / some terminals' Backspace. */
17
15
  var BACKSPACE = String.fromCharCode(8);
18
- /** Delete (DEL, U+007F) — the usual Backspace byte on a Unix TTY. */
16
+ /** Names the delete byte (DEL, U+007F) — the usual Backspace byte on a Unix TTY. */
19
17
  var DELETE = String.fromCharCode(127);
20
- /** Space (U+0020). */
18
+ /** Names the space byte (U+0020). */
21
19
  var SPACE = " ";
22
- /** Ctrl+C (ETX, U+0003) — interrupt / cancel. */
20
+ /** Names the Ctrl+C byte (ETX, U+0003) — interrupt / cancel. */
23
21
  var CTRL_C = String.fromCharCode(3);
24
- /** Ctrl+D (EOT, U+0004) — end-of-transmission / finish (the editor's commit key). */
22
+ /** Names the Ctrl+D byte (EOT, U+0004) — end-of-transmission / finish (the editor's commit key). */
25
23
  var CTRL_D = String.fromCharCode(4);
26
- /** Ctrl+U (NAK, U+0015) — clear the current line. */
24
+ /** Names the Ctrl+U byte (NAK, U+0015) — clear the current line. */
27
25
  var CTRL_U = String.fromCharCode(21);
28
- /** Ctrl+A (SOH, U+0001) — move to start of line. */
26
+ /** Names the Ctrl+A byte (SOH, U+0001) — move to start of line. */
29
27
  var CTRL_A = String.fromCharCode(1);
30
- /** Ctrl+E (ENQ, U+0005) — move to end of line. */
28
+ /** Names the Ctrl+E byte (ENQ, U+0005) — move to end of line. */
31
29
  var CTRL_E = String.fromCharCode(5);
32
30
  /**
33
- * The Control Sequence Introducer lead (`ESC[`) for the navigation keys — the prefix of the
34
- * arrow / home / end / delete sequences {@link SEQUENCE_NAMES} is keyed by. Named `KEY_CSI`
35
- * (not `CSI`) so it never collides with the console module's SGR `CSI` (both barrel through
36
- * `@src/core`).
31
+ * Names the Single Shift Three lead (`ESCO`) — the alternate arrow-key prefix some terminals emit
32
+ * (`ESC O A`). Built from the console module's own {@link import('@orkestrel/console').ESC}; the
33
+ * navigation keys' CSI lead is that module's {@link import('@orkestrel/console').CSI}, which this
34
+ * package reuses rather than redeclaring.
37
35
  */
38
- var KEY_CSI = `[`;
39
- /** The Single Shift Three lead (`ESCO`) — the alternate arrow-key prefix some terminals emit (`ESC O A`). */
40
- var KEY_SS3 = `O`;
36
+ var KEY_SS3 = `${ESC}O`;
41
37
  /**
42
- * The exact escape SEQUENCE → canonical key NAME table {@link import('./helpers.js').parseKey}
38
+ * Holds the exact escape SEQUENCE → canonical key NAME table {@link import('./helpers.js').parseKey}
43
39
  * consults for the navigation / editing keys. Covers BOTH the CSI form (`ESC[A`…) and the SS3
44
- * form (`ESCOA`…) of the four arrows, plus the `home` / `end` / `delete` CSI sequences (with
40
+ * form (`ESCOA`…) of the arrows, plus the `home` / `end` / `delete` CSI sequences (with
45
41
  * their numeric-tilde variants). The source of truth for the multi-byte key decode; frozen.
46
42
  *
47
43
  * @remarks
@@ -51,32 +47,34 @@ var KEY_SS3 = `O`;
51
47
  * sees the wire encoding.
52
48
  */
53
49
  var SEQUENCE_NAMES = Object.freeze({
54
- [`${KEY_CSI}A`]: "up",
55
- [`${KEY_CSI}B`]: "down",
56
- [`${KEY_CSI}C`]: "right",
57
- [`${KEY_CSI}D`]: "left",
50
+ [`${CSI}A`]: "up",
51
+ [`${CSI}B`]: "down",
52
+ [`${CSI}C`]: "right",
53
+ [`${CSI}D`]: "left",
58
54
  [`${KEY_SS3}A`]: "up",
59
55
  [`${KEY_SS3}B`]: "down",
60
56
  [`${KEY_SS3}C`]: "right",
61
57
  [`${KEY_SS3}D`]: "left",
62
- [`${KEY_CSI}H`]: "home",
63
- [`${KEY_CSI}F`]: "end",
64
- [`${KEY_CSI}1~`]: "home",
65
- [`${KEY_CSI}4~`]: "end",
66
- [`${KEY_CSI}3~`]: "delete",
67
- [`${KEY_CSI}7~`]: "home",
68
- [`${KEY_CSI}8~`]: "end"
58
+ [`${CSI}H`]: "home",
59
+ [`${CSI}F`]: "end",
60
+ [`${CSI}1~`]: "home",
61
+ [`${CSI}4~`]: "end",
62
+ [`${CSI}3~`]: "delete",
63
+ [`${CSI}7~`]: "home",
64
+ [`${CSI}8~`]: "end"
69
65
  });
70
66
  /**
71
- * The single control BYTE → key descriptor table {@link import('./helpers.js').parseKey}
72
- * consults for the one-byte keys. Each entry carries the canonical `name` and whether it is a
73
- * `ctrl` combination. The source of truth for the single-byte key decode; frozen.
67
+ * Holds the control BYTE (or CRLF pair) → key descriptor table {@link import('./helpers.js').parseKey}
68
+ * consults for the one-byte keys and the two-byte CRLF Enter chunk. Each entry carries the
69
+ * canonical `name` and whether it is a `ctrl` combination. The source of truth for that decode;
70
+ * frozen.
74
71
  *
75
72
  * @remarks
76
- * `return` / `newline` map to `return` (one canonical Enter name); `delete` / `backspace` both
77
- * map to `backspace` (the two Backspace bytes); the Ctrl combos (`c` / `d` / `u` / `a` / `e`)
78
- * carry `ctrl: true` so a reducer can match `key.ctrl && key.name === 'c'`. `escape` / `tab` /
79
- * `space` are plain named keys.
73
+ * `return` / `newline` / `return + newline` all map to `return` (one canonical Enter name) — a
74
+ * terminal or a paste can deliver Enter as `\r`, `\n`, or the `\r\n` pair in one chunk;
75
+ * `delete` / `backspace` both map to `backspace` (the Backspace bytes); the Ctrl combos
76
+ * (`c` / `d` / `u` / `a` / `e`) carry `ctrl: true` so a reducer can match
77
+ * `key.ctrl && key.name === 'c'`. `escape` / `tab` / `space` are plain named keys.
80
78
  */
81
79
  var CONTROL_NAMES = Object.freeze({
82
80
  ["\r"]: Object.freeze({
@@ -87,11 +85,16 @@ var CONTROL_NAMES = Object.freeze({
87
85
  name: "return",
88
86
  ctrl: false
89
87
  }),
88
+ [`\r
89
+ `]: Object.freeze({
90
+ name: "return",
91
+ ctrl: false
92
+ }),
90
93
  [" "]: Object.freeze({
91
94
  name: "tab",
92
95
  ctrl: false
93
96
  }),
94
- ["\x1B"]: Object.freeze({
97
+ [ESC]: Object.freeze({
95
98
  name: "escape",
96
99
  ctrl: false
97
100
  }),
@@ -128,10 +131,10 @@ var CONTROL_NAMES = Object.freeze({
128
131
  ctrl: true
129
132
  })
130
133
  });
131
- /** The default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */
134
+ /** Names the default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */
132
135
  var DEFAULT_MASK = "*";
133
136
  /**
134
- * The six default glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from. Read only when
137
+ * Holds the default glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from. Read only when
135
138
  * the default theme is assembled; a view reads its resolved theme and never this constant. Frozen.
136
139
  *
137
140
  * @remarks
@@ -149,7 +152,7 @@ var PROMPT_ICONS = Object.freeze({
149
152
  unchecked: "☐"
150
153
  });
151
154
  /**
152
- * Every {@link import('./types.js').PromptRole}, in one frozen list — the role axis's source of
155
+ * Holds every {@link import('./types.js').PromptRole}, in one frozen list — the role axis's source of
153
156
  * truth. {@link import('./helpers.js').createPromptTheme} walks it to merge a partial theme, and
154
157
  * a consumer building a complete role map reads it rather than retyping every name.
155
158
  */
@@ -167,7 +170,7 @@ var PROMPT_ROLES = Object.freeze([
167
170
  "description"
168
171
  ]);
169
172
  /**
170
- * The {@link import('./types.js').PromptTheme} every prompt renders with unless its options supply
173
+ * Holds the {@link import('./types.js').PromptTheme} every prompt renders with unless its options supply
171
174
  * another — the glyph set assembled from {@link PROMPT_ICONS} plus the console
172
175
  * {@link import('@orkestrel/console').STATUS_ICONS} `success` / `error` marks, and the console
173
176
  * {@link import('@orkestrel/console').Style} each role is painted with. Deeply frozen through the
@@ -221,30 +224,30 @@ var DEFAULT_PROMPT_THEME = Object.freeze({
221
224
  description: freezeStyle({ attributes: ["dim"] })
222
225
  })
223
226
  });
224
- /** How long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered prompt before it expires — 5 minutes. */
227
+ /** Holds how long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered prompt before it expires — 5 minutes. */
225
228
  var DEFAULT_PROMPT_TIMEOUT_MS = 3e5;
226
- /** How long (ms) the {@link import('./types.js').PromptClientInterface} waits before each reconnect attempt — 2 seconds. */
229
+ /** Holds how long (ms) the {@link import('./types.js').PromptClientInterface} waits before each reconnect attempt — 2 seconds. */
227
230
  var DEFAULT_RECONNECT_DELAY_MS = 2e3;
228
231
  /**
229
- * The SSE `event:` names the broker emits and the {@link import('./types.js').PromptClientInterface}
232
+ * Holds the SSE `event:` names the broker emits and the {@link import('./types.js').PromptClientInterface}
230
233
  * dispatches on. Frozen; the source of truth for the wire event vocabulary.
231
234
  *
232
235
  * @remarks
233
236
  * - `pending` — a serialized {@link import('./types.js').PendingForm} to dispatch and answer.
234
237
  * - `expire` — an `{ id }` payload: the broker expired or released a parked prompt (the client drops it).
235
- * - `shutdown` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable.
238
+ * - `destroy` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable.
236
239
  */
237
240
  var SSE_EVENTS = Object.freeze({
238
241
  pending: "pending",
239
242
  expire: "expire",
240
- shutdown: "shutdown"
243
+ destroy: "destroy"
241
244
  });
242
- /** The auth-token request header the {@link import('./types.js').PromptClientInterface} sends. */
245
+ /** Names the auth-token request header the {@link import('./types.js').PromptClientInterface} sends. */
243
246
  var HEADER_TOKEN = "x-orkestrel-token";
244
- /** The `Accept` header value that opens the broker's SSE stream. */
247
+ /** Names the `Accept` header value that opens the broker's SSE stream. */
245
248
  var ACCEPT_EVENT_STREAM = "text/event-stream";
246
249
  /**
247
- * The maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its
250
+ * Sets the maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its
248
251
  * SSE parser buffer before treating the stream as hostile — 1 MiB, comfortably above any
249
252
  * legitimate prompt payload. Passed as the `limit` to `createSSEParser` so an unterminated
250
253
  * or oversized `data:` field cannot grow the buffer without bound (a memory-exhaustion guard).
@@ -253,7 +256,7 @@ var SSE_BUFFER_LIMIT = 1048576;
253
256
  //#endregion
254
257
  //#region src/core/errors.ts
255
258
  /**
256
- * The error the terminal surfaces for its own refusals: parking on a destroyed or full broker, an
259
+ * Represents the error the terminal surfaces for its own refusals: parking on a destroyed or full broker, an
257
260
  * unusable driver stream, a manager routing fault, or a ctrl-c cancellation. A parked form's own
258
261
  * lifecycle failures reject through the form's `answer` with the form package's error, never with
259
262
  * this one.
@@ -264,10 +267,17 @@ var SSE_BUFFER_LIMIT = 1048576;
264
267
  * a caught value with {@link isTerminalError} and branch on `error.code`.
265
268
  */
266
269
  var TerminalError = class extends Error {
267
- /** The machine-readable condition — see {@link TerminalErrorCode}. */
270
+ /** Holds the machine-readable condition — see {@link TerminalErrorCode}. */
268
271
  code;
269
- /** An optional context bag naming the offending values — see the class {@link TerminalError remarks}. */
272
+ /** Holds an optional context bag naming the offending values — see the class {@link TerminalError remarks}. */
270
273
  context;
274
+ /**
275
+ * Builds one terminal refusal.
276
+ *
277
+ * @param code - The machine-readable {@link TerminalErrorCode} a caller branches on
278
+ * @param message - The human-readable reason
279
+ * @param context - The optional bag naming the offending values — see the class {@link TerminalError remarks}
280
+ */
271
281
  constructor(code, message, context) {
272
282
  super(message);
273
283
  this.name = "TerminalError";
@@ -276,10 +286,10 @@ var TerminalError = class extends Error {
276
286
  }
277
287
  };
278
288
  /**
279
- * Narrow an unknown caught value to a {@link TerminalError}.
289
+ * Narrows an unknown caught value to a {@link TerminalError}.
280
290
  *
281
291
  * @param value - The value to test (typically a `catch` binding or a rejected prompt call)
282
- * @returns `true` when `value` is a {@link TerminalError}
292
+ * @returns True if `value` is a {@link TerminalError}; false otherwise
283
293
  *
284
294
  * @example
285
295
  * ```ts
@@ -297,17 +307,22 @@ function isTerminalError(value) {
297
307
  }
298
308
  //#endregion
299
309
  //#region src/core/validators.ts
300
- /** Narrow an unknown value to a {@link PendingFormStatus}. */
310
+ /**
311
+ * Narrows an unknown value to a {@link PendingFormStatus}.
312
+ *
313
+ * @param value - The candidate ticket status
314
+ * @returns True if the value is one of the declared ticket statuses; false otherwise
315
+ */
301
316
  var isPendingFormStatus = literalOf("pending", "answered", "expired");
302
317
  /**
303
- * Narrow an unknown wire value to a {@link PendingForm} envelope.
318
+ * Narrows an unknown wire value to a {@link PendingForm} envelope.
304
319
  *
305
320
  * @remarks
306
321
  * This guard checks the transport record and proves only that `schema` is a record. The Form
307
322
  * package's `parseForm` owns the schema payload and its semantic audit.
308
323
  *
309
324
  * @param value - The decoded wire value to inspect
310
- * @returns Whether the value is a complete pending-form envelope
325
+ * @returns True if the value is a complete pending-form envelope; false otherwise
311
326
  */
312
327
  function isPendingForm(value) {
313
328
  return recordOf({
@@ -320,17 +335,23 @@ function isPendingForm(value) {
320
335
  }, ["from", "to"])(value);
321
336
  }
322
337
  /**
323
- * Narrow an unknown value to a transport-neutral {@link WireEvent}.
338
+ * Narrows an unknown value to a transport-neutral {@link WireEvent}.
324
339
  *
325
340
  * @param value - The candidate wire event
326
- * @returns Whether the value carries an event name, serialized data, and an optional id
341
+ * @returns True if the value carries an event name, serialized data, and an optional id; false otherwise
327
342
  */
328
343
  var isWireEvent = recordOf({
329
344
  event: isString,
330
345
  data: isString,
331
346
  id: isString
332
347
  }, ["id"]);
333
- /** Narrow an unknown value to a {@link TerminalSnapshot}. */
348
+ /**
349
+ * Narrows an unknown value to a {@link TerminalSnapshot} — the read boundary a store applies to an
350
+ * untrusted persisted row.
351
+ *
352
+ * @param value - The candidate snapshot read back from storage
353
+ * @returns True if the value carries a non-empty `id` and an optional numeric `timeout`; false otherwise
354
+ */
334
355
  var isTerminalSnapshot = recordOf({
335
356
  id: isNonEmptyString,
336
357
  timeout: isNumber
@@ -338,24 +359,24 @@ var isTerminalSnapshot = recordOf({
338
359
  //#endregion
339
360
  //#region src/core/helpers.ts
340
361
  /**
341
- * Decode one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
342
- * read as UTF-8; the resulting string is matched against the known control bytes
343
- * ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a
344
- * single printable character. An unrecognized sequence yields `name: ''` with the raw `sequence`
362
+ * Decodes one keypress's bytes into a {@link KeyEvent} — total, never throws. A `Uint8Array` is
363
+ * read as UTF-8; the resulting string is matched against the known control bytes and the CRLF
364
+ * pair ({@link CONTROL_NAMES}) and escape sequences ({@link SEQUENCE_NAMES}), falling back to a
365
+ * single printable character. An unrecognized sequence carries NO `name`, with the raw `sequence`
345
366
  * preserved.
346
367
  *
347
368
  * @remarks
348
369
  * - **Single control byte.** A one-character control input (`return` / `backspace` / `tab` /
349
- * `escape` / `space`, or a Ctrl combo `c` / `d` / `u` / `a` / `e`) is looked up in
350
- * {@link CONTROL_NAMES}, carrying its `ctrl` flag.
370
+ * `escape` / `space`, or a Ctrl combo `c` / `d` / `u` / `a` / `e`), or the two-byte `\r\n`
371
+ * CRLF pair, is looked up in {@link CONTROL_NAMES}, carrying its `ctrl` flag.
351
372
  * - **Escape sequence.** A multi-byte ESC sequence (`up` / `down` / `left` / `right` in BOTH the
352
373
  * `ESC[A` and `ESCOA` forms, plus `home` / `end` / `delete`) is looked up in
353
374
  * {@link SEQUENCE_NAMES} and flagged `meta`.
354
375
  * - **Printable character.** A single printable character becomes `name` = that character, with
355
376
  * `shift` set when it is an uppercase letter. A multi-code-point printable (an emoji, a pasted
356
377
  * run) keeps its first code point as the name and the whole input as `sequence`.
357
- * - **Unknown.** Anything else (an unrecognized escape, an empty input) yields `name: ''` —
358
- * total, so the driver never crashes on a stray byte.
378
+ * - **Unknown.** Anything else (an unrecognized escape, an empty input) yields an event with NO
379
+ * `name` — absence, never an empty string — total, so the driver never crashes on a stray byte.
359
380
  *
360
381
  * @param input - The raw keypress bytes, as a string or `Uint8Array`
361
382
  * @returns The decoded {@link KeyEvent}
@@ -363,6 +384,7 @@ var isTerminalSnapshot = recordOf({
363
384
  * @example
364
385
  * ```ts
365
386
  * parseKey('\r') // { name: 'return', sequence: '\r', ctrl: false, meta: false, shift: false }
387
+ * parseKey('\r\n') // { name: 'return', sequence: '\r\n', ctrl: false, meta: false, shift: false }
366
388
  * parseKey('\x1b[A') // { name: 'up', sequence: '\x1b[A', ctrl: false, meta: true, shift: false }
367
389
  * parseKey('A') // { name: 'A', sequence: 'A', ctrl: false, meta: false, shift: true }
368
390
  * parseKey('\x03') // { name: 'c', sequence: '\x03', ctrl: true, meta: false, shift: false }
@@ -395,14 +417,19 @@ function parseKey(input) {
395
417
  shift: first !== first.toLowerCase()
396
418
  };
397
419
  return {
398
- name: "",
399
420
  sequence,
400
421
  ctrl: false,
401
422
  meta: false,
402
423
  shift: false
403
424
  };
404
425
  }
405
- /** Whether a single character is a printable (non-control) character — used by {@link parseKey}'s char fallback. */
426
+ /**
427
+ * Checks whether a single character is a printable (non-control) character — used by
428
+ * {@link parseKey}'s char fallback.
429
+ *
430
+ * @param character - The single character to test
431
+ * @returns True if the character is at or above space and is not DEL; false otherwise
432
+ */
406
433
  function isPrintable(character) {
407
434
  if (character.length === 0) return false;
408
435
  const code = character.codePointAt(0);
@@ -410,7 +437,7 @@ function isPrintable(character) {
410
437
  return code >= 32 && code !== 127;
411
438
  }
412
439
  /**
413
- * Build a complete {@link PromptTheme} by merging a partial one over
440
+ * Builds a complete {@link PromptTheme} by merging a partial one over
414
441
  * {@link DEFAULT_PROMPT_THEME}, leaf by leaf — each supplied icon replaces that glyph, each
415
442
  * supplied role replaces that {@link Style}, and everything else keeps its default. Each supplied
416
443
  * style is snapshotted through the console module's own
@@ -445,7 +472,7 @@ function createPromptTheme(options) {
445
472
  });
446
473
  }
447
474
  /**
448
- * Sanitize text for one single-line display slot. Composes console's ANSI {@link strip} and C0
475
+ * Sanitizes text for one single-line display slot. Composes console's ANSI {@link strip} and C0
449
476
  * {@link stripControls} passes with removal of tab, line feed, and carriage return.
450
477
  *
451
478
  * @param text - The text to sanitize for a glyph or hint slot
@@ -460,7 +487,7 @@ function sanitizeDisplayText(text) {
460
487
  return stripControls(strip(text)).replaceAll(" ", "").replaceAll("\n", "").replaceAll("\r", "");
461
488
  }
462
489
  /**
463
- * Sanitize every terminal-readable string in a parsed form schema.
490
+ * Sanitizes every terminal-readable string in a parsed form schema.
464
491
  *
465
492
  * @remarks
466
493
  * Display strings pass through {@link sanitizeDisplayText}: labels, help, placeholders, masks,
@@ -502,6 +529,7 @@ function sanitizeSchema(schema) {
502
529
  switch (field.control) {
503
530
  case "text":
504
531
  case "editor":
532
+ case "number":
505
533
  fields.push({
506
534
  ...field,
507
535
  ...shared,
@@ -515,22 +543,10 @@ function sanitizeSchema(schema) {
515
543
  ...field.mask !== void 0 ? { mask: sanitizeDisplayText(field.mask) } : {}
516
544
  });
517
545
  break;
518
- case "number":
519
- fields.push({
520
- ...field,
521
- ...shared,
522
- ...field.placeholder !== void 0 ? { placeholder: sanitizeDisplayText(field.placeholder) } : {}
523
- });
524
- break;
525
546
  case "date":
526
547
  case "time":
527
548
  case "datetime":
528
549
  case "color":
529
- fields.push({
530
- ...field,
531
- ...shared
532
- });
533
- break;
534
550
  case "confirm":
535
551
  fields.push({
536
552
  ...field,
@@ -538,17 +554,6 @@ function sanitizeSchema(schema) {
538
554
  });
539
555
  break;
540
556
  case "select":
541
- fields.push({
542
- ...field,
543
- ...shared,
544
- choices: field.choices.map((choice) => ({
545
- ...choice,
546
- value: choice.value,
547
- label: sanitizeDisplayText(choice.label),
548
- ...choice.help !== void 0 ? { help: sanitizeDisplayText(choice.help) } : {}
549
- }))
550
- });
551
- break;
552
557
  case "checkbox":
553
558
  fields.push({
554
559
  ...field,
@@ -577,7 +582,7 @@ function sanitizeSchema(schema) {
577
582
  };
578
583
  }
579
584
  /**
580
- * Sanitize every glyph a wire-supplied {@link PromptThemeOptions} carries for a single-line display
585
+ * Sanitizes every glyph a wire-supplied {@link PromptThemeOptions} carries for a single-line display
581
586
  * slot. Only the icons need it: a role is guard-narrowed to a console {@link Style}, whose colors
582
587
  * and attributes are fixed name sets, so no role can carry a byte a terminal would act on.
583
588
  *
@@ -594,13 +599,21 @@ function sanitizeThemeIcons(theme) {
594
599
  icons: sanitized
595
600
  };
596
601
  }
597
- /** The styled prompt-message header (`? message`) — the leading line every active prompt view shares, themed by the `question` + `message` roles. */
598
- function promptHeader(styler, theme, message) {
602
+ /**
603
+ * Renders the styled question header (`? message`) — the leading line every active prompt view
604
+ * shares, themed by the `question` + `message` roles.
605
+ *
606
+ * @param styler - The console styler that renders each role
607
+ * @param theme - The resolved prompt theme
608
+ * @param message - The prompt's question text
609
+ * @returns The rendered question header
610
+ */
611
+ function renderPromptHeader(styler, theme, message) {
599
612
  return `${styler.render(theme.roles.question, theme.icons.question)} ${styler.render(theme.roles.message, message)}`;
600
613
  }
601
614
  /**
602
- * Render a prompt header followed by a key hint painted with the `hint` role, or the header alone
603
- * when no hint is supplied.
615
+ * Renders a question header followed by a key hint painted with the `hint` role, or the header
616
+ * alone when no hint is supplied.
604
617
  *
605
618
  * @param styler - The console styler that renders each role
606
619
  * @param theme - The resolved prompt theme
@@ -608,20 +621,35 @@ function promptHeader(styler, theme, message) {
608
621
  * @param hint - The optional key hint to append
609
622
  * @returns The rendered header with the optional hint
610
623
  */
611
- function hintedHeader(styler, theme, message, hint) {
612
- const head = promptHeader(styler, theme, message);
624
+ function renderHintedHeader(styler, theme, message, hint) {
625
+ const head = renderPromptHeader(styler, theme, message);
613
626
  return hint === void 0 ? head : `${head} ${styler.render(theme.roles.hint, hint)}`;
614
627
  }
615
- /** The styled submit line (`✔ message`) — the committed header an interactive prompt shows once resolved, themed by the `success` + `message` roles. */
616
- function submitHeader(styler, theme, message) {
628
+ /**
629
+ * Renders the styled submit line (`✔ message`) — the committed header an interactive prompt shows
630
+ * after it resolves, themed by the `success` + `message` roles.
631
+ *
632
+ * @param styler - The console styler that renders each role
633
+ * @param theme - The resolved prompt theme
634
+ * @param message - The settled prompt's question text
635
+ * @returns The rendered committed header
636
+ */
637
+ function renderSubmitHeader(styler, theme, message) {
617
638
  return `${styler.render(theme.roles.success, theme.icons.success)} ${styler.render(theme.roles.message, message)}`;
618
639
  }
619
- /** The styled error line (`✖ message`) a form driver appends for a field failure. */
620
- function errorLine(styler, theme, message) {
640
+ /**
641
+ * Renders the styled failure line (`✖ message`) a form driver appends for a refused field.
642
+ *
643
+ * @param styler - The console styler that renders each role
644
+ * @param theme - The resolved prompt theme
645
+ * @param message - The failure text, normally the field's label and the reason
646
+ * @returns The rendered failure line
647
+ */
648
+ function renderErrorLine(styler, theme, message) {
621
649
  return `${styler.render(theme.roles.error, theme.icons.error)} ${styler.render(theme.roles.error, message)}`;
622
650
  }
623
651
  /**
624
- * Build the initial text-field key state.
652
+ * Builds the initial text-field key state.
625
653
  *
626
654
  * @param field - The text field to render
627
655
  * @param styler - The styler used to render the view
@@ -637,22 +665,31 @@ function createInputState(field, styler = createStyler(), theme) {
637
665
  value: ""
638
666
  };
639
667
  }
640
- /** Render a text-field key state as a styled view. */
641
- function inputView(state) {
668
+ /**
669
+ * Renders a text-field key state as a styled view.
670
+ *
671
+ * @param state - The text field's current reducer state
672
+ * @returns The rendered single-line view — header, pointer, and the typed value or the default
673
+ */
674
+ function renderInputView(state) {
642
675
  const content = state.value.length > 0 ? state.value : state.default;
643
676
  const role = state.value.length > 0 ? state.theme.roles.content : state.theme.roles.hint;
644
677
  const shown = state.styler.render(role, sanitizeDisplayText(content));
645
- return `${promptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${shown}`;
678
+ return `${renderPromptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${shown}`;
646
679
  }
647
680
  /**
648
- * Advance an input prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
681
+ * Advances an input prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
649
682
  * reducer. Printable characters extend the value; backspace shrinks it; ctrl-u clears it; ctrl-c
650
683
  * cancels; return produces the candidate value, with an empty line falling back to the default.
684
+ *
685
+ * @param state - The text field's current reducer state
686
+ * @param key - The decoded keypress to apply
687
+ * @returns The next step — the new state, the rendered view, the status, and the value on submit
651
688
  */
652
- function inputReduce(state, key) {
689
+ function reduceInput(state, key) {
653
690
  if (key.ctrl && key.name === "c") return {
654
691
  state,
655
- view: inputView(state),
692
+ view: renderInputView(state),
656
693
  status: "cancel"
657
694
  };
658
695
  if (key.name === "return") {
@@ -662,7 +699,7 @@ function inputReduce(state, key) {
662
699
  ...state,
663
700
  value: answer
664
701
  },
665
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, sanitizeDisplayText(answer))}`,
702
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, sanitizeDisplayText(answer))}`,
666
703
  status: "submit",
667
704
  value: answer
668
705
  };
@@ -670,7 +707,7 @@ function inputReduce(state, key) {
670
707
  const value = editLine(state.value, key);
671
708
  if (value === void 0) return {
672
709
  state,
673
- view: inputView(state),
710
+ view: renderInputView(state),
674
711
  status: "active"
675
712
  };
676
713
  const next = {
@@ -679,12 +716,12 @@ function inputReduce(state, key) {
679
716
  };
680
717
  return {
681
718
  state: next,
682
- view: inputView(next),
719
+ view: renderInputView(next),
683
720
  status: "active"
684
721
  };
685
722
  }
686
723
  /**
687
- * Build the initial password-field key state.
724
+ * Builds the initial password-field key state.
688
725
  *
689
726
  * @param field - The password field to render
690
727
  * @param styler - The styler used to render the view
@@ -700,32 +737,41 @@ function createPasswordState(field, styler = createStyler(), theme) {
700
737
  value: ""
701
738
  };
702
739
  }
703
- /** Render a password-field key state as a styled view. */
704
- function passwordView(state) {
740
+ /**
741
+ * Renders a password-field key state as a styled view.
742
+ *
743
+ * @param state - The password field's current reducer state
744
+ * @returns The rendered view, with the mask repeated in place of the typed value
745
+ */
746
+ function renderPasswordView(state) {
705
747
  const masked = state.styler.render(state.theme.roles.content, state.mask.repeat(state.value.length));
706
- return `${promptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${masked}`;
748
+ return `${renderPromptHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer)} ${masked}`;
707
749
  }
708
750
  /**
709
- * Advance a password prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
710
- * reducer. Identical line-editing to {@link inputReduce} (printable extends, backspace shrinks,
751
+ * Advances a password prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
752
+ * reducer. Identical line-editing to {@link reduceInput} (printable extends, backspace shrinks,
711
753
  * ctrl-u clears, ctrl-c cancels) but the view masks the value. Return produces the candidate value.
754
+ *
755
+ * @param state - The password field's current reducer state
756
+ * @param key - The decoded keypress to apply
757
+ * @returns The next step — the new state, the masked view, the status, and the value on submit
712
758
  */
713
- function passwordReduce(state, key) {
759
+ function reducePassword(state, key) {
714
760
  if (key.ctrl && key.name === "c") return {
715
761
  state,
716
- view: passwordView(state),
762
+ view: renderPasswordView(state),
717
763
  status: "cancel"
718
764
  };
719
765
  if (key.name === "return") return {
720
766
  state,
721
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, state.mask.repeat(state.value.length))}`,
767
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, state.mask.repeat(state.value.length))}`,
722
768
  status: "submit",
723
769
  value: state.value
724
770
  };
725
771
  const value = editLine(state.value, key);
726
772
  if (value === void 0) return {
727
773
  state,
728
- view: passwordView(state),
774
+ view: renderPasswordView(state),
729
775
  status: "active"
730
776
  };
731
777
  const next = {
@@ -734,12 +780,12 @@ function passwordReduce(state, key) {
734
780
  };
735
781
  return {
736
782
  state: next,
737
- view: passwordView(next),
783
+ view: renderPasswordView(next),
738
784
  status: "active"
739
785
  };
740
786
  }
741
787
  /**
742
- * Build the initial confirm-field key state.
788
+ * Builds the initial confirm-field key state.
743
789
  *
744
790
  * @param field - The confirm field to render
745
791
  * @param styler - The styler used to render the view
@@ -755,43 +801,50 @@ function createConfirmState(field, styler = createStyler(), theme) {
755
801
  };
756
802
  }
757
803
  /**
758
- * Render a confirm-field key state as a styled view. The selected role paints the default letter.
804
+ * Renders a confirm-field key state as a styled view. The selected role paints the default letter.
805
+ *
806
+ * @param state - The confirm field's current reducer state
807
+ * @returns The rendered view — the header and the yes/no group with the default capitalized
759
808
  */
760
- function confirmView(state) {
761
- const head = promptHeader(state.styler, state.theme, state.message);
809
+ function renderConfirmView(state) {
810
+ const head = renderPromptHeader(state.styler, state.theme, state.message);
762
811
  const answer = state.default ? `${state.styler.render(state.theme.roles.selected, "Y")}${state.styler.render(state.theme.roles.hint, "/n")}` : `${state.styler.render(state.theme.roles.hint, "y/")}${state.styler.render(state.theme.roles.selected, "N")}`;
763
812
  return `${head} ${state.styler.render(state.theme.roles.hint, "(")}${answer}${state.styler.render(state.theme.roles.hint, ")")}`;
764
813
  }
765
814
  /**
766
- * Advance a confirm prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<boolean>`
815
+ * Advances a confirm prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<boolean>`
767
816
  * reducer. `y` / `Y` submits `true`, `n` / `N` submits `false`, return on an empty line submits
768
817
  * the `default`, ctrl-c cancels; any other key is ignored (stays active).
818
+ *
819
+ * @param state - The confirm field's current reducer state
820
+ * @param key - The decoded keypress to apply
821
+ * @returns The next step — the state, the rendered view, the status, and the answer on submit
769
822
  */
770
- function confirmReduce(state, key) {
823
+ function reduceConfirm(state, key) {
771
824
  if (key.ctrl && key.name === "c") return {
772
825
  state,
773
- view: confirmView(state),
826
+ view: renderConfirmView(state),
774
827
  status: "cancel"
775
828
  };
776
829
  let answer;
777
- const choice = key.name.toLowerCase();
830
+ const choice = key.name?.toLowerCase();
778
831
  if (key.name === "return") answer = state.default;
779
832
  else if (choice === "y") answer = true;
780
833
  else if (choice === "n") answer = false;
781
834
  if (answer === void 0) return {
782
835
  state,
783
- view: confirmView(state),
836
+ view: renderConfirmView(state),
784
837
  status: "active"
785
838
  };
786
839
  return {
787
840
  state,
788
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, answer ? "yes" : "no")}`,
841
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, answer ? "yes" : "no")}`,
789
842
  status: "submit",
790
843
  value: answer
791
844
  };
792
845
  }
793
846
  /**
794
- * Build the initial select-field key state.
847
+ * Builds the initial select-field key state.
795
848
  *
796
849
  * @param field - The select field to render
797
850
  * @param styler - The styler used to render the view
@@ -809,30 +862,39 @@ function createSelectState(field, styler = createStyler(), theme) {
809
862
  focused: index >= 0 ? index : 0
810
863
  };
811
864
  }
812
- /** Render a select-field key state as a multi-line styled view. */
813
- function selectView(state) {
865
+ /**
866
+ * Renders a select-field key state as a multi-line styled view.
867
+ *
868
+ * @param state - The select field's current reducer state
869
+ * @returns The rendered view — the header, then one row per choice with the focused row marked
870
+ */
871
+ function renderSelectView(state) {
814
872
  const lines = state.choices.map((choice, index) => {
815
873
  const active = index === state.focused;
816
874
  return `${active ? state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer) : " "} ${active ? state.styler.render(state.theme.roles.selected, state.theme.icons.selected) : state.styler.render(state.theme.roles.muted, state.theme.icons.dot)} ${active ? state.styler.render(state.theme.roles.focus, choice.label) : state.styler.render(state.theme.roles.content, choice.label)}${choice.help === void 0 ? "" : ` ${state.styler.render(state.theme.roles.description, choice.help)}`}`;
817
875
  });
818
- return [promptHeader(state.styler, state.theme, state.message), ...lines].join("\n");
876
+ return [renderPromptHeader(state.styler, state.theme, state.message), ...lines].join("\n");
819
877
  }
820
878
  /**
821
- * Advance a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
879
+ * Advances a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
822
880
  * reducer. `up` / `down` (and `k` / `j`) move the focus, WRAPPING at the ends; return submits the
823
881
  * focused choice's `value`; ctrl-c cancels. An empty choice list can never submit (a higher layer
824
882
  * guards against it); any other key is ignored.
883
+ *
884
+ * @param state - The select field's current reducer state
885
+ * @param key - The decoded keypress to apply
886
+ * @returns The next step — the state, the rendered view, the status, and the chosen value on submit
825
887
  */
826
- function selectReduce(state, key) {
888
+ function reduceSelect(state, key) {
827
889
  if (key.ctrl && key.name === "c") return {
828
890
  state,
829
- view: selectView(state),
891
+ view: renderSelectView(state),
830
892
  status: "cancel"
831
893
  };
832
894
  const count = state.choices.length;
833
895
  if (count === 0) return {
834
896
  state,
835
- view: selectView(state),
897
+ view: renderSelectView(state),
836
898
  status: "active"
837
899
  };
838
900
  if (key.name === "up" || key.name === "k") {
@@ -842,7 +904,7 @@ function selectReduce(state, key) {
842
904
  };
843
905
  return {
844
906
  state: next,
845
- view: selectView(next),
907
+ view: renderSelectView(next),
846
908
  status: "active"
847
909
  };
848
910
  }
@@ -853,7 +915,7 @@ function selectReduce(state, key) {
853
915
  };
854
916
  return {
855
917
  state: next,
856
- view: selectView(next),
918
+ view: renderSelectView(next),
857
919
  status: "active"
858
920
  };
859
921
  }
@@ -862,19 +924,19 @@ function selectReduce(state, key) {
862
924
  const value = choice?.value ?? "";
863
925
  return {
864
926
  state,
865
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, choice?.label ?? "")}`,
927
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, choice?.label ?? "")}`,
866
928
  status: "submit",
867
929
  value
868
930
  };
869
931
  }
870
932
  return {
871
933
  state,
872
- view: selectView(state),
934
+ view: renderSelectView(state),
873
935
  status: "active"
874
936
  };
875
937
  }
876
938
  /**
877
- * Build the initial checkbox-field key state.
939
+ * Builds the initial checkbox-field key state.
878
940
  *
879
941
  * @param field - The checkbox field to render
880
942
  * @param styler - The styler used to render the view
@@ -896,8 +958,13 @@ function createCheckboxState(field, styler = createStyler(), theme) {
896
958
  checked
897
959
  };
898
960
  }
899
- /** Render a checkbox-field key state as a multi-line styled view. */
900
- function checkboxView(state) {
961
+ /**
962
+ * Renders a checkbox-field key state as a multi-line styled view.
963
+ *
964
+ * @param state - The checkbox field's current reducer state
965
+ * @returns The rendered view — the header, one box per choice, and the selected count
966
+ */
967
+ function renderCheckboxView(state) {
901
968
  const lines = state.choices.map((choice, index) => {
902
969
  const active = index === state.focused;
903
970
  const ticked = state.checked.includes(index);
@@ -905,21 +972,25 @@ function checkboxView(state) {
905
972
  });
906
973
  const summary = state.styler.render(state.theme.roles.hint, `${state.checked.length} selected`);
907
974
  return [
908
- promptHeader(state.styler, state.theme, state.message),
975
+ renderPromptHeader(state.styler, state.theme, state.message),
909
976
  ...lines,
910
977
  summary
911
978
  ].join("\n");
912
979
  }
913
980
  /**
914
- * Advance a checkbox prompt by one {@link KeyEvent} — the pure
981
+ * Advances a checkbox prompt by one {@link KeyEvent} — the pure
915
982
  * `(state, key) → PromptStep<readonly string[]>` reducer. `up` / `down` (and `k` / `j`) move the
916
983
  * focus (wrapping); `space` toggles the focused index in the checked set; return submits the
917
984
  * checked values in choice order; ctrl-c cancels. The form applies selection-count rules.
985
+ *
986
+ * @param state - The checkbox field's current reducer state
987
+ * @param key - The decoded keypress to apply
988
+ * @returns The next step — the state, the rendered view, the status, and the ticked values on submit
918
989
  */
919
- function checkboxReduce(state, key) {
990
+ function reduceCheckbox(state, key) {
920
991
  if (key.ctrl && key.name === "c") return {
921
992
  state,
922
- view: checkboxView(state),
993
+ view: renderCheckboxView(state),
923
994
  status: "cancel"
924
995
  };
925
996
  const count = state.choices.length;
@@ -930,7 +1001,7 @@ function checkboxReduce(state, key) {
930
1001
  };
931
1002
  return {
932
1003
  state: next,
933
- view: checkboxView(next),
1004
+ view: renderCheckboxView(next),
934
1005
  status: "active"
935
1006
  };
936
1007
  }
@@ -941,7 +1012,7 @@ function checkboxReduce(state, key) {
941
1012
  };
942
1013
  return {
943
1014
  state: next,
944
- view: checkboxView(next),
1015
+ view: renderCheckboxView(next),
945
1016
  status: "active"
946
1017
  };
947
1018
  }
@@ -953,7 +1024,7 @@ function checkboxReduce(state, key) {
953
1024
  };
954
1025
  return {
955
1026
  state: next,
956
- view: checkboxView(next),
1027
+ view: renderCheckboxView(next),
957
1028
  status: "active"
958
1029
  };
959
1030
  }
@@ -963,23 +1034,30 @@ function checkboxReduce(state, key) {
963
1034
  const summary = ordered.map((index) => state.choices[index]?.label).filter((name) => name !== void 0).join(", ");
964
1035
  return {
965
1036
  state,
966
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, summary)}`,
1037
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, summary)}`,
967
1038
  status: "submit",
968
1039
  value: values
969
1040
  };
970
1041
  }
971
1042
  return {
972
1043
  state,
973
- view: checkboxView(state),
1044
+ view: renderCheckboxView(state),
974
1045
  status: "active"
975
1046
  };
976
1047
  }
977
- /** Toggle `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion list. */
1048
+ /**
1049
+ * Toggles `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion
1050
+ * list.
1051
+ *
1052
+ * @param indices - The ticked indices, in tick order
1053
+ * @param index - The index to add when absent, or drop when present
1054
+ * @returns A new list carrying the toggled membership; the input is never mutated
1055
+ */
978
1056
  function toggleIndex(indices, index) {
979
1057
  return indices.includes(index) ? indices.filter((i) => i !== index) : [...indices, index];
980
1058
  }
981
1059
  /**
982
- * Build the initial editor-field key state.
1060
+ * Builds the initial editor-field key state.
983
1061
  *
984
1062
  * @param field - The editor field to render
985
1063
  * @param styler - The styler used to render the view
@@ -997,23 +1075,30 @@ function createEditorState(field, styler = createStyler(), theme) {
997
1075
  };
998
1076
  }
999
1077
  /**
1000
- * Render an editor-field key state as a multi-line styled view with its finish hint.
1078
+ * Renders an editor-field key state as a multi-line styled view with its finish hint.
1079
+ *
1080
+ * @param state - The editor field's current reducer state
1081
+ * @returns The rendered view — the hinted header, the committed lines, and the line in progress
1001
1082
  */
1002
- function editorView(state) {
1003
- const head = hintedHeader(state.styler, state.theme, state.message, "(Ctrl+D to finish)");
1083
+ function renderEditorView(state) {
1084
+ const head = renderHintedHeader(state.styler, state.theme, state.message, "(Ctrl+D to finish)");
1004
1085
  const pointer = state.styler.render(state.theme.roles.pointer, state.theme.icons.pointer);
1005
1086
  return [head, ...[...state.lines.map((line) => state.styler.render(state.theme.roles.content, line)), `${pointer} ${state.styler.render(state.theme.roles.content, state.current)}`]].join("\n");
1006
1087
  }
1007
1088
  /**
1008
- * Advance an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
1089
+ * Advances an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
1009
1090
  * reducer. Printable characters extend the current line; backspace shrinks it; return commits the
1010
1091
  * current line and starts a fresh one; ctrl-d FINISHES (joining all lines, falling back to the
1011
1092
  * default when empty); ctrl-c cancels. The form validates the candidate after the driver fills it.
1093
+ *
1094
+ * @param state - The editor field's current reducer state
1095
+ * @param key - The decoded keypress to apply
1096
+ * @returns The next step — the state, the rendered view, the status, and the joined text on submit
1012
1097
  */
1013
- function editorReduce(state, key) {
1098
+ function reduceEditor(state, key) {
1014
1099
  if (key.ctrl && key.name === "c") return {
1015
1100
  state,
1016
- view: editorView(state),
1101
+ view: renderEditorView(state),
1017
1102
  status: "cancel"
1018
1103
  };
1019
1104
  if (key.ctrl && key.name === "d") {
@@ -1022,7 +1107,7 @@ function editorReduce(state, key) {
1022
1107
  const answer = joined.length > 0 ? joined : state.default;
1023
1108
  return {
1024
1109
  state,
1025
- view: `${submitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, `${String(lines.length)} line${lines.length === 1 ? "" : "s"}`)}`,
1110
+ view: `${renderSubmitHeader(state.styler, state.theme, state.message)} ${state.styler.render(state.theme.roles.hint, `${String(lines.length)} line${lines.length === 1 ? "" : "s"}`)}`,
1026
1111
  status: "submit",
1027
1112
  value: answer
1028
1113
  };
@@ -1035,14 +1120,14 @@ function editorReduce(state, key) {
1035
1120
  };
1036
1121
  return {
1037
1122
  state: next,
1038
- view: editorView(next),
1123
+ view: renderEditorView(next),
1039
1124
  status: "active"
1040
1125
  };
1041
1126
  }
1042
1127
  const current = editLine(state.current, key);
1043
1128
  if (current === void 0) return {
1044
1129
  state,
1045
- view: editorView(state),
1130
+ view: renderEditorView(state),
1046
1131
  status: "active"
1047
1132
  };
1048
1133
  const next = {
@@ -1051,47 +1136,65 @@ function editorReduce(state, key) {
1051
1136
  };
1052
1137
  return {
1053
1138
  state: next,
1054
- view: editorView(next),
1139
+ view: renderEditorView(next),
1055
1140
  status: "active"
1056
1141
  };
1057
1142
  }
1058
1143
  /**
1059
- * Apply a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input /
1144
+ * Applies a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input /
1060
1145
  * password / editor. A printable key appends its character; `backspace` drops the last character;
1061
- * `space` appends a space; ctrl-u clears the line. Returns the new buffer, or `undefined` when the
1062
- * key does not edit the line (so the caller can leave the state untouched).
1146
+ * `space` appends a space; ctrl-u clears the line.
1147
+ *
1148
+ * @param value - The buffer the field holds so far
1149
+ * @param key - The decoded keypress to apply
1150
+ * @returns The new buffer, or `undefined` when the key does not edit the line, so the caller can
1151
+ * leave the state untouched
1063
1152
  */
1064
1153
  function editLine(value, key) {
1065
1154
  if (key.ctrl && key.name === "u") return "";
1066
1155
  if (key.name === "backspace") return value.slice(0, -1);
1067
1156
  if (key.name === "space") return `${value} `;
1068
- if (!key.ctrl && !key.meta && [...key.name].length === 1 && isPrintable(key.name)) return `${value}${key.sequence}`;
1157
+ if (!key.ctrl && !key.meta && key.name !== void 0 && [...key.name].length === 1 && isPrintable(key.name)) return `${value}${key.sequence}`;
1069
1158
  }
1070
1159
  /**
1071
- * The default {@link import('./types.js').TimerHandler} — a thin host `setTimeout` / `clearTimeout`
1072
- * wrapper that arms `callback` after `ms` and returns a {@link TimerCancel}. The deadline seam
1160
+ * Implements the default {@link import('./types.js').TimerHandler} — a thin host `setTimeout` / `clearTimeout`
1161
+ * wrapper that arms `callback` after `ms` and returns a {@link TimerCancelFunction}. The deadline seam
1073
1162
  * behind both the {@link import('./Prompt.js').Prompt} broker (its expiry) and the
1074
1163
  * {@link import('./PromptClient.js').PromptClient} (its reconnect backoff); a test injects a
1075
1164
  * deterministic timer instead, so neither entity touches real time.
1165
+ *
1166
+ * @param callback - The deadline callback to arm
1167
+ * @param ms - How long to wait before firing it, in milliseconds
1168
+ * @returns The {@link TimerCancelFunction} that clears the armed deadline
1076
1169
  */
1077
1170
  function defaultTimer(callback, ms) {
1078
1171
  const handle = setTimeout(callback, ms);
1079
1172
  return () => clearTimeout(handle);
1080
1173
  }
1081
- /** The default {@link import('./types.js').FetchHandler} — the global `fetch`, adapted to the minimal injected shape the {@link import('./PromptClient.js').PromptClient} uses. */
1174
+ /**
1175
+ * Implements the default {@link import('./types.js').FetchHandler} — the global `fetch`, adapted to
1176
+ * the minimal injected shape the {@link import('./PromptClient.js').PromptClient} uses.
1177
+ *
1178
+ * @param input - The request URL
1179
+ * @param init - The request init the client sets — method, headers, body, and abort signal
1180
+ * @returns The host `fetch` promise for that request
1181
+ */
1082
1182
  function globalFetch(input, init) {
1083
1183
  return fetch(input, init);
1084
1184
  }
1085
1185
  /**
1086
- * Whether a caught value is an `AbortError` — the {@link import('./PromptClient.js').PromptClient}
1186
+ * Checks whether a caught value is an `AbortError` — the {@link import('./PromptClient.js').PromptClient}
1087
1187
  * distinguishes a deliberate `disconnect` / teardown (an aborted `fetch`) from a real fault, so it
1088
1188
  * exits its connect loop quietly instead of emitting `error` / reconnecting.
1189
+ *
1190
+ * @param error - The caught value to test
1191
+ * @returns True if the value is a host `Error` or `DOMException` named `AbortError`; false otherwise
1089
1192
  */
1090
1193
  function isAbortError(error) {
1091
1194
  return (error instanceof DOMException || error instanceof Error) && error.name === "AbortError";
1092
1195
  }
1093
1196
  /**
1094
- * Whether `url` is an INSECURE remote endpoint — a plain `http://` URL whose host is NOT a
1197
+ * Checks whether `url` is an INSECURE remote endpoint — a plain `http://` URL whose host is NOT a
1095
1198
  * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input.
1096
1199
  *
1097
1200
  * @remarks
@@ -1100,7 +1203,7 @@ function isAbortError(error) {
1100
1203
  * An `https://` URL (or any non-`http://` scheme) is never flagged.
1101
1204
  *
1102
1205
  * @param url - The candidate endpoint URL
1103
- * @returns `true` when `url` is a non-loopback `http://` endpoint
1206
+ * @returns True if `url` is a non-loopback `http://` endpoint; false otherwise
1104
1207
  *
1105
1208
  * @example
1106
1209
  * ```ts
@@ -1118,7 +1221,12 @@ function isInsecureRemote(url) {
1118
1221
  const hostname = host.startsWith("[") ? host.slice(0, host.indexOf("]") + 1) : host.split(":")[0] ?? "";
1119
1222
  return hostname !== "localhost" && hostname !== "127.0.0.1" && hostname !== "[::1]";
1120
1223
  }
1121
- /** Serialize a parked {@link PendingForm} into a {@link WireEvent}. */
1224
+ /**
1225
+ * Serializes a parked {@link PendingForm} into a {@link WireEvent}.
1226
+ *
1227
+ * @param form - The parked form's wire-safe record
1228
+ * @returns The `pending` frame — the JSON-stringified record as `data`, and the form's own `id`
1229
+ */
1122
1230
  function serializePending(form) {
1123
1231
  return {
1124
1232
  event: "pending",
@@ -1126,24 +1234,33 @@ function serializePending(form) {
1126
1234
  id: form.id
1127
1235
  };
1128
1236
  }
1129
- /** Serialize a parked prompt's expiry or release into a {@link WireEvent} — event `'expire'`, `data` the JSON-stringified `{ id }` payload. */
1237
+ /**
1238
+ * Serializes a parked prompt's expiry or release into a {@link WireEvent}.
1239
+ *
1240
+ * @param id - The id of the parked form that expired or was released
1241
+ * @returns The `expire` frame, carrying the JSON-stringified `{ id }` payload as `data`
1242
+ */
1130
1243
  function serializeExpire(id) {
1131
1244
  return {
1132
1245
  event: "expire",
1133
1246
  data: JSON.stringify({ id })
1134
1247
  };
1135
1248
  }
1136
- /** The {@link WireEvent} a broker/manager sends when it is going away — event `'shutdown'`, no payload. */
1137
- function serializeShutdown() {
1249
+ /**
1250
+ * Serializes the {@link WireEvent} a broker or manager sends when it is going away.
1251
+ *
1252
+ * @returns The `destroy` frame, whose `data` is empty because the signal carries no payload
1253
+ */
1254
+ function serializeDestroy() {
1138
1255
  return {
1139
- event: "shutdown",
1256
+ event: "destroy",
1140
1257
  data: ""
1141
1258
  };
1142
1259
  }
1143
1260
  //#endregion
1144
1261
  //#region src/core/Prompt.ts
1145
1262
  /**
1146
- * The headless form broker. It parks live forms, exposes their serialized schemas, and applies
1263
+ * Implements the headless form broker. It parks live forms, exposes their serialized schemas, and applies
1147
1264
  * remote answers to the authoritative form.
1148
1265
  *
1149
1266
  * @remarks
@@ -1334,7 +1451,7 @@ var Prompt = class {
1334
1451
  //#endregion
1335
1452
  //#region src/core/PromptClient.ts
1336
1453
  /**
1337
- * The SSE form bridge. It ingests serialized forms from a remote broker, renders them through a
1454
+ * Implements the SSE form bridge. It ingests serialized forms from a remote broker, renders them through a
1338
1455
  * local terminal, and posts answers back without blocking the event stream.
1339
1456
  *
1340
1457
  * @remarks
@@ -1343,12 +1460,12 @@ var Prompt = class {
1343
1460
  * {@link disconnect} deliberately stopped it.
1344
1461
  * - **Ingest + render.** Each `pending` envelope passes through `isPendingForm`, Form's `parseForm`,
1345
1462
  * and terminal's `sanitizeSchema`, then enters a serial render queue. The SSE reader never awaits
1346
- * that queue, so `expire` and `shutdown` remain live while a person is answering.
1463
+ * that queue, so `expire` and `destroy` remain live while a person is answering.
1347
1464
  * - **Safe local form.** The rendering copy omits every wire `pattern`, because Form compiles a
1348
1465
  * pattern during local evaluation. The broker's parked form retains it and remains authoritative.
1349
1466
  * - **Refusal retry.** A structured `rejected` response seeds a new rendering form with the values
1350
1467
  * just submitted, applies every {@link FieldError} through `invalidate`, and asks again. No retry
1351
- * counter truncates the loop; acceptance, expiry, and shutdown are its bounds.
1468
+ * counter truncates the loop; acceptance, expiry, and the broker's own teardown are its bounds.
1352
1469
  * - **Replay safety.** A replayed id is skipped while it is queued, rendering, or posting. Once an
1353
1470
  * attempt ends, a later delivery of that id may be rendered again.
1354
1471
  *
@@ -1486,7 +1603,7 @@ var PromptClient = class {
1486
1603
  if (isRecord(parsed) && isString(parsed.id)) this.#expire(parsed.id);
1487
1604
  return;
1488
1605
  }
1489
- if (event.event === SSE_EVENTS.shutdown) {
1606
+ if (event.event === SSE_EVENTS.destroy) {
1490
1607
  this.disconnect();
1491
1608
  this.#interrupt();
1492
1609
  }
@@ -1639,10 +1756,9 @@ var PromptClient = class {
1639
1756
  //#endregion
1640
1757
  //#region src/core/TerminalManager.ts
1641
1758
  /**
1642
- * The multi-endpoint terminal MANAGER — a registry of named {@link PromptInterface} brokers (one
1643
- * per endpoint), so several parties can `ask` forms of each other by NAME with a `from` → `to`
1644
- * attribution edge on every parked form, and a transitive DEADLOCK check across all in-flight
1645
- * asks.
1759
+ * Registers named {@link PromptInterface} brokers (one per endpoint), so several parties can
1760
+ * `ask` forms of each other by NAME with a `from` → `to` attribution edge on every parked form,
1761
+ * and a transitive DEADLOCK check across all in-flight asks.
1646
1762
  *
1647
1763
  * @remarks
1648
1764
  * - **Registry.** `add(name, options?)` mints (or, if `name` is already mounted, returns the
@@ -1657,7 +1773,7 @@ var PromptClient = class {
1657
1773
  * - **Durable open / save.** `open(name)` restores an EMPTY broker from the `store` (parked
1658
1774
  * Promises are process-bound and never resurrected); `save(name)` persists the endpoint's
1659
1775
  * configured `timeout`.
1660
- * - **Removal.** `remove` drops one endpoint, a batch (§9.2, array overload FIRST), or every
1776
+ * - **Removal.** `remove` drops one endpoint, a batch (the array overload declared FIRST), or every
1661
1777
  * endpoint when called without an argument. It destroys each broker, which expires every form
1662
1778
  * still parked on it. `destroy` is idempotent.
1663
1779
  *
@@ -1702,7 +1818,7 @@ var TerminalManager = class {
1702
1818
  return this.#terminals.get(name);
1703
1819
  }
1704
1820
  terminals() {
1705
- return [...this.#terminals.keys()];
1821
+ return [...this.#terminals.values()];
1706
1822
  }
1707
1823
  add(name, options) {
1708
1824
  if (this.#destroyed) throw new TerminalError("DESTROYED", "manager destroyed");
@@ -1734,7 +1850,7 @@ var TerminalManager = class {
1734
1850
  ask(from, to, form) {
1735
1851
  const broker = this.#terminals.get(to);
1736
1852
  if (broker === void 0) {
1737
- const known = this.terminals();
1853
+ const known = [...this.#terminals.keys()];
1738
1854
  return Promise.reject(new TerminalError("TARGET", `unknown terminal '${to}' (known: ${known.length > 0 ? known.join(", ") : "none"})`, {
1739
1855
  to,
1740
1856
  known
@@ -1772,7 +1888,7 @@ var TerminalManager = class {
1772
1888
  const broker = this.#terminals.get(to);
1773
1889
  if (broker === void 0) return {
1774
1890
  success: false,
1775
- error: { reason: "terminal" }
1891
+ error: { reason: "target" }
1776
1892
  };
1777
1893
  const result = broker.answer(id, values);
1778
1894
  if (result.success) this.#edges.delete(id);
@@ -1805,8 +1921,8 @@ var TerminalManager = class {
1805
1921
  return;
1806
1922
  }
1807
1923
  if (isArray(names)) {
1808
- let removed = false;
1809
- for (const name of names) if (this.#removeOne(name)) removed = true;
1924
+ let removed = true;
1925
+ for (const name of names) if (!this.#removeOne(name)) removed = false;
1810
1926
  return removed;
1811
1927
  }
1812
1928
  return this.#removeOne(names);
@@ -1881,16 +1997,16 @@ var TerminalManager = class {
1881
1997
  }
1882
1998
  };
1883
1999
  //#endregion
1884
- //#region src/core/MemoryTerminalStore.ts
2000
+ //#region src/core/stores/MemoryTerminalStore.ts
1885
2001
  /**
1886
- * The in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of
2002
+ * Implements the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of
1887
2003
  * {@link TerminalSnapshot}s keyed by endpoint id, the DEFAULT store
1888
- * {@link import('./factories.js').createMemoryTerminalStore} builds. The EXACT twin of
2004
+ * {@link import('../factories.js').createMemoryTerminalStore} builds. The EXACT twin of
1889
2005
  * {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore}.
1890
2006
  *
1891
2007
  * @remarks
1892
- * A plain `Map<string, TerminalSnapshot>` (AGENTS §21 — the snapshot is already pure, self-contained
1893
- * CONFIG-only JSON, so no encoding is needed for the memory tier). There is NO idle-TTL and NO
2008
+ * A plain `Map<string, TerminalSnapshot>` — the snapshot is already pure, self-contained CONFIG-only
2009
+ * JSON, so the memory tier needs no encoding. There is NO idle-TTL and NO
1894
2010
  * eviction: a persisted config lives until an explicit `delete`. A durable backend (JSON / SQLite /
1895
2011
  * IndexedDB) swaps in through the SAME interface without touching the manager — its
1896
2012
  * driver-pluggable twin is {@link import('./DatabaseTerminalStore.js').DatabaseTerminalStore} (the
@@ -1900,13 +2016,13 @@ var TerminalManager = class {
1900
2016
  * - **`set` inserts / replaces under the snapshot's OWN `id`** (no separate id param).
1901
2017
  * - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
1902
2018
  *
1903
- * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1904
- * bijection with {@link TerminalStoreInterface}). Hydration is a caller concern: `open` always
2019
+ * The public surface is EXACTLY `get` / `set` / `delete` — no extra members, so the class and
2020
+ * {@link TerminalStoreInterface} carry the same methods. Hydration is a caller concern: `open` always
1905
2021
  * restores an EMPTY broker — parked Promises are process-bound and never resurrected.
1906
2022
  *
1907
2023
  * @example
1908
2024
  * ```ts
1909
- * import { createMemoryTerminalStore } from '@src/core'
2025
+ * import { createMemoryTerminalStore } from '@orkestrel/terminal'
1910
2026
  *
1911
2027
  * const store = createMemoryTerminalStore()
1912
2028
  * await store.set({ id: 'shell', timeout: 5000 }) // persist a config
@@ -1916,22 +2032,40 @@ var TerminalManager = class {
1916
2032
  */
1917
2033
  var MemoryTerminalStore = class {
1918
2034
  #snapshots = /* @__PURE__ */ new Map();
2035
+ /**
2036
+ * Resolves the persisted snapshot for `id`.
2037
+ *
2038
+ * @param id - The endpoint name the snapshot is keyed by
2039
+ * @returns The stored snapshot, or `undefined` when none is held
2040
+ */
1919
2041
  get(id) {
1920
2042
  return Promise.resolve(this.#snapshots.get(id));
1921
2043
  }
2044
+ /**
2045
+ * Inserts or replaces under the snapshot's OWN `id` (no separate id param).
2046
+ *
2047
+ * @param snapshot - The config snapshot to persist, carrying its own `id`
2048
+ * @returns A promise that settles once the snapshot is held
2049
+ */
1922
2050
  set(snapshot) {
1923
2051
  this.#snapshots.set(snapshot.id, snapshot);
1924
2052
  return Promise.resolve();
1925
2053
  }
2054
+ /**
2055
+ * Drops a snapshot by id; an absent id is a no-op (no throw).
2056
+ *
2057
+ * @param id - The endpoint name to drop
2058
+ * @returns A promise that settles once the snapshot is gone
2059
+ */
1926
2060
  delete(id) {
1927
2061
  this.#snapshots.delete(id);
1928
2062
  return Promise.resolve();
1929
2063
  }
1930
2064
  };
1931
2065
  //#endregion
1932
- //#region src/core/DatabaseTerminalStore.ts
2066
+ //#region src/core/stores/DatabaseTerminalStore.ts
1933
2067
  /**
1934
- * A {@link TerminalStoreInterface} backed by one table of the `databases` layer — an endpoint's
2068
+ * Implements a {@link TerminalStoreInterface} backed by one table of the `databases` layer — an endpoint's
1935
2069
  * durable CONFIG state IS a row, so persistence reduces to keyed point-access (`get` / `set` /
1936
2070
  * `delete`) over a {@link TableInterface}, the driver-pluggable twin of the plain-`Map`
1937
2071
  * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}.
@@ -1941,7 +2075,7 @@ var MemoryTerminalStore = class {
1941
2075
  * JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
1942
2076
  * IndexedDB backend swaps in WITHOUT touching the manager — the same seam as
1943
2077
  * {@link import('./MemoryTerminalStore.js').MemoryTerminalStore}. The driver defaults to memory
1944
- * ({@link import('./factories.js').createDatabaseTerminalStore} passes `createMemoryDriver()`), so
2078
+ * ({@link import('../factories.js').createDatabaseTerminalStore} passes `createMemoryDriver()`), so
1945
2079
  * it ALSO works in memory out of the box; you opt into the durable plumbing by passing a JSON /
1946
2080
  * SQLite / IndexedDB driver.
1947
2081
  *
@@ -1953,18 +2087,19 @@ var MemoryTerminalStore = class {
1953
2087
  * - **`set(snapshot)` upserts under the snapshot's OWN `id`** (no separate id param) — it writes
1954
2088
  * the row `{ id: snapshot.id, snapshot }`.
1955
2089
  * - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
1956
- * a {@link TerminalSnapshot} ({@link import('./validators.js').isTerminalSnapshot} — the AGENTS §14
1957
- * boundary narrow for an untrusted storage read), or `undefined` if none is stored.
2090
+ * a {@link TerminalSnapshot} ({@link import('../validators.js').isTerminalSnapshot} — the total
2091
+ * guard that narrows an untrusted storage read), or `undefined` if none is stored.
1958
2092
  * - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
1959
2093
  *
1960
2094
  * There is NO idle-TTL / eviction — a persisted config lives until an explicit `delete`. The public
1961
- * surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method bijection with
1962
- * {@link TerminalStoreInterface}). Hydration stays a caller concern: `open` always restores an EMPTY
2095
+ * surface is EXACTLY `get` / `set` / `delete` — no extra members, so the class and
2096
+ * {@link TerminalStoreInterface} carry the same methods. Hydration stays a caller concern: `open` always restores an EMPTY
1963
2097
  * broker — parked Promises are process-bound and never resurrected.
1964
2098
  *
1965
2099
  * @example
1966
2100
  * ```ts
1967
- * import { createDatabaseTerminalStore, createMemoryDriver } from '@src/core'
2101
+ * import { createDatabaseTerminalStore } from '@orkestrel/terminal'
2102
+ * import { createMemoryDriver } from '@orkestrel/database'
1968
2103
  *
1969
2104
  * const store = createDatabaseTerminalStore(createMemoryDriver()) // a durable driver swaps in here
1970
2105
  * await store.set({ id: 'shell', timeout: 5000 }) // persist the config (one JSON column)
@@ -1975,7 +2110,7 @@ var MemoryTerminalStore = class {
1975
2110
  var DatabaseTerminalStore = class {
1976
2111
  #table;
1977
2112
  /**
1978
- * Wrap a table as a terminal store.
2113
+ * Wraps a table as a terminal store.
1979
2114
  *
1980
2115
  * @param table - The {@link TableInterface} holding the snapshots — its row is the
1981
2116
  * {@link TerminalSnapshotRow} `{ id; snapshot }` shape (the snapshot one opaque JSON column)
@@ -1983,20 +2118,37 @@ var DatabaseTerminalStore = class {
1983
2118
  constructor(table) {
1984
2119
  this.#table = table;
1985
2120
  }
1986
- /** Resolve the persisted snapshot for `id`, narrowing the opaque JSON column back to a `TerminalSnapshot`. */
2121
+ /**
2122
+ * Resolves the persisted snapshot for `id`, narrowing the opaque JSON column back to a
2123
+ * `TerminalSnapshot`.
2124
+ *
2125
+ * @param id - The endpoint name the row is keyed by
2126
+ * @returns The stored snapshot, or `undefined` when none is held or the column is off-shape
2127
+ */
1987
2128
  async get(id) {
1988
2129
  const row = await this.#table.get(id);
1989
2130
  if (row === void 0) return void 0;
1990
2131
  return isTerminalSnapshot(row.snapshot) ? row.snapshot : void 0;
1991
2132
  }
1992
- /** Insert or replace under the snapshot's OWN `id` (no separate id param) — the row is `{ id, snapshot }`. */
2133
+ /**
2134
+ * Inserts or replaces under the snapshot's OWN `id` (no separate id param) — the row is
2135
+ * `{ id, snapshot }`.
2136
+ *
2137
+ * @param snapshot - The config snapshot to persist, carrying its own `id`
2138
+ * @returns A promise that settles once the row is written
2139
+ */
1993
2140
  async set(snapshot) {
1994
2141
  await this.#table.set({
1995
2142
  id: snapshot.id,
1996
2143
  snapshot
1997
2144
  });
1998
2145
  }
1999
- /** Drop a snapshot by id; an absent id is a no-op (no throw). */
2146
+ /**
2147
+ * Drops a snapshot by id; an absent id is a no-op (no throw).
2148
+ *
2149
+ * @param id - The endpoint name to drop
2150
+ * @returns A promise that settles once the row is gone
2151
+ */
2000
2152
  async delete(id) {
2001
2153
  await this.#table.remove(id);
2002
2154
  }
@@ -2004,7 +2156,7 @@ var DatabaseTerminalStore = class {
2004
2156
  //#endregion
2005
2157
  //#region src/core/factories.ts
2006
2158
  /**
2007
- * Create the headless {@link PromptInterface} broker. It parks live forms and applies remote
2159
+ * Creates the headless {@link PromptInterface} broker. It parks live forms and applies remote
2008
2160
  * answers to the authoritative instances.
2009
2161
  *
2010
2162
  * @param options - See {@link PromptOptions}
@@ -2017,7 +2169,7 @@ var DatabaseTerminalStore = class {
2017
2169
  *
2018
2170
  * @example
2019
2171
  * ```ts
2020
- * import { createPrompt } from '@src/core'
2172
+ * import { createPrompt } from '@orkestrel/terminal'
2021
2173
  * import { createForm } from '@orkestrel/form'
2022
2174
  *
2023
2175
  * const prompt = createPrompt()
@@ -2030,7 +2182,7 @@ function createPrompt(options) {
2030
2182
  return new Prompt(options);
2031
2183
  }
2032
2184
  /**
2033
- * Create the SSE prompt {@link PromptClientInterface} BRIDGE — it connects to a remote broker's SSE
2185
+ * Creates the SSE prompt {@link PromptClientInterface} BRIDGE — it connects to a remote broker's SSE
2034
2186
  * endpoint, dispatches each received form to a local {@link import('./types.js').TerminalInterface},
2035
2187
  * and POSTs the answer back. Universal — `fetch` / SSE are web-standard.
2036
2188
  *
@@ -2042,11 +2194,11 @@ function createPrompt(options) {
2042
2194
  * ends; it reconnects with the `delay` backoff unless `reconnect` is `false` / the client was
2043
2195
  * `destroy`ed. Inject `options.fetch` (a scripted `fetch`) and `options.timer` to drive it
2044
2196
  * deterministically in tests — no real network.
2045
- * - **§14 wire narrowing.** Every decoded prompt is guard-narrowed before dispatch (never an `as`).
2197
+ * - **Wire narrowing.** Every decoded prompt is guard-narrowed before dispatch (never an `as`).
2046
2198
  *
2047
2199
  * @example
2048
2200
  * ```ts
2049
- * import { createPromptClient } from '@src/core'
2201
+ * import { createPromptClient } from '@orkestrel/terminal'
2050
2202
  *
2051
2203
  * const client = createPromptClient({ url: 'http://host/prompts', terminal })
2052
2204
  * await client.connect()
@@ -2056,7 +2208,7 @@ function createPromptClient(options) {
2056
2208
  return new PromptClient(options);
2057
2209
  }
2058
2210
  /**
2059
- * Create the multi-endpoint {@link TerminalManager} — a named registry of
2211
+ * Creates the multi-endpoint {@link TerminalManager} — a named registry of
2060
2212
  * {@link PromptInterface} brokers so several parties can `ask` prompts of each other by name,
2061
2213
  * with a transitive DEADLOCK check across every in-flight ask.
2062
2214
  *
@@ -2065,7 +2217,7 @@ function createPromptClient(options) {
2065
2217
  *
2066
2218
  * @example
2067
2219
  * ```ts
2068
- * import { createTerminalManager } from '@src/core'
2220
+ * import { createTerminalManager } from '@orkestrel/terminal'
2069
2221
  *
2070
2222
  * const manager = createTerminalManager()
2071
2223
  * manager.add('agent')
@@ -2075,14 +2227,14 @@ function createTerminalManager(options) {
2075
2227
  return new TerminalManager(options);
2076
2228
  }
2077
2229
  /**
2078
- * Create the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint
2230
+ * Creates the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint
2079
2231
  * config snapshots, the default store backing a {@link TerminalManagerInterface}'s `open` / `save`.
2080
2232
  *
2081
2233
  * @returns A {@link TerminalStoreInterface}
2082
2234
  *
2083
2235
  * @example
2084
2236
  * ```ts
2085
- * import { createMemoryTerminalStore } from '@src/core'
2237
+ * import { createMemoryTerminalStore } from '@orkestrel/terminal'
2086
2238
  *
2087
2239
  * const store = createMemoryTerminalStore()
2088
2240
  * ```
@@ -2091,7 +2243,7 @@ function createMemoryTerminalStore() {
2091
2243
  return new MemoryTerminalStore();
2092
2244
  }
2093
2245
  /**
2094
- * Create a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
2246
+ * Creates a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
2095
2247
  * driver-pluggable twin of {@link createMemoryTerminalStore}, storing each endpoint's config
2096
2248
  * snapshot as one opaque JSON column.
2097
2249
  *
@@ -2100,7 +2252,7 @@ function createMemoryTerminalStore() {
2100
2252
  *
2101
2253
  * @example
2102
2254
  * ```ts
2103
- * import { createDatabaseTerminalStore } from '@src/core'
2255
+ * import { createDatabaseTerminalStore } from '@orkestrel/terminal'
2104
2256
  *
2105
2257
  * const store = createDatabaseTerminalStore() // in-memory by default
2106
2258
  * ```
@@ -2116,6 +2268,6 @@ function createDatabaseTerminalStore(driver = createMemoryDriver()) {
2116
2268
  }).table("terminals"));
2117
2269
  }
2118
2270
  //#endregion
2119
- export { ACCEPT_EVENT_STREAM, BACKSPACE, CONTROL_NAMES, CTRL_A, CTRL_C, CTRL_D, CTRL_E, CTRL_U, DEFAULT_MASK, DEFAULT_PROMPT_THEME, DEFAULT_PROMPT_TIMEOUT_MS, DEFAULT_RECONNECT_DELAY_MS, DELETE, DatabaseTerminalStore, ESCAPE, HEADER_TOKEN, KEY_CSI, KEY_SS3, MemoryTerminalStore, NEWLINE, PROMPT_ICONS, PROMPT_ROLES, Prompt, PromptClient, RETURN, SEQUENCE_NAMES, SPACE, SSE_BUFFER_LIMIT, SSE_EVENTS, TAB, TerminalError, TerminalManager, checkboxReduce, checkboxView, confirmReduce, confirmView, createCheckboxState, createConfirmState, createDatabaseTerminalStore, createEditorState, createInputState, createMemoryTerminalStore, createPasswordState, createPrompt, createPromptClient, createPromptTheme, createSelectState, createTerminalManager, defaultTimer, editLine, editorReduce, editorView, errorLine, globalFetch, hintedHeader, inputReduce, inputView, isAbortError, isInsecureRemote, isPendingForm, isPendingFormStatus, isPrintable, isTerminalError, isTerminalSnapshot, isWireEvent, parseKey, passwordReduce, passwordView, promptHeader, sanitizeDisplayText, sanitizeSchema, sanitizeThemeIcons, selectReduce, selectView, serializeExpire, serializePending, serializeShutdown, submitHeader, toggleIndex };
2271
+ export { ACCEPT_EVENT_STREAM, BACKSPACE, CONTROL_NAMES, CTRL_A, CTRL_C, CTRL_D, CTRL_E, CTRL_U, DEFAULT_MASK, DEFAULT_PROMPT_THEME, DEFAULT_PROMPT_TIMEOUT_MS, DEFAULT_RECONNECT_DELAY_MS, DELETE, DatabaseTerminalStore, HEADER_TOKEN, KEY_SS3, MemoryTerminalStore, NEWLINE, PROMPT_ICONS, PROMPT_ROLES, Prompt, PromptClient, RETURN, SEQUENCE_NAMES, SPACE, SSE_BUFFER_LIMIT, SSE_EVENTS, TAB, TerminalError, TerminalManager, createCheckboxState, createConfirmState, createDatabaseTerminalStore, createEditorState, createInputState, createMemoryTerminalStore, createPasswordState, createPrompt, createPromptClient, createPromptTheme, createSelectState, createTerminalManager, defaultTimer, editLine, globalFetch, isAbortError, isInsecureRemote, isPendingForm, isPendingFormStatus, isPrintable, isTerminalError, isTerminalSnapshot, isWireEvent, parseKey, reduceCheckbox, reduceConfirm, reduceEditor, reduceInput, reducePassword, reduceSelect, renderCheckboxView, renderConfirmView, renderEditorView, renderErrorLine, renderHintedHeader, renderInputView, renderPasswordView, renderPromptHeader, renderSelectView, renderSubmitHeader, sanitizeDisplayText, sanitizeSchema, sanitizeThemeIcons, serializeDestroy, serializeExpire, serializePending, toggleIndex };
2120
2272
 
2121
2273
  //# sourceMappingURL=index.js.map