Skill Tracer
Skills › Skills For Real Engineers › wizard › template.sh

template.sh

223 lines · skills/engineering/wizard/template.sh at 4ae2317

1#!/usr/bin/env bash
2#
3# A wizard walks a human through a manual procedure, step by step.
4# Generated by the /wizard skill.
5#
6# Everything above the "STAGES" marker is the wizard library: do not hand-edit
7# it. Author the per-step stages below the marker.
8
9set -euo pipefail
11# ──────────────────────────────────────────────────────────────────────────
12# Wizard library: delightful, consistent UX, identical across every wizard.
13# ──────────────────────────────────────────────────────────────────────────
15if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
16 BOLD=$(tput bold); DIM=$(tput dim); RESET=$(tput sgr0)
17 BLUE=$(tput setaf 4); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3)
18else
19 BOLD=""; DIM=""; RESET=""; BLUE=""; GREEN=""; YELLOW=""
20fi
22# Author sets this at the top of the stages section.
23TOTAL_STAGES=0
25_STAGE_INDEX=0
26ENV_FILE="${ENV_FILE:-.env}"
27WRITTEN_ENV=() # KEYs written to ENV_FILE this run
28WRITTEN_SECRET=() # secret NAMEs set this run
29SKIPPED=() # things we couldn't do (e.g. gh missing)
31# _clear wipes the terminal so only the current step is on screen. No-op when
32# output isn't a terminal, so piped logs stay readable.
33_clear() {
34 [[ -t 1 ]] || return 0
35 if command -v tput >/dev/null 2>&1 && tput clear 2>/dev/null; then return 0; fi
36 printf '\033[2J\033[3J\033[H'
39# banner "Title" shows the opening frame: what this wizard does.
40banner() {
41 _clear
42 printf '\n%s%s %s%s\n' "$BOLD" "$BLUE" "$1" "$RESET"
43 printf '%s %s stages%s\n\n' "$DIM" "$TOTAL_STAGES" "$RESET"
44 printf '%s You drive the browser; this wizard tells you exactly what to do and\n' "$DIM"
45 printf ' captures the values you copy back. Stop any time with Ctrl-C and re-run\n'
46 printf ' later, since it remembers values already saved.%s\n' "$RESET"
47 pause "Ready to start?"
50# stage "Name" clears the screen, then announces a stage and shows progress.
51# Clearing keeps only the current step on screen.
52stage() {
53 _clear
54 _STAGE_INDEX=$((_STAGE_INDEX + 1))
55 printf '\n%s%s▸ Stage %s/%s · %s%s\n' \
56 "$BOLD" "$BLUE" "$_STAGE_INDEX" "$TOTAL_STAGES" "$1" "$RESET"
59# say "..." prints a plain instruction line.
60say() { printf ' %s\n' "$1"; }
61# step "..." is a numbered-feeling action the human takes in the browser.
62step() { printf ' %s•%s %s\n' "$BLUE" "$RESET" "$1"; }
63note() { printf ' %s%s%s\n' "$DIM" "$1" "$RESET"; }
64warn() { printf ' %s⚠ %s%s\n' "$YELLOW" "$1" "$RESET"; }
66# open_url URL opens it in the human's browser, cross-platform incl. WSL.
67open_url() {
68 local url="$1"
69 printf ' %s↗ opening%s %s\n' "$GREEN" "$RESET" "$url"
70 { if command -v wslview >/dev/null 2>&1; then wslview "$url"
71 elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
72 elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
73 elif command -v open >/dev/null 2>&1; then open "$url"
74 else false; fi
75 } >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
78# pause "msg" waits for the human to confirm they've done the manual part.
79pause() {
80 printf ' %s%s%s ' "$DIM" "${1:-Press Enter to continue}" "$RESET"
81 read -r _ || true
84# confirm "question" is a y/N gate; returns success on yes.
85confirm() {
86 local reply=""
87 printf ' %s? %s [y/N] ' "$YELLOW" "$1"
88 read -r reply || true
89 [[ "$reply" =~ ^[Yy] ]]
92# _existing KEY: current value of KEY in ENV_FILE, if any, with write_env's single
93# quotes or a plain dotenv double-quoted value undone.
94_existing() {
95 [[ -f "$ENV_FILE" ]] || return 1
96 local line value sq="'\\''"; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
97 value="${line#*=}"
98 if [[ "$value" == \'*\' ]]; then value="${value:1:${#value}-2}"; value="${value//"$sq"/\'}"
99 elif [[ "$value" == \"*\" ]]; then
100 value="${value:1:${#value}-2}"
101 [[ "$value" == *[\\\$\"]* ]] && return 1 # escapes, $ or ": can't decode safely; ask again
102 fi
103 printf '%s' "$value"
106# ask KEY "Prompt" reads a value into $KEY. Offers the existing .env value as
107# a default on re-runs (Enter keeps it). Visible input (non-secret), edited
108# with Readline. Fails at EOF with no input.
109ask() {
110 local key="$1" prompt="$2" current input s=$'\001' e=$'\002' p rc=0
111 current=$(_existing "$key" || true)
112 # Readline needs the colour codes wrapped in \001..\002 to place the cursor.
113 p=" $s$BOLD$e$prompt$s$RESET$e "
114 [[ -n "$current" ]] && p+="$s$DIM$e[Enter keeps current]$s$RESET$e "
115 read -e -r -p "$p" input || rc=1
116 if (( rc )) && [[ -z "$input" ]]; then return 1; fi
117 [[ -z "$input" && -n "$current" ]] && input="$current"
118 printf -v "$key" '%s' "$input"
121# ask_secret KEY "Prompt" is like ask, but input is hidden.
122ask_secret() {
123 local key="$1" prompt="$2" current input rc=0
124 current=$(_existing "$key" || true)
125 if [[ -n "$current" ]]; then
126 printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
127 else
128 printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
129 fi
130 read -rs input || rc=1
131 printf '\n'
132 if (( rc )) && [[ -z "$input" ]]; then return 1; fi
133 [[ -z "$input" && -n "$current" ]] && input="$current"
134 printf -v "$key" '%s' "$input"
137# write_env KEY VALUE upserts KEY='VALUE' into ENV_FILE (replaces any existing
138# line) and sets $KEY. Idempotent. Creates ENV_FILE at mode 0600; writes through
139# a symlink and keeps an existing file's mode.
140write_env() {
141 local key="$1" value="$2" tmp sq="'\\''"
142 [[ -e "$ENV_FILE" ]] || (umask 077 && : > "$ENV_FILE")
143 tmp=$(mktemp)
144 grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
145 printf "%s='%s'\n" "$key" "${value//\'/$sq}" >> "$tmp"
146 cat "$tmp" > "$ENV_FILE"
147 rm -f "$tmp"
148 printf -v "$key" '%s' "$value"
149 WRITTEN_ENV+=("$key")
150 printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
153# set_secret NAME VALUE sets a GitHub Actions repo secret via gh. Falls back
154# to a warning (and records it) if gh is unavailable or unauthenticated.
155set_secret() {
156 local name="$1" value="$2"
157 if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
158 if printf '%s' "$value" | gh secret set "$name" >/dev/null 2>&1; then
159 WRITTEN_SECRET+=("$name")
160 printf ' %s✓ set%s GitHub secret %s\n' "$GREEN" "$RESET" "$name"
161 return
162 fi
163 fi
164 SKIPPED+=("GitHub secret $name (set it manually: gh secret set $name)")
165 warn "skipped GitHub secret $name: gh not ready; set it later"
168# set_var NAME VALUE sets a GitHub Actions repo variable (non-secret).
169set_var() {
170 local name="$1" value="$2"
171 if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
172 if gh variable set "$name" --body "$value" >/dev/null 2>&1; then
173 printf ' %s✓ set%s GitHub variable %s\n' "$GREEN" "$RESET" "$name"
174 return
175 fi
176 fi
177 SKIPPED+=("GitHub variable $name")
178 warn "skipped GitHub variable $name, gh not ready; set it later"
181# finish clears, then shows a closing summary of everything configured.
182finish() {
183 _clear
184 printf '\n%s%s ✓ Setup complete%s\n' "$BOLD" "$GREEN" "$RESET"
185 (( ${#WRITTEN_ENV[@]} )) && note "wrote ${#WRITTEN_ENV[@]} value(s) to $ENV_FILE: ${WRITTEN_ENV[*]}"
186 (( ${#WRITTEN_SECRET[@]} )) && note "set ${#WRITTEN_SECRET[@]} GitHub secret(s): ${WRITTEN_SECRET[*]}"
187 if (( ${#SKIPPED[@]} )); then
188 printf '\n'; warn "still to do by hand:"
189 for s in "${SKIPPED[@]}"; do note " - $s"; done
190 fi
191 printf '\n'
194# ──────────────────────────────────────────────────────────────────────────
195# STAGES: author this section. One stage() per step the human takes.
196# Replace the example below. Set TOTAL_STAGES to match the stages you write.
197# Keep every stage inside run_wizard and the last line as is: bash then parses
198# the whole file before the first prompt, so an edit mid-run can't break it.
199# ──────────────────────────────────────────────────────────────────────────
201run_wizard() {
203TOTAL_STAGES=1
205banner "Stripe setup"
207# ── Example stage: replace with your real steps ───────────────────────────
208stage "Stripe: API keys"
209say "We'll grab your Stripe test keys and store them for local dev + CI."
210open_url "https://dashboard.stripe.com/test/apikeys"
211step "On the API keys page, copy the Publishable key (starts pk_test_)."
212ask STRIPE_PUBLISHABLE_KEY "Paste the publishable key:"
213step "Click 'Reveal test key' on the Secret key row, then copy it."
214ask_secret STRIPE_SECRET_KEY "Paste the secret key:"
215write_env STRIPE_PUBLISHABLE_KEY "$STRIPE_PUBLISHABLE_KEY"
216write_env STRIPE_SECRET_KEY "$STRIPE_SECRET_KEY"
217set_secret STRIPE_SECRET_KEY "$STRIPE_SECRET_KEY" # CI needs this one
218# ──────────────────────────────────────────────────────────────────────────
220finish
223run_wizard "$@"; exit