ropt
Add interactive menus, text inputs, and yes/no prompts to any shell script — without writing any parsing logic yourself.
cargo install ropt
What it is
Shell scripts that need user input usually end up doing one of three things: hard-coding values, reading positional arguments (and hoping the user gets the order right), or growing a wall of getopts boilerplate. ropt is a fourth option.
You describe what you want to ask — a pick list, a free-text input, a yes/no flag — using a series of ropt calls. When you run ropt execute, it renders the prompts interactively in the terminal. The user navigates with arrow keys, types, presses Enter, and when they're done, ropt hands back the results for your script to use.
All the interactive rendering happens on stderr. Results come out on stdout. So capturing output with $() works the way you'd expect.
Quick look
#!/usr/bin/env bash
action=
What the user sees (on stderr, doesn't interfere with stdout):
? What would you like to do?
▶ Deploy application
Rollback to previous version
Check deployment status
After they pick "Deploy application" and press Enter, $action is deploy.
How it works
Every ropt call is just a shell command. push opens a scope, pop closes it, append is both at once. You build up a structure describing your prompts — then ropt execute walks it, shows the prompts one after another, and outputs the answers.
The state between calls is tracked via ROPT_SESSION. You set it once at the top of your script:
Every subsequent ropt call in that shell process (or any subprocess) picks it up automatically. When you're done, ropt end cleans up.
Because push/append/pop are just shell commands running in sequence, you get conditional options for free:
if ; then
fi
No special syntax. No DSL. The if block is just bash.
Node types
| Type | What it does |
|---|---|
select |
Arrow-key menu or type-to-filter list. Contains option and group children. |
option |
A single selectable item inside a select. |
group |
A visual section header grouping related option nodes. |
flag |
A yes/no prompt. Result is true or false. |
input |
Free-text entry with optional type checking and validation. |
select
With fewer than 5 options, --render=auto (the default) uses an arrow-key picklist. With 5 or more it switches to a type-to-filter mode. You can force either with --render=picklist or --render=input.
Add --multiple to let the user pick more than one value. Results come back space-separated in raw mode, or as a bash array with --format=sh.
option
--default pre-selects the option. --disabled shows it greyed out but won't let the user pick it.
group
Groups are display-only — the label appears as a non-selectable header in the list.
flag
# User sees: ? Enable verbose logging? (y/n):
# Result: true or false
input
Supported types:
| Type | Validation |
|---|---|
string |
Any text; --validate-min/--validate-max set length bounds |
number |
Must parse as a number; min/max are numeric range |
email |
Must contain @ with text on both sides |
path |
Must be non-empty |
regex:<pattern> |
Must fully match the embedded pattern |
Add --validate-regex on top of any type for an extra custom pattern check. Add --sensitive to mask characters as * (useful for passwords).
Commands
ropt begin Create a session, print its ID
ropt end [--session=ID] Delete the session
ropt push <type> [options...] Open a new scope
ropt append <type> [options...] Add a node without changing scope
ropt pop [--session=ID] Close the current scope
ropt execute [--format=raw|sh|json] Run prompts, print results
[--prefix=PREFIX]
ropt read --key <path> Read one result value by key
ropt show [--format=tree|json] Debug: print the current structure
Environment variables
| Variable | Default | Description |
|---|---|---|
ROPT_SESSION |
— | Active session ID. Set once with export ROPT_SESSION=$(ropt begin) and every subsequent call picks it up. Pass --session=<id> to any command to override it explicitly. |
ROPT_TIMEOUT |
60 |
Seconds before an unanswered prompt times out. |
Examples
1. Simple pick list
#!/usr/bin/env bash
action=
What the user sees:
? What would you like to do?
▶ Deploy application
Rollback to previous version
Check deployment status
Exit
What the script gets (after selecting "Deploy application"):
deploy
2. Options that depend on runtime conditions
#!/usr/bin/env bash
ENV=""
# Only appears when ENV=dev
if ; then
fi
if ; then
fi
target=
With ENV=dev, the user sees four options. With ENV=prod, they see two. No special ropt syntax — just an if block.
3. Text inputs and flags
When you have multiple prompts, --format=raw outputs one value per line sorted by key name. Assign them with read:
#!/usr/bin/env bash
{ ; ; ; }
if ; then
else
fi
Raw output is sorted alphabetically by key name, so the order here is dry-run, project-name, workers. If that ordering feels fragile, see the output formats section — --format=sh and --format=json are better fits for multi-value results.
What the user sees:
? Project name (letters, numbers, hyphens only): my-app
? Number of parallel workers (default: 4): 8
? Dry run (no side effects)? (y/n): n
What ropt execute --format=raw prints:
false
my-app
8
4. Grouped options with type-ahead filtering
#!/usr/bin/env bash
engine=
What the user sees (they type "post" and the list filters live):
? Database engine: post
── Relational ──────────────────
▶ PostgreSQL
5. Options built from live data
#!/usr/bin/env bash
# Build options from a git branch list
while ; do
branch=""
branch=""
&&
done
branch=
The option list is built at runtime from git branch. Any command, array, or file listing works the same way — you're just calling ropt append option in a loop.
Output formats
ropt execute supports three output formats. The examples above use --format=raw for simplicity, but --format=sh and --format=json are often better when you have multiple prompts.
--format=raw
One value per line, no keys, sorted alphabetically by key name:
deploy
false
8
Best for single-prompt scripts where you capture the result directly into a variable:
action=
For multiple prompts it still works, but you need to know the sort order of your key names to assign values correctly with read.
--format=sh
Shell variable assignments, safe to eval:
_ropt_out=
Variable names come from the --name you gave each node, with dots and hyphens replaced by underscores. The --prefix option prepends a string to all names, keeping ropt results from colliding with your own variables.
# --name "action" with --prefix ropt_:
ropt_action='deploy'
# --name "dry-run":
ropt_dry_run=false
# --multiple select --name "targets":
ropt_targets=('api' 'worker' 'scheduler')
Good choice when you have several prompts and want named variables without reaching for jq.
--format=json
A JSON object, one key per prompt:
result=
target=
Dot-separated --name paths nest into objects:
Use this when you need structured access, or when the key names contain characters that are awkward to work with as shell variable names.
License
MIT