@effected/cli 0.10.0 → 0.12.0

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.
Files changed (92) hide show
  1. package/Cancelled.js +44 -0
  2. package/CliAudience.js +178 -0
  3. package/CliColor.js +13 -19
  4. package/CliEnv.js +89 -0
  5. package/CliExit.js +1 -1
  6. package/CliFailure.js +302 -0
  7. package/CliInteractive.js +71 -0
  8. package/CliLinks.js +154 -0
  9. package/CliLog.js +346 -0
  10. package/CliLogger.js +34 -33
  11. package/CliMessage.js +80 -0
  12. package/CliPrompt.js +104 -0
  13. package/CliRuntime.js +110 -54
  14. package/CliTest.js +16 -0
  15. package/CliTheme.js +141 -0
  16. package/ConfigIssueRenderer.js +14 -33
  17. package/Doc.js +536 -0
  18. package/Fmt.js +133 -0
  19. package/GithubAnnotation.js +40 -0
  20. package/Glyphs.js +83 -0
  21. package/NotInteractive.js +42 -0
  22. package/README.md +145 -131
  23. package/Render.js +255 -0
  24. package/SchemaIssueRenderer.js +7 -10
  25. package/Status.js +166 -0
  26. package/TestTerminal.js +80 -0
  27. package/Token.js +69 -0
  28. package/index.d.ts +3089 -169
  29. package/index.js +19 -1
  30. package/internal/ansi.js +230 -0
  31. package/internal/autoFormat.js +34 -0
  32. package/internal/canPrompt.js +15 -0
  33. package/internal/counts.js +84 -0
  34. package/internal/diagnostics.js +32 -0
  35. package/internal/displayWidth.js +35 -0
  36. package/internal/failureTarget.js +195 -0
  37. package/internal/fallbackAnswer.js +18 -0
  38. package/internal/fileSink.js +62 -0
  39. package/internal/format.js +62 -7
  40. package/internal/layout.js +250 -0
  41. package/internal/linkScheme.js +30 -0
  42. package/internal/linkTarget.js +50 -0
  43. package/internal/logSafety.js +46 -0
  44. package/internal/renderAnsi.js +52 -0
  45. package/internal/renderDoc.js +320 -0
  46. package/internal/renderGithubLog.js +46 -0
  47. package/internal/renderMarkdown.js +368 -0
  48. package/internal/renderPlain.js +50 -0
  49. package/internal/scanAudience.js +106 -0
  50. package/internal/splitFrame.js +56 -0
  51. package/internal/wizardGate.js +18 -0
  52. package/package.json +40 -5
  53. package/testing.d.ts +88 -2
  54. package/testing.js +2 -1
  55. package/ui/CliUi.js +432 -0
  56. package/ui/CliUiLive.js +446 -0
  57. package/ui/Confirm.js +245 -0
  58. package/ui/DocView.js +74 -0
  59. package/ui/KeyHelp.js +62 -0
  60. package/ui/KeyTable.js +199 -0
  61. package/ui/MultiSelect.js +260 -0
  62. package/ui/Select.js +230 -0
  63. package/ui/Tabs.js +202 -0
  64. package/ui/TextInput.js +290 -0
  65. package/ui/Toggle.js +32 -0
  66. package/ui/UiKey.js +44 -0
  67. package/ui/UiProvider.js +60 -0
  68. package/ui/UiStreams.js +18 -0
  69. package/ui/UiTheme.js +119 -0
  70. package/ui/Viewport.js +204 -0
  71. package/ui/internal/ErrorBoundary.js +30 -0
  72. package/ui/internal/Holder.js +74 -0
  73. package/ui/internal/ScreenContext.js +52 -0
  74. package/ui/internal/UiProviders.js +21 -0
  75. package/ui/internal/ink.js +122 -0
  76. package/ui/internal/inkChalk.js +58 -0
  77. package/ui/internal/inkConsole.js +146 -0
  78. package/ui/internal/lazyView.js +74 -0
  79. package/ui/internal/lineText.js +19 -0
  80. package/ui/internal/mountPermit.js +16 -0
  81. package/ui/internal/perfDrain.js +33 -0
  82. package/ui/internal/processStreams.js +19 -0
  83. package/ui/internal/renderOptions.js +13 -0
  84. package/ui/testing/CliUiTest.js +760 -0
  85. package/ui/testing/fakeStreams.js +79 -0
  86. package/ui/testing/terminalModel.js +59 -0
  87. package/ui-testing-serializer.d.ts +14 -0
  88. package/ui-testing-serializer.js +33 -0
  89. package/ui-testing.d.ts +527 -0
  90. package/ui-testing.js +3 -0
  91. package/ui.d.ts +1790 -0
  92. package/ui.js +17 -0
