#!/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 } # ── Cleanup state tracking ──────────────────────────────────────────────── _WIZARD_CORE_CLONED=false _WIZARD_PROJECT_CREATED=false wizard_cleanup() { local removed=false if [[ "$_WIZARD_PROJECT_CREATED" == "true" && -n "${PROJECT_DIR:-}" && -d "$PROJECT_DIR" ]]; then rm -rf "$PROJECT_DIR" gum style --foreground "$C_GREEN" " ✓ Removed $PROJECT_DIR" removed=true fi if [[ "$_WIZARD_CORE_CLONED" == "true" && -d "$CS_CORE_DIR" ]]; then rm -rf "$CS_CORE_DIR" gum style --foreground "$C_GREEN" " ✓ Removed $CS_CORE_DIR" removed=true fi if [[ "$removed" == "false" ]]; then gum style --foreground "$C_SURFACE" " Nothing to revert." fi } handle_sigint() { trap '' INT # block further Ctrl+C while dialog is open echo "" if gum confirm \ --affirmative "Yes, quit" \ --negative "No, continue" \ --default=No \ --prompt.foreground "$C_YELLOW" \ --selected.background "$C_RED" \ --selected.foreground "$C_BASE" \ --unselected.foreground "$C_TEXT" \ " Abort setup and revert changes?"; then echo "" gum style --foreground "$C_YELLOW" --bold " Reverting changes..." wizard_cleanup echo "" gum style --foreground "$C_RED" --bold " Setup aborted." echo "" exit 130 fi trap 'handle_sigint' INT # re-arm after "No, continue" } # ── Build ───────────────────────────────────────────────────────────────── build_project() { header "Building Project" _WIZARD_PROJECT_CREATED=true 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() { trap 'handle_sigint' INT show_banner check_prerequisites setup_core collect_project_info collect_workflow_info confirm_summary build_project print_next_steps trap - INT } main "$@"