Interactive Prompts
Some commands need to ask the user questions, show progress, or handle long-running work gracefully. Repl provides a typed interaction channel for all of these.
Prompts
Section titled “Prompts”Inject IReplInteractionChannel (namespace Repl.Interaction) to ask the user questions mid-command:
using Repl.Interaction;
app.Map("add", async (IReplInteractionChannel ui, IContactStore store, CancellationToken ct) =>{ var name = await ui.AskTextAsync("name", "Full name:", ct: ct); var email = await ui.AskTextAsync("email", "Email address:", ct: ct);
var contact = store.Add(name, email); return Results.Success($"Added {contact.Name} (#{contact.Id}).");});In REPL mode this presents an interactive prompt. In CLI mode with --answer:* flags, answers are pre-filled deterministically.
Choice prompts
Section titled “Choice prompts”string[] options = ["admin", "editor", "viewer"];var roleIndex = await ui.AskChoiceAsync("role", "Assign role:", options, ct: ct);var role = options[roleIndex];AskChoiceAsync returns the zero-based index of the selected item. AskMultiChoiceAsync returns IReadOnlyList<int>.
Confirmation
Section titled “Confirmation”app.Map("delete {id:int}", async (int id, IReplInteractionChannel ui, IContactStore store, CancellationToken ct) =>{ var contact = store.Get(id); var confirmed = await ui.AskConfirmationAsync("confirm", $"Delete '{contact.Name}'? This cannot be undone.");
if (!confirmed) return Results.Cancelled("Delete cancelled.");
store.Delete(id); return Results.Success($"Deleted '{contact.Name}'.");});Progress
Section titled “Progress”Each WriteProgressAsync call updates the progress display. Fire-and-forget style — no disposable to manage:
app.Map("import {file}", async (string file, IReplInteractionChannel ui, CancellationToken ct) =>{ var lines = await File.ReadAllLinesAsync(file, ct);
for (int i = 0; i < lines.Length; i++) { ct.ThrowIfCancellationRequested(); await ImportLine(lines[i], ct); await ui.WriteProgressAsync($"Importing…", (double)(i + 1) / lines.Length, ct); }
return Results.Success($"Imported {lines.Length} records.");});Timeouts
Section titled “Timeouts”app.Map("ping {host}", async (string host, CancellationToken ct) =>{ using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct); cts.CancelAfter(TimeSpan.FromSeconds(5)); try { var reply = await Ping(host, cts.Token); return $"{host}: {reply.RoundtripTime}ms"; } catch (OperationCanceledException) { return Results.Error("timeout", $"Ping to {host} timed out after 5s."); }});Deterministic automation with --answer:*
Section titled “Deterministic automation with --answer:*”Pre-fill prompts by name from the command line — useful for scripts and tests without Repl.Testing:
myapp add --answer:name=Alice --answer:email=alice@example.comThis makes interactive commands fully scriptable without changing handler code. Prompt names match the first argument to AskTextAsync / AskChoiceAsync / AskConfirmationAsync.
Spectre.Console prompts
Section titled “Spectre.Console prompts”With Repl.Spectre, all prompts upgrade to rich Spectre.Console widgets automatically:
app.UseSpectreConsole();No handler changes required. See the Spectre cookbook for details.
Further reading
Section titled “Further reading”- Interactivity — complete prompt API: all prompt types, progress reporting, MCP interaction tiers, and
--answer:*flag semantics.