module

Hwaro::CLI::Prompt

Small, reusable line-prompt toolkit for interactive commands.

Output goes through Logger.io (the same redirectable IO the rest of the CLI prints to) and reads from an injectable input IO that defaults to STDIN. Tests drive both ends without a real terminal:

Logger.io = IO::Memory.new
Prompt.input = IO::Memory.new("My Title\n\n")

Every prompt is styled with the shared "ember" identity (the prompt glyph + a dim default hint) and degrades to plain text under NO_COLOR / non-TTY, so piped or captured output stays clean.

A bare EOF (Ctrl-D) on any prompt returns nil — callers treat that as "cancelled" rather than looping forever on a closed stream.

Class methods

ask(label : String, default : String | Nil = nil) : String | Nil

Ask a free-text question. Returns the trimmed answer, default when the user just presses Enter, or nil on EOF (Ctrl-D).

Source
ask_required(label : String) : String | Nil

Ask until a non-empty answer is given. Returns nil on EOF so the caller can cancel cleanly instead of spinning on a closed stream.

Source
confirm?(label : String, default : Bool = false) : Bool | Nil

Yes/no question. default decides the Enter behaviour and which letter is capitalised in the [Y/n] / [y/N] hint. Returns nil on EOF.

Source
input
Source
input=(io : IO) : Nil

Inject the input stream (specs). Pass STDIN to restore the default.

Source
interactive?

True only when both ends are a real terminal. Commands gate interactive flows on this so pipes, CI, and agents fall back to non-interactive behaviour instead of blocking on a gets that will never arrive.

Source
select(label : String, choices : Array(String), skip_hint : String = "Enter to skip") : String | Nil

Numbered single-choice picker. Enter (or an out-of-the-way blank line) returns nil, meaning "no selection" — callers use that for the auto/default branch. Accepts either the 1-based index or an exact choice string. Returns nil on EOF. Re-asks on an unrecognised entry.

Source