- 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>
243 lines
10 KiB
Bash
Executable file
243 lines
10 KiB
Bash
Executable file
#!/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 5–15 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 "$@"
|