@orkestrel/terminal 0.0.6 → 0.0.8

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,6 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _src_core = require("../core/index.cjs");
3
- let _orkestrel_console = require("@orkestrel/console");
4
3
  let node_readline = require("node:readline");
5
4
  let node_process = require("node:process");
6
5
  //#region src/server/constants.ts
@@ -183,7 +182,7 @@ var Terminal = class {
183
182
  input(options) {
184
183
  const state = (0, _src_core.createInputState)(options);
185
184
  if (rawCapable(this.#input)) return this.#drive(state, _src_core.inputReduce);
186
- return this.#lineFallback(options.message, options.styler, (answer) => {
185
+ return this.#lineFallback(options.message, state.styler, state.theme, void 0, (answer) => {
187
186
  const value = answer.length > 0 ? answer : options.default ?? "";
188
187
  return (0, _src_core.resolveValidation)(options.validate)(value) === true ? { value } : void 0;
189
188
  }, (answer) => answer.length > 0 ? answer : options.default ?? "");
@@ -191,12 +190,12 @@ var Terminal = class {
191
190
  password(options) {
192
191
  const state = (0, _src_core.createPasswordState)(options);
193
192
  if (rawCapable(this.#input)) return this.#drive(state, _src_core.passwordReduce);
194
- return this.#lineFallback(options.message, options.styler, (answer) => (0, _src_core.resolveValidation)(options.validate)(answer) === true ? { value: answer } : void 0, (answer) => answer, true);
193
+ return this.#lineFallback(options.message, state.styler, state.theme, void 0, (answer) => (0, _src_core.resolveValidation)(options.validate)(answer) === true ? { value: answer } : void 0, (answer) => answer, true);
195
194
  }
196
195
  confirm(options) {
197
196
  const state = (0, _src_core.createConfirmState)(options);
198
197
  if (rawCapable(this.#input)) return this.#drive(state, _src_core.confirmReduce);
199
- return this.#lineFallback(options.message, options.styler, (answer) => {
198
+ return this.#lineFallback(options.message, state.styler, state.theme, state.hint, (answer) => {
200
199
  const normalized = answer.trim().toLowerCase();
201
200
  if (normalized.length === 0) return { value: options.default ?? false };
202
201
  if (normalized === "y" || normalized === "yes") return { value: true };
@@ -206,7 +205,7 @@ var Terminal = class {
206
205
  select(options) {
207
206
  const state = (0, _src_core.createSelectState)(options);
208
207
  if (rawCapable(this.#input)) return this.#drive(state, _src_core.selectReduce);
209
- return this.#listFallback(options.message, options.styler, state.choices, FALLBACK_SELECT_HINT, (line) => {
208
+ return this.#listFallback(options.message, state.styler, state.theme, state.choices, state.hint ?? "Enter a number", (line) => {
210
209
  const index = Number.parseInt(line.trim(), 10) - 1;
211
210
  const choice = state.choices[index];
212
211
  return choice === void 0 ? void 0 : { value: choice.value };
@@ -215,7 +214,7 @@ var Terminal = class {
215
214
  checkbox(options) {
216
215
  const state = (0, _src_core.createCheckboxState)(options);
217
216
  if (rawCapable(this.#input)) return this.#drive(state, _src_core.checkboxReduce);
218
- return this.#listFallback(options.message, options.styler, state.choices, FALLBACK_CHECKBOX_HINT, (line) => {
217
+ return this.#listFallback(options.message, state.styler, state.theme, state.choices, state.hint ?? "Enter numbers separated by commas", (line) => {
219
218
  const indices = line.split(",").map((part) => Number.parseInt(part.trim(), 10) - 1).filter((index) => index >= 0 && index < state.choices.length);
220
219
  if (options.min !== void 0 && indices.length < options.min) return void 0;
221
220
  if (options.max !== void 0 && indices.length > options.max) return void 0;
@@ -225,7 +224,7 @@ var Terminal = class {
225
224
  editor(options) {
226
225
  const state = (0, _src_core.createEditorState)(options);
227
226
  if (rawCapable(this.#input)) return this.#drive(state, _src_core.editorReduce);
228
- return this.#editorFallback(options);
227
+ return this.#editorFallback(state);
229
228
  }
230
229
  /**
231
230
  * The irreducible Node raw-mode primitive — the ONLY place raw mode is touched. Switches the input
@@ -318,10 +317,9 @@ var Terminal = class {
318
317
  * would SPIN. On EOF the loop accepts the final line through `take` if it passes, else returns the
319
318
  * caller's `eof` fallback (the default / empty value) — the prompt always settles, never spins.
320
319
  */
321
- async #lineFallback(message, styler, take, eof, masked = false) {
322
- const paint = styler ?? (0, _orkestrel_console.createStyler)();
320
+ async #lineFallback(message, styler, theme, hint, take, eof, masked = false) {
323
321
  for (;;) {
324
- const { answer, ended } = await this.#question(`${paint.cyan("?")} ${paint.bold(message)} `, masked);
322
+ const { answer, ended } = await this.#question(`${(0, _src_core.hintedHeader)(styler, theme, message, hint)} `, masked);
325
323
  const accepted = take(answer);
326
324
  if (accepted !== void 0) return accepted.value;
327
325
  if (ended) return eof(answer);
@@ -330,17 +328,17 @@ var Terminal = class {
330
328
  /**
331
329
  * Print a numbered choice list, then read one readline line and accept it through `take` — the
332
330
  * non-TTY fallback for `select` / `checkbox`. The list is rendered once; the user types the
333
- * number(s); `take` parses + gates the line, re-prompting until it returns a value.
331
+ * number(s); `take` parses + gates the line, re-prompting until it returns a value. Each index is
332
+ * painted by the `muted` role and each choice name by the `content` role.
334
333
  */
335
- async #listFallback(message, styler, choices, hint, take, eof) {
336
- const paint = styler ?? (0, _orkestrel_console.createStyler)();
337
- let list = `${paint.cyan("?")} ${paint.bold(message)}\n`;
334
+ async #listFallback(message, styler, theme, choices, hint, take, eof) {
335
+ let list = `${(0, _src_core.promptHeader)(styler, theme, message)}\n`;
338
336
  choices.forEach((choice, index) => {
339
- list += ` ${paint.dim(`${String(index + 1)})`)} ${choice.name}\n`;
337
+ list += ` ${styler.render(theme.roles.muted, `${String(index + 1)})`)} ${styler.render(theme.roles.content, choice.name)}\n`;
340
338
  });
341
339
  this.#output.write(list);
342
340
  for (;;) {
343
- const { answer, ended } = await this.#question(`${paint.dim(`${hint}:`)} `);
341
+ const { answer, ended } = await this.#question(`${styler.render(theme.roles.hint, `${hint}:`)} `);
344
342
  const accepted = take(answer);
345
343
  if (accepted !== void 0) return accepted.value;
346
344
  if (ended) return eof;
@@ -358,11 +356,10 @@ var Terminal = class {
358
356
  * on the consumed stream — the EOF analogue of the raw-mode editor's re-edit, which a piped stream
359
357
  * cannot offer.
360
358
  */
361
- async #editorFallback(options) {
362
- const paint = options.styler ?? (0, _orkestrel_console.createStyler)();
363
- this.#output.write(`${paint.cyan("?")} ${paint.bold(options.message)} ${paint.dim("(EOF to finish)")}\n`);
359
+ async #editorFallback(state) {
360
+ this.#output.write(`${(0, _src_core.hintedHeader)(state.styler, state.theme, state.message, state.hint ?? "(EOF to finish)")}\n`);
364
361
  const text = await this.#lines();
365
- return text.length > 0 ? text : options.default ?? "";
362
+ return text.length > 0 ? text : state.default;
366
363
  }
367
364
  /**
368
365
  * Ask one readline question on the resolved streams and resolve the typed (un-trimmed) line plus
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["#input","#output","#handlers","#drive","#lineFallback","#listFallback","#editorFallback","#createHandler","#enterRaw","#render","#leaveRaw","#question","#lines","#readline","#createQuestionSettler"],"sources":["../../../src/server/constants.ts","../../../src/server/helpers.ts","../../../src/server/Terminal.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-terminals constants (the T-c branch) — the cursor / line-clear ANSI control sequences the\n// interactive `Terminal` driver writes to redraw a prompt view IN PLACE, plus the readline-fallback\n// defaults. UPPER_SNAKE, every member exported (AGENTS §5). Sequences are built from a named ESC\n// byte via `String.fromCharCode` so no raw control character appears in source (the core\n// terminals / console-module idiom).\n\n/** The Escape byte (ESC, U+001B) — the lead byte of every CSI cursor-control sequence below. */\nexport const ESCAPE = String.fromCharCode(27)\n\n/** The Control Sequence Introducer (`ESC[`) — the prefix of every cursor / erase sequence. */\nexport const CSI = `${ESCAPE}[`\n\n/**\n * The cursor-UP sequence TEMPLATE (`ESC[{count}A`) — {@link import('./helpers.js').moveUp}\n * interpolates `{count}` with the number of lines to climb (the `{count}` placeholder idiom the core\n * terminals' `RULE_MESSAGES` uses). Kept as a template so the count stays out of the constant.\n */\nexport const CSI_UP = `${CSI}{count}A`\n\n/**\n * Hide the cursor (`ESC[?25l`) — written before the driver starts redrawing a prompt so the cursor\n * does not flicker across the view during an in-place re-render; paired with {@link CURSOR_SHOW}.\n */\nexport const CURSOR_HIDE = `${CSI}?25l`\n\n/** Show the cursor (`ESC[?25h`) — restores the cursor after a prompt resolves / cancels (the {@link CURSOR_HIDE} pair). */\nexport const CURSOR_SHOW = `${CSI}?25h`\n\n/**\n * Erase from the cursor down to the end of the screen (`ESC[J`) — wipes the WHOLE previous (possibly\n * multi-line `select` / `checkbox`) view in one write before the new view is rendered, so a redraw\n * never leaves orphaned rows below.\n */\nexport const CLEAR_DOWN = `${CSI}J`\n\n/** A carriage return (`\\r`, U+000D) — returns the cursor to column 0 so a redraw starts at the line's left edge. */\nexport const CARRIAGE_RETURN = String.fromCharCode(13)\n\n/** A line feed (`\\n`, U+000A) — the line terminator the driver writes after the final committed prompt view. */\nexport const LINE_FEED = String.fromCharCode(10)\n\n/**\n * The numbered-list prompt the non-TTY {@link import('./Terminal.js').Terminal} `select` / `checkbox`\n * fallback appends — a piped (non-terminal) stream cannot navigate with arrow keys, so the choices are\n * printed numbered and the user types the number(s) on a single readline line.\n */\nexport const FALLBACK_SELECT_HINT = 'Enter a number'\n\n/** The comma-separated multi-select hint the non-TTY `checkbox` fallback shows (the user types one or more numbers). */\nexport const FALLBACK_CHECKBOX_HINT = 'Enter numbers separated by commas'\n","// Pure helpers for the T-c server-terminals branch (AGENTS §5 — every function here is exported and\n// unit-tested). Total utilities: the two stream-boundary guards (narrow `process.stdin` /\n// `process.stdout` / any injected stream without `as`, §14), the raw-mode capability probe, and the\n// pure cursor-math the interactive `Terminal` driver uses to redraw a prompt view IN PLACE (count a\n// view's lines, build the cursor-up sequence, and assemble the whole reposition-and-clear prefix) —\n// so the impure driver only feeds bytes into the reducers and writes the strings these helpers build.\n\nimport type { InputStreamInterface, OutputStreamInterface } from './types.js'\nimport { CARRIAGE_RETURN, CLEAR_DOWN, CSI_UP } from './constants.js'\n\n/**\n * Whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` / `off`\n * `'data'` subscription methods. A total type guard (AGENTS §14): it NEVER throws and returns `false`\n * for anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`,\n * or a fake TTY a test injects) to the exact slice the driver reads — no `as`.\n *\n * @remarks\n * Only `on` / `off` are required (the irreducible event seam); `setRawMode` / `resume` / `pause` /\n * `isTTY` are optional on {@link InputStreamInterface}, so their absence does not disqualify a stream\n * — a piped, non-TTY stream is still a valid input, just one the driver reads through the readline\n * fallback rather than raw mode.\n *\n * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)\n * @returns `true` when `value` has callable `on` and `off`\n */\nexport function isInputStream(value: unknown): value is InputStreamInterface {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'on' in value &&\n\t\ttypeof value.on === 'function' &&\n\t\t'off' in value &&\n\t\ttypeof value.off === 'function'\n\t)\n}\n\n/**\n * Whether `value` is a usable {@link OutputStreamInterface} — a record with a callable `write`. A\n * total type guard (AGENTS §14): it NEVER throws and returns `false` for anything off-shape, so it\n * narrows the output boundary (the real `process.stdout`, or a recording fake a test injects) to the\n * one method the driver writes through — no `as`.\n *\n * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)\n * @returns `true` when `value` has a callable `write`\n */\nexport function isOutputStream(value: unknown): value is OutputStreamInterface {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'write' in value &&\n\t\ttypeof value.write === 'function'\n\t)\n}\n\n/**\n * Whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard (AGENTS §14)\n * checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface` requires\n * as its `input`. The non-TTY fallback narrows the resolved input stream through this before handing\n * it to readline (never an `as`), so a real piped `process.stdin` (or a `PassThrough` a test injects)\n * crosses into the readline boundary honestly. Never throws; returns `false` for a minimal fake that\n * isn't a full readable.\n *\n * @param value - The resolved input stream (or any value crossing the boundary)\n * @returns `true` when `value` has the readable methods readline needs\n */\nexport function isReadable(value: unknown): value is NodeJS.ReadableStream {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'read' in value &&\n\t\ttypeof value.read === 'function' &&\n\t\t'pipe' in value &&\n\t\ttypeof value.pipe === 'function'\n\t)\n}\n\n/**\n * Whether `value` is a Node {@link NodeJS.WritableStream} — a total structural guard (AGENTS §14)\n * checking for the callable `write` / `end` that `node:readline`'s `createInterface` accepts as its\n * `output`. Paired with {@link isReadable} so the non-TTY fallback narrows BOTH streams to the\n * readline boundary without an `as`. Never throws.\n *\n * @param value - The resolved output stream (or any value crossing the boundary)\n * @returns `true` when `value` has the writable methods readline needs\n */\nexport function isWritable(value: unknown): value is NodeJS.WritableStream {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'write' in value &&\n\t\ttypeof value.write === 'function' &&\n\t\t'end' in value &&\n\t\ttypeof value.end === 'function'\n\t)\n}\n\n/**\n * Whether an input stream can be driven in RAW mode — it both reports `isTTY === true` AND exposes a\n * callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to choose its path:\n * `true` ⇒ the interactive raw-mode prompts (arrow-key navigation, live re-render); `false` ⇒ the\n * `node:readline` line-input fallback (a piped / non-terminal stream cannot enter raw mode). Total —\n * never throws.\n *\n * @param input - The resolved {@link InputStreamInterface}\n * @returns `true` when the stream is a TTY with `setRawMode`\n */\nexport function rawCapable(input: InputStreamInterface): boolean {\n\treturn input.isTTY === true && typeof input.setRawMode === 'function'\n}\n\n/**\n * The number of terminal LINES a rendered prompt `view` occupies — one more than its newline count\n * (a view with no newline is a single line; N newlines span N+1 lines). The basis of the in-place\n * re-render: the driver records the line count of the view it just wrote so the next redraw knows how\n * far up to move the cursor before overwriting. Total; an empty string is one (empty) line.\n *\n * @param view - The rendered (possibly multi-line, possibly ANSI-styled) view string\n * @returns The number of lines the view spans (always at least 1)\n */\nexport function lineCount(view: string): number {\n\tlet lines = 1\n\tfor (const character of view) {\n\t\tif (character === '\\n') lines += 1\n\t}\n\treturn lines\n}\n\n/**\n * The cursor-UP control sequence that moves the cursor up `count` lines (`ESC[{count}A`) — or the\n * empty string when `count` is zero or negative (no movement needed, and `ESC[0A` is a wasted write).\n * The pure step the in-place re-render uses to climb back over the previous view before clearing it.\n * Total.\n *\n * @param count - How many lines to move the cursor up\n * @returns The `ESC[{count}A` sequence, or `''` when `count <= 0`\n */\nexport function moveUp(count: number): string {\n\tif (count <= 0) return ''\n\treturn `${CSI_UP.replace('{count}', String(count))}`\n}\n\n/**\n * The full reposition-and-clear prefix to write BEFORE re-rendering a prompt view in place — given\n * the line count of the PREVIOUS view, it moves the cursor up over those lines, returns it to column\n * 0, and erases everything from there to the end of the screen, so the next view is drawn on a clean\n * region (no orphaned rows from a taller previous view). Pure; the driver writes this immediately\n * followed by the new view.\n *\n * @remarks\n * For the FIRST render `previousLines` is `1` (the cursor sits on the line the prompt opened on) so\n * the prefix is just a carriage return + clear-down — the prompt draws from the current line. For a\n * subsequent render it climbs `previousLines - 1` lines (the cursor is on the LAST line of the prior\n * view) before clearing. Keeping the math here (not in the driver) makes the re-render unit-testable\n * without a real terminal.\n *\n * @param previousLines - The line count of the view currently on screen (from {@link lineCount})\n * @returns The control-sequence prefix to write before the new view\n */\nexport function redrawPrefix(previousLines: number): string {\n\treturn `${moveUp(previousLines - 1)}${CARRIAGE_RETURN}${CLEAR_DOWN}`\n}\n","import type {\n\tCheckboxOptions,\n\tConfirmOptions,\n\tEditorOptions,\n\tInputOptions,\n\tKeyEvent,\n\tPasswordOptions,\n\tPromptStep,\n\tSelectOptions,\n} from '@src/core'\nimport type { StylerInterface } from '@orkestrel/console'\nimport type {\n\tInputStreamInterface,\n\tOutputStreamInterface,\n\tTerminalInterface,\n\tTerminalOptions,\n} from './types.js'\nimport {\n\tcreateCheckboxState,\n\tcreateConfirmState,\n\tcreateEditorState,\n\tcreateInputState,\n\tcreatePasswordState,\n\tcreateSelectState,\n\tcheckboxReduce,\n\tconfirmReduce,\n\teditorReduce,\n\tinputReduce,\n\tparseKey,\n\tpasswordReduce,\n\tresolveValidation,\n\tselectReduce,\n\tTerminalError,\n} from '@src/core'\nimport { createStyler } from '@orkestrel/console'\nimport { createInterface } from 'node:readline'\nimport { stdin, stdout } from 'node:process'\nimport {\n\tCURSOR_HIDE,\n\tCURSOR_SHOW,\n\tFALLBACK_CHECKBOX_HINT,\n\tFALLBACK_SELECT_HINT,\n\tLINE_FEED,\n} from './constants.js'\nimport {\n\tisInputStream,\n\tisOutputStream,\n\tisReadable,\n\tisWritable,\n\tlineCount,\n\trawCapable,\n\tredrawPrefix,\n} from './helpers.js'\n\n/**\n * The interactive terminal prompt driver (T-c) — the third {@link TerminalInterface} surface (beside\n * the core headless `Prompt` broker and the SSE `PromptClient` bridge), and the ONLY impure part of\n * the prompt stack. It reads the TTY and DRIVES the pure core `*Reduce` reducers: it feeds raw-mode\n * stdin bytes through `parseKey` into the matching reducer, renders the returned `view` in place, and\n * resolves on a `submit` step / rejects with a {@link TerminalError} (`CANCEL`) on ctrl-c. It owns no\n * prompt logic of its own — state, view, validation, and the cancel signal all come from the pure\n * core; this class owns ONLY the cursor + raw-mode + in-place re-render mechanics.\n *\n * @remarks\n * See {@link TerminalInterface} for the behavioral contract (raw-mode leak-freedom, the in-place\n * re-render, the non-TTY readline fallback, event-free).\n */\nexport class Terminal implements TerminalInterface {\n\treadonly #input: InputStreamInterface\n\treadonly #output: OutputStreamInterface\n\treadonly #handlers = new Map<object, (chunk: string | Uint8Array) => void>()\n\n\tconstructor(options?: TerminalOptions) {\n\t\t// Resolve each stream through its guard (§14): a present, well-shaped injected stream is used as\n\t\t// is; otherwise the real process stream — no `as`, and an `undefined` option falls through.\n\t\tthis.#input = isInputStream(options?.input) ? options.input : stdin\n\t\tthis.#output = isOutputStream(options?.output) ? options.output : stdout\n\t}\n\n\t// === Prompt forms (PromptFormInterface)\n\n\tinput(options: InputOptions): Promise<string> {\n\t\tconst state = createInputState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, inputReduce)\n\t\treturn this.#lineFallback(\n\t\t\toptions.message,\n\t\t\toptions.styler,\n\t\t\t(answer) => {\n\t\t\t\tconst value = answer.length > 0 ? answer : (options.default ?? '')\n\t\t\t\treturn resolveValidation(options.validate)(value) === true ? { value } : undefined\n\t\t\t},\n\t\t\t// EOF: settle the entered line or the default — a piped stream can't be re-prompted.\n\t\t\t(answer) => (answer.length > 0 ? answer : (options.default ?? '')),\n\t\t)\n\t}\n\n\tpassword(options: PasswordOptions): Promise<string> {\n\t\tconst state = createPasswordState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, passwordReduce)\n\t\treturn this.#lineFallback(\n\t\t\toptions.message,\n\t\t\toptions.styler,\n\t\t\t(answer) =>\n\t\t\t\tresolveValidation(options.validate)(answer) === true ? { value: answer } : undefined,\n\t\t\t// EOF: settle the entered secret (or '') — a piped stream can't be re-prompted.\n\t\t\t(answer) => answer,\n\t\t\t// A degraded TTY (isTTY but no setRawMode) must never echo the secret being typed.\n\t\t\ttrue,\n\t\t)\n\t}\n\n\tconfirm(options: ConfirmOptions): Promise<boolean> {\n\t\tconst state = createConfirmState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, confirmReduce)\n\t\treturn this.#lineFallback(\n\t\t\toptions.message,\n\t\t\toptions.styler,\n\t\t\t(answer) => {\n\t\t\t\tconst normalized = answer.trim().toLowerCase()\n\t\t\t\tif (normalized.length === 0) return { value: options.default ?? false }\n\t\t\t\tif (normalized === 'y' || normalized === 'yes') return { value: true }\n\t\t\t\tif (normalized === 'n' || normalized === 'no') return { value: false }\n\t\t\t\treturn undefined\n\t\t\t},\n\t\t\t// EOF: settle the default — a piped stream can't be re-prompted for a y/n.\n\t\t\t() => options.default ?? false,\n\t\t)\n\t}\n\n\tselect(options: SelectOptions): Promise<string> {\n\t\tconst state = createSelectState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, selectReduce)\n\t\treturn this.#listFallback(\n\t\t\toptions.message,\n\t\t\toptions.styler,\n\t\t\tstate.choices,\n\t\t\tFALLBACK_SELECT_HINT,\n\t\t\t(line) => {\n\t\t\t\tconst index = Number.parseInt(line.trim(), 10) - 1\n\t\t\t\tconst choice = state.choices[index]\n\t\t\t\treturn choice === undefined ? undefined : { value: choice.value }\n\t\t\t},\n\t\t\t// EOF: no choice was picked — settle the empty string (a piped stream can't be re-prompted).\n\t\t\t'',\n\t\t)\n\t}\n\n\tcheckbox(options: CheckboxOptions): Promise<readonly string[]> {\n\t\tconst state = createCheckboxState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, checkboxReduce)\n\t\treturn this.#listFallback(\n\t\t\toptions.message,\n\t\t\toptions.styler,\n\t\t\tstate.choices,\n\t\t\tFALLBACK_CHECKBOX_HINT,\n\t\t\t(line) => {\n\t\t\t\tconst indices = line\n\t\t\t\t\t.split(',')\n\t\t\t\t\t.map((part) => Number.parseInt(part.trim(), 10) - 1)\n\t\t\t\t\t.filter((index) => index >= 0 && index < state.choices.length)\n\t\t\t\tif (options.min !== undefined && indices.length < options.min) return undefined\n\t\t\t\tif (options.max !== undefined && indices.length > options.max) return undefined\n\t\t\t\tconst values = indices\n\t\t\t\t\t.map((index) => state.choices[index]?.value)\n\t\t\t\t\t.filter((value): value is string => value !== undefined)\n\t\t\t\treturn { value: values }\n\t\t\t},\n\t\t\t// EOF: nothing selected — settle the empty list (a piped stream can't be re-prompted).\n\t\t\t[],\n\t\t)\n\t}\n\n\teditor(options: EditorOptions): Promise<string> {\n\t\tconst state = createEditorState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, editorReduce)\n\t\treturn this.#editorFallback(options)\n\t}\n\n\t// === The raw-mode kernel\n\n\t/**\n\t * The irreducible Node raw-mode primitive — the ONLY place raw mode is touched. Switches the input\n\t * into raw mode (each keypress delivered immediately, no echo), resumes its flow, and subscribes\n\t * `handler` to `'data'`. The paired {@link #leaveRaw} operation unsubscribes the exact handler,\n\t * leaves raw mode, and pauses the stream on submit, cancel, or throw.\n\t */\n\t#enterRaw(token: object, handler: (chunk: string | Uint8Array) => void): void {\n\t\tthis.#handlers.set(token, handler)\n\t\tthis.#input.setRawMode?.(true)\n\t\tthis.#input.resume?.()\n\t\tthis.#input.on('data', handler)\n\t}\n\n\t#leaveRaw(token: object): void {\n\t\tconst handler = this.#handlers.get(token)\n\t\tif (handler === undefined) return\n\t\tthis.#handlers.delete(token)\n\t\tthis.#input.off('data', handler)\n\t\tthis.#input.setRawMode?.(false)\n\t\tthis.#input.pause?.()\n\t}\n\n\t/**\n\t * Drive ONE interactive prompt over raw-mode stdin — the generic engine all six TTY prompts share.\n\t * Renders the reducer's initial `view`, enters raw mode once, and on each keypress runs `parseKey`\n\t * → `reduce` → an in-place re-render; on `submit` it cleans up + resolves the reducer's `value`, on\n\t * `cancel` (ctrl-c) it cleans up + rejects a {@link TerminalError} (`CANCEL`). Raw mode is entered\n\t * exactly once and cleaned up on every exit path (submit / cancel / a throw inside a step), so no\n\t * raw mode and no `'data'` listener ever leak. The cursor is hidden for the duration and restored\n\t * on exit.\n\t */\n\t#drive<T, S>(initial: S, reduce: (state: S, key: KeyEvent) => PromptStep<T, S>): Promise<T> {\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst state = initial\n\t\t\t// Render the first view, tracking how many lines it spans so the next redraw climbs over them.\n\t\t\tthis.#output.write(CURSOR_HIDE)\n\t\t\tlet firstView: string\n\t\t\ttry {\n\t\t\t\tfirstView = reduce(state, parseKey('')).view\n\t\t\t\tthis.#output.write(firstView)\n\t\t\t} catch (error) {\n\t\t\t\t// Raw mode was never entered yet, but the cursor was hidden — restore it before rejecting.\n\t\t\t\tthis.#output.write(`${CURSOR_SHOW}${LINE_FEED}`)\n\t\t\t\treject(error)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst token = {}\n\t\t\tconst handler = this.#createHandler(\n\t\t\t\ttoken,\n\t\t\t\tstate,\n\t\t\t\tlineCount(firstView),\n\t\t\t\treduce,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t)\n\t\t\tthis.#enterRaw(token, handler)\n\t\t})\n\t}\n\n\t#createHandler<T, S>(\n\t\ttoken: object,\n\t\tinitial: S,\n\t\tinitialLines: number,\n\t\treduce: (state: S, key: KeyEvent) => PromptStep<T, S>,\n\t\tresolve: (value: T | PromiseLike<T>) => void,\n\t\treject: (reason?: unknown) => void,\n\t): (chunk: string | Uint8Array) => void {\n\t\tlet state = initial\n\t\tlet lines = initialLines\n\t\treturn (chunk) => {\n\t\t\ttry {\n\t\t\t\tconst step = reduce(state, parseKey(chunk))\n\t\t\t\tstate = step.state\n\t\t\t\tif (step.status === 'active') {\n\t\t\t\t\tthis.#render(step.view, lines)\n\t\t\t\t\tlines = lineCount(step.view)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Terminal step (submit / cancel): paint the final committed view, then tear down.\n\t\t\t\tthis.#render(step.view, lines)\n\t\t\t\tthis.#leaveRaw(token)\n\t\t\t\tthis.#output.write(`${CURSOR_SHOW}${LINE_FEED}`)\n\t\t\t\tif (step.status === 'cancel') reject(new TerminalError('CANCEL', 'Prompt cancelled'))\n\t\t\t\telse if (step.status === 'submit' && step.value !== undefined) resolve(step.value)\n\t\t\t\telse reject(new TerminalError('DRIVER', 'submit produced no value'))\n\t\t\t} catch (error) {\n\t\t\t\t// A throw inside the reducer/validator/styler: tear down raw mode + the listener,\n\t\t\t\t// restore the cursor exactly as the normal exit path does, then reject.\n\t\t\t\tthis.#leaveRaw(token)\n\t\t\t\tthis.#output.write(`${CURSOR_SHOW}${LINE_FEED}`)\n\t\t\t\treject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Redraw a prompt view in place — climb over the previous view's `previousLines`, clear, and write the new view (the pure cursor-math is {@link redrawPrefix}). */\n\t#render(view: string, previousLines: number): void {\n\t\tthis.#output.write(`${redrawPrefix(previousLines)}${view}`)\n\t}\n\n\t// === Non-TTY fallbacks (node:readline line input)\n\n\t/**\n\t * Read a single line via `node:readline` and accept it through `take` — the non-TTY line-input\n\t * fallback shared by `input` / `password` / `confirm`. Re-prompts until `take` returns a value (so\n\t * validation still gates), writing the prompt header each round. A piped stream cannot enter raw\n\t * mode, so there is no masking / live edit — just a validated line read.\n\t *\n\t * @remarks\n\t * The re-prompt loop is bounded by end-of-input: once the stream reaches EOF (a piped stream that\n\t * ran out, or one with no trailing newline) it can never deliver another line, so re-prompting\n\t * would SPIN. On EOF the loop accepts the final line through `take` if it passes, else returns the\n\t * caller's `eof` fallback (the default / empty value) — the prompt always settles, never spins.\n\t */\n\tasync #lineFallback<T>(\n\t\tmessage: string,\n\t\tstyler: StylerInterface | undefined,\n\t\ttake: (answer: string) => { readonly value: T } | undefined,\n\t\teof: (answer: string) => T,\n\t\t// A degraded TTY (isTTY but no setRawMode) must never echo a masked answer — used by `password`.\n\t\tmasked = false,\n\t): Promise<T> {\n\t\tconst paint = styler ?? createStyler()\n\t\tfor (;;) {\n\t\t\tconst { answer, ended } = await this.#question(\n\t\t\t\t`${paint.cyan('?')} ${paint.bold(message)} `,\n\t\t\t\tmasked,\n\t\t\t)\n\t\t\tconst accepted = take(answer)\n\t\t\tif (accepted !== undefined) return accepted.value\n\t\t\tif (ended) return eof(answer)\n\t\t}\n\t}\n\n\t/**\n\t * Print a numbered choice list, then read one readline line and accept it through `take` — the\n\t * non-TTY fallback for `select` / `checkbox`. The list is rendered once; the user types the\n\t * number(s); `take` parses + gates the line, re-prompting until it returns a value.\n\t */\n\tasync #listFallback<T>(\n\t\tmessage: string,\n\t\tstyler: StylerInterface | undefined,\n\t\tchoices: readonly { readonly name: string }[],\n\t\thint: string,\n\t\ttake: (line: string) => { readonly value: T } | undefined,\n\t\teof: T,\n\t): Promise<T> {\n\t\tconst paint = styler ?? createStyler()\n\t\tlet list = `${paint.cyan('?')} ${paint.bold(message)}\\n`\n\t\tchoices.forEach((choice, index) => {\n\t\t\tlist += ` ${paint.dim(`${String(index + 1)})`)} ${choice.name}\\n`\n\t\t})\n\t\tthis.#output.write(list)\n\t\tfor (;;) {\n\t\t\tconst { answer, ended } = await this.#question(`${paint.dim(`${hint}:`)} `)\n\t\t\tconst accepted = take(answer)\n\t\t\tif (accepted !== undefined) return accepted.value\n\t\t\t// EOF on a piped stream — no further line can arrive, so settle the empty fallback rather\n\t\t\t// than spin re-prompting an exhausted stream (a select with no answer ⇒ '', checkbox ⇒ []).\n\t\t\tif (ended) return eof\n\t\t}\n\t}\n\n\t/**\n\t * The non-TTY `editor` fallback — read lines until EOF (the readline `close`), join them, and fall\n\t * back to the default when empty. A piped stream has no ctrl-d keypress, so end-of-input is the\n\t * natural terminator.\n\t *\n\t * @remarks\n\t * Single-pass by necessity: `#lines` drains the whole stream to EOF in one read, so there is no\n\t * second block to re-prompt for (re-reading an exhausted stream returns nothing forever). Validation\n\t * is still applied; on a validation FAILURE the best-effort block is returned rather than spinning\n\t * on the consumed stream — the EOF analogue of the raw-mode editor's re-edit, which a piped stream\n\t * cannot offer.\n\t */\n\tasync #editorFallback(options: EditorOptions): Promise<string> {\n\t\tconst paint = options.styler ?? createStyler()\n\t\tthis.#output.write(\n\t\t\t`${paint.cyan('?')} ${paint.bold(options.message)} ${paint.dim('(EOF to finish)')}\\n`,\n\t\t)\n\t\tconst text = await this.#lines()\n\t\treturn text.length > 0 ? text : (options.default ?? '')\n\t}\n\n\t/**\n\t * Ask one readline question on the resolved streams and resolve the typed (un-trimmed) line plus\n\t * whether the stream ENDED. EOF (the readline `close`) before `rl.question`'s callback fires\n\t * resolves instead of hanging — a piped stream commonly ends WITHOUT a trailing newline (or is\n\t * empty), and the `question` callback only fires on a NEWLINE-terminated line, so without this the\n\t * prompt would wait forever. The trailing unterminated line is still recovered: readline emits it\n\t * as a `'line'` event just before `'close'`, so the last seen line (or `''` for a truly empty\n\t * stream) is returned with `ended: true`. The `ended` flag lets the fallback loops settle rather\n\t * than re-prompt an exhausted stream (which would SPIN). Settling is one-shot (a `close` after a\n\t * delivered line is ignored, so a normal newline-terminated line reports `ended: false`).\n\t */\n\t#question(\n\t\tprompt: string,\n\t\t// A degraded TTY (isTTY but no setRawMode) must never echo a masked answer — used by `password`.\n\t\tmasked = false,\n\t): Promise<{ readonly answer: string; readonly ended: boolean }> {\n\t\tconst rl = createInterface(this.#readline(masked))\n\t\treturn new Promise<{ readonly answer: string; readonly ended: boolean }>((resolve) => {\n\t\t\tlet last = ''\n\t\t\tconst settle = this.#createQuestionSettler(rl, resolve)\n\t\t\t// Track the most recent line so an EOF-on-`close` can recover an unterminated final line.\n\t\t\trl.on('line', (line) => {\n\t\t\t\tlast = line\n\t\t\t})\n\t\t\trl.question(prompt, (answer) => settle(answer, false))\n\t\t\t// EOF before a completed question: settle the recovered partial line (or '') as ended.\n\t\t\trl.on('close', () => settle(last, true))\n\t\t})\n\t}\n\n\t#createQuestionSettler(\n\t\trl: ReturnType<typeof createInterface>,\n\t\tresolve: (value: { readonly answer: string; readonly ended: boolean }) => void,\n\t): (answer: string, ended: boolean) => void {\n\t\tlet settled = false\n\t\treturn (answer, ended) => {\n\t\t\tif (settled) return\n\t\t\tsettled = true\n\t\t\trl.close()\n\t\t\tresolve({ answer, ended })\n\t\t}\n\t}\n\n\t/** Read every line from the input until EOF and resolve them joined by newlines (the editor fallback's reader). */\n\t#lines(): Promise<string> {\n\t\tconst rl = createInterface(this.#readline())\n\t\tconst collected: string[] = []\n\t\treturn new Promise<string>((resolve) => {\n\t\t\trl.on('line', (line) => collected.push(line))\n\t\t\trl.on('close', () => resolve(collected.join('\\n')))\n\t\t})\n\t}\n\n\t/**\n\t * Narrow the resolved streams to the `node:readline` `createInterface` boundary (§14, never an\n\t * `as`). The non-TTY fallback only runs on a real piped `process.stdin` (or a `PassThrough` a test\n\t * injects), both genuine readables; a minimal non-readable fake reaching here means the fallback\n\t * was driven with a stream it cannot use, which fails loudly rather than silently.\n\t */\n\t#readline(\n\t\t// A degraded TTY (isTTY but no setRawMode) must never echo a masked answer — used by `password`.\n\t\tmasked = false,\n\t): { input: NodeJS.ReadableStream; output?: NodeJS.WritableStream; terminal?: boolean } {\n\t\t// Bind to locals first — a guard narrows a local, not a `#private` field access.\n\t\tconst input = this.#input\n\t\tconst output = this.#output\n\t\tif (!isReadable(input))\n\t\t\tthrow new TerminalError('DRIVER', 'Terminal fallback requires a readable input stream')\n\t\t// `terminal: false` disables readline's own echo/line-editing, so a masked (password) answer is\n\t\t// never rendered on a TTY that lacks `setRawMode` (the raw-mode `#drive` path already masks it).\n\t\treturn {\n\t\t\tinput,\n\t\t\t...(isWritable(output) ? { output } : {}),\n\t\t\t...(masked ? { terminal: false } : {}),\n\t\t}\n\t}\n}\n","import type { TerminalInterface, TerminalOptions } from './types.js'\nimport { Terminal } from './Terminal.js'\n\n/**\n * Create the interactive terminal prompt {@link TerminalInterface} (T-c) — the local-TTY arm of the\n * prompt tri-surface, the env-symmetric sibling of the core headless `createPrompt` broker and the\n * SSE `createPromptClient` bridge. A `Terminal` answers each prompt LOCALLY at this machine's\n * keyboard: it reads raw-mode stdin, drives the pure core `*Reduce` reducers, renders each `view` in\n * place, and resolves on submit / rejects with a {@link import('@src/core').TerminalError} (`CANCEL`)\n * on ctrl-c. It is the ONLY impure part of the prompt stack.\n *\n * @param options - See {@link TerminalOptions}\n * @returns A {@link TerminalInterface} — the six async prompt forms (`input` / `password` / `confirm`\n * / `select` / `checkbox` / `editor`) driven over the resolved streams\n *\n * @remarks\n * - **Drives the pure reducers.** Each prompt builds its initial state (`create*State`), enters raw\n * mode ONCE, and feeds each keypress through `parseKey` → the reducer → an in-place re-render; the\n * driver owns no prompt logic itself (state / view / validation / cancel all come from the core).\n * - **Raw-mode leak-free.** Raw mode is entered exactly once per prompt and ALWAYS cleaned up — on\n * submit, on cancel, and on a throw — leaving no raw mode and no leaked `'data'` listener.\n * - **Injectable + guard-narrowed.** `options.input` / `options.output` default to `process.stdin` /\n * `process.stdout` but accept ANY {@link import('./types.js').InputStreamInterface} /\n * {@link import('./types.js').OutputStreamInterface}, resolved through their §14 guards (never an\n * `as`), so a test drives every prompt with a fake TTY that emits scripted key chunks and records\n * the rendered output — and asserts the resolved value, cancel-on-ctrl-c, and no leaked raw mode.\n * - **Non-TTY fallback.** When `input` is not a TTY (piped), raw mode is unavailable, so the prompts\n * fall back to `node:readline` line input (still validating); `select` / `checkbox` present a\n * numbered list, and `editor` reads lines until EOF.\n *\n * @example\n * ```ts\n * import { createTerminal } from '@src/server'\n *\n * const terminal = createTerminal()\n * const name = await terminal.input({ message: 'Your name' })\n * const proceed = await terminal.confirm({ message: 'Continue?', default: true })\n * ```\n */\nexport function createTerminal(options?: TerminalOptions): TerminalInterface {\n\treturn new Terminal(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["#input","#output","#handlers","#drive","#lineFallback","#listFallback","#editorFallback","#createHandler","#enterRaw","#render","#leaveRaw","#question","#lines","#readline","#createQuestionSettler"],"sources":["../../../src/server/constants.ts","../../../src/server/helpers.ts","../../../src/server/Terminal.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-terminals constants (the T-c branch) — the cursor / line-clear ANSI control sequences the\n// interactive `Terminal` driver writes to redraw a prompt view IN PLACE, plus the readline-fallback\n// defaults. UPPER_SNAKE, every member exported (AGENTS §5). Sequences are built from a named ESC\n// byte via `String.fromCharCode` so no raw control character appears in source (the core\n// terminals / console-module idiom).\n\n/** The Escape byte (ESC, U+001B) — the lead byte of every CSI cursor-control sequence below. */\nexport const ESCAPE = String.fromCharCode(27)\n\n/** The Control Sequence Introducer (`ESC[`) — the prefix of every cursor / erase sequence. */\nexport const CSI = `${ESCAPE}[`\n\n/**\n * The cursor-UP sequence TEMPLATE (`ESC[{count}A`) — {@link import('./helpers.js').moveUp}\n * interpolates `{count}` with the number of lines to climb (the `{count}` placeholder idiom the core\n * terminals' `RULE_MESSAGES` uses). Kept as a template so the count stays out of the constant.\n */\nexport const CSI_UP = `${CSI}{count}A`\n\n/**\n * Hide the cursor (`ESC[?25l`) — written before the driver starts redrawing a prompt so the cursor\n * does not flicker across the view during an in-place re-render; paired with {@link CURSOR_SHOW}.\n */\nexport const CURSOR_HIDE = `${CSI}?25l`\n\n/** Show the cursor (`ESC[?25h`) — restores the cursor after a prompt resolves / cancels (the {@link CURSOR_HIDE} pair). */\nexport const CURSOR_SHOW = `${CSI}?25h`\n\n/**\n * Erase from the cursor down to the end of the screen (`ESC[J`) — wipes the WHOLE previous (possibly\n * multi-line `select` / `checkbox`) view in one write before the new view is rendered, so a redraw\n * never leaves orphaned rows below.\n */\nexport const CLEAR_DOWN = `${CSI}J`\n\n/** A carriage return (`\\r`, U+000D) — returns the cursor to column 0 so a redraw starts at the line's left edge. */\nexport const CARRIAGE_RETURN = String.fromCharCode(13)\n\n/** A line feed (`\\n`, U+000A) — the line terminator the driver writes after the final committed prompt view. */\nexport const LINE_FEED = String.fromCharCode(10)\n\n/**\n * The numbered-list prompt the non-TTY {@link import('./Terminal.js').Terminal} `select` / `checkbox`\n * fallback appends — a piped (non-terminal) stream cannot navigate with arrow keys, so the choices are\n * printed numbered and the user types the number(s) on a single readline line.\n */\nexport const FALLBACK_SELECT_HINT = 'Enter a number'\n\n/** The comma-separated multi-select hint the non-TTY `checkbox` fallback shows (the user types one or more numbers). */\nexport const FALLBACK_CHECKBOX_HINT = 'Enter numbers separated by commas'\n","// Pure helpers for the T-c server-terminals branch (AGENTS §5 — every function here is exported and\n// unit-tested). Total utilities: the two stream-boundary guards (narrow `process.stdin` /\n// `process.stdout` / any injected stream without `as`, §14), the raw-mode capability probe, and the\n// pure cursor-math the interactive `Terminal` driver uses to redraw a prompt view IN PLACE (count a\n// view's lines, build the cursor-up sequence, and assemble the whole reposition-and-clear prefix) —\n// so the impure driver only feeds bytes into the reducers and writes the strings these helpers build.\n\nimport type { InputStreamInterface, OutputStreamInterface } from './types.js'\nimport { CARRIAGE_RETURN, CLEAR_DOWN, CSI_UP } from './constants.js'\n\n/**\n * Whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` / `off`\n * `'data'` subscription methods. A total type guard (AGENTS §14): it NEVER throws and returns `false`\n * for anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`,\n * or a fake TTY a test injects) to the exact slice the driver reads — no `as`.\n *\n * @remarks\n * Only `on` / `off` are required (the irreducible event seam); `setRawMode` / `resume` / `pause` /\n * `isTTY` are optional on {@link InputStreamInterface}, so their absence does not disqualify a stream\n * — a piped, non-TTY stream is still a valid input, just one the driver reads through the readline\n * fallback rather than raw mode.\n *\n * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)\n * @returns `true` when `value` has callable `on` and `off`\n */\nexport function isInputStream(value: unknown): value is InputStreamInterface {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'on' in value &&\n\t\ttypeof value.on === 'function' &&\n\t\t'off' in value &&\n\t\ttypeof value.off === 'function'\n\t)\n}\n\n/**\n * Whether `value` is a usable {@link OutputStreamInterface} — a record with a callable `write`. A\n * total type guard (AGENTS §14): it NEVER throws and returns `false` for anything off-shape, so it\n * narrows the output boundary (the real `process.stdout`, or a recording fake a test injects) to the\n * one method the driver writes through — no `as`.\n *\n * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)\n * @returns `true` when `value` has a callable `write`\n */\nexport function isOutputStream(value: unknown): value is OutputStreamInterface {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'write' in value &&\n\t\ttypeof value.write === 'function'\n\t)\n}\n\n/**\n * Whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard (AGENTS §14)\n * checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface` requires\n * as its `input`. The non-TTY fallback narrows the resolved input stream through this before handing\n * it to readline (never an `as`), so a real piped `process.stdin` (or a `PassThrough` a test injects)\n * crosses into the readline boundary honestly. Never throws; returns `false` for a minimal fake that\n * isn't a full readable.\n *\n * @param value - The resolved input stream (or any value crossing the boundary)\n * @returns `true` when `value` has the readable methods readline needs\n */\nexport function isReadable(value: unknown): value is NodeJS.ReadableStream {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'read' in value &&\n\t\ttypeof value.read === 'function' &&\n\t\t'pipe' in value &&\n\t\ttypeof value.pipe === 'function'\n\t)\n}\n\n/**\n * Whether `value` is a Node {@link NodeJS.WritableStream} — a total structural guard (AGENTS §14)\n * checking for the callable `write` / `end` that `node:readline`'s `createInterface` accepts as its\n * `output`. Paired with {@link isReadable} so the non-TTY fallback narrows BOTH streams to the\n * readline boundary without an `as`. Never throws.\n *\n * @param value - The resolved output stream (or any value crossing the boundary)\n * @returns `true` when `value` has the writable methods readline needs\n */\nexport function isWritable(value: unknown): value is NodeJS.WritableStream {\n\treturn (\n\t\ttypeof value === 'object' &&\n\t\tvalue !== null &&\n\t\t'write' in value &&\n\t\ttypeof value.write === 'function' &&\n\t\t'end' in value &&\n\t\ttypeof value.end === 'function'\n\t)\n}\n\n/**\n * Whether an input stream can be driven in RAW mode — it both reports `isTTY === true` AND exposes a\n * callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to choose its path:\n * `true` ⇒ the interactive raw-mode prompts (arrow-key navigation, live re-render); `false` ⇒ the\n * `node:readline` line-input fallback (a piped / non-terminal stream cannot enter raw mode). Total —\n * never throws.\n *\n * @param input - The resolved {@link InputStreamInterface}\n * @returns `true` when the stream is a TTY with `setRawMode`\n */\nexport function rawCapable(input: InputStreamInterface): boolean {\n\treturn input.isTTY === true && typeof input.setRawMode === 'function'\n}\n\n/**\n * The number of terminal LINES a rendered prompt `view` occupies — one more than its newline count\n * (a view with no newline is a single line; N newlines span N+1 lines). The basis of the in-place\n * re-render: the driver records the line count of the view it just wrote so the next redraw knows how\n * far up to move the cursor before overwriting. Total; an empty string is one (empty) line.\n *\n * @param view - The rendered (possibly multi-line, possibly ANSI-styled) view string\n * @returns The number of lines the view spans (always at least 1)\n */\nexport function lineCount(view: string): number {\n\tlet lines = 1\n\tfor (const character of view) {\n\t\tif (character === '\\n') lines += 1\n\t}\n\treturn lines\n}\n\n/**\n * The cursor-UP control sequence that moves the cursor up `count` lines (`ESC[{count}A`) — or the\n * empty string when `count` is zero or negative (no movement needed, and `ESC[0A` is a wasted write).\n * The pure step the in-place re-render uses to climb back over the previous view before clearing it.\n * Total.\n *\n * @param count - How many lines to move the cursor up\n * @returns The `ESC[{count}A` sequence, or `''` when `count <= 0`\n */\nexport function moveUp(count: number): string {\n\tif (count <= 0) return ''\n\treturn `${CSI_UP.replace('{count}', String(count))}`\n}\n\n/**\n * The full reposition-and-clear prefix to write BEFORE re-rendering a prompt view in place — given\n * the line count of the PREVIOUS view, it moves the cursor up over those lines, returns it to column\n * 0, and erases everything from there to the end of the screen, so the next view is drawn on a clean\n * region (no orphaned rows from a taller previous view). Pure; the driver writes this immediately\n * followed by the new view.\n *\n * @remarks\n * For the FIRST render `previousLines` is `1` (the cursor sits on the line the prompt opened on) so\n * the prefix is just a carriage return + clear-down — the prompt draws from the current line. For a\n * subsequent render it climbs `previousLines - 1` lines (the cursor is on the LAST line of the prior\n * view) before clearing. Keeping the math here (not in the driver) makes the re-render unit-testable\n * without a real terminal.\n *\n * @param previousLines - The line count of the view currently on screen (from {@link lineCount})\n * @returns The control-sequence prefix to write before the new view\n */\nexport function redrawPrefix(previousLines: number): string {\n\treturn `${moveUp(previousLines - 1)}${CARRIAGE_RETURN}${CLEAR_DOWN}`\n}\n","import type {\n\tCheckboxOptions,\n\tConfirmOptions,\n\tEditorOptions,\n\tEditorState,\n\tInputOptions,\n\tKeyEvent,\n\tPasswordOptions,\n\tPromptStep,\n\tPromptTheme,\n\tSelectOptions,\n} from '@src/core'\nimport type { StylerInterface } from '@orkestrel/console'\nimport type {\n\tInputStreamInterface,\n\tOutputStreamInterface,\n\tTerminalInterface,\n\tTerminalOptions,\n} from './types.js'\nimport {\n\tcreateCheckboxState,\n\tcreateConfirmState,\n\tcreateEditorState,\n\tcreateInputState,\n\tcreatePasswordState,\n\tcreateSelectState,\n\tcheckboxReduce,\n\tconfirmReduce,\n\teditorReduce,\n\thintedHeader,\n\tinputReduce,\n\tparseKey,\n\tpasswordReduce,\n\tpromptHeader,\n\tresolveValidation,\n\tselectReduce,\n\tTerminalError,\n} from '@src/core'\nimport { createInterface } from 'node:readline'\nimport { stdin, stdout } from 'node:process'\nimport {\n\tCURSOR_HIDE,\n\tCURSOR_SHOW,\n\tFALLBACK_CHECKBOX_HINT,\n\tFALLBACK_SELECT_HINT,\n\tLINE_FEED,\n} from './constants.js'\nimport {\n\tisInputStream,\n\tisOutputStream,\n\tisReadable,\n\tisWritable,\n\tlineCount,\n\trawCapable,\n\tredrawPrefix,\n} from './helpers.js'\n\n/**\n * The interactive terminal prompt driver (T-c) — the third {@link TerminalInterface} surface (beside\n * the core headless `Prompt` broker and the SSE `PromptClient` bridge), and the ONLY impure part of\n * the prompt stack. It reads the TTY and DRIVES the pure core `*Reduce` reducers: it feeds raw-mode\n * stdin bytes through `parseKey` into the matching reducer, renders the returned `view` in place, and\n * resolves on a `submit` step / rejects with a {@link TerminalError} (`CANCEL`) on ctrl-c. It owns no\n * prompt logic of its own — state, view, validation, and the cancel signal all come from the pure\n * core; this class owns ONLY the cursor + raw-mode + in-place re-render mechanics.\n *\n * @remarks\n * See {@link TerminalInterface} for the behavioral contract (raw-mode leak-freedom, the in-place\n * re-render, the non-TTY readline fallback, event-free).\n */\nexport class Terminal implements TerminalInterface {\n\treadonly #input: InputStreamInterface\n\treadonly #output: OutputStreamInterface\n\treadonly #handlers = new Map<object, (chunk: string | Uint8Array) => void>()\n\n\tconstructor(options?: TerminalOptions) {\n\t\t// Resolve each stream through its guard (§14): a present, well-shaped injected stream is used as\n\t\t// is; otherwise the real process stream — no `as`, and an `undefined` option falls through.\n\t\tthis.#input = isInputStream(options?.input) ? options.input : stdin\n\t\tthis.#output = isOutputStream(options?.output) ? options.output : stdout\n\t}\n\n\t// === Prompt forms (PromptFormInterface)\n\n\tinput(options: InputOptions): Promise<string> {\n\t\tconst state = createInputState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, inputReduce)\n\t\treturn this.#lineFallback(\n\t\t\toptions.message,\n\t\t\tstate.styler,\n\t\t\tstate.theme,\n\t\t\tundefined,\n\t\t\t(answer) => {\n\t\t\t\tconst value = answer.length > 0 ? answer : (options.default ?? '')\n\t\t\t\treturn resolveValidation(options.validate)(value) === true ? { value } : undefined\n\t\t\t},\n\t\t\t// EOF: settle the entered line or the default — a piped stream can't be re-prompted.\n\t\t\t(answer) => (answer.length > 0 ? answer : (options.default ?? '')),\n\t\t)\n\t}\n\n\tpassword(options: PasswordOptions): Promise<string> {\n\t\tconst state = createPasswordState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, passwordReduce)\n\t\treturn this.#lineFallback(\n\t\t\toptions.message,\n\t\t\tstate.styler,\n\t\t\tstate.theme,\n\t\t\tundefined,\n\t\t\t(answer) =>\n\t\t\t\tresolveValidation(options.validate)(answer) === true ? { value: answer } : undefined,\n\t\t\t// EOF: settle the entered secret (or '') — a piped stream can't be re-prompted.\n\t\t\t(answer) => answer,\n\t\t\t// A degraded TTY (isTTY but no setRawMode) must never echo the secret being typed.\n\t\t\ttrue,\n\t\t)\n\t}\n\n\tconfirm(options: ConfirmOptions): Promise<boolean> {\n\t\tconst state = createConfirmState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, confirmReduce)\n\t\treturn this.#lineFallback(\n\t\t\toptions.message,\n\t\t\tstate.styler,\n\t\t\tstate.theme,\n\t\t\tstate.hint,\n\t\t\t(answer) => {\n\t\t\t\tconst normalized = answer.trim().toLowerCase()\n\t\t\t\tif (normalized.length === 0) return { value: options.default ?? false }\n\t\t\t\tif (normalized === 'y' || normalized === 'yes') return { value: true }\n\t\t\t\tif (normalized === 'n' || normalized === 'no') return { value: false }\n\t\t\t\treturn undefined\n\t\t\t},\n\t\t\t// EOF: settle the default — a piped stream can't be re-prompted for a y/n.\n\t\t\t() => options.default ?? false,\n\t\t)\n\t}\n\n\tselect(options: SelectOptions): Promise<string> {\n\t\tconst state = createSelectState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, selectReduce)\n\t\treturn this.#listFallback(\n\t\t\toptions.message,\n\t\t\tstate.styler,\n\t\t\tstate.theme,\n\t\t\tstate.choices,\n\t\t\tstate.hint ?? FALLBACK_SELECT_HINT,\n\t\t\t(line) => {\n\t\t\t\tconst index = Number.parseInt(line.trim(), 10) - 1\n\t\t\t\tconst choice = state.choices[index]\n\t\t\t\treturn choice === undefined ? undefined : { value: choice.value }\n\t\t\t},\n\t\t\t// EOF: no choice was picked — settle the empty string (a piped stream can't be re-prompted).\n\t\t\t'',\n\t\t)\n\t}\n\n\tcheckbox(options: CheckboxOptions): Promise<readonly string[]> {\n\t\tconst state = createCheckboxState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, checkboxReduce)\n\t\treturn this.#listFallback(\n\t\t\toptions.message,\n\t\t\tstate.styler,\n\t\t\tstate.theme,\n\t\t\tstate.choices,\n\t\t\tstate.hint ?? FALLBACK_CHECKBOX_HINT,\n\t\t\t(line) => {\n\t\t\t\tconst indices = line\n\t\t\t\t\t.split(',')\n\t\t\t\t\t.map((part) => Number.parseInt(part.trim(), 10) - 1)\n\t\t\t\t\t.filter((index) => index >= 0 && index < state.choices.length)\n\t\t\t\tif (options.min !== undefined && indices.length < options.min) return undefined\n\t\t\t\tif (options.max !== undefined && indices.length > options.max) return undefined\n\t\t\t\tconst values = indices\n\t\t\t\t\t.map((index) => state.choices[index]?.value)\n\t\t\t\t\t.filter((value): value is string => value !== undefined)\n\t\t\t\treturn { value: values }\n\t\t\t},\n\t\t\t// EOF: nothing selected — settle the empty list (a piped stream can't be re-prompted).\n\t\t\t[],\n\t\t)\n\t}\n\n\teditor(options: EditorOptions): Promise<string> {\n\t\tconst state = createEditorState(options)\n\t\tif (rawCapable(this.#input)) return this.#drive(state, editorReduce)\n\t\treturn this.#editorFallback(state)\n\t}\n\n\t// === The raw-mode kernel\n\n\t/**\n\t * The irreducible Node raw-mode primitive — the ONLY place raw mode is touched. Switches the input\n\t * into raw mode (each keypress delivered immediately, no echo), resumes its flow, and subscribes\n\t * `handler` to `'data'`. The paired {@link #leaveRaw} operation unsubscribes the exact handler,\n\t * leaves raw mode, and pauses the stream on submit, cancel, or throw.\n\t */\n\t#enterRaw(token: object, handler: (chunk: string | Uint8Array) => void): void {\n\t\tthis.#handlers.set(token, handler)\n\t\tthis.#input.setRawMode?.(true)\n\t\tthis.#input.resume?.()\n\t\tthis.#input.on('data', handler)\n\t}\n\n\t#leaveRaw(token: object): void {\n\t\tconst handler = this.#handlers.get(token)\n\t\tif (handler === undefined) return\n\t\tthis.#handlers.delete(token)\n\t\tthis.#input.off('data', handler)\n\t\tthis.#input.setRawMode?.(false)\n\t\tthis.#input.pause?.()\n\t}\n\n\t/**\n\t * Drive ONE interactive prompt over raw-mode stdin — the generic engine all six TTY prompts share.\n\t * Renders the reducer's initial `view`, enters raw mode once, and on each keypress runs `parseKey`\n\t * → `reduce` → an in-place re-render; on `submit` it cleans up + resolves the reducer's `value`, on\n\t * `cancel` (ctrl-c) it cleans up + rejects a {@link TerminalError} (`CANCEL`). Raw mode is entered\n\t * exactly once and cleaned up on every exit path (submit / cancel / a throw inside a step), so no\n\t * raw mode and no `'data'` listener ever leak. The cursor is hidden for the duration and restored\n\t * on exit.\n\t */\n\t#drive<T, S>(initial: S, reduce: (state: S, key: KeyEvent) => PromptStep<T, S>): Promise<T> {\n\t\treturn new Promise<T>((resolve, reject) => {\n\t\t\tconst state = initial\n\t\t\t// Render the first view, tracking how many lines it spans so the next redraw climbs over them.\n\t\t\tthis.#output.write(CURSOR_HIDE)\n\t\t\tlet firstView: string\n\t\t\ttry {\n\t\t\t\tfirstView = reduce(state, parseKey('')).view\n\t\t\t\tthis.#output.write(firstView)\n\t\t\t} catch (error) {\n\t\t\t\t// Raw mode was never entered yet, but the cursor was hidden — restore it before rejecting.\n\t\t\t\tthis.#output.write(`${CURSOR_SHOW}${LINE_FEED}`)\n\t\t\t\treject(error)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst token = {}\n\t\t\tconst handler = this.#createHandler(\n\t\t\t\ttoken,\n\t\t\t\tstate,\n\t\t\t\tlineCount(firstView),\n\t\t\t\treduce,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t)\n\t\t\tthis.#enterRaw(token, handler)\n\t\t})\n\t}\n\n\t#createHandler<T, S>(\n\t\ttoken: object,\n\t\tinitial: S,\n\t\tinitialLines: number,\n\t\treduce: (state: S, key: KeyEvent) => PromptStep<T, S>,\n\t\tresolve: (value: T | PromiseLike<T>) => void,\n\t\treject: (reason?: unknown) => void,\n\t): (chunk: string | Uint8Array) => void {\n\t\tlet state = initial\n\t\tlet lines = initialLines\n\t\treturn (chunk) => {\n\t\t\ttry {\n\t\t\t\tconst step = reduce(state, parseKey(chunk))\n\t\t\t\tstate = step.state\n\t\t\t\tif (step.status === 'active') {\n\t\t\t\t\tthis.#render(step.view, lines)\n\t\t\t\t\tlines = lineCount(step.view)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t// Terminal step (submit / cancel): paint the final committed view, then tear down.\n\t\t\t\tthis.#render(step.view, lines)\n\t\t\t\tthis.#leaveRaw(token)\n\t\t\t\tthis.#output.write(`${CURSOR_SHOW}${LINE_FEED}`)\n\t\t\t\tif (step.status === 'cancel') reject(new TerminalError('CANCEL', 'Prompt cancelled'))\n\t\t\t\telse if (step.status === 'submit' && step.value !== undefined) resolve(step.value)\n\t\t\t\telse reject(new TerminalError('DRIVER', 'submit produced no value'))\n\t\t\t} catch (error) {\n\t\t\t\t// A throw inside the reducer/validator/styler: tear down raw mode + the listener,\n\t\t\t\t// restore the cursor exactly as the normal exit path does, then reject.\n\t\t\t\tthis.#leaveRaw(token)\n\t\t\t\tthis.#output.write(`${CURSOR_SHOW}${LINE_FEED}`)\n\t\t\t\treject(error)\n\t\t\t}\n\t\t}\n\t}\n\n\t/** Redraw a prompt view in place — climb over the previous view's `previousLines`, clear, and write the new view (the pure cursor-math is {@link redrawPrefix}). */\n\t#render(view: string, previousLines: number): void {\n\t\tthis.#output.write(`${redrawPrefix(previousLines)}${view}`)\n\t}\n\n\t// === Non-TTY fallbacks (node:readline line input)\n\n\t/**\n\t * Read a single line via `node:readline` and accept it through `take` — the non-TTY line-input\n\t * fallback shared by `input` / `password` / `confirm`. Re-prompts until `take` returns a value (so\n\t * validation still gates), writing the prompt header each round. A piped stream cannot enter raw\n\t * mode, so there is no masking / live edit — just a validated line read.\n\t *\n\t * @remarks\n\t * The re-prompt loop is bounded by end-of-input: once the stream reaches EOF (a piped stream that\n\t * ran out, or one with no trailing newline) it can never deliver another line, so re-prompting\n\t * would SPIN. On EOF the loop accepts the final line through `take` if it passes, else returns the\n\t * caller's `eof` fallback (the default / empty value) — the prompt always settles, never spins.\n\t */\n\tasync #lineFallback<T>(\n\t\tmessage: string,\n\t\tstyler: StylerInterface,\n\t\ttheme: PromptTheme,\n\t\thint: string | undefined,\n\t\ttake: (answer: string) => { readonly value: T } | undefined,\n\t\teof: (answer: string) => T,\n\t\t// A degraded TTY (isTTY but no setRawMode) must never echo a masked answer — used by `password`.\n\t\tmasked = false,\n\t): Promise<T> {\n\t\tfor (;;) {\n\t\t\tconst { answer, ended } = await this.#question(\n\t\t\t\t`${hintedHeader(styler, theme, message, hint)} `,\n\t\t\t\tmasked,\n\t\t\t)\n\t\t\tconst accepted = take(answer)\n\t\t\tif (accepted !== undefined) return accepted.value\n\t\t\tif (ended) return eof(answer)\n\t\t}\n\t}\n\n\t/**\n\t * Print a numbered choice list, then read one readline line and accept it through `take` — the\n\t * non-TTY fallback for `select` / `checkbox`. The list is rendered once; the user types the\n\t * number(s); `take` parses + gates the line, re-prompting until it returns a value. Each index is\n\t * painted by the `muted` role and each choice name by the `content` role.\n\t */\n\tasync #listFallback<T>(\n\t\tmessage: string,\n\t\tstyler: StylerInterface,\n\t\ttheme: PromptTheme,\n\t\tchoices: ReadonlyArray<{ readonly name: string }>,\n\t\thint: string,\n\t\ttake: (line: string) => { readonly value: T } | undefined,\n\t\teof: T,\n\t): Promise<T> {\n\t\tlet list = `${promptHeader(styler, theme, message)}\\n`\n\t\tchoices.forEach((choice, index) => {\n\t\t\tlist += ` ${styler.render(theme.roles.muted, `${String(index + 1)})`)} ${styler.render(theme.roles.content, choice.name)}\\n`\n\t\t})\n\t\tthis.#output.write(list)\n\t\tfor (;;) {\n\t\t\tconst { answer, ended } = await this.#question(\n\t\t\t\t`${styler.render(theme.roles.hint, `${hint}:`)} `,\n\t\t\t)\n\t\t\tconst accepted = take(answer)\n\t\t\tif (accepted !== undefined) return accepted.value\n\t\t\t// EOF on a piped stream — no further line can arrive, so settle the empty fallback rather\n\t\t\t// than spin re-prompting an exhausted stream (a select with no answer ⇒ '', checkbox ⇒ []).\n\t\t\tif (ended) return eof\n\t\t}\n\t}\n\n\t/**\n\t * The non-TTY `editor` fallback — read lines until EOF (the readline `close`), join them, and fall\n\t * back to the default when empty. A piped stream has no ctrl-d keypress, so end-of-input is the\n\t * natural terminator.\n\t *\n\t * @remarks\n\t * Single-pass by necessity: `#lines` drains the whole stream to EOF in one read, so there is no\n\t * second block to re-prompt for (re-reading an exhausted stream returns nothing forever). Validation\n\t * is still applied; on a validation FAILURE the best-effort block is returned rather than spinning\n\t * on the consumed stream — the EOF analogue of the raw-mode editor's re-edit, which a piped stream\n\t * cannot offer.\n\t */\n\tasync #editorFallback(state: EditorState): Promise<string> {\n\t\tthis.#output.write(\n\t\t\t`${hintedHeader(state.styler, state.theme, state.message, state.hint ?? '(EOF to finish)')}\\n`,\n\t\t)\n\t\tconst text = await this.#lines()\n\t\treturn text.length > 0 ? text : state.default\n\t}\n\n\t/**\n\t * Ask one readline question on the resolved streams and resolve the typed (un-trimmed) line plus\n\t * whether the stream ENDED. EOF (the readline `close`) before `rl.question`'s callback fires\n\t * resolves instead of hanging — a piped stream commonly ends WITHOUT a trailing newline (or is\n\t * empty), and the `question` callback only fires on a NEWLINE-terminated line, so without this the\n\t * prompt would wait forever. The trailing unterminated line is still recovered: readline emits it\n\t * as a `'line'` event just before `'close'`, so the last seen line (or `''` for a truly empty\n\t * stream) is returned with `ended: true`. The `ended` flag lets the fallback loops settle rather\n\t * than re-prompt an exhausted stream (which would SPIN). Settling is one-shot (a `close` after a\n\t * delivered line is ignored, so a normal newline-terminated line reports `ended: false`).\n\t */\n\t#question(\n\t\tprompt: string,\n\t\t// A degraded TTY (isTTY but no setRawMode) must never echo a masked answer — used by `password`.\n\t\tmasked = false,\n\t): Promise<{ readonly answer: string; readonly ended: boolean }> {\n\t\tconst rl = createInterface(this.#readline(masked))\n\t\treturn new Promise<{ readonly answer: string; readonly ended: boolean }>((resolve) => {\n\t\t\tlet last = ''\n\t\t\tconst settle = this.#createQuestionSettler(rl, resolve)\n\t\t\t// Track the most recent line so an EOF-on-`close` can recover an unterminated final line.\n\t\t\trl.on('line', (line) => {\n\t\t\t\tlast = line\n\t\t\t})\n\t\t\trl.question(prompt, (answer) => settle(answer, false))\n\t\t\t// EOF before a completed question: settle the recovered partial line (or '') as ended.\n\t\t\trl.on('close', () => settle(last, true))\n\t\t})\n\t}\n\n\t#createQuestionSettler(\n\t\trl: ReturnType<typeof createInterface>,\n\t\tresolve: (value: { readonly answer: string; readonly ended: boolean }) => void,\n\t): (answer: string, ended: boolean) => void {\n\t\tlet settled = false\n\t\treturn (answer, ended) => {\n\t\t\tif (settled) return\n\t\t\tsettled = true\n\t\t\trl.close()\n\t\t\tresolve({ answer, ended })\n\t\t}\n\t}\n\n\t/** Read every line from the input until EOF and resolve them joined by newlines (the editor fallback's reader). */\n\t#lines(): Promise<string> {\n\t\tconst rl = createInterface(this.#readline())\n\t\tconst collected: string[] = []\n\t\treturn new Promise<string>((resolve) => {\n\t\t\trl.on('line', (line) => collected.push(line))\n\t\t\trl.on('close', () => resolve(collected.join('\\n')))\n\t\t})\n\t}\n\n\t/**\n\t * Narrow the resolved streams to the `node:readline` `createInterface` boundary (§14, never an\n\t * `as`). The non-TTY fallback only runs on a real piped `process.stdin` (or a `PassThrough` a test\n\t * injects), both genuine readables; a minimal non-readable fake reaching here means the fallback\n\t * was driven with a stream it cannot use, which fails loudly rather than silently.\n\t */\n\t#readline(\n\t\t// A degraded TTY (isTTY but no setRawMode) must never echo a masked answer — used by `password`.\n\t\tmasked = false,\n\t): { input: NodeJS.ReadableStream; output?: NodeJS.WritableStream; terminal?: boolean } {\n\t\t// Bind to locals first — a guard narrows a local, not a `#private` field access.\n\t\tconst input = this.#input\n\t\tconst output = this.#output\n\t\tif (!isReadable(input))\n\t\t\tthrow new TerminalError('DRIVER', 'Terminal fallback requires a readable input stream')\n\t\t// `terminal: false` disables readline's own echo/line-editing, so a masked (password) answer is\n\t\t// never rendered on a TTY that lacks `setRawMode` (the raw-mode `#drive` path already masks it).\n\t\treturn {\n\t\t\tinput,\n\t\t\t...(isWritable(output) ? { output } : {}),\n\t\t\t...(masked ? { terminal: false } : {}),\n\t\t}\n\t}\n}\n","import type { TerminalInterface, TerminalOptions } from './types.js'\nimport { Terminal } from './Terminal.js'\n\n/**\n * Create the interactive terminal prompt {@link TerminalInterface} (T-c) — the local-TTY arm of the\n * prompt tri-surface, the env-symmetric sibling of the core headless `createPrompt` broker and the\n * SSE `createPromptClient` bridge. A `Terminal` answers each prompt LOCALLY at this machine's\n * keyboard: it reads raw-mode stdin, drives the pure core `*Reduce` reducers, renders each `view` in\n * place, and resolves on submit / rejects with a {@link import('@src/core').TerminalError} (`CANCEL`)\n * on ctrl-c. It is the ONLY impure part of the prompt stack.\n *\n * @param options - See {@link TerminalOptions}\n * @returns A {@link TerminalInterface} — the six async prompt forms (`input` / `password` / `confirm`\n * / `select` / `checkbox` / `editor`) driven over the resolved streams\n *\n * @remarks\n * - **Drives the pure reducers.** Each prompt builds its initial state (`create*State`), enters raw\n * mode ONCE, and feeds each keypress through `parseKey` → the reducer → an in-place re-render; the\n * driver owns no prompt logic itself (state / view / validation / cancel all come from the core).\n * - **Raw-mode leak-free.** Raw mode is entered exactly once per prompt and ALWAYS cleaned up — on\n * submit, on cancel, and on a throw — leaving no raw mode and no leaked `'data'` listener.\n * - **Injectable + guard-narrowed.** `options.input` / `options.output` default to `process.stdin` /\n * `process.stdout` but accept ANY {@link import('./types.js').InputStreamInterface} /\n * {@link import('./types.js').OutputStreamInterface}, resolved through their §14 guards (never an\n * `as`), so a test drives every prompt with a fake TTY that emits scripted key chunks and records\n * the rendered output — and asserts the resolved value, cancel-on-ctrl-c, and no leaked raw mode.\n * - **Non-TTY fallback.** When `input` is not a TTY (piped), raw mode is unavailable, so the prompts\n * fall back to `node:readline` line input (still validating); `select` / `checkbox` present a\n * numbered list, and `editor` reads lines until EOF.\n *\n * @example\n * ```ts\n * import { createTerminal } from '@src/server'\n *\n * const terminal = createTerminal()\n * const name = await terminal.input({ message: 'Your name' })\n * const proceed = await terminal.confirm({ message: 'Continue?', default: true })\n * ```\n */\nexport function createTerminal(options?: TerminalOptions): TerminalInterface {\n\treturn new Terminal(options)\n}\n"],"mappings":"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"}
@@ -1,10 +1,10 @@
1
- import { CheckboxOptions } from '../core/index.ts';
2
- import { ConfirmOptions } from '../core/index.ts';
3
- import { EditorOptions } from '../core/index.ts';
4
- import { InputOptions } from '../core/index.ts';
5
- import { PasswordOptions } from '../core/index.ts';
6
- import { PromptFormInterface } from '../core/index.ts';
7
- import { SelectOptions } from '../core/index.ts';
1
+ import { CheckboxOptions } from '@orkestrel/terminal';
2
+ import { ConfirmOptions } from '@orkestrel/terminal';
3
+ import { EditorOptions } from '@orkestrel/terminal';
4
+ import { InputOptions } from '@orkestrel/terminal';
5
+ import { PasswordOptions } from '@orkestrel/terminal';
6
+ import { PromptFormInterface } from '@orkestrel/terminal';
7
+ import { SelectOptions } from '@orkestrel/terminal';
8
8
 
9
9
  /** A carriage return (`\r`, U+000D) — returns the cursor to column 0 so a redraw starts at the line's left edge. */
10
10
  export declare const CARRIAGE_RETURN: string;
@@ -1,10 +1,10 @@
1
- import { CheckboxOptions } from '../core/index.ts';
2
- import { ConfirmOptions } from '../core/index.ts';
3
- import { EditorOptions } from '../core/index.ts';
4
- import { InputOptions } from '../core/index.ts';
5
- import { PasswordOptions } from '../core/index.ts';
6
- import { PromptFormInterface } from '../core/index.ts';
7
- import { SelectOptions } from '../core/index.ts';
1
+ import { CheckboxOptions } from '@orkestrel/terminal';
2
+ import { ConfirmOptions } from '@orkestrel/terminal';
3
+ import { EditorOptions } from '@orkestrel/terminal';
4
+ import { InputOptions } from '@orkestrel/terminal';
5
+ import { PasswordOptions } from '@orkestrel/terminal';
6
+ import { PromptFormInterface } from '@orkestrel/terminal';
7
+ import { SelectOptions } from '@orkestrel/terminal';
8
8
 
9
9
  /** A carriage return (`\r`, U+000D) — returns the cursor to column 0 so a redraw starts at the line's left edge. */
10
10
  export declare const CARRIAGE_RETURN: string;
@@ -1,5 +1,4 @@
1
- import { TerminalError, checkboxReduce, confirmReduce, createCheckboxState, createConfirmState, createEditorState, createInputState, createPasswordState, createSelectState, editorReduce, inputReduce, parseKey, passwordReduce, resolveValidation, selectReduce } from "../core/index.js";
2
- import { createStyler } from "@orkestrel/console";
1
+ import { TerminalError, checkboxReduce, confirmReduce, createCheckboxState, createConfirmState, createEditorState, createInputState, createPasswordState, createSelectState, editorReduce, hintedHeader, inputReduce, parseKey, passwordReduce, promptHeader, resolveValidation, selectReduce } from "../core/index.js";
3
2
  import { createInterface } from "node:readline";
4
3
  import { stdin, stdout } from "node:process";
5
4
  //#region src/server/constants.ts
@@ -182,7 +181,7 @@ var Terminal = class {
182
181
  input(options) {
183
182
  const state = createInputState(options);
184
183
  if (rawCapable(this.#input)) return this.#drive(state, inputReduce);
185
- return this.#lineFallback(options.message, options.styler, (answer) => {
184
+ return this.#lineFallback(options.message, state.styler, state.theme, void 0, (answer) => {
186
185
  const value = answer.length > 0 ? answer : options.default ?? "";
187
186
  return resolveValidation(options.validate)(value) === true ? { value } : void 0;
188
187
  }, (answer) => answer.length > 0 ? answer : options.default ?? "");
@@ -190,12 +189,12 @@ var Terminal = class {
190
189
  password(options) {
191
190
  const state = createPasswordState(options);
192
191
  if (rawCapable(this.#input)) return this.#drive(state, passwordReduce);
193
- return this.#lineFallback(options.message, options.styler, (answer) => resolveValidation(options.validate)(answer) === true ? { value: answer } : void 0, (answer) => answer, true);
192
+ return this.#lineFallback(options.message, state.styler, state.theme, void 0, (answer) => resolveValidation(options.validate)(answer) === true ? { value: answer } : void 0, (answer) => answer, true);
194
193
  }
195
194
  confirm(options) {
196
195
  const state = createConfirmState(options);
197
196
  if (rawCapable(this.#input)) return this.#drive(state, confirmReduce);
198
- return this.#lineFallback(options.message, options.styler, (answer) => {
197
+ return this.#lineFallback(options.message, state.styler, state.theme, state.hint, (answer) => {
199
198
  const normalized = answer.trim().toLowerCase();
200
199
  if (normalized.length === 0) return { value: options.default ?? false };
201
200
  if (normalized === "y" || normalized === "yes") return { value: true };
@@ -205,7 +204,7 @@ var Terminal = class {
205
204
  select(options) {
206
205
  const state = createSelectState(options);
207
206
  if (rawCapable(this.#input)) return this.#drive(state, selectReduce);
208
- return this.#listFallback(options.message, options.styler, state.choices, FALLBACK_SELECT_HINT, (line) => {
207
+ return this.#listFallback(options.message, state.styler, state.theme, state.choices, state.hint ?? "Enter a number", (line) => {
209
208
  const index = Number.parseInt(line.trim(), 10) - 1;
210
209
  const choice = state.choices[index];
211
210
  return choice === void 0 ? void 0 : { value: choice.value };
@@ -214,7 +213,7 @@ var Terminal = class {
214
213
  checkbox(options) {
215
214
  const state = createCheckboxState(options);
216
215
  if (rawCapable(this.#input)) return this.#drive(state, checkboxReduce);
217
- return this.#listFallback(options.message, options.styler, state.choices, FALLBACK_CHECKBOX_HINT, (line) => {
216
+ return this.#listFallback(options.message, state.styler, state.theme, state.choices, state.hint ?? "Enter numbers separated by commas", (line) => {
218
217
  const indices = line.split(",").map((part) => Number.parseInt(part.trim(), 10) - 1).filter((index) => index >= 0 && index < state.choices.length);
219
218
  if (options.min !== void 0 && indices.length < options.min) return void 0;
220
219
  if (options.max !== void 0 && indices.length > options.max) return void 0;
@@ -224,7 +223,7 @@ var Terminal = class {
224
223
  editor(options) {
225
224
  const state = createEditorState(options);
226
225
  if (rawCapable(this.#input)) return this.#drive(state, editorReduce);
227
- return this.#editorFallback(options);
226
+ return this.#editorFallback(state);
228
227
  }
229
228
  /**
230
229
  * The irreducible Node raw-mode primitive — the ONLY place raw mode is touched. Switches the input
@@ -317,10 +316,9 @@ var Terminal = class {
317
316
  * would SPIN. On EOF the loop accepts the final line through `take` if it passes, else returns the
318
317
  * caller's `eof` fallback (the default / empty value) — the prompt always settles, never spins.
319
318
  */
320
- async #lineFallback(message, styler, take, eof, masked = false) {
321
- const paint = styler ?? createStyler();
319
+ async #lineFallback(message, styler, theme, hint, take, eof, masked = false) {
322
320
  for (;;) {
323
- const { answer, ended } = await this.#question(`${paint.cyan("?")} ${paint.bold(message)} `, masked);
321
+ const { answer, ended } = await this.#question(`${hintedHeader(styler, theme, message, hint)} `, masked);
324
322
  const accepted = take(answer);
325
323
  if (accepted !== void 0) return accepted.value;
326
324
  if (ended) return eof(answer);
@@ -329,17 +327,17 @@ var Terminal = class {
329
327
  /**
330
328
  * Print a numbered choice list, then read one readline line and accept it through `take` — the
331
329
  * non-TTY fallback for `select` / `checkbox`. The list is rendered once; the user types the
332
- * number(s); `take` parses + gates the line, re-prompting until it returns a value.
330
+ * number(s); `take` parses + gates the line, re-prompting until it returns a value. Each index is
331
+ * painted by the `muted` role and each choice name by the `content` role.
333
332
  */
334
- async #listFallback(message, styler, choices, hint, take, eof) {
335
- const paint = styler ?? createStyler();
336
- let list = `${paint.cyan("?")} ${paint.bold(message)}\n`;
333
+ async #listFallback(message, styler, theme, choices, hint, take, eof) {
334
+ let list = `${promptHeader(styler, theme, message)}\n`;
337
335
  choices.forEach((choice, index) => {
338
- list += ` ${paint.dim(`${String(index + 1)})`)} ${choice.name}\n`;
336
+ list += ` ${styler.render(theme.roles.muted, `${String(index + 1)})`)} ${styler.render(theme.roles.content, choice.name)}\n`;
339
337
  });
340
338
  this.#output.write(list);
341
339
  for (;;) {
342
- const { answer, ended } = await this.#question(`${paint.dim(`${hint}:`)} `);
340
+ const { answer, ended } = await this.#question(`${styler.render(theme.roles.hint, `${hint}:`)} `);
343
341
  const accepted = take(answer);
344
342
  if (accepted !== void 0) return accepted.value;
345
343
  if (ended) return eof;
@@ -357,11 +355,10 @@ var Terminal = class {
357
355
  * on the consumed stream — the EOF analogue of the raw-mode editor's re-edit, which a piped stream
358
356
  * cannot offer.
359
357
  */
360
- async #editorFallback(options) {
361
- const paint = options.styler ?? createStyler();
362
- this.#output.write(`${paint.cyan("?")} ${paint.bold(options.message)} ${paint.dim("(EOF to finish)")}\n`);
358
+ async #editorFallback(state) {
359
+ this.#output.write(`${hintedHeader(state.styler, state.theme, state.message, state.hint ?? "(EOF to finish)")}\n`);
363
360
  const text = await this.#lines();
364
- return text.length > 0 ? text : options.default ?? "";
361
+ return text.length > 0 ? text : state.default;
365
362
  }
366
363
  /**
367
364
  * Ask one readline question on the resolved streams and resolve the typed (un-trimmed) line plus