SKILL.md

  1---
  2name: charm-gum
  3description: "Interactive shell script prompts, fuzzy filters, spinners, and styled output with gum. Use when building bash/shell script UIs, gum commands, interactive shell prompts, or CLI script workflows. NOT for Go terminal forms (use huh)."
  4---
  5
  6# charm-gum
  7
  8gum is a CLI for glamorous shell scripts. All interaction writes to stdout; capture with `$()`. All commands render to stderr so stdout stays clean for piping.
  9
 10## Quick Start
 11
 12```bash
 13brew install gum           # macOS/Linux
 14go install github.com/charmbracelet/gum@latest
 15
 16NAME=$(gum input --placeholder "your name")
 17gum confirm "Continue?" && echo "hello $NAME"
 18```
 19
 20Every flag has an env var equivalent: `--placeholder` = `GUM_INPUT_PLACEHOLDER`. Export env vars to set defaults project-wide.
 21
 22## Command Reference
 23
 24### input - single-line prompt
 25
 26```bash
 27gum input [flags]
 28# key flags:
 29#   --placeholder "text"   hint text
 30#   --value "text"         pre-filled value
 31#   --password             mask input
 32#   --header "text"        label above input
 33#   --width N              fixed width (0 = terminal width)
 34#   --char-limit N         max chars (default 400, 0 = unlimited)
 35#   --timeout 30s          auto-submit after duration
 36
 37NAME=$(gum input --placeholder "full name" --header "Enter your name")
 38PASS=$(gum input --password --placeholder "password")
 39```
 40
 41### write - multi-line textarea
 42
 43```bash
 44gum write [flags]
 45# key flags:
 46#   --placeholder "text"
 47#   --header "text"
 48#   --width N, --height N
 49#   --show-line-numbers
 50#   --show-cursor-line
 51#   --max-lines N
 52# ctrl+d to submit, ctrl+c to cancel
 53
 54BODY=$(gum write --placeholder "PR description..." --header "Description" --width 80)
 55```
 56
 57### choose - pick from a list
 58
 59```bash
 60gum choose [options...] [flags]
 61# pipe options or pass as args
 62# key flags:
 63#   --limit N              max selectable (default 1)
 64#   --no-limit             unlimited selection
 65#   --header "text"
 66#   --height N             visible rows (default 10)
 67#   --cursor "> "          cursor prefix
 68#   --selected "val"       pre-selected item
 69#   --ordered              preserve selection order
 70#   --timeout 30s
 71
 72TYPE=$(gum choose "fix" "feat" "docs" "chore" "refactor")
 73PKGS=$(brew list | gum choose --no-limit --header "Remove packages")
 74```
 75
 76### filter - fuzzy search a list
 77
 78```bash
 79gum filter [options...] [flags]
 80# reads from stdin or args; fuzzy match by default
 81# key flags:
 82#   --limit N
 83#   --no-limit
 84#   --placeholder "text"
 85#   --header "text"
 86#   --height N
 87#   --value "text"         initial filter query
 88#   --no-fuzzy             prefix match only
 89#   --no-strict            return query if no match
 90#   --reverse              render from bottom
 91
 92SESSION=$(tmux list-sessions -F '#S' | gum filter --placeholder "pick session...")
 93BRANCH=$(git branch | cut -c 3- | gum filter --placeholder "checkout...")
 94```
 95
 96### confirm - yes/no prompt
 97
 98```bash
 99gum confirm [prompt] [flags]
100# exits 0 = yes, 1 = no; use with && / ||
101# key flags:
102#   --affirmative "Yes"    confirm button label
103#   --negative "No"        cancel button label
104#   --default              which is pre-selected (true = yes)
105#   --timeout 30s
106#   --show-output          echo chosen action to stdout
107
108gum confirm "Delete branch?" && git branch -D "$BRANCH"
109gum confirm "Overwrite?" --affirmative "Overwrite" --negative "Skip" || exit 0
110```
111
112### spin - spinner while command runs
113
114```bash
115gum spin [flags] -- <command>
116# key flags:
117#   --title "text"         message shown next to spinner
118#   --spinner dot          type: line,dot,minidot,jump,pulse,points,globe,moon,monkey,meter,hamburger
119#   --show-output          stream stdout/stderr live
120#   --show-error           show output only on failure
121#   --timeout 60s
122
123gum spin --title "Installing deps..." -- npm install
124OUTPUT=$(gum spin --show-output --title "Fetching..." -- curl -s https://api.example.com/data)
125```
126
127### style - styled text output
128
129```bash
130gum style [flags] "text" ["text2" ...]
131# multiple strings are rendered as separate lines in one block
132# key flags:
133#   --foreground "#hex"|"256color"
134#   --background "#hex"|"256color"
135#   --border none|hidden|normal|rounded|thick|double
136#   --border-foreground
137#   --align left|center|right
138#   --width N, --height N
139#   --margin "T R B L" (css shorthand)
140#   --padding "T R B L"
141#   --bold, --italic, --underline, --strikethrough, --faint
142
143gum style --foreground 212 --border rounded --padding "1 2" "Done!"
144```
145
146### format - render markdown, code, templates, emoji
147
148```bash
149gum format [flags] [text...]
150# key flags:
151#   -t markdown|template|code|emoji   (default: markdown)
152#   -l python                          language hint for code type
153#   --theme pink                       glamour theme for markdown
154
155echo "# Hello\n- item 1\n- item 2" | gum format
156cat script.sh | gum format -t code -l bash
157echo '{{ Bold "OK" }} {{ Color "99" "0" " gum " }}' | gum format -t template
158echo "I :heart: gum :candy:" | gum format -t emoji
159```
160
161### join - compose styled blocks side by side or stacked
162
163```bash
164gum join [flags] "block1" "block2"
165# flags:
166#   --vertical     stack top to bottom (default is horizontal)
167#   --align left|center|right
168
169# always quote gum style output to preserve newlines
170A=$(gum style --border rounded --padding "0 2" "left")
171B=$(gum style --border rounded --padding "0 2" "right")
172gum join "$A" "$B"
173```
174
175### file - file picker from tree
176
177```bash
178gum file [path]   # defaults to current dir
179# flags: --cursor, --height, --show-hidden
180
181$EDITOR "$(gum file $HOME)"
182```
183
184### pager - scrollable viewer
185
186```bash
187gum pager < README.md
188gum pager --show-line-numbers < file.txt
189```
190
191### table - pick a row from CSV
192
193```bash
194gum table < data.csv
195gum table -c Name,Age,Role < users.csv | cut -d',' -f1
196```
197
198### log - structured log output
199
200```bash
201# levels: debug, info, warn, error, fatal
202gum log --level info "Server started" port 8080
203gum log --structured --level error "Failed" file foo.txt
204gum log --time rfc822 --level warn "Slow response"
205```
206
207## Shell Script Patterns
208
209### 1. conventional commit helper
210
211```bash
212#!/bin/bash
213set -e
214
215TYPE=$(gum choose "fix" "feat" "docs" "style" "refactor" "test" "chore")
216SCOPE=$(gum input --placeholder "scope (optional)")
217[ -n "$SCOPE" ] && SCOPE="($SCOPE)"
218
219SUMMARY=$(gum input --value "$TYPE$SCOPE: " --placeholder "short summary")
220BODY=$(gum write --placeholder "longer description (ctrl+d to skip)" --height 6)
221
222gum confirm "Commit?" || exit 0
223git commit -m "$SUMMARY" ${BODY:+-m "$BODY"}
224```
225
226### 2. interactive branch cleanup
227
228```bash
229#!/bin/bash
230set -e
231
232# pick branches to delete
233BRANCHES=$(git branch | cut -c 3- | gum choose --no-limit --header "Branches to delete")
234[ -z "$BRANCHES" ] && exit 0
235
236gum style --foreground 196 --bold "Will delete:"
237echo "$BRANCHES" | gum format
238
239gum confirm "Delete these branches?" --affirmative "Delete" --negative "Cancel" || exit 0
240
241echo "$BRANCHES" | while IFS= read -r branch; do
242  gum spin --title "Deleting $branch..." -- git branch -D "$branch"
243  gum log --level info "Deleted" branch "$branch"
244done
245```
246
247### 3. deploy script with env selection
248
249```bash
250#!/bin/bash
251set -e
252
253ENV=$(gum choose "staging" "production" --header "Deploy target")
254TAG=$(git tag --sort=-v:refname | gum filter --placeholder "pick version tag...")
255
256gum style \
257  --border rounded --border-foreground 214 \
258  --padding "1 3" --margin "1" \
259  "Deploy $TAG to $ENV?"
260
261gum confirm "Proceed?" --default=false || exit 0
262
263gum spin --title "Deploying $TAG to $ENV..." --show-error -- \
264  ./deploy.sh "$ENV" "$TAG"
265
266gum style --foreground 82 --bold "Deployed $TAG to $ENV"
267```
268
269### 4. quick file notes launcher
270
271```bash
272#!/bin/bash
273# browse vault notes, open selected in editor
274VAULT="$HOME/notes"
275
276FILE=$(find "$VAULT" -name "*.md" | sed "s|$VAULT/||" \
277  | gum filter --placeholder "search notes..." --height 20)
278
279[ -z "$FILE" ] && exit 0
280$EDITOR "$VAULT/$FILE"
281```
282
283### 5. package manager TUI
284
285```bash
286#!/bin/bash
287set -e
288
289ACTION=$(gum choose "install" "remove" "update" --header "Package action")
290
291case "$ACTION" in
292  install)
293    PKG=$(gum input --placeholder "package name")
294    gum spin --title "Installing $PKG..." -- brew install "$PKG"
295    ;;
296  remove)
297    PKGS=$(brew list | gum choose --no-limit --header "Select packages to remove")
298    [ -z "$PKGS" ] && exit 0
299    gum confirm "Remove selected?" || exit 0
300    echo "$PKGS" | xargs gum spin --title "Removing..." -- brew uninstall
301    ;;
302  update)
303    gum spin --title "Updating Homebrew..." --show-error -- brew update
304    gum spin --title "Upgrading packages..." --show-output -- brew upgrade
305    ;;
306esac
307
308gum log --level info "Done" action "$ACTION"
309```
310
311## Styling
312
313Colors accept ANSI 256 codes (`212`) or hex (`#FF79C6`). Padding/margin use CSS shorthand: `"1 2"` = top/bottom 1, left/right 2.
314
315```bash
316# banner helper pattern
317banner() {
318  gum style \
319    --foreground 212 --border-foreground 212 \
320    --border double --align center \
321    --padding "1 4" --margin "1 2" \
322    "$@"
323}
324banner "Build Complete" "v1.4.2"
325
326# side by side layout
327LEFT=$(gum style --border rounded --padding "0 3" --foreground 82 "OK")
328RIGHT=$(gum style --border rounded --padding "0 3" --foreground 196 "ERR")
329gum join "$LEFT" "$RIGHT"
330```
331
332Style env vars use `GUM_<CMD>_<FLAG>` format. Set in shell profile for persistent defaults:
333
334```bash
335export GUM_CHOOSE_CURSOR_FOREGROUND="#FF79C6"
336export GUM_INPUT_PLACEHOLDER="..."
337export GUM_SPIN_SPINNER="dot"
338```
339
340## Common Mistakes
341
342- **capturing output**: gum writes the prompt to stderr, result to stdout. `VAL=$(gum input)` works correctly.
343- **spin command separator**: `--` is required before the command: `gum spin --title "..." -- npm install`. Without it gum parses your command as its own flags.
344- **confirm exit code**: `gum confirm` returns 0 for yes, 1 for no. Use `&&`/`||` not `if [ $? -eq 0 ]` - both work but `&&` is idiomatic.
345- **join with newlines**: always quote `$(gum style ...)` in join args or newlines collapse: `gum join "$A" "$B"` not `gum join $A $B`.
346- **filter with no match**: by default `--strict` is on - filter returns nothing if no match. Use `--no-strict` to return the query string instead.
347- **choose vs filter**: `choose` = static list, cursor navigation. `filter` = fuzzy search while typing. Use filter for long lists.
348- **multi-select output**: each selection on its own line. Iterate with `while IFS= read -r item; do ... done <<< "$SELECTION"`.
349
350## Checklist
351
352- [ ] capture interactive output with `$()`, not redirect
353- [ ] add `-- command` separator for `gum spin`
354- [ ] handle empty selection (`[ -z "$VAR" ] && exit 0`)
355- [ ] use `--no-limit` for multi-select, iterate output line by line
356- [ ] use `--default=false` on destructive confirms
357- [ ] quote `$(gum style ...)` when passing to `gum join`
358- [ ] set `--timeout` for unattended or CI-adjacent scripts
359- [ ] test ctrl+c behavior - gum exits non-zero, handle with `set -e` or explicit checks