Merge pull request #1198 from mattpocock/wizard-template-fixes

wizard: fix template.sh readline, .env quoting, symlinks, EOF and opener warning
This commit is contained in:
Matt Pocock authored and GitHub committed 2026-10-07 11:02:08 +01:00
commit 0e1b345683
3 files changed
+30 -22

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"mattpocock-skills": patch
---
`wizard` template fixes: `ask` uses Readline so arrow keys move the cursor (#741); `write_env` single-quotes values so spaces, `#`, `$` and quotes survive `source` and dotenv (#770); `open_url` prints the open-it-yourself warning when no browser opener exists (#774); `write_env` writes through a symlinked `.env` and keeps an existing file's mode (#811); `ask` and `ask_secret` fail at EOF instead of looping (#852).
-2
View File
@@ -62,8 +62,6 @@ No. The agent writes a script; it doesn't run it. You run the script yourself, a
Not mid-run. There is no back button. The stages run forward, and a wrong answer on stage 3 means Ctrl-C and re-run. Re-running is cheap, because any value already written to `.env` is offered back as a default, so you press Enter through the stages you got right and retype only the wrong one. Users asked for this in launch week, and it is still open: "loved it! One thing though, is there a way to go back and correct what you've entered?"
There's a related open bug. Arrow keys in an `ask` prompt insert `^[[D` / `^[[C` instead of moving the cursor, because the prompt uses `read -r` rather than Readline ([issue #741](https://github.com/mattpocock/skills/issues/741)). Backspace works; arrow keys don't. Delete back to the mistake rather than moving the cursor into it.
**Does it know what I've already set up?**
Partly, and less than the launch reactions assumed. It reads the repo before it asks (your `.env` files, `docker-compose`, framework config, the `secrets.*` references in CI), so it scopes to values that are missing rather than starting from zero the way a README does. What it doesn't do is check the third-party service. If a key exists in your `.env` the wizard offers it back and Enter keeps it; if you already created the Stripe account but never saved the key, the wizard still sends you to the dashboard for it.
+25 -20
View File
@@ -70,7 +70,7 @@ open_url() {
elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
elif command -v open >/dev/null 2>&1; then open "$url"
else warn "couldn't open a browser; visit it manually: $url"; fi
else false; fi
} >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
}
@@ -88,52 +88,57 @@ confirm() {
[[ "$reply" =~ ^[Yy] ]]
}
# _existing KEY: current value of KEY in ENV_FILE, if any.
# _existing KEY: current value of KEY in ENV_FILE, if any, with write_env's quoting undone.
_existing() {
[[ -f "$ENV_FILE" ]] || return 1
local line; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
printf '%s' "${line#*=}"
local line value sq="'\\''"; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
value="${line#*=}"
if [[ "$value" == \'*\' ]]; then value="${value:1:${#value}-2}"; value="${value//"$sq"/\'}"; fi
printf '%s' "$value"
}
# ask KEY "Prompt" reads a value into $KEY. Offers the existing .env value as
# a default on re-runs (Enter keeps it). Visible input (non-secret).
# a default on re-runs (Enter keeps it). Visible input (non-secret), edited
# with Readline. Fails at EOF with no input.
ask() {
local key="$1" prompt="$2" current input
local key="$1" prompt="$2" current input s=$'\001' e=$'\002' p rc=0
current=$(_existing "$key" || true)
if [[ -n "$current" ]]; then
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
else
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
fi
read -r input || true
# Readline needs the colour codes wrapped in \001..\002 to place the cursor.
p=" $s$BOLD$e$prompt$s$RESET$e "
[[ -n "$current" ]] && p+="$s$DIM$e[Enter keeps current]$s$RESET$e "
read -e -r -p "$p" input || rc=1
if (( rc )) && [[ -z "$input" ]]; then return 1; fi
[[ -z "$input" && -n "$current" ]] && input="$current"
printf -v "$key" '%s' "$input"
}
# ask_secret KEY "Prompt" is like ask, but input is hidden.
ask_secret() {
local key="$1" prompt="$2" current input
local key="$1" prompt="$2" current input rc=0
current=$(_existing "$key" || true)
if [[ -n "$current" ]]; then
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
else
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
fi
read -rs input || true
read -rs input || rc=1
printf '\n'
if (( rc )) && [[ -z "$input" ]]; then return 1; fi
[[ -z "$input" && -n "$current" ]] && input="$current"
printf -v "$key" '%s' "$input"
}
# write_env KEY VALUE upserts KEY=VALUE into ENV_FILE (creates it; replaces
# any existing line). Idempotent.
# write_env KEY VALUE upserts KEY='VALUE' into ENV_FILE (replaces any existing
# line). Idempotent. Creates ENV_FILE at mode 0600; writes through a symlink
# and keeps an existing file's mode.
write_env() {
local key="$1" value="$2" tmp
touch "$ENV_FILE"
local key="$1" value="$2" tmp sq="'\\''"
[[ -e "$ENV_FILE" ]] || (umask 077 && : > "$ENV_FILE")
tmp=$(mktemp)
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
printf '%s=%s\n' "$key" "$value" >> "$tmp"
mv "$tmp" "$ENV_FILE"
printf "%s='%s'\n" "$key" "${value//\'/$sq}" >> "$tmp"
cat "$tmp" > "$ENV_FILE"
rm -f "$tmp"
WRITTEN_ENV+=("$key")
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
}