claude-os/setup-claude-os.sh
Bob Roberts e98ecbcf14 Add beautiful unified installer with Local/Cloud support
✨ New Features:
- Beautiful CLI installer (setup-claude-os.sh) with ASCII art,
  progress bars, spinners, and styled prompts
- Choose between Local (Ollama) or Cloud (OpenAI) providers
- Lite model (llama3.2:3b) as default - faster download, works on more machines
- Full model (llama3.1:8b) option for users who want better quality
- OpenAI API support for users who prefer cloud

🔧 Changes:
- setup.sh and install.sh now redirect to setup-claude-os.sh
- config.py updated with provider support and new defaults
- Works on both macOS and Linux

📦 Default Model Change:
- Before: llama3.1:latest (8B, 4.7GB download)
- After: llama3.2:3b (3B, 2GB download)

This makes Claude OS accessible to more users with smaller machines
while still offering the full experience for power users.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-05 20:53:20 -06:00

682 lines
26 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
# ╔═══════════════════════════════════════════════════════════════════════════╗
# ║ Claude OS Installer ║
# ║ ║
# ║ Beautiful, unified setup for Claude's AI memory system ║
# ║ Works on macOS and Linux ║
# ╚═══════════════════════════════════════════════════════════════════════════╝
set -e
# ═══════════════════════════════════════════════════════════════════════════
# CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════
VERSION="2.1.0"
CLAUDE_OS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
USER_CLAUDE_DIR="${HOME}/.claude"
TEMPLATES_DIR="${CLAUDE_OS_DIR}/templates"
# Default model choices
DEFAULT_LLM_MODEL="llama3.2:3b" # Lite model - faster download, works on most machines
DEFAULT_EMBED_MODEL="nomic-embed-text" # Best local embedding model
FULL_LLM_MODEL="llama3.1:8b" # Full model - better quality
# ═══════════════════════════════════════════════════════════════════════════
# COLORS & STYLING
# ═══════════════════════════════════════════════════════════════════════════
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
MAGENTA='\033[0;35m'
WHITE='\033[1;37m'
DIM='\033[2m'
BOLD='\033[1m'
NC='\033[0m'
# Box drawing characters
BOX_TL="╭"
BOX_TR="╮"
BOX_BL="╰"
BOX_BR="╯"
BOX_H="─"
BOX_V="│"
# ═══════════════════════════════════════════════════════════════════════════
# HELPER FUNCTIONS
# ═══════════════════════════════════════════════════════════════════════════
# Print a styled box
print_box() {
local title="$1"
local width=60
local padding=$(( (width - ${#title} - 2) / 2 ))
echo ""
echo -e "${CYAN}${BOX_TL}$(printf '%*s' "$width" | tr ' ' "$BOX_H")${BOX_TR}${NC}"
echo -e "${CYAN}${BOX_V}${NC}$(printf '%*s' "$padding")${BOLD}${WHITE}$title${NC}$(printf '%*s' "$((width - padding - ${#title}))") ${CYAN}${BOX_V}${NC}"
echo -e "${CYAN}${BOX_BL}$(printf '%*s' "$width" | tr ' ' "$BOX_H")${BOX_BR}${NC}"
echo ""
}
# Print a section header
print_section() {
echo ""
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${BLUE} $1${NC}"
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
}
# Animated spinner
spinner() {
local pid=$1
local message=$2
local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'
local i=0
while kill -0 $pid 2>/dev/null; do
i=$(( (i + 1) % 10 ))
printf "\r${CYAN} ${spin:$i:1}${NC} ${message}"
sleep 0.1
done
printf "\r"
}
# Success message
success() {
echo -e "${GREEN} ✓${NC} $1"
}
# Warning message
warn() {
echo -e "${YELLOW} ⚠${NC} $1"
}
# Error message
error() {
echo -e "${RED} ✗${NC} $1"
}
# Info message
info() {
echo -e "${CYAN} ℹ${NC} $1"
}
# Progress bar
progress_bar() {
local current=$1
local total=$2
local width=40
local percentage=$((current * 100 / total))
local filled=$((current * width / total))
local empty=$((width - filled))
printf "\r ${CYAN}["
printf "%${filled}s" | tr ' ' '█'
printf "%${empty}s" | tr ' ' '░'
printf "]${NC} ${percentage}%%"
}
# Detect OS
detect_os() {
if [[ "$OSTYPE" == "darwin"* ]]; then
OS="macos"
PKG_MANAGER="brew"
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
OS="linux"
if command -v apt-get &> /dev/null; then
PKG_MANAGER="apt"
elif command -v dnf &> /dev/null; then
PKG_MANAGER="dnf"
elif command -v yum &> /dev/null; then
PKG_MANAGER="yum"
elif command -v pacman &> /dev/null; then
PKG_MANAGER="pacman"
else
PKG_MANAGER="unknown"
fi
else
OS="unknown"
PKG_MANAGER="unknown"
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# BANNER
# ═══════════════════════════════════════════════════════════════════════════
show_banner() {
clear
echo ""
echo -e "${CYAN}"
cat << 'EOF'
██████╗██╗ █████╗ ██╗ ██╗██████╗ ███████╗ ██████╗ ███████╗
██╔════╝██║ ██╔══██╗██║ ██║██╔══██╗██╔════╝ ██╔═══██╗██╔════╝
██║ ██║ ███████║██║ ██║██║ ██║█████╗ ██║ ██║███████╗
██║ ██║ ██╔══██║██║ ██║██║ ██║██╔══╝ ██║ ██║╚════██║
╚██████╗███████╗██║ ██║╚██████╔╝██████╔╝███████╗ ╚██████╔╝███████║
╚═════╝╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═════╝ ╚══════╝
EOF
echo -e "${NC}"
echo -e "${DIM} Your AI Memory System • v${VERSION}${NC}"
echo ""
echo -e "${WHITE} Claude CLI + Claude OS = Invincible! 🚀${NC}"
echo ""
}
# ═══════════════════════════════════════════════════════════════════════════
# PROVIDER SELECTION
# ═══════════════════════════════════════════════════════════════════════════
select_provider() {
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
}
# ═══════════════════════════════════════════════════════════════════════════
# MODEL SIZE SELECTION (for local installs)
# ═══════════════════════════════════════════════════════════════════════════
select_model_size() {
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
EMBED_MODEL="$DEFAULT_EMBED_MODEL"
}
# ═══════════════════════════════════════════════════════════════════════════
# OPENAI CONFIGURATION
# ═══════════════════════════════════════════════════════════════════════════
configure_openai() {
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 ""
if [[ -z "$OPENAI_API_KEY" ]]; then
error "API key cannot be empty"
exit 1
fi
# Validate key format
if [[ ! "$OPENAI_API_KEY" =~ ^sk- ]]; then
warn "API key doesn't start with 'sk-' - are you sure it's correct?"
fi
echo ""
success "API key saved"
}
# ═══════════════════════════════════════════════════════════════════════════
# PYTHON SETUP
# ═══════════════════════════════════════════════════════════════════════════
setup_python() {
print_section "Setting Up Python Environment"
# Find compatible Python
PYTHON_CMD=""
if command -v python3.12 &> /dev/null; then
PYTHON_CMD="python3.12"
elif command -v python3.11 &> /dev/null; then
PYTHON_CMD="python3.11"
elif command -v python3 &> /dev/null; then
local ver=$(python3 --version 2>&1 | cut -d' ' -f2 | cut -d'.' -f1,2)
if [[ "$ver" == "3.11" || "$ver" == "3.12" ]]; then
PYTHON_CMD="python3"
fi
fi
if [[ -z "$PYTHON_CMD" ]]; then
error "Python 3.11 or 3.12 required"
echo ""
echo -e " ${WHITE}Install Python:${NC}"
echo -e " macOS: ${CYAN}brew install python@3.12${NC}"
echo -e " Ubuntu: ${CYAN}sudo apt install python3.12${NC}"
echo -e " Fedora: ${CYAN}sudo dnf install python3.12${NC}"
exit 1
fi
local py_version=$($PYTHON_CMD --version | cut -d' ' -f2)
success "Found Python $py_version"
# Create virtual environment
if [[ ! -d "${CLAUDE_OS_DIR}/venv" ]]; then
info "Creating virtual environment..."
$PYTHON_CMD -m venv "${CLAUDE_OS_DIR}/venv" &
spinner $! "Creating virtual environment..."
success "Virtual environment created"
else
success "Virtual environment exists"
fi
# Activate and install dependencies
source "${CLAUDE_OS_DIR}/venv/bin/activate"
info "Installing dependencies..."
pip install --quiet --upgrade pip setuptools wheel 2>/dev/null &
spinner $! "Upgrading pip..."
pip install --quiet -r "${CLAUDE_OS_DIR}/requirements.txt" 2>/dev/null &
spinner $! "Installing dependencies (this may take a minute)..."
success "Dependencies installed"
}
# ═══════════════════════════════════════════════════════════════════════════
# OLLAMA SETUP
# ═══════════════════════════════════════════════════════════════════════════
setup_ollama() {
print_section "Setting Up Ollama"
# Check if Ollama is installed
if command -v ollama &> /dev/null; then
success "Ollama already installed"
else
info "Installing Ollama..."
if [[ "$OS" == "macos" ]]; then
if command -v brew &> /dev/null; then
brew install ollama &>/dev/null &
spinner $! "Installing via Homebrew..."
else
error "Homebrew required. Install from https://brew.sh"
exit 1
fi
elif [[ "$OS" == "linux" ]]; then
curl -fsSL https://ollama.ai/install.sh | sh &>/dev/null &
spinner $! "Installing Ollama..."
fi
success "Ollama installed"
fi
# Start Ollama if not running
if ! curl -s http://localhost:11434/api/tags &>/dev/null; then
info "Starting Ollama..."
if [[ "$OS" == "macos" ]]; then
brew services start ollama &>/dev/null || ollama serve &>/dev/null &
else
ollama serve &>/dev/null &
fi
sleep 3
if curl -s http://localhost:11434/api/tags &>/dev/null; then
success "Ollama started"
else
warn "Ollama may need manual start: ollama serve"
fi
else
success "Ollama is running"
fi
# Download models
echo ""
info "Downloading AI models..."
echo ""
# Download LLM model
if curl -s http://localhost:11434/api/tags | grep -q "\"name\":\"${LLM_MODEL}\""; then
success "Model ${LLM_MODEL} ready"
else
echo -e " ${CYAN}Downloading ${LLM_MODEL}...${NC}"
echo -e " ${DIM}(This may take a few minutes on first install)${NC}"
ollama pull "$LLM_MODEL" 2>&1 | while read line; do
if [[ "$line" =~ ([0-9]+)% ]]; then
progress_bar "${BASH_REMATCH[1]}" 100
fi
done
echo ""
success "Model ${LLM_MODEL} downloaded"
fi
# Download embedding model
if curl -s http://localhost:11434/api/tags | grep -q "\"name\":\"${EMBED_MODEL}\""; then
success "Model ${EMBED_MODEL} ready"
else
echo -e " ${CYAN}Downloading ${EMBED_MODEL}...${NC}"
ollama pull "$EMBED_MODEL" 2>&1 | while read line; do
if [[ "$line" =~ ([0-9]+)% ]]; then
progress_bar "${BASH_REMATCH[1]}" 100
fi
done
echo ""
success "Model ${EMBED_MODEL} downloaded"
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# REDIS SETUP
# ═══════════════════════════════════════════════════════════════════════════
setup_redis() {
print_section "Setting Up Redis"
if command -v redis-cli &> /dev/null; then
success "Redis already installed"
else
info "Installing Redis..."
if [[ "$OS" == "macos" ]]; then
brew install redis &>/dev/null &
spinner $! "Installing via Homebrew..."
elif [[ "$OS" == "linux" ]]; then
case "$PKG_MANAGER" in
apt) sudo apt-get update -qq && sudo apt-get install -y -qq redis-server &>/dev/null & ;;
dnf) sudo dnf install -y -q redis &>/dev/null & ;;
yum) sudo yum install -y -q redis &>/dev/null & ;;
pacman) sudo pacman -S --noconfirm --quiet redis &>/dev/null & ;;
esac
spinner $! "Installing Redis..."
fi
success "Redis installed"
fi
# Start Redis if not running
if redis-cli ping &>/dev/null; then
success "Redis is running"
else
info "Starting Redis..."
if [[ "$OS" == "macos" ]]; then
brew services start redis &>/dev/null || true
else
redis-server --daemonize yes &>/dev/null || true
fi
sleep 1
if redis-cli ping &>/dev/null; then
success "Redis started"
else
warn "Redis may need manual start"
fi
fi
}
# ═══════════════════════════════════════════════════════════════════════════
# CLAUDE CODE INTEGRATION
# ═══════════════════════════════════════════════════════════════════════════
setup_claude_integration() {
print_section "Integrating with Claude Code"
# Create directories
mkdir -p "${USER_CLAUDE_DIR}/commands"
mkdir -p "${USER_CLAUDE_DIR}/skills"
mkdir -p "${USER_CLAUDE_DIR}/mcp-servers"
mkdir -p "${CLAUDE_OS_DIR}/data"
mkdir -p "${CLAUDE_OS_DIR}/logs"
success "Created directories"
# Symlink commands
local cmd_count=0
for cmd_file in "${TEMPLATES_DIR}"/commands/*.md; do
if [[ -f "$cmd_file" ]]; then
local cmd_name=$(basename "$cmd_file")
local dest="${USER_CLAUDE_DIR}/commands/${cmd_name}"
rm -f "$dest" 2>/dev/null
ln -s "$cmd_file" "$dest"
((cmd_count++))
fi
done
success "Linked ${cmd_count} commands"
# Symlink skills
local skill_count=0
for skill_dir in "${TEMPLATES_DIR}"/skills/*/; do
if [[ -d "$skill_dir" ]]; then
local skill_name=$(basename "$skill_dir")
local dest="${USER_CLAUDE_DIR}/skills/${skill_name}"
rm -rf "$dest" 2>/dev/null
ln -s "$skill_dir" "$dest"
((skill_count++))
fi
done
success "Linked ${skill_count} skills"
# Configure MCP server in settings.json
local settings_file="${USER_CLAUDE_DIR}/settings.json"
if [[ ! -f "$settings_file" ]]; then
echo "{}" > "$settings_file"
fi
python3 << EOF
import json
import os
settings_file = "$settings_file"
claude_os_dir = "$CLAUDE_OS_DIR"
try:
with open(settings_file, 'r') as f:
settings = json.load(f)
except:
settings = {}
if 'mcpServers' not in settings:
settings['mcpServers'] = {}
settings['mcpServers']['code-forge'] = {
"command": os.path.join(claude_os_dir, "venv", "bin", "python3"),
"args": [os.path.join(claude_os_dir, "mcp_server", "claude_code_mcp.py")],
"env": {
"CLAUDE_OS_API": "http://localhost:8051"
}
}
with open(settings_file, 'w') as f:
json.dump(settings, f, indent=2)
EOF
success "Configured MCP server"
}
# ═══════════════════════════════════════════════════════════════════════════
# CREATE CONFIGURATION FILE
# ═══════════════════════════════════════════════════════════════════════════
create_config() {
print_section "Creating Configuration"
local config_file="${CLAUDE_OS_DIR}/.env"
cat > "$config_file" << EOF
# Claude OS Configuration
# Generated by setup-claude-os.sh on $(date)
# Provider: local or openai
CLAUDE_OS_PROVIDER=${PROVIDER}
# LLM Settings
LLM_MODEL=${LLM_MODEL:-gpt-4o-mini}
EMBEDDING_MODEL=${EMBED_MODEL:-text-embedding-3-small}
# Ollama Settings (for local provider)
OLLAMA_HOST=http://localhost:11434
# OpenAI Settings (for cloud provider)
OPENAI_API_KEY=${OPENAI_API_KEY:-}
# Server Settings
CLAUDE_OS_HOST=0.0.0.0
CLAUDE_OS_PORT=8051
# Database
CLAUDE_OS_DB_PATH=./data/claude-os.db
EOF
success "Created configuration file"
# Also create the JSON config for backwards compatibility
cat > "${CLAUDE_OS_DIR}/claude-os-config.json" << EOF
{
"provider": "${PROVIDER}",
"llm_model": "${LLM_MODEL:-gpt-4o-mini}",
"embed_model": "${EMBED_MODEL:-text-embedding-3-small}",
"version": "${VERSION}",
"installed_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
}
EOF
}
# ═══════════════════════════════════════════════════════════════════════════
# COMPLETION
# ═══════════════════════════════════════════════════════════════════════════
show_completion() {
echo ""
echo -e "${GREEN}"
cat << 'EOF'
╔═══════════════════════════════════════════════════════════════════╗
║ ║
║ ✨ Claude OS is ready! ✨ ║
║ ║
╚═══════════════════════════════════════════════════════════════════╝
EOF
echo -e "${NC}"
echo -e " ${WHITE}What was set up:${NC}"
echo ""
[[ -d "${CLAUDE_OS_DIR}/venv" ]] && echo -e " ${GREEN}✓${NC} Python environment"
[[ "$PROVIDER" == "local" ]] && echo -e " ${GREEN}✓${NC} Ollama with ${LLM_MODEL}"
[[ "$PROVIDER" == "local" ]] && echo -e " ${GREEN}✓${NC} Embedding model (${EMBED_MODEL})"
[[ "$PROVIDER" == "openai" ]] && echo -e " ${GREEN}✓${NC} OpenAI API configured"
command -v redis-cli &>/dev/null && echo -e " ${GREEN}✓${NC} Redis cache"
[[ -d "${USER_CLAUDE_DIR}/commands" ]] && echo -e " ${GREEN}✓${NC} Claude Code commands"
[[ -d "${USER_CLAUDE_DIR}/skills" ]] && echo -e " ${GREEN}✓${NC} Claude Code skills"
[[ -f "${USER_CLAUDE_DIR}/settings.json" ]] && echo -e " ${GREEN}✓${NC} MCP server configured"
echo ""
echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e " ${WHITE}Next Steps:${NC}"
echo ""
echo -e " ${CYAN}1.${NC} Start Claude OS:"
echo -e " ${DIM}./start_all_services.sh${NC}"
echo ""
echo -e " ${CYAN}2.${NC} In your project, initialize Claude OS:"
echo -e " ${DIM}/claude-os-init${NC}"
echo ""
echo -e " ${CYAN}3.${NC} Start a session:"
echo -e " ${DIM}/claude-os-session start \"working on feature X\"${NC}"
echo ""
echo -e " ${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e " ${DIM}Documentation: README.md${NC}"
echo -e " ${DIM}Issues: https://github.com/brobertsaz/claude-os/issues${NC}"
echo ""
echo -e " ${WHITE}Happy coding! 🚀${NC}"
echo ""
}
# ═══════════════════════════════════════════════════════════════════════════
# MAIN
# ═══════════════════════════════════════════════════════════════════════════
main() {
# Detect OS first
detect_os
# Show banner
show_banner
# Provider selection
select_provider
# Provider-specific setup
if [[ "$PROVIDER" == "local" ]]; then
select_model_size
elif [[ "$PROVIDER" == "openai" ]]; then
configure_openai
LLM_MODEL="gpt-4o-mini"
EMBED_MODEL="text-embedding-3-small"
elif [[ "$PROVIDER" == "custom" ]]; then
info "Custom setup - you'll need to configure .env manually"
LLM_MODEL=""
EMBED_MODEL=""
fi
# Common setup
setup_python
# Provider-specific dependencies
if [[ "$PROVIDER" == "local" ]]; then
setup_ollama
fi
# Optional: Redis (useful for caching)
setup_redis
# Claude Code integration
setup_claude_integration
# Create configuration
create_config
# Show completion
show_completion
}
# Run main
main "$@"