Redesign wizard UI with gum (charmbracelet)

- Bootstrap gum automatically on first run (Arch/Debian/RHEL/Fedora/SUSE)
- utils.sh: replace all bash color helpers with gum equivalents
  - gum input for text prompts (with value pre-fill for defaults)
  - gum choose for selection menus
  - gum confirm for yes/no
  - gum spin for long-running operations
  - gum style/log for output (catppuccin mocha palette)
  - gum style for banners and summary box
- core.sh: spinner on git clone/pull
- workflow.sh: spinner on git clone
- prereqs.sh: spinner on package installs
- wizard.sh: double-border welcome banner, rounded summary box,
  success banner with next-steps panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Eli 2026-03-09 13:02:42 +01:00
commit 699087f08c
6 changed files with 267 additions and 243 deletions

173
wizard.sh
View file

@ -6,6 +6,45 @@ set -uo pipefail
WIZARD_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# ── Bootstrap gum ────────────────────────────────────────────────────────
# Must happen before sourcing any lib that uses gum.
_install_gum() {
echo " Installing gum..."
if command -v pacman &>/dev/null; then
sudo pacman -Sy --noconfirm gum
elif command -v apt-get &>/dev/null; then
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://repo.charm.sh/apt/gpg.key \
| sudo gpg --dearmor -o /etc/apt/keyrings/charm.gpg
echo "deb [signed-by=/etc/apt/keyrings/charm.gpg] https://repo.charm.sh/apt/ * *" \
| sudo tee /etc/apt/sources.list.d/charm.list >/dev/null
sudo apt-get update -qq && sudo apt-get install -y gum
elif command -v dnf &>/dev/null; then
sudo dnf install -y 'https://repo.charm.sh/rpm/charm-repo.rpm' 2>/dev/null || true
sudo dnf install -y gum
elif command -v yum &>/dev/null; then
sudo yum install -y 'https://repo.charm.sh/rpm/charm-repo.rpm' 2>/dev/null || true
sudo yum install -y gum
elif command -v zypper &>/dev/null; then
local ver="0.14.5"
curl -sL "https://github.com/charmbracelet/gum/releases/download/v${ver}/gum_${ver}_Linux_x86_64.tar.gz" \
| tar -xz -C /tmp gum
sudo mv /tmp/gum /usr/local/bin/gum
else
echo "Cannot install gum automatically."
echo "Install it manually: https://github.com/charmbracelet/gum"
exit 1
fi
}
if ! command -v gum &>/dev/null; then
echo ""
echo " gum is required for the wizard UI (https://github.com/charmbracelet/gum)"
_install_gum
command -v gum &>/dev/null || { echo "gum installation failed."; exit 1; }
fi
# ── Source libs ──────────────────────────────────────────────────────────
source "$WIZARD_DIR/lib/utils.sh"
source "$WIZARD_DIR/lib/prereqs.sh"
source "$WIZARD_DIR/lib/core.sh"
@ -13,7 +52,7 @@ source "$WIZARD_DIR/lib/project.sh"
source "$WIZARD_DIR/lib/workflow.sh"
source "$WIZARD_DIR/lib/container.sh"
# ── Find existing projects base dir ──────────────────────────────────────
# ── Find existing projects base dir ──────────────────────────────────────
find_projects_dir() {
local candidates=("Projects" "projects" "Project" "project" "Dev" "dev" "Workspace" "workspace" "Code" "code" "src" "Src")
for candidate in "${candidates[@]}"; do
@ -25,7 +64,27 @@ find_projects_dir() {
echo "$HOME/Projects"
}
# ── Project info ───────────────────────────────────────────────────────────
# ── Banner ────────────────────────────────────────────────────────────────
show_banner() {
clear
echo ""
gum style \
--border double \
--border-foreground "$C_MAUVE" \
--foreground "$C_MAUVE" \
--bold \
--align center \
--width 54 \
--margin "0 2" \
--padding "1 8" \
"🧙 Context Studio Wizard" \
"" \
"$(gum style --foreground "$C_SKY" --italic --bold "Scaffold your multi-agent AI")" \
"$(gum style --foreground "$C_SKY" --italic --bold "development environment")"
echo ""
}
# ── Project info ──────────────────────────────────────────────────────────
collect_project_info() {
header "Project Details"
@ -46,11 +105,11 @@ collect_project_info() {
fi
}
# ── Workflow source ───────────────────────────────────────────────────────
# ── Workflow source ───────────────────────────────────────────────────────
collect_workflow_info() {
header "Workflow Configuration"
ask_choice WORKFLOW_SOURCE "How do you want to configure your workflow?" \
ask_choice WORKFLOW_SOURCE "Workflow source" \
"Generate from scratch" \
"Clone from existing repo"
@ -59,10 +118,10 @@ collect_workflow_info() {
if [[ -z "$WORKFLOW_REPO" ]]; then die "Repo URL is required."; fi
else
ask PROJECT_DESC "Project description" "A software project"
ask TECH_STACK "Tech stack (e.g. Node.js, Rust, Python)" "Node.js"
ask TECH_STACK "Tech stack" "Node.js"
ask_choice AGENT_PRESET "Agent preset" \
"minimal (5 agents: coordinator, 2 coders, researcher, tester)" \
"standard (9 agents: 2 coordinators, 3 coders, 2 researchers, tester, reviewer)"
"minimal — 5 agents (coordinator · 2× coder · researcher · tester)" \
"standard — 9 agents (2× coordinator · 3× coder · 2× researcher · tester · reviewer)"
case "$AGENT_PRESET" in
minimal*) AGENT_PRESET="minimal" ;;
*) AGENT_PRESET="standard" ;;
@ -70,25 +129,33 @@ collect_workflow_info() {
fi
}
# ── Summary & confirm ──────────────────────────────────────────────────────
# ── Summary ───────────────────────────────────────────────────────────────
confirm_summary() {
header "Summary"
echo -e " ${BOLD}Project name${RESET} : $PROJECT_NAME"
echo -e " ${BOLD}Location${RESET} : $PROJECT_DIR"
echo -e " ${BOLD}Core${RESET} : $CS_CORE_DIR"
local workflow_line
if [[ "${WORKFLOW_SOURCE:-}" == "Clone from existing repo" ]]; then
echo -e " ${BOLD}Workflow${RESET} : clone ${WORKFLOW_REPO:-}"
workflow_line="clone ${WORKFLOW_REPO:-}"
else
echo -e " ${BOLD}Workflow${RESET} : generate (${AGENT_PRESET:-minimal} preset)"
echo -e " ${BOLD}Description${RESET} : ${PROJECT_DESC:-}"
echo -e " ${BOLD}Tech stack${RESET} : ${TECH_STACK:-}"
workflow_line="generate (${AGENT_PRESET:-minimal} preset)"
fi
gum style \
--border rounded \
--border-foreground "$C_MAUVE" \
--padding "1 3" \
--margin "0 2" \
"$(gum style --foreground "$C_SKY" --bold "Project") $(gum style --foreground "$C_TEXT" "$PROJECT_NAME")" \
"$(gum style --foreground "$C_SKY" --bold "Location") $(gum style --foreground "$C_TEXT" "$PROJECT_DIR")" \
"$(gum style --foreground "$C_SKY" --bold "Core") $(gum style --foreground "$C_TEXT" "$CS_CORE_DIR")" \
"$(gum style --foreground "$C_SKY" --bold "Workflow") $(gum style --foreground "$C_TEXT" "$workflow_line")"
echo ""
ask_yn _ok "Create project?" "y"
if [[ "$_ok" != "y" ]]; then die "Aborted."; fi
}
# ── Build ──────────────────────────────────────────────────────────────────
# ── Build ─────────────────────────────────────────────────────────────────
build_project() {
header "Building Project"
@ -108,58 +175,62 @@ build_project() {
"${AGENT_PRESET:-minimal}"
fi
git -C "$PROJECT_DIR" init -q
git -C "$PROJECT_DIR" add .
git -C "$PROJECT_DIR" commit -q -m "Initial project setup via Context Studio Wizard"
spin "Initializing git repo..." bash -c "
git -C '$PROJECT_DIR' init -q
git -C '$PROJECT_DIR' add .
git -C '$PROJECT_DIR' commit -q -m 'Initial project setup via Context Studio Wizard'
"
success "Git repo initialized"
}
# ── Done ──────────────────────────────────────────────────────────────────
# ── Done ──────────────────────────────────────────────────────────────────
print_next_steps() {
local slug
slug="$(slugify "$PROJECT_NAME")"
header "Done!"
success "Project created at: $PROJECT_DIR"
echo ""
echo -e "${BOLD}${GREEN} ➜ Enter your project now:${RESET}"
echo -e "${BOLD}${CYAN} cd \"$PROJECT_DIR\"${RESET}"
gum style \
--border double \
--border-foreground "$C_GREEN" \
--foreground "$C_GREEN" \
--bold \
--align center \
--width 54 \
--margin "0 2" \
--padding "1 4" \
"✓ Project Created Successfully"
echo ""
echo -e "${BOLD}Start everything:${RESET}"
echo -e " ${CYAN}./start.sh${RESET}"
echo -e " Starts the agents container, then launches Context Studio Core on your host."
gum style --foreground "$C_MAUVE" --bold --margin "0 2" " ➜ Enter your project:"
gum style --foreground "$C_SKY" --bold --margin "0 4" "cd \"$PROJECT_DIR\""
echo ""
echo -e " ${YELLOW}⚠ First run only:${RESET} the container image needs to be built."
echo -e " This downloads Node.js, Rust, Claude Code and all build tools."
echo -e " ${BOLD}Expect 515 minutes depending on your connection.${RESET}"
echo -e " Subsequent starts are instant — the image is cached."
gum style --foreground "$C_MAUVE" --bold --margin "0 2" " Available commands:"
echo ""
echo -e "${BOLD}Stop the container:${RESET}"
echo -e " ${CYAN}./stop.sh${RESET}"
gum style --margin "0 4" \
"$(gum style --foreground "$C_SKY" --bold "./start.sh") — start container + Context Studio" \
"$(gum style --foreground "$C_SKY" --bold "./stop.sh") — stop the agents container" \
"$(gum style --foreground "$C_SKY" --bold "./update.sh") — update core, claude-code, OS packages"
echo ""
echo -e "${BOLD}Update everything:${RESET}"
echo -e " ${CYAN}./update.sh${RESET}"
echo -e " Updates Context Studio Core, Claude Code, and OS packages."
echo -e " Optionally rebuilds the image from scratch."
gum style \
--border normal \
--border-foreground "$C_YELLOW" \
--foreground "$C_YELLOW" \
--padding "0 2" \
--margin "0 2" \
"⚠ First run: start.sh builds the container image." \
" Expect 515 min on first start. Subsequent starts are instant."
echo ""
echo -e "${BOLD}How it works:${RESET}"
echo -e " • Context Studio Core runs on your host (Electron UI, no display issues)"
echo -e " • Claude Code agents run inside container ${CYAN}cs-${slug}${RESET}"
echo -e "${CYAN}bin/claude${RESET} intercepts every agent call and routes it into the container"
echo -e " • Project files are mounted at the same path on host and in container"
echo ""
echo -e "${BOLD}VS Code (for editing):${RESET}"
echo -e " ${CYAN}code \"$PROJECT_DIR\"${RESET} → \"Reopen in Container\""
gum style --foreground "$C_SURFACE" --margin "0 2" \
"VS Code: code \"$PROJECT_DIR\" → Reopen in Container"
echo ""
}
# ── Main ──────────────────────────────────────────────────────────────────
# ── Main ──────────────────────────────────────────────────────────────────
main() {
echo ""
echo -e "${BOLD}${CYAN}╔══════════════════════════════════════╗${RESET}"
echo -e "${BOLD}${CYAN}║ Context Studio Wizard v1.0 ║${RESET}"
echo -e "${BOLD}${CYAN}╚══════════════════════════════════════╝${RESET}"
show_banner
check_prerequisites
setup_core
collect_project_info