package/ui.d.ts ADDED
@@ -0,0 +1,1790 @@
1
+ import * as Cli from "@effected/cli";
2
+ import { Console, Context, Effect, Option, PubSub, Scope, Stream } from "effect";
3
+ import { Param } from "effect/cli";
4
+ import { ReactElement, ReactNode } from "react";
5
+ import { Key } from "ink";
6
+ import { ColorLevel } from "@effected/env";
7
+ //#region src/ui/CliUiLive.d.ts
8
+ /**
9
+ * Options for `CliUi.live`.
10
+ *
11
+ * @public
12
+ */
13
+ interface LiveOptions<E, S> {
14
+ /**
15
+ * The events to fold: a `PubSub` subscription, or a stream.
16
+ *
17
+ * A subscription (`PubSub.subscribe`, made before the first publish) is the surest: the view takes from it directly,
18
+ * so nothing published after the subscribe is missed, and `LiveHandle.close` folds every message still queued in it
19
+ * before the view ends. A publisher can also end it from its side, and the two ends differ:
20
+ *
21
+ * - `PubSub.end(pubsub, last)` keeps everything: the view folds what is buffered, then `last` once (core repeats a
22
+ * final message to every later take; the view takes it once), then ends. Make `last` an `isTerminal` event to
23
+ * commit the run with it; otherwise the run is committed as drawn.
24
+ * - `PubSub.shutdown` drops what the view has not taken yet. End with `close` (or `PubSub.end`) first.
25
+ *
26
+ * A stream: `live` makes its first pull before it returns, so one that subscribes on its first pull without forking
27
+ * (`Stream.fromPubSub`) is subscribed by then and sees an event published at once. One that forks its upstream
28
+ * (`Stream.merge`, `buffer`, a concurrent `flatMap`) subscribes later, and an event published before that is lost.
29
+ * `close` folds what the view has already pulled; what the stream holds and has not yielded is the stream's.
30
+ */
31
+ readonly events: Stream.Stream<E> | PubSub.Subscription<E>;
32
+ /** The state before the first event. */
33
+ readonly initial: S;
34
+ /** Fold one event into the state. The kit never resets the state: a reducer that wants a fresh run resets it. */
35
+ readonly reduce: (state: S, event: E) => S;
36
+ /**
37
+ * Draw the state. `frame` is the wall clock in ticks of `tickMillis` (`floor(now / tickMillis)`, from `Clock`), so a
38
+ * spinner keeps turning across runs. Rendered inside the kit's providers: `useTheme`, `useGlyphs`, `Styled` and
39
+ * `useTerminalSize` work in it. When the final frame is printed as a string (not interactive, or a run that degraded
40
+ * before it painted), `useTerminalSize().rows` is `Infinity`, since that frame has no height to fit: a render must
41
+ * not allocate per row.
42
+ *
43
+ * To keep the view's module (its JSX, and so React) off every run that never draws it, pass
44
+ * `CliUi.lazyView(() => import("./view.js"))`: the module is loaded only when a run first mounts or prints its frame
45
+ * with Ink, so `--help`, a usage error, and, with `final`, a run that is not interactive, never load it.
46
+ */
47
+ readonly render: (state: S, frame: number) => ReactElement;
48
+ /** Whether an event starts a run: by default, the only event that begins one (see `begins`). */
49
+ readonly isStart: (event: E) => boolean;
50
+ /**
51
+ * Whether an event begins a run while none is going, given the state before and after it is folded; `isStart` by
52
+ * default, so only a start begins one. Given, it replaces that default rather than adding to it, so keep `isStart`
53
+ * in it to begin on a start as well as on something else, such as a stream that a program joins mid-run:
54
+ * `(event, before, after) => isStart(event) || (before.phase === "idle" && after.phase !== "idle")`.
55
+ * An event that begins nothing while no run is going is folded and not drawn. A start while a run is going redraws
56
+ * that run in place, and this is not asked then; a start while a degraded run is going ends that run and begins a
57
+ * fresh one.
58
+ */
59
+ readonly begins?: (event: E, before: S, after: S) => boolean;
60
+ /** Whether an event ends a run: its frame is committed to the terminal and the view unmounts until the next. */
61
+ readonly isTerminal: (event: E) => boolean;
62
+ /**
63
+ * `"owned"` (the default) for a view that owns its output, `"hosted"` for one drawn inside a host's (a test
64
+ * reporter). They differ only when the run is not interactive: owned writes each run's final frame once, as a
65
+ * string; hosted writes nothing, its host having its own output.
66
+ */
67
+ readonly mode?: "owned" | "hosted";
68
+ /**
69
+ * The final frame of a run that is not interactive (an agent, CI, a pipe, `TERM=dumb`), as a document: given, an
70
+ * `owned` view prints each run's `final(state)` at the run's end instead of rendering `render` to a string with Ink,
71
+ * so such a run loads neither Ink nor React, nor a `CliUi.lazyView` module.
72
+ *
73
+ * @remarks
74
+ * It is called once per run, at the run's terminal event (or when the events end mid-run), with the state then, and
75
+ * replaces the string that run would have printed: never both. It is rendered as `Doc.print` renders a document:
76
+ * `Render.context("stdout")` when the environment `CliRuntime.main` builds is there (`TerminalEnv`, `Audience` and
77
+ * `CliLinks`; otherwise the stdout theme, the `Audience` if any, and no width limit), the renderer the audience gets
78
+ * (`ansi` for a person, `plain` for an agent, `githubLog` under GitHub Actions), and an agent gets no escape. It is
79
+ * written where the view writes, to `UiStreams` stdout, so a host's capture of the view's output holds it. A document
80
+ * that renders to nothing prints nothing. A `final` that throws is the run degrading: one warning, nothing printed.
81
+ *
82
+ * An interactive run never calls it, and a `hosted` view never prints either way. It need not match the Ink frame:
83
+ * it is what a reader with no terminal gets.
84
+ *
85
+ * With `final` set, `render` is never called on a run that is not interactive, not even to build a string that would
86
+ * go unused: the run's output is `final(state)` alone. (Pinned by `CliUi.live.final.test.ts`, whose watch-mode test
87
+ * counts zero `render` calls over three runs.)
88
+ */
89
+ readonly final?: ((state: S) => Cli.Document) | undefined;
90
+ /**
91
+ * The frame tick, in milliseconds; 80 by default. While a run is drawn the view redraws on every tick, so a spinner
92
+ * turns without events. Anything but a positive, finite number is a defect.
93
+ */
94
+ readonly tickMillis?: number;
95
+ /**
96
+ * Clear React's user-timing entries after every render: `true`, `false`, or `"auto"` (the default), which clears
97
+ * unless `NODE_ENV` is exactly `"production"`. React's development build records them on every render and never
98
+ * clears them. The clear is process-wide: it removes every `measure` entry, a program's own included; marks are left
99
+ * alone.
100
+ */
101
+ readonly drainPerformance?: boolean | "auto";
102
+ }
103
+ /**
104
+ * The handle of a live view.
105
+ *
106
+ * @public
107
+ */
108
+ interface LiveHandle<S> {
109
+ /** The current state of the fold. */
110
+ readonly state: Effect.Effect<S>;
111
+ /**
112
+ * A `Console` whose every method writes above the frame while a run is mounted, and straight to the stream otherwise;
113
+ * none falls through to the program's own console. Output is split as Node's console splits it: `log`, `info`,
114
+ * `debug`, `dir`, `dirxml`, `table`, `count`, `timeLog`, `timeEnd` and a group's label to stdout, and `error`,
115
+ * `warn`, `trace` and a failed `assert` to stderr; a group indents what follows. An `Error` argument is written with
116
+ * its stack. `clear` does nothing, since erasing the screen would take the scrollback above the frame. Provide it
117
+ * around the work done while the view is mounted; a line written to the terminal any other way tears the frame.
118
+ *
119
+ * `Console.Console` is the seam: the kit's logger (`CliLogger`, `CliLog`) and `Effect.log*` write through whatever
120
+ * `Console` the fiber has, so they need no reference to the view. A host whose logging lives outside the view's
121
+ * owner provides the handle's console at the top of the program it runs, and every log line under it lands above
122
+ * the frame: `Effect.provideService(program, Console.Console, handle.logConsole)`. Output that never goes through
123
+ * Effect's `Console` (another library's own `process.stderr` writes) still tears the frame.
124
+ */
125
+ readonly logConsole: Console.Console;
126
+ /**
127
+ * Completes once the events have ended (the stream ended, the subscription's `PubSub` was ended with `PubSub.end` or
128
+ * shut down, or `close` ended them) and the last run's frame is committed. Dies with what the view died of: a `reduce` that threw, or a
129
+ * stream that died.
130
+ */
131
+ readonly done: Effect.Effect<void>;
132
+ /**
133
+ * End the view cleanly, then wait for `done`.
134
+ *
135
+ * @remarks
136
+ * It stops taking events, folds what the view holds and has not folded yet, and ends the run as the events ending
137
+ * does: an `isTerminal` event among them commits its run as usual, and a run with no terminal event is committed as
138
+ * drawn (owned and not interactive: its final frame is printed once). With a subscription, that includes every
139
+ * message still queued in it, so a run's tail published just before the close is never lost, as it would be to a
140
+ * `PubSub.shutdown`. With a stream, it is what the view has already pulled; elements the stream holds and has not
141
+ * yielded are the stream's own.
142
+ *
143
+ * Safe from the moment `live` returns, before Ink has loaded: a run whose mount is still under way is mounted and
144
+ * then ended, and a view with no run to end loads nothing.
145
+ *
146
+ * Idempotent: a second `close`, concurrent or later, waits for the same end and writes nothing more. After the events
147
+ * have ended it only waits for `done`. It dies as `done` does (a `reduce` that threw, a stream that died). Closing
148
+ * the caller's scope instead of calling `close` stops the view at once (nothing still queued is folded); closing it
149
+ * after `close` releases what is left. A `close` after the scope has closed completes at once, there being nothing
150
+ * left to end, where `done` is interrupted. A host ends its view with:
151
+ *
152
+ * ```ts
153
+ * handle.close.pipe(Effect.ensuring(Scope.close(scope, Exit.void)))
154
+ * ```
155
+ */
156
+ readonly close: Effect.Effect<void>;
157
+ }
158
+ //#endregion
159
+ //#region src/ui/UiProvider.d.ts
160
+ /**
161
+ * What the kit's hooks read in a tree the kit did not mount: the theme, the glyph set, and optionally the size.
162
+ *
163
+ * @remarks
164
+ * Only `CliUi.context` mints one, because only it also loads the Ink and React the provider renders with. A value
165
+ * without the brand does not compile, which stops one being built by accident; the brand is a plain key, so a
166
+ * literal that spells it out compiles, and must never be written. Spread a minted value to change its fields
167
+ * (`{ ...value, size }`).
168
+ *
169
+ * @public
170
+ */
171
+ interface UiContextValue {
172
+ /** Set by `CliUi.context` alone; never set it yourself. */
173
+ readonly "~@effected/cli/ui/UiContextValue": true;
174
+ /** The theme of the stream the tree draws on. */
175
+ readonly theme: Cli.StreamTheme;
176
+ /** The glyph set in use. */
177
+ readonly glyphs: Cli.GlyphSet;
178
+ /** The terminal size the tree is laid out at, which `useTerminalSize` reads in place of the stdout's. */
179
+ readonly size?: {
180
+ readonly columns: number;
181
+ readonly rows: number;
182
+ };
183
+ /**
184
+ * Whether the GitHub Actions runner reads the output (`CliUi.context` sets it from `CurrentRuntimeEnv`, when one is
185
+ * provided): a `DocView` under it neutralizes any line its data would turn into a workflow command. `false` opts the
186
+ * tree out of that under GitHub Actions, the consumer's choice; a nested provider cannot clear it once a provider
187
+ * above it has set it.
188
+ */
189
+ readonly neutralizeWorkflowCommands?: boolean;
190
+ }
191
+ /**
192
+ * Props of {@link UiProvider}.
193
+ *
194
+ * @public
195
+ */
196
+ interface UiProviderProps {
197
+ /** The context, from `CliUi.context`. */
198
+ readonly value: UiContextValue;
199
+ /** The tree. */
200
+ readonly children?: ReactNode;
201
+ }
202
+ /**
203
+ * Provide the kit's context to an Ink tree the kit did not mount, so `useTheme`, `useGlyphs`, `Styled` and
204
+ * `useTerminalSize` work in it.
205
+ *
206
+ * @remarks
207
+ * Take the value from `CliUi.context`, which also loads Ink and React: the provider and the kit's hooks render with
208
+ * the modules the kit loaded. A screen mounted by `CliUi.run` already has this context.
209
+ *
210
+ * With `size`, `useTerminalSize` reads it instead of the stdout Ink draws on, less one column and one row as ever.
211
+ * Give it to Ink's `renderToString`, whose terminal hooks see the process's own stdout rather than the width it lays
212
+ * out at: `renderToString(tree, { columns })` with `size: { columns, rows }` keeps the kit's widgets cut to that
213
+ * width. A tree given a `size` no longer follows the terminal's resizes.
214
+ *
215
+ * Standalone, there is no screen to end: a kit widget's own quit key, such as `Select`'s `q`, does nothing, and an
216
+ * input handler a kit widget registers is called as is, so what it throws escapes as Ink leaves it. Nested inside a `CliUi.run` screen, it overrides only the theme, the glyphs and the size: the screen's
217
+ * cancel and its defect route pass through, so `q` still cancels and a throwing handler is still the screen's defect.
218
+ * Ink's colour level is the host's: `Styled` passes the theme's props, none at colour `none`.
219
+ *
220
+ * @param props - the context, and the tree
221
+ *
222
+ * @public
223
+ */
224
+ export declare const UiProvider: (props: UiProviderProps) => ReactElement;
225
+ //#endregion
226
+ //#region src/ui/CliUi.d.ts
227
+ /**
228
+ * How a screen ends: with a result, or cancelled for a reason.
229
+ *
230
+ * @remarks
231
+ * Only the first call counts. Resolving with an empty value (`[]`, `""`) is a result, not a cancel.
232
+ *
233
+ * @public
234
+ */
235
+ interface ScreenControl<A> {
236
+ /** End the screen with `value`. */
237
+ readonly resolve: (value: A) => void;
238
+ /** End the screen as cancelled: `"escape"` for Esc or a widget's quit key, `"interrupt"` for Ctrl-C. */
239
+ readonly cancel: (reason: "escape" | "interrupt") => void;
240
+ }
241
+ /**
242
+ * A screen: given its control, the React element to mount, or a promise of one.
243
+ *
244
+ * @remarks
245
+ * The kit's widgets sanitise the text they draw from data; a screen's own components (Ink's `Text`, `Styled`) draw
246
+ * what they are given, so text from data in them is the screen author's to pass through `Fmt.sanitize` first.
247
+ *
248
+ * @public
249
+ */
250
+ type Screen<A> = (control: ScreenControl<A>) => ReactElement | Promise<ReactElement>;
251
+ /**
252
+ * Options for {@link CliUi.run}.
253
+ *
254
+ * @public
255
+ */
256
+ interface CliUiRunOptions {
257
+ /**
258
+ * Erase the screen's last frame as it unmounts, however it ended; `false` by default, which leaves the last frame
259
+ * on the terminal, as a record of the answer.
260
+ */
261
+ readonly clear?: boolean;
262
+ }
263
+ /**
264
+ * Options for {@link CliUi.prompt}.
265
+ *
266
+ * @public
267
+ */
268
+ interface CliUiPromptOptions<A> {
269
+ /** The value to use when the run is not interactive. Without it a non-interactive run fails with `NotInteractive`. */
270
+ readonly otherwise?: A;
271
+ /** Erase the screen's last frame as it unmounts; `false` by default. See {@link CliUiRunOptions.clear}. */
272
+ readonly clear?: boolean;
273
+ }
274
+ /**
275
+ * Options for {@link CliUi.fallback}: `CliPrompt.fallback`'s, and whether the screen erases its last frame.
276
+ *
277
+ * @public
278
+ */
279
+ type CliUiFallbackOptions<A> = Cli.CliPromptFallbackOptions<A> & {
280
+ /** Erase the screen's last frame as it unmounts; `false` by default. See {@link CliUiRunOptions.clear}. */
281
+ readonly clear?: boolean;
282
+ };
283
+ /**
284
+ * Interactive screens drawn with Ink, mounted as scoped resources.
285
+ *
286
+ * @example
287
+ * ```ts
288
+ * import { CliUi, Confirm } from "@effected/cli/ui"
289
+ * import { Effect } from "effect"
290
+ *
291
+ * // Answers `otherwise` when there is no person to ask, and fails with `Cancelled` when they back out.
292
+ * const ask = CliUi.prompt(Confirm.screen({ message: "Overwrite the config?" }), {
293
+ * otherwise: { confirmed: false, toggles: {} },
294
+ * })
295
+ * ```
296
+ *
297
+ * @public
298
+ */
299
+ export declare class CliUi {
300
+ private constructor();
301
+ /**
302
+ * Mount `screen` and wait for it to resolve or cancel.
303
+ *
304
+ * @remarks
305
+ * When `CliInteractive` is false it fails with `NotInteractive` and mounts nothing; Ink and React are not even
306
+ * loaded. Otherwise it loads them, holds Ink's colour level at stdout's, and mounts the screen on
307
+ * `UiStreams` with Ink's own Ctrl-C exit off: Ctrl-C cancels with `"interrupt"` and Esc with `"escape"`.
308
+ *
309
+ * Mounting is one scoped resource. However the screen ends (resolved, cancelled, crashed, or the fiber
310
+ * interrupted), it is unmounted, raw mode and bracketed paste are off, the cursor is shown, and the colour level is
311
+ * restored. A
312
+ * component that throws is a defect, never a hang or a typed failure, and nothing of Ink's crash screen reaches
313
+ * stdout. So is a `useKeys` handler that throws; a handler a consumer registers with Ink's own `useInput` or
314
+ * `usePaste` is outside the kit, and what it throws escapes as Ink leaves it. A crash wins over an end in the same
315
+ * tick: a handler that cancels or resolves and then throws, or a component that throws before the screen has
316
+ * unmounted, is a defect, never the `Cancelled` or the value; a defect raised while the screen unmounts stays beside
317
+ * that crash in the cause rather than replacing it. An interrupt stays an interrupt, even when the tree reports a
318
+ * crash as it unmounts.
319
+ *
320
+ * A screen draws on stdout (`UiStreams`), and mounts only when `CliInteractive` is true: a human audience, a
321
+ * terminal on both stdin and stdout, and a `TERM` that is not `dumb`. `CliInteractive` reads `false` until a layer
322
+ * sets it (`CliRuntime.main`'s `env` does), so a program that never provides one always gets `NotInteractive`.
323
+ *
324
+ * Screens run one at a time, process-wide: Ink owns raw mode on the one terminal, so a second `run` waits until the
325
+ * first is released; so does a `run` while a {@link CliUi.live} view has a run drawn. A screen that itself awaits
326
+ * another `CliUi.run` therefore deadlocks, and nothing guards against it.
327
+ *
328
+ * Do not log while a screen is mounted. Ink redraws its frame by counting the lines it last wrote, and it is
329
+ * mounted with `patchConsole` off, so a line written to the terminal from elsewhere (an `Effect.log`, `CliLog`, a
330
+ * background fiber) lands inside the frame and tears it. Log before the screen mounts or after it resolves.
331
+ *
332
+ * With `clear` the last frame is erased as the screen unmounts, so a wizard of several screens leaves only what the
333
+ * program prints; without it the last frame stays, with the highlight where the answer was.
334
+ *
335
+ * @param screen - builds the element to mount from its {@link ScreenControl}
336
+ * @param options - whether to erase the last frame
337
+ */
338
+ static readonly run: <A>(screen: Screen<A>, options?: CliUiRunOptions) => Effect.Effect<A, Cli.Cancelled | Cli.NotInteractive, Cli.CliTheme>;
339
+ /**
340
+ * The kit's context for an Ink tree the kit did not mount: stdout's theme and glyph set.
341
+ *
342
+ * @remarks
343
+ * Hand it to {@link UiProvider}. It loads Ink and React, as a screen's mount does, so the provider and the kit's
344
+ * hooks can render; a missing peer is a defect naming both. It is the only way to get a `UiContextValue`.
345
+ *
346
+ * Ink loads asynchronously, so this is an `Effect` that must run before the first render. A renderer that is itself
347
+ * synchronous (a test helper, a report-time `renderToString`) runs it once, with a top-level `await` at module
348
+ * scope, and then renders synchronously from the value as often as it likes:
349
+ *
350
+ * ```ts
351
+ * const value = await Effect.runPromise(CliUi.context.pipe(Effect.provide(themeLayer)))
352
+ *
353
+ * // later, synchronously:
354
+ * const text = renderToString(createElement(UiProvider, { value: { ...value, size: { columns, rows } } }, tree), {
355
+ * columns,
356
+ * })
357
+ * ```
358
+ */
359
+ static readonly context: Effect.Effect<UiContextValue, never, Cli.CliTheme>;
360
+ /**
361
+ * A live view over a stream: fold `events` into state, and draw it with Ink while a run is going, for the caller's
362
+ * scope.
363
+ *
364
+ * @remarks
365
+ * `events` is a `PubSub` subscription or a stream, folded in a fiber of the caller's scope. A subscription, made
366
+ * before the first publish, is the surest: nothing published after it is missed. `live` makes a stream's first pull
367
+ * before it returns, so one that subscribes on its first pull without forking (`Stream.fromPubSub`) is subscribed by
368
+ * then; one that forks its upstream (`Stream.merge`, `buffer`, a concurrent `flatMap`) subscribes later, and loses
369
+ * what is published before.
370
+ *
371
+ * `live` returns its handle at once, before Ink has loaded: it loads Ink when a run first mounts (or, when not
372
+ * interactive, when an owned run without a `final` prints its final frame), and it waits on nothing asynchronous before returning, so
373
+ * a host outside Effect can take the handle with `Effect.runSync`. The handle works from the start: a `close`
374
+ * before any run has mounted folds what is queued and ends the view as the events ending would, waiting for a mount
375
+ * already under way, and one with no run to end loads nothing.
376
+ *
377
+ * End the view with `handle.close`: it stops taking events, folds what is still queued (a subscription's queued
378
+ * messages included), commits or prints the run as the events ending would, and waits for `done`. Then close the
379
+ * scope. A publisher may instead end a subscription with `PubSub.end(pubsub, last)`, which keeps everything: the view
380
+ * folds what is buffered and `last` once, then ends. A `PubSub.shutdown` drops what the view has not taken yet, and
381
+ * closing the scope by itself stops the fold at once: both lose a run's tail. Closing the scope unmounts whatever is drawn: the terminal is restored (the
382
+ * cursor shown, Ink's colour level put back) and nothing more is written. `done` completes when the events end.
383
+ *
384
+ * A run begins at an `isStart` event (or wherever `begins` says, given the state before and after the event) and ends
385
+ * at an `isTerminal` event; an event while no run is going that begins none is folded and not drawn, so what a program
386
+ * reports after a run ends never mounts a second copy of it. A run mounts the view; its end unmounts it, which leaves
387
+ * its last frame on the terminal, and the next run mounts afresh below it. A start while a run is drawn redraws in
388
+ * place: the frame is never cleared, so nothing above it is erased. The state is never reset by the kit: a reducer that wants a fresh run
389
+ * resets it on the start.
390
+ *
391
+ * The frame is at most the terminal's rows less one, re-read on every render and on a resize, so a tall frame never
392
+ * makes Ink wipe the scrollback; its width is Ink's own. The clamp
393
+ * lags one paint when the terminal gets shorter: Ink re-lays out and repaints the tree it already has on a resize,
394
+ * before React re-renders with the new row count, so a frame already at the old height can be drawn once taller
395
+ * than the terminal, which Ink answers by clearing the screen and its scrollback. Only a shrink in height while
396
+ * the frame is at its full height does it; a frame that keeps a few rows spare never meets it.
397
+ *
398
+ * While a run is drawn the view also redraws on a tick of `tickMillis` (80 by default), a schedule in the run's
399
+ * scope: interrupted with the run or the scope, it never outlives them. Its timer is not unref'd, so while a run is
400
+ * drawn it keeps the process alive: the run's terminal event, or the scope's close, is what lets the process exit.
401
+ * The frame index never steps back. Events that arrive at
402
+ * once, in one chunk or in several the view had not yet caught up with, are folded together and drawn once. The view
403
+ * takes events from `events` as fast as the stream yields them, so a stream that applies backpressure buffers in the
404
+ * view while it draws.
405
+ *
406
+ * A run whose drawing fails (a `render` that throws, or a mount that fails) degrades rather than ending the view:
407
+ * it is unmounted, leaving its last good frame on the terminal, then one warning is logged (`Effect.logWarning`),
408
+ * and the fold goes on. At its terminal event, a run with no frame left on the terminal (it never painted, or its
409
+ * last good frame threw too) writes its final frame once, as a string. The next run mounts afresh, and so does a
410
+ * start that comes while a degraded run is going: it ends that run as its terminal event would. A `reduce` that
411
+ * throws, or an `events` stream that dies, unmounts the run, then `done` dies with the error.
412
+ *
413
+ * When the run is not interactive, nothing is mounted and Ink is loaded only when a string is due. In the `owned`
414
+ * mode (the default) each run's final frame is written once to stdout, as a string laid out at stdout's width (80
415
+ * when it reports none) with no height to fit, at its terminal event or when the stream ends. It is escape-free at
416
+ * colour `none`, and for an agent audience (`Audience`, when provided) whatever the terminal could do. With a
417
+ * `final` document, that document is printed instead, once per run, rendered as `Doc.print` renders it, and Ink,
418
+ * React and a `CliUi.lazyView` module are never loaded: an agent, CI or piped run of a command with a live view pays
419
+ * for none of them. In the `hosted` mode nothing is written.
420
+ *
421
+ * Keep React off the runs that never draw (`--help`, a usage error) with `render: CliUi.lazyView(() => import(...))`.
422
+ *
423
+ * No input is mounted: the view reads no keys and never enters raw mode, so Ctrl-C stays the platform's SIGINT,
424
+ * which interrupts the program and so closes the scope. Each run holds the process-wide mount permit from its
425
+ * mount to its end, so a `CliUi.run` during a run waits for the run to end, and one between runs mounts at once.
426
+ *
427
+ * While a run is drawn, write logs through `logConsole`, provided around the work the view reports on: its lines
428
+ * land above the frame. A line written to the terminal any other way tears the frame.
429
+ *
430
+ * The view draws on stdout (`UiStreams`), at stdout's colour level and glyphs, and mounts only when the run is
431
+ * interactive (`CliInteractive`).
432
+ *
433
+ * @param options - the events, the fold, the drawing, and what starts and ends a run
434
+ */
435
+ static readonly live: <E, S>(options: LiveOptions<E, S>) => Effect.Effect<LiveHandle<S>, never, Scope.Scope | Cli.CliTheme>;
436
+ /**
437
+ * Run `screen` from a handler when the run is interactive; otherwise answer with `otherwise`, or fail with
438
+ * `NotInteractive` when there is none.
439
+ *
440
+ * @remarks
441
+ * `CliUi.run` with a default: not interactive, it returns `otherwise` and Ink and React are never loaded.
442
+ * Interactive, it mounts the screen, and a cancel is the typed `Cancelled` a handler can catch, which
443
+ * `CliRuntime.main` otherwise renders as one line with exit `130`. A missing Ink in an interactive run is a defect
444
+ * naming the peers, never a silent `otherwise`. Screens in sequence make a wizard: discover the defaults first,
445
+ * pass each as an `otherwise`, and a non-interactive run returns exactly them.
446
+ *
447
+ * Without `otherwise`, `NotInteractive` stays in the error type even after the caller checked `CliInteractive`, since
448
+ * the type cannot know. Catch the tag and fail with `CliError.UserError` to exit as a usage error (`64` under
449
+ * `CliRuntime.main`).
450
+ *
451
+ * As with `CliUi.run`, do not log while the screen is mounted: a line written to the terminal from elsewhere tears
452
+ * the frame.
453
+ *
454
+ * @param screen - the screen to show
455
+ * @param options - the non-interactive default, and whether to erase the last frame
456
+ */
457
+ static readonly prompt: <A>(screen: Screen<A>, options?: CliUiPromptOptions<A>) => Effect.Effect<A, Cli.Cancelled | Cli.NotInteractive, Cli.CliTheme>;
458
+ /**
459
+ * A fallback for `Flag.withFallbackPrompt` or `Argument.withFallbackPrompt` that shows a screen when the run is
460
+ * interactive: `CliPrompt.fallback` for screens.
461
+ *
462
+ * @remarks
463
+ * Interactive, the screen mounts and its answer is the parameter's value. Not interactive, `otherwise` is used when
464
+ * given and Ink and React are never loaded; without it the parameter fails as missing, exactly as with no fallback,
465
+ * so core renders its own message and `CliRuntime.main` exits `64`. Name the parameter with `flag` (the name
466
+ * without dashes) or `argument` so that error can be built.
467
+ *
468
+ * The options are {@link @effected/cli!CliPromptFallbackOptions}, the same as `CliPrompt.fallback`'s, and `clear`
469
+ * as for {@link CliUi.run}.
470
+ *
471
+ * It runs during parsing, whose environment is core's alone, so it reads `CliTheme` if one is there: with
472
+ * `CliRuntime.main`'s `env` (`CliEnv.layer`), or provided around the program. With no theme it treats the run as
473
+ * not interactive, and when `CliInteractive` is on it says so once, at debug level. Interactivity is
474
+ * `CliInteractive`, which an audience flag can set before parsing under `CliAudience`.
475
+ *
476
+ * As with `CliPrompt.fallback`, the screen runs here rather than being handed to core, whose fallback runner
477
+ * turns a quit into the missing-parameter error, which would exit `64`. A cancel (Esc, Ctrl-C) is `Cancelled`,
478
+ * raised as a defect because core's parse step turns every typed failure into a usage error, so only
479
+ * `CliRuntime.main` (or `CliRuntime.reportFailures`) renders it, as one line with exit `130`. A missing Ink in an
480
+ * interactive run is a defect naming the peers, never a silent `otherwise`.
481
+ *
482
+ * After the screen has unmounted, core still runs the answered `Prompt.succeed` it is handed against the
483
+ * terminal, exactly as it does for `CliPrompt.fallback`: `Prompt.run` opens the terminal's input in a scope (on
484
+ * Node a readline over stdin, in raw mode) before looking at the prompt. That is harmless. The prompt is already
485
+ * answered, so nothing is read and no key is waited for; the scope closes at once, restoring the mode and closing
486
+ * the reader; and Ink has already let go of stdin, so the two never hold it together. Not interactive, the screen
487
+ * never mounts, and `CliPrompt.gateTerminal`, which `CliEnv.layer` installs, keeps that subscription off the real
488
+ * terminal altogether.
489
+ *
490
+ * @param screen - the screen to show
491
+ * @param options - the parameter it stands in for, the non-interactive default, and whether to erase the last frame
492
+ */
493
+ static readonly fallback: <A>(screen: Screen<A>, options: CliUiFallbackOptions<A>) => Param.FallbackPrompt<A>;
494
+ /**
495
+ * A screen whose module is loaded only when it mounts, so importing the command that uses it loads neither the
496
+ * screen's own code nor React.
497
+ *
498
+ * @param load - imports the module whose default export is the screen
499
+ */
500
+ static readonly lazy: <A>(load: () => Promise<{
501
+ readonly default: Screen<A>;
502
+ }>) => Screen<A>;
503
+ /**
504
+ * A live view's `render` whose module is loaded only when a run first draws it, so importing the command that uses
505
+ * the view loads neither the view's own code nor React: `CliUi.lazy` for `CliUi.live`.
506
+ *
507
+ * @remarks
508
+ * `load` resolves to the view, `(state, frame) => ReactElement`, exactly what `render` takes, so the frame index a
509
+ * spinner needs reaches it: either a module whose default export is the view (`() => import("./view.js")`), or the
510
+ * view itself (`() => import("./views.js").then((module) => module.syncView)`, for a named export).
511
+ *
512
+ * It is optional: `render` still takes the view directly, and needs no dynamic import. A view passed directly
513
+ * loads with the module that imports it, so it costs React on every run that loads that module; `lazyView` is
514
+ * how a command keeps React off the runs that never draw. `CliUi.live` loads the module before a run mounts with Ink, or before it
515
+ * prints a run's final frame as a string; a run that is not interactive and has a `final` document never loads it,
516
+ * nor Ink, nor React. An import that fails degrades the run, as a render that throws does: one warning, and the next
517
+ * run tries the import again. A load that resolves to no view (neither a function nor a module whose `default` is
518
+ * one) is a programming error, and deterministic, so it is kept for the view's life: every run degrades without
519
+ * loading again, and the view warns once, saying what it received and what is expected, rather than once per run. A
520
+ * view function that happens to carry a `default` property is the view.
521
+ *
522
+ * The returned function is for `CliUi.live`'s `render` alone: called before its module has loaded, it throws.
523
+ *
524
+ * Keep the `LiveOptions` (the state, the events and the `lazyView` call) in a module the view does not import. The
525
+ * view module usually imports the state's types or its fold from somewhere; if that somewhere is the module that
526
+ * holds the `import("./view.js")`, the dynamic import closes a cycle, which Biome's `noImportCycles` reports even
527
+ * though it is lazy. A layout that stays acyclic: the model (state, events, fold) in one module, the view importing
528
+ * the model, and the options (with `lazyView`) in a third that imports the model and loads the view.
529
+ *
530
+ * ```ts
531
+ * // commands/sync.ts: no JSX, no React
532
+ * const view = yield* CliUi.live({
533
+ * events,
534
+ * initial,
535
+ * reduce,
536
+ * render: CliUi.lazyView(() => import("./sync-view.js")),
537
+ * final: (state) => [Doc.paragraph(`${state.done} synced`)],
538
+ * isStart,
539
+ * isTerminal,
540
+ * })
541
+ * ```
542
+ *
543
+ * @param load - resolves to the view, or to a module whose default export is the view
544
+ */
545
+ static readonly lazyView: <S>(load: () => Promise<((state: S, frame: number) => ReactElement) | {
546
+ readonly default: (state: S, frame: number) => ReactElement;
547
+ }>) => (state: S, frame: number) => ReactElement;
548
+ /**
549
+ * A screen whose answer is `f` of `screen`'s: it mounts `screen` and resolves with `f(value)` when `screen` resolves
550
+ * with `value`.
551
+ *
552
+ * @remarks
553
+ * Only the resolve is mapped. A cancel passes through unchanged, as the same `Cancelled`, and so does everything
554
+ * else about the screen: what it draws, its keys, a lazy load. `f` runs when the screen resolves; what it throws is
555
+ * thrown from the screen's resolve, so it is a defect of the run, as any other throw in a key handler is.
556
+ *
557
+ * The mapped screen is a `Screen` like any other, so it goes wherever a screen goes: `CliUi.run`, `CliUi.prompt`,
558
+ * `CliUi.fallback`, or around a `CliUi.lazy` one. The commonest use is a `Confirm` behind a boolean flag, where the
559
+ * fallback needs a `Screen<boolean>` and `Confirm` answers a whole `ConfirmResult`:
560
+ *
561
+ * ```ts
562
+ * const yes = Flag.Boolean("yes").pipe(
563
+ * Flag.withFallbackPrompt(
564
+ * CliUi.fallback(
565
+ * CliUi.map(Confirm.screen({ message: "Publish?" }), (result) => result.confirmed),
566
+ * { flag: "yes", otherwise: false },
567
+ * ),
568
+ * ),
569
+ * )
570
+ * ```
571
+ *
572
+ * @param screen - the screen to show
573
+ * @param f - turns its answer into the mapped screen's
574
+ */
575
+ static readonly map: <A, B>(screen: Screen<A>, f: (value: A) => B) => Screen<B>;
576
+ }
577
+ //#endregion
578
+ //#region src/ui/UiKey.d.ts
579
+ /**
580
+ * The named keys a screen understands and a test can press.
581
+ *
582
+ * @remarks
583
+ * Letters, digits and punctuation are not named: they arrive as typed text.
584
+ *
585
+ * @public
586
+ */
587
+ type KeyName = "up" | "down" | "left" | "right" | "enter" | "space" | "tab" | "shift+tab" | "backspace" | "delete" | "escape" | "ctrl+c" | "home" | "end" | "pageup" | "pagedown";
588
+ /**
589
+ * A key as a screen sees it: a named key, or typed text.
590
+ *
591
+ * @remarks
592
+ * Space is always `Named("space")`, never `Char(" ")`, so a key table binds it once. A `Char` is what Ink delivers
593
+ * as one input, so a paste arrives as one `Char` holding the pasted text.
594
+ *
595
+ * @public
596
+ */
597
+ export type UiKey = {
598
+ readonly _tag: "Named";
599
+ readonly name: KeyName;
600
+ } | {
601
+ readonly _tag: "Char";
602
+ readonly char: string;
603
+ };
604
+ /**
605
+ * Normalising Ink's input into {@link UiKey}s.
606
+ *
607
+ * @public
608
+ */
609
+ export declare const UiKey: {
610
+ /**
611
+ * The key Ink reported to a `useInput` handler, or `undefined` for one the kit does not name: a Ctrl or Meta
612
+ * combination other than Ctrl-C, or an input holding a control character.
613
+ */
614
+ readonly fromInk: (input: string, key: Key) => UiKey | undefined;
615
+ /** A named key. */
616
+ readonly named: (name: KeyName) => UiKey;
617
+ /** Typed text. */
618
+ readonly char: (char: string) => UiKey;
619
+ };
620
+ //#endregion
621
+ //#region src/ui/KeyTable.d.ts
622
+ /**
623
+ * One row of a {@link KeyTable}: the keys that trigger an action, and the help line that names it.
624
+ *
625
+ * @public
626
+ */
627
+ interface Binding<Action> {
628
+ /** The keys, named or typed (`{ char: "q" }`); any of them triggers the action. */
629
+ readonly keys: ReadonlyArray<KeyName | {
630
+ readonly char: string;
631
+ }>;
632
+ /** What the keys do. */
633
+ readonly action: Action;
634
+ /** The words `KeyHelp` shows beside the keys. */
635
+ readonly help: string;
636
+ /** Bound but left out of the help line. */
637
+ readonly hidden?: boolean;
638
+ }
639
+ /**
640
+ * One entry of a key table's help: the key labels and what they do.
641
+ *
642
+ * @public
643
+ */
644
+ interface KeyHelpRow {
645
+ /** The keys, labelled for the glyph set and joined with `/`. */
646
+ readonly label: string;
647
+ /** What they do. */
648
+ readonly help: string;
649
+ }
650
+ /**
651
+ * Options for {@link useKeys}.
652
+ *
653
+ * @public
654
+ */
655
+ interface UseKeysOptions {
656
+ /** Whether the keys are read; `true` by default. */
657
+ readonly isActive?: boolean;
658
+ }
659
+ /**
660
+ * The keys a widget understands, as data: the one source both for dispatching input and for the help line, so the
661
+ * two cannot drift apart.
662
+ *
663
+ * @remarks
664
+ * Read it with {@link useKeys}, whose handler must step from current state, never render-closure state: several keys
665
+ * from one stdin read are dispatched before React re-renders.
666
+ *
667
+ * @public
668
+ */
669
+ export declare class KeyTable<Action> {
670
+ /** The bindings, in priority order. */
671
+ readonly bindings: ReadonlyArray<Binding<Action>>;
672
+ private constructor();
673
+ /**
674
+ * A table from its bindings. When two bindings share a key, the first wins. A `{ char: " " }` key is stored as
675
+ * the named `"space"`, the only form in which Ink reports a space.
676
+ *
677
+ * @param bindings - the bindings, in priority order
678
+ */
679
+ static readonly make: <Action_1>(bindings: ReadonlyArray<Binding<Action_1>>) => KeyTable<Action_1>;
680
+ /**
681
+ * The keys every screen has: Esc cancels with `"escape"` (help: cancel), and Ctrl-C cancels with `"interrupt"`,
682
+ * bound but hidden. `q` is never a root key: it belongs to a widget's own table, so a text input can type it.
683
+ */
684
+ static readonly root: KeyTable<"escape" | "interrupt">;
685
+ /**
686
+ * The action of the first binding that holds `key`, or `None`.
687
+ *
688
+ * @param key - the key pressed
689
+ */
690
+ readonly match: (key: UiKey) => Option.Option<Action>;
691
+ /**
692
+ * The help rows of every binding not hidden that can still fire, in order, labelled for `glyphs`: `↑/↓` under
693
+ * Unicode, `up/down` under ASCII.
694
+ *
695
+ * @remarks
696
+ * A key an earlier binding already holds (hidden or not) can never fire a later one, so a later binding is
697
+ * labelled with its remaining keys only, and left out when none remain.
698
+ *
699
+ * @param glyphs - the glyph set the labels are drawn with
700
+ */
701
+ readonly help: (glyphs: Cli.GlyphSet) => ReadonlyArray<KeyHelpRow>;
702
+ }
703
+ /**
704
+ * Read the keys of `table` and dispatch the action each one matches; keys the table does not bind are ignored.
705
+ *
706
+ * @remarks
707
+ * One Ink `useInput` per call, and nothing else reads input.
708
+ *
709
+ * Text read in one go (`"yy"`, `"y\r"`) reaches Ink's `useInput` as one string; it is split here into a key per
710
+ * grapheme (a decomposed letter or a ZWJ emoji is one key), a line break (CR, LF or CR LF) as one enter, a tab as tab, a
711
+ * space as space, so `{ char: "y" }` matches each `y`. A `{ char }` binding matches in NFC, whichever form was typed.
712
+ * A bracketed paste never reaches it: the screen takes pastes on Ink's paste channel, so pasted text cannot press a
713
+ * widget's keys (a pasted `q` does not cancel); `TextInput` reads pastes as text.
714
+ *
715
+ * Several keys from one stdin read (a fast typist, a held arrow, a terminal that batches) are each dispatched
716
+ * before React re-renders, so `dispatch` must never step from state captured in the render that created it: the
717
+ * second key would see the first key's starting point and repeat its move. Step with a functional update
718
+ * (`setState((current) => step(current, action))`), a `useReducer` dispatch, or a ref the handler itself advances.
719
+ *
720
+ * Inside a screen mounted by `CliUi.run`, a `dispatch` that throws ends the screen as a defect carrying the error, as a
721
+ * component that throws in render does; it never escapes as an uncaught exception.
722
+ *
723
+ * @param table - the keys to read
724
+ * @param dispatch - receives each matched action
725
+ * @param options - whether the keys are read
726
+ *
727
+ * @public
728
+ */
729
+ export declare const useKeys: <Action>(table: KeyTable<Action>, dispatch: (action: Action) => void, options?: UseKeysOptions) => void;
730
+ //#endregion
731
+ //#region src/ui/Confirm.d.ts
732
+ /**
733
+ * An extra on/off row a {@link Confirm} hosts beneath its yes/no answer.
734
+ *
735
+ * @public
736
+ */
737
+ interface ConfirmToggle<K extends string> {
738
+ /** Names the toggle in the result. */
739
+ readonly key: K;
740
+ /** What the row shows. */
741
+ readonly label: string;
742
+ /** Whether it starts on. */
743
+ readonly value: boolean;
744
+ }
745
+ /**
746
+ * What a {@link Confirm} resolves with: the answer, and every toggle by key.
747
+ *
748
+ * @public
749
+ */
750
+ interface ConfirmResult<K extends string> {
751
+ /** Yes or no. */
752
+ readonly confirmed: boolean;
753
+ /**
754
+ * Each toggle's final value, by its key. Partial, because a toggle passed conditionally may be absent: read one
755
+ * with a fallback, `toggles.promote ?? false`.
756
+ */
757
+ readonly toggles: Readonly<Partial<Record<K, boolean>>>;
758
+ }
759
+ /**
760
+ * Where a {@link Confirm} is.
761
+ *
762
+ * @public
763
+ */
764
+ interface ConfirmState<K extends string> {
765
+ /** The answer. */
766
+ readonly confirmed: boolean;
767
+ /** The highlighted row: 0 is the yes/no row, each toggle follows. */
768
+ readonly row: number;
769
+ /** The toggles and their current values. */
770
+ readonly toggles: ReadonlyArray<ConfirmToggle<K>>;
771
+ /** Whether enter was pressed. */
772
+ readonly submitted: boolean;
773
+ }
774
+ /**
775
+ * What a key does in a {@link Confirm}.
776
+ *
777
+ * @public
778
+ */
779
+ type ConfirmAction = "up" | "down" | "toggle" | "yes" | "no" | "flip" | "submit" | "cancel";
780
+ /**
781
+ * Options for {@link Confirm.init}.
782
+ *
783
+ * @public
784
+ */
785
+ interface ConfirmInitOptions<K extends string> {
786
+ /** The starting answer; no (`false`) by default. */
787
+ readonly initial?: boolean;
788
+ /** Extra on/off rows, with unique keys. */
789
+ readonly toggles?: ReadonlyArray<ConfirmToggle<K>>;
790
+ }
791
+ /**
792
+ * Options for {@link Confirm.screen}.
793
+ *
794
+ * @public
795
+ */
796
+ interface ConfirmScreenOptions<K extends string> extends ConfirmInitOptions<K> {
797
+ /** The question. */
798
+ readonly message: string;
799
+ }
800
+ /**
801
+ * Props of {@link Confirm.View}.
802
+ *
803
+ * @public
804
+ */
805
+ interface ConfirmViewProps<K extends string> extends ConfirmScreenOptions<K> {
806
+ /** Receives the answer and the toggles when enter is pressed. */
807
+ readonly onSubmit: (result: ConfirmResult<K>) => void;
808
+ }
809
+ /**
810
+ * A yes/no question, optionally with extra on/off rows beneath it: a pure reducer, its key table, a view and a
811
+ * ready-made screen.
812
+ *
813
+ * @remarks
814
+ * The rows are the yes/no row first, then each toggle. `y` and `n` set the answer and `←`/`→` flip it, from any
815
+ * row; `↑`/`↓` move between rows; space flips the highlighted toggle and does nothing on the yes/no row; enter
816
+ * submits; `q` cancels with `"escape"`. Toggle keys must be unique; `init` throws, and `screen` dies, on a repeat.
817
+ * With no toggles, the help line leaves out the row and toggle keys. Toggles that do not fit the terminal scroll
818
+ * in a window under the answer row, so the question, the answer and the help line stay on screen.
819
+ * The answer starts as no unless `initial` says otherwise, and `←`/`→` flip it whichever row is highlighted.
820
+ *
821
+ * A toggle passed conditionally is absent from the result when it was left out, so read it back with a fallback:
822
+ *
823
+ * ```ts
824
+ * import { CliUi, Confirm } from "@effected/cli/ui"
825
+ * import { Effect } from "effect"
826
+ *
827
+ * const publish = (drafts: number) =>
828
+ * Effect.gen(function* () {
829
+ * const { confirmed, toggles } = yield* CliUi.run(
830
+ * Confirm.screen({
831
+ * message: "Publish the release?",
832
+ * toggles: drafts > 0 ? [{ key: "promote", label: `promote ${drafts} drafts to stable`, value: true }] : [],
833
+ * }),
834
+ * )
835
+ * const promote = toggles.promote ?? false
836
+ * return { confirmed, promote }
837
+ * })
838
+ * ```
839
+ *
840
+ * @public
841
+ */
842
+ export declare class Confirm {
843
+ private constructor();
844
+ /**
845
+ * A confirm with its answer (no by default) and toggles, on the yes/no row.
846
+ *
847
+ * @param options - the starting answer and the toggles
848
+ */
849
+ static readonly init: <K extends string>(options?: ConfirmInitOptions<K>) => ConfirmState<K>;
850
+ /**
851
+ * Apply an action (see the class remarks for what each key does).
852
+ *
853
+ * @param state - where the confirm is
854
+ * @param action - the action
855
+ */
856
+ static readonly step: <K extends string>(state: ConfirmState<K>, action: ConfirmAction) => ConfirmState<K>;
857
+ /**
858
+ * The answer and every toggle's value by key.
859
+ *
860
+ * @param state - where the confirm is
861
+ */
862
+ static readonly result: <K extends string>(state: ConfirmState<K>) => ConfirmResult<K>;
863
+ /** The keys: y yes, n no, ←/→ flip, ↑/↓ row, space toggle, enter submit, q cancel (the view hides row and toggle from its help when there are no toggles). */
864
+ static readonly keys: KeyTable<ConfirmAction>;
865
+ /**
866
+ * Draw the confirm: the question, the answer row (`[Yes] No` or ` Yes [No]`, the chosen answer in brackets so it
867
+ * shows without colour, and in the accent token), a {@link Toggle} row per toggle, and the key help.
868
+ *
869
+ * @remarks
870
+ * Single-shot, like `Select.View`: the options are read once at mount.
871
+ *
872
+ * @param props - the question, the starting answer, the toggles and where the result goes
873
+ */
874
+ static readonly View: <K extends string>(props: ConfirmViewProps<K>) => ReactElement;
875
+ /**
876
+ * A ready-made screen for `CliUi.run`: the confirm, resolving with the answer and the toggles.
877
+ *
878
+ * @param options - the question, the starting answer and the toggles
879
+ */
880
+ static readonly screen: <K extends string>(options: ConfirmScreenOptions<K>) => Screen<ConfirmResult<K>>;
881
+ }
882
+ //#endregion
883
+ //#region src/ui/DocView.d.ts
884
+ /**
885
+ * Props of {@link DocView}.
886
+ *
887
+ * @public
888
+ */
889
+ interface DocViewProps {
890
+ /** The document, or one block of it, which is drawn as a one-block document. */
891
+ readonly doc: Cli.Document | Cli.Block;
892
+ /**
893
+ * The render context. Omitted, it is built from the tree's theme (`useTheme`): its colour, its `paint` (token
894
+ * overrides included) and its glyphs, the width `useTerminalSize().columns`, a human audience, links off, the
895
+ * identity `displayPath`, and `neutralizeWorkflowCommands` when the tree is under the GitHub Actions runner. Given,
896
+ * it replaces that context entirely, neutralizing included (set `neutralizeWorkflowCommands` on it when the runner
897
+ * reads the output), and the view needs no provider.
898
+ */
899
+ readonly ctx?: Cli.RenderContext;
900
+ }
901
+ /**
902
+ * The kit's document IR (`Doc`) drawn as Ink rows, laid out by the kit's own renderers, so a live view and a static
903
+ * report show a document the same way.
904
+ *
905
+ * @remarks
906
+ * The document is rendered with `Render.ansi` (`Render.plain` at colour `none`) at the width, and each line becomes
907
+ * one Ink `Text` row cut with `wrap: "truncate-end"`, so Ink never re-wraps what the renderer laid out. Everything the
908
+ * static renderers do holds: a collapsible is drawn open, an annotation is skipped, text from data is sanitised.
909
+ *
910
+ * Without a `ctx` the view takes its theme from the tree (a screen, a live view, or a `UiProvider`) and its width from
911
+ * `useTerminalSize`, so it follows a resize; for an agent the theme is colourless, so the view is escape-free. Links
912
+ * are off. The layout is memoised on the document's identity and the context: re-render with the same document, as a
913
+ * live view's tick does, and the renderer does not run again; build a new document only when it changes.
914
+ *
915
+ * @param props - the document, and optionally the render context
916
+ *
917
+ * @public
918
+ */
919
+ export declare const DocView: (props: DocViewProps) => ReactElement;
920
+ //#endregion
921
+ //#region src/ui/KeyHelp.d.ts
922
+ /**
923
+ * Props of {@link KeyHelp}.
924
+ *
925
+ * @public
926
+ */
927
+ interface KeyHelpProps {
928
+ /** The tables to describe, in order. */
929
+ readonly tables: ReadonlyArray<KeyTable<unknown>>;
930
+ /** Whether to end with the root keys (`esc cancel`); `true` by default. */
931
+ readonly root?: boolean;
932
+ }
933
+ /**
934
+ * A one-line footer naming every visible binding of the given tables, then the root keys:
935
+ * `↑/↓ move · space toggle · enter continue · esc cancel`.
936
+ *
937
+ * @remarks
938
+ * Drawn from the same tables that dispatch the keys, so the help cannot name a key the screen ignores. Neighbouring
939
+ * rows with the same help share one entry (`↑/↓ move`). It stays one line, cut to the terminal width with the glyph
940
+ * set's ellipsis; when it must be cut, the widget's own keys give way and the root hint (`esc cancel`) stays whole at
941
+ * the end. Painted with the `muted` token; labels follow the screen's glyph set.
942
+ *
943
+ * @param props - the tables, and whether to append the root keys
944
+ *
945
+ * @public
946
+ */
947
+ export declare const KeyHelp: (props: KeyHelpProps) => ReactElement;
948
+ //#endregion
949
+ //#region src/ui/Viewport.d.ts
950
+ /**
951
+ * Where a viewport is: the selected item, the first item in view, how many items fit, and how many there are.
952
+ *
953
+ * @remarks
954
+ * Every number counts items, never section headers. The reducer keeps `0 <= cursor < count` (or both 0 when there
955
+ * are no items) and `offset <= cursor < offset + height`, and never lets the window run past the last item.
956
+ *
957
+ * @public
958
+ */
959
+ interface ViewportState {
960
+ /** The selected item. */
961
+ readonly cursor: number;
962
+ /** The first item in view. */
963
+ readonly offset: number;
964
+ /** How many items the window holds. */
965
+ readonly height: number;
966
+ /** How many items there are. */
967
+ readonly count: number;
968
+ }
969
+ /**
970
+ * A row a viewport shows: a section header, or an item. Only items are selectable. An item's `key` is its React
971
+ * key in the view, so keys must be unique within one list: `Viewport.View` dies on a repeat.
972
+ *
973
+ * @public
974
+ */
975
+ type ViewportRow = {
976
+ readonly _tag: "Header";
977
+ readonly label: string;
978
+ } | {
979
+ readonly _tag: "Item";
980
+ readonly key: string;
981
+ };
982
+ /**
983
+ * A move through a viewport.
984
+ *
985
+ * @public
986
+ */
987
+ type ViewportMove = "up" | "down" | "home" | "end" | "pageup" | "pagedown";
988
+ /**
989
+ * Props of {@link Viewport.View}.
990
+ *
991
+ * @public
992
+ */
993
+ interface ViewportViewProps {
994
+ /** The rows, headers and items, in order; the items are what `state` counts. */
995
+ readonly rows: ReadonlyArray<ViewportRow>;
996
+ /** Where the viewport is. */
997
+ readonly state: ViewportState;
998
+ /** Draws one row; `highlighted` is true for the selected item. Each row is clipped to one line. */
999
+ readonly renderRow: (row: ViewportRow, highlighted: boolean) => ReactElement;
1000
+ /** Lines the rest of the screen uses (a title, a help line), taken off the terminal height; 0 by default. */
1001
+ readonly reserved?: number;
1002
+ }
1003
+ /**
1004
+ * A scrolling list: a pure reducer over a window of items, its key table, and a view that draws the window.
1005
+ *
1006
+ * @remarks
1007
+ * The view never draws more lines than fit: its height is `min(state.height, terminal rows - 1 - reserved)`, and
1008
+ * every row is clipped to one line of `columns - 1` cells, so a frame never fills the terminal and Ink never clears
1009
+ * the screen and scrollback to redraw it. A section header stays visible: when the header of the first visible item
1010
+ * has scrolled off, it is drawn again atop the slice.
1011
+ *
1012
+ * @public
1013
+ */
1014
+ export declare class Viewport {
1015
+ private constructor();
1016
+ /**
1017
+ * A viewport over `count` items, `height` of them in view, with `cursor` selected (clamped; 0 by default).
1018
+ *
1019
+ * @param count - how many items
1020
+ * @param height - how many fit (at least 1)
1021
+ * @param cursor - the item to select
1022
+ */
1023
+ static readonly init: (count: number, height: number, cursor?: number) => ViewportState;
1024
+ /**
1025
+ * Move the cursor, clamped at both ends with no wrap, keeping it in view. A page is the window height.
1026
+ *
1027
+ * @param state - where the viewport is
1028
+ * @param move - the move
1029
+ */
1030
+ static readonly step: (state: ViewportState, move: ViewportMove) => ViewportState;
1031
+ /**
1032
+ * Change the window height, keeping the cursor where it is and in view.
1033
+ *
1034
+ * @param state - where the viewport is
1035
+ * @param height - the new height (at least 1)
1036
+ */
1037
+ static readonly resize: (state: ViewportState, height: number) => ViewportState;
1038
+ /** The keys: ↑/↓ move, pgup/pgdn page, home top, end bottom. */
1039
+ static readonly keys: KeyTable<ViewportMove>;
1040
+ /**
1041
+ * Draw the window: the visible rows, each clipped to one line. A repeated item key is a defect: the view throws, so
1042
+ * the screen dies with the reason.
1043
+ *
1044
+ * @param props - the rows, the state, how to draw a row, and the lines reserved for the rest of the screen
1045
+ */
1046
+ static readonly View: (props: ViewportViewProps) => ReactElement;
1047
+ }
1048
+ //#endregion
1049
+ //#region src/ui/MultiSelect.d.ts
1050
+ /**
1051
+ * One item of a {@link MultiSelect} section.
1052
+ *
1053
+ * @public
1054
+ */
1055
+ interface MultiSelectItem<A> {
1056
+ /** Identifies the item within its section. */
1057
+ readonly key: string;
1058
+ /** What the row shows. */
1059
+ readonly label: string;
1060
+ /** What selecting it returns. */
1061
+ readonly value: A;
1062
+ /** A line shown, muted, beneath the list while this item is highlighted. */
1063
+ readonly detail?: string;
1064
+ /** Whether it starts selected. */
1065
+ readonly selected?: boolean;
1066
+ }
1067
+ /**
1068
+ * A titled group of items; the title is a header the cursor never stops on.
1069
+ *
1070
+ * @public
1071
+ */
1072
+ interface MultiSelectSection<A> {
1073
+ /** The header. */
1074
+ readonly title: string;
1075
+ /** The items, in order. */
1076
+ readonly items: ReadonlyArray<MultiSelectItem<A>>;
1077
+ }
1078
+ /**
1079
+ * Where a {@link MultiSelect} is: its sections, which items are selected, the viewport over the items, and whether
1080
+ * it was submitted.
1081
+ *
1082
+ * @remarks
1083
+ * Items are numbered across sections in order, section by section; `chosen` holds those numbers.
1084
+ *
1085
+ * @public
1086
+ */
1087
+ interface MultiSelectState<A> {
1088
+ /** The sections. */
1089
+ readonly sections: ReadonlyArray<MultiSelectSection<A>>;
1090
+ /** The selected items, by their number across all sections. */
1091
+ readonly chosen: ReadonlySet<number>;
1092
+ /** The highlighted item and the window over the list; it counts items only, never headers. */
1093
+ readonly viewport: ViewportState;
1094
+ /** Whether enter was pressed. */
1095
+ readonly submitted: boolean;
1096
+ }
1097
+ /**
1098
+ * What a key does in a {@link MultiSelect}.
1099
+ *
1100
+ * @public
1101
+ */
1102
+ type MultiSelectAction = ViewportMove | "toggle" | "toggleSection" | "submit" | "cancel";
1103
+ /**
1104
+ * Options for {@link MultiSelect.init}.
1105
+ *
1106
+ * @public
1107
+ */
1108
+ interface MultiSelectInitOptions {
1109
+ /** How many rows the list shows at most; the terminal height also limits it. 10 by default. */
1110
+ readonly height?: number;
1111
+ }
1112
+ /**
1113
+ * Options for {@link MultiSelect.screen}.
1114
+ *
1115
+ * @public
1116
+ */
1117
+ interface MultiSelectScreenOptions<A> {
1118
+ /** The question, shown above the list. */
1119
+ readonly message: string;
1120
+ /** The sections. */
1121
+ readonly sections: ReadonlyArray<MultiSelectSection<A>>;
1122
+ /** How many rows the list shows at most. */
1123
+ readonly height?: number;
1124
+ }
1125
+ /**
1126
+ * Props of {@link MultiSelect.View}.
1127
+ *
1128
+ * @public
1129
+ */
1130
+ interface MultiSelectViewProps<A> extends MultiSelectScreenOptions<A> {
1131
+ /** Receives the selected values, in section then item order, when enter is pressed. */
1132
+ readonly onSubmit: (values: ReadonlyArray<A>) => void;
1133
+ }
1134
+ /**
1135
+ * Several choices from sectioned lists: a pure reducer, its key table, a view and a ready-made screen.
1136
+ *
1137
+ * Item keys must be unique across all sections; `init` throws, and `screen` dies, on a repeat.
1138
+ *
1139
+ * @remarks
1140
+ * The cursor moves over items only; section titles are headers drawn by the viewport, which keeps a scrolled-off
1141
+ * header visible. Submitting with nothing selected resolves an empty list, which is a result, not a cancel.
1142
+ *
1143
+ * @example
1144
+ * ```ts
1145
+ * import { CliUi, MultiSelect } from "@effected/cli/ui"
1146
+ * import { Effect } from "effect"
1147
+ *
1148
+ * const pickFeatures = Effect.gen(function* () {
1149
+ * const features = yield* CliUi.run(
1150
+ * MultiSelect.screen({
1151
+ * message: "Which features?",
1152
+ * sections: [
1153
+ * {
1154
+ * title: "Tooling",
1155
+ * items: [
1156
+ * { key: "lint", label: "Linting", value: "lint", selected: true },
1157
+ * { key: "test", label: "Tests", value: "test" },
1158
+ * ],
1159
+ * },
1160
+ * ],
1161
+ * }),
1162
+ * )
1163
+ * return features
1164
+ * })
1165
+ * ```
1166
+ *
1167
+ * @public
1168
+ */
1169
+ export declare class MultiSelect {
1170
+ private constructor();
1171
+ /**
1172
+ * A multi-select over `sections`, each item starting as its own `selected` flag says, on the first item.
1173
+ *
1174
+ * @param sections - the sections
1175
+ * @param options - the list height
1176
+ */
1177
+ static readonly init: <A>(sections: ReadonlyArray<MultiSelectSection<A>>, options?: MultiSelectInitOptions) => MultiSelectState<A>;
1178
+ /**
1179
+ * Apply an action: a viewport move over the items; `"toggle"` flips the highlighted item; `"toggleSection"`
1180
+ * selects every item of the highlighted item's section while any is unselected, and clears them all otherwise;
1181
+ * `"submit"` marks it submitted; `"cancel"` changes nothing here, because ending the screen is the view's job.
1182
+ *
1183
+ * @param state - where the multi-select is
1184
+ * @param action - the action
1185
+ */
1186
+ static readonly step: <A>(state: MultiSelectState<A>, action: MultiSelectAction) => MultiSelectState<A>;
1187
+ /**
1188
+ * The selected values, in section order and then item order, however they were toggled.
1189
+ *
1190
+ * @param state - where the multi-select is
1191
+ */
1192
+ static readonly selected: <A>(state: MultiSelectState<A>) => ReadonlyArray<A>;
1193
+ /** The keys: ↑/↓ move (page, home and end too), space toggle, a toggle section, enter continue, q cancel. */
1194
+ static readonly keys: KeyTable<MultiSelectAction>;
1195
+ /**
1196
+ * Draw the multi-select: the message, the sections (each item a check glyph, `◉`/`◯` or `[x]`/`[ ]` under ASCII,
1197
+ * then its label cut to the width; the highlighted one in the accent token with the arrow glyph), the highlighted
1198
+ * item's detail, and the key help. Enter calls `onSubmit` with the selected values; `q` cancels with `"escape"`.
1199
+ *
1200
+ * @remarks
1201
+ * Single-shot, like `Select.View`: the sections are read once at mount.
1202
+ *
1203
+ * @param props - the message, the sections, and where the selection goes
1204
+ */
1205
+ static readonly View: <A>(props: MultiSelectViewProps<A>) => ReactElement;
1206
+ /**
1207
+ * A ready-made screen for `CliUi.run`: the multi-select, resolving with the selected values (`[]` when none are).
1208
+ *
1209
+ * @param options - the message, the sections and the list height
1210
+ */
1211
+ static readonly screen: <A>(options: MultiSelectScreenOptions<A>) => Screen<ReadonlyArray<A>>;
1212
+ }
1213
+ //#endregion
1214
+ //#region src/ui/Select.d.ts
1215
+ /**
1216
+ * One choice of a {@link Select}.
1217
+ *
1218
+ * @public
1219
+ */
1220
+ interface SelectChoice<A> {
1221
+ /** What the row shows. */
1222
+ readonly label: string;
1223
+ /** What choosing it returns. */
1224
+ readonly value: A;
1225
+ /** A line shown, muted, beneath the list while this choice is highlighted. */
1226
+ readonly detail?: string;
1227
+ /** Shown muted and never highlighted: moves skip it. */
1228
+ readonly disabled?: boolean;
1229
+ }
1230
+ /**
1231
+ * Where a {@link Select} is: its choices, the viewport over them, and whether one was submitted.
1232
+ *
1233
+ * @public
1234
+ */
1235
+ interface SelectState<A> {
1236
+ /** The choices. */
1237
+ readonly choices: ReadonlyArray<SelectChoice<A>>;
1238
+ /** The highlighted choice and the window over the list. */
1239
+ readonly viewport: ViewportState;
1240
+ /** Whether the highlighted choice was submitted. */
1241
+ readonly submitted: boolean;
1242
+ }
1243
+ /**
1244
+ * What a key does in a {@link Select}: a viewport move, submit, or cancel.
1245
+ *
1246
+ * @public
1247
+ */
1248
+ type SelectAction = ViewportMove | "submit" | "cancel";
1249
+ /**
1250
+ * Options for {@link Select.init}.
1251
+ *
1252
+ * @public
1253
+ */
1254
+ interface SelectInitOptions {
1255
+ /**
1256
+ * The choice to start on: the first enabled one at or after it, or the nearest enabled one before it when none
1257
+ * follows. 0 by default.
1258
+ */
1259
+ readonly initial?: number;
1260
+ /** How many rows the list shows at most; the terminal height also limits it. 10 by default. */
1261
+ readonly height?: number;
1262
+ }
1263
+ /**
1264
+ * Props of {@link Select.View}.
1265
+ *
1266
+ * @public
1267
+ */
1268
+ interface SelectViewProps<A> {
1269
+ /** The question, shown above the list. */
1270
+ readonly message: string;
1271
+ /** The choices. */
1272
+ readonly choices: ReadonlyArray<SelectChoice<A>>;
1273
+ /** The choice to start on. */
1274
+ readonly initial?: number;
1275
+ /** How many rows the list shows at most. */
1276
+ readonly height?: number;
1277
+ /** Receives the value chosen with enter. */
1278
+ readonly onSubmit: (value: A) => void;
1279
+ }
1280
+ /**
1281
+ * Options for {@link Select.screen}.
1282
+ *
1283
+ * @public
1284
+ */
1285
+ interface SelectScreenOptions<A> {
1286
+ /** The question, shown above the list. */
1287
+ readonly message: string;
1288
+ /** The choices. */
1289
+ readonly choices: ReadonlyArray<SelectChoice<A>>;
1290
+ /** The choice to start on. */
1291
+ readonly initial?: number;
1292
+ /** How many rows the list shows at most. */
1293
+ readonly height?: number;
1294
+ }
1295
+ /**
1296
+ * A single choice from a list: a pure reducer, its key table, a view, and a ready-made screen.
1297
+ *
1298
+ * @remarks
1299
+ * A select with no enabled choice is a programming error: `init` throws, and `screen` dies, saying so.
1300
+ *
1301
+ * @example
1302
+ * ```ts
1303
+ * import { CliUi, Select } from "@effected/cli/ui"
1304
+ * import { Effect } from "effect"
1305
+ *
1306
+ * const pickTarget = Effect.gen(function* () {
1307
+ * const target = yield* CliUi.run(
1308
+ * Select.screen({
1309
+ * message: "Deploy to which environment?",
1310
+ * choices: [
1311
+ * { label: "staging", value: "staging" },
1312
+ * { label: "production", value: "production", detail: "Needs approval" },
1313
+ * ],
1314
+ * }),
1315
+ * )
1316
+ * return target
1317
+ * })
1318
+ * ```
1319
+ *
1320
+ * @public
1321
+ */
1322
+ export declare class Select {
1323
+ private constructor();
1324
+ /**
1325
+ * A select over `choices`, on the first enabled choice at or after `initial` (the nearest enabled one before it
1326
+ * when none follows).
1327
+ *
1328
+ * @param choices - the choices
1329
+ * @param options - the starting choice and the list height
1330
+ */
1331
+ static readonly init: <A>(choices: ReadonlyArray<SelectChoice<A>>, options?: SelectInitOptions) => SelectState<A>;
1332
+ /**
1333
+ * Apply an action: a move lands on the nearest enabled choice, never on a disabled one and never past either
1334
+ * end; `"submit"` marks the highlighted choice chosen (a disabled one never is); `"cancel"` changes nothing here,
1335
+ * because ending the screen is the view's job.
1336
+ *
1337
+ * @param state - where the select is
1338
+ * @param action - the action
1339
+ */
1340
+ static readonly step: <A>(state: SelectState<A>, action: SelectAction) => SelectState<A>;
1341
+ /**
1342
+ * The submitted value, or `None` before a submit.
1343
+ *
1344
+ * @param state - where the select is
1345
+ */
1346
+ static readonly chosen: <A>(state: SelectState<A>) => Option.Option<A>;
1347
+ /** The keys: the viewport's moves, enter choose, q cancel. */
1348
+ static readonly keys: KeyTable<SelectAction>;
1349
+ /**
1350
+ * Draw the select: the message, the list (the highlighted row in the accent token with the arrow glyph, disabled
1351
+ * rows muted, and at colour `none` ending in ` (disabled)` instead, every row cut to the width with the glyph set's
1352
+ * ellipsis), the highlighted choice's detail, and the
1353
+ * key help.
1354
+ *
1355
+ * @remarks
1356
+ * A choice's `detail` is drawn only while that choice is highlighted, as one muted line under the list, so the others'
1357
+ * details are not on screen until the cursor reaches them. Enter calls `onSubmit` with the value; `q` cancels the screen with `"escape"`.
1358
+ *
1359
+ * Single-shot: the choices and the starting choice are read once, when the view mounts, and later changes to them
1360
+ * are ignored; after a submit it stays as it is. Render a new view (a new screen) to ask again.
1361
+ *
1362
+ * @param props - the message, the choices, and where the chosen value goes
1363
+ */
1364
+ static readonly View: <A>(props: SelectViewProps<A>) => ReactElement;
1365
+ /**
1366
+ * A ready-made screen for `CliUi.run`: the select, resolving with the chosen value.
1367
+ *
1368
+ * @param options - the message, the choices, the starting choice and the list height
1369
+ */
1370
+ static readonly screen: <A>(options: SelectScreenOptions<A>) => Screen<A>;
1371
+ }
1372
+ //#endregion
1373
+ //#region src/ui/Tabs.d.ts
1374
+ /**
1375
+ * What a key does to a {@link Tabs} row: move to the previous or next tab (wrapping), or jump to one by index.
1376
+ *
1377
+ * @public
1378
+ */
1379
+ type TabsAction = "prev" | "next" | {
1380
+ readonly jump: number;
1381
+ };
1382
+ /**
1383
+ * One tab.
1384
+ *
1385
+ * @public
1386
+ */
1387
+ interface Tab<N extends string> {
1388
+ /** Identifies the tab; `onChange` and `value` speak in names. */
1389
+ readonly name: N;
1390
+ /** What the tab shows. */
1391
+ readonly label: string;
1392
+ }
1393
+ /**
1394
+ * Props of {@link Tabs.View}.
1395
+ *
1396
+ * @public
1397
+ */
1398
+ interface TabsProps<N extends string> {
1399
+ /** The tabs, in order. */
1400
+ readonly tabs: ReadonlyArray<Tab<N>>;
1401
+ /** The active tab, when the caller controls it: keys then only ask, through `onChange`. */
1402
+ readonly value?: N;
1403
+ /** The starting tab when uncontrolled; the first tab by default. */
1404
+ readonly defaultValue?: N;
1405
+ /** Called with the starting tab once on mount, then with every tab a key asks for. */
1406
+ readonly onChange?: (name: N, index: number) => void;
1407
+ /**
1408
+ * Whether the tabs read keys; `true` by default. Unfocused, every tab is muted (the active one still underlined)
1409
+ * and keys are ignored.
1410
+ */
1411
+ readonly isFocused?: boolean;
1412
+ /** Number each tab, `1. Label`; `false` by default. */
1413
+ readonly showIndex?: boolean;
1414
+ /** Between tabs in a row; ` │ ` by default (` | ` under ASCII glyphs). */
1415
+ readonly separator?: string;
1416
+ /** A row (`←`/`→`) or a column (`↑`/`↓`) of tabs; a row by default. */
1417
+ readonly direction?: "row" | "column";
1418
+ }
1419
+ /**
1420
+ * A row (or column) of tabs, for a consumer's own screen.
1421
+ *
1422
+ * @remarks
1423
+ * Not a screen: render `Tabs.View` inside a consumer's own screen. Its keys come from one `useKeys`, so there is a
1424
+ * single input reader. Tab and Shift-Tab cycle whenever the tabs are focused, and plain digits jump, so a screen that
1425
+ * hosts Tabs beside another widget reading Tab, arrows or digits (a `TextInput`, say) must decide who has the keys:
1426
+ * pass `isFocused: false` to the Tabs while the other widget is being typed into. When unfocused, Tabs reads no key
1427
+ * and is drawn muted.
1428
+ *
1429
+ * @public
1430
+ */
1431
+ export declare class Tabs {
1432
+ private constructor();
1433
+ /**
1434
+ * The tab an action lands on: `"prev"` and `"next"` wrap at both ends; `{ jump }` moves to that index and does
1435
+ * nothing when it is out of range.
1436
+ *
1437
+ * @param index - the active tab
1438
+ * @param count - how many tabs there are
1439
+ * @param action - the action
1440
+ */
1441
+ static readonly step: (index: number, count: number, action: TabsAction) => number;
1442
+ /** The keys of a row: `←`/`→` and Tab/Shift-Tab switch, plain digits 1–9 jump, 0 is the tenth tab. */
1443
+ static readonly keys: KeyTable<TabsAction>;
1444
+ /** The keys of a column: `↑`/`↓` in place of `←`/`→`. */
1445
+ static readonly columnKeys: KeyTable<TabsAction>;
1446
+ /**
1447
+ * Draw the tabs: the active one in the accent token, bold and underlined; the others plain; every tab muted while
1448
+ * unfocused. At colour `"none"`, where all of that vanishes, the active tab is bracketed, `[Alpha]`, and the others
1449
+ * padded a space each side. A row wider than the terminal shows the tabs that fit around the active one, with the
1450
+ * glyph set's ellipsis at a cut edge, so it never wraps.
1451
+ *
1452
+ * @remarks
1453
+ * Controlled when `value` is given: a key calls `onChange` and the active tab moves only when `value` does.
1454
+ * Uncontrolled otherwise, starting at `defaultValue` or the first tab. Either way `onChange` fires once on mount
1455
+ * with the starting tab.
1456
+ *
1457
+ * @param props - the tabs and how they behave
1458
+ */
1459
+ static readonly View: <N extends string>(props: TabsProps<N>) => ReactElement;
1460
+ }
1461
+ //#endregion
1462
+ //#region src/ui/TextInput.d.ts
1463
+ /**
1464
+ * Where a {@link TextInput} is: its value, the cursor within it, and whether enter was pressed.
1465
+ *
1466
+ * @public
1467
+ */
1468
+ interface TextInputState {
1469
+ /** The text. */
1470
+ readonly value: string;
1471
+ /**
1472
+ * The insertion point, from 0 to the value's length, in UTF-16 code units. Left, right, backspace and delete move
1473
+ * and delete by grapheme, so a character built from several code points (an emoji, a flag, a letter with a
1474
+ * combining accent) is crossed and deleted whole, and the cursor never stops inside one it moved over.
1475
+ */
1476
+ readonly cursor: number;
1477
+ /** Whether enter was pressed; the view submits only when the value also validates. */
1478
+ readonly submitted: boolean;
1479
+ }
1480
+ /**
1481
+ * Options for {@link TextInput.init}.
1482
+ *
1483
+ * @public
1484
+ */
1485
+ interface TextInputInitOptions {
1486
+ /** The starting text; the cursor starts after it. */
1487
+ readonly initial?: string;
1488
+ }
1489
+ /**
1490
+ * Options for {@link TextInput.screen}.
1491
+ *
1492
+ * @public
1493
+ */
1494
+ interface TextInputScreenOptions {
1495
+ /** The question, shown above the input. */
1496
+ readonly message: string;
1497
+ /** The starting text. */
1498
+ readonly initial?: string;
1499
+ /** Shown, muted, while the value is empty. */
1500
+ readonly placeholder?: string;
1501
+ /** Returns a message when the value cannot be submitted, or `undefined` when it can; given the real text. */
1502
+ readonly validate?: (value: string) => string | undefined;
1503
+ /**
1504
+ * Draw the value masked, so a secret typed or pasted into it is never drawn: one mask per grapheme, so an emoji, a
1505
+ * flag or a letter with a combining accent is one mask, not one per code unit. `true` masks with `•` (`*` under ASCII
1506
+ * glyphs); a string masks with that string, its controls removed. A predicate, `(value) => boolean`, is asked with
1507
+ * the real value and masks with `•` from the first render it answers `true`, and then LATCHES: the value stays masked
1508
+ * through every edit after (deleting a pasted token's first character never redraws the rest in clear) until the
1509
+ * value is cleared to empty. So a field that holds an address (an `op://` reference) stays readable while typed and
1510
+ * hides a value the moment it looks like a token. Match a giveaway ANYWHERE in the value, never as a prefix:
1511
+ * `(value) => /gh[pousr]_|github_pat_/.test(value)` masks `op://v/` followed by a pasted token, which a prefix
1512
+ * match never would. Frames drawn before it first answered `true` showed the text typed so far; a paste arrives
1513
+ * whole, so a pasted token is masked from its first frame. Unmasked by default.
1514
+ *
1515
+ * @remarks
1516
+ * Only the drawing changes: `validate` and the resolved value get the real text, the cursor moves through it as
1517
+ * before, and the placeholder still shows while the value is empty. A masked frame never holds the text, so neither
1518
+ * does the scrollback; erase the frame as well with `clear: true` on the run when even the mask's length should not
1519
+ * stay behind.
1520
+ *
1521
+ * The message `validate` returns is drawn as it is, unmasked: a message that echoes the value (`"ghp_abc is a
1522
+ * token"`) draws the secret in the frame. Say what is wrong without quoting the value.
1523
+ */
1524
+ readonly mask?: string | true | ((value: string) => boolean);
1525
+ }
1526
+ /**
1527
+ * Props of {@link TextInput.View}.
1528
+ *
1529
+ * @public
1530
+ */
1531
+ interface TextInputViewProps extends TextInputScreenOptions {
1532
+ /** Receives the value when enter is pressed and it validates. */
1533
+ readonly onSubmit: (value: string) => void;
1534
+ }
1535
+ /**
1536
+ * One line of text: a pure reducer, a view and a ready-made screen.
1537
+ *
1538
+ * @remarks
1539
+ * Every typed character is text, `q` included: the input binds no letter, so Esc and Ctrl-C are the screen's root
1540
+ * keys and still cancel with `"escape"` and `"interrupt"`.
1541
+ *
1542
+ * @example
1543
+ * ```ts
1544
+ * import { CliUi, TextInput } from "@effected/cli/ui"
1545
+ * import { Effect } from "effect"
1546
+ *
1547
+ * const askName = Effect.gen(function* () {
1548
+ * const name = yield* CliUi.run(
1549
+ * TextInput.screen({
1550
+ * message: "Package name?",
1551
+ * placeholder: "my-package",
1552
+ * validate: (value) => (value.trim() === "" ? "A name is required" : undefined),
1553
+ * }),
1554
+ * )
1555
+ * return name
1556
+ * })
1557
+ * ```
1558
+ *
1559
+ * @public
1560
+ */
1561
+ export declare class TextInput {
1562
+ private constructor();
1563
+ /**
1564
+ * An input holding `initial`, the cursor after it.
1565
+ *
1566
+ * @param options - the starting text
1567
+ */
1568
+ static readonly init: (options?: TextInputInitOptions) => TextInputState;
1569
+ /**
1570
+ * Apply a key: a typed character (any, `q` included) or space is inserted at the cursor; backspace and delete
1571
+ * remove the grapheme before or after it; left and right move it a grapheme, home and end to either end, clamped
1572
+ * to the text; enter marks it submitted. Every other key changes nothing.
1573
+ *
1574
+ * @param state - where the input is
1575
+ * @param key - the key pressed
1576
+ */
1577
+ static readonly step: (state: TextInputState, key: UiKey) => TextInputState;
1578
+ /**
1579
+ * Draw the input: the message, the value with the cursor shown as `▏` (`|` under ASCII glyphs, so it stays visible
1580
+ * without colour), or one mask per grapheme in its place with `mask`, the placeholder while empty, a validation
1581
+ * message in the error token, and the key help. Enter
1582
+ * submits when `validate` passes; otherwise its message is shown until the next key other than enter, or a paste.
1583
+ *
1584
+ * @remarks
1585
+ * Text read in one go (a fast typist) is typed as it reads: printable runs are inserted whole, a return submits
1586
+ * what came before it (anything after it is dropped), a backspace byte deletes, a line feed becomes a space, and a
1587
+ * tab or other control character is dropped. A bracketed paste is inserted as text, its line breaks as spaces, and
1588
+ * never submits.
1589
+ *
1590
+ * @param props - the message, the starting text, the placeholder, the validator and where the value goes
1591
+ */
1592
+ static readonly View: (props: TextInputViewProps) => ReactElement;
1593
+ /**
1594
+ * A ready-made screen for `CliUi.run`: the input, resolving with the submitted text.
1595
+ *
1596
+ * @remarks
1597
+ * With `mask`, a secret is drawn as one mask per grapheme and never as itself, while the screen still resolves with
1598
+ * the real text:
1599
+ *
1600
+ * ```ts
1601
+ * const token = CliUi.run(TextInput.screen({ message: "Token reference?", mask: true }), { clear: true })
1602
+ * ```
1603
+ *
1604
+ * @param options - the message, the starting text, the placeholder, the validator and the mask
1605
+ */
1606
+ static readonly screen: (options: TextInputScreenOptions) => Screen<string>;
1607
+ }
1608
+ //#endregion
1609
+ //#region src/ui/Toggle.d.ts
1610
+ /**
1611
+ * Props of {@link Toggle.View}.
1612
+ *
1613
+ * @public
1614
+ */
1615
+ interface ToggleViewProps {
1616
+ /** What the toggle controls. */
1617
+ readonly label: string;
1618
+ /** Whether it is on. */
1619
+ readonly value: boolean;
1620
+ /** Whether the row is the highlighted one. */
1621
+ readonly highlighted: boolean;
1622
+ }
1623
+ /**
1624
+ * An on/off row: a check glyph and a label.
1625
+ *
1626
+ * @public
1627
+ */
1628
+ export declare class Toggle {
1629
+ private constructor();
1630
+ /**
1631
+ * Draw a toggle row: `◉` on or `◯` off (`[x]` and `[ ]` under ASCII glyphs), then the label, cut to the width with
1632
+ * the glyph set's ellipsis. A highlighted row starts with the arrow glyph and is painted with the accent token.
1633
+ *
1634
+ * @param props - the label, the value and whether the row is highlighted
1635
+ */
1636
+ static readonly View: (props: ToggleViewProps) => ReactElement;
1637
+ }
1638
+ //#endregion
1639
+ //#region src/ui/UiStreams.d.ts
1640
+ /**
1641
+ * The streams a screen mounts on: Node streams, because Ink's stream contract is Node's.
1642
+ *
1643
+ * @remarks
1644
+ * `stdin` must offer `isTTY`, `setRawMode`, `ref` and `unref`, and emit `readable`; `stdout` and `stderr` offer
1645
+ * `columns`, `rows`, `isTTY` and `write`, and emit `resize`.
1646
+ *
1647
+ * The members are typed with Node's own stream types (`NodeJS.ReadStream`, `NodeJS.WriteStream`), as Ink's are, so a
1648
+ * TypeScript consumer of `./ui` needs `@types/node`, beside the optional peer `@types/react` for its React types.
1649
+ *
1650
+ * @public
1651
+ */
1652
+ interface UiStreamsShape {
1653
+ /** The input a screen reads keys from. */
1654
+ readonly stdin: NodeJS.ReadStream;
1655
+ /** The output a screen draws on. */
1656
+ readonly stdout: NodeJS.WriteStream;
1657
+ /** The error output, which Ink also binds. */
1658
+ readonly stderr: NodeJS.WriteStream;
1659
+ }
1660
+ declare const UiStreams_base: Context.Reference<UiStreamsShape>;
1661
+ /**
1662
+ * The streams a screen mounts on, the process's own standard streams by default.
1663
+ *
1664
+ * @remarks
1665
+ * A `Context.Reference`, so it never appears in `R`: the default reads the process streams when first used, never
1666
+ * at import, and a test provides in-memory streams with `Effect.provideService(UiStreams, streams)`. `./ui` binds
1667
+ * Node's process streams.
1668
+ *
1669
+ * @public
1670
+ */
1671
+ export declare class UiStreams extends UiStreams_base {}
1672
+ //#endregion
1673
+ //#region src/ui/UiTheme.d.ts
1674
+ /**
1675
+ * The styling props of an Ink `Text` that a {@link @effected/cli!Style} maps to.
1676
+ *
1677
+ * @public
1678
+ */
1679
+ interface InkTextProps {
1680
+ /** The foreground: a colour name or a `#rrggbb` hex. */
1681
+ readonly color?: string;
1682
+ /** Bold. */
1683
+ readonly bold?: boolean;
1684
+ /** Dim. */
1685
+ readonly dimColor?: boolean;
1686
+ /** Italic. */
1687
+ readonly italic?: boolean;
1688
+ /** Underline. */
1689
+ readonly underline?: boolean;
1690
+ }
1691
+ /**
1692
+ * Props of {@link Styled}.
1693
+ *
1694
+ * @public
1695
+ */
1696
+ interface StyledProps {
1697
+ /** A theme token, or an explicit style. */
1698
+ readonly token: Cli.TokenName | Cli.Style;
1699
+ /** The text. */
1700
+ readonly children?: ReactNode;
1701
+ }
1702
+ /**
1703
+ * The usable size of the terminal.
1704
+ *
1705
+ * @public
1706
+ */
1707
+ interface TerminalSize {
1708
+ /** The width, less one column, so a full-width line never wraps the cursor. */
1709
+ readonly columns: number;
1710
+ /** The height, less one row, so a full-height frame never scrolls. */
1711
+ readonly rows: number;
1712
+ }
1713
+ /**
1714
+ * The Ink `Text` props for `style` at `color`.
1715
+ *
1716
+ * @remarks
1717
+ * At `"none"` it gives no styling props at all, not even bold or dim, so a frame is escape-free by construction;
1718
+ * Ink's colour level held at 0 is the backstop. A flag set to `false` adds no prop.
1719
+ *
1720
+ * Omit `color` in an Ink tree the kit did not mount, which has no colour level of its own to pass: every prop is
1721
+ * emitted, as at any level but `"none"`, and Ink's own chalk gates what reaches the terminal.
1722
+ *
1723
+ * @param style - the resolved style
1724
+ * @param color - the stream's colour level; omitted, every prop is emitted for Ink's chalk to gate
1725
+ *
1726
+ * @public
1727
+ */
1728
+ export declare const inkProps: (style: Cli.Style, color?: ColorLevel) => InkTextProps;
1729
+ /**
1730
+ * The theme of the stream the mounted screen draws on.
1731
+ *
1732
+ * @remarks
1733
+ * A React hook: call it from a component rendered inside a `CliUi.run` screen or a `UiProvider`.
1734
+ *
1735
+ * @public
1736
+ */
1737
+ export declare const useTheme: () => Cli.StreamTheme;
1738
+ /**
1739
+ * The glyph set of the mounted screen, so a component draws Unicode or ASCII glyphs to match the rest of the output.
1740
+ *
1741
+ * @remarks
1742
+ * A React hook: call it from a component rendered inside a `CliUi.run` screen or a `UiProvider`.
1743
+ *
1744
+ * @public
1745
+ */
1746
+ export declare const useGlyphs: () => Cli.GlyphSet;
1747
+ /**
1748
+ * Text painted with a theme token or style, through the mounted screen's theme.
1749
+ *
1750
+ * @remarks
1751
+ * Its children are drawn as given. The kit's widgets sanitise every string they draw from data (escapes removed,
1752
+ * line breaks folded) before handing it here; text a consumer passes to `Styled`, or to Ink's own `Text`, is the
1753
+ * consumer's to sanitise, with `Fmt.sanitize`. Ink keeps the escape sequences it is handed, so text from data drawn
1754
+ * unsanitised can paint colour at colour `none` or plant a hyperlink, and a line break in it adds a row the screen's
1755
+ * height budget did not count.
1756
+ *
1757
+ * @param props - the token or style, and the text
1758
+ *
1759
+ * @public
1760
+ */
1761
+ export declare const Styled: (props: StyledProps) => ReactElement;
1762
+ /**
1763
+ * The usable terminal size: the stdout Ink draws on, less one column and one row, re-read on every render and when
1764
+ * the terminal resizes; or, under a `UiProvider` given a `size`, that size less one column and one row.
1765
+ *
1766
+ * @remarks
1767
+ * A width or height the stream does not report, or reports as 0 (a pty that `script` opens says `0 0`), is unknown and
1768
+ * reads as 80 columns by 24 rows, so a screen never lays itself out at width 0. This is not Ink's own fallback, which
1769
+ * first asks the process's terminal (`terminal-size`: the tty, `COLUMNS`, `tput`) and only then uses 80x24; the kit
1770
+ * reads no `process` here. On a 0x0 pty with `COLUMNS=50`, Ink lays out at 50 while these rows are cut at 79.
1771
+ *
1772
+ * The override exists for Ink's `renderToString`, whose `useStdout` is the process's own stdout whatever width it
1773
+ * lays out at; without it, the kit's widgets would cut their rows to the wrong width there.
1774
+ *
1775
+ * Never feed `columns` into a `Box`'s `width`. On a resize Ink re-lays out the tree it already has and repaints
1776
+ * before React re-renders with the new size, so a width taken from this hook is one paint stale. After a shrink,
1777
+ * that stale, wider frame wraps in the narrower terminal and leaves a copy stranded above the live one. For a
1778
+ * one-column margin use `marginRight: 1`, which Ink recomputes within its own resize. Text cut to `columns` lags the
1779
+ * same paint, so give a long row Ink's `wrap: "truncate-end"` too: on a shrink Ink then clips it rather than letting
1780
+ * the terminal wrap it.
1781
+ *
1782
+ * A React hook: call it from a component rendered inside an Ink tree; it needs no screen, but reads a `UiProvider`'s
1783
+ * size when there is one.
1784
+ *
1785
+ * @public
1786
+ */
1787
+ export declare const useTerminalSize: () => TerminalSize;
1788
+ //#endregion
1789
+ export type { Binding, CliUiFallbackOptions, CliUiPromptOptions, CliUiRunOptions, ConfirmAction, ConfirmInitOptions, ConfirmResult, ConfirmScreenOptions, ConfirmState, ConfirmToggle, ConfirmViewProps, DocViewProps, InkTextProps, KeyHelpProps, KeyHelpRow, KeyName, LiveHandle, LiveOptions, MultiSelectAction, MultiSelectInitOptions, MultiSelectItem, MultiSelectScreenOptions, MultiSelectSection, MultiSelectState, MultiSelectViewProps, Screen, ScreenControl, SelectAction, SelectChoice, SelectInitOptions, SelectScreenOptions, SelectState, SelectViewProps, StyledProps, Tab, TabsAction, TabsProps, TerminalSize, TextInputInitOptions, TextInputScreenOptions, TextInputState, TextInputViewProps, ToggleViewProps, UiContextValue, UiProviderProps, UiStreamsShape, UseKeysOptions, ViewportMove, ViewportRow, ViewportState, ViewportViewProps };
1790
+ //# sourceMappingURL=ui.d.ts.map