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:
parent
8a82d27dae
commit
699087f08c
6 changed files with 267 additions and 243 deletions
12
lib/core.sh
12
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"
|
||||
}
|
||||
|
|
|
|||
116
lib/prereqs.sh
116
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" <<EOF
|
||||
# $project_name
|
||||
|
||||
## Quick Start
|
||||
|
||||
### First time setup
|
||||
\`\`\`bash
|
||||
# Open in devcontainer (VS Code)
|
||||
code .
|
||||
# → Reopen in Container
|
||||
|
||||
# Or via CLI
|
||||
docker build -t $( slugify "$project_name" ) .devcontainer/
|
||||
docker run -it --rm \\
|
||||
-v "\$(pwd)":/workspace \\
|
||||
-v "\$HOME/.context-studio/core":/opt/context-studio/core \\
|
||||
-e ANTHROPIC_API_KEY="\$ANTHROPIC_API_KEY" \\
|
||||
$( slugify "$project_name" ) bash
|
||||
\`\`\`
|
||||
|
||||
### Start Context Studio
|
||||
\`\`\`bash
|
||||
CS_WORKFLOW_DIR=/workspace/workflow node /opt/context-studio/core/core/start.js
|
||||
\`\`\`
|
||||
|
||||
### Start headless (no Electron UI)
|
||||
\`\`\`bash
|
||||
CS_WORKFLOW_DIR=/workspace/workflow node /opt/context-studio/core/core/start.js --ui-mode=headless
|
||||
\`\`\`
|
||||
EOF
|
||||
GITIGNORE
|
||||
|
||||
success "Project structure created"
|
||||
}
|
||||
|
|
@ -85,17 +35,11 @@ create_devcontainer() {
|
|||
local slug
|
||||
slug="$(slugify "$project_name")"
|
||||
|
||||
info "Generating devcontainer config..."
|
||||
|
||||
# Dockerfile
|
||||
sed "s/{{PROJECT_SLUG}}/$slug/g" \
|
||||
"$WIZARD_DIR/templates/Dockerfile" \
|
||||
> "$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"
|
||||
}
|
||||
|
|
|
|||
133
lib/utils.sh
133
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() {
|
||||
|
|
|
|||
|
|
@ -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 <agent> "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"
|
||||
}
|
||||
|
|
|
|||
173
wizard.sh
173
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue