feat(optional-skills): add setup-wizard-generator — bash wizard for human-only setup steps
Ports the MIT-licensed 'wizard' skill from mattpocock/skills as setup-wizard-generator (optional-skills/devops). Generates an interactive bash wizard that walks a human through manual procedures: opens dashboard URLs, captures values (hidden entry for secrets), writes .env / GitHub secrets idempotently, and confirms each stage. Vendors upstream's template.sh library verbatim (bash -n verified) plus a staging test suite covering frontmatter, template integrity, and de-upstreaming.
This commit is contained in:
116
optional-skills/devops/setup-wizard-generator/SKILL.md
Normal file
116
optional-skills/devops/setup-wizard-generator/SKILL.md
Normal file
@@ -0,0 +1,116 @@
|
||||
---
|
||||
name: setup-wizard-generator
|
||||
description: "Generate a bash wizard guiding a human through manual setup."
|
||||
version: 1.0.0
|
||||
author: "Matt Pocock (mattpocock/skills, wizard) + Hermes Agent"
|
||||
license: MIT
|
||||
platforms: [linux, macos]
|
||||
metadata:
|
||||
hermes:
|
||||
tags: [wizard, setup, onboarding, credentials, secrets, migration, bash, human-in-the-loop]
|
||||
related_skills: []
|
||||
---
|
||||
|
||||
# Setup Wizard Generator
|
||||
|
||||
Generates an interactive bash **wizard**: a script that walks a human, step
|
||||
by step, through a manual procedure that is tedious to do by hand and tedious
|
||||
to re-explain every time. It opens each URL, says exactly what to click and
|
||||
copy, captures the values, writes them where they belong (`.env`, GitHub
|
||||
secrets), confirms at every stage, and shows how many stages are left.
|
||||
|
||||
Ported from mattpocock/skills' MIT-licensed `wizard` skill.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Provisioning infrastructure or third-party services (Stripe, Supabase,
|
||||
DNS, OAuth apps) where only a human can click through the dashboard
|
||||
- Setting up credentials, CI secrets, or repo variables
|
||||
- One-off migrations or cutovers with irreversible human-gated steps
|
||||
- Any procedure the user will hand to a teammate to run
|
||||
|
||||
Do NOT use for steps the agent can perform itself — do those directly.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `bash`; `gh` CLI only if stages write GitHub secrets/variables
|
||||
- The library template: `templates/template.sh` in this skill's directory
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Scope the procedure
|
||||
|
||||
Work out every manual step the human must take and every value captured
|
||||
along the way. Read the repo first, don't ask cold:
|
||||
|
||||
- Setup: `.env`, `.env.example`, `README`, `docker-compose*`, framework
|
||||
config, and `.github/workflows/*` (every `secrets.*` / `vars.*` reference
|
||||
is a value the wizard must produce).
|
||||
- Migration/cutover: the current state, the target state, and the
|
||||
irreversible actions between them.
|
||||
|
||||
Show the user the ordered stage list and the values each produces; they may
|
||||
add, drop, or reorder. Done when every stage is named in order and, for each
|
||||
captured value, you know (a) where the human gets it, (b) where it's written
|
||||
(`.env`, a GitHub secret, both, or nowhere), and (c) whether it's secret
|
||||
(hidden entry) or public.
|
||||
|
||||
### 2. Map each stage's journey
|
||||
|
||||
For each stage, write the precise path a human follows: which URL to open,
|
||||
what to do there, where the value is shown — e.g. "Dashboard → Developers →
|
||||
API keys → Reveal test key → copy". Where you don't know the current UI or
|
||||
exact command, say so and check the docs or ask — never invent steps that
|
||||
may not exist.
|
||||
|
||||
### 3. Author the wizard
|
||||
|
||||
Copy `templates/template.sh` (from this skill's directory) to the target
|
||||
path. Replace the example stage with one `stage` per step, in dependency
|
||||
order. Set `TOTAL_STAGES` to the number of stages you wrote.
|
||||
|
||||
Library helpers: `stage`, `say`/`step`/`note`/`warn`, `open_url`,
|
||||
`ask`/`ask_secret`, `write_env`, `set_secret`/`set_var`, `pause`/`confirm`,
|
||||
`banner`, `finish`. The library above the `STAGES` marker is identical in
|
||||
every wizard — never hand-edit it; that consistency is the point.
|
||||
|
||||
Hold the bar the template sets: open the URL before asking for its value,
|
||||
`ask_secret` for anything secret, `write_env` every persisted value,
|
||||
`set_secret` only what CI actually needs, and `confirm` before anything
|
||||
irreversible. Each `stage` clears the screen — keep one focused task per
|
||||
stage so nothing the human needs scrolls away.
|
||||
|
||||
A wizard is ephemeral by default: save it to a scratch or `scripts/` path,
|
||||
delete it when the job's done. Commit it only when the user wants a
|
||||
repeatable setup path living in the repo.
|
||||
|
||||
### 4. Verify and hand off
|
||||
|
||||
- `bash -n <script>`; run `shellcheck` if available; `chmod +x <script>`.
|
||||
- Do NOT run it end-to-end yourself: it opens browsers and blocks on human
|
||||
input. Trace it statically: every value from step 1 is captured and lands
|
||||
where step 1 said, and every `set_secret` name exactly matches a
|
||||
`secrets.*` reference in CI.
|
||||
- Tell the user how to run it. If it's repeatable, commit it and link it
|
||||
from the README.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **Editing the library section.** Everything above the `STAGES` marker is
|
||||
the wizard library; author only below it.
|
||||
2. **Inventing dashboard paths.** Third-party UIs drift. If unsure of the
|
||||
click path, verify against current docs or flag it as approximate.
|
||||
3. **`set_secret` for values CI doesn't use.** Only push to GitHub secrets
|
||||
what a workflow actually references.
|
||||
4. **Running the wizard yourself.** It blocks on human input; static tracing
|
||||
plus `bash -n` is the verification.
|
||||
5. **One mega-stage.** Screen clearing per stage means a long stage scrolls
|
||||
critical instructions away; split it.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Stage list confirmed with the user before authoring
|
||||
- [ ] `bash -n` passes; script is executable
|
||||
- [ ] Every captured value traced to its declared destination
|
||||
- [ ] Every `set_secret` name matches a CI `secrets.*` reference
|
||||
- [ ] Library section untouched from the template
|
||||
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# A wizard walks a human through a manual procedure, step by step.
|
||||
# Generated by the setup-wizard-generator skill.
|
||||
#
|
||||
# Everything above the "STAGES" marker is the wizard library: do not hand-edit
|
||||
# it. Author the per-step stages below the marker.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# Wizard library: delightful, consistent UX, identical across every wizard.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
|
||||
BOLD=$(tput bold); DIM=$(tput dim); RESET=$(tput sgr0)
|
||||
BLUE=$(tput setaf 4); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3); RED=$(tput setaf 1)
|
||||
else
|
||||
BOLD=""; DIM=""; RESET=""; BLUE=""; GREEN=""; YELLOW=""; RED=""
|
||||
fi
|
||||
|
||||
# Author sets this at the top of the stages section.
|
||||
TOTAL_STAGES=0
|
||||
|
||||
_STAGE_INDEX=0
|
||||
ENV_FILE="${ENV_FILE:-.env}"
|
||||
WRITTEN_ENV=() # KEYs written to ENV_FILE this run
|
||||
WRITTEN_SECRET=() # secret NAMEs set this run
|
||||
SKIPPED=() # things we couldn't do (e.g. gh missing)
|
||||
|
||||
# _clear wipes the terminal so only the current step is on screen. No-op when
|
||||
# output isn't a terminal, so piped logs stay readable.
|
||||
_clear() {
|
||||
[[ -t 1 ]] || return 0
|
||||
if command -v tput >/dev/null 2>&1; then tput clear; else printf '\033[2J\033[3J\033[H'; fi
|
||||
}
|
||||
|
||||
# banner "Title" shows the opening frame: what this wizard does.
|
||||
banner() {
|
||||
_clear
|
||||
printf '\n%s%s %s%s\n' "$BOLD" "$BLUE" "$1" "$RESET"
|
||||
printf '%s %s stages%s\n\n' "$DIM" "$TOTAL_STAGES" "$RESET"
|
||||
printf '%s You drive the browser; this wizard tells you exactly what to do and\n' "$DIM"
|
||||
printf ' captures the values you copy back. Stop any time with Ctrl-C and re-run\n'
|
||||
printf ' later, since it remembers values already saved.%s\n' "$RESET"
|
||||
pause "Ready to start?"
|
||||
}
|
||||
|
||||
# stage "Name" clears the screen, then announces a stage and shows progress.
|
||||
# Clearing keeps only the current step on screen.
|
||||
stage() {
|
||||
_clear
|
||||
_STAGE_INDEX=$((_STAGE_INDEX + 1))
|
||||
printf '\n%s%s▸ Stage %s/%s · %s%s\n' \
|
||||
"$BOLD" "$BLUE" "$_STAGE_INDEX" "$TOTAL_STAGES" "$1" "$RESET"
|
||||
}
|
||||
|
||||
# say "..." prints a plain instruction line.
|
||||
say() { printf ' %s\n' "$1"; }
|
||||
# step "..." is a numbered-feeling action the human takes in the browser.
|
||||
step() { printf ' %s•%s %s\n' "$BLUE" "$RESET" "$1"; }
|
||||
note() { printf ' %s%s%s\n' "$DIM" "$1" "$RESET"; }
|
||||
warn() { printf ' %s⚠ %s%s\n' "$YELLOW" "$1" "$RESET"; }
|
||||
|
||||
# open_url URL opens it in the human's browser, cross-platform incl. WSL.
|
||||
open_url() {
|
||||
local url="$1"
|
||||
printf ' %s↗ opening%s %s\n' "$GREEN" "$RESET" "$url"
|
||||
{ if command -v wslview >/dev/null 2>&1; then wslview "$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
|
||||
} >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
|
||||
}
|
||||
|
||||
# pause "msg" waits for the human to confirm they've done the manual part.
|
||||
pause() {
|
||||
printf ' %s%s%s ' "$DIM" "${1:-Press Enter to continue}" "$RESET"
|
||||
read -r _ || true
|
||||
}
|
||||
|
||||
# confirm "question" is a y/N gate; returns success on yes.
|
||||
confirm() {
|
||||
local reply=""
|
||||
printf ' %s? %s [y/N] ' "$YELLOW" "$1"
|
||||
read -r reply || true
|
||||
[[ "$reply" =~ ^[Yy] ]]
|
||||
}
|
||||
|
||||
# _existing KEY: current value of KEY in ENV_FILE, if any.
|
||||
_existing() {
|
||||
[[ -f "$ENV_FILE" ]] || return 1
|
||||
local line; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
|
||||
printf '%s' "${line#*=}"
|
||||
}
|
||||
|
||||
# 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).
|
||||
ask() {
|
||||
local key="$1" prompt="$2" current input
|
||||
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
|
||||
[[ -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
|
||||
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
|
||||
printf '\n'
|
||||
[[ -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() {
|
||||
local key="$1" value="$2" tmp
|
||||
touch "$ENV_FILE"
|
||||
tmp=$(mktemp)
|
||||
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
|
||||
printf '%s=%s\n' "$key" "$value" >> "$tmp"
|
||||
mv "$tmp" "$ENV_FILE"
|
||||
WRITTEN_ENV+=("$key")
|
||||
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
|
||||
}
|
||||
|
||||
# set_secret NAME VALUE sets a GitHub Actions repo secret via gh. Falls back
|
||||
# to a warning (and records it) if gh is unavailable or unauthenticated.
|
||||
set_secret() {
|
||||
local name="$1" value="$2"
|
||||
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
|
||||
if printf '%s' "$value" | gh secret set "$name" >/dev/null 2>&1; then
|
||||
WRITTEN_SECRET+=("$name")
|
||||
printf ' %s✓ set%s GitHub secret %s\n' "$GREEN" "$RESET" "$name"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
SKIPPED+=("GitHub secret $name (set it manually: gh secret set $name)")
|
||||
warn "skipped GitHub secret $name: gh not ready; set it later"
|
||||
}
|
||||
|
||||
# set_var NAME VALUE sets a GitHub Actions repo variable (non-secret).
|
||||
set_var() {
|
||||
local name="$1" value="$2"
|
||||
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
|
||||
if gh variable set "$name" --body "$value" >/dev/null 2>&1; then
|
||||
printf ' %s✓ set%s GitHub variable %s\n' "$GREEN" "$RESET" "$name"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
SKIPPED+=("GitHub variable $name")
|
||||
warn "skipped GitHub variable $name, gh not ready; set it later"
|
||||
}
|
||||
|
||||
# finish clears, then shows a closing summary of everything configured.
|
||||
finish() {
|
||||
_clear
|
||||
printf '\n%s%s ✓ Setup complete%s\n' "$BOLD" "$GREEN" "$RESET"
|
||||
(( ${#WRITTEN_ENV[@]} )) && note "wrote ${#WRITTEN_ENV[@]} value(s) to $ENV_FILE: ${WRITTEN_ENV[*]}"
|
||||
(( ${#WRITTEN_SECRET[@]} )) && note "set ${#WRITTEN_SECRET[@]} GitHub secret(s): ${WRITTEN_SECRET[*]}"
|
||||
if (( ${#SKIPPED[@]} )); then
|
||||
printf '\n'; warn "still to do by hand:"
|
||||
for s in "${SKIPPED[@]}"; do note " - $s"; done
|
||||
fi
|
||||
printf '\n'
|
||||
}
|
||||
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
# STAGES: author this section. One stage() per step the human takes.
|
||||
# Replace the example below. Set TOTAL_STAGES to match the stages you write.
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
TOTAL_STAGES=1
|
||||
|
||||
banner "Stripe setup"
|
||||
|
||||
# ── Example stage: replace with your real steps ───────────────────────────
|
||||
stage "Stripe: API keys"
|
||||
say "We'll grab your Stripe test keys and store them for local dev + CI."
|
||||
open_url "https://dashboard.stripe.com/test/apikeys"
|
||||
step "On the API keys page, copy the Publishable key (starts pk_test_)."
|
||||
ask STRIPE_PUBLISHABLE_KEY "Paste the publishable key:"
|
||||
step "Click 'Reveal test key' on the Secret key row, then copy it."
|
||||
ask_secret STRIPE_SECRET_KEY "Paste the secret key:"
|
||||
write_env STRIPE_PUBLISHABLE_KEY "$STRIPE_PUBLISHABLE_KEY"
|
||||
write_env STRIPE_SECRET_KEY "$STRIPE_SECRET_KEY"
|
||||
set_secret STRIPE_SECRET_KEY "$STRIPE_SECRET_KEY" # CI needs this one
|
||||
# ──────────────────────────────────────────────────────────────────────────
|
||||
|
||||
finish
|
||||
87
tests/skills/test_setup_wizard_generator_skill.py
Normal file
87
tests/skills/test_setup_wizard_generator_skill.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Tests for optional-skills/devops/setup-wizard-generator (template integrity)."""
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
SKILL_DIR = (
|
||||
Path(__file__).resolve().parents[2]
|
||||
/ "optional-skills"
|
||||
/ "devops"
|
||||
/ "setup-wizard-generator"
|
||||
)
|
||||
SKILL_MD = SKILL_DIR / "SKILL.md"
|
||||
TEMPLATE = SKILL_DIR / "templates" / "template.sh"
|
||||
|
||||
|
||||
class TestFrontmatter:
|
||||
def _fm(self):
|
||||
text = SKILL_MD.read_text(encoding="utf-8")
|
||||
m = re.match(r"^---\n(.*?)\n---\n", text, re.DOTALL)
|
||||
assert m, "SKILL.md missing YAML frontmatter"
|
||||
return yaml.safe_load(m.group(1))
|
||||
|
||||
def test_name_matches_directory(self):
|
||||
assert self._fm()["name"] == "setup-wizard-generator"
|
||||
|
||||
def test_description_length(self):
|
||||
assert len(self._fm()["description"]) <= 60
|
||||
|
||||
def test_license_and_platforms(self):
|
||||
fm = self._fm()
|
||||
assert fm["license"] == "MIT"
|
||||
assert "linux" in fm["platforms"]
|
||||
|
||||
|
||||
class TestTemplate:
|
||||
def test_template_exists(self):
|
||||
assert TEMPLATE.is_file()
|
||||
|
||||
def test_bash_syntax(self):
|
||||
proc = subprocess.run(
|
||||
["bash", "-n", str(TEMPLATE)], capture_output=True, text=True
|
||||
)
|
||||
assert proc.returncode == 0, proc.stderr
|
||||
|
||||
def test_stages_marker_present(self):
|
||||
text = TEMPLATE.read_text(encoding="utf-8")
|
||||
assert "STAGES" in text, "authoring marker missing"
|
||||
|
||||
def test_library_helpers_defined(self):
|
||||
text = TEMPLATE.read_text(encoding="utf-8")
|
||||
for helper in (
|
||||
"stage()",
|
||||
"say()",
|
||||
"step()",
|
||||
"open_url()",
|
||||
"write_env()",
|
||||
"set_secret()",
|
||||
"finish()",
|
||||
):
|
||||
assert helper in text, f"missing library helper {helper}"
|
||||
|
||||
def test_ask_secret_defined(self):
|
||||
# secret entry must exist (hidden input path)
|
||||
assert "ask_secret" in TEMPLATE.read_text(encoding="utf-8")
|
||||
|
||||
def test_total_stages_variable(self):
|
||||
assert re.search(
|
||||
r"^TOTAL_STAGES=", TEMPLATE.read_text(encoding="utf-8"), re.MULTILINE
|
||||
)
|
||||
|
||||
|
||||
class TestSkillBody:
|
||||
def test_references_template_path(self):
|
||||
assert "templates/template.sh" in SKILL_MD.read_text(encoding="utf-8")
|
||||
|
||||
def test_no_upstream_harness_residue(self):
|
||||
text = SKILL_MD.read_text(encoding="utf-8").lower()
|
||||
for token in ("claude", "/wizard", "slash command"):
|
||||
assert token not in text, f"upstream residue: {token}"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(pytest.main([__file__, "-q"]))
|
||||
@@ -86,6 +86,7 @@ hermes skills uninstall <skill-name>
|
||||
| [**hermes-s6-container-supervision**](/docs/user-guide/skills/optional/devops/devops-hermes-s6-container-supervision) | Modify or debug s6 services in the Hermes Docker image. |
|
||||
| [**inference-sh-cli**](/docs/user-guide/skills/optional/devops/devops-inference-sh-cli) | Run 150+ AI apps (image, video, LLM) via inference.sh CLI. |
|
||||
| [**pinggy-tunnel**](/docs/user-guide/skills/optional/devops/devops-pinggy-tunnel) | Zero-install localhost tunnels over SSH via Pinggy. |
|
||||
| [**setup-wizard-generator**](/docs/user-guide/skills/optional/devops/devops-setup-wizard-generator) | Generate a bash wizard guiding a human through manual setup. |
|
||||
| [**watchers**](/docs/user-guide/skills/optional/devops/devops-watchers) | Poll RSS, JSON APIs, and GitHub with watermark dedup. |
|
||||
|
||||
## dogfood
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
---
|
||||
title: "Setup Wizard Generator — Generate a bash wizard guiding a human through manual setup"
|
||||
sidebar_label: "Setup Wizard Generator"
|
||||
description: "Generate a bash wizard guiding a human through manual setup"
|
||||
---
|
||||
|
||||
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
|
||||
|
||||
# Setup Wizard Generator
|
||||
|
||||
Generate a bash wizard guiding a human through manual setup.
|
||||
|
||||
## Skill metadata
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| Source | Optional — install with `hermes skills install official/devops/setup-wizard-generator` |
|
||||
| Path | `optional-skills/devops/setup-wizard-generator` |
|
||||
| Version | `1.0.0` |
|
||||
| Author | Matt Pocock (mattpocock/skills, wizard) + Hermes Agent |
|
||||
| License | MIT |
|
||||
| Platforms | linux, macos |
|
||||
| Tags | `wizard`, `setup`, `onboarding`, `credentials`, `secrets`, `migration`, `bash`, `human-in-the-loop` |
|
||||
|
||||
## Reference: full SKILL.md
|
||||
|
||||
:::info
|
||||
The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active.
|
||||
:::
|
||||
|
||||
# Setup Wizard Generator
|
||||
|
||||
Generates an interactive bash **wizard**: a script that walks a human, step
|
||||
by step, through a manual procedure that is tedious to do by hand and tedious
|
||||
to re-explain every time. It opens each URL, says exactly what to click and
|
||||
copy, captures the values, writes them where they belong (`.env`, GitHub
|
||||
secrets), confirms at every stage, and shows how many stages are left.
|
||||
|
||||
Ported from mattpocock/skills' MIT-licensed `wizard` skill.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Provisioning infrastructure or third-party services (Stripe, Supabase,
|
||||
DNS, OAuth apps) where only a human can click through the dashboard
|
||||
- Setting up credentials, CI secrets, or repo variables
|
||||
- One-off migrations or cutovers with irreversible human-gated steps
|
||||
- Any procedure the user will hand to a teammate to run
|
||||
|
||||
Do NOT use for steps the agent can perform itself — do those directly.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- `bash`; `gh` CLI only if stages write GitHub secrets/variables
|
||||
- The library template: `templates/template.sh` in this skill's directory
|
||||
|
||||
## Procedure
|
||||
|
||||
### 1. Scope the procedure
|
||||
|
||||
Work out every manual step the human must take and every value captured
|
||||
along the way. Read the repo first, don't ask cold:
|
||||
|
||||
- Setup: `.env`, `.env.example`, `README`, `docker-compose*`, framework
|
||||
config, and `.github/workflows/*` (every `secrets.*` / `vars.*` reference
|
||||
is a value the wizard must produce).
|
||||
- Migration/cutover: the current state, the target state, and the
|
||||
irreversible actions between them.
|
||||
|
||||
Show the user the ordered stage list and the values each produces; they may
|
||||
add, drop, or reorder. Done when every stage is named in order and, for each
|
||||
captured value, you know (a) where the human gets it, (b) where it's written
|
||||
(`.env`, a GitHub secret, both, or nowhere), and (c) whether it's secret
|
||||
(hidden entry) or public.
|
||||
|
||||
### 2. Map each stage's journey
|
||||
|
||||
For each stage, write the precise path a human follows: which URL to open,
|
||||
what to do there, where the value is shown — e.g. "Dashboard → Developers →
|
||||
API keys → Reveal test key → copy". Where you don't know the current UI or
|
||||
exact command, say so and check the docs or ask — never invent steps that
|
||||
may not exist.
|
||||
|
||||
### 3. Author the wizard
|
||||
|
||||
Copy `templates/template.sh` (from this skill's directory) to the target
|
||||
path. Replace the example stage with one `stage` per step, in dependency
|
||||
order. Set `TOTAL_STAGES` to the number of stages you wrote.
|
||||
|
||||
Library helpers: `stage`, `say`/`step`/`note`/`warn`, `open_url`,
|
||||
`ask`/`ask_secret`, `write_env`, `set_secret`/`set_var`, `pause`/`confirm`,
|
||||
`banner`, `finish`. The library above the `STAGES` marker is identical in
|
||||
every wizard — never hand-edit it; that consistency is the point.
|
||||
|
||||
Hold the bar the template sets: open the URL before asking for its value,
|
||||
`ask_secret` for anything secret, `write_env` every persisted value,
|
||||
`set_secret` only what CI actually needs, and `confirm` before anything
|
||||
irreversible. Each `stage` clears the screen — keep one focused task per
|
||||
stage so nothing the human needs scrolls away.
|
||||
|
||||
A wizard is ephemeral by default: save it to a scratch or `scripts/` path,
|
||||
delete it when the job's done. Commit it only when the user wants a
|
||||
repeatable setup path living in the repo.
|
||||
|
||||
### 4. Verify and hand off
|
||||
|
||||
- `bash -n <script>`; run `shellcheck` if available; `chmod +x <script>`.
|
||||
- Do NOT run it end-to-end yourself: it opens browsers and blocks on human
|
||||
input. Trace it statically: every value from step 1 is captured and lands
|
||||
where step 1 said, and every `set_secret` name exactly matches a
|
||||
`secrets.*` reference in CI.
|
||||
- Tell the user how to run it. If it's repeatable, commit it and link it
|
||||
from the README.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
1. **Editing the library section.** Everything above the `STAGES` marker is
|
||||
the wizard library; author only below it.
|
||||
2. **Inventing dashboard paths.** Third-party UIs drift. If unsure of the
|
||||
click path, verify against current docs or flag it as approximate.
|
||||
3. **`set_secret` for values CI doesn't use.** Only push to GitHub secrets
|
||||
what a workflow actually references.
|
||||
4. **Running the wizard yourself.** It blocks on human input; static tracing
|
||||
plus `bash -n` is the verification.
|
||||
5. **One mega-stage.** Screen clearing per stage means a long stage scrolls
|
||||
critical instructions away; split it.
|
||||
|
||||
## Verification
|
||||
|
||||
- [ ] Stage list confirmed with the user before authoring
|
||||
- [ ] `bash -n` passes; script is executable
|
||||
- [ ] Every captured value traced to its declared destination
|
||||
- [ ] Every `set_secret` name matches a CI `secrets.*` reference
|
||||
- [ ] Library section untouched from the template
|
||||
@@ -416,6 +416,7 @@ const sidebars: SidebarsConfig = {
|
||||
'user-guide/skills/optional/devops/devops-hermes-s6-container-supervision',
|
||||
'user-guide/skills/optional/devops/devops-inference-sh-cli',
|
||||
'user-guide/skills/optional/devops/devops-pinggy-tunnel',
|
||||
'user-guide/skills/optional/devops/devops-setup-wizard-generator',
|
||||
'user-guide/skills/optional/devops/devops-watchers',
|
||||
],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user