This skill guides building small, pragmatic CLIs and scripts using the gum command (https://github.com/charmbracelet/gum) for TUI prompts and polished terminal output.
When To Use
Use this skill when the user wants:
- A small CLI or script (usually Bash) with interactive prompts.
- A TUI flow for choices, confirmations, forms, progress spinners, or styled output.
- A fast "ops" tool that’s nicer than raw
read/select.
Do not force TUI if:
- The command must run in CI/non-interactive mode by default.
- Output must be machine-parseable (prefer
--json/ plain output). - The environment likely lacks a TTY.
Defaults
- Shell:
bashwithset -euo pipefail. - Provide
--help,--version, and--no-interactivewhen relevant. - Respect
NO_COLOR=1(and/orGUM_NO_COLOR=1if the project uses it). - Allow non-interactive input via flags or environment variables.
- Fail fast with clear errors if
gumis missing, plus install hints.
Gum Components To Prefer
- Selection:
gum choose,gum filter - Input:
gum input,gum write(multiline) - Confirmation:
gum confirm - Feedback:
gum spin,gum log - Presentation:
gum style,gum table,gum pager,gum format,gum join
Interaction Rules
- Never prompt if stdin is not a TTY unless the user explicitly wants that behavior.
- If a value is provided by flag/env, do not prompt for it.
- Provide safe defaults; show the default in the prompt.
- Make cancel behavior explicit. If the user cancels, exit with code
130.
Script Skeleton (Copyable)
Use this as the default structure unless the repo already has conventions:
#!/usr/bin/env bash
set -euo pipefail
VERSION="0.1.0"
usage() {
cat <<'EOF'
Usage:
mytool [options]
Options:
--name <name> Name to use
--no-interactive Never prompt; fail if required inputs are missing
-h, --help Show help
-v, --version Show version
EOF
}
die() { printf "error: %s\n" "$*" >&2; exit 1; }
have_tty() { [[ -t 0 && -t 1 ]]; }
need_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing dependency: $1"
}
NO_INTERACTIVE=0
NAME="${NAME:-}"
while [[ $# -gt 0 ]]; do
case "$1" in
--name) NAME="${2:-}"; shift 2 ;;
--no-interactive) NO_INTERACTIVE=1; shift ;;
-h|--help) usage; exit 0 ;;
-v|--version) printf "%s\n" "$VERSION"; exit 0 ;;
*) die "unknown argument: $1 (use --help)" ;;
esac
done
if [[ "$NO_INTERACTIVE" -eq 0 ]] && have_tty; then
need_cmd gum
fi
if [[ -z "$NAME" ]]; then
if [[ "$NO_INTERACTIVE" -eq 1 || ! have_tty ]]; then
die "missing --name (or NAME env) in non-interactive mode"
fi
NAME="$(gum input --prompt "Name: " --placeholder "Jane Doe")" || exit 130
fi
if [[ "$NO_INTERACTIVE" -eq 0 ]] && have_tty; then
gum confirm "Proceed with name '$NAME'?" || exit 130
fi
run() {
printf "Hello, %s\n" "$NAME"
}
if [[ "$NO_INTERACTIVE" -eq 0 ]] && have_tty; then
gum spin --title "Working..." -- run
else
run
fiPatterns To Apply
- Choose menus: use
gum choosefor small lists;gum filterfor long lists. - Multi-step flows: keep data in variables; validate after each step.
- Progress: wrap long-running commands with
gum spin -- <cmd>. - Styled summaries: use
gum styleto render a final "review" screen before executing destructive actions. - Exit codes:
0success,1errors,2usage,130user cancel.
Safety And UX
- Destructive operations require explicit confirmation (
gum confirm). - Print what will happen before doing it.
- Avoid hiding stderr; show errors plainly.
- Support
--dry-runwhen actions change state.
What To Produce For The User
- A single script file (or minimal small set) that runs end-to-end.
- Clear usage text and examples.
- A short "Dependencies" note:
gumrequired for interactive mode. - A non-interactive path suitable for CI.
Quick Reference Commands
gum input --prompt "X: " --value "$X"gum choose "a" "b" "c"gum filter --placeholder "Search..." < <(printf "%s\n" "${items[@]}")gum confirm "Are you sure?"gum spin --title "Doing thing..." -- <cmd>gum style --border rounded --padding "1 2" "Title" "Body"gum table < file.tsvgum pager < file.txt