@orkestrel/terminal 0.0.13 → 0.0.15
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.
- package/README.md +11 -16
- package/dist/src/core/index.cjs +458 -281
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +728 -369
- package/dist/src/core/index.d.ts +728 -369
- package/dist/src/core/index.js +443 -264
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +268 -258
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +217 -201
- package/dist/src/server/index.d.ts +217 -201
- package/dist/src/server/index.js +257 -242
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -20
|
@@ -1,27 +1,25 @@
|
|
|
1
|
-
import { FieldChoice } from '@orkestrel/form';
|
|
2
|
-
import { FieldControl } from '@orkestrel/form';
|
|
3
|
-
import { FieldValue } from '@orkestrel/form';
|
|
4
|
-
import { FormField } from '@orkestrel/form';
|
|
5
|
-
import { FormInterface } from '@orkestrel/form';
|
|
6
|
-
import { FormValues } from '@orkestrel/form';
|
|
7
|
-
import { PromptTheme } from '@orkestrel/terminal';
|
|
8
|
-
import { PromptThemeOptions } from '@orkestrel/terminal';
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
/** A carriage return (`\r`, U+000D) — returns the cursor to column 0 so a redraw starts at the line's left edge. */
|
|
14
|
-
export declare const CARRIAGE_RETURN: string;
|
|
1
|
+
import type { FieldChoice } from '@orkestrel/form';
|
|
2
|
+
import type { FieldControl } from '@orkestrel/form';
|
|
3
|
+
import type { FieldValue } from '@orkestrel/form';
|
|
4
|
+
import type { FormField } from '@orkestrel/form';
|
|
5
|
+
import type { FormInterface } from '@orkestrel/form';
|
|
6
|
+
import type { FormValues } from '@orkestrel/form';
|
|
7
|
+
import type { PromptTheme } from '@orkestrel/terminal';
|
|
8
|
+
import type { PromptThemeOptions } from '@orkestrel/terminal';
|
|
9
|
+
import type { StreamTargetInterface } from '@orkestrel/console/server';
|
|
10
|
+
import type { StylerInterface } from '@orkestrel/console';
|
|
11
|
+
import type { TerminalInterface } from '@orkestrel/terminal';
|
|
12
|
+
import type { TextField } from '@orkestrel/form';
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* never leaves orphaned rows
|
|
15
|
+
* Erases from the cursor down to the end of the screen (`ESC[J`) — wipes the whole previous view,
|
|
16
|
+
* which a `select` or `checkbox` can spread over several lines, in one write before the new view is
|
|
17
|
+
* rendered, so a redraw never leaves orphaned rows behind.
|
|
20
18
|
*/
|
|
21
19
|
export declare const CLEAR_DOWN: string;
|
|
22
20
|
|
|
23
21
|
/**
|
|
24
|
-
*
|
|
22
|
+
* Holds the format cue appended to a field's label for each control the walk reads as a line of text —
|
|
25
23
|
* the terminal has no date picker, no color well, and no file chooser, so the accepted shape is
|
|
26
24
|
* stated instead. A control with no entry needs none: `text` and `editor` accept any line,
|
|
27
25
|
* `password` masks one, and `confirm`, `select`, and `checkbox` are answered by key rather than by
|
|
@@ -30,12 +28,12 @@ export declare const CLEAR_DOWN: string;
|
|
|
30
28
|
export declare const CONTROL_HINTS: Readonly<Partial<Record<FieldControl, string>>>;
|
|
31
29
|
|
|
32
30
|
/**
|
|
33
|
-
*
|
|
34
|
-
* the core headless `createPrompt` broker and the SSE `createPromptClient` bridge. Where the
|
|
35
|
-
*
|
|
36
|
-
* form's fields in schema order, drives each control's pure reducer over raw-mode stdin,
|
|
37
|
-
* answer through the form's own `fill`, and submits. It is the only impure part of the
|
|
38
|
-
* stack.
|
|
31
|
+
* Creates the interactive terminal form driver — the local-keyboard arm of the terminal trio,
|
|
32
|
+
* beside the core headless `createPrompt` broker and the SSE `createPromptClient` bridge. Where the
|
|
33
|
+
* broker parks a live form until somebody elsewhere answers it, a `Terminal` answers one here: it
|
|
34
|
+
* walks the form's fields in schema order, drives each control's pure reducer over raw-mode stdin,
|
|
35
|
+
* binds every answer through the form's own `fill`, and submits. It is the only impure part of the
|
|
36
|
+
* terminal stack.
|
|
39
37
|
*
|
|
40
38
|
* @param options - See {@link TerminalOptions}
|
|
41
39
|
* @returns A {@link TerminalInterface} whose `ask` drives one whole form over the resolved streams
|
|
@@ -43,7 +41,7 @@ export declare const CONTROL_HINTS: Readonly<Partial<Record<FieldControl, string
|
|
|
43
41
|
* @remarks
|
|
44
42
|
* - **The form is the unit.** `ask` takes the caller's live form and returns its settled values. The
|
|
45
43
|
* returned promise is that form's own `answer`, so a caller holding the form can await either one.
|
|
46
|
-
* - **Every control renders.**
|
|
44
|
+
* - **Every control renders.** The line-read controls are read as a line of text with their format cue,
|
|
47
45
|
* `password` masks, `confirm` takes a key, `editor` takes a block, `select` and `checkbox` drive a
|
|
48
46
|
* list, and an open `select` accepts a value its list does not offer.
|
|
49
47
|
* - **The form decides.** A blank answer binds as absence, the form evaluates, and a refusal
|
|
@@ -55,90 +53,82 @@ export declare const CONTROL_HINTS: Readonly<Partial<Record<FieldControl, string
|
|
|
55
53
|
* - **Non-TTY fallback.** A piped stream cannot enter raw mode, so the same walk runs over
|
|
56
54
|
* `node:readline` line input.
|
|
57
55
|
*
|
|
58
|
-
* @example
|
|
56
|
+
* @example Ask one form at this keyboard
|
|
59
57
|
* ```ts
|
|
60
58
|
* import { createForm } from '@orkestrel/form'
|
|
59
|
+
* import { isTerminalError } from '@orkestrel/terminal'
|
|
61
60
|
* import { createTerminal } from '@orkestrel/terminal/server'
|
|
62
61
|
*
|
|
63
|
-
* const terminal = createTerminal()
|
|
64
|
-
* const
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
62
|
+
* const terminal = createTerminal() // process.stdin / process.stdout by default
|
|
63
|
+
* const form = createForm({
|
|
64
|
+
* label: 'Sign up',
|
|
65
|
+
* fields: [
|
|
66
|
+
* { control: 'text', name: 'name', label: 'Your name', rule: { required: true, minimum: 2 } },
|
|
67
|
+
* { control: 'text', name: 'email', label: 'Email', rule: { required: true, email: true } },
|
|
68
|
+
* { control: 'password', name: 'token', label: 'Token' },
|
|
69
|
+
* { control: 'confirm', name: 'terms', label: 'Accept the terms', rule: { required: true } },
|
|
70
|
+
* {
|
|
71
|
+
* control: 'select',
|
|
72
|
+
* name: 'role',
|
|
73
|
+
* label: 'Role',
|
|
74
|
+
* choices: [
|
|
75
|
+
* { value: 'admin', label: 'Admin' },
|
|
76
|
+
* { value: 'viewer', label: 'Viewer', help: 'read-only' },
|
|
77
|
+
* ],
|
|
78
|
+
* },
|
|
79
|
+
* ],
|
|
80
|
+
* })
|
|
81
|
+
*
|
|
82
|
+
* try {
|
|
83
|
+
* const values = await terminal.ask(form)
|
|
84
|
+
* deploy(values)
|
|
85
|
+
* } catch (error) {
|
|
86
|
+
* // Ctrl-c: the walk ended, and the form is still `editing` for whoever owns it.
|
|
87
|
+
* if (isTerminalError(error) && error.code === 'CANCEL') form.destroy()
|
|
88
|
+
* }
|
|
73
89
|
* ```
|
|
74
90
|
*/
|
|
75
91
|
export declare function createTerminal(options?: TerminalOptions): TerminalInterface;
|
|
76
92
|
|
|
77
|
-
/** The Control Sequence Introducer (`ESC[`) — the prefix of every cursor / erase sequence. */
|
|
78
|
-
export declare const CSI: string;
|
|
79
|
-
|
|
80
93
|
/**
|
|
81
|
-
*
|
|
82
|
-
* interpolates the `{count}` placeholder with the
|
|
83
|
-
* the count stays out of the constant.
|
|
94
|
+
* Holds the cursor-up sequence template (`ESC[{count}A`) —
|
|
95
|
+
* {@link import('./helpers.js').renderCursorUp} interpolates the `{count}` placeholder with the
|
|
96
|
+
* number of lines to climb. Kept as a template so the count stays out of the constant.
|
|
84
97
|
*/
|
|
85
98
|
export declare const CSI_UP: string;
|
|
86
99
|
|
|
87
100
|
/**
|
|
88
|
-
*
|
|
101
|
+
* Hides the cursor (`ESC[?25l`) — written before the driver starts redrawing a prompt so the cursor
|
|
89
102
|
* does not flicker across the view during an in-place re-render; paired with {@link CURSOR_SHOW}.
|
|
90
103
|
*/
|
|
91
104
|
export declare const CURSOR_HIDE: string;
|
|
92
105
|
|
|
93
|
-
/**
|
|
106
|
+
/** Shows the cursor (`ESC[?25h`) — restores the cursor after a prompt resolves / cancels (the {@link CURSOR_HIDE} pair). */
|
|
94
107
|
export declare const CURSOR_SHOW: string;
|
|
95
108
|
|
|
96
|
-
/**
|
|
97
|
-
* The choices a `select` or `checkbox` field SHOWS but refuses — the complement of
|
|
98
|
-
* {@link enabledChoices}, rendered by {@link unavailableLine} above the list so a reader sees why a
|
|
99
|
-
* declared choice is missing from it.
|
|
100
|
-
*
|
|
101
|
-
* @param choices - The field's declared choices
|
|
102
|
-
* @returns The refused choices, in declared order
|
|
103
|
-
*/
|
|
104
|
-
export declare function disabledChoices(choices: readonly FieldChoice[]): readonly FieldChoice[];
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* The choices a `select` or `checkbox` field actually OFFERS — the form refuses a disabled choice's
|
|
108
|
-
* value at every door, including a fill, so the walk never puts one in front of the cursor. Pair
|
|
109
|
-
* with {@link disabledChoices} to tell the reader what was withheld.
|
|
110
|
-
*
|
|
111
|
-
* @param choices - The field's declared choices
|
|
112
|
-
* @returns The choices the walk offers, in declared order
|
|
113
|
-
*/
|
|
114
|
-
export declare function enabledChoices(choices: readonly FieldChoice[]): readonly FieldChoice[];
|
|
115
|
-
|
|
116
|
-
/** The Escape byte (ESC, U+001B) — the lead byte of every CSI cursor-control sequence below. */
|
|
117
|
-
export declare const ESCAPE: string;
|
|
118
|
-
|
|
119
|
-
/** The comma-separated multi-select hint the non-TTY `checkbox` fallback shows (the user types one or more numbers). */
|
|
109
|
+
/** Holds the comma-separated multi-select hint the non-TTY `checkbox` fallback shows (the user types one or more numbers). */
|
|
120
110
|
export declare const FALLBACK_CHECKBOX_HINT = "Enter numbers separated by commas";
|
|
121
111
|
|
|
122
|
-
/**
|
|
112
|
+
/** Holds the hint the non-TTY `confirm` fallback shows — a piped stream sends a whole line, so the answer is typed rather than pressed. */
|
|
123
113
|
export declare const FALLBACK_CONFIRM_HINT = "(y/n)";
|
|
124
114
|
|
|
125
|
-
/**
|
|
115
|
+
/** Holds the hint the non-TTY `editor` fallback shows — a piped stream has no ctrl-d, so end of input finishes the block. */
|
|
126
116
|
export declare const FALLBACK_EDITOR_HINT = "(end of input finishes)";
|
|
127
117
|
|
|
128
118
|
/**
|
|
129
|
-
*
|
|
119
|
+
* Holds the numbered-list prompt the non-TTY {@link import('./Terminal.js').Terminal} `select` fallback
|
|
130
120
|
* appends — a piped (non-terminal) stream cannot navigate with arrow keys, so the choices are
|
|
131
121
|
* printed numbered and the user types one number on a single readline line.
|
|
132
122
|
*/
|
|
133
123
|
export declare const FALLBACK_SELECT_HINT = "Enter a number";
|
|
134
124
|
|
|
135
125
|
/**
|
|
136
|
-
*
|
|
137
|
-
* takes — `text` itself, and the
|
|
138
|
-
* `
|
|
139
|
-
*
|
|
140
|
-
* projection carries no rule, because the
|
|
141
|
-
* binds; it exists only so one reducer
|
|
126
|
+
* Projects any field the walk reads as a line of text into the {@link TextField} the text reducer
|
|
127
|
+
* takes — `text` itself, and the controls a terminal has no widget for: `number`, `date`, `time`,
|
|
128
|
+
* `datetime`, `color`, and one `file` entry. The label carries that control's format cue from
|
|
129
|
+
* {@link CONTROL_HINTS}, and a declared `default` becomes the line a bare return submits. The
|
|
130
|
+
* projection carries no rule, because the authoritative form still evaluates the answer this line
|
|
131
|
+
* binds; it exists only so one reducer covers every one of them.
|
|
142
132
|
*
|
|
143
133
|
* @param field - The field being read
|
|
144
134
|
* @returns The text field the reducer renders for it
|
|
@@ -151,14 +141,31 @@ export declare const FALLBACK_SELECT_HINT = "Enter a number";
|
|
|
151
141
|
*/
|
|
152
142
|
export declare function fieldToText(field: FormField): TextField;
|
|
153
143
|
|
|
154
|
-
/**
|
|
144
|
+
/** Holds the instruction a `file` field with `multiple` shows before its entries — one path per line, and a blank line ends the list. */
|
|
155
145
|
export declare const FILE_HINT = "One path per line, blank to finish";
|
|
156
146
|
|
|
157
|
-
/**
|
|
158
|
-
|
|
147
|
+
/**
|
|
148
|
+
* Returns the choices a `select` or `checkbox` field shows but refuses — the complement of
|
|
149
|
+
* {@link filterEnabled}, rendered by {@link renderUnavailableLine} above the list so a reader sees
|
|
150
|
+
* why a declared choice is missing from it.
|
|
151
|
+
*
|
|
152
|
+
* @param choices - The field's declared choices
|
|
153
|
+
* @returns The refused choices, in declared order
|
|
154
|
+
*/
|
|
155
|
+
export declare function filterDisabled(choices: readonly FieldChoice[]): readonly FieldChoice[];
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Returns the choices a `select` or `checkbox` field actually offers — the form refuses a disabled
|
|
159
|
+
* choice's value at every door, including a fill, so the walk never puts one in front of the
|
|
160
|
+
* cursor. Pair with {@link filterDisabled} to tell the reader what was withheld.
|
|
161
|
+
*
|
|
162
|
+
* @param choices - The field's declared choices
|
|
163
|
+
* @returns The choices the walk offers, in declared order
|
|
164
|
+
*/
|
|
165
|
+
export declare function filterEnabled(choices: readonly FieldChoice[]): readonly FieldChoice[];
|
|
159
166
|
|
|
160
167
|
/**
|
|
161
|
-
*
|
|
168
|
+
* Represents the minimal input-stream shape the driver reads — exactly the slice of a Node `tty.ReadStream` /
|
|
162
169
|
* `process.stdin` it touches, and no more. A {@link TerminalOptions} `input` is narrowed to this
|
|
163
170
|
* through {@link import('./helpers.js').isInputStream}, never an assertion, so a test drives a whole
|
|
164
171
|
* form with a hand-built fake stream that emits scripted key chunks, never touches the real
|
|
@@ -167,7 +174,7 @@ export declare function groupHeader(styler: StylerInterface, theme: PromptTheme,
|
|
|
167
174
|
* @remarks
|
|
168
175
|
* - `on(event, listener)` / `off(event, listener)` — subscribe / unsubscribe a `'data'` chunk
|
|
169
176
|
* listener (the irreducible event seam; a `Buffer`, string, or `Uint8Array` chunk arrives). The
|
|
170
|
-
*
|
|
177
|
+
* required methods.
|
|
171
178
|
* - `setRawMode(mode)` — switch the TTY in and out of raw mode (each keypress delivered
|
|
172
179
|
* immediately, no line buffering, no echo). Present on a real `tty.ReadStream`; ABSENT on a
|
|
173
180
|
* piped, non-TTY stream, and its absence (or `isTTY !== true`) selects the
|
|
@@ -177,195 +184,202 @@ export declare function groupHeader(styler: StylerInterface, theme: PromptTheme,
|
|
|
177
184
|
* - `isTTY` — `true` on a real terminal, absent or `false` when piped to a file or another process.
|
|
178
185
|
*/
|
|
179
186
|
export declare interface InputStreamInterface {
|
|
187
|
+
/** Subscribes a `'data'` chunk listener — the irreducible event seam. */
|
|
180
188
|
on(event: 'data', listener: (chunk: string | Uint8Array) => void): void;
|
|
189
|
+
/** Unsubscribes that listener. The driver always pairs it, so no listener leaks. */
|
|
181
190
|
off(event: 'data', listener: (chunk: string | Uint8Array) => void): void;
|
|
191
|
+
/** Switches the TTY in and out of raw mode. Absent on a piped stream. */
|
|
182
192
|
setRawMode?(mode: boolean): void;
|
|
193
|
+
/** Starts the flow of `'data'` events. */
|
|
183
194
|
resume?(): void;
|
|
195
|
+
/** Stops it again on cleanup. */
|
|
184
196
|
pause?(): void;
|
|
197
|
+
/** Reports whether the stream is a real terminal; absent or false when piped. */
|
|
185
198
|
readonly isTTY?: boolean;
|
|
186
199
|
}
|
|
187
200
|
|
|
188
201
|
/**
|
|
189
|
-
*
|
|
190
|
-
* `'data'` subscription methods. A total type guard
|
|
191
|
-
*
|
|
192
|
-
* or a fake TTY a test injects) to the exact slice the driver reads
|
|
202
|
+
* Checks whether `value` is a usable {@link InputStreamInterface} — a record with callable `on` /
|
|
203
|
+
* `off` `'data'` subscription methods. A total type guard: it never throws and returns `false` for
|
|
204
|
+
* anything off-shape, so it narrows the one unavoidable input boundary (the real `process.stdin`,
|
|
205
|
+
* or a fake TTY a test injects) to the exact slice the driver reads, never an assertion.
|
|
193
206
|
*
|
|
194
207
|
* @remarks
|
|
195
208
|
* Only `on` / `off` are required (the irreducible event seam); `setRawMode` / `resume` / `pause` /
|
|
196
209
|
* `isTTY` are optional on {@link InputStreamInterface}, so their absence does not disqualify a stream
|
|
197
|
-
* — a piped, non-TTY stream is still a valid input,
|
|
210
|
+
* — a piped, non-TTY stream is still a valid input, one the driver reads through the readline
|
|
198
211
|
* fallback rather than raw mode.
|
|
199
212
|
*
|
|
200
213
|
* @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
|
|
201
|
-
* @returns
|
|
214
|
+
* @returns True if `value` has callable `on` and `off`; false otherwise
|
|
202
215
|
*/
|
|
203
216
|
export declare function isInputStream(value: unknown): value is InputStreamInterface;
|
|
204
217
|
|
|
205
218
|
/**
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
* @returns `true` when `value` has a callable `write`
|
|
213
|
-
*/
|
|
214
|
-
export declare function isOutputStream(value: unknown): value is OutputStreamInterface;
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard (AGENTS §14)
|
|
218
|
-
* checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface` requires
|
|
219
|
-
* as its `input`. The non-TTY fallback narrows the resolved input stream through this before handing
|
|
220
|
-
* it to readline (never an `as`), so a real piped `process.stdin` (or a `PassThrough` a test injects)
|
|
221
|
-
* crosses into the readline boundary honestly. Never throws; returns `false` for a minimal fake that
|
|
222
|
-
* isn't a full readable.
|
|
219
|
+
* Checks whether `value` is a Node {@link NodeJS.ReadableStream} — a total structural guard
|
|
220
|
+
* checking for the callable `read` / `pipe` / `on` that `node:readline`'s `createInterface`
|
|
221
|
+
* requires as its `input`. The non-TTY fallback narrows the resolved input stream through this
|
|
222
|
+
* before handing it to readline, never through an assertion, so a real piped `process.stdin` (or a
|
|
223
|
+
* `PassThrough` a test injects) crosses into the readline boundary honestly. Never throws; returns
|
|
224
|
+
* `false` for a minimal fake that isn't a full readable.
|
|
223
225
|
*
|
|
224
226
|
* @param value - The resolved input stream (or any value crossing the boundary)
|
|
225
|
-
* @returns
|
|
227
|
+
* @returns True if `value` has the readable methods readline needs; false otherwise
|
|
226
228
|
*/
|
|
227
229
|
export declare function isReadable(value: unknown): value is NodeJS.ReadableStream;
|
|
228
230
|
|
|
229
|
-
/** A line feed (`\n`, U+000A) — the line terminator the driver writes after the final committed prompt view. */
|
|
230
|
-
export declare const LINE_FEED: string;
|
|
231
|
-
|
|
232
231
|
/**
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
* re-render: the driver records the line count of the view it
|
|
236
|
-
* far up to move the cursor before overwriting. Total; an empty string is one
|
|
232
|
+
* Counts the terminal lines a rendered prompt `view` occupies — one more than its newline count, so
|
|
233
|
+
* a view with no newline is a single line and a view with N newlines spans N+1 lines. The basis of
|
|
234
|
+
* the in-place re-render: the driver records the line count of the view it wrote so the next redraw
|
|
235
|
+
* knows how far up to move the cursor before overwriting. Total; an empty string is one empty line.
|
|
237
236
|
*
|
|
238
237
|
* @param view - The rendered (possibly multi-line, possibly ANSI-styled) view string
|
|
239
238
|
* @returns The number of lines the view spans (always at least 1)
|
|
240
239
|
*/
|
|
241
240
|
export declare function lineCount(view: string): number;
|
|
242
241
|
|
|
243
|
-
/**
|
|
242
|
+
/** Holds the mark on a locked field's line — the walk renders its value and moves on, because the form refuses an edit there. */
|
|
244
243
|
export declare const LOCKED_MARK = "(locked)";
|
|
245
244
|
|
|
246
245
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
246
|
+
* Returns the full reposition-and-clear prefix to write before re-rendering a prompt view in place
|
|
247
|
+
* — given the line count of the previous view, it moves the cursor up over those lines, returns it
|
|
248
|
+
* to column 0, and erases everything from there to the end of the screen, so the next view is drawn
|
|
249
|
+
* on a clean region and a taller previous view leaves no orphaned rows. Pure; the driver writes
|
|
250
|
+
* this immediately followed by the new view.
|
|
250
251
|
*
|
|
251
|
-
* @
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
252
|
+
* @remarks
|
|
253
|
+
* For the first render `previousLines` is `1` (the cursor sits on the line the prompt opened on) so
|
|
254
|
+
* the prefix is a carriage return + clear-down — the prompt draws from the current line. For a
|
|
255
|
+
* subsequent render it climbs `previousLines - 1` lines (the cursor is on the last line of the prior
|
|
256
|
+
* view) before clearing. Keeping the math here (not in the driver) makes the re-render unit-testable
|
|
257
|
+
* without a real terminal.
|
|
258
|
+
*
|
|
259
|
+
* @param previousLines - The line count of the view on screen (from {@link lineCount})
|
|
260
|
+
* @returns The control-sequence prefix to write before the new view
|
|
261
|
+
*/
|
|
262
|
+
export declare function redrawPrefix(previousLines: number): string;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* States what a field is told when the walk read an answer the control cannot hold — a word typed into a
|
|
266
|
+
* `number`, an off-list value typed into an open `select` whose choice is refused. The value binds
|
|
267
|
+
* as absence and this message is invalidated onto the field, so the walk re-asks it with the reason
|
|
268
|
+
* on screen.
|
|
256
269
|
*/
|
|
257
|
-
export declare
|
|
270
|
+
export declare const REFUSAL_MESSAGE = "Enter a value this field accepts";
|
|
258
271
|
|
|
259
272
|
/**
|
|
260
|
-
*
|
|
261
|
-
* empty string when `count` is zero or negative
|
|
262
|
-
* The pure step the in-place re-render uses to climb back over the previous view
|
|
263
|
-
* Total.
|
|
273
|
+
* Returns the cursor-up control sequence that moves the cursor up `count` lines (`ESC[{count}A`),
|
|
274
|
+
* or the empty string when `count` is zero or negative, because no movement is needed and `ESC[0A`
|
|
275
|
+
* is a wasted write. The pure step the in-place re-render uses to climb back over the previous view
|
|
276
|
+
* before clearing it. Total.
|
|
264
277
|
*
|
|
265
278
|
* @param count - How many lines to move the cursor up
|
|
266
279
|
* @returns The `ESC[{count}A` sequence, or `''` when `count <= 0`
|
|
267
280
|
*/
|
|
268
|
-
export declare function
|
|
281
|
+
export declare function renderCursorUp(count: number): string;
|
|
269
282
|
|
|
270
283
|
/**
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* choice, with no trailing newline.
|
|
284
|
+
* Renders the section header the walk writes when it enters a new field group, painted by the
|
|
285
|
+
* `message` role.
|
|
274
286
|
*
|
|
275
287
|
* @param styler - The console styler that renders each role
|
|
276
288
|
* @param theme - The resolved prompt theme
|
|
277
|
-
* @param
|
|
278
|
-
* @returns The rendered
|
|
289
|
+
* @param label - The group's declared label, falling back to its own name
|
|
290
|
+
* @returns The rendered section header
|
|
279
291
|
*/
|
|
280
|
-
export declare function
|
|
292
|
+
export declare function renderGroupHeader(styler: StylerInterface, theme: PromptTheme, label: string): string;
|
|
281
293
|
|
|
282
294
|
/**
|
|
283
|
-
*
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* the walk renders and asserts the rendered content with the ANSI stripped.
|
|
295
|
+
* Renders the read-only line a locked field shows — its label, the {@link LOCKED_MARK}, and the
|
|
296
|
+
* answer the form already holds. The walk writes this instead of a prompt, because the field is
|
|
297
|
+
* still validated and still submitted but must not be edited here.
|
|
287
298
|
*
|
|
288
|
-
* @
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
* it; the styler already decided color.
|
|
299
|
+
* @param styler - The console styler that renders each role
|
|
300
|
+
* @param theme - The resolved prompt theme
|
|
301
|
+
* @param label - The field's label
|
|
302
|
+
* @param value - The held answer, from {@link valueToText}
|
|
303
|
+
* @returns The rendered line, with no trailing space when there is nothing to show
|
|
294
304
|
*/
|
|
295
|
-
export declare
|
|
296
|
-
write(text: string): boolean | void;
|
|
297
|
-
readonly isTTY?: boolean;
|
|
298
|
-
}
|
|
305
|
+
export declare function renderLockedLine(styler: StylerInterface, theme: PromptTheme, label: string, value: string): string;
|
|
299
306
|
|
|
300
307
|
/**
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* `node:readline` line-input fallback (a piped / non-terminal stream cannot enter raw mode). Total —
|
|
305
|
-
* never throws.
|
|
308
|
+
* Renders the numbered choice list the non-TTY fallback prints — a piped stream cannot navigate
|
|
309
|
+
* with arrow keys, so each offered choice is printed with the number the reader types back. One
|
|
310
|
+
* line per choice, with no trailing newline.
|
|
306
311
|
*
|
|
307
|
-
* @param
|
|
308
|
-
* @
|
|
312
|
+
* @param styler - The console styler that renders each role
|
|
313
|
+
* @param theme - The resolved prompt theme
|
|
314
|
+
* @param choices - The choices the walk offers, from {@link filterEnabled}
|
|
315
|
+
* @returns The rendered list
|
|
309
316
|
*/
|
|
310
|
-
export declare function
|
|
317
|
+
export declare function renderNumberedList(styler: StylerInterface, theme: PromptTheme, choices: readonly FieldChoice[]): string;
|
|
311
318
|
|
|
312
319
|
/**
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* 0, and erases everything from there to the end of the screen, so the next view is drawn on a clean
|
|
316
|
-
* region (no orphaned rows from a taller previous view). Pure; the driver writes this immediately
|
|
317
|
-
* followed by the new view.
|
|
318
|
-
*
|
|
319
|
-
* @remarks
|
|
320
|
-
* For the FIRST render `previousLines` is `1` (the cursor sits on the line the prompt opened on) so
|
|
321
|
-
* the prefix is just a carriage return + clear-down — the prompt draws from the current line. For a
|
|
322
|
-
* subsequent render it climbs `previousLines - 1` lines (the cursor is on the LAST line of the prior
|
|
323
|
-
* view) before clearing. Keeping the math here (not in the driver) makes the re-render unit-testable
|
|
324
|
-
* without a real terminal.
|
|
320
|
+
* Renders the line listing an open select's offered values above its text prompt — a suggestion
|
|
321
|
+
* list, because an open select admits an answer the list does not offer.
|
|
325
322
|
*
|
|
326
|
-
* @param
|
|
327
|
-
* @
|
|
323
|
+
* @param styler - The console styler that renders each role
|
|
324
|
+
* @param theme - The resolved prompt theme
|
|
325
|
+
* @param choices - The choices the open select offers, from {@link filterEnabled}
|
|
326
|
+
* @returns The rendered suggestion line
|
|
328
327
|
*/
|
|
329
|
-
export declare function
|
|
328
|
+
export declare function renderSuggestionLine(styler: StylerInterface, theme: PromptTheme, choices: readonly FieldChoice[]): string;
|
|
330
329
|
|
|
331
330
|
/**
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
*
|
|
335
|
-
*
|
|
331
|
+
* Renders the line naming the choices a field shows but refuses, written above the list the walk
|
|
332
|
+
* drives.
|
|
333
|
+
*
|
|
334
|
+
* @param styler - The console styler that renders each role
|
|
335
|
+
* @param theme - The resolved prompt theme
|
|
336
|
+
* @param choices - The refused choices, from {@link filterDisabled}
|
|
337
|
+
* @returns The rendered unavailable line
|
|
336
338
|
*/
|
|
337
|
-
export declare
|
|
339
|
+
export declare function renderUnavailableLine(styler: StylerInterface, theme: PromptTheme, choices: readonly FieldChoice[]): string;
|
|
338
340
|
|
|
339
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* Holds the lead on the line listing an open `select`'s offered values, which a typed answer can
|
|
343
|
+
* ignore.
|
|
344
|
+
*/
|
|
340
345
|
export declare const SUGGESTION_LEAD = "Suggestions";
|
|
341
346
|
|
|
342
|
-
/**
|
|
343
|
-
|
|
347
|
+
/**
|
|
348
|
+
* Checks whether an input stream can be driven in raw mode — it reports `isTTY === true` and
|
|
349
|
+
* exposes a callable `setRawMode`. The {@link import('./Terminal.js').Terminal} probes this to
|
|
350
|
+
* choose its path: true selects the interactive raw-mode fields, with arrow-key navigation and a
|
|
351
|
+
* live re-render; false selects the `node:readline` line-input fallback, because a piped or
|
|
352
|
+
* non-terminal stream cannot enter raw mode. Total — never throws.
|
|
353
|
+
*
|
|
354
|
+
* @param input - The resolved {@link InputStreamInterface}
|
|
355
|
+
* @returns True if the stream is a TTY with `setRawMode`; false otherwise
|
|
356
|
+
*/
|
|
357
|
+
export declare function supportsRawMode(input: InputStreamInterface): boolean;
|
|
344
358
|
|
|
345
359
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* the form it is given, and this class owns only raw mode, the cursor, and
|
|
360
|
+
* Implements {@link TerminalInterface} for a human at this machine's keyboard — the interactive
|
|
361
|
+
* form driver, and the only impure part of the terminal stack. {@link ask} walks one form's fields
|
|
362
|
+
* in schema order, feeds raw-mode stdin bytes through `parseKey` into the matching pure reducer,
|
|
363
|
+
* renders each returned view in place, binds every answer through the form's own `fill`, and
|
|
364
|
+
* re-asks what the form refused. It owns no form logic: the schema, the rules, the values, and the
|
|
365
|
+
* settlement all belong to the form it is given, and this class owns only raw mode, the cursor, and
|
|
366
|
+
* the re-render.
|
|
352
367
|
*
|
|
353
368
|
* @remarks
|
|
354
369
|
* See {@link TerminalInterface} for the driving contract. The walk itself:
|
|
355
370
|
*
|
|
356
|
-
* - **
|
|
371
|
+
* - **Controls and reducers.** `text`, `number`, `date`, `time`, `datetime`, `color`, and
|
|
357
372
|
* each `file` entry are read as one line of text through {@link fieldToText}, which appends that
|
|
358
373
|
* control's format cue to the label. `password`, `confirm`, `editor`, `select`, and `checkbox`
|
|
359
374
|
* each drive their own reducer. An open `select` is a suggestion list plus a typed line, because
|
|
360
375
|
* `open` means the answer need not come from the list.
|
|
361
376
|
* - **The binding projects through `matchesAnswer`.** Every answer is filled as
|
|
362
377
|
* `fill(name, matchesAnswer(value) ? value : undefined)` after `parseValue` has coerced it to the
|
|
363
|
-
* control's own shape, so a bare return on a field with no default binds as
|
|
378
|
+
* control's own shape, so a bare return on a field with no default binds as absence and the
|
|
364
379
|
* form's `required` rule refuses it. A typed answer the control cannot hold binds as absence and
|
|
365
380
|
* invalidates the field, so the walk asks again with the reason on screen.
|
|
366
|
-
* - **Visibility is honored.** A `hidden` field and a field
|
|
367
|
-
*
|
|
368
|
-
* header.
|
|
381
|
+
* - **Visibility is honored.** A `hidden` field and a field in `form.disabled` are skipped; a
|
|
382
|
+
* `locked` field renders read-only; entering a new group writes its label as a section header.
|
|
369
383
|
* - **Refusal re-asks.** After the walk the form is submitted. A refusal re-walks only the erroring
|
|
370
384
|
* fields the walk can edit and submits again. When every erroring field is one the walk cannot
|
|
371
385
|
* edit — hidden, locked, or disabled — the form is abandoned instead, because asking again could
|
|
@@ -382,7 +396,7 @@ export declare class Terminal implements TerminalInterface {
|
|
|
382
396
|
}
|
|
383
397
|
|
|
384
398
|
/**
|
|
385
|
-
*
|
|
399
|
+
* Configures {@link import('./factories.js').createTerminal} — every member optional, so a bare
|
|
386
400
|
* `createTerminal()` walks a form over the real `process.stdin` / `process.stdout` with the default
|
|
387
401
|
* theme.
|
|
388
402
|
*
|
|
@@ -392,27 +406,29 @@ export declare class Terminal implements TerminalInterface {
|
|
|
392
406
|
* {@link import('./helpers.js').isInputStream}, so a test injects a fake TTY that emits scripted
|
|
393
407
|
* `'data'` chunks. A stream that is not a TTY falls back to `node:readline` line input.
|
|
394
408
|
* - `output` — the stream each view is rendered to; defaults to `process.stdout`. Any
|
|
395
|
-
* {@link
|
|
396
|
-
* {@link import('
|
|
409
|
+
* {@link import('@orkestrel/console/server').StreamTargetInterface}-shaped stream is accepted,
|
|
410
|
+
* resolved through {@link import('@orkestrel/console/server').isStreamTarget}, so a test records
|
|
411
|
+
* the rendered output. That is the console module's own boundary shape, so one target drives a
|
|
412
|
+
* console sink and this driver alike.
|
|
397
413
|
* - `theme` — the glyphs and role styles every rendered line is painted with, merged over
|
|
398
|
-
* {@link import('@
|
|
399
|
-
* {@link import('@
|
|
414
|
+
* {@link import('@orkestrel/terminal').DEFAULT_PROMPT_THEME} leaf by leaf by
|
|
415
|
+
* {@link import('@orkestrel/terminal').createPromptTheme}. Supplying one icon or one role leaves every
|
|
400
416
|
* other slot at its default.
|
|
401
417
|
*/
|
|
402
418
|
export declare interface TerminalOptions {
|
|
403
419
|
readonly input?: InputStreamInterface;
|
|
404
|
-
readonly output?:
|
|
420
|
+
readonly output?: StreamTargetInterface;
|
|
405
421
|
readonly theme?: PromptThemeOptions;
|
|
406
422
|
}
|
|
407
423
|
|
|
408
|
-
/**
|
|
424
|
+
/**
|
|
425
|
+
* Holds the lead on the line listing the choices a `select` or `checkbox` shows but refuses, so a
|
|
426
|
+
* reader sees why one is missing from the list it heads.
|
|
427
|
+
*/
|
|
409
428
|
export declare const UNAVAILABLE_LEAD = "Unavailable";
|
|
410
429
|
|
|
411
|
-
/** The line naming the choices a field shows but refuses, written above the list the walk drives. */
|
|
412
|
-
export declare function unavailableLine(styler: StylerInterface, theme: PromptTheme, choices: readonly FieldChoice[]): string;
|
|
413
|
-
|
|
414
430
|
/**
|
|
415
|
-
*
|
|
431
|
+
* Projects one held answer into the text a read-only line shows — a scalar as itself, a boolean as
|
|
416
432
|
* `yes` / `no` (the word the confirm reducer commits), and a list joined by commas. Absence renders
|
|
417
433
|
* as nothing, because a locked field nobody has answered has nothing to show.
|
|
418
434
|
*
|