@orkestrel/terminal 0.0.7 → 0.0.9

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,139 +1,71 @@
1
+ import { CheckboxField } from '@orkestrel/form';
2
+ import { ConfirmField } from '@orkestrel/form';
1
3
  import { DriverInterface } from '@orkestrel/database';
4
+ import { EditorField } from '@orkestrel/form';
2
5
  import { EmitterErrorHandler } from '@orkestrel/emitter';
3
6
  import { EmitterHooks } from '@orkestrel/emitter';
4
7
  import { EmitterInterface } from '@orkestrel/emitter';
8
+ import { FieldChoice } from '@orkestrel/form';
9
+ import { FieldError } from '@orkestrel/form';
10
+ import { FormInterface } from '@orkestrel/form';
11
+ import { FormSchema } from '@orkestrel/form';
12
+ import { FormValues } from '@orkestrel/form';
5
13
  import { Guard } from '@orkestrel/contract';
14
+ import { JSONRecord } from '@orkestrel/contract';
15
+ import { PasswordField } from '@orkestrel/form';
16
+ import { Result } from '@orkestrel/contract';
17
+ import { SelectField } from '@orkestrel/form';
18
+ import { Style } from '@orkestrel/console';
6
19
  import { StylerInterface } from '@orkestrel/console';
7
20
  import { TableInterface } from '@orkestrel/database';
21
+ import { TextField } from '@orkestrel/form';
8
22
 
9
23
  /** The `Accept` header value that opens the broker's SSE stream. */
10
24
  export declare const ACCEPT_EVENT_STREAM = "text/event-stream";
11
25
 
