cli/prompt.nu — interactive prompts over std/term + core/io.
All prompts write to stderr (so a command's stdout stays clean for pipes) and read a line from stdin. cli_password suppresses the echo when stdin is a real terminal, and degrades to a plain read otherwise.
@ cli_prompt s question → StringAsk a free-text question; returns the entered line (trailing newline stripped by read_line). Empty String on EOF.
@ cli_confirm s question b def → bYes/no confirmation. def is returned on a bare Enter or EOF. Only the first character is inspected (y/Y → true, n/N → false, else the default).
@ cli_password s question → StringRead a secret. When stdin is a TTY the terminal echo is disabled for the duration; otherwise this is a normal (echoed) line read. No in-line editing (backspace) in the no-echo path — kept intentionally minimal.
cli/cli.nu — the ergonomic command-line facade over the stdlib.
The stdlib ships a flat argument parser (std/args), terminal control (std/term: isatty + ANSI + a line editor), stdin/stdout (core/io) and the process environment (ext/env). What every CLI re-invents on top is the same glue: a subcommand dispatch cascade, typed flag accessors with env fallbacks and defaults, coloured --help / --version / usage, error → exit-code handling, and interactive prompts.
Cli collapses that into one object:
: Cli c ( cli_new greet a tiny greeter 1.0.0 ) ( cli_flag_str c name 110 NAME who to greet world ` ) ( cli_flag_bool c loud 108 shout it ) ( cli_cmd c hello print a greeting \ CliCtx x → i { : String who ( ctx_str x name ) ( nurl_print hello, ) ( nurl_print ( string_data who ) ) ( nurl_print \n` ) ^ 0 } ) ^ ( cli_run c ) // parse argv, route, auto --help/--version → exit code
Scope: this facades the command-line surface (args + env + stdin + tty / colour / prompts + exit codes). It deliberately does NOT absorb terminal widgets (tables/spinners), config-file loading, or logging — those compose as their own packages.
Memory model: cli_new returns a Cli handle, mutable across the builder calls; every copy of it is the same CLI, and its last owner releases it — nothing to free (cli_free is an optional early release). Flags and commands accumulate into its Vecs. cli_run owns the parse; what it allocates is dropped with it.
: CliFlag: CliFlag {
String long
i short // short-option char code, 0 = none
String metavar // value placeholder in help (empty for bool)
String help
i kind
String dflt // default rendered as text (empty = none)
String env // environment-variable fallback (empty = none)
}
Flag kinds. 0 = string 1 = int 2 = bool (presence) 3 = float
: CliCtx: CliCtx {
ArgParser parser
Cli cli
String cmdname
}
Passed to every command handler; wraps the parsed args plus a back-ref to the Cli so accessors can resolve env fallbacks and defaults.
Declared BEFORE CliCmd on purpose. CliCmd's handler field is a function type taking a CliCtx BY VALUE, and nurlc emits %Name = type as it parses, so declaration order here IS emission order in the .ll. Some LLVM IR parsers reject a forward-referenced named struct in that position with "invalid type for function argument" — observed on Windows, where moving this below CliCmd fails every binary that links cli. clang 18 on Linux accepts the forward reference, so the ordering is belt-and-braces there rather than load-bearing; keep it anyway, it costs nothing and the back-ref (a Cli handle, one word) stays legal in either direction.
: CliCmd: CliCmd {
String name
String help
( @ i CliCtx ) handler
}
: CliImpl: CliImpl {
String prog
String about
String version
( Vec CliFlag ) globals
( Vec CliCmd ) cmds
}
: Cli { s ctl }A Cli is a handle on its state in an rcbox (stdlib/core/rcbox.nu): every copy is the same state (a CliCtx's back-ref is one), and the last owner releases it — its strings, flags and commands are dropped with it.
@ Cli_drop sink Cli h → v: ~ b g_cli_color F@ cli_free sink Cli c → vLet go of c now rather than at the end of its owner's scope.
@ ctx_int CliCtx x s name → i@ ctx_float CliCtx x s name → f@ ctx_bool CliCtx x s name → b@ ctx_nargs CliCtx x → iNumber of positional arguments after the command token. Under the default handler (empty cmdname) there is no command token to skip — every positional (e.g. a connection URL) is an argument.
@ ctx_arg CliCtx x i idx → StringThe idx-th positional after the command (0-based); empty String if absent. args_positionals hands back a BORROW of the parser's own vector — read from it and copy out the one element; never free it here.
@ ctx_stdin CliCtx x → StringSlurp stdin to EOF (for pipe-friendly commands).
@ ctx_cmd CliCtx x → sThe command name that was invoked.