Add Charm CLI (gum) support for premium installer experience
- Detect gum and offer to install for enhanced UX - Add wrapper functions with graceful bash fallback: - gum_choose: Interactive option selection - gum_confirm: Yes/no prompts - gum_input: Text input - gum_password: Hidden input - gum_spin: Animated spinners - gum_style_box: Styled boxes - Works perfectly without gum, beautiful with it - Supports macOS (Homebrew), Debian/Ubuntu (apt), and Go install 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
e98ecbcf14
commit
8e2c4cc1d0
1 changed files with 281 additions and 52 deletions
|
|
@ -46,6 +46,161 @@ BOX_BR="╯"
|
|||
BOX_H="─"
|
||||
BOX_V="│"
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# GUM DETECTION & SETUP
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# Gum (https://github.com/charmbracelet/gum) provides beautiful interactive
|
||||
# prompts. If available, we use it. Otherwise, we fall back to bash.
|
||||
|
||||
HAS_GUM=false
|
||||
if command -v gum &> /dev/null; then
|
||||
HAS_GUM=true
|
||||
fi
|
||||
|
||||
# Offer to install gum for enhanced experience
|
||||
offer_gum_install() {
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${CYAN}┌────────────────────────────────────────────────────────────────┐${NC}"
|
||||
echo -e "${CYAN}│${NC} ${WHITE}✨ Enhanced installer available!${NC} ${CYAN}│${NC}"
|
||||
echo -e "${CYAN}│${NC} ${CYAN}│${NC}"
|
||||
echo -e "${CYAN}│${NC} ${DIM}Install 'gum' for a more beautiful experience with${NC} ${CYAN}│${NC}"
|
||||
echo -e "${CYAN}│${NC} ${DIM}interactive menus and smooth animations.${NC} ${CYAN}│${NC}"
|
||||
echo -e "${CYAN}└────────────────────────────────────────────────────────────────┘${NC}"
|
||||
echo ""
|
||||
echo -ne " ${WHITE}Install gum? [y/N]:${NC} "
|
||||
read -r install_gum
|
||||
|
||||
if [[ "$install_gum" =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
info "Installing gum..."
|
||||
|
||||
if [[ "$OS" == "macos" ]]; then
|
||||
brew install gum &>/dev/null &
|
||||
spinner $! "Installing gum via Homebrew..."
|
||||
elif [[ "$OS" == "linux" ]]; then
|
||||
# Try various package managers
|
||||
if [[ "$PKG_MANAGER" == "apt" ]]; 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 2>/dev/null
|
||||
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 update -qq && sudo apt install -y -qq gum &>/dev/null &
|
||||
spinner $! "Installing gum via apt..."
|
||||
elif command -v go &> /dev/null; then
|
||||
go install github.com/charmbracelet/gum@latest &>/dev/null &
|
||||
spinner $! "Installing gum via go..."
|
||||
else
|
||||
warn "Could not auto-install gum. Install manually: https://github.com/charmbracelet/gum"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
if command -v gum &> /dev/null; then
|
||||
HAS_GUM=true
|
||||
success "Gum installed! You'll get the premium experience ✨"
|
||||
else
|
||||
warn "Gum installation may have failed. Continuing with standard installer."
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# GUM WRAPPER FUNCTIONS (with bash fallback)
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
# Choose from a list of options
|
||||
# Usage: choice=$(gum_choose "Option 1" "Option 2" "Option 3")
|
||||
gum_choose() {
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
gum choose --cursor.foreground="#00FFFF" --selected.foreground="#00FF00" "$@"
|
||||
else
|
||||
# Bash fallback
|
||||
local options=("$@")
|
||||
local i=1
|
||||
for opt in "${options[@]}"; do
|
||||
echo -e " ${CYAN}[$i]${NC} $opt"
|
||||
((i++))
|
||||
done
|
||||
echo ""
|
||||
while true; do
|
||||
echo -ne " ${WHITE}Enter choice [1-${#options[@]}]:${NC} "
|
||||
read -r choice
|
||||
if [[ "$choice" =~ ^[0-9]+$ ]] && (( choice >= 1 && choice <= ${#options[@]} )); then
|
||||
echo "${options[$((choice-1))]}"
|
||||
return
|
||||
fi
|
||||
echo -e " ${RED}Please enter a number between 1 and ${#options[@]}${NC}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Confirm yes/no
|
||||
# Usage: if gum_confirm "Are you sure?"; then ...
|
||||
gum_confirm() {
|
||||
local prompt="$1"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
gum confirm --prompt.foreground="#FFFFFF" "$prompt"
|
||||
else
|
||||
echo -ne " ${WHITE}$prompt [y/N]:${NC} "
|
||||
read -r response
|
||||
[[ "$response" =~ ^[Yy]$ ]]
|
||||
fi
|
||||
}
|
||||
|
||||
# Get text input
|
||||
# Usage: value=$(gum_input "Enter value" "placeholder")
|
||||
gum_input() {
|
||||
local prompt="$1"
|
||||
local placeholder="$2"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
gum input --placeholder "$placeholder" --prompt "$prompt: " --prompt.foreground="#00FFFF"
|
||||
else
|
||||
echo -ne " ${WHITE}$prompt:${NC} "
|
||||
read -r value
|
||||
echo "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get password input (hidden)
|
||||
# Usage: value=$(gum_password "Enter password")
|
||||
gum_password() {
|
||||
local prompt="$1"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
gum input --password --placeholder "••••••••••••••••" --prompt "$prompt: " --prompt.foreground="#00FFFF"
|
||||
else
|
||||
echo -ne " ${WHITE}$prompt:${NC} "
|
||||
read -rs value
|
||||
echo ""
|
||||
echo "$value"
|
||||
fi
|
||||
}
|
||||
|
||||
# Show a spinner while a command runs
|
||||
# Usage: gum_spin "Message" command arg1 arg2
|
||||
gum_spin() {
|
||||
local message="$1"
|
||||
shift
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
gum spin --spinner dot --title "$message" -- "$@"
|
||||
else
|
||||
"$@" &
|
||||
spinner $! "$message"
|
||||
fi
|
||||
}
|
||||
|
||||
# Style text in a box
|
||||
gum_style_box() {
|
||||
local text="$1"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
gum style --border rounded --padding "1 2" --border-foreground "#00FFFF" "$text"
|
||||
else
|
||||
print_box "$text"
|
||||
fi
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
# HELPER FUNCTIONS
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -174,32 +329,60 @@ EOF
|
|||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
select_provider() {
|
||||
print_box "Choose Your Setup"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
# Beautiful gum version
|
||||
echo ""
|
||||
gum style --border rounded --padding "1 2" --border-foreground "#00FFFF" \
|
||||
"$(gum style --foreground "#FFFFFF" --bold "How would you like to power Claude OS?")"
|
||||
echo ""
|
||||
|
||||
echo -e " ${WHITE}How would you like to power Claude OS?${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[1]${NC} ${GREEN}🏠 Local (Ollama)${NC}"
|
||||
echo -e " ${DIM}Free, private, runs on your machine${NC}"
|
||||
echo -e " ${DIM}Best for: Privacy-focused users, offline use${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[2]${NC} ${BLUE}☁️ Cloud (OpenAI)${NC}"
|
||||
echo -e " ${DIM}Fast, no local resources needed${NC}"
|
||||
echo -e " ${DIM}Best for: Quick setup, Linux servers${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[3]${NC} ${MAGENTA}🔧 Custom${NC}"
|
||||
echo -e " ${DIM}I'll configure it myself${NC}"
|
||||
echo ""
|
||||
local choice=$(gum choose --cursor.foreground="#00FFFF" --selected.foreground="#00FF00" \
|
||||
"🏠 Local (Ollama) - Free, private, runs on your machine" \
|
||||
"☁️ Cloud (OpenAI) - Fast, no local resources needed" \
|
||||
"🔧 Custom - I'll configure it myself")
|
||||
|
||||
while true; do
|
||||
echo -ne " ${WHITE}Enter choice [1-3]:${NC} "
|
||||
read -r choice
|
||||
case $choice in
|
||||
1) PROVIDER="local"; break ;;
|
||||
2) PROVIDER="openai"; break ;;
|
||||
3) PROVIDER="custom"; break ;;
|
||||
*) echo -e " ${RED}Please enter 1, 2, or 3${NC}" ;;
|
||||
case "$choice" in
|
||||
*"Local"*) PROVIDER="local" ;;
|
||||
*"Cloud"*) PROVIDER="openai" ;;
|
||||
*"Custom"*) PROVIDER="custom" ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
# Bash fallback
|
||||
print_box "Choose Your Setup"
|
||||
|
||||
echo -e " ${WHITE}How would you like to power Claude OS?${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[1]${NC} ${GREEN}🏠 Local (Ollama)${NC}"
|
||||
echo -e " ${DIM}Free, private, runs on your machine${NC}"
|
||||
echo -e " ${DIM}Best for: Privacy-focused users, offline use${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[2]${NC} ${BLUE}☁️ Cloud (OpenAI)${NC}"
|
||||
echo -e " ${DIM}Fast, no local resources needed${NC}"
|
||||
echo -e " ${DIM}Best for: Quick setup, Linux servers${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[3]${NC} ${MAGENTA}🔧 Custom${NC}"
|
||||
echo -e " ${DIM}I'll configure it myself${NC}"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
echo -ne " ${WHITE}Enter choice [1-3]:${NC} "
|
||||
read -r choice
|
||||
case $choice in
|
||||
1) PROVIDER="local"; break ;;
|
||||
2) PROVIDER="openai"; break ;;
|
||||
3) PROVIDER="custom"; break ;;
|
||||
*) echo -e " ${RED}Please enter 1, 2, or 3${NC}" ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
# Show selection confirmation
|
||||
echo ""
|
||||
case "$PROVIDER" in
|
||||
local) success "Selected: Local (Ollama)" ;;
|
||||
openai) success "Selected: Cloud (OpenAI)" ;;
|
||||
custom) success "Selected: Custom configuration" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -207,30 +390,52 @@ select_provider() {
|
|||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
select_model_size() {
|
||||
print_box "Choose Model Size"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
# Beautiful gum version
|
||||
echo ""
|
||||
gum style --border rounded --padding "1 2" --border-foreground "#00FFFF" \
|
||||
"$(gum style --foreground "#FFFFFF" --bold "Choose your model size")"
|
||||
echo ""
|
||||
|
||||
echo -e " ${WHITE}Select your local model:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[1]${NC} ${GREEN}💨 Lite${NC} ${DIM}(Recommended)${NC}"
|
||||
echo -e " ${WHITE}llama3.2:3b${NC} - 2GB download, ~4GB RAM"
|
||||
echo -e " ${DIM}Fast download, works on most machines${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[2]${NC} ${BLUE}🚀 Full${NC}"
|
||||
echo -e " ${WHITE}llama3.1:8b${NC} - 4.7GB download, ~8GB RAM"
|
||||
echo -e " ${DIM}Better quality, needs more resources${NC}"
|
||||
echo ""
|
||||
local choice=$(gum choose --cursor.foreground="#00FFFF" --selected.foreground="#00FF00" \
|
||||
"💨 Lite (Recommended) - llama3.2:3b - 2GB download, ~4GB RAM" \
|
||||
"🚀 Full - llama3.1:8b - 4.7GB download, ~8GB RAM")
|
||||
|
||||
while true; do
|
||||
echo -ne " ${WHITE}Enter choice [1-2]:${NC} "
|
||||
read -r choice
|
||||
case $choice in
|
||||
1) LLM_MODEL="$DEFAULT_LLM_MODEL"; break ;;
|
||||
2) LLM_MODEL="$FULL_LLM_MODEL"; break ;;
|
||||
*) echo -e " ${RED}Please enter 1 or 2${NC}" ;;
|
||||
case "$choice" in
|
||||
*"Lite"*) LLM_MODEL="$DEFAULT_LLM_MODEL" ;;
|
||||
*"Full"*) LLM_MODEL="$FULL_LLM_MODEL" ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
# Bash fallback
|
||||
print_box "Choose Model Size"
|
||||
|
||||
echo -e " ${WHITE}Select your local model:${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[1]${NC} ${GREEN}💨 Lite${NC} ${DIM}(Recommended)${NC}"
|
||||
echo -e " ${WHITE}llama3.2:3b${NC} - 2GB download, ~4GB RAM"
|
||||
echo -e " ${DIM}Fast download, works on most machines${NC}"
|
||||
echo ""
|
||||
echo -e " ${CYAN}[2]${NC} ${BLUE}🚀 Full${NC}"
|
||||
echo -e " ${WHITE}llama3.1:8b${NC} - 4.7GB download, ~8GB RAM"
|
||||
echo -e " ${DIM}Better quality, needs more resources${NC}"
|
||||
echo ""
|
||||
|
||||
while true; do
|
||||
echo -ne " ${WHITE}Enter choice [1-2]:${NC} "
|
||||
read -r choice
|
||||
case $choice in
|
||||
1) LLM_MODEL="$DEFAULT_LLM_MODEL"; break ;;
|
||||
2) LLM_MODEL="$FULL_LLM_MODEL"; break ;;
|
||||
*) echo -e " ${RED}Please enter 1 or 2${NC}" ;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
|
||||
EMBED_MODEL="$DEFAULT_EMBED_MODEL"
|
||||
|
||||
# Show selection confirmation
|
||||
echo ""
|
||||
success "Selected: $LLM_MODEL"
|
||||
}
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
|
@ -238,14 +443,27 @@ select_model_size() {
|
|||
# ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
configure_openai() {
|
||||
print_box "OpenAI Configuration"
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
# Beautiful gum version
|
||||
echo ""
|
||||
gum style --border rounded --padding "1 2" --border-foreground "#00FFFF" \
|
||||
"$(gum style --foreground "#FFFFFF" --bold "OpenAI Configuration")"
|
||||
echo ""
|
||||
echo -e " ${DIM}Get your API key at https://platform.openai.com/api-keys${NC}"
|
||||
echo ""
|
||||
|
||||
echo -e " ${WHITE}Enter your OpenAI API key:${NC}"
|
||||
echo -e " ${DIM}(Get one at https://platform.openai.com/api-keys)${NC}"
|
||||
echo ""
|
||||
echo -ne " ${WHITE}API Key:${NC} "
|
||||
read -rs OPENAI_API_KEY
|
||||
echo ""
|
||||
OPENAI_API_KEY=$(gum input --password --placeholder "sk-..." --prompt "API Key: " --prompt.foreground="#00FFFF")
|
||||
else
|
||||
# Bash fallback
|
||||
print_box "OpenAI Configuration"
|
||||
|
||||
echo -e " ${WHITE}Enter your OpenAI API key:${NC}"
|
||||
echo -e " ${DIM}(Get one at https://platform.openai.com/api-keys)${NC}"
|
||||
echo ""
|
||||
echo -ne " ${WHITE}API Key:${NC} "
|
||||
read -rs OPENAI_API_KEY
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ -z "$OPENAI_API_KEY" ]]; then
|
||||
error "API key cannot be empty"
|
||||
|
|
@ -586,16 +804,24 @@ EOF
|
|||
|
||||
show_completion() {
|
||||
echo ""
|
||||
echo -e "${GREEN}"
|
||||
cat << 'EOF'
|
||||
|
||||
if [[ "$HAS_GUM" == "true" ]]; then
|
||||
# Beautiful gum completion
|
||||
gum style --border double --padding "1 4" --border-foreground "#00FF00" --foreground "#00FF00" \
|
||||
"✨ Claude OS is ready! ✨"
|
||||
else
|
||||
echo -e "${GREEN}"
|
||||
cat << 'EOF'
|
||||
╔═══════════════════════════════════════════════════════════════════╗
|
||||
║ ║
|
||||
║ ✨ Claude OS is ready! ✨ ║
|
||||
║ ║
|
||||
╚═══════════════════════════════════════════════════════════════════╝
|
||||
EOF
|
||||
echo -e "${NC}"
|
||||
echo -e "${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e " ${WHITE}What was set up:${NC}"
|
||||
echo ""
|
||||
[[ -d "${CLAUDE_OS_DIR}/venv" ]] && echo -e " ${GREEN}✓${NC} Python environment"
|
||||
|
|
@ -641,6 +867,9 @@ main() {
|
|||
# Show banner
|
||||
show_banner
|
||||
|
||||
# Offer to install gum for enhanced experience
|
||||
offer_gum_install
|
||||
|
||||
# Provider selection
|
||||
select_provider
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue