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