From 699087f08cbe26a568d5d1de0f22fe96b13f7a6e Mon Sep 17 00:00:00 2001 From: Karamelmar Date: Mon, 9 Mar 2026 13:02:42 +0100 Subject: [PATCH] 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 --- lib/core.sh | 12 ++-- lib/prereqs.sh | 116 +++++++++++--------------------- lib/project.sh | 66 ++---------------- lib/utils.sh | 133 +++++++++++++++++++++++++------------ lib/workflow.sh | 10 +-- wizard.sh | 173 ++++++++++++++++++++++++++++++++++-------------- 6 files changed, 267 insertions(+), 243 deletions(-) diff --git a/lib/core.sh b/lib/core.sh index 586eb76..4f4f514 100644 --- a/lib/core.sh +++ b/lib/core.sh @@ -9,12 +9,11 @@ setup_core() { header "Context Studio Core" if [[ -d "$CS_CORE_DIR/.git" ]]; then - success "Core already installed at $CS_CORE_DIR" - ask_yn _update "Update core to latest?" "n" + success "Core installed at $CS_CORE_DIR" + ask_yn _update "Pull latest updates?" "n" if [[ "$_update" == "y" ]]; then - info "Pulling latest core..." - git -C "$CS_CORE_DIR" pull --ff-only \ - || warn "Pull failed — continuing with existing version" + spin "Updating core..." git -C "$CS_CORE_DIR" pull --ff-only \ + || warn "Pull failed — continuing with current version." success "Core updated" fi return 0 @@ -22,7 +21,8 @@ setup_core() { info "Cloning context-studio-core → $CS_CORE_DIR" mkdir -p "$CS_HOME" - git clone "$CS_CORE_REPO" "$CS_CORE_DIR" \ + spin "Cloning context-studio-core..." \ + git clone "$CS_CORE_REPO" "$CS_CORE_DIR" \ || die "Failed to clone context-studio-core. Check your SSH key and network." success "Core installed at $CS_CORE_DIR" } diff --git a/lib/prereqs.sh b/lib/prereqs.sh index e0f1c13..404451a 100644 --- a/lib/prereqs.sh +++ b/lib/prereqs.sh @@ -1,13 +1,12 @@ #!/usr/bin/env bash # prereqs.sh — detect distro, install missing prerequisites -# Detect package manager / distro family detect_distro() { - if command -v pacman &>/dev/null; then echo "arch"; + if command -v pacman &>/dev/null; then echo "arch"; elif command -v apt-get &>/dev/null; then echo "debian"; - elif command -v dnf &>/dev/null; then echo "rhel"; - elif command -v yum &>/dev/null; then echo "rhel-yum"; - elif command -v zypper &>/dev/null; then echo "suse"; + elif command -v dnf &>/dev/null; then echo "rhel"; + elif command -v yum &>/dev/null; then echo "rhel-yum"; + elif command -v zypper &>/dev/null; then echo "suse"; else echo "unknown"; fi } @@ -16,130 +15,91 @@ install_pkg() { local pkg="$1" local distro distro="$(detect_distro)" - - info "Installing $pkg (distro: $distro)..." case "$distro" in - arch) - sudo pacman -Sy --noconfirm "$pkg" ;; - debian) - sudo apt-get update -qq && sudo apt-get install -y "$pkg" ;; - rhel) - sudo dnf install -y "$pkg" ;; - rhel-yum) - sudo yum install -y "$pkg" ;; - suse) - sudo zypper install -y "$pkg" ;; - *) - die "Unsupported distro — please install $pkg manually." ;; + arch) sudo pacman -Sy --noconfirm "$pkg" ;; + debian) sudo apt-get update -qq && sudo apt-get install -y "$pkg" ;; + rhel) sudo dnf install -y "$pkg" ;; + rhel-yum) sudo yum install -y "$pkg" ;; + suse) sudo zypper install -y "$pkg" ;; + *) die "Unsupported distro — please install $pkg manually." ;; esac } ensure_git() { if command -v git &>/dev/null; then - success "git: $(git --version)" + success "git $(git --version | awk '{print $3}')" return fi warn "git not found." ask_yn _install "Install git now?" "y" if [[ "$_install" != "y" ]]; then die "git is required."; fi - install_pkg git + gum spin --spinner dot --spinner.foreground "$C_MAUVE" \ + --title " Installing git..." --title.foreground "$C_SKY" \ + -- bash -c "$(declare -f install_pkg detect_distro); install_pkg git" command -v git &>/dev/null || die "git installation failed." - success "git installed: $(git --version)" + success "git $(git --version | awk '{print $3}')" } ensure_container_runtime() { - # Already available? if command -v podman &>/dev/null; then CONTAINER_CMD="podman" - success "podman: $(podman --version)" + success "podman $(podman --version | awk '{print $3}')" return fi if command -v docker &>/dev/null; then CONTAINER_CMD="docker" - success "docker: $(docker --version | head -1)" + success "docker $(docker --version | awk '{print $3}' | tr -d ',')" return fi - warn "No container runtime found (podman or docker)." + warn "No container runtime found." echo "" - echo -e " ${CYAN}podman${RESET} is preferred (rootless, no daemon)" - echo -e " ${CYAN}docker${RESET} is the alternative" + gum style --foreground "$C_SKY" --margin "0 4" \ + "podman — recommended (rootless, no daemon)" \ + "docker — alternative" echo "" + ask_choice _runtime "Which would you like to install?" \ "podman (recommended)" \ "docker" local runtime_choice="$_runtime" case "$runtime_choice" in - podman*) - _install_podman - CONTAINER_CMD="podman" - ;; - docker*) - _install_docker - CONTAINER_CMD="docker" - ;; + podman*) _install_podman; CONTAINER_CMD="podman" ;; + docker*) _install_docker; CONTAINER_CMD="docker" ;; esac command -v "$CONTAINER_CMD" &>/dev/null \ || die "$CONTAINER_CMD installation failed. Please install manually." - success "$CONTAINER_CMD installed: $($CONTAINER_CMD --version | head -1)" + success "$CONTAINER_CMD installed" } _install_podman() { local distro distro="$(detect_distro)" - case "$distro" in - arch) install_pkg podman ;; - debian) install_pkg podman ;; - rhel) install_pkg podman ;; - rhel-yum) install_pkg podman ;; - suse) install_pkg podman ;; - *) die "Unsupported distro — install podman manually: https://podman.io/getting-started/installation" ;; - esac + gum spin --spinner dot --spinner.foreground "$C_MAUVE" \ + --title " Installing podman..." --title.foreground "$C_SKY" \ + -- bash -c "$(declare -f install_pkg detect_distro); install_pkg podman" } _install_docker() { local distro distro="$(detect_distro)" case "$distro" in - arch) - install_pkg docker - sudo systemctl enable --now docker - sudo usermod -aG docker "$USER" - warn "Added $USER to docker group — log out and back in for it to take effect." - ;; - debian) - install_pkg docker.io - sudo systemctl enable --now docker - sudo usermod -aG docker "$USER" - warn "Added $USER to docker group — log out and back in for it to take effect." - ;; - rhel) - install_pkg docker - sudo systemctl enable --now docker - sudo usermod -aG docker "$USER" - warn "Added $USER to docker group — log out and back in for it to take effect." - ;; - rhel-yum) - install_pkg docker - sudo systemctl enable --now docker - sudo usermod -aG docker "$USER" - warn "Added $USER to docker group — log out and back in for it to take effect." - ;; - suse) - install_pkg docker - sudo systemctl enable --now docker - sudo usermod -aG docker "$USER" - warn "Added $USER to docker group — log out and back in for it to take effect." - ;; - *) - die "Unsupported distro — install docker manually: https://docs.docker.com/engine/install/" ;; + arch) install_pkg docker ;; + debian) install_pkg docker.io ;; + rhel) install_pkg docker ;; + rhel-yum) install_pkg docker ;; + suse) install_pkg docker ;; + *) die "Unsupported distro — install docker manually." ;; esac + sudo systemctl enable --now docker 2>/dev/null || true + sudo usermod -aG docker "$USER" 2>/dev/null || true + warn "Added $USER to docker group — log out and back in for it to take effect." } check_prerequisites() { - header "Checking Prerequisites" + header "Prerequisites" ensure_git ensure_container_runtime } diff --git a/lib/project.sh b/lib/project.sh index a562198..5b5c18d 100644 --- a/lib/project.sh +++ b/lib/project.sh @@ -7,74 +7,24 @@ create_project_structure() { local project_dir="$1" local project_name="$2" - info "Creating project structure at $project_dir..." - mkdir -p "$project_dir/src" mkdir -p "$project_dir/.devcontainer" - # .gitignore - cat > "$project_dir/.gitignore" <<'EOF' -# Dependencies + cat > "$project_dir/.gitignore" <<'GITIGNORE' node_modules/ -.pnp/ -.pnp.js - -# Build outputs dist/ build/ *.AppImage *.dmg *.exe - -# Environment .env .env.local -.env.*.local - -# Runtime data workflow/data/registry.db workflow/users/*/session-history/ - -# Logs *.log -npm-debug.log* - -# OS .DS_Store Thumbs.db -EOF - - # README - cat > "$project_dir/README.md" < "$project_dir/.devcontainer/Dockerfile" - - # devcontainer.json sed "s/{{PROJECT_NAME}}/$project_name/g; s/{{PROJECT_SLUG}}/$slug/g" \ "$WIZARD_DIR/templates/devcontainer.json" \ > "$project_dir/.devcontainer/devcontainer.json" - success "Devcontainer config written to $project_dir/.devcontainer/" + cp "$WIZARD_DIR/templates/Dockerfile" "$project_dir/.devcontainer/Dockerfile" + + success "Devcontainer config written" } diff --git a/lib/utils.sh b/lib/utils.sh index 36d46cf..7360e8c 100644 --- a/lib/utils.sh +++ b/lib/utils.sh @@ -1,70 +1,119 @@ #!/usr/bin/env bash -# utils.sh — colors, prompts, helpers +# utils.sh — gum-based UI helpers (requires gum) -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -CYAN='\033[0;36m' -BOLD='\033[1m' -RESET='\033[0m' +# ── Catppuccin Mocha palette ───────────────────────────────────────────── +C_MAUVE="#CBA6F7" +C_SKY="#89DCEB" +C_GREEN="#A6E3A1" +C_YELLOW="#F9E2AF" +C_RED="#F38BA8" +C_PINK="#F5C2E7" +C_BASE="#1E1E2E" +C_TEXT="#CDD6F4" +C_SURFACE="#585B70" -info() { echo -e "${CYAN}${BOLD}[info]${RESET} $*"; } -success() { echo -e "${GREEN}${BOLD}[ok]${RESET} $*"; } -warn() { echo -e "${YELLOW}${BOLD}[warn]${RESET} $*"; } -error() { echo -e "${RED}${BOLD}[error]${RESET} $*" >&2; } -die() { error "$*"; exit 1; } -header() { echo -e "\n${BOLD}${CYAN}━━━ $* ━━━${RESET}\n"; } +# ── Output helpers ──────────────────────────────────────────────────────── +info() { gum log --level info -- "$*"; } +success() { gum style --foreground "$C_GREEN" " ✓ $*"; } +warn() { gum log --level warn -- "$*"; } +error() { gum log --level error -- "$*" >&2; } +die() { gum style --foreground "$C_RED" --bold " ✗ $*" >&2; exit 1; } -# ask VAR "prompt" "default" +header() { + echo "" + gum style \ + --foreground "$C_MAUVE" --bold \ + --margin "0 2" \ + "◆ $*" + gum style \ + --foreground "$C_SURFACE" \ + --margin "0 2" \ + "────────────────────────────────────────────────" + echo "" +} + +# ── Prompts ─────────────────────────────────────────────────────────────── + +# ask VAR "Label" "default" ask() { - local var="$1" prompt="$2" default="$3" - local input + local var="$1" prompt="$2" default="${3:-}" + local result if [[ -n "$default" ]]; then - echo -ne "${BOLD}${prompt}${RESET} ${CYAN}[${default}]${RESET}: " + result=$(gum input \ + --value "$default" \ + --prompt " › " \ + --prompt.foreground "$C_MAUVE" \ + --cursor.foreground "$C_MAUVE" \ + --header " $prompt" \ + --header.foreground "$C_SKY" \ + --width 70) || true else - echo -ne "${BOLD}${prompt}${RESET}: " + result=$(gum input \ + --placeholder "(required)" \ + --prompt " › " \ + --prompt.foreground "$C_MAUVE" \ + --cursor.foreground "$C_MAUVE" \ + --header " $prompt" \ + --header.foreground "$C_SKY" \ + --width 70) || true fi - read -r input || true - if [[ -z "$input" && -n "$default" ]]; then + if [[ -z "$result" && -n "$default" ]]; then eval "$var=\"\$default\"" else - eval "$var=\"\$input\"" + eval "$var=\"\$result\"" fi } -# ask_yn VAR "prompt" "y|n" +# ask_yn VAR "Question" "y|n" ask_yn() { local var="$1" prompt="$2" default="$3" - local input options - if [[ "$default" == "y" ]]; then options="Y/n"; else options="y/N"; fi - echo -ne "${BOLD}${prompt}${RESET} ${CYAN}[${options}]${RESET}: " - read -r input || true - input="${input:-$default}" - if [[ "$input" =~ ^[Yy]$ ]]; then + local affirmative="Yes" negative="No" + [[ "$default" == "y" ]] && affirmative="Yes" || affirmative="Yes" + if gum confirm \ + --affirmative "Yes" \ + --negative "No" \ + --default="$([[ "$default" == "y" ]] && echo Yes || echo No)" \ + --prompt.foreground "$C_SKY" \ + --selected.background "$C_MAUVE" \ + --selected.foreground "$C_BASE" \ + --unselected.foreground "$C_TEXT" \ + " $prompt"; then eval "$var=y" else eval "$var=n" fi } -# ask_choice VAR "prompt" option1 option2 ... +# ask_choice VAR "Header" option1 option2 ... ask_choice() { local var="$1" prompt="$2"; shift 2 - local options=("$@") - local input idx - echo -e "${BOLD}${prompt}${RESET}" - for i in "${!options[@]}"; do - echo -e " ${CYAN}$((i+1))${RESET}) ${options[$i]}" - done - echo -ne "Choice ${CYAN}[1]${RESET}: " - read -r input || true - input="${input:-1}" - if [[ "$input" =~ ^[0-9]+$ ]] && (( input >= 1 && input <= ${#options[@]} )); then - idx=$(( input - 1 )) + local first="$1" + local result + result=$(gum choose \ + --cursor " › " \ + --cursor.foreground "$C_MAUVE" \ + --selected.foreground "$C_MAUVE" \ + --selected.bold \ + --header " $prompt" \ + --header.foreground "$C_SKY" \ + --height 10 \ + "$@") || true + if [[ -z "$result" ]]; then + eval "$var=\"\$first\"" else - idx=0 + eval "$var=\"\$result\"" fi - eval "$var=\"\${options[$idx]}\"" +} + +# spin "Title" command args... +spin() { + local title="$1"; shift + gum spin \ + --spinner dot \ + --spinner.foreground "$C_MAUVE" \ + --title " $title" \ + --title.foreground "$C_SKY" \ + -- "$@" } require_cmd() { diff --git a/lib/workflow.sh b/lib/workflow.sh index c179a0a..9f41ebe 100644 --- a/lib/workflow.sh +++ b/lib/workflow.sh @@ -12,7 +12,7 @@ generate_workflow() { local preset="$5" local workflow_dir="$project_dir/workflow" - info "Generating workflow config ($preset preset)..." + info "Generating workflow ($preset preset)..." mkdir -p "$workflow_dir/agents" mkdir -p "$workflow_dir/roles" @@ -111,7 +111,7 @@ Read \`project-docs/project-vision.md\` for project goals. Use \`/sm "message"\` to send messages between agents. EOF - success "Workflow generated at $workflow_dir" + success "Workflow generated" } _create_agent_dir() { @@ -159,8 +159,8 @@ clone_workflow() { local repo_url="$2" local workflow_dir="$project_dir/workflow" - info "Cloning workflow from $repo_url..." - git clone "$repo_url" "$workflow_dir" \ + spin "Cloning workflow..." \ + git clone "$repo_url" "$workflow_dir" \ || die "Failed to clone workflow repo: $repo_url" - success "Workflow cloned to $workflow_dir" + success "Workflow cloned" } diff --git a/wizard.sh b/wizard.sh index 48574fb..1db7f76 100755 --- a/wizard.sh +++ b/wizard.sh @@ -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 5–15 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 5–15 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