@fishwithcat/dsh-tui-app 0.0.0-stage → 0.2.2-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/startup.js ADDED
@@ -0,0 +1,54 @@
1
+ import { Command } from "commander";
2
+ import { parseCmdline } from "@deepseek-ai/dsh-cmdline";
3
+ //#region lib/types/startup.js
4
+ /**
5
+ * The terminal app's command-line provider: it parses the `dsh --profile tui`
6
+ * flag family (`--resume`, `--provider`, `--model`, `--watch`) and its `--help` text, then
7
+ * publishes the values as {@link TUI_STARTUP_SERVICE}. The TUI row injects that
8
+ * service, so a help or usage invocation — which publishes nothing — mounts no
9
+ * terminal UI.
10
+ * @module @deepseek-ai/dsh-tui-app/startup
11
+ */
12
+ /** Stable Cordis plugin name. */
13
+ const name = "tui-startup";
14
+ /** Services required before the flags can be resolved. */
15
+ const inject = ["cmdlineArgs"];
16
+ /** Service provided by this plugin and injected by the TUI row. */
17
+ const TUI_STARTUP_SERVICE = "tuiStartup";
18
+ /**
19
+ * This app's command: its flags, its description, and its help text.
20
+ * @returns a fresh program, so one process can parse more than once (tests).
21
+ */
22
+ function tuiCommand() {
23
+ return new Command().name("dsh --profile tui").description("Open the interactive terminal UI: one Agent, one session, driven in this process.").helpOption("-h, --help", "show this help").option("--resume <session-id>", "resume a stored session instead of starting a new one").option("--provider <provider>", "provider route for this session (requires --model)").option("--model <model>", "model id for this session (requires --provider)").option("--watch <session-id>", "view a local subagent session read-only; cannot be combined with --resume, --provider, or --model").addHelpText("after", `
24
+ Examples:
25
+ dsh open the TUI in the current directory
26
+ dsh --profile tui --resume <session-id> continue a stored session
27
+ dsh watch <session-id> watch a local subagent read-only
28
+ dsh --provider deepseek-official --model deepseek-v4-flash
29
+ `);
30
+ }
31
+ /**
32
+ * Parse and publish the TUI invocation as an ordinary Cordis service. A partial
33
+ * route is a usage error, so on rejection (and on `--help`) nothing is provided
34
+ * and the TUI row stays pending.
35
+ * @param ctx - plugin context carrying the command line.
36
+ */
37
+ function apply(ctx) {
38
+ const program = tuiCommand();
39
+ program.action(() => {
40
+ const options = program.opts();
41
+ if (options.provider === void 0 !== (options.model === void 0)) program.error("error: --provider and --model must be given together");
42
+ if (options.watch === "") program.error("error: --watch needs a session id");
43
+ if (options.watch !== void 0 && (options.resume !== void 0 || options.provider !== void 0 || options.model !== void 0)) program.error("error: --watch cannot be combined with --resume, --provider, or --model");
44
+ ctx.provide(TUI_STARTUP_SERVICE, {
45
+ ...options.resume === void 0 ? {} : { resume: options.resume },
46
+ ...options.provider === void 0 ? {} : { provider: options.provider },
47
+ ...options.model === void 0 ? {} : { model: options.model },
48
+ ...options.watch === void 0 ? {} : { watch: options.watch }
49
+ });
50
+ });
51
+ parseCmdline(ctx, program);
52
+ }
53
+ //#endregion
54
+ export { TUI_STARTUP_SERVICE, apply, inject, name };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * ANSI styling for the terminal surface and the pi-tui theme objects its
3
+ * components require. Colour is opt-out through the conventional environment
4
+ * switches, the palette follows the terminal background (dark by default, or
5
+ * the light variant when the deployment selects one), and a disabled theme
6
+ * returns text unchanged so rendering, width calculation, and tests stay
7
+ * colour-independent.
8
+ * @module @deepseek-ai/dsh-tui-app/ansi
9
+ */
10
+ import type { EditorTheme, MarkdownTheme, SelectListTheme } from '@earendil-works/pi-tui';
11
+ import type { TuiColorScheme } from './config.ts';
12
+ /** One style: text in, styled text out, no state. */
13
+ export type Styler = (text: string) => string;
14
+ /** Semantic styles the transcript, status bar, and prompts share. */
15
+ export interface TuiTheme {
16
+ /** Human prompt role marker and its continuation rule. */
17
+ user: Styler;
18
+ /** Assistant message body. */
19
+ assistant: Styler;
20
+ /** Assistant message role marker and its continuation rule. */
21
+ assistantRule: Styler;
22
+ /** Provider reasoning text, shown while it streams. */
23
+ reasoning: Styler;
24
+ /** Tool-call heading. */
25
+ tool: Styler;
26
+ /** Successful tool outcome. */
27
+ toolOk: Styler;
28
+ /** Failed tool outcome. */
29
+ toolError: Styler;
30
+ /** Tool result body: the output a Tool returned. */
31
+ toolResult: Styler;
32
+ /** Neutral app notice. */
33
+ notice: Styler;
34
+ /** Failure notice. */
35
+ error: Styler;
36
+ /** Approaching a budget the user still has room to act on. */
37
+ warning: Styler;
38
+ /** Emphasis on a value or label. */
39
+ accent: Styler;
40
+ /** Rules, separators, and other low-signal chrome. */
41
+ border: Styler;
42
+ /** De-emphasized supporting text. */
43
+ dim: Styler;
44
+ /** Bold text. */
45
+ bold: Styler;
46
+ /** Italic text, the markdown emphasis face. */
47
+ italic: Styler;
48
+ /** Added line of a file diff. */
49
+ diffAdd: Styler;
50
+ /** Removed line of a file diff. */
51
+ diffDel: Styler;
52
+ /** Unchanged context line of a file diff. */
53
+ diffContext: Styler;
54
+ /** Diff chrome: a file header or a hunk boundary. */
55
+ diffMeta: Styler;
56
+ }
57
+ /** The palette a run resolves to. */
58
+ export type TuiPalette = 'dark' | 'light';
59
+ /** Construction facts for {@link createTheme}. */
60
+ export interface ThemeOptions {
61
+ /** Whether ANSI styles are emitted. */
62
+ enabled: boolean;
63
+ /** Palette the terminal background calls for. */
64
+ palette: TuiPalette;
65
+ }
66
+ /**
67
+ * Decide whether to emit ANSI styles for one output stream.
68
+ * @param environment - the process environment (`NO_COLOR`, `FORCE_COLOR`, `TERM`).
69
+ * @param isTty - whether that stream is a terminal.
70
+ * @returns true when styled output is appropriate.
71
+ */
72
+ export declare function supportsColor(environment: NodeJS.ProcessEnv, isTty: boolean): boolean;
73
+ /**
74
+ * Resolve the palette a run uses.
75
+ *
76
+ * `auto` reads `COLORFGBG`, the background signal xterm-family terminals
77
+ * export as `foreground;background`; a background index of 7 or 15 selects the
78
+ * light palette. An absent or unparseable value stays dark, because the dark
79
+ * palette is the one every terminal can render legibly.
80
+ * @param mode - the deployment's selection.
81
+ * @param environment - the process environment.
82
+ * @returns the palette to render with.
83
+ */
84
+ export declare function resolveColorScheme(mode: TuiColorScheme, environment: NodeJS.ProcessEnv): TuiPalette;
85
+ /**
86
+ * Build the surface theme.
87
+ * @param options - whether styles are emitted and which palette to use.
88
+ * @returns every semantic style the surface uses.
89
+ */
90
+ export declare function createTheme(options: ThemeOptions): TuiTheme;
91
+ /**
92
+ * Build the pi-tui select-list theme used by pickers and menus.
93
+ * @param theme - the surface theme.
94
+ * @returns the component's theme object.
95
+ */
96
+ export declare function selectListTheme(theme: TuiTheme): SelectListTheme;
97
+ /**
98
+ * Build the pi-tui composer theme.
99
+ * @param theme - the surface theme.
100
+ * @returns the component's theme object.
101
+ */
102
+ export declare function editorTheme(theme: TuiTheme): EditorTheme;
103
+ /**
104
+ * Build the pi-tui markdown theme used for assistant message bodies.
105
+ * @param theme - the surface theme.
106
+ * @returns the component's theme object.
107
+ */
108
+ export declare function markdownTheme(theme: TuiTheme): MarkdownTheme;
109
+ //# sourceMappingURL=ansi.d.ts.map
@@ -0,0 +1,480 @@
1
+ /**
2
+ * The interactive terminal application: builds the pi-tui surface, owns the one
3
+ * session it drives, routes composer input to local commands, the command
4
+ * registry, or the Agent, and restores the terminal on exit.
5
+ * @module @deepseek-ai/dsh-tui-app/app
6
+ */
7
+ import type { Context } from '@deepseek-ai/cordis';
8
+ import type { SelectItem, Terminal } from '@earendil-works/pi-tui';
9
+ import { copyClipboardText } from './clipboard.ts';
10
+ import type { ClipboardImage } from './clipboard.ts';
11
+ import type { Config } from './config.ts';
12
+ import type { InteractionHost, InteractionTitle, PromptOutcome } from './interactions.ts';
13
+ import type { TuiStartupValues } from './startup.ts';
14
+ /** Process-facing effects the app needs; tests substitute them. */
15
+ export declare const internals: {
16
+ /** Build the terminal the TUI drives. */
17
+ createTerminal: () => Terminal;
18
+ /** Whether this invocation has an interactive terminal. */
19
+ isInteractive: () => boolean;
20
+ /** Read one image from the system clipboard, or undefined when it holds none. */
21
+ readClipboardImage: () => Promise<ClipboardImage | undefined>;
22
+ /** Copy selected text through the host clipboard or terminal. */
23
+ copyClipboardText: typeof copyClipboardText;
24
+ /** Route console writes away from the terminal the app owns; returns the restore function. */
25
+ captureConsole: (sink: (text: string) => void) => () => void;
26
+ /** Sink for boot failures and the exit resume hint, neither of which the terminal surface can show. */
27
+ stderr: {
28
+ write(chunk: string): unknown;
29
+ };
30
+ };
31
+ /** Everything one TUI invocation needs from its launcher. */
32
+ export interface TuiAppOptions {
33
+ /** The plugin context the tree mounted. */
34
+ ctx: Context;
35
+ /** Validated app settings. */
36
+ config: Config;
37
+ /** Working directory the session starts in. */
38
+ cwd: string;
39
+ /** Parsed invocation values, read from the startup provider. */
40
+ invocation: TuiStartupValues;
41
+ /** Request bounded process exit with `code`. */
42
+ exit(code: number): void;
43
+ }
44
+ /** The interactive application. */
45
+ export declare class TuiApp implements InteractionHost {
46
+ private readonly options;
47
+ private readonly tui;
48
+ private readonly viewport;
49
+ private readonly theme;
50
+ private readonly transcript;
51
+ private readonly transcriptView;
52
+ private readonly statusBar;
53
+ /** Local `!` commands; owns the single foreground execution and its row. */
54
+ private readonly shell;
55
+ /** Local subagent watch service; owns the HTTP listener and runtime records. */
56
+ private readonly watch;
57
+ private readonly editor;
58
+ private readonly composer;
59
+ private readonly disposers;
60
+ /** Subscriptions to the current session; `/new` and `/resume` replace them wholesale. */
61
+ private readonly sessionDisposers;
62
+ private session;
63
+ private leaving;
64
+ private stopped;
65
+ private promptActive;
66
+ private promptChain;
67
+ /** Repaints a prompt whose heading changes while it is open, such as a timed countdown. */
68
+ private promptTick;
69
+ /** Clipboard images the composer holds, by the marker number the draft shows. */
70
+ private readonly pendingImages;
71
+ /** Clipboard reads in flight; the footer reports their wait until the last one settles. */
72
+ private pasteReads;
73
+ /** Whether this host's terminal claims Ctrl+V, so image paste belongs on Alt+V. */
74
+ private readonly altPaste;
75
+ /** Providers the deployment registered; more than one qualifies the footer's model label. */
76
+ private readonly providerCount;
77
+ /** Whether the mounted compaction engine schedules its own work. */
78
+ private readonly autoCompaction;
79
+ /** Whether a compaction transaction is in flight; the footer reports its wait. */
80
+ private compacting;
81
+ /** Restores the console methods; a no-op until the renderer installs its capture. */
82
+ private restoreConsole;
83
+ /** Footer measurement cached against the session and log position it was read at. */
84
+ private measurementCache;
85
+ /** Footer subagent counts cached against the session and log position they were read at. */
86
+ private constructor();
87
+ /**
88
+ * Boot one interactive TUI over the composed tree.
89
+ * @param options - the launcher's context, settings, invocation, and exit request.
90
+ * @returns the running application.
91
+ * @throws when the invocation has no interactive terminal.
92
+ */
93
+ static boot(options: TuiAppOptions): Promise<TuiApp>;
94
+ /**
95
+ * Build the surface, subscribe to the session, and start the terminal UI.
96
+ */
97
+ mount(): Promise<void>;
98
+ /**
99
+ * Stop the application, drain the session, and request process exit.
100
+ * @param code - the process exit code.
101
+ */
102
+ stop(code: number): Promise<void>;
103
+ /** The Agent this surface answers for. */
104
+ private get agent();
105
+ /**
106
+ * Subscribe to the session's durable events, live stream, and status, and
107
+ * fold the stored history a resumed session already holds.
108
+ * @param session - the session to observe.
109
+ */
110
+ private attach;
111
+ /**
112
+ * Fold one live event into the transcript and the footer's facts.
113
+ *
114
+ * A manual compaction runs outside the turn lifecycle, so the Agent's status
115
+ * stays `idle` and these durable markers are the only account of the wait.
116
+ * @param event - the event appended to this surface's session.
117
+ */
118
+ private applyEvent;
119
+ /** Install the global key bindings; the viewport owns scrolling keys. */
120
+ private registerKeys;
121
+ /**
122
+ * Flip plan mode for this session's Agent.
123
+ *
124
+ * The service commits the change immediately between turns and holds it until
125
+ * the next accepted in-turn pre-step while a turn is open, so the notice
126
+ * distinguishes the two.
127
+ */
128
+ private togglePlanMode;
129
+ /**
130
+ * Abort the running turn, interrupt the session's live subagents, and report
131
+ * the interruption.
132
+ *
133
+ * The Agent's lifecycle status stays `running` until the turn settles, so the
134
+ * notice is the immediate acknowledgement that the interrupt landed. The
135
+ * notice names subagents too whenever delegated work was in flight, whether
136
+ * or not the Agent itself was working.
137
+ */
138
+ private interrupt;
139
+ /**
140
+ * Route one composer submission.
141
+ *
142
+ * A `!` line runs locally, a `\!` line reaches the Agent with the backslash
143
+ * removed, a line citing held images becomes a prompt carrying them, and
144
+ * every other line runs a command or reaches the Agent as text. A refused
145
+ * line leaves the composer and its held images untouched.
146
+ * @param text - the submitted text.
147
+ */
148
+ private submit;
149
+ /**
150
+ * Why one submission cannot run now, or undefined when it may proceed.
151
+ *
152
+ * The local shell refuses a second command, and a prompt while it runs; a
153
+ * running turn refuses `!` but still accepts steering text. Commands stay
154
+ * available because they run in-process.
155
+ * @param value - the trimmed line.
156
+ * @param hasImages - whether the line cites held images.
157
+ * @returns the refusal to report, or undefined.
158
+ */
159
+ private refusal;
160
+ /**
161
+ * Whether the line names a command the composer dispatches instead of the Agent.
162
+ * @param value - the trimmed line.
163
+ * @returns whether the registry or the app owns the line.
164
+ */
165
+ private isCommandLine;
166
+ /**
167
+ * Run one composer line that carries no image: an app command, a registry
168
+ * command, or the Agent's next prompt.
169
+ * @param value - the trimmed line.
170
+ */
171
+ private runLine;
172
+ /**
173
+ * Hand one line to the Agent, steering while a turn runs.
174
+ * @param line - the model-visible prompt text.
175
+ */
176
+ private promptLine;
177
+ /**
178
+ * Attach one clipboard image to the composing draft.
179
+ *
180
+ * The bytes stay in memory until the submission citing the marker admits them
181
+ * to the durable attachment store, so a draft that never submits writes
182
+ * nothing. The read runs a platform program, so the footer reports the wait
183
+ * from the key press until the bytes arrive.
184
+ */
185
+ private pasteClipboardImage;
186
+ /**
187
+ * Admit one prompt's images and hand the Agent the text and durable references.
188
+ * @param text - the model-visible prompt text; empty for an image-only prompt.
189
+ * @param images - the prompt's images, in content order.
190
+ * @throws when the deployment stores no attachments, the routed model refuses images, or admission fails.
191
+ */
192
+ private sendImages;
193
+ /**
194
+ * Put a refused submission back into the composer without discarding what the
195
+ * user typed while its images were being admitted.
196
+ * @param text - the refused line, markers included.
197
+ * @param images - the images the line cites.
198
+ */
199
+ private restoreDraft;
200
+ /**
201
+ * Run one slash line: an app command, or a registry command against the Agent.
202
+ * @param name - the command name without the slash.
203
+ * @param input - the trailing input.
204
+ * @param line - the original line, forwarded to the registry unchanged.
205
+ */
206
+ private runCommand;
207
+ /** Invocation-derived session options. */
208
+ private sessionOptions;
209
+ /**
210
+ * Execute one registry command and render its settled result.
211
+ * @param name - the command name without the slash.
212
+ * @param line - the original line.
213
+ */
214
+ private registryCommand;
215
+ /** The `/help` body: app commands followed by registry commands. */
216
+ private helpText;
217
+ /** List stored sessions, newest first. */
218
+ private listSessions;
219
+ /**
220
+ * Fold the stored title of each listed session.
221
+ *
222
+ * The title is the client-visible label the other surfaces use: the leading
223
+ * words of the first eligible human message, a generated title, or an
224
+ * explicit rename. A failed read leaves that one row on its short id, and a
225
+ * deployment without the query service keeps the previous id-only list.
226
+ * @param ids - stored session ids to read.
227
+ * @returns title text by session id.
228
+ */
229
+ private sessionTitles;
230
+ /**
231
+ * Show the session's delegated children in a pinned panel.
232
+ *
233
+ * The panel reads live projections on every render, so the Agent keeps
234
+ * streaming and new children appear without writing a transcript row. A
235
+ * catalog read failure is shown in the panel instead of an empty list.
236
+ */
237
+ private subagentsCommand;
238
+ /**
239
+ * Resume a stored session named by input, or chosen from a picker.
240
+ * @param input - the trailing `/resume` input.
241
+ */
242
+ private resumeCommand;
243
+ /**
244
+ * Resume the stored session `id` over the live one.
245
+ *
246
+ * The typed and picker paths share this guard so neither reaches
247
+ * {@link replaceSession} without a backend or with the live session's own id:
248
+ * the live session already holds the state, and reopening the same id would
249
+ * conflict with the write claim its handle holds.
250
+ * @param id - the stored session id.
251
+ */
252
+ private resumeSession;
253
+ /**
254
+ * Show or switch the model route.
255
+ * @param input - the trailing `/model` input.
256
+ */
257
+ private modelCommand;
258
+ /**
259
+ * Apply a `provider/model` route.
260
+ * @param value - the route text.
261
+ */
262
+ private applyRoute;
263
+ /**
264
+ * Show or switch the reasoning effort of the routed model.
265
+ * @param input - the trailing `/effort` input.
266
+ */
267
+ private effortCommand;
268
+ /**
269
+ * Answer one question whose foreground window already closed.
270
+ *
271
+ * The timed call's own result recorded the timeout, so a late answer travels
272
+ * as a steered user message through the service, which also records the
273
+ * settlement in the session projection.
274
+ */
275
+ private questionsCommand;
276
+ /**
277
+ * Questions this session can still take an answer for.
278
+ * @returns the continued calls in ask order; empty when no projection registry is mounted.
279
+ */
280
+ private continuedQuestions;
281
+ /**
282
+ * Apply one reasoning effort to the route in force.
283
+ * @param effort - the selected effort, or undefined to send none and let the provider decide.
284
+ */
285
+ private applyEffort;
286
+ /**
287
+ * Swap the live session, rewiring events and clearing the transcript.
288
+ *
289
+ * The previous session is flushed and the replacement is opened before the
290
+ * previous one is retired, so a rejected flush or `open` leaves the running
291
+ * session, its subscriptions, and the transcript untouched. The replacement
292
+ * becomes current before the previous session is closed, so even a disposal
293
+ * failure leaves the app on a usable session.
294
+ * @param open - creates the replacement session.
295
+ */
296
+ private replaceSession;
297
+ /** Push the current facts into the footer and request a repaint. */
298
+ private refresh;
299
+ /**
300
+ * The directory the session currently works in. The model can change it
301
+ * during a turn, so this reads the working-directory service on every read
302
+ * and falls back to the launch directory when it is unmounted.
303
+ * @returns the absolute working directory.
304
+ */
305
+ private workingDirectory;
306
+ /**
307
+ * The footer's workspace label, abbreviated against the home directory.
308
+ * @returns the display path.
309
+ */
310
+ private workspaceLabel;
311
+ /**
312
+ * Whether plan mode is in force, or selected to apply from the next step.
313
+ *
314
+ * A queued selection changes without appending an event, so this reads the
315
+ * service on every refresh instead of caching against the log position.
316
+ * @returns the effective mode, or undefined when the deployment mounts no plan mode.
317
+ */
318
+ private planStatus;
319
+ /**
320
+ * Footer measurement for one session, read from the projection registry.
321
+ *
322
+ * The live Assistant stream repaints far more often than it appends session
323
+ * events, and a projection read folds every registered unit, so the whole
324
+ * measurement is cached against the Session and log position it was read at —
325
+ * keying on the Session too keeps a replaced session's figures out of the
326
+ * next one.
327
+ * @param session - the session whose footer facts are presented.
328
+ * @returns context occupancy and whole-log token figures for the frame.
329
+ */
330
+ private measurement;
331
+ /**
332
+ * The delegated-work counts the footer reports, read from the session's
333
+ * catalogs on the first frame after a log position or lifecycle edge that
334
+ * can move them. A deployment without the projection registry exposes no
335
+ * subagent catalog, so the footer omits the marker rather than failing a
336
+ * frame the way `/subagents` reports the gap.
337
+ * @param session - the session whose catalogs are walked.
338
+ * @returns the counts, or `undefined` while no child is recorded or the registry is absent.
339
+ */
340
+ private delegatedSubagents;
341
+ /**
342
+ * Refresh the delegated rows and repaint when the asynchronous read settles.
343
+ * The footer and panel read the session's cache, so this never blocks a frame.
344
+ */
345
+ private reloadSubagents;
346
+ /**
347
+ * The placeholder the empty composer shows: the keys valid in the current state.
348
+ * @param busy - whether the Agent or a live subagent descendant is working.
349
+ * @param planAvailable - whether the deployment mounts plan mode.
350
+ */
351
+ private hints;
352
+ /** Append one notice row and repaint. */
353
+ private notice;
354
+ /**
355
+ * Put one captured console write into the transcript.
356
+ *
357
+ * A console write carries no severity of its own: plugins that reserve stdout
358
+ * for protocol messages may report through `console.error`. A transcript row
359
+ * therefore stays an ordinary notice, keeping the tag that names its producer.
360
+ * @param text - the plain-text message.
361
+ */
362
+ private diagnostic;
363
+ /**
364
+ * Ask the user to choose one item through a modal list.
365
+ * @param title - the question shown above the list; a provider is re-evaluated on every repaint.
366
+ * @param items - the selectable items.
367
+ * @param signal - cancellation lifetime; aborting cancels the prompt.
368
+ * @param detail - markdown shown above the list; while it overflows, Up/Down
369
+ * and the wheel scroll it and Left/Right move the list selection. A detail
370
+ * gives the panel every row above the pinned footer instead of the picker's cap.
371
+ * @returns how the prompt settled: the chosen item, Escape's skip, or Ctrl+C's cancel.
372
+ */
373
+ choose(title: InteractionTitle, items: readonly SelectItem[], signal?: AbortSignal, detail?: string): Promise<PromptOutcome<SelectItem>>;
374
+ /**
375
+ * Ask the user to check any number of items through a modal list.
376
+ * @param title - the question shown above the list; a provider is re-evaluated on every repaint.
377
+ * @param items - the selectable items.
378
+ * @param signal - cancellation lifetime; aborting cancels the prompt.
379
+ * @param detail - markdown shown above the list; while it overflows, Up/Down
380
+ * and the wheel scroll it and Left/Right move the list selection.
381
+ * @returns how the prompt settled: the checked items in list order, Escape's
382
+ * skip, or Ctrl+C's cancel.
383
+ */
384
+ chooseMany(title: InteractionTitle, items: readonly SelectItem[], signal?: AbortSignal, detail?: string): Promise<PromptOutcome<SelectItem[]>>;
385
+ /**
386
+ * Open one modal list prompt and settle it through the caller's wiring.
387
+ *
388
+ * Both pickers share the panel budget, the selection a long detail moves with
389
+ * Left/Right, and the dismissal plumbing; they differ only in the control they
390
+ * build and the key that settles it, so those arrive as callbacks. Escape is
391
+ * the panel's own skip, so the wiring only supplies the control's two outcomes.
392
+ * @param title - the heading shown above the list.
393
+ * @param items - the selectable items, in display order.
394
+ * @param signal - cancellation lifetime; aborting cancels the prompt.
395
+ * @param detail - markdown shown above the list; absent shows the list alone.
396
+ * @param reservedRows - body rows the picker spends outside its options, such as a key hint.
397
+ * @param createList - builds the picker control for the computed visible count.
398
+ * @param wrapBody - wraps the picker with the detail viewport when a detail is present.
399
+ * @param wire - installs the caller's answer and cancel handlers.
400
+ * @returns how the prompt settled.
401
+ */
402
+ private pickList;
403
+ /**
404
+ * Ask the user for one line of text through a modal input.
405
+ * @param title - the question shown above the input; a provider is re-evaluated on every repaint.
406
+ * @param signal - cancellation lifetime; aborting cancels the prompt.
407
+ * @param detail - markdown shown above the input; Up/Down, PageUp/PageDown,
408
+ * and the wheel scroll it while it overflows. A detail gives the panel every
409
+ * row above the pinned footer instead of the picker's cap.
410
+ * @returns how the prompt settled: the entered text, Escape's skip, or Ctrl+C's cancel.
411
+ */
412
+ ask(title: InteractionTitle, signal?: AbortSignal, detail?: string): Promise<PromptOutcome<string>>;
413
+ /**
414
+ * Serialize modal prompts: only one may own the keyboard at a time.
415
+ * @param run - the prompt to run when its turn arrives.
416
+ * @returns the prompt's result.
417
+ */
418
+ private enqueuePrompt;
419
+ /**
420
+ * Show one modal over the transcript.
421
+ *
422
+ * The alternate-screen layout pins the composer and the footer, so the panel
423
+ * is anchored directly above them and reads as the transcript's next lines;
424
+ * the inline layout has no fixed footer position, so the panel is centered.
425
+ * @param title - the heading, a provider re-evaluated on every repaint, or
426
+ * undefined when the body supplies its own first line.
427
+ * @param body - the component that owns input while the modal is up.
428
+ * @param detailed - whether the body carries scrollable detail, which lets the
429
+ * panel take every row above the pinned footer instead of the picker's cap.
430
+ * @returns the overlay handle and the panel that draws the heading.
431
+ */
432
+ private showPrompt;
433
+ /**
434
+ * Rows one prompt panel may occupy above the pinned footer.
435
+ * @param detailed - whether the panel carries scrollable detail, which may
436
+ * take every row above the footer rather than the picker's cap.
437
+ * @returns the panel's row budget.
438
+ */
439
+ private promptRows;
440
+ /**
441
+ * Rows one prompt's heading takes from its panel budget.
442
+ * @param title - the heading to measure.
443
+ * @returns the heading's rows at the current viewport width.
444
+ */
445
+ private headingRows;
446
+ /**
447
+ * Close one modal and give the keyboard back to the composer.
448
+ * @param handle - the overlay handle returned by {@link showPrompt}.
449
+ */
450
+ private dismissPrompt;
451
+ /** Point the watch service at the session tree this surface now owns. */
452
+ private activateWatch;
453
+ /** Restore the terminal exactly once. */
454
+ private teardown;
455
+ }
456
+ /**
457
+ * Whether this host's terminals claim Ctrl+V for their own paste.
458
+ *
459
+ * Windows consoles and WSL distribute Ctrl+V to the terminal's paste, so the
460
+ * surface binds image paste to Alt+V there.
461
+ * @param platform - the platform the surface runs on.
462
+ * @param env - the process environment.
463
+ * @returns whether the alternate paste key governs this host.
464
+ */
465
+ export declare function reservesCtrlV(platform: NodeJS.Platform, env: NodeJS.ProcessEnv): boolean;
466
+ /**
467
+ * Decode a mouse-wheel input sequence into the lines it scrolls.
468
+ *
469
+ * The alternate-screen renderer handles a wheel over its own scroll views; a
470
+ * focused prompt makes it defer the rest, so the surface needs its own reading
471
+ * of the two wheel encodings it accepts. The SGR form is `CSI < button ; column
472
+ * ; row M`; the legacy form is `CSI M` followed by button, column, and row
473
+ * bytes offset by 32. A wheel reports bit 6, a direction of 0 or 1, and bit 3
474
+ * for the Alt modifier.
475
+ * @param data - raw terminal input.
476
+ * @returns the signed scroll distance, negative upward, or undefined when the
477
+ * input is not a wheel event.
478
+ */
479
+ export declare function wheelScroll(data: string): number | undefined;
480
+ //# sourceMappingURL=app.d.ts.map