ContextStudioWizard/wizard.sh
Karamelmar 699087f08c 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>
2026-03-09 13:02:42 +01:00

243 lines
10 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ╔══════════════════════════════════════════════════════════════════╗
# ║ Context Studio Wizard — Project Setup ║
# ╚══════════════════════════════════════════════════════════════════╝
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"
source "$WIZARD_DIR/lib/project.sh"
source "$WIZARD_DIR/lib/workflow.sh"
source "$WIZARD_DIR/lib/container.sh"
# ── 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
if [[ -d "$HOME/$candidate" ]]; then
echo "$HOME/$candidate"
return
fi
done
echo "$HOME/Projects"
}
# ── 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"
ask PROJECT_NAME "Project name" ""
if [[ -z "$PROJECT_NAME" ]]; then die "Project name is required."; fi
local projects_base
projects_base="$(find_projects_dir)"
local default_dir="$projects_base/$(slugify "$PROJECT_NAME")"
ask PROJECT_DIR "Project location" "$default_dir"
if [[ -z "$PROJECT_DIR" ]]; then die "Project location is required."; fi
PROJECT_DIR="${PROJECT_DIR/#\~/$HOME}"
if [[ -e "$PROJECT_DIR" ]]; then
warn "Directory already exists: $PROJECT_DIR"
ask_yn _continue "Continue anyway?" "n"
if [[ "$_continue" != "y" ]]; then die "Aborted."; fi
fi
}
# ── Workflow source ───────────────────────────────────────────────────────
collect_workflow_info() {
header "Workflow Configuration"
ask_choice WORKFLOW_SOURCE "Workflow source" \
"Generate from scratch" \
"Clone from existing repo"
if [[ "$WORKFLOW_SOURCE" == "Clone from existing repo" ]]; then
ask WORKFLOW_REPO "Workflow repo URL" ""
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" "Node.js"
ask_choice AGENT_PRESET "Agent preset" \
"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" ;;
esac
fi
}
# ── Summary ───────────────────────────────────────────────────────────────
confirm_summary() {
header "Summary"
local workflow_line
if [[ "${WORKFLOW_SOURCE:-}" == "Clone from existing repo" ]]; then
workflow_line="clone ${WORKFLOW_REPO:-}"
else
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_project() {
header "Building Project"
local slug
slug="$(slugify "$PROJECT_NAME")"
create_project_structure "$PROJECT_DIR" "$PROJECT_NAME"
create_devcontainer "$PROJECT_DIR" "$PROJECT_NAME"
generate_container_scripts "$PROJECT_DIR" "$PROJECT_NAME" "$slug"
if [[ "${WORKFLOW_SOURCE:-}" == "Clone from existing repo" ]]; then
clone_workflow "$PROJECT_DIR" "${WORKFLOW_REPO:-}"
else
generate_workflow "$PROJECT_DIR" "$PROJECT_NAME" \
"${PROJECT_DESC:-A software project}" \
"${TECH_STACK:-Node.js}" \
"${AGENT_PRESET:-minimal}"
fi
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 ──────────────────────────────────────────────────────────────────
print_next_steps() {
local slug
slug="$(slugify "$PROJECT_NAME")"
echo ""
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 ""
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 ""
gum style --foreground "$C_MAUVE" --bold --margin "0 2" " Available commands:"
echo ""
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 ""
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 ""
gum style --foreground "$C_SURFACE" --margin "0 2" \
"VS Code: code \"$PROJECT_DIR\" → Reopen in Container"
echo ""
}
# ── Main ──────────────────────────────────────────────────────────────────
main() {
show_banner
check_prerequisites
setup_core
collect_project_info
collect_workflow_info
confirm_summary
build_project
print_next_steps
}
main "$@"