12
- /** Matches an alphanumeric string (letters and digits only). The `alphanumeric` rule tests against this. */
13
- export declare const ALPHANUMERIC_PATTERN: RegExp;
14
-
15
- /** The rejection reason a bare {@link PromptInterface.answer} returns — `'unknown'` (no such parked prompt) or `'rejected'` (failed validation / type-check). */
16
- export declare type AnswerError = 'unknown' | 'rejected';
17
-
18
- /** The outcome of a bare {@link PromptInterface.answer} call — the accepted `value` on success, else the {@link AnswerError}. */
19
- export declare type AnswerResult = {
20
- readonly success: true;
21
- readonly value: unknown;
26
+ /**
27
+ * Why {@link PromptInterface.answer} refused. Names its axis with `reason`.
28
+ *
29
+ * @remarks
30
+ * - `unknown` — no form is parked under that id, or the one that was has already settled.
31
+ * - `rejected` — the authoritative form refused the values, and `errors` is exactly what it
32
+ * reported. A client seeds its local form with the values it sent, applies each failure through
33
+ * the form's `invalidate`, and asks again; the parked form stays parked until it accepts or
34
+ * expires. This is the retry loop that makes a server-side `custom` rule enforceable, because
35
+ * that rule never crossed the wire and the client could not have checked it.
36
+ */
37
+ export declare type AnswerError = {
38
+ readonly reason: 'unknown';
22
39
  } | {
23
- readonly success: false;
24
- readonly error: AnswerError;
40
+ readonly reason: 'rejected';
41
+ readonly errors: readonly FieldError[];
25
42
  };
26
43
 
27
- /**
28
- * Append a rule-backed {@link Validator} to `validators` when the rule is enabled — a `false` /
29
- * `undefined` `check` is skipped, a function `check` is added verbatim (the custom override), and
30
- * a primitive `check` is wrapped via {@link buildRuleValidator}. Mutates `validators` in place.
31
- */
32
- export declare function appendRule(validators: Validator[], rule: string, check: boolean | number | string | Validator | undefined): void;
33
-
34
44
  /** Backspace (BS, U+0008) — Ctrl+H / some terminals' Backspace. */
35
45
  export declare const BACKSPACE: string;
36
46
 
37
- /** Wrap a named rule + its primitive check into a {@link Validator} (returns `true` or the rule's message). */
38
- export declare function buildRuleValidator(rule: string, check: boolean | number | string): Validator;
39
-
40
- /** A choice item in a {@link CheckboxOptions} prompt — a {@link PromptChoice} plus an optional initial `checked` state. */
41
- export declare interface CheckboxChoice {
42
- readonly name: string;
43
- readonly value: string;
44
- readonly description?: string;
45
- readonly checked?: boolean;
46
- }
47
-
48
- /**
49
- * Options for a multi-selection {@link import('./helpers.js').checkboxReduce} prompt.
50
- *
51
- * @remarks
52
- * `choices` accepts bare strings or full {@link CheckboxChoice} objects (with an optional
53
- * initial `checked`); {@link import('./helpers.js').createCheckboxState} normalizes them.
54
- * `min` / `max` gate submission — a submit with fewer than `min` or more than `max` selected
55
- * is rejected (the prompt stays active with the reason in the view).
56
- */
57
- export declare interface CheckboxOptions {
58
- readonly message: string;
59
- readonly choices: ReadonlyArray<string | CheckboxChoice>;
60
- readonly min?: number;
61
- readonly max?: number;
62
- readonly styler?: StylerInterface;
63
- }
64
-
65
47
  /**
66
48
  * Advance a checkbox prompt by one {@link KeyEvent} — the pure
67
49
  * `(state, key) → PromptStep<readonly string[]>` reducer. `up` / `down` (and `k` / `j`) move the
68
50
  * focus (wrapping); `space` toggles the focused index in the checked set; return submits the
69
- * checked values in CHOICE order — gated by `min` / `max` (an out-of-range count stays active with
70
- * the reason in the view); ctrl-c cancels.
51
+ * checked values in choice order; ctrl-c cancels. The form applies selection-count rules.
71
52
  */
72
- export declare function checkboxReduce(state: CheckboxState, key: KeyEvent): PromptStep<readonly string[], CheckboxState>;
53
+ export declare function checkboxReduce(state: ReturnType<typeof createCheckboxState>, key: KeyEvent): PromptStep<readonly string[], ReturnType<typeof createCheckboxState>>;
73
54
 
74
- /**
75
- * The immutable state of a checkbox prompt — the normalized choices, the styler, the `focused`
76
- * index, the set of `checked` indices, the optional `min` / `max` gate, and the current `error`.
77
- *
78
- * @remarks
79
- * Built by {@link import('./helpers.js').createCheckboxState}; advanced by
80
- * {@link import('./helpers.js').checkboxReduce}. `up` / `down` move `focus` (wrapping); `space`
81
- * toggles the focused index in `checked`; `return` submits the checked values in choice order
82
- * (rejected, with `error`, when the count is outside `[min, max]`). `checked` is modelled as a
83
- * readonly index array (plain JSON data, copy-on-write — no `Set` to clone).
84
- */
85
- export declare interface CheckboxState {
86
- readonly message: string;
87
- readonly choices: readonly CheckboxChoice[];
88
- readonly styler: StylerInterface;
89
- readonly focused: number;
90
- readonly checked: readonly number[];
91
- readonly min?: number;
92
- readonly max?: number;
93
- readonly error?: string;
94
- }
95
-
96
- /** Render a {@link CheckboxState} as a MULTI-LINE styled view — the header, one box per choice (focused + checked marked), a count, and any error. */
97
- export declare function checkboxView(state: CheckboxState): string;
98
-
99
- /**
100
- * Compose several {@link Validator}s into ONE short-circuiting validator — it runs them in order
101
- * and returns the FIRST error message, or `true` when all pass. The empty composition always
102
- * passes.
103
- */
104
- export declare function composeValidators(...validators: Validator[]): Validator;
105
-
106
- /** Options for a yes/no {@link import('./helpers.js').confirmReduce} confirmation prompt. */
107
- export declare interface ConfirmOptions {
108
- readonly message: string;
109
- readonly default?: boolean;
110
- readonly styler?: StylerInterface;
111
- }
55
+ /** Render a checkbox-field key state as a multi-line styled view. */
56
+ export declare function checkboxView(state: ReturnType<typeof createCheckboxState>): string;
112
57
 
113
58
  /**
114
59
  * Advance a confirm prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<boolean>`
115
60
  * reducer. `y` / `Y` submits `true`, `n` / `N` submits `false`, return on an empty line submits
116
61
  * the `default`, ctrl-c cancels; any other key is ignored (stays active).
117
62
  */
118
- export declare function confirmReduce(state: ConfirmState, key: KeyEvent): PromptStep<boolean, ConfirmState>;
63
+ export declare function confirmReduce(state: ReturnType<typeof createConfirmState>, key: KeyEvent): PromptStep<boolean, ReturnType<typeof createConfirmState>>;
119
64
 
120
65
  /**
121
- * The immutable state of a confirm prompt — its message, the default answer, and the styler.
122
- *
123
- * @remarks
124
- * Built by {@link import('./helpers.js').createConfirmState}; advanced by
125
- * {@link import('./helpers.js').confirmReduce}. `y` / `Y` submits `true`, `n` / `N` submits
126
- * `false`, and `enter` takes the `default` (rendered with the active letter capitalized in the
127
- * `(Y/n)` hint).
128
- */
129
- export declare interface ConfirmState {
130
- readonly message: string;
131
- readonly default: boolean;
132
- readonly styler: StylerInterface;
133
- }
134
-
135
- /** Render a {@link ConfirmState} as a styled view — the header plus a `(Y/n)` hint with the default letter emphasized. */
136
- export declare function confirmView(state: ConfirmState): string;
66
+ * Render a confirm-field key state as a styled view. The selected role paints the default letter.
67
+ */
68
+ export declare function confirmView(state: ReturnType<typeof createConfirmState>): string;
137
69
 
138
70
  /**
139
71
  * The single control BYTE → key descriptor table {@link import('./helpers.js').parseKey}
@@ -151,11 +83,37 @@ export declare const CONTROL_NAMES: Readonly<Record<string, {
151
83
  readonly ctrl: boolean;
152
84
  }>>;
153
85
 
154
- /** Build the initial {@link CheckboxState} from {@link CheckboxOptions} — normalizing choices, seeding the checked set, carrying min/max. */
155
- export declare function createCheckboxState(options: CheckboxOptions): CheckboxState;
86
+ /**
87
+ * Build the initial checkbox-field key state.
88
+ *
89
+ * @param field - The checkbox field to render
90
+ * @param styler - The styler used to render the view
91
+ * @param theme - The optional terminal theme
92
+ * @returns The initial immutable key state
93
+ */
94
+ export declare function createCheckboxState(field: CheckboxField, styler?: StylerInterface, theme?: PromptThemeOptions): {
95
+ message: string;
96
+ choices: FieldChoice[];
97
+ styler: StylerInterface;
98
+ theme: PromptTheme;
99
+ focused: number;
100
+ checked: readonly number[];
101
+ };
156
102
 
157
- /** Build the initial {@link ConfirmState} from {@link ConfirmOptions} — defaulting the answer to `false`. */
158
- export declare function createConfirmState(options: ConfirmOptions): ConfirmState;
103
+ /**
104
+ * Build the initial confirm-field key state.
105
+ *
106
+ * @param field - The confirm field to render
107
+ * @param styler - The styler used to render the view
108
+ * @param theme - The optional terminal theme
109
+ * @returns The initial immutable key state
110
+ */
111
+ export declare function createConfirmState(field: ConfirmField, styler?: StylerInterface, theme?: PromptThemeOptions): {
112
+ message: string;
113
+ default: boolean;
114
+ styler: StylerInterface;
115
+ theme: PromptTheme;
116
+ };
159
117
 
160
118
  /**
161
119
  * Create a {@link TerminalStoreInterface} backed by one table of the `databases` layer — the
@@ -174,11 +132,38 @@ export declare function createConfirmState(options: ConfirmOptions): ConfirmStat
174
132
  */
175
133
  export declare function createDatabaseTerminalStore(driver?: DriverInterface): TerminalStoreInterface;
176
134
 
177
- /** Build the initial {@link EditorState} from {@link EditorOptions} — resolving the validator + styler, seeding empty lines. */
178
- export declare function createEditorState(options: EditorOptions): EditorState;
135
+ /**
136
+ * Build the initial editor-field key state.
137
+ *
138
+ * @param field - The editor field to render
139
+ * @param styler - The styler used to render the view
140
+ * @param theme - The optional terminal theme
141
+ * @returns The initial immutable key state
142
+ */
143
+ export declare function createEditorState(field: EditorField, styler?: StylerInterface, theme?: PromptThemeOptions): {
144
+ message: string;
145
+ default: string;
146
+ styler: StylerInterface;
147
+ theme: PromptTheme;
148
+ lines: readonly string[];
149
+ current: string;
150
+ };
179
151
 
180
- /** Build the initial {@link InputState} from {@link InputOptions} — resolving the validator + styler, seeding an empty value. */
181
- export declare function createInputState(options: InputOptions): InputState;
152
+ /**
153
+ * Build the initial text-field key state.
154
+ *
155
+ * @param field - The text field to render
156
+ * @param styler - The styler used to render the view
157
+ * @param theme - The optional terminal theme
158
+ * @returns The initial immutable key state
159
+ */
160
+ export declare function createInputState(field: TextField, styler?: StylerInterface, theme?: PromptThemeOptions): {
161
+ message: string;
162
+ default: string;
163
+ styler: StylerInterface;
164
+ theme: PromptTheme;
165
+ value: string;
166
+ };
182
167
 
183
168
  /**
184
169
  * Create the in-memory {@link TerminalStoreInterface} — a process-lifetime `Map` of endpoint
@@ -195,43 +180,51 @@ export declare function createInputState(options: InputOptions): InputState;
195
180
  */
196
181
  export declare function createMemoryTerminalStore(): TerminalStoreInterface;
197
182
 
198
- /** Build the initial {@link PasswordState} from {@link PasswordOptions} — resolving the validator + styler + mask. */
199
- export declare function createPasswordState(options: PasswordOptions): PasswordState;
183
+ /**
184
+ * Build the initial password-field key state.
185
+ *
186
+ * @param field - The password field to render
187
+ * @param styler - The styler used to render the view
188
+ * @param theme - The optional terminal theme
189
+ * @returns The initial immutable key state
190
+ */
191
+ export declare function createPasswordState(field: PasswordField, styler?: StylerInterface, theme?: PromptThemeOptions): {
192
+ message: string;
193
+ mask: string;
194
+ styler: StylerInterface;
195
+ theme: PromptTheme;
196
+ value: string;
197
+ };
200
198
 
201
199
  /**
202
- * Create the headless prompt {@link PromptInterface} BROKER — it parks each prompt call as a
203
- * pending prompt and resolves it when {@link PromptInterface.answer} arrives (or rejects on
204
- * timeout). The tri-surface's headless arm: subscribe `emitter.on('pending', …)` to forward each
205
- * prompt to whoever can answer (an SSE transport, a remote terminal), then route the answer back
206
- * through `answer(id, value)`.
200
+ * Create the headless {@link PromptInterface} broker. It parks live forms and applies remote
201
+ * answers to the authoritative instances.
207
202
  *
208
203
  * @param options - See {@link PromptOptions}
209
204
  * @returns A {@link PromptInterface}
210
205
  *
211
206
  * @remarks
212
- * - **Park-as-Promise.** `await prompt.input({ message })` blocks until `answer(id, value)` accepts
213
- * a matching value; the value is validated (text forms) and type-checked to the form first.
214
- * - **Timeout → expire → reject (deterministic).** An unanswered prompt expires after
215
- * `options.timeout` (default {@link import('./constants.js').DEFAULT_PROMPT_TIMEOUT_MS}) and its
216
- * Promise rejects with a {@link import('./errors.js').TerminalError}; inject `options.timer` to
217
- * drive expiry without real time.
207
+ * The caller awaits the parked form's own `answer`. Timeout or teardown destroys the form, so that
208
+ * promise rejects with the Form package's `ABANDONED` error. Inject `options.timer` to drive
209
+ * expiry without real time.
218
210
  *
219
211
  * @example
220
212
  * ```ts
221
213
  * import { createPrompt } from '@src/core'
214
+ * import { createForm } from '@orkestrel/form'
222
215
  *
223
216
  * const prompt = createPrompt()
224
- * prompt.emitter.on('pending', (pending) => send(pending)) // forward to a remote client
225
- * const name = await prompt.input({ message: 'Your name', validate: { required: true } })
217
+ * const form = createForm({ fields: [{ control: 'text', name: 'name' }] })
218
+ * const id = prompt.park(form)
219
+ * prompt.answer(id, { name: 'Ada' })
226
220
  * ```
227
221
  */
228
222
  export declare function createPrompt(options?: PromptOptions): PromptInterface;
229
223
 
230
224
  /**
231
225
  * Create the SSE prompt {@link PromptClientInterface} BRIDGE — it connects to a remote broker's SSE
232
- * endpoint, dispatches each received prompt to a LOCAL {@link import('./types.js').PromptFormInterface}
233
- * terminal (so a human at this machine answers a prompt parked elsewhere), and POSTs the answer
234
- * back. Universal — `fetch` / SSE are web-standard.
226
+ * endpoint, dispatches each received form to a local {@link import('./types.js').TerminalInterface},
227
+ * and POSTs the answer back. Universal — `fetch` / SSE are web-standard.
235
228
  *
236
229
  * @param options - See {@link PromptClientOptions} (`url` + `terminal` required)
237
230
  * @returns A {@link PromptClientInterface}
@@ -253,16 +246,51 @@ export declare function createPrompt(options?: PromptOptions): PromptInterface;
253
246
  */
254
247
  export declare function createPromptClient(options: PromptClientOptions): PromptClientInterface;
255
248
 
256
- /** Build the initial {@link SelectState} from {@link SelectOptions} — normalizing choices and pre-focusing the default. */
257
- export declare function createSelectState(options: SelectOptions): SelectState;
249
+ /**
250
+ * Build a complete {@link PromptTheme} by merging a partial one over
251
+ * {@link DEFAULT_PROMPT_THEME}, leaf by leaf — each supplied icon replaces that glyph, each
252
+ * supplied role replaces that {@link Style}, and everything else keeps its default. Each supplied
253
+ * style is snapshotted through the console module's own
254
+ * {@link import('@orkestrel/console').freezeStyle}, so the result is deeply frozen and a caller
255
+ * mutating its own attribute list afterwards cannot reach into a built theme.
256
+ *
257
+ * @param options - The partial theme to merge, or `undefined` for the defaults
258
+ * @returns The resolved, deeply frozen theme every prompt state carries
259
+ *
260
+ * @example
261
+ * ```ts
262
+ * createPromptTheme() // the defaults
263
+ * createPromptTheme({
264
+ * icons: { pointer: '=>' },
265
+ * roles: { message: { foreground: 'magenta', attributes: ['bold'] } },
266
+ * })
267
+ * ```
268
+ */
269
+ export declare function createPromptTheme(options?: PromptThemeOptions): PromptTheme;
270
+
271
+ /**
272
+ * Build the initial select-field key state.
273
+ *
274
+ * @param field - The select field to render
275
+ * @param styler - The styler used to render the view
276
+ * @param theme - The optional terminal theme
277
+ * @returns The initial immutable key state
278
+ */
279
+ export declare function createSelectState(field: SelectField, styler?: StylerInterface, theme?: PromptThemeOptions): {
280
+ message: string;
281
+ choices: FieldChoice[];
282
+ styler: StylerInterface;
283
+ theme: PromptTheme;
284
+ focused: number;
285
+ };
258
286
 
259
287
  /**
260
- * Create the multi-endpoint {@link TerminalManagerInterface} — a named registry of
288
+ * Create the multi-endpoint {@link TerminalManager} — a named registry of
261
289
  * {@link PromptInterface} brokers so several parties can `ask` prompts of each other by name,
262
290
  * with a transitive DEADLOCK check across every in-flight ask.
263
291
  *
264
292
  * @param options - See {@link TerminalManagerOptions}
265
- * @returns A {@link TerminalManagerInterface}
293
+ * @returns A {@link TerminalManager}
266
294
  *
267
295
  * @example
268
296
  * ```ts
@@ -348,9 +376,26 @@ export declare class DatabaseTerminalStore implements TerminalStoreInterface {
348
376
  delete(id: string): Promise<void>;
349
377
  }
350
378
 
351
- /** The default mask glyph a {@link import('./types.js').PasswordState} renders each input character as — `*`. */
379
+ /** The default mask glyph {@link import('./helpers.js').createPasswordState} uses — `*`. */
352
380
  export declare const DEFAULT_MASK = "*";
353
381
 
382
+ /**
383
+ * The {@link import('./types.js').PromptTheme} every prompt renders with unless its options supply
384
+ * another — the glyph set assembled from {@link PROMPT_ICONS} plus the console
385
+ * {@link import('@orkestrel/console').STATUS_ICONS} `success` / `error` marks, and the console
386
+ * {@link import('@orkestrel/console').Style} each role is painted with. Deeply frozen through the
387
+ * console module's own {@link import('@orkestrel/console').freezeStyle}; the baseline
388
+ * {@link import('./helpers.js').createPromptTheme} merges a partial theme over.
389
+ *
390
+ * @remarks
391
+ * The default roles reproduce the views' historical coloring exactly: `question` / `pointer` cyan,
392
+ * `message` / `focus` bold, `success` / `selected` green, `error` red, `hint` / `muted` /
393
+ * `description` dim, and `content` the EMPTY style — an empty style renders bare text, so primary
394
+ * content keeps the bytes it had before it became themeable.
395
+ * Two roles sharing a style today are still two roles — re-mapping one leaves the other alone.
396
+ */
397
+ export declare const DEFAULT_PROMPT_THEME: PromptTheme;
398
+
354
399
  /** How long (ms) the {@link import('./types.js').PromptInterface} broker parks an unanswered prompt before it expires — 5 minutes. */
355
400
  export declare const DEFAULT_PROMPT_TIMEOUT_MS = 300000;
356
401
 
@@ -369,19 +414,6 @@ export declare function defaultTimer(callback: () => void, ms: number): TimerCan
369
414
  /** Delete (DEL, U+007F) — the usual Backspace byte on a Unix TTY. */
370
415
  export declare const DELETE: string;
371
416
 
372
- /**
373
- * Dispatch a {@link PendingPrompt} to the matching {@link PromptFormInterface} method — the bridge
374
- * step a {@link PromptClient} runs to drive a LOCAL terminal with a prompt issued elsewhere.
375
- * Reconstructs typed options from the wire-safe {@link PendingPrompt.options} (every field
376
- * §14-narrowed, never an `as`; the validator rebuilt from rules via {@link reconstructValidationRules}),
377
- * then calls the matching prompt form and returns its resolved value.
378
- *
379
- * @param terminal - The local {@link PromptFormInterface} to drive
380
- * @param pending - The decoded pending prompt to dispatch
381
- * @returns The prompt's resolved value (a `string` / `boolean` / `readonly string[]` per form)
382
- */
383
- export declare function dispatchPendingPrompt(terminal: PromptFormInterface, pending: PendingPrompt): Promise<string | boolean | readonly string[]>;
384
-
385
417
  /**
386
418
  * Apply a single line-editing {@link KeyEvent} to a text buffer — the editing shared by input /
387
419
  * password / editor. A printable key appends its character; `backspace` drops the last character;
@@ -390,85 +422,35 @@ export declare function dispatchPendingPrompt(terminal: PromptFormInterface, pen
390
422
  */
391
423
  export declare function editLine(value: string, key: KeyEvent): string | undefined;
392
424
 
393
- /** Options for a multi-line {@link import('./helpers.js').editorReduce} editor prompt (terminated by ctrl-d). */
394
- export declare interface EditorOptions {
395
- readonly message: string;
396
- readonly default?: string;
397
- readonly validate?: Validator | ValidationRules;
398
- readonly styler?: StylerInterface;
399
- }
400
-
401
425
  /**
402
426
  * Advance an editor prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
403
427
  * reducer. Printable characters extend the current line; backspace shrinks it; return commits the
404
428
  * current line and starts a fresh one; ctrl-d FINISHES (joining all lines, falling back to the
405
- * default when empty) through the validator; ctrl-c cancels. An invalid finish stays active with
406
- * the error.
429
+ * default when empty); ctrl-c cancels. The form validates the candidate after the driver fills it.
407
430
  */
408
- export declare function editorReduce(state: EditorState, key: KeyEvent): PromptStep<string, EditorState>;
431
+ export declare function editorReduce(state: ReturnType<typeof createEditorState>, key: KeyEvent): PromptStep<string, ReturnType<typeof createEditorState>>;
409
432
 
410
433
  /**
411
- * The immutable state of an editor prompt — the committed `lines`, the in-progress `current`
412
- * line, plus the resolved validator + styler, the default, and the current `error`.
413
- *
414
- * @remarks
415
- * Built by {@link import('./helpers.js').createEditorState}; advanced by
416
- * {@link import('./helpers.js').editorReduce}. A printable key appends to `current`; `return`
417
- * commits `current` to `lines` and starts a fresh line; ctrl-d finishes, submitting
418
- * `lines + current` joined by newlines (or `default` when empty). The whole text is validated
419
- * on finish.
420
- */
421
- export declare interface EditorState {
422
- readonly message: string;
423
- readonly default: string;
424
- readonly validator: Validator;
425
- readonly styler: StylerInterface;
426
- readonly lines: readonly string[];
427
- readonly current: string;
428
- readonly error?: string;
429
- }
430
-
431
- /** Render an {@link EditorState} as a MULTI-LINE styled view — the header (with a Ctrl+D hint), the committed lines, the in-progress line, and any error. */
432
- export declare function editorView(state: EditorState): string;
433
-
434
- /** Matches an email address — a non-trivial `local@domain.tld` shape. The `email` rule tests against this. */
435
- export declare const EMAIL_PATTERN: RegExp;
434
+ * Render an editor-field key state as a multi-line styled view with its finish hint.
435
+ */
436
+ export declare function editorView(state: ReturnType<typeof createEditorState>): string;
436
437
 
437
- /** The styled error line (`✖ message`) — appended beneath a prompt view when the last submit failed validation. */
438
- export declare function errorLine(styler: StylerInterface, message: string): string;
438
+ /** The styled error line (`✖ message`) a form driver appends for a field failure. */
439
+ export declare function errorLine(styler: StylerInterface, theme: PromptTheme, message: string): string;
439
440
 
440
441
  /** Escape (ESC, U+001B) — the lone byte, and the lead byte of every CSI / SS3 sequence. */
441
442
  export declare const ESCAPE: string;
442
443
 
443
444
  /**
444
- * Evaluate ONE built-in validation rule against `input`, returning its error message when the
445
- * rule fails or `undefined` when it passes. The atomic check {@link buildRuleValidator} wraps
446
- * into a {@link Validator}. Pure.
447
- *
448
- * @remarks
449
- * A function `check` is the custom-override path: it is called and its `true` ⇒ pass, a string
450
- * ⇒ that message, anything else ⇒ the generic {@link RULE_MESSAGES.invalid}. A primitive `check`
451
- * runs the named built-in: `required` (non-empty trimmed), `minimum` / `maximum` (length
452
- * bounds, the message interpolated with the count), `pattern` (a regex source), and the
453
- * `email` / `url` / `numeric` / `integer` / `alphanumeric` pattern tests.
454
- *
455
- * @param rule - The rule name (`'required'`, `'minimum'`, …)
456
- * @param check - The configured value (a primitive toggle / bound / pattern, or a custom {@link Validator})
457
- * @param input - The input string to test
458
- * @returns The error message when the rule fails, else `undefined`
459
- */
460
- export declare function evaluateRule(rule: string, check: boolean | number | string | Validator, input: string): string | undefined;
461
-
462
- /**
463
- * A minimal `fetch` — the subset of the global `fetch` the {@link PromptClient} uses (open the
464
- * SSE stream, POST an answer). Injected so a test drives the client with a controlled
465
- * `Response` (a scripted SSE `ReadableStream`) instead of a real network.
445
+ * A minimal `fetch` — the subset of the global `fetch` a {@link PromptClientInterface} uses: open
446
+ * the SSE stream, POST an answer. Injected so a test drives the client with a scripted `Response`
447
+ * instead of a real network.
466
448
  */
467
449
  export declare type FetchHandler = (input: string, init?: FetchInit) => Promise<Response>;
468
450
 
469
451
  /**
470
- * The request init the {@link PromptClient} passes to its {@link FetchHandler} — the `fetch`
471
- * `RequestInit` fields it actually sets (method / headers / body / abort signal).
452
+ * The request init a {@link PromptClientInterface} passes to its {@link FetchHandler} — the
453
+ * `RequestInit` fields it actually sets.
472
454
  */
473
455
  export declare interface FetchInit {
474
456
  readonly method?: string;
@@ -477,55 +459,33 @@ export declare interface FetchInit {
477
459
  readonly signal?: AbortSignal;
478
460
  }
479
461
 
480
- /** The min/max gate for a checkbox submit — the rejection message when `count` is out of range, else `undefined`. */
481
- export declare function gateSelection(count: number, min?: number, max?: number): string | undefined;
482
-
483
462
  /** The default {@link import('./types.js').FetchHandler} — the global `fetch`, adapted to the minimal injected shape the {@link import('./PromptClient.js').PromptClient} uses. */
484
463
  export declare function globalFetch(input: string, init?: FetchInit): Promise<Response>;
485
464
 
486
- /** The auth-token request header the {@link import('./types.js').PromptClient} sends when a `token` is configured. */
465
+ /** The auth-token request header the {@link import('./types.js').PromptClientInterface} sends. */
487
466
  export declare const HEADER_TOKEN = "x-orkestrel-token";
488
467
 
489
- /** Options for a single-line text {@link import('./helpers.js').inputReduce} prompt. */
490
- export declare interface InputOptions {
491
- readonly message: string;
492
- readonly default?: string;
493
- readonly validate?: Validator | ValidationRules;
494
- readonly styler?: StylerInterface;
495
- }
468
+ /**
469
+ * Render a prompt header followed by a key hint painted with the `hint` role, or the header alone
470
+ * when no hint is supplied.
471
+ *
472
+ * @param styler - The console styler that renders each role
473
+ * @param theme - The resolved prompt theme
474
+ * @param message - The prompt's question text
475
+ * @param hint - The optional key hint to append
476
+ * @returns The rendered header with the optional hint
477
+ */
478
+ export declare function hintedHeader(styler: StylerInterface, theme: PromptTheme, message: string, hint?: string): string;
496
479
 
497
480
  /**
498
481
  * Advance an input prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
499
482
  * reducer. Printable characters extend the value; backspace shrinks it; ctrl-u clears it; ctrl-c
500
- * cancels; return submits (the empty line falls back to the default) through the validator — an
501
- * invalid submit stays active with the error in the view.
483
+ * cancels; return produces the candidate value, with an empty line falling back to the default.
502
484
  */
503
- export declare function inputReduce(state: InputState, key: KeyEvent): PromptStep<string, InputState>;
485
+ export declare function inputReduce(state: ReturnType<typeof createInputState>, key: KeyEvent): PromptStep<string, ReturnType<typeof createInputState>>;
504
486
 
505
- /**
506
- * The immutable state of a text input prompt — its options, the resolved validator + styler,
507
- * the accumulated `value`, and the current `error` (when the last submit failed validation).
508
- *
509
- * @remarks
510
- * Built by {@link import('./helpers.js').createInputState}; advanced by
511
- * {@link import('./helpers.js').inputReduce}. `value` accumulates printable characters and
512
- * shrinks on backspace; `error` holds the validation message shown in the view after a
513
- * rejected submit (cleared on the next keystroke).
514
- */
515
- export declare interface InputState {
516
- readonly message: string;
517
- readonly default: string;
518
- readonly validator: Validator;
519
- readonly styler: StylerInterface;
520
- readonly value: string;
521
- readonly error?: string;
522
- }
523
-
524
- /** Render an {@link InputState} as a styled view — the header, the typed value (or dimmed default hint), and any error. */
525
- export declare function inputView(state: InputState): string;
526
-
527
- /** Matches an integer (optional sign). The `integer` rule tests against this. */
528
- export declare const INTEGER_PATTERN: RegExp;
487
+ /** Render a text-field key state as a styled view. */
488
+ export declare function inputView(state: ReturnType<typeof createInputState>): string;
529
489
 
530
490
  /**
531
491
  * Whether a caught value is an `AbortError` — the {@link import('./PromptClient.js').PromptClient}
@@ -534,15 +494,6 @@ export declare const INTEGER_PATTERN: RegExp;
534
494
  */
535
495
  export declare function isAbortError(error: unknown): boolean;
536
496
 
537
- /** Narrow an unknown wire payload to an answer POST body — a non-empty `id` string plus a `value` key (of any shape) present. */
538
- export declare function isAnswerPayload(value: unknown): value is {
539
- readonly id: string;
540
- readonly value: unknown;
541
- };
542
-
543
- /** Narrow an unknown value to a {@link CheckboxChoice} — the `recordOf` shape inlined so no non-exported member lingers (§5). */
544
- export declare function isCheckboxChoice(value: unknown): value is CheckboxChoice;
545
-
546
497
  /**
547
498
  * Whether `url` is an INSECURE remote endpoint — a plain `http://` URL whose host is NOT a
548
499
  * loopback address. Pure string parsing (no `URL` global), so it stays total on malformed input.
@@ -565,25 +516,23 @@ export declare function isCheckboxChoice(value: unknown): value is CheckboxChoic
565
516
  export declare function isInsecureRemote(url: string): boolean;
566
517
 
567
518
  /**
568
- * Narrow an unknown wire value to a {@link PendingPrompt} before dispatching it.
519
+ * Narrow an unknown wire value to a {@link PendingForm} envelope.
520
+ *
521
+ * @remarks
522
+ * This guard checks the transport record and proves only that `schema` is a record. The Form
523
+ * package's `parseForm` owns the schema payload and its semantic audit.
569
524
  *
570
525
  * @param value - The decoded wire value to inspect
571
- * @returns Whether `value` is a complete pending-prompt record
526
+ * @returns Whether the value is a complete pending-form envelope
572
527
  */
573
- export declare const isPendingPrompt: Guard<PendingPrompt>;
528
+ export declare function isPendingForm(value: unknown): value is PendingForm;
574
529
 
575
- /** Narrow an unknown value to a {@link PendingPromptStatus}. */
576
- export declare const isPendingPromptStatus: Guard<PendingPromptStatus>;
530
+ /** Narrow an unknown value to a {@link PendingFormStatus}. */
531
+ export declare const isPendingFormStatus: Guard<PendingFormStatus>;
577
532
 
578
533
  /** Whether a single character is a printable (non-control) character — used by {@link parseKey}'s char fallback. */
579
534
  export declare function isPrintable(character: string): boolean;
580
535
 
581
- /** Narrow an unknown value to a {@link PromptChoice} — the `recordOf` shape inlined so no non-exported member lingers (§5). */
582
- export declare function isPromptChoice(value: unknown): value is PromptChoice;
583
-
584
- /** Narrow an unknown value to a {@link PromptType} — one of the six prompt forms. */
585
- export declare const isPromptType: Guard<PromptType>;
586
-
587
536
  /**
588
537
  * Narrow an unknown caught value to a {@link TerminalError}.
589
538
  *
@@ -593,9 +542,11 @@ export declare const isPromptType: Guard<PromptType>;
593
542
  * @example
594
543
  * ```ts
595
544
  * try {
596
- * const name = await prompt.input({ message: 'Your name' })
545
+ * await terminal.ask(form)
597
546
  * } catch (error) {
598
- * if (isTerminalError(error) && error.code === 'EXPIRE') retryLater()
547
+ * if (isTerminalError(error) && error.code === 'CANCEL') {
548
+ * // the person aborted
549
+ * }
599
550
  * }
600
551
  * ```
601
552
  */
@@ -604,6 +555,14 @@ export declare function isTerminalError(value: unknown): value is TerminalError;
604
555
  /** Narrow an unknown value to a {@link TerminalSnapshot}. */
605
556
  export declare const isTerminalSnapshot: Guard<TerminalSnapshot>;
606
557
 
558
+ /**
559
+ * Narrow an unknown value to a transport-neutral {@link WireEvent}.
560
+ *
561
+ * @param value - The candidate wire event
562
+ * @returns Whether the value carries an event name, serialized data, and an optional id
563
+ */
564
+ export declare const isWireEvent: Guard<WireEvent>;
565
+
607
566
  /**
608
567
  * The Control Sequence Introducer lead (`ESC[`) for the navigation keys — the prefix of the
609
568
  * arrow / home / end / delete sequences {@link SEQUENCE_NAMES} is keyed by. Named `KEY_CSI`
@@ -616,23 +575,21 @@ export declare const KEY_CSI: string;
616
575
  export declare const KEY_SS3: string;
617
576
 
618
577
  /**
619
- * One decoded keypress — the universal, TTY-agnostic representation of a single key, the
620
- * output of {@link import('./helpers.js').parseKey}. A reducer reads `name` (and the
621
- * modifier flags) to decide its transition; the raw `sequence` is preserved so a printable
622
- * character round-trips and an unknown escape is never lost.
578
+ * One decoded keypress — the TTY-agnostic representation of a single key, the output of
579
+ * {@link import('./helpers.js').parseKey}. A driver reads `name` and the modifier flags to decide
580
+ * its transition; `sequence` is preserved so a printable character round-trips and an unknown
581
+ * escape is never lost.
623
582
  *
624
583
  * @remarks
625
- * - `name` — the canonical key name: a control / navigation key (`return`, `backspace`,
626
- * `tab`, `escape`, `up` / `down` / `left` / `right`, `space`, `home`, `end`, `delete`),
627
- * a named ctrl combo (`c` with `ctrl` true for ctrl-c, likewise `d` / `u` / `a` / `e`),
628
- * or the printable character itself (`'a'`, `'7'`, `'?'`). An UNRECOGNIZED sequence
629
- * yields `name: ''` (empty) — never a throw (the decoder is total).
630
- * - `sequence` — the exact input bytes as a string (a `Uint8Array` is decoded UTF-8). The
631
- * driver writes this verbatim for a printable key; a reducer that needs the literal char
632
- * reads it.
633
- * - `ctrl` / `meta` / `shift` — the modifier flags. `ctrl` is `true` for a C0 control byte
634
- * (ctrl-c / ctrl-d / ctrl-u / ctrl-a / ctrl-e and the like); `meta` is `true` for an
635
- * ESC-prefixed (Alt) sequence; `shift` is `true` for an uppercase-letter printable.
584
+ * - `name` — the canonical key name: a control or navigation key (`return`, `backspace`, `tab`,
585
+ * `escape`, `up` / `down` / `left` / `right`, `space`, `home`, `end`, `delete`), a named ctrl
586
+ * combo (`c` with `ctrl` true for ctrl-c, likewise `d` / `u` / `a` / `e`), or the printable
587
+ * character itself (`'a'`, `'7'`, `'?'`). An unrecognized sequence yields `name: ''`; the decoder
588
+ * is total and never throws.
589
+ * - `sequence` — the exact input bytes as a string (a `Uint8Array` is decoded UTF-8). The driver
590
+ * writes this verbatim for a printable key.
591
+ * - `ctrl` / `meta` / `shift` — the modifier flags. `ctrl` is true for a C0 control byte, `meta`
592
+ * for an ESC-prefixed (Alt) sequence, `shift` for an uppercase-letter printable.
636
593
  */
637
594
  export declare interface KeyEvent {
638
595
  readonly name: string;
@@ -684,43 +641,31 @@ export declare class MemoryTerminalStore implements TerminalStoreInterface {
684
641
  /** Line feed (`\n`, U+000A) — Enter on some terminals / pasted input. */
685
642
  export declare const NEWLINE: string;
686
643
 
687
- /** Normalize a checkbox choice input into a full {@link CheckboxChoice} (a bare string becomes both name and value). */
688
- export declare function normalizeCheckboxChoice(choice: string | CheckboxChoice): CheckboxChoice;
689
-
690
- /** Normalize a select choice input into a full {@link PromptChoice} (a bare string becomes both name and value). */
691
- export declare function normalizeChoice(choice: string | PromptChoice): PromptChoice;
692
-
693
- /** Matches a numeric value (integer or decimal, optional sign). The `numeric` rule tests against this. */
694
- export declare const NUMERIC_PATTERN: RegExp;
695
-
696
644
  /**
697
- * One parked prompt's runtime state inside the broker — the wire-safe {@link PendingPrompt} record
698
- * it exposes, plus the live machinery that settles that prompt's Promise. `respond` is the per-form
699
- * gate-and-resolve closure: it validates + type-checks an answer and (on accept) resolves the parked
700
- * Promise, returning whether it accepted; it closes over the form's precisely-typed `resolve`, so no
701
- * per-form generic leaks into `answer`. `expire` rejects the parked Promise; `cancel` clears the
702
- * injected expiry timer ({@link TimerCancel}).
645
+ * One parked form's runtime state inside the broker — the live form, the wire-safe record the
646
+ * broker exposes, and the cancel for its expiry timer.
647
+ *
648
+ * @remarks
649
+ * `form` is the AUTHORITATIVE form the caller parked, not a copy: an answer fills and submits this
650
+ * one, so a `custom` rule that never crossed the wire still decides. Expiry destroys it, which
651
+ * abandons it and settles the caller's promise through the form's own lifecycle. `pending` is the
652
+ * wire record, whose `status` tracks the ticket.
703
653
  */
704
654
  export declare interface Parked {
705
- readonly prompt: PendingPrompt;
706
- readonly respond: (value: unknown) => unknown;
707
- readonly expire: () => void;
655
+ readonly form: FormInterface;
656
+ readonly pending: PendingForm;
708
657
  readonly cancel: TimerCancel;
709
658
  }
710
659
 
711
660
  /**
712
- * The request to {@link PromptInterface.park} a prompt directly — the general form the six
713
- * `PromptFormInterface` methods each wrap via a private `gateFor(form, options)` dispatch.
661
+ * The parking envelope — everything the broker needs about a park that the form itself does not
662
+ * say.
714
663
  *
715
664
  * @remarks
716
- * - `form` — which {@link PromptType} to park.
717
- * - `options` — that form's options bag ({@link PromptFormOptions}).
718
- * - `from` / `to` — set ONLY by a {@link TerminalManagerInterface} (the attribution edge); a
719
- * direct broker caller leaves both `undefined`.
665
+ * `from` and `to` are the attribution edge, set ONLY by a {@link TerminalManagerInterface}: which
666
+ * endpoint asked, which endpoint must answer. A direct broker caller passes no request at all.
720
667
  */
721
668
  export declare interface ParkRequest {
722
- readonly form: PromptType;
723
- readonly options: PromptFormOptions;
724
669
  readonly from?: string;
725
670
  readonly to?: string;
726
671
  }
@@ -758,127 +703,70 @@ export declare interface ParkRequest {
758
703
  */
759
704
  export declare function parseKey(input: string | Uint8Array): KeyEvent;
760
705
 
761
- /**
762
- * Parse a JSON wire string TOTAL — a malformed / empty payload yields `undefined` (the caller's
763
- * guard then rejects it), never a throw. The {@link import('./PromptClient.js').PromptClient}
764
- * decodes every SSE `data` field through this before §14-narrowing it.
765
- */
766
- export declare function parseWireJSON(text: string): unknown;
767
-
768
- /** The always-passing {@link Validator} — the resolved validator when no rules were supplied. */
769
- export declare function passing(_input: string): true;
770
-
771
- /** Options for a masked password {@link import('./helpers.js').passwordReduce} prompt. */
772
- export declare interface PasswordOptions {
773
- readonly message: string;
774
- readonly mask?: string;
775
- readonly validate?: Validator | ValidationRules;
776
- readonly styler?: StylerInterface;
777
- }
778
-
779
706
  /**
780
707
  * Advance a password prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
781
708
  * reducer. Identical line-editing to {@link inputReduce} (printable extends, backspace shrinks,
782
- * ctrl-u clears, ctrl-c cancels) but the view masks the value; return submits through the
783
- * validator (no default fallback — a password has no echoed default).
709
+ * ctrl-u clears, ctrl-c cancels) but the view masks the value. Return produces the candidate value.
784
710
  */
785
- export declare function passwordReduce(state: PasswordState, key: KeyEvent): PromptStep<string, PasswordState>;
786
-
787
- /**
788
- * The immutable state of a password prompt — like {@link InputState} but with a `mask`
789
- * character the view renders in place of each input character.
790
- *
791
- * @remarks
792
- * Built by {@link import('./helpers.js').createPasswordState}; advanced by
793
- * {@link import('./helpers.js').passwordReduce}. The `value` is the real (unmasked) input;
794
- * the view shows `mask` repeated `value.length` times.
795
- */
796
- export declare interface PasswordState {
797
- readonly message: string;
798
- readonly mask: string;
799
- readonly validator: Validator;
800
- readonly styler: StylerInterface;
801
- readonly value: string;
802
- readonly error?: string;
803
- }
711
+ export declare function passwordReduce(state: ReturnType<typeof createPasswordState>, key: KeyEvent): PromptStep<string, ReturnType<typeof createPasswordState>>;
804
712
 
805
- /** Render a {@link PasswordState} as a styled view — the header, the value masked to `mask` repeated, and any error. */
806
- export declare function passwordView(state: PasswordState): string;
713
+ /** Render a password-field key state as a styled view. */
714
+ export declare function passwordView(state: ReturnType<typeof createPasswordState>): string;
807
715
 
808
716
  /**
809
- * One prompt PARKED by the broker — an id-keyed, wire-safe record of a {@link PromptFormInterface}
810
- * call awaiting a remote answer. The value a `pending` listener receives and the broker serializes
811
- * over SSE to a {@link PromptClient}.
717
+ * One form PARKED by the broker — an id-keyed, wire-safe record of a live form awaiting a remote
718
+ * answer. The value a `pending` listener receives and the broker serializes over SSE to a
719
+ * {@link PromptClientInterface}.
812
720
  *
813
721
  * @remarks
814
- * - `id` — the unique id (minted via `crypto.randomUUID()`); the key for {@link PromptInterface.answer}.
815
- * - `form` — which prompt form was called ({@link PromptType}); the discriminant a client
816
- * dispatches on (named for its axis — the prompt form — never `kind` / `type`).
817
- * - `message` — the prompt's question (lifted out of `options` for direct display).
818
- * - `options` — the WIRE-SAFE options (a `validate` FUNCTION dropped; the declarative
819
- * {@link ValidationRules} data + `choices` / `default` / `mask` kept — see
820
- * {@link import('./helpers.js').serializePromptOptions}). A client reconstructs the validator
821
- * from the rules via {@link resolveValidation}.
822
- * - `status` — the current {@link PendingPromptStatus}.
823
- * - `time` — the creation timestamp (ms since epoch).
824
- * - `from` / `to` — the OPTIONAL attribution edge a {@link TerminalManagerInterface} stamps on a
825
- * parked prompt (which endpoint asked, which endpoint must answer); absent for a bare broker
826
- * {@link PromptInterface} used directly (no manager attribution).
827
- */
828
- export declare interface PendingPrompt {
722
+ * - `id` — the unique id, minted with `crypto.randomUUID()`; the key for
723
+ * {@link PromptInterface.answer}.
724
+ * - `schema` — the parked form's schema projected to JSON by the dependency's own `serializeForm`.
725
+ * Every `custom` validator is dropped on the way out, so an authoritative rule the wire cannot
726
+ * carry stays server-side and is enforced when the answer comes back.
727
+ * - `status` — the ticket's {@link PendingFormStatus}.
728
+ * - `time` — the creation timestamp, ms since epoch.
729
+ * - `from` / `to` — the attribution edge a {@link TerminalManagerInterface} stamps on a parked
730
+ * form: which endpoint asked, which endpoint must answer. Both absent for a bare broker used
731
+ * directly.
732
+ */
733
+ export declare interface PendingForm {
829
734
  readonly id: string;
830
- readonly form: PromptType;
831
- readonly message: string;
832
- readonly options: Readonly<Record<string, unknown>>;
833
- readonly status: PendingPromptStatus;
735
+ readonly schema: JSONRecord;
736
+ readonly status: PendingFormStatus;
834
737
  readonly time: number;
835
738
  readonly from?: string;
836
739
  readonly to?: string;
837
740
  }
838
741
 
839
742
  /**
840
- * The lifecycle status of a parked {@link PendingPrompt} — where a brokered prompt stands.
841
- * Names its axis (the pending prompt's progression), never `kind` (AGENTS §4.4).
743
+ * The lifecycle status of a parked {@link PendingForm} — where the TICKET stands, which is not
744
+ * where the form stands. A ticket is `pending` until somebody answers it; the form it carries has
745
+ * its own status, and the two are separate facts about separate entities.
842
746
  *
843
747
  * @remarks
844
- * - `pending` — parked, awaiting an {@link PromptInterface.answer} (the Promise is unresolved).
845
- * - `answered` — answered and accepted (the Promise resolved with the validated value).
846
- * - `expired` — timed out (or torn down by `destroy`) before an answer (the Promise rejected).
748
+ * - `pending` — parked, awaiting {@link PromptInterface.answer}.
749
+ * - `answered` — answered and accepted, so the parked form settled.
750
+ * - `expired` — timed out, or torn down by `destroy`, before an answer.
847
751
  */
848
- export declare type PendingPromptStatus = 'pending' | 'answered' | 'expired';
752
+ export declare type PendingFormStatus = 'pending' | 'answered' | 'expired';
849
753
 
850
754
  /**
851
- * The headless prompt BROKER (observable §13) — parks each {@link PromptInterface} call as a
852
- * pending prompt and returns a Promise that resolves when the prompt is {@link answer}ed (or
853
- * rejects on timeout / teardown). The tri-surface's headless arm: there is no terminal here, so a
854
- * transport forwards each `pending` event to whoever can answer, and {@link answer} resolves the
855
- * parked Promise — for environments where direct user access is unavailable (a headless server on
856
- * stdio, a browser with no TTY, a prompt issued on one machine answered on another).
755
+ * The headless form broker. It parks live forms, exposes their serialized schemas, and applies
756
+ * remote answers to the authoritative form.
857
757
  *
858
758
  * @remarks
859
- * - **Park-as-Promise.** Each `input` / `password` / `confirm` / `select` / `checkbox` / `editor`
860
- * mints an id (`crypto.randomUUID()`), parks a wire-safe {@link PendingPrompt}, emits `pending`,
861
- * and returns an unresolved Promise. The prompt's options are serialized
862
- * ({@link serializePromptOptions}) so a transport can forward them as-is.
863
- * - **Answer validates + type-checks.** {@link answer} runs the prompt's per-form gate: it
864
- * type-checks `value` to the form (string / boolean / string[]) AND, for the text forms, runs
865
- * the validator resolved from the original `validate` rules. A rejected answer returns `false`
866
- * and the prompt stays `pending`; an accepted answer resolves the Promise, emits `answer`, and
867
- * removes the prompt.
868
- * - **Timeout → expire → reject.** An unanswered prompt expires after `timeout` ms (via the
869
- * INJECTED timer): `expire` fires and the parked Promise rejects with a {@link TerminalError}
870
- * (`code: 'EXPIRE'`). {@link destroy} expires every still-pending prompt the same way.
871
- * - **Deterministic.** The timer is injectable ({@link import('./types.js').TimerHandler}); a test
872
- * supplies one that fires on demand, so expiry is driven without real time.
759
+ * A parked record carries one call to `serializeForm`. A failed fill or submit leaves the record
760
+ * parked for another answer. A successful submit settles the form once, emits `answer`, and removes
761
+ * the record. Timeout and teardown abandon every unsettled form through its own `destroy` method.
873
762
  *
874
763
  * @example
875
764
  * ```ts
876
- * const prompt = createPrompt({ timeout: 60_000 })
877
- * prompt.emitter.on('pending', (pending) => broadcast(pending)) // forward to a client
878
- *
879
- * const name = await prompt.input({ message: 'Your name' }) // parks; resolves on answer()
880
- * // ...elsewhere, a client POSTs the answer back:
881
- * prompt.answer(id, 'Ada') // resolves the awaited input() above
765
+ * const form = createForm({ fields: [{ control: 'text', name: 'name' }] })
766
+ * const prompt = createPrompt()
767
+ * const id = prompt.park(form)
768
+ * prompt.answer(id, { name: 'Ada' })
769
+ * await form.answer // { name: 'Ada' }
882
770
  * ```
883
771
  */
884
772
  export declare class Prompt implements PromptInterface {
@@ -886,25 +774,16 @@ export declare class Prompt implements PromptInterface {
886
774
  constructor(options?: PromptOptions);
887
775
  get emitter(): EmitterInterface<PromptEventMap>;
888
776
  get count(): number;
889
- pending(): readonly PendingPrompt[];
890
- pending(id: string): PendingPrompt | undefined;
891
- answer(id: string, value: unknown): AnswerResult;
892
- park(request: ParkRequest): Ticket;
893
- input(options: InputOptions): Promise<string>;
894
- password(options: PasswordOptions): Promise<string>;
895
- confirm(options: ConfirmOptions): Promise<boolean>;
896
- select(options: SelectOptions): Promise<string>;
897
- checkbox(options: CheckboxOptions): Promise<readonly string[]>;
898
- editor(options: EditorOptions): Promise<string>;
777
+ pending(): readonly PendingForm[];
778
+ pending(id: string): PendingForm | undefined;
779
+ park(form: FormInterface, request?: ParkRequest): string;
780
+ answer(id: string, values: FormValues): Result<FormValues, AnswerError>;
899
781
  destroy(): void;
900
782
  }
901
783
 
902
784
  /**
903
- * The prompt-view icon glyphs the reducers render the prompt line and choice rows with. PLAIN
904
- * glyphs — color is applied by the {@link import('./types.js').PromptState}'s
905
- * {@link import('@orkestrel/console').StylerInterface} at render time, never baked into the
906
- * constant (AGENTS — styling orthogonal to data; the rework fixes the terminal's ANSI-in-the-icon).
907
- * Frozen.
785
+ * The six default glyphs {@link DEFAULT_PROMPT_THEME} assembles its `icons` from. Read only when
786
+ * the default theme is assembled; a view reads its resolved theme and never this constant. Frozen.
908
787
  *
909
788
  * @remarks
910
789
  * - `question` — the leading mark on a prompt's message line.
@@ -921,49 +800,39 @@ export declare const PROMPT_ICONS: Readonly<{
921
800
  unchecked: "☐";
922
801
  }>;
923
802
 
924
- /** A choice item in a {@link SelectOptions} prompt — its displayed `name`, its resolved `value`, and an optional one-line `description`. */
925
- export declare interface PromptChoice {
926
- readonly name: string;
927
- readonly value: string;
928
- readonly description?: string;
929
- }
803
+ /**
804
+ * Every {@link import('./types.js').PromptRole}, in one frozen list — the role axis's source of
805
+ * truth. {@link import('./helpers.js').createPromptTheme} walks it to merge a partial theme, and
806
+ * a consumer building a complete role map reads it rather than retyping every name.
807
+ */
808
+ export declare const PROMPT_ROLES: readonly PromptRole[];
930
809
 
931
810
  /**
932
- * The SSE prompt BRIDGE (observable §13) — the client-side counterpart to
933
- * {@link import('./Prompt.js').Prompt}. Connects to a remote broker's SSE endpoint, receives
934
- * serialized pending prompts, dispatches EACH to a LOCAL {@link PromptFormInterface} terminal (so
935
- * a human at THIS machine answers a prompt parked elsewhere), and POSTs the answer back.
936
- * Universal — `fetch` + SSE are web-standard, so it runs in a browser or on a server; the
937
- * injected `fetch` / timer make it fully deterministic in tests.
811
+ * The SSE form bridge. It ingests serialized forms from a remote broker, renders them through a
812
+ * local terminal, and posts answers back without blocking the event stream.
938
813
  *
939
814
  * @remarks
940
- * - **Connect + reconnect.** {@link connect} opens the SSE stream (via the injected `fetch` + the
941
- * core `SSEParser`) and loops, reconnecting after the stream drops with the `delay` backoff
942
- * (driven by the injected timer) — unless `reconnect` is `false`, the client was
943
- * {@link destroy}ed, or the drop was a deliberate {@link disconnect} (an abort).
944
- * - **Dispatch + answer.** Each decoded `pending` event is narrowed to a `PendingPrompt` (§14 —
945
- * never an `as`), dispatched to `terminal`, and its resolved value POSTed back to `url`.
946
- * Dispatch is strictly SERIAL: the read loop drives the terminal for ONE prompt at a time and
947
- * only reads/dispatches the next event after the current prompt fully settles (its answer
948
- * POSTed). A prompt id redelivered (e.g. after a reconnect) WHILE its prior dispatch is still
949
- * in flight is skipped (a `#seen` set tracks in-flight ids); once that dispatch settles (or the
950
- * broker sends `expire` for the id) the id is dropped from `#seen`, so a later redelivery of the
951
- * SAME id after completion is dispatched again — the client does not dedupe across completion.
952
- * - **Server signals.** An `expire` event (the broker dropped a parked prompt) emits `expire`; a
953
- * `shutdown` event calls {@link disconnect} (not {@link destroy}) — the client stops streaming
954
- * without auto-reconnect but stays reusable; a later {@link connect} recovers it.
955
- * - **Lean events (§13).** `connect` / `disconnect` / `expire` / `error` — errors are `unknown`.
956
- * `disconnect` fires exactly once per connected-to-disconnected transition, whether triggered by
957
- * the server ending the stream cleanly or by a deliberate {@link disconnect} / {@link destroy}.
815
+ * - **Connect + reconnect.** {@link connect} opens the SSE stream and reconnects after a transport
816
+ * drop with the injected backoff unless reconnect is disabled, the client was destroyed, or
817
+ * {@link disconnect} deliberately stopped it.
818
+ * - **Ingest + render.** Each `pending` envelope passes through `isPendingForm`, Form's `parseForm`,
819
+ * and terminal's `sanitizeSchema`, then enters a serial render queue. The SSE reader never awaits
820
+ * that queue, so `expire` and `shutdown` remain live while a person is answering.
821
+ * - **Safe local form.** The rendering copy omits every wire `pattern`, because Form compiles a
822
+ * pattern during local evaluation. The broker's parked form retains it and remains authoritative.
823
+ * - **Refusal retry.** A structured `rejected` response seeds a new rendering form with the values
824
+ * just submitted, applies every {@link FieldError} through `invalidate`, and asks again. No retry
825
+ * counter truncates the loop; acceptance, expiry, and shutdown are its bounds.
826
+ * - **Replay safety.** A replayed id is skipped while it is queued, rendering, or posting. Once an
827
+ * attempt ends, a later delivery of that id may be rendered again.
958
828
  *
959
829
  * @example
960
830
  * ```ts
961
831
  * const client = createPromptClient({
962
832
  * url: 'http://localhost:3001/prompts',
963
- * terminal: createTerminal(), // a local PromptFormInterface (T-c)
964
- * on: { connect: () => log('connected') },
833
+ * terminal: createTerminal(),
965
834
  * })
966
- * await client.connect() // streams remote prompts to the local terminal, POSTs answers back
835
+ * await client.connect()
967
836
  * ```
968
837
  */
969
838
  export declare class PromptClient implements PromptClientInterface {
@@ -978,13 +847,15 @@ export declare class PromptClient implements PromptClientInterface {
978
847
  }
979
848
 
980
849
  /**
981
- * The client's event map (AGENTS §13) — lean, errors `unknown`, no listener-error event.
850
+ * The client's event map — lean, errors `unknown`, no listener-error event.
982
851
  *
983
852
  * @remarks
984
853
  * - `connect` — the SSE stream opened.
985
- * - `disconnect` — the SSE stream closed (the server ended it, or {@link PromptClientInterface.disconnect}).
986
- * - `expire` — the remote broker signalled a parked prompt expired (carries its `id`).
987
- * - `error` — a connection / dispatch / POST fault (errors are `unknown`).
854
+ * - `disconnect` — the SSE stream closed, by the server or by
855
+ * {@link PromptClientInterface.disconnect}.
856
+ * - `expire` — the remote broker signalled that a parked form expired, carrying its id. The client
857
+ * destroys the local form rendering it, or drops it from the queue if it has not started.
858
+ * - `error` — a connection, render, or POST fault.
988
859
  */
989
860
  export declare type PromptClientEventMap = {
990
861
  readonly connect: readonly [];
@@ -994,17 +865,22 @@ export declare type PromptClientEventMap = {
994
865
  };
995
866
 
996
867
  /**
997
- * The SSE prompt BRIDGE (observable §13) — the client-side counterpart to {@link PromptInterface}.
998
- * Connects to a remote broker's SSE endpoint, receives serialized {@link PendingPrompt}s,
999
- * dispatches EACH to a local {@link PromptFormInterface} terminal, and POSTs the answer back —
1000
- * so a human at this machine answers prompts a broker parked elsewhere.
868
+ * The SSE form BRIDGE — the client-side counterpart to {@link PromptInterface}. It receives
869
+ * serialized {@link PendingForm} records from a remote broker, rebuilds each schema locally, drives
870
+ * it through a {@link TerminalInterface}, and POSTs the answer back, so a human at this machine
871
+ * answers forms a broker parked elsewhere.
1001
872
  *
1002
873
  * @remarks
1003
- * - **Connect.** {@link connect} opens the SSE stream (via the injected `fetch` + the core
1004
- * `SSEParser`) and resolves when the stream ends; it reconnects with the `delay` backoff
1005
- * unless `reconnect` is `false` or the client was {@link destroy}ed.
1006
- * - **Dispatch + answer.** Each decoded prompt is narrowed (§14) and dispatched to `terminal`;
1007
- * the resolved value POSTs back to `url`.
874
+ * - **Connect.** {@link connect} opens the SSE stream through the injected `fetch` and resolves
875
+ * when the stream ends. It reconnects on the `delay` backoff unless `reconnect` is false or the
876
+ * client was destroyed.
877
+ * - **Ingest, then render.** Ingestion never waits on a render. Each decoded record is narrowed,
878
+ * its schema parsed and sanitized, and queued; one form is driven at a time while the stream
879
+ * keeps reading, so an unanswered form never starves the connection.
880
+ * - **Never trust the wire.** Every rendered string is sanitized, and a `pattern` that arrived over
881
+ * the wire is never executed locally. The authoritative form decides.
882
+ * - **Refusal retries.** A rejected answer comes back with the parked form's own errors; the client
883
+ * applies them to the local form and asks again until the answer is accepted or the form expires.
1008
884
  * - **`connected`** reflects whether the stream is currently open.
1009
885
  */
1010
886
  export declare interface PromptClientInterface {
@@ -1017,27 +893,27 @@ export declare interface PromptClientInterface {
1017
893
  }
1018
894
 
1019
895
  /**
1020
- * Options for {@link import('./factories.js').createPromptClient} / the {@link PromptClientInterface}.
896
+ * Options for {@link import('./factories.js').createPromptClient} and the
897
+ * {@link PromptClientInterface}.
1021
898
  *
1022
899
  * @remarks
1023
- * - `url` — the remote broker's SSE endpoint (GET opens the stream; answers POST back to it).
1024
- * - `terminal` — the LOCAL {@link PromptFormInterface} each remote prompt is dispatched to, so a
1025
- * human at THIS machine answers a prompt issued elsewhere.
1026
- * - `token` — an optional auth token, sent as the
1027
- * {@link import('./constants.js').HEADER_TOKEN} header on every request.
1028
- * - `reconnect` — whether to reconnect after the stream drops (default `true`).
900
+ * - `url` — the remote broker's SSE endpoint. A GET opens the stream; answers POST back to it.
901
+ * - `terminal` — the LOCAL {@link TerminalInterface} each remote form is driven through, so a human
902
+ * at THIS machine answers a form parked elsewhere.
903
+ * - `token` — an optional auth token, sent as the {@link import('./constants.js').HEADER_TOKEN}
904
+ * header on every request.
905
+ * - `reconnect` — whether to reconnect after the stream drops, default true.
1029
906
  * - `delay` — ms to wait before each reconnect attempt (default
1030
907
  * {@link import('./constants.js').DEFAULT_RECONNECT_DELAY_MS}).
1031
- * - `on` — initial {@link PromptClientEventMap} listeners (AGENTS §8/§13).
1032
- * - `error` — the emitter's listener-error handler (AGENTS §13).
1033
- * - `fetch` — the injected {@link FetchHandler} (default the global `fetch`); supply a scripted
1034
- * fetch to drive the client deterministically in tests.
1035
- * - `timer` — the injected {@link TimerHandler} for the reconnect backoff (default the host
1036
- * `setTimeout`); supply a deterministic timer to drive reconnection without real time.
908
+ * - `on` — initial {@link PromptClientEventMap} listeners.
909
+ * - `error` — the emitter's listener-error handler.
910
+ * - `fetch` — the injected {@link FetchHandler}, default the global `fetch`.
911
+ * - `timer` — the injected {@link TimerHandler} for the reconnect backoff, default the host
912
+ * `setTimeout`.
1037
913
  */
1038
914
  export declare interface PromptClientOptions {
1039
915
  readonly url: string;
1040
- readonly terminal: PromptFormInterface;
916
+ readonly terminal: TerminalInterface;
1041
917
  readonly token?: string;
1042
918
  readonly reconnect?: boolean;
1043
919
  readonly delay?: number;
@@ -1048,98 +924,77 @@ export declare interface PromptClientOptions {
1048
924
  }
1049
925
 
1050
926
  /**
1051
- * The broker's event map (AGENTS §13) — lean, errors `unknown`, no listener-error event.
927
+ * The broker's event map — lean, errors `unknown`, no listener-error event.
1052
928
  *
1053
929
  * @remarks
1054
- * - `pending` — a prompt was parked (carries the wire-safe {@link PendingPrompt}); a transport
1055
- * forwards it to remote clients.
1056
- * - `answer` — a parked prompt was answered (carries its `id` + the accepted `value`).
1057
- * - `expire` — a parked prompt timed out (or was torn down) unanswered (carries its `id`).
930
+ * - `pending` — a form was parked; a transport forwards the wire record to remote clients.
931
+ * - `answer` — a parked form was answered and accepted, carrying its id and the settled values.
932
+ * - `expire` — a parked form timed out or was torn down unanswered, carrying its id.
1058
933
  */
1059
934
  export declare type PromptEventMap = {
1060
- readonly pending: readonly [prompt: PendingPrompt];
1061
- readonly answer: readonly [id: string, value: unknown];
935
+ readonly pending: readonly [form: PendingForm];
936
+ readonly answer: readonly [id: string, values: FormValues];
1062
937
  readonly expire: readonly [id: string];
1063
938
  };
1064
939
 
940
+ /** The styled prompt-message header (`? message`) — the leading line every active prompt view shares, themed by the `question` + `message` roles. */
941
+ export declare function promptHeader(styler: StylerInterface, theme: PromptTheme, message: string): string;
942
+
1065
943
  /**
1066
- * The shared ASYNC prompt contract — the six prompt forms as Promise-returning methods. The
1067
- * ONE vocabulary BOTH the headless {@link PromptInterface} broker (this module) and the server
1068
- * `Terminal` driver (T-c) implement, so a prompt issued through this surface resolves the same
1069
- * way on every surface (local TTY, headless broker, remote bridge).
944
+ * One glyph slot a rendered field draws — the icon axis of a {@link PromptTheme}. A named value
945
+ * set, not a toggle, so it stays a union.
1070
946
  *
1071
947
  * @remarks
1072
- * Each method takes the prompt's `*Options` and resolves to that form's value type:
1073
- * - `input` / `password` / `select` / `editor` → a `string`
1074
- * - `confirm` → a `boolean`
1075
- * - `checkbox` → a `readonly string[]` (the checked values, in choice order)
1076
- *
1077
- * This is a behavioral CONTRACT (the method surface), not an observable entity — the broker and
1078
- * the driver add their own emitter / lifecycle on top. The {@link PromptClient} dispatches a
1079
- * remote {@link PendingPrompt} to a LOCAL `PromptFormInterface` (so a human at this machine
1080
- * answers a prompt parked elsewhere).
1081
- */
1082
- export declare interface PromptFormInterface {
1083
- input(options: InputOptions): Promise<string>;
1084
- password(options: PasswordOptions): Promise<string>;
1085
- confirm(options: ConfirmOptions): Promise<boolean>;
1086
- select(options: SelectOptions): Promise<string>;
1087
- checkbox(options: CheckboxOptions): Promise<readonly string[]>;
1088
- editor(options: EditorOptions): Promise<string>;
1089
- }
1090
-
1091
- /**
1092
- * The union of every prompt form's options bag — the `options` a {@link ParkRequest} carries,
1093
- * narrowed at the call site by the paired {@link PromptType}.
948
+ * - `question` — the leading mark on a field's label line.
949
+ * - `pointer` — the cursor before the input or the focused choice row.
950
+ * - `dot` / `selected` — an unfocused / focused row marker in a choice list.
951
+ * - `checked` / `unchecked` — a checked / unchecked box in a multi-choice list.
952
+ * - `success` / `error` — the mark on a settled field's line / on its failure line.
1094
953
  */
1095
- export declare type PromptFormOptions = InputOptions | PasswordOptions | ConfirmOptions | SelectOptions | CheckboxOptions | EditorOptions;
1096
-
1097
- /** The styled prompt-message header (`? message`) — the leading line every active prompt view shares. */
1098
- export declare function promptHeader(styler: StylerInterface, message: string): string;
954
+ export declare type PromptIcon = 'question' | 'pointer' | 'dot' | 'selected' | 'checked' | 'unchecked' | 'success' | 'error';
1099
955
 
1100
956
  /**
1101
- * The headless prompt BROKER (observable §13) — implements {@link PromptFormInterface} by
1102
- * PARKING each call as a {@link PendingPrompt} and returning a Promise that resolves when the
1103
- * prompt is {@link answer}ed (or rejects on timeout). The local-TTY / headless / remote
1104
- * tri-surface's headless arm: no terminal here — a transport forwards each `pending` to whoever
1105
- * can answer, and {@link answer} resolves the parked Promise.
957
+ * The headless form BROKER — parks a live form until somebody elsewhere answers it. The headless
958
+ * arm of the local-TTY / headless / remote trio: there is no terminal here, so a transport forwards
959
+ * each `pending` record to whoever can answer, and {@link answer} drives the parked form to
960
+ * settlement.
1106
961
  *
1107
962
  * @remarks
1108
- * - **Park-as-Promise.** Each `input` / `password` / … call mints an id, parks a
1109
- * {@link PendingPrompt}, emits `pending`, and returns an unresolved Promise. {@link park} is the
1110
- * general entry point the six form methods wrap.
1111
- * - **Answer validates.** {@link answer} validates `value` against the prompt's resolved
1112
- * validator AND type-checks it to the prompt form before accepting; a bad answer is rejected
1113
- * (an {@link AnswerResult} failure, the prompt stays `pending`).
1114
- * - **Timeout → expire → reject.** An unanswered prompt expires after `timeout` ms — `expire`
1115
- * fires and the parked Promise rejects (a {@link import('./errors.js').TerminalError}). The
1116
- * timer is injectable for deterministic tests.
1117
- * - **Accessors (§9.1).** `pending()` lists the parked prompts; `pending(id)` looks one up.
1118
- */
1119
- export declare interface PromptInterface extends PromptFormInterface {
963
+ * - **The form is the unit.** {@link park} takes a live form, mints an id, emits `pending`, and
964
+ * returns the id. It wraps no promise, because the caller already holds one: the form's own
965
+ * `answer`.
966
+ * - **The parked form is authoritative.** {@link answer} fills and submits THAT form, so every rule
967
+ * it carries decides, including a `custom` validator the wire dropped. A refusal returns the
968
+ * form's own errors and leaves the form parked.
969
+ * - **Timeout abandons.** An unanswered form is destroyed after `timeout` ms; `expire` fires and
970
+ * the caller's promise rejects on the form's own lifecycle. The timer is injectable.
971
+ * - **Accessors.** `pending()` lists the parked records; `pending(id)` looks one up.
972
+ */
973
+ export declare interface PromptInterface {
1120
974
  readonly emitter: EmitterInterface<PromptEventMap>;
1121
975
  readonly count: number;
1122
- park(request: ParkRequest): Ticket;
1123
- pending(): readonly PendingPrompt[];
1124
- pending(id: string): PendingPrompt | undefined;
1125
- answer(id: string, value: unknown): AnswerResult;
976
+ park(form: FormInterface, request?: ParkRequest): string;
977
+ pending(): readonly PendingForm[];
978
+ pending(id: string): PendingForm | undefined;
979
+ answer(id: string, values: FormValues): Result<FormValues, AnswerError>;
1126
980
  destroy(): void;
1127
981
  }
1128
982
 
1129
983
  /**
1130
- * Options for {@link import('./factories.js').createPrompt} / the {@link PromptInterface} broker.
984
+ * Options for {@link import('./factories.js').createPrompt} and every {@link PromptInterface}
985
+ * broker, including one a {@link TerminalManagerInterface} mounts per endpoint.
1131
986
  *
1132
987
  * @remarks
1133
- * - `on` — initial {@link PromptEventMap} listeners (AGENTS §8/§13).
1134
- * - `error` — the emitter's listener-error handler (AGENTS §13).
1135
- * - `timeout` — ms a parked prompt waits before it expires + its Promise rejects (default
988
+ * - `on` — initial {@link PromptEventMap} listeners.
989
+ * - `error` — the emitter's listener-error handler.
990
+ * - `timeout` — ms a parked form waits before it expires and is abandoned (default
1136
991
  * {@link import('./constants.js').DEFAULT_PROMPT_TIMEOUT_MS}).
1137
- * - `timer` — the injected {@link TimerHandler} (default the host `setTimeout`); supply a
992
+ * - `timer` — the injected {@link TimerHandler}, default the host `setTimeout`; supply a
1138
993
  * deterministic timer to drive expiry in tests without real time.
1139
- * - `cap` — the maximum number of prompts this broker will hold PARKED at once (default
1140
- * UNBOUNDED — no cap unless supplied). Once `count` reaches `cap`, a new park is rejected with
1141
- * a {@link import('./errors.js').TerminalError} (`code: 'LIMIT'`) WITHOUT parking, minting an
1142
- * id, emitting `pending`, or arming the expiry timer — the runaway-asker memory ceiling.
994
+ * - `cap` — the maximum number of forms this broker holds parked at once, default unbounded. Once
995
+ * `count` reaches `cap` a new park is refused with a
996
+ * {@link import('./errors.js').TerminalError} coded `LIMIT`, without parking, minting an id,
997
+ * emitting `pending`, or arming a timer. The runaway-asker memory ceiling.
1143
998
  */
1144
999
  export declare interface PromptOptions {
1145
1000
  readonly on?: EmitterHooks<PromptEventMap>;
@@ -1150,33 +1005,50 @@ export declare interface PromptOptions {
1150
1005
  }
1151
1006
 
1152
1007
  /**
1153
- * The discriminant of a {@link PromptStep} — where the prompt stands after a key. `active`:
1154
- * keep prompting (the input was consumed, or rejected by validation). `submit`: the prompt
1155
- * resolved with its `value`. `cancel`: the user aborted (ctrl-c). Names its axis (the prompt's
1156
- * progression), never `kind` (AGENTS §4.4).
1008
+ * One styling slot a rendered field paints through — the semantic axis of a {@link PromptTheme}. A
1009
+ * role says what a fragment MEANS; the theme decides what that meaning looks like, so a consumer
1010
+ * re-maps styled output by naming roles rather than reimplementing a renderer.
1011
+ *
1012
+ * @remarks
1013
+ * - `question` — the leading mark on an active field's line.
1014
+ * - `pointer` — the cursor before the input or the focused choice row.
1015
+ * - `message` — the field's own label text.
1016
+ * - `content` — the field's primary content: the typed value, the mask run, an unfocused choice
1017
+ * label, or a committed editor line. Its default is the EMPTY style, so unthemed content renders
1018
+ * as bare text.
1019
+ * - `success` / `error` — a settled field's mark / a failure mark and its message.
1020
+ * - `selected` — a chosen value: the checked box, the focused choice marker, the confirm default
1021
+ * letter.
1022
+ * - `focus` — the label of the row the cursor is on.
1023
+ * - `hint` — dim supplementary text: a default value, a key hint, a selection count, a committed
1024
+ * answer.
1025
+ * - `muted` — a dim off-state mark: an unfocused choice marker, an unchecked box, a fallback index.
1026
+ * - `description` — a choice's one-line help text.
1027
+ */
1028
+ export declare type PromptRole = 'question' | 'pointer' | 'message' | 'content' | 'success' | 'error' | 'selected' | 'focus' | 'hint' | 'muted' | 'description';
1029
+
1030
+ /**
1031
+ * Where one field's reducer stands after a key. `active`: keep asking, because the key was
1032
+ * consumed or the answer was refused. `submit`: the field resolved with its `value`. `cancel`: the
1033
+ * user aborted with ctrl-c. Names its axis, never `kind`.
1157
1034
  */
1158
1035
  export declare type PromptStatus = 'active' | 'submit' | 'cancel';
1159
1036
 
1160
1037
  /**
1161
- * The result of one reducer step — the next `state`, the rendered `view`, the `status`, and,
1162
- * on `submit`, the resolved `value`. The whole contract between a pure prompt reducer and the
1163
- * impure driver: the driver applies the next `state`, writes the `view`, and — when `status`
1164
- * is `submit` — reads `value`.
1038
+ * The result of one reducer step — the next `state`, the rendered `view`, the `status`, and, on
1039
+ * `submit`, the resolved `value`. The whole contract between a pure reducer and the impure driver:
1040
+ * the driver applies the next `state`, writes the `view`, and reads `value` on `submit`.
1165
1041
  *
1166
- * @typeParam T - The prompt's resolved value type (`string` for input / password / select /
1167
- * editor, `boolean` for confirm, `readonly string[]` for checkbox).
1168
- * @typeParam S - The prompt's concrete state shape ({@link InputState}, {@link SelectState}, …) —
1169
- * carried directly so `state` stays precisely typed with no union narrowing or assertion.
1042
+ * @typeParam T - The value this field resolves to, as its control admits it.
1043
+ * @typeParam S - The reducer's concrete state shape, carried directly so `state` stays precisely
1044
+ * typed with no union narrowing and no assertion.
1170
1045
  *
1171
1046
  * @remarks
1172
- * - `state` — the next immutable prompt state (feed it to the next reduce call). On `submit` /
1173
- * `cancel` it is the final state.
1174
- * - `view` — the styled string to render NOW (possibly MULTI-LINE for `select` / `checkbox`),
1175
- * built through the state's {@link StylerInterface}. On an invalid `submit` it carries the
1176
- * error; the driver re-renders it each step.
1177
- * - `status` — the {@link PromptStatus}: `active` to continue, `submit` when resolved, `cancel`
1178
- * on abort.
1179
- * - `value` — present ONLY on a `submit` step, carrying the prompt's resolved value.
1047
+ * - `state` — the next immutable state; feed it to the next reduce call. On `submit` or `cancel` it
1048
+ * is the final state.
1049
+ * - `view` — the styled string to render now, possibly multi-line. On a refused `submit` it carries
1050
+ * the failure; the driver re-renders it each step.
1051
+ * - `value` — present ONLY on a `submit` step.
1180
1052
  */
1181
1053
  export declare interface PromptStep<T, S> {
1182
1054
  readonly state: S;
@@ -1186,105 +1058,81 @@ export declare interface PromptStep<T, S> {
1186
1058
  }
1187
1059
 
1188
1060
  /**
1189
- * The six prompt KINDS this core provides — the value family a higher-level broker (T-b)
1190
- * dispatches on. Names the prompt axis; a named value set (not a toggle), so it stays a union.
1191
- */
1192
- export declare type PromptType = 'input' | 'password' | 'confirm' | 'select' | 'checkbox' | 'editor';
1061
+ * A resolved PRESENTATION — the glyph for every {@link PromptIcon} and the console {@link Style}
1062
+ * for every {@link PromptRole}. Plain JSON data with no functions, so it crosses the wire with the
1063
+ * form it decorates. Built by {@link import('./helpers.js').createPromptTheme}.
1064
+ *
1065
+ * @remarks
1066
+ * A role's value is the console module's own {@link Style} — the one style model the whole console
1067
+ * and terminal system shares — so a renderer paints a role through
1068
+ * {@link import('@orkestrel/console').StylerInterface} and this package holds no second style
1069
+ * vocabulary. A `Style` carries a foreground, a background, and an attribute list, so a role can
1070
+ * express a background color, which a styler's accessor chain cannot name.
1071
+ */
1072
+ export declare interface PromptTheme {
1073
+ readonly icons: Readonly<Record<PromptIcon, string>>;
1074
+ readonly roles: Readonly<Record<PromptRole, Style>>;
1075
+ }
1193
1076
 
1194
1077
  /**
1195
- * The union of a resolved prompt's value shapes — a `string` (`input` / `password` / `select` /
1196
- * `editor`), a `boolean` (`confirm`), or a `readonly string[]` (`checkbox`, the checked values in
1197
- * choice order). The type a {@link Ticket}'s `value` Promise resolves to.
1078
+ * The PARTIAL {@link PromptTheme} an option bag carries — every icon and every role is optional,
1079
+ * and {@link import('./helpers.js').createPromptTheme} merges what is supplied over
1080
+ * {@link import('./constants.js').DEFAULT_PROMPT_THEME} leaf by leaf. Supplying one icon or one
1081
+ * role leaves every other slot at its default.
1198
1082
  */
1199
- export declare type PromptValue = string | boolean | readonly string[];
1083
+ export declare interface PromptThemeOptions {
1084
+ readonly icons?: Readonly<Partial<Record<PromptIcon, string>>>;
1085
+ readonly roles?: Readonly<Partial<Record<PromptRole, Style>>>;
1086
+ }
1087
+
1088
+ /** Carriage return (`\r`, U+000D) — Enter on most terminals. */
1089
+ export declare const RETURN: string;
1200
1090
 
1201
1091
  /**
1202
- * Rebuild a wire-decoded `validate` payload into a {@link ValidationRules} bag — the inverse of
1203
- * {@link serializeValidationRules}. Keeps only the primitive rule values (`boolean` / `number` /
1204
- * `string`) a serialized prompt could carry; an empty / non-record / all-dropped payload yields
1205
- * `undefined` (no rules to apply). The client feeds the result back through {@link resolveValidation}.
1092
+ * Sanitize text for one single-line display slot. Composes console's ANSI {@link strip} and C0
1093
+ * {@link stripControls} passes with removal of tab, line feed, and carriage return.
1094
+ *
1095
+ * @param text - The text to sanitize for a glyph or hint slot
1096
+ * @returns The text with ANSI sequences, every C0 control character, and DEL removed
1097
+ *
1098
+ * @example
1099
+ * ```ts
1100
+ * sanitizeDisplayText('Q\rOVERWRITE\nNEXT\tX') // 'QOVERWRITENEXTX'
1101
+ * ```
1206
1102
  */
1207
- export declare function reconstructValidationRules(value: unknown): ValidationRules | undefined;
1208
-
1209
- /** Read a `choices` option as a list of bare strings / full choices — each element narrowed by `guard`, off-shape elements stringified. */
1210
- export declare function resolveChoices<TChoice extends PromptChoice | CheckboxChoice>(options: Readonly<Record<string, unknown>>, guard: Guard<TChoice>): ReadonlyArray<string | TChoice>;
1211
-
1212
- /** Read one option by key, narrowed by `guard` — `undefined` when absent or off-shape (§14, never an `as`). */
1213
- export declare function resolveOption<T>(options: Readonly<Record<string, unknown>>, key: string, guard: Guard<T>): T | undefined;
1103
+ export declare function sanitizeDisplayText(text: string): string;
1214
1104
 
1215
1105
  /**
1216
- * Compile a {@link Validator} or declarative {@link ValidationRules} (or nothing) into ONE
1217
- * composed {@link Validator}. A bare validator passes through; rules are appended in the fixed
1218
- * order (required → minimum → maximum → pattern → email → url → numeric → integer → alphanumeric
1219
- * → custom) and composed; absent / empty input yields an always-passing validator. Pure.
1106
+ * Sanitize every terminal-readable string in a parsed form schema.
1220
1107
  *
1221
1108
  * @remarks
1222
- * Unlike a prior variant (which returned `Validator | undefined`), this ALWAYS returns a
1223
- * `Validator` — an absent or empty rule set yields a validator that returns `true` for every
1224
- * input. That keeps a prompt's state unconditional (it always holds a real validator to apply on
1225
- * submit), with no `undefined` branch at the call site.
1109
+ * Display strings pass through {@link sanitizeDisplayText}: labels, help, placeholders, masks,
1110
+ * choice labels and help, file accept entries, and pattern sources. Identity and answer strings
1111
+ * stay verbatim: schema, group, and field names, group references, choice values, and defaults.
1112
+ * Rewriting those would sever the rendering copy from the authoritative form. Field metadata is
1113
+ * removed because terminal neither renders nor interprets it. Pattern sources are sanitized as
1114
+ * text only and are never compiled or executed here.
1226
1115
  *
1227
- * @param validate - A custom {@link Validator}, a {@link ValidationRules} bag, or `undefined`
1228
- * @returns The composed {@link Validator} (always-passing when nothing was supplied)
1116
+ * @param schema - A schema already accepted by the Form package's `parseForm`
1117
+ * @returns A new schema with terminal-readable strings sanitized and field metadata omitted
1229
1118
  *
1230
1119
  * @example
1231
1120
  * ```ts
1232
- * const v = resolveValidation({ required: true, minimum: 3 })
1233
- * v('') // 'This field is required'
1234
- * v('ab') // 'Must be at least 3 characters'
1235
- * v('abc') // true
1121
+ * sanitizeSchema({ fields: [{ control: 'text', name: 'na\u001bme', label: 'N\u0000ame' }] })
1122
+ * // { fields: [{ control: 'text', name: 'na\u001bme', label: 'Name' }] }
1236
1123
  * ```
1237
1124
  */
1238
- export declare function resolveValidation(validate?: Validator | ValidationRules): Validator;
1239
-
1240
- /** Carriage return (`\r`, U+000D) — Enter on most terminals. */
1241
- export declare const RETURN: string;
1125
+ export declare function sanitizeSchema(schema: FormSchema): FormSchema;
1242
1126
 
1243
1127
  /**
1244
- * Each built-in validation rule's default error message — what the composed {@link
1245
- * import('./types.js').Validator} returns when that rule fails (the `minimum` / `maximum`
1246
- * messages are interpolated with the configured length at build time). Frozen; the source of
1247
- * truth for the rule-failure copy.
1248
- */
1249
- export declare const RULE_MESSAGES: Readonly<{
1250
- required: "This field is required";
1251
- minimum: "Must be at least {count} characters";
1252
- maximum: "Must be at most {count} characters";
1253
- pattern: "Must match pattern: {pattern}";
1254
- email: "Must be a valid email address";
1255
- url: "Must be a valid URL";
1256
- numeric: "Must be a numeric value";
1257
- integer: "Must be an integer";
1258
- alphanumeric: "Must contain only letters and digits";
1259
- invalid: "Invalid input";
1260
- }>;
1261
-
1262
- /**
1263
- * Sanitize a list of resolved choices' human-readable labels (`name` + `description`) with
1264
- * {@link stripControls} — shared by the `select` and `checkbox` branches of
1265
- * {@link dispatchPendingPrompt} so a remote-supplied choice can never inject raw control bytes
1266
- * into the local terminal's rendered view.
1128
+ * Sanitize every glyph a wire-supplied {@link PromptThemeOptions} carries for a single-line display
1129
+ * slot. Only the icons need it: a role is guard-narrowed to a console {@link Style}, whose colors
1130
+ * and attributes are fixed name sets, so no role can carry a byte a terminal would act on.
1267
1131
  *
1268
- * @param choices - The resolved choices (bare strings or full {@link PromptChoice} /
1269
- * {@link CheckboxChoice} objects) as returned by {@link resolveChoices}
1270
- * @returns The same choices with every `name` / `description` control-stripped
1132
+ * @param theme - The narrowed theme options a remote prompt supplied
1133
+ * @returns The same theme with every supplied glyph sanitized for a single-line display slot
1271
1134
  */
1272
- export declare function sanitizeChoiceLabels<TChoice extends PromptChoice | CheckboxChoice>(choices: ReadonlyArray<string | TChoice>): ReadonlyArray<string | TChoice>;
1273
-
1274
- /**
1275
- * Options for a single-selection {@link import('./helpers.js').selectReduce} prompt.
1276
- *
1277
- * @remarks
1278
- * `choices` accepts bare strings (used as both `name` and `value`) or full {@link PromptChoice}
1279
- * objects; {@link import('./helpers.js').createSelectState} normalizes them. `default` pre-focuses
1280
- * the choice whose `value` matches it.
1281
- */
1282
- export declare interface SelectOptions {
1283
- readonly message: string;
1284
- readonly choices: ReadonlyArray<string | PromptChoice>;
1285
- readonly default?: string;
1286
- readonly styler?: StylerInterface;
1287
- }
1135
+ export declare function sanitizeThemeIcons(theme: PromptThemeOptions): PromptThemeOptions;
1288
1136
 
1289
1137
  /**
1290
1138
  * Advance a select prompt by one {@link KeyEvent} — the pure `(state, key) → PromptStep<string>`
@@ -1292,26 +1140,10 @@ export declare interface SelectOptions {
1292
1140
  * focused choice's `value`; ctrl-c cancels. An empty choice list can never submit (a higher layer
1293
1141
  * guards against it); any other key is ignored.
1294
1142
  */
1295
- export declare function selectReduce(state: SelectState, key: KeyEvent): PromptStep<string, SelectState>;
1143
+ export declare function selectReduce(state: ReturnType<typeof createSelectState>, key: KeyEvent): PromptStep<string, ReturnType<typeof createSelectState>>;
1296
1144
 
1297
- /**
1298
- * The immutable state of a select prompt — the normalized choices, the styler, and the
1299
- * `focused` index (the highlighted row).
1300
- *
1301
- * @remarks
1302
- * Built by {@link import('./helpers.js').createSelectState}; advanced by
1303
- * {@link import('./helpers.js').selectReduce}. `up` / `down` move `focused` (wrapping at the
1304
- * ends); `return` submits the focused choice's `value`.
1305
- */
1306
- export declare interface SelectState {
1307
- readonly message: string;
1308
- readonly choices: readonly PromptChoice[];
1309
- readonly styler: StylerInterface;
1310
- readonly focused: number;
1311
- }
1312
-
1313
- /** Render a {@link SelectState} as a MULTI-LINE styled view — the header followed by one row per choice, the focused row marked. */
1314
- export declare function selectView(state: SelectState): string;
1145
+ /** Render a select-field key state as a multi-line styled view. */
1146
+ export declare function selectView(state: ReturnType<typeof createSelectState>): string;
1315
1147
 
1316
1148
  /**
1317
1149
  * The exact escape SEQUENCE → canonical key NAME table {@link import('./helpers.js').parseKey}
@@ -1327,53 +1159,20 @@ export declare function selectView(state: SelectState): string;
1327
1159
  */
1328
1160
  export declare const SEQUENCE_NAMES: Readonly<Record<string, string>>;
1329
1161
 
1330
- /** Strip functions from a `choices` option — each choice keeps its plain fields; a bare string passes through. */
1331
- export declare function serializeChoices(choices: unknown): readonly unknown[];
1332
-
1333
1162
  /** Serialize a parked prompt's expiry into a {@link WireEvent} — event `'expire'`, `data` the JSON-stringified `{ id }` payload. */
1334
1163
  export declare function serializeExpire(id: string): WireEvent;
1335
1164
 
1336
- /** Serialize a parked {@link PendingPrompt} into a {@link WireEvent} — event `'pending'`, `data` the JSON-stringified prompt, `id` the prompt's own id (an SSE bridge sets its frame `id:` from this). */
1337
- export declare function serializePending(prompt: PendingPrompt): WireEvent;
1338
-
1339
- /**
1340
- * Produce the WIRE-SAFE form of a prompt's options — the {@link PendingPrompt.options} a broker
1341
- * serializes over SSE. Drops everything that cannot cross the wire (a `styler`, and any
1342
- * function-valued `validate` rule), while KEEPING the declarative data a remote client needs to
1343
- * reconstruct the prompt: the {@link ValidationRules} as data, plus `message` / `choices` /
1344
- * `default` / `mask` / `min` / `max`. This is WHY T-a's validation is rules-as-data: a `Validator`
1345
- * FUNCTION can't be serialized, but its declarative rules can — the client rebuilds the validator
1346
- * from them via {@link resolveValidation}.
1347
- *
1348
- * @remarks
1349
- * - **Functions are dropped.** A top-level function value (e.g. a bare-`Validator` `validate`) is
1350
- * omitted entirely; the `styler` (a fluent function-bearing object) is dropped by key.
1351
- * - **`validate` rules are flattened.** A {@link ValidationRules} bag is copied with each
1352
- * function rule replaced by `true` (the rule's INTENT survives as the built-in check; its
1353
- * custom function does not). A bare-function `validate` is dropped (no declarative form to keep).
1354
- * - **`choices` are function-stripped.** Each choice keeps its plain fields (`name` / `value` /
1355
- * `description` / `checked`); a bare string passes through.
1356
- *
1357
- * @param options - The raw prompt options bag (may hold functions / a styler)
1358
- * @returns A JSON-safe options record — only serializable, declarative data
1359
- */
1360
- export declare function serializePromptOptions(options: object): Readonly<Record<string, unknown>>;
1165
+ /** Serialize a parked {@link PendingForm} into a {@link WireEvent}. */
1166
+ export declare function serializePending(form: PendingForm): WireEvent;
1361
1167
 
1362
1168
  /** The {@link WireEvent} a broker/manager sends when it is going away — event `'shutdown'`, no payload. */
1363
1169
  export declare function serializeShutdown(): WireEvent;
1364
1170
 
1365
- /**
1366
- * Flatten a `validate` option to its wire-safe {@link ValidationRules} DATA — a function rule
1367
- * becomes `true` (its built-in check survives; its body cannot cross the wire). A bare-function
1368
- * `validate` (no rules object) has no declarative form, so it yields `undefined` (dropped).
1369
- */
1370
- export declare function serializeValidationRules(validate: unknown): Readonly<Record<string, unknown>> | undefined;
1371
-
1372
1171
  /** Space (U+0020). */
1373
1172
  export declare const SPACE = " ";
1374
1173
 
1375
1174
  /**
1376
- * The maximum number of characters the {@link import('./types.js').PromptClient} lets its
1175
+ * The maximum number of characters the {@link import('./types.js').PromptClientInterface} lets its
1377
1176
  * SSE parser buffer before treating the stream as hostile — 1 MiB, comfortably above any
1378
1177
  * legitimate prompt payload. Passed as the `limit` to `createSSEParser` so an unterminated
1379
1178
  * or oversized `data:` field cannot grow the buffer without bound (a memory-exhaustion guard).
@@ -1381,11 +1180,11 @@ export declare const SPACE = " ";
1381
1180
  export declare const SSE_BUFFER_LIMIT = 1048576;
1382
1181
 
1383
1182
  /**
1384
- * The SSE `event:` names the broker emits and the {@link import('./types.js').PromptClient}
1183
+ * The SSE `event:` names the broker emits and the {@link import('./types.js').PromptClientInterface}
1385
1184
  * dispatches on. Frozen; the source of truth for the wire event vocabulary.
1386
1185
  *
1387
1186
  * @remarks
1388
- * - `pending` — a serialized {@link import('./types.js').PendingPrompt} to dispatch + answer.
1187
+ * - `pending` — a serialized {@link import('./types.js').PendingForm} to dispatch and answer.
1389
1188
  * - `expire` — an `{ id }` payload: the broker expired a parked prompt (the client drops it).
1390
1189
  * - `shutdown` — the broker is going away; the client disconnects (no auto-reconnect) but stays reusable.
1391
1190
  */
@@ -1395,62 +1194,87 @@ export declare const SSE_EVENTS: Readonly<{
1395
1194
  shutdown: "shutdown";
1396
1195
  }>;
1397
1196
 
1398
- /** The styled submit line (`✔ message`) — the committed header an interactive prompt shows once resolved. */
1399
- export declare function submitHeader(styler: StylerInterface, message: string): string;
1197
+ /** The styled submit line (`✔ message`) — the committed header an interactive prompt shows once resolved, themed by the `success` + `message` roles. */
1198
+ export declare function submitHeader(styler: StylerInterface, theme: PromptTheme, message: string): string;
1400
1199
 
1401
1200
  /** Tab (`\t`, U+0009). */
1402
1201
  export declare const TAB: string;
1403
1202
 
1404
- /** The rejection reason a {@link TerminalManagerInterface.answer} call returns — an {@link AnswerError}, plus `'terminal'` (no such endpoint). */
1405
- export declare type TerminalAnswerError = AnswerError | 'terminal';
1406
-
1407
- /** The outcome of a {@link TerminalManagerInterface.answer} call — the accepted `value` on success, else the {@link TerminalAnswerError}. */
1408
- export declare type TerminalAnswerResult = {
1409
- readonly success: true;
1410
- readonly value: unknown;
1411
- } | {
1412
- readonly success: false;
1413
- readonly error: TerminalAnswerError;
1203
+ /**
1204
+ * Why a {@link TerminalManagerInterface.answer} call refused — an {@link AnswerError} from the
1205
+ * endpoint's own broker, or `terminal` when no endpoint is mounted under that name. One
1206
+ * discriminant, `reason`, across both.
1207
+ */
1208
+ export declare type TerminalAnswerError = AnswerError | {
1209
+ readonly reason: 'terminal';
1414
1210
  };
1415
1211
 
1416
1212
  /**
1417
- * An error a {@link import('./Prompt.js').Prompt} broker rejects a parked prompt's Promise with.
1213
+ * The error the terminal surfaces for its own refusals: parking on a destroyed or full broker, an
1214
+ * unusable driver stream, a manager routing fault, or a ctrl-c cancellation. A parked form's own
1215
+ * lifecycle failures reject through the form's `answer` with the form package's error, never with
1216
+ * this one.
1418
1217
  *
1419
1218
  * @remarks
1420
- * Carries a {@link TerminalErrorCode} and an optional `context` bag (the prompt `id`). Thrown —
1421
- * as a Promise rejection on the awaited prompt call — when a parked prompt is not answered before
1422
- * its `timeout`, or when the broker is `destroy`ed while the prompt is still `pending` (both
1423
- * `EXPIRE`); or when the user aborts an interactive server-`Terminal` prompt with ctrl-c
1424
- * (`CANCEL`). Narrow a caught value with {@link isTerminalError} and branch on `error.code`.
1219
+ * Carries a {@link TerminalErrorCode} and an optional `context` bag naming the offending values:
1220
+ * `{ cap }` on `LIMIT`, `{ to, known }` on `TARGET`, and `{ from, to, path }` on `DEADLOCK`. Narrow
1221
+ * a caught value with {@link isTerminalError} and branch on `error.code`.
1425
1222
  */
1426
1223
  export declare class TerminalError extends Error {
1427
1224
  /** The machine-readable condition — see {@link TerminalErrorCode}. */
1428
1225
  readonly code: TerminalErrorCode;
1429
- /** An optional context bag naming the offending prompt id. */
1226
+ /** An optional context bag naming the offending values — see the class {@link TerminalError remarks}. */
1430
1227
  readonly context?: Readonly<Record<string, unknown>>;
1431
1228
  constructor(code: TerminalErrorCode, message: string, context?: Readonly<Record<string, unknown>>);
1432
1229
  }
1433
1230
 
1434
1231
  /**
1435
1232
  * The machine-readable condition carried by a {@link import('./errors.js').TerminalError} — the
1436
- * axis a `catch` branches on. Names its axis (the failure condition), never `kind` (AGENTS §4.4).
1233
+ * axis a `catch` branches on. Names its axis (the failure condition), never `kind`.
1437
1234
  *
1438
1235
  * @remarks
1439
- * - `EXPIRE` — a parked broker prompt was not answered before its `timeout` (or the broker was
1440
- * `destroy`ed while it was still pending); the prompt's Promise rejects with this.
1441
- * - `CANCEL` — the user aborted an interactive prompt (ctrl-c) at the server `Terminal` (T-c)
1442
- * driver; the awaited prompt call rejects with this so a caller can branch on `error.code`.
1443
- * - `LIMIT` — a broker's optional `cap` on concurrently-parked prompts was already reached; the
1444
- * new call is rejected WITHOUT parking (never counted, never emitted, no timer armed).
1445
- * - `DESTROYED` — a call reached an already-`destroy`ed {@link TerminalManagerInterface} (`add`
1446
- * at entry, or `open` whose `store` read resolved after a `destroy` happened in the gap).
1236
+ * Every code here is terminal's own. A refusal that belongs to the form — a malformed schema, a
1237
+ * value a control cannot hold, a write to a settled form — arrives as the dependency's own
1238
+ * `FormError` and is never re-coded.
1239
+ *
1240
+ * - `EXPIRE` — `park` was called on an already-`destroy`ed broker: the given form is destroyed and
1241
+ * the call throws before minting an id. A parked form that times out is abandoned instead, not
1242
+ * coded `EXPIRE` — the broker destroys it and the caller's `answer` promise rejects on the
1243
+ * form's own lifecycle with the Form package's `ABANDONED` error.
1244
+ * - `CANCEL` — the user aborted at the server `Terminal` driver with ctrl-c.
1245
+ * - `DRIVER` — the driver could not read the terminal it was given.
1246
+ * - `DEADLOCK` — an endpoint was asked to answer its own question.
1247
+ * - `TARGET` — the named endpoint cannot be reached.
1248
+ * - `LIMIT` — the broker's optional `cap` on concurrently parked forms was already reached. The
1249
+ * new call is refused WITHOUT parking: no id, no `pending` event, no timer.
1250
+ * - `DESTROYED` — a call reached an already-destroyed {@link TerminalManagerInterface}.
1447
1251
  */
1448
1252
  export declare type TerminalErrorCode = 'EXPIRE' | 'CANCEL' | 'DRIVER' | 'DEADLOCK' | 'TARGET' | 'LIMIT' | 'DESTROYED';
1449
1253
 
1254
+ /**
1255
+ * The contract for asking a form of a human at a keyboard — one method, because a form is one
1256
+ * question however many fields it holds. The server `Terminal` implements it against a real TTY;
1257
+ * a {@link PromptClientInterface} holds one to answer forms parked elsewhere.
1258
+ *
1259
+ * @remarks
1260
+ * `ask` drives the form the caller passes: it walks the schema's fields in order, binds each
1261
+ * keystroke through the form's own `fill`, submits, and returns the settled values. The returned
1262
+ * promise is the form's `answer`, so a caller holding the form can await either one. Ctrl-c at an
1263
+ * interactive driver is the one exception: it rejects THIS promise with a `TerminalError` coded
1264
+ * `CANCEL` and leaves the form `editing`, so the form's own `answer` stays pending for its owner.
1265
+ *
1266
+ * A driver never owns the form's lifetime. To interrupt an active walk, destroy the form: it
1267
+ * abandons, `answer` rejects, and the driver stops rendering on the form's `abandon` event. That
1268
+ * is the only cancellation channel, which is why this contract needs no second method.
1269
+ */
1270
+ export declare interface TerminalInterface {
1271
+ ask(form: FormInterface): Promise<FormValues>;
1272
+ }
1273
+
1450
1274
  /**
1451
1275
  * The multi-endpoint terminal MANAGER — a registry of named {@link PromptInterface} brokers (one
1452
- * per endpoint), so several parties can `ask` prompts of each other by NAME with a `from` → `to`
1453
- * attribution edge on every parked prompt, and a transitive DEADLOCK check across all in-flight
1276
+ * per endpoint), so several parties can `ask` forms of each other by NAME with a `from` → `to`
1277
+ * attribution edge on every parked form, and a transitive DEADLOCK check across all in-flight
1454
1278
  * asks.
1455
1279
  *
1456
1280
  * @remarks
@@ -1461,22 +1285,23 @@ export declare type TerminalErrorCode = 'EXPIRE' | 'CANCEL' | 'DRIVER' | 'DEADLO
1461
1285
  * - **`ask`.** The target must already be mounted via {@link add} — `ask` never auto-adds it;
1462
1286
  * rejects `TARGET` for an unknown `to` (listing the known names). Rejects `DEADLOCK` when parking
1463
1287
  * `from → to` would close a cycle over the CURRENT in-flight edge set (walked transitively);
1464
- * otherwise parks through the target's broker and resolves with the ORIGINAL ticket Promise (edge
1465
- * cleanup is attached via `.then`, never altering the value/rejection the caller observes).
1288
+ * otherwise parks the caller's live form through the target's broker and returns that form's own
1289
+ * `answer` promise. Edge cleanup never alters the value or rejection the caller observes.
1466
1290
  * - **Durable open / save.** `open(name)` restores an EMPTY broker from the `store` (parked
1467
1291
  * Promises are process-bound and never resurrected); `save(name)` persists the endpoint's
1468
1292
  * configured `timeout`.
1469
- * - **Removal.** `remove` drops one endpoint or a batch (§9.2, array overload FIRST), destroying
1470
- * each broker (which expires every prompt still parked on it). `clear` drops all; `destroy` is
1471
- * idempotent.
1293
+ * - **Removal.** `remove` drops one endpoint, a batch (§9.2, array overload FIRST), or every
1294
+ * endpoint when called without an argument. It destroys each broker, which expires every form
1295
+ * still parked on it. `destroy` is idempotent.
1472
1296
  *
1473
1297
  * @example
1474
1298
  * ```ts
1299
+ * const form = createForm({ fields: [{ control: 'text', name: 'name' }] })
1475
1300
  * const manager = new TerminalManager()
1476
1301
  * manager.add('agent')
1477
- * const name = manager.ask('user', 'agent', 'input', { message: 'Your name' })
1478
- * manager.answer('agent', manager.pending('agent')[0].id, 'Ada')
1479
- * await name // 'Ada'
1302
+ * const answer = manager.ask('user', 'agent', form)
1303
+ * manager.answer('agent', manager.pending('agent')[0].id, { name: 'Ada' })
1304
+ * await answer // { name: 'Ada' }
1480
1305
  * ```
1481
1306
  */
1482
1307
  export declare class TerminalManager implements TerminalManagerInterface {
@@ -1486,91 +1311,83 @@ export declare class TerminalManager implements TerminalManagerInterface {
1486
1311
  get count(): number;
1487
1312
  terminal(name: string): PromptInterface | undefined;
1488
1313
  terminals(): readonly string[];
1489
- add(name: string, options?: TerminalOptions): PromptInterface;
1490
- ask(from: string, to: string, form: 'input' | 'password' | 'editor', options: InputOptions | PasswordOptions | EditorOptions): Promise<string>;
1491
- ask(from: string, to: string, form: 'confirm', options: ConfirmOptions): Promise<boolean>;
1492
- ask(from: string, to: string, form: 'select', options: SelectOptions): Promise<string>;
1493
- ask(from: string, to: string, form: 'checkbox', options: CheckboxOptions): Promise<readonly string[]>;
1494
- pending(): readonly PendingPrompt[];
1495
- pending(to: string): readonly PendingPrompt[];
1496
- answer(to: string, id: string, value: unknown): TerminalAnswerResult;
1314
+ add(name: string, options?: PromptOptions): PromptInterface;
1315
+ ask(from: string, to: string, form: FormInterface): Promise<FormValues>;
1316
+ pending(): readonly PendingForm[];
1317
+ pending(to: string): readonly PendingForm[];
1318
+ answer(to: string, id: string, values: FormValues): Result<FormValues, TerminalAnswerError>;
1497
1319
  open(name: string): Promise<PromptInterface | undefined>;
1498
1320
  save(name: string): Promise<boolean>;
1499
1321
  remove(names: readonly string[]): boolean;
1500
1322
  remove(name: string): boolean;
1501
- clear(): void;
1323
+ remove(): void;
1502
1324
  destroy(): void;
1503
1325
  }
1504
1326
 
1505
1327
  /**
1506
- * The manager's event map (AGENTS §13) — the name-attributed re-emission of every mounted
1507
- * broker's events, so a caller subscribes once for ALL endpoints instead of per-broker.
1328
+ * The manager's event map — the name-attributed re-emission of every mounted broker's events, so a
1329
+ * caller subscribes once for ALL endpoints instead of once per broker.
1508
1330
  *
1509
1331
  * @remarks
1510
- * - `pending` — an endpoint parked a prompt (carries the {@link PendingPrompt}, itself carrying
1511
- * `from` / `to`).
1512
- * - `answer` — an endpoint's parked prompt was answered (`to` names the endpoint).
1513
- * - `expire` — an endpoint's parked prompt timed out (`to` names the endpoint).
1332
+ * - `pending` — an endpoint parked a form; the record itself carries `from` and `to`.
1333
+ * - `answer` — an endpoint's parked form was answered; `to` names the endpoint.
1334
+ * - `expire` — an endpoint's parked form expired; `to` names the endpoint.
1514
1335
  */
1515
1336
  export declare type TerminalManagerEventMap = {
1516
- readonly pending: readonly [prompt: PendingPrompt];
1517
- readonly answer: readonly [to: string, id: string, value: unknown];
1337
+ readonly pending: readonly [form: PendingForm];
1338
+ readonly answer: readonly [to: string, id: string, values: FormValues];
1518
1339
  readonly expire: readonly [to: string, id: string];
1519
1340
  };
1520
1341
 
1521
1342
  /**
1522
- * The multi-endpoint terminal MANAGER (§9.1/§9.2) — a registry of named {@link PromptInterface}
1523
- * brokers (one per endpoint), so several parties (agents, tools, humans) can `ask` prompts of
1524
- * each other by NAME, attributed with a `from` → `to` edge on every parked {@link PendingPrompt}.
1343
+ * The multi-endpoint terminal MANAGER — a registry of named {@link PromptInterface} brokers, one
1344
+ * per endpoint, so several parties (agents, tools, humans) can ask forms of each other BY NAME,
1345
+ * attributed with a `from` → `to` edge on every parked record.
1525
1346
  *
1526
1347
  * @remarks
1527
- * - **Accessors (§9.1).** `terminal(name)` looks up one endpoint's broker; `terminals()` lists
1528
- * every mounted endpoint name.
1529
- * - **`add`** mints (or returns the existing) broker for `name` — idempotent, never clobbers a
1530
- * live endpoint.
1531
- * - **`ask`** is the attributed convenience: parks a prompt from `from` to `to` (auto-`add`ing
1532
- * `to` if absent) and resolves with the typed value, precisely overloaded per {@link PromptType}.
1533
- * - **`pending()`** lists every endpoint's parked prompts; `pending(to)` scopes to one endpoint.
1348
+ * - **Accessors.** `terminal(name)` looks up one endpoint's broker; `terminals()` lists every
1349
+ * mounted endpoint name.
1350
+ * - **`add`** mints, or returns, the broker for `name`. Idempotent; it never clobbers a live
1351
+ * endpoint.
1352
+ * - **`ask`** is the attributed convenience: it parks `form` from `from` to `to`, adding `to` if it
1353
+ * is absent, and resolves with the settled values.
1354
+ * - **`pending()`** lists every endpoint's parked records; `pending(to)` scopes to one endpoint.
1534
1355
  * - **`answer`** routes to the named endpoint's broker.
1535
- * - **`open`** restores (or returns the live) broker for `name` from the `store`.
1536
- * - **`save`** persists an endpoint's config snapshot to the `store` (`false` when there is no
1537
- * store, or `name` is unknown).
1538
- * - **Batch `remove` (§9.2).** The array overload is declared FIRST — `remove(names)` removes each
1539
- * listed endpoint (`true` when any of the named terminals was removed); `remove(name)` removes one.
1540
- * - **`clear`** removes every endpoint without destroying the manager; **`destroy`** tears down
1541
- * every broker, then the manager's own emitter.
1356
+ * - **`open`** restores, or returns the live, broker for `name` from the `store`.
1357
+ * - **`save`** persists an endpoint's config snapshot; false when there is no store, or `name` is
1358
+ * unknown.
1359
+ * - **Batch `remove`.** The array overload is declared FIRST: `remove(names)` removes each listed
1360
+ * endpoint and reports whether any named terminal was removed; `remove(name)` removes one;
1361
+ * `remove()` removes every endpoint without destroying the manager.
1362
+ * - **`destroy`** tears down every broker, then the manager's own emitter.
1542
1363
  */
1543
1364
  export declare interface TerminalManagerInterface {
1544
1365
  readonly emitter: EmitterInterface<TerminalManagerEventMap>;
1545
1366
  readonly count: number;
1546
1367
  terminal(name: string): PromptInterface | undefined;
1547
1368
  terminals(): readonly string[];
1548
- add(name: string, options?: TerminalOptions): PromptInterface;
1549
- ask(from: string, to: string, form: 'input' | 'password' | 'editor', options: InputOptions | PasswordOptions | EditorOptions): Promise<string>;
1550
- ask(from: string, to: string, form: 'confirm', options: ConfirmOptions): Promise<boolean>;
1551
- ask(from: string, to: string, form: 'select', options: SelectOptions): Promise<string>;
1552
- ask(from: string, to: string, form: 'checkbox', options: CheckboxOptions): Promise<readonly string[]>;
1553
- pending(): readonly PendingPrompt[];
1554
- pending(to: string): readonly PendingPrompt[];
1555
- answer(to: string, id: string, value: unknown): TerminalAnswerResult;
1369
+ add(name: string, options?: PromptOptions): PromptInterface;
1370
+ ask(from: string, to: string, form: FormInterface): Promise<FormValues>;
1371
+ pending(): readonly PendingForm[];
1372
+ pending(to: string): readonly PendingForm[];
1373
+ answer(to: string, id: string, values: FormValues): Result<FormValues, TerminalAnswerError>;
1556
1374
  open(name: string): Promise<PromptInterface | undefined>;
1557
1375
  save(name: string): Promise<boolean>;
1558
1376
  remove(names: readonly string[]): boolean;
1559
1377
  remove(name: string): boolean;
1560
- clear(): void;
1378
+ remove(): void;
1561
1379
  destroy(): void;
1562
1380
  }
1563
1381
 
1564
1382
  /**
1565
- * Options for {@link import('./factories.js').createTerminalManager} / the
1383
+ * Options for {@link import('./factories.js').createTerminalManager} and the
1566
1384
  * {@link TerminalManagerInterface}.
1567
1385
  *
1568
1386
  * @remarks
1569
- * - `store` — the optional {@link TerminalStoreInterface} backing `open` / `save`.
1570
- * - `timeout` / `timer` / `cap` — the manager-wide default for each endpoint's broker (overridable
1571
- * per {@link TerminalManagerInterface.add} call via {@link TerminalOptions}, exactly like
1572
- * `timeout` / `timer`).
1573
- * - `on` / `error` — the manager's {@link EmitterHooks} + {@link EmitterErrorHandler} (AGENTS §13).
1387
+ * - `store` — the optional {@link TerminalStoreInterface} backing `open` and `save`.
1388
+ * - `timeout` / `timer` / `cap` — the manager-wide default for each endpoint's broker, overridable
1389
+ * per {@link TerminalManagerInterface.add} call.
1390
+ * - `on` / `error` — the manager's own emitter hooks and listener-error handler.
1574
1391
  */
1575
1392
  export declare interface TerminalManagerOptions {
1576
1393
  readonly store?: TerminalStoreInterface;
@@ -1581,28 +1398,29 @@ export declare interface TerminalManagerOptions {
1581
1398
  readonly error?: EmitterErrorHandler;
1582
1399
  }
1583
1400
 
1584
- /** Options for {@link import('./factories.js').createTerminal} / a manager-owned {@link PromptInterface} broker — the per-endpoint `timeout` + injected `timer` + optional `cap` on concurrently-parked prompts. */
1585
- export declare interface TerminalOptions {
1586
- readonly timeout?: number;
1587
- readonly timer?: TimerHandler_2;
1588
- readonly cap?: number;
1589
- }
1590
-
1591
- /** One endpoint's persisted CONFIG snapshot — `id` is the endpoint name; `timeout` its configured default. Parked Promises are process-bound and are never resurrected — `open` always restores an EMPTY broker. */
1401
+ /**
1402
+ * One endpoint's persisted CONFIG snapshot — `id` is the endpoint name and `timeout` its configured
1403
+ * default. Parked forms are process-bound and are never resurrected, so `open` always restores an
1404
+ * EMPTY broker.
1405
+ */
1592
1406
  export declare interface TerminalSnapshot {
1593
1407
  readonly id: string;
1594
1408
  readonly timeout?: number;
1595
1409
  }
1596
1410
 
1597
- /** One opaque persisted row — the shape a `TableInterface<TerminalSnapshotRow>`-backed store reads/writes; `snapshot` is narrowed with {@link import('./validators.js').isTerminalSnapshot} on read. */
1411
+ /**
1412
+ * One opaque persisted row — the shape a `TableInterface<TerminalSnapshotRow>`-backed store reads
1413
+ * and writes. `snapshot` is narrowed with {@link import('./validators.js').isTerminalSnapshot} on
1414
+ * read.
1415
+ */
1598
1416
  export declare interface TerminalSnapshotRow {
1599
1417
  readonly id: string;
1600
1418
  readonly snapshot: unknown;
1601
1419
  }
1602
1420
 
1603
1421
  /**
1604
- * The point-access persistence seam (AGENTS §5 — Stores) for a {@link TerminalManagerInterface}'s
1605
- * endpoint configs. Every primitive is async; `delete` of an absent id is a no-op.
1422
+ * The point-access persistence seam for a {@link TerminalManagerInterface}'s endpoint configs.
1423
+ * Every primitive is async; deleting an absent id is a no-op.
1606
1424
  */
1607
1425
  export declare interface TerminalStoreInterface {
1608
1426
  get(id: string): Promise<TerminalSnapshot | undefined>;
@@ -1610,20 +1428,14 @@ export declare interface TerminalStoreInterface {
1610
1428
  delete(id: string): Promise<void>;
1611
1429
  }
1612
1430
 
1613
- /** The handle {@link PromptInterface.park} returns — the parked prompt's `id` plus the Promise that resolves (or rejects) with its {@link PromptValue}. */
1614
- export declare interface Ticket {
1615
- readonly id: string;
1616
- readonly value: Promise<PromptValue>;
1617
- }
1618
-
1619
1431
  /** Cancel a pending {@link TimerHandler} deadline — idempotent, safe to call after the timer fired. */
1620
1432
  export declare type TimerCancel = () => void;
1621
1433
 
1622
1434
  /**
1623
1435
  * One injected timer — arms a deadline `callback` to fire after `ms`, returning a
1624
1436
  * {@link TimerCancel} that cancels it. The broker's timeout seam: the default wraps the host
1625
- * `setTimeout` / `clearTimeout`; a test injects a deterministic timer that captures the callback
1626
- * and fires it on demand (no real time, no global fake-timer patching).
1437
+ * `setTimeout` and `clearTimeout`; a test injects a deterministic timer that captures the callback
1438
+ * and fires it on demand, with no real time and no global patching.
1627
1439
  */
1628
1440
  declare type TimerHandler_2 = (callback: () => void, ms: number) => TimerCancel;
1629
1441
  export { TimerHandler_2 as TimerHandler }
@@ -1631,59 +1443,12 @@ export { TimerHandler_2 as TimerHandler }
1631
1443
  /** Toggle `index` in a readonly index list — copy-on-write, returning the new sorted-by-insertion list. */
1632
1444
  export declare function toggleIndex(indices: readonly number[], index: number): readonly number[];
1633
1445
 
1634
- /** Matches an HTTP(S) URL. The `url` rule tests against this. */
1635
- export declare const URL_PATTERN: RegExp;
1636
-
1637
1446
  /**
1638
- * Declarative validation rules for a text prompt — each key toggles (or overrides) one
1639
- * built-in check. {@link import('./helpers.js').resolveValidation} compiles a `ValidationRules`
1640
- * (or a bare {@link Validator}) into ONE composed {@link Validator}.
1641
- *
1642
- * @remarks
1643
- * Each rule is EITHER a primitive that turns on the built-in check, OR a {@link Validator}
1644
- * that replaces it with custom logic:
1645
- *
1646
- * - `required` — `true` ⇒ input must be non-empty after trimming.
1647
- * - `minimum` — a number ⇒ input must be at least that many characters.
1648
- * - `maximum` — a number ⇒ input must be at most that many characters.
1649
- * - `pattern` — a string ⇒ input must match that regex source.
1650
- * - `email` — `true` ⇒ input must look like an email address.
1651
- * - `url` — `true` ⇒ input must look like an HTTP(S) URL.
1652
- * - `numeric` — `true` ⇒ input must be a number (integer or decimal).
1653
- * - `integer` — `true` ⇒ input must be an integer.
1654
- * - `alphanumeric` — `true` ⇒ input must contain only letters and digits.
1655
- * - `custom` — an arbitrary {@link Validator} escape hatch.
1656
- *
1657
- * Rules compose in the fixed order above; the FIRST failing rule short-circuits and its
1658
- * message is returned. A `false` / `undefined` rule is skipped.
1659
- */
1660
- export declare interface ValidationRules {
1661
- readonly required?: boolean | Validator;
1662
- readonly minimum?: number | Validator;
1663
- readonly maximum?: number | Validator;
1664
- readonly pattern?: string | Validator;
1665
- readonly email?: boolean | Validator;
1666
- readonly url?: boolean | Validator;
1667
- readonly numeric?: boolean | Validator;
1668
- readonly integer?: boolean | Validator;
1669
- readonly alphanumeric?: boolean | Validator;
1670
- readonly custom?: Validator;
1671
- }
1672
-
1673
- /**
1674
- * A single input validator — given the current input string, returns `true` when the input
1675
- * is valid, or an error MESSAGE string when it is not. The atomic unit the validation engine
1676
- * composes and the prompts apply on submit.
1677
- *
1678
- * @remarks
1679
- * A `Validator` is total and pure — it never throws and never mutates. The `true | string`
1680
- * shape (not `boolean`) is deliberate: an invalid result CARRIES its message, so a prompt
1681
- * can render the exact reason. {@link import('./helpers.js').composeValidators} runs several
1682
- * in order and returns the FIRST error (short-circuiting), so the most specific rule wins.
1447
+ * One SSE-shaped wire frame — the `event` name, its already-stringified `data` payload, and an
1448
+ * optional `id`. The transport-neutral shape {@link import('./helpers.js').serializePending},
1449
+ * {@link import('./helpers.js').serializeExpire}, and
1450
+ * {@link import('./helpers.js').serializeShutdown} build, with no `http` dependency.
1683
1451
  */
1684
- export declare type Validator = (input: string) => true | string;
1685
-
1686
- /** One SSE-shaped wire frame — the `event` name, its `data` payload (already JSON-stringified), and an optional `id`. The transport-neutral shape {@link import('./helpers.js').serializePending} / {@link import('./helpers.js').serializeExpire} / {@link import('./helpers.js').serializeShutdown} build, with no `http` dependency. */
1687
1452
  export declare interface WireEvent {
1688
1453
  readonly event: string;
1689
1454
  readonly data: string;