mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
hanzo-dev was a 53-day-frozen diverged fork that release.yml shipped only because codex-rs's v8 dep was believed to block musl. It doesn't — the prebuilt rusty_v8 musl artifacts already exist (rust-release.yml uses them). So collapse to the single canonical tree. - Delete hanzo-dev/ (1416 paths). codex-rs has zero deps on it; its 'unique' crates (zap/sse/backoff) aren't needed (codex-rs has its own upstream impls); zt/zap zero-trust was feature-gated OFF in release builds so it never shipped (external ~/work/hanzozt SDKs + docs unaffected; design notes kept in LLM.md). - Repoint every remaining hanzo-dev build/path ref to codex-rs: workflows (preview-build, issue-code, issue-comment), scripts (ci-tests.sh, generate-homebrew-formula.sh, pre-release.sh, build-fast.sh, hanzo.sh, hanzo-cli), code-sign actions, dependabot.yaml, merge-policy.json, docs. - Port the dev-fast cargo profile into codex-rs/Cargo.toml (build-fast.sh / pre-release.sh / release.yml preflight all use it). - Delete obsolete hanzo-dev-only tooling (check-codex-path-deps.sh, upstream-merge/diff-crates.sh) + repoint their callers. - package.json/build-fast.sh build only --bin dev (codex-rs has no dev-tui/dev-exec split bins). Verified locally: codex-rs builds `dev` (Hanzo Dev header, CODE_VERSION-aware); zero dangling source refs (only npm package names + Homebrew formula name kept).
1253 lines
35 KiB
Bash
Executable File
1253 lines
35 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# hanzo.sh - Full-stack Hanzo installation script
|
|
# Supports macOS (arm64/x64) and Linux (x64/arm64)
|
|
#
|
|
# Usage:
|
|
# curl -fsSL https://hanzo.sh | bash
|
|
# curl -fsSL https://hanzo.sh | bash -s -- --components all
|
|
# ./hanzo.sh [options]
|
|
#
|
|
# Options:
|
|
# --dev Install development dependencies and build from source
|
|
# --prod Production mode (use pre-built binaries) [default]
|
|
# --components Comma-separated list: dev,cli,llm,chat,mcp,hanzod (default: dev,cli,mcp)
|
|
# --no-docker Skip Docker-based services
|
|
# --uninstall Remove Hanzo installation
|
|
# --version Show version information
|
|
# --quiet Minimal output (for scripted installs)
|
|
# --force Skip confirmation prompts
|
|
# --help Show this help message
|
|
#
|
|
# Environment:
|
|
# HANZO_HOME Base directory (default: ~/.hanzo)
|
|
# HANZO_VERSION Version to install (default: latest)
|
|
|
|
set -euo pipefail
|
|
|
|
# Determine script directory (works even when piped from curl)
|
|
SCRIPT_DIR="${BASH_SOURCE[0]:-}"
|
|
if [[ -n "$SCRIPT_DIR" ]] && [[ -f "$SCRIPT_DIR" ]]; then
|
|
SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_DIR")" && pwd)"
|
|
else
|
|
SCRIPT_DIR=""
|
|
fi
|
|
|
|
# Colors for output (disabled if not a terminal or in quiet mode)
|
|
if [[ -t 1 ]] && [[ "${QUIET:-false}" != "true" ]]; then
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
BLUE='\033[0;34m'
|
|
MAGENTA='\033[0;35m'
|
|
CYAN='\033[0;36m'
|
|
NC='\033[0m' # No Color
|
|
BOLD='\033[1m'
|
|
else
|
|
RED='' GREEN='' YELLOW='' BLUE='' MAGENTA='' CYAN='' NC='' BOLD=''
|
|
fi
|
|
|
|
# Configuration
|
|
HANZO_HOME="${HANZO_HOME:-$HOME/.hanzo}"
|
|
HANZO_BIN="${HANZO_BIN:-$HANZO_HOME/bin}"
|
|
HANZO_CONFIG="${HANZO_CONFIG:-$HANZO_HOME/config}"
|
|
HANZO_DATA="${HANZO_DATA:-$HANZO_HOME/data}"
|
|
HANZO_LOGS="${HANZO_LOGS:-$HANZO_HOME/logs}"
|
|
|
|
# Default options
|
|
MODE="prod"
|
|
COMPONENTS="dev,cli,mcp"
|
|
SKIP_DOCKER=false
|
|
VERBOSE=false
|
|
QUIET=false
|
|
FORCE=false
|
|
UNINSTALL=false
|
|
|
|
# Version info
|
|
HANZO_VERSION="${HANZO_VERSION:-latest}"
|
|
SCRIPT_VERSION="1.1.0"
|
|
NODE_VERSION="20.18.1"
|
|
# shellcheck disable=SC2034
|
|
PYTHON_VERSION="3.11"
|
|
RUST_VERSION="stable"
|
|
|
|
# Cleanup trap for partial installations
|
|
CLEANUP_NEEDED=false
|
|
cleanup_on_error() {
|
|
if [[ "$CLEANUP_NEEDED" == "true" ]]; then
|
|
log_warn "Installation interrupted. Partial installation may remain at $HANZO_HOME"
|
|
log_info "Run './hanzo.sh --uninstall' to clean up, then retry."
|
|
fi
|
|
}
|
|
trap cleanup_on_error EXIT
|
|
|
|
# Logging functions
|
|
log_info() { [[ "$QUIET" == "true" ]] || echo -e "${BLUE}[INFO]${NC} $*"; }
|
|
log_success() { [[ "$QUIET" == "true" ]] || echo -e "${GREEN}[OK]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
|
log_step() { [[ "$QUIET" == "true" ]] || echo -e "${MAGENTA}[STEP]${NC} ${BOLD}$*${NC}"; }
|
|
log_debug() { [[ "$VERBOSE" == "true" ]] && echo -e "${CYAN}[DEBUG]${NC} $*" || true; }
|
|
|
|
# Print banner
|
|
print_banner() {
|
|
[[ "$QUIET" == "true" ]] && return 0
|
|
echo -e "${CYAN}"
|
|
cat << 'EOF'
|
|
__ __
|
|
/ / / /___ _____ ____ ____
|
|
/ /_/ / __ `/ __ \/_ / / __ \
|
|
/ __ / /_/ / / / / / /_/ /_/ /
|
|
/_/ /_/\__,_/_/ /_/ /___/\____/
|
|
|
|
AI Development Platform
|
|
EOF
|
|
echo -e "${NC}"
|
|
echo -e "${BOLD}Hanzo Full-Stack Installer v${SCRIPT_VERSION}${NC}"
|
|
echo ""
|
|
}
|
|
|
|
# Show version
|
|
show_version() {
|
|
echo "hanzo.sh version $SCRIPT_VERSION"
|
|
echo "Hanzo installer for version: $HANZO_VERSION"
|
|
}
|
|
|
|
# Show help
|
|
show_help() {
|
|
cat << 'EOF'
|
|
Hanzo Full-Stack Installation Script
|
|
|
|
USAGE:
|
|
hanzo.sh [OPTIONS]
|
|
curl -fsSL https://hanzo.sh | bash
|
|
curl -fsSL https://hanzo.sh | bash -s -- [OPTIONS]
|
|
|
|
OPTIONS:
|
|
--dev Development mode (build from source)
|
|
--prod Production mode (pre-built binaries) [default]
|
|
--components LIST Components to install (comma-separated)
|
|
Available: dev,cli,llm,chat,mcp,hanzod,all
|
|
Default: dev,mcp
|
|
--no-docker Skip Docker-based services (llm, chat)
|
|
--uninstall Remove Hanzo installation
|
|
--verbose Enable verbose output
|
|
--quiet Minimal output (for scripted installs)
|
|
--force Skip confirmation prompts
|
|
--version Show version information
|
|
--help Show this help message
|
|
|
|
COMPONENTS:
|
|
dev Hanzo Dev CLI - AI coding assistant
|
|
cli Hanzo Python CLI - Unified platform CLI (hanzo command)
|
|
llm LLM Gateway - Unified proxy for 100+ LLM providers
|
|
chat Hanzo Chat - LibreChat fork with MCP integration
|
|
mcp MCP Tools - Model Context Protocol server
|
|
hanzod Hanzod - AI blockchain daemon
|
|
|
|
ENVIRONMENT VARIABLES:
|
|
HANZO_HOME Base directory (default: ~/.hanzo)
|
|
HANZO_VERSION Version to install (default: latest)
|
|
OPENAI_API_KEY OpenAI API key
|
|
ANTHROPIC_API_KEY Anthropic API key
|
|
|
|
EXAMPLES:
|
|
# Quick install (dev CLI + MCP)
|
|
./hanzo.sh
|
|
|
|
# Full stack with all components
|
|
./hanzo.sh --components all
|
|
|
|
# Development setup (build from source)
|
|
./hanzo.sh --dev --components dev,mcp,llm
|
|
|
|
# Production with specific components
|
|
./hanzo.sh --prod --components dev,llm,chat
|
|
|
|
# Scripted install (quiet mode)
|
|
curl -fsSL https://hanzo.sh | bash -s -- --quiet --force
|
|
|
|
# Uninstall
|
|
./hanzo.sh --uninstall
|
|
|
|
For more information: https://hanzo.ai/docs
|
|
EOF
|
|
}
|
|
|
|
# Detect platform
|
|
detect_platform() {
|
|
local os arch
|
|
|
|
os="$(uname -s)"
|
|
arch="$(uname -m)"
|
|
|
|
case "$os" in
|
|
Darwin) PLATFORM="darwin" ;;
|
|
Linux) PLATFORM="linux" ;;
|
|
MINGW*|MSYS*|CYGWIN*)
|
|
log_error "Windows is not directly supported. Use WSL2."
|
|
log_info "Install WSL2: https://docs.microsoft.com/en-us/windows/wsl/install"
|
|
exit 1
|
|
;;
|
|
*)
|
|
log_error "Unsupported operating system: $os"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
case "$arch" in
|
|
x86_64|amd64) ARCH="x64" ;;
|
|
aarch64|arm64) ARCH="arm64" ;;
|
|
*)
|
|
log_error "Unsupported architecture: $arch"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
log_info "Detected platform: $PLATFORM-$ARCH"
|
|
}
|
|
|
|
# Uninstall Hanzo
|
|
uninstall_hanzo() {
|
|
log_step "Uninstalling Hanzo..."
|
|
|
|
# Confirm uninstall
|
|
if [[ "$FORCE" != "true" ]]; then
|
|
echo -e "${YELLOW}This will remove:${NC}"
|
|
echo " - $HANZO_HOME (all Hanzo data)"
|
|
echo " - Global npm packages (@hanzo/dev, @hanzo/mcp)"
|
|
echo ""
|
|
read -rp "Are you sure? [y/N] " confirm
|
|
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
|
log_info "Uninstall cancelled"
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# Remove npm packages
|
|
if check_command npm; then
|
|
log_info "Removing npm packages..."
|
|
npm uninstall -g @hanzo/dev 2>/dev/null || true
|
|
npm uninstall -g @hanzo/mcp 2>/dev/null || true
|
|
fi
|
|
|
|
# Stop Docker services if running
|
|
if check_command docker; then
|
|
for dir in "$HANZO_DATA/llm" "$HANZO_DATA/chat"; do
|
|
if [[ -f "$dir/compose.yml" ]]; then
|
|
log_info "Stopping services in $dir..."
|
|
(cd "$dir" && docker compose down 2>/dev/null) || true
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# Remove Hanzo home directory
|
|
if [[ -d "$HANZO_HOME" ]]; then
|
|
log_info "Removing $HANZO_HOME..."
|
|
rm -rf "$HANZO_HOME"
|
|
fi
|
|
|
|
# Clean up shell config (careful not to break user's config)
|
|
for rc_file in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do
|
|
if [[ -f "$rc_file" ]] && grep -q "# Hanzo AI - Added by hanzo.sh" "$rc_file"; then
|
|
log_info "Cleaning shell config in $rc_file..."
|
|
# Remove the Hanzo block (from comment to empty line or EOF)
|
|
sed -i.bak '/# Hanzo AI - Added by hanzo.sh/,/^$/d' "$rc_file" 2>/dev/null || \
|
|
sed -i '' '/# Hanzo AI - Added by hanzo.sh/,/^$/d' "$rc_file" 2>/dev/null || true
|
|
rm -f "${rc_file}.bak"
|
|
fi
|
|
done
|
|
|
|
log_success "Hanzo has been uninstalled"
|
|
log_info "Please restart your shell or run: exec \$SHELL"
|
|
}
|
|
|
|
# Check for required commands
|
|
check_command() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Install Homebrew on macOS
|
|
install_homebrew() {
|
|
if check_command brew; then
|
|
log_success "Homebrew already installed"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Installing Homebrew..."
|
|
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
|
|
|
# Add to PATH for current session
|
|
if [[ "$ARCH" == "arm64" ]]; then
|
|
eval "$(/opt/homebrew/bin/brew shellenv)"
|
|
else
|
|
eval "$(/usr/local/bin/brew shellenv)"
|
|
fi
|
|
|
|
log_success "Homebrew installed"
|
|
}
|
|
|
|
# Install system dependencies
|
|
install_system_deps() {
|
|
log_step "Installing system dependencies..."
|
|
|
|
if [[ "$PLATFORM" == "darwin" ]]; then
|
|
install_homebrew
|
|
|
|
# Core dependencies
|
|
local deps=(git curl wget jq openssl)
|
|
|
|
# Development dependencies
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
deps+=(cmake pkg-config protobuf)
|
|
fi
|
|
|
|
for dep in "${deps[@]}"; do
|
|
if ! check_command "$dep"; then
|
|
log_info "Installing $dep..."
|
|
brew install "$dep" 2>/dev/null || true
|
|
fi
|
|
done
|
|
|
|
log_success "System dependencies installed"
|
|
|
|
elif [[ "$PLATFORM" == "linux" ]]; then
|
|
# Detect package manager
|
|
if check_command apt-get; then
|
|
PKG_MANAGER="apt"
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq git curl wget jq build-essential libssl-dev pkg-config
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
sudo apt-get install -y -qq cmake protobuf-compiler
|
|
fi
|
|
elif check_command yum; then
|
|
PKG_MANAGER="yum"
|
|
sudo yum install -y git curl wget jq gcc gcc-c++ openssl-devel pkgconfig
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
sudo yum install -y cmake protobuf-compiler
|
|
fi
|
|
elif check_command dnf; then
|
|
PKG_MANAGER="dnf"
|
|
sudo dnf install -y git curl wget jq gcc gcc-c++ openssl-devel pkgconfig
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
sudo dnf install -y cmake protobuf-compiler
|
|
fi
|
|
elif check_command pacman; then
|
|
PKG_MANAGER="pacman"
|
|
sudo pacman -Sy --noconfirm git curl wget jq base-devel openssl pkgconf
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
sudo pacman -S --noconfirm cmake protobuf
|
|
fi
|
|
else
|
|
log_warn "Unknown package manager. Please install dependencies manually."
|
|
fi
|
|
|
|
log_success "System dependencies installed"
|
|
fi
|
|
}
|
|
|
|
# Install Node.js
|
|
install_nodejs() {
|
|
log_step "Setting up Node.js..."
|
|
|
|
if check_command node; then
|
|
local current_version
|
|
current_version=$(node --version | sed 's/v//' | cut -d. -f1)
|
|
if [[ "$current_version" -ge 20 ]]; then
|
|
log_success "Node.js $(node --version) already installed"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [[ "$PLATFORM" == "darwin" ]]; then
|
|
# Use Homebrew for Node.js
|
|
brew install node@20 2>/dev/null || true
|
|
brew link node@20 --force --overwrite 2>/dev/null || true
|
|
else
|
|
# Use n for version management
|
|
if ! check_command n; then
|
|
curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n | sudo bash -s lts
|
|
fi
|
|
sudo n "$NODE_VERSION"
|
|
fi
|
|
|
|
# Install pnpm
|
|
if ! check_command pnpm; then
|
|
log_info "Installing pnpm..."
|
|
npm install -g pnpm 2>/dev/null || true
|
|
fi
|
|
|
|
log_success "Node.js $(node --version) ready"
|
|
}
|
|
|
|
# Install Python
|
|
install_python() {
|
|
log_step "Setting up Python..."
|
|
|
|
if check_command python3; then
|
|
local current_version
|
|
current_version=$(python3 --version | awk '{print $2}' | cut -d. -f1,2)
|
|
if [[ "$current_version" == "3.11" ]] || [[ "$current_version" == "3.12" ]]; then
|
|
log_success "Python $current_version already installed"
|
|
install_uv
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
if [[ "$PLATFORM" == "darwin" ]]; then
|
|
brew install [email protected] 2>/dev/null || true
|
|
brew link [email protected] --force --overwrite 2>/dev/null || true
|
|
else
|
|
if check_command apt-get; then
|
|
sudo apt-get install -y -qq python3.11 python3.11-venv python3.11-dev
|
|
elif check_command yum || check_command dnf; then
|
|
sudo "${PKG_MANAGER:-dnf}" install -y python3.11 python3.11-devel
|
|
fi
|
|
fi
|
|
|
|
install_uv
|
|
log_success "Python ready"
|
|
}
|
|
|
|
# Install uv (fast Python package manager)
|
|
install_uv() {
|
|
if check_command uv; then
|
|
log_success "uv already installed"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Installing uv..."
|
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
log_success "uv installed"
|
|
}
|
|
|
|
# Install Rust
|
|
install_rust() {
|
|
log_step "Setting up Rust..."
|
|
|
|
if check_command rustc; then
|
|
log_success "Rust $(rustc --version | awk '{print $2}') already installed"
|
|
return 0
|
|
fi
|
|
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_VERSION"
|
|
# shellcheck source=/dev/null
|
|
source "$HOME/.cargo/env"
|
|
|
|
log_success "Rust $(rustc --version | awk '{print $2}') installed"
|
|
}
|
|
|
|
# Install Docker
|
|
install_docker() {
|
|
if [[ "$SKIP_DOCKER" == true ]]; then
|
|
log_info "Skipping Docker installation (--no-docker flag)"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Setting up Docker..."
|
|
|
|
if check_command docker; then
|
|
if docker info >/dev/null 2>&1; then
|
|
log_success "Docker already installed and running"
|
|
return 0
|
|
else
|
|
log_warn "Docker installed but not running. Please start Docker."
|
|
fi
|
|
return 0
|
|
fi
|
|
|
|
if [[ "$PLATFORM" == "darwin" ]]; then
|
|
log_info "Installing Docker Desktop..."
|
|
brew install --cask docker 2>/dev/null || true
|
|
log_warn "Please open Docker Desktop to complete setup"
|
|
else
|
|
log_info "Installing Docker Engine..."
|
|
curl -fsSL https://get.docker.com | sh
|
|
sudo usermod -aG docker "$USER" 2>/dev/null || true
|
|
sudo systemctl enable docker 2>/dev/null || true
|
|
sudo systemctl start docker 2>/dev/null || true
|
|
log_warn "You may need to log out and back in for Docker permissions"
|
|
fi
|
|
|
|
# Install docker-compose if needed
|
|
if ! check_command docker-compose && ! docker compose version >/dev/null 2>&1; then
|
|
if [[ "$PLATFORM" == "linux" ]]; then
|
|
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" \
|
|
-o /usr/local/bin/docker-compose
|
|
sudo chmod +x /usr/local/bin/docker-compose
|
|
fi
|
|
fi
|
|
|
|
log_success "Docker setup complete"
|
|
}
|
|
|
|
# Create directory structure
|
|
create_directories() {
|
|
log_step "Creating Hanzo directories..."
|
|
|
|
mkdir -p "$HANZO_HOME"
|
|
mkdir -p "$HANZO_BIN"
|
|
mkdir -p "$HANZO_CONFIG"
|
|
mkdir -p "$HANZO_DATA"
|
|
mkdir -p "$HANZO_LOGS"
|
|
|
|
log_success "Directories created at $HANZO_HOME"
|
|
}
|
|
|
|
# Install Hanzo Dev CLI
|
|
install_dev() {
|
|
log_step "Installing Hanzo Dev CLI..."
|
|
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
# Build from source - try multiple locations
|
|
local source_dir=""
|
|
|
|
# Priority 1: Script directory (if running from repo)
|
|
if [[ -n "$SCRIPT_DIR" ]] && [[ -f "$SCRIPT_DIR/build-fast.sh" ]]; then
|
|
source_dir="$SCRIPT_DIR"
|
|
# Priority 2: Current directory
|
|
elif [[ -f "./build-fast.sh" ]]; then
|
|
source_dir="$(pwd)"
|
|
# Priority 3: Common development path
|
|
elif [[ -d "$HOME/work/hanzo/dev" ]] && [[ -f "$HOME/work/hanzo/dev/build-fast.sh" ]]; then
|
|
source_dir="$HOME/work/hanzo/dev"
|
|
fi
|
|
|
|
if [[ -n "$source_dir" ]]; then
|
|
log_info "Building from source at $source_dir..."
|
|
local original_dir
|
|
original_dir="$(pwd)"
|
|
cd "$source_dir"
|
|
|
|
if ./build-fast.sh; then
|
|
local bin_path="./codex-rs/target/dev-fast/dev"
|
|
if [[ -f "$bin_path" ]]; then
|
|
cp "$bin_path" "$HANZO_BIN/dev"
|
|
chmod +x "$HANZO_BIN/dev"
|
|
log_success "Built and installed dev CLI from source"
|
|
cd "$original_dir"
|
|
return 0
|
|
else
|
|
log_warn "Build completed but binary not found at $bin_path"
|
|
fi
|
|
else
|
|
log_warn "Build failed"
|
|
fi
|
|
cd "$original_dir"
|
|
fi
|
|
|
|
log_warn "Source build not available, falling back to binary install"
|
|
fi
|
|
|
|
# Production mode: Install from npm (pre-built binary)
|
|
log_info "Installing from npm..."
|
|
if npm install -g @hanzo/dev 2>/dev/null; then
|
|
log_success "Hanzo Dev CLI installed via npm"
|
|
return 0
|
|
fi
|
|
|
|
# Fallback: try with pnpm
|
|
if check_command pnpm && pnpm add -g @hanzo/dev 2>/dev/null; then
|
|
log_success "Hanzo Dev CLI installed via pnpm"
|
|
return 0
|
|
fi
|
|
|
|
# Final fallback: direct binary download
|
|
log_info "Package managers failed, downloading binary directly..."
|
|
if download_dev_binary; then
|
|
log_success "Hanzo Dev CLI installed via direct download"
|
|
return 0
|
|
fi
|
|
|
|
log_error "Failed to install Hanzo Dev CLI"
|
|
return 1
|
|
}
|
|
|
|
# Download pre-built dev binary
|
|
download_dev_binary() {
|
|
local triple
|
|
case "$PLATFORM-$ARCH" in
|
|
darwin-arm64) triple="aarch64-apple-darwin" ;;
|
|
darwin-x64) triple="x86_64-apple-darwin" ;;
|
|
linux-x64) triple="x86_64-unknown-linux-musl" ;;
|
|
linux-arm64) triple="aarch64-unknown-linux-musl" ;;
|
|
*)
|
|
log_error "Unsupported platform for binary download: $PLATFORM-$ARCH"
|
|
return 1
|
|
;;
|
|
esac
|
|
|
|
local base_url="https://github.com/hanzoai/dev/releases"
|
|
local version_path="latest/download"
|
|
|
|
# If specific version requested, use that
|
|
if [[ "$HANZO_VERSION" != "latest" ]]; then
|
|
version_path="download/v${HANZO_VERSION}"
|
|
fi
|
|
|
|
local url="${base_url}/${version_path}/code-${triple}.tar.gz"
|
|
local tmp_dir
|
|
tmp_dir="$(mktemp -d)"
|
|
local tmp_file="${tmp_dir}/hanzo-dev.tar.gz"
|
|
|
|
log_info "Downloading from $url..."
|
|
log_debug "Temp directory: $tmp_dir"
|
|
|
|
# Download with retry logic
|
|
local max_retries=3
|
|
local retry=0
|
|
while [[ $retry -lt $max_retries ]]; do
|
|
if curl -fsSL --connect-timeout 30 --max-time 300 "$url" -o "$tmp_file" 2>/dev/null; then
|
|
break
|
|
fi
|
|
retry=$((retry + 1))
|
|
if [[ $retry -lt $max_retries ]]; then
|
|
log_warn "Download failed, retrying ($retry/$max_retries)..."
|
|
sleep 2
|
|
fi
|
|
done
|
|
|
|
if [[ ! -f "$tmp_file" ]]; then
|
|
log_error "Failed to download binary after $max_retries attempts"
|
|
rm -rf "$tmp_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Verify it's a valid gzip file
|
|
if ! file "$tmp_file" | grep -q "gzip"; then
|
|
log_error "Downloaded file is not a valid gzip archive"
|
|
log_debug "File type: $(file "$tmp_file")"
|
|
rm -rf "$tmp_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Extract to temp directory first
|
|
log_info "Extracting binary..."
|
|
if ! tar -xzf "$tmp_file" -C "$tmp_dir" 2>/dev/null; then
|
|
log_error "Failed to extract archive"
|
|
rm -rf "$tmp_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Find the binary (handle various naming conventions)
|
|
local binary_path=""
|
|
for candidate in "$tmp_dir/code" "$tmp_dir/code-${triple}" "$tmp_dir/dev"; do
|
|
if [[ -f "$candidate" ]]; then
|
|
binary_path="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
|
|
# Also check subdirectories
|
|
if [[ -z "$binary_path" ]]; then
|
|
binary_path="$(find "$tmp_dir" -type f -name 'code' -o -name 'dev' 2>/dev/null | head -1)"
|
|
fi
|
|
|
|
if [[ -z "$binary_path" ]] || [[ ! -f "$binary_path" ]]; then
|
|
log_error "Binary not found in archive"
|
|
log_debug "Archive contents: $(ls -la "$tmp_dir")"
|
|
rm -rf "$tmp_dir"
|
|
return 1
|
|
fi
|
|
|
|
# Install to HANZO_BIN
|
|
cp "$binary_path" "$HANZO_BIN/dev"
|
|
chmod +x "$HANZO_BIN/dev"
|
|
|
|
# Verify the binary works
|
|
if ! "$HANZO_BIN/dev" --version >/dev/null 2>&1; then
|
|
log_warn "Binary installed but version check failed (this may be ok)"
|
|
fi
|
|
|
|
# Cleanup
|
|
rm -rf "$tmp_dir"
|
|
|
|
log_debug "Binary installed to $HANZO_BIN/dev"
|
|
return 0
|
|
}
|
|
|
|
# Install MCP Tools
|
|
install_mcp() {
|
|
log_step "Installing MCP Tools..."
|
|
|
|
npm install -g @hanzo/mcp 2>/dev/null || pnpm add -g @hanzo/mcp 2>/dev/null || {
|
|
log_warn "Failed to install MCP from npm"
|
|
}
|
|
|
|
log_success "MCP Tools installed"
|
|
}
|
|
|
|
# Install LLM Gateway
|
|
install_llm() {
|
|
if [[ "$SKIP_DOCKER" == true ]]; then
|
|
log_warn "Skipping LLM Gateway (requires Docker)"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Installing LLM Gateway..."
|
|
|
|
local llm_dir="$HANZO_DATA/llm"
|
|
mkdir -p "$llm_dir"
|
|
|
|
# Create docker-compose.yml for LLM Gateway
|
|
cat > "$llm_dir/compose.yml" << 'YAML'
|
|
services:
|
|
llm:
|
|
image: ghcr.io/hanzoai/llm:latest
|
|
container_name: hanzo-llm
|
|
ports:
|
|
- "4000:4000"
|
|
environment:
|
|
- DATABASE_URL=postgresql://hanzo:hanzo@db:5432/llm
|
|
- REDIS_HOST=redis
|
|
- MASTER_KEY=${HANZO_LLM_MASTER_KEY:-}
|
|
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
|
depends_on:
|
|
- db
|
|
- redis
|
|
restart: unless-stopped
|
|
|
|
db:
|
|
image: postgres:16-alpine
|
|
container_name: hanzo-llm-db
|
|
environment:
|
|
- POSTGRES_USER=hanzo
|
|
- POSTGRES_PASSWORD=hanzo
|
|
- POSTGRES_DB=llm
|
|
volumes:
|
|
- llm_pgdata:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
|
|
redis:
|
|
image: redis:7-alpine
|
|
container_name: hanzo-llm-redis
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
llm_pgdata:
|
|
YAML
|
|
|
|
# Create .env file
|
|
cat > "$llm_dir/.env" << ENV
|
|
HANZO_LLM_MASTER_KEY=${HANZO_LLM_MASTER_KEY:-$(openssl rand -hex 16)}
|
|
OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
|
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
|
ENV
|
|
|
|
log_info "LLM Gateway configuration created at $llm_dir"
|
|
log_info "Start with: cd $llm_dir && docker compose up -d"
|
|
log_success "LLM Gateway installed"
|
|
}
|
|
|
|
# Install Hanzo Chat
|
|
install_chat() {
|
|
if [[ "$SKIP_DOCKER" == true ]]; then
|
|
log_warn "Skipping Hanzo Chat (requires Docker)"
|
|
return 0
|
|
fi
|
|
|
|
log_step "Installing Hanzo Chat..."
|
|
|
|
local chat_dir="$HANZO_DATA/chat"
|
|
mkdir -p "$chat_dir"
|
|
|
|
# Create docker-compose.yml for Chat
|
|
cat > "$chat_dir/compose.yml" << 'YAML'
|
|
services:
|
|
api:
|
|
image: ghcr.io/danny-avila/librechat-dev:latest
|
|
container_name: hanzo-chat
|
|
ports:
|
|
- "3081:3081"
|
|
environment:
|
|
- HOST=0.0.0.0
|
|
- PORT=3081
|
|
- MONGO_URI=mongodb://mongodb:27017/HanzoChat
|
|
- MEILI_HOST=http://meilisearch:7700
|
|
- RAG_PORT=8000
|
|
- RAG_API_URL=http://rag_api:8000
|
|
volumes:
|
|
- ./images:/app/client/public/images
|
|
- ./uploads:/app/uploads
|
|
- ./logs:/app/api/logs
|
|
- ./.env:/app/.env
|
|
depends_on:
|
|
- mongodb
|
|
- meilisearch
|
|
restart: unless-stopped
|
|
|
|
mongodb:
|
|
image: mongo:7
|
|
container_name: hanzo-chat-mongodb
|
|
volumes:
|
|
- chat_mongo:/data/db
|
|
restart: unless-stopped
|
|
|
|
meilisearch:
|
|
image: getmeili/meilisearch:v1.12.3
|
|
container_name: hanzo-chat-meilisearch
|
|
environment:
|
|
- MEILI_NO_ANALYTICS=true
|
|
volumes:
|
|
- chat_meili:/meili_data
|
|
restart: unless-stopped
|
|
|
|
vectordb:
|
|
image: ankane/pgvector:latest
|
|
container_name: hanzo-chat-vectordb
|
|
environment:
|
|
- POSTGRES_DB=hanzo_chat
|
|
- POSTGRES_USER=hanzo
|
|
- POSTGRES_PASSWORD=hanzo
|
|
volumes:
|
|
- chat_pgvector:/var/lib/postgresql/data
|
|
restart: unless-stopped
|
|
|
|
rag_api:
|
|
image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest
|
|
container_name: hanzo-chat-rag
|
|
environment:
|
|
- DB_HOST=vectordb
|
|
- RAG_PORT=8000
|
|
depends_on:
|
|
- vectordb
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
chat_mongo:
|
|
chat_meili:
|
|
chat_pgvector:
|
|
YAML
|
|
|
|
# Create .env file
|
|
cat > "$chat_dir/.env" << ENV
|
|
OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
|
ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
|
ENV
|
|
|
|
log_info "Hanzo Chat configuration created at $chat_dir"
|
|
log_info "Start with: cd $chat_dir && docker compose up -d"
|
|
log_success "Hanzo Chat installed"
|
|
}
|
|
|
|
# Install Hanzo Python CLI
|
|
install_hanzo_cli() {
|
|
log_step "Installing Hanzo CLI (Python)..."
|
|
|
|
# Ensure Python and uv are available
|
|
install_python
|
|
|
|
if check_command uv; then
|
|
log_info "Installing via uv..."
|
|
uv tool install "hanzo[all]" --force 2>/dev/null && {
|
|
log_success "Hanzo CLI installed via uv"
|
|
return 0
|
|
}
|
|
# Fallback: uv pip
|
|
uv pip install --system "hanzo[all]" 2>/dev/null && {
|
|
log_success "Hanzo CLI installed via uv pip"
|
|
return 0
|
|
}
|
|
fi
|
|
|
|
if check_command pip3; then
|
|
log_info "Installing via pip3..."
|
|
pip3 install "hanzo[all]" 2>/dev/null && {
|
|
log_success "Hanzo CLI installed via pip3"
|
|
return 0
|
|
}
|
|
fi
|
|
|
|
log_warn "Failed to install Hanzo CLI - install manually: pip install hanzo[all]"
|
|
return 1
|
|
}
|
|
|
|
# Install Hanzod
|
|
install_hanzod() {
|
|
log_step "Installing Hanzod..."
|
|
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
# Build from source requires the codex-rs workspace
|
|
if [[ -d "/Users/z/work/hanzo/dev/codex-rs" ]]; then
|
|
log_info "Building Hanzod from source..."
|
|
cd /Users/z/work/hanzo/dev/codex-rs
|
|
cargo build --release
|
|
cp target/release/hanzod "$HANZO_BIN/" 2>/dev/null || true
|
|
else
|
|
log_warn "Hanzod source not found"
|
|
fi
|
|
else
|
|
log_info "Hanzod binary distribution not yet available"
|
|
log_info "Build from source with: ./hanzo.sh --dev --components hanzod"
|
|
fi
|
|
|
|
log_success "Hanzod setup complete"
|
|
}
|
|
|
|
# Setup shell integration
|
|
setup_shell() {
|
|
log_step "Setting up shell integration..."
|
|
|
|
local shell_config
|
|
case "$SHELL" in
|
|
*/zsh) shell_config="$HOME/.zshrc" ;;
|
|
*/bash) shell_config="$HOME/.bashrc" ;;
|
|
*) shell_config="$HOME/.profile" ;;
|
|
esac
|
|
|
|
# Check if already configured (look for the comment marker)
|
|
if grep -q "# Hanzo AI - Added by hanzo.sh" "$shell_config" 2>/dev/null; then
|
|
log_info "Shell already configured in $shell_config"
|
|
return 0
|
|
fi
|
|
|
|
# Also check if HANZO_HOME is set via any means
|
|
if grep -q "export HANZO_HOME=" "$shell_config" 2>/dev/null; then
|
|
log_info "HANZO_HOME already set in $shell_config"
|
|
return 0
|
|
fi
|
|
|
|
# Add to shell config
|
|
cat >> "$shell_config" << EOF
|
|
|
|
# Hanzo AI - Added by hanzo.sh installer
|
|
export HANZO_HOME="$HANZO_HOME"
|
|
export PATH="\$HANZO_HOME/bin:\$PATH"
|
|
EOF
|
|
|
|
log_success "Shell integration added to $shell_config"
|
|
log_info "Run: source $shell_config"
|
|
}
|
|
|
|
# Generate configuration
|
|
generate_config() {
|
|
log_step "Generating configuration..."
|
|
|
|
# Main Hanzo config
|
|
cat > "$HANZO_CONFIG/config.toml" << TOML
|
|
# Hanzo AI Configuration
|
|
# Generated by hanzo.sh installer
|
|
|
|
[general]
|
|
home = "$HANZO_HOME"
|
|
log_level = "info"
|
|
|
|
[dev]
|
|
# Dev CLI settings
|
|
model = "gpt-4.1"
|
|
model_provider = "openai"
|
|
approval_policy = "on-request"
|
|
model_reasoning_effort = "medium"
|
|
|
|
[llm]
|
|
# LLM Gateway settings
|
|
port = 4000
|
|
database_url = "postgresql://hanzo:hanzo@localhost:5432/llm"
|
|
|
|
[chat]
|
|
# Hanzo Chat settings
|
|
port = 3081
|
|
|
|
[mcp]
|
|
# MCP server settings
|
|
enabled = true
|
|
TOML
|
|
|
|
log_success "Configuration generated at $HANZO_CONFIG/config.toml"
|
|
}
|
|
|
|
# Print completion message
|
|
print_completion() {
|
|
# In quiet mode, just output success indicator
|
|
if [[ "$QUIET" == "true" ]]; then
|
|
echo "OK"
|
|
return 0
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD}Installation Complete!${NC}"
|
|
echo ""
|
|
echo "Installed components:"
|
|
for comp in ${COMPONENTS//,/ }; do
|
|
echo -e " ${GREEN}*${NC} $comp"
|
|
done
|
|
echo ""
|
|
echo -e "${BOLD}Quick Start:${NC}"
|
|
echo ""
|
|
echo " 1. Reload your shell:"
|
|
|
|
# Detect shell for accurate instructions
|
|
case "$SHELL" in
|
|
*/zsh) echo " source ~/.zshrc" ;;
|
|
*/bash) echo " source ~/.bashrc" ;;
|
|
*) echo " source ~/.profile # or restart your terminal" ;;
|
|
esac
|
|
|
|
echo ""
|
|
echo " 2. Set API keys (recommended):"
|
|
echo " export OPENAI_API_KEY=sk-..."
|
|
echo " export ANTHROPIC_API_KEY=sk-ant-..."
|
|
echo ""
|
|
echo " 3. Start coding:"
|
|
echo " dev"
|
|
echo ""
|
|
|
|
local step=4
|
|
if [[ "$COMPONENTS" == *"llm"* ]] && [[ "$SKIP_DOCKER" == false ]]; then
|
|
echo " $step. Start LLM Gateway:"
|
|
echo " cd $HANZO_DATA/llm && docker compose up -d"
|
|
echo " Access at http://localhost:4000"
|
|
echo ""
|
|
step=$((step + 1))
|
|
fi
|
|
|
|
if [[ "$COMPONENTS" == *"chat"* ]] && [[ "$SKIP_DOCKER" == false ]]; then
|
|
echo " $step. Start Hanzo Chat:"
|
|
echo " cd $HANZO_DATA/chat && docker compose up -d"
|
|
echo " Open http://localhost:3081"
|
|
echo ""
|
|
fi
|
|
|
|
echo -e "${BOLD}Documentation:${NC} https://hanzo.ai/docs"
|
|
echo -e "${BOLD}Support:${NC} https://github.com/hanzoai/dev/issues"
|
|
echo ""
|
|
}
|
|
|
|
# Parse command line arguments
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--dev)
|
|
MODE="dev"
|
|
shift
|
|
;;
|
|
--prod)
|
|
MODE="prod"
|
|
shift
|
|
;;
|
|
--components)
|
|
if [[ -z "${2:-}" ]]; then
|
|
log_error "--components requires a value"
|
|
exit 1
|
|
fi
|
|
COMPONENTS="$2"
|
|
shift 2
|
|
;;
|
|
--no-docker)
|
|
SKIP_DOCKER=true
|
|
shift
|
|
;;
|
|
--verbose)
|
|
VERBOSE=true
|
|
export VERBOSE
|
|
set -x
|
|
shift
|
|
;;
|
|
--quiet|-q)
|
|
QUIET=true
|
|
export QUIET
|
|
shift
|
|
;;
|
|
--force|-f)
|
|
FORCE=true
|
|
export FORCE
|
|
shift
|
|
;;
|
|
--uninstall)
|
|
UNINSTALL=true
|
|
shift
|
|
;;
|
|
--version|-V)
|
|
show_version
|
|
exit 0
|
|
;;
|
|
--help|-h)
|
|
show_help
|
|
exit 0
|
|
;;
|
|
-*)
|
|
log_error "Unknown option: $1"
|
|
log_info "Run './hanzo.sh --help' for usage information"
|
|
exit 1
|
|
;;
|
|
*)
|
|
# Allow positional arguments for curl | bash -s -- usage
|
|
log_warn "Ignoring unknown argument: $1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Handle 'all' components
|
|
if [[ "$COMPONENTS" == "all" ]]; then
|
|
COMPONENTS="dev,cli,llm,chat,mcp,hanzod"
|
|
fi
|
|
|
|
# Validate components
|
|
local valid_components="dev cli llm chat mcp hanzod"
|
|
for component in ${COMPONENTS//,/ }; do
|
|
# shellcheck disable=SC2076
|
|
if [[ ! " $valid_components " =~ " $component " ]]; then
|
|
log_error "Unknown component: $component"
|
|
log_info "Valid components: $valid_components"
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Verify installation
|
|
verify_installation() {
|
|
log_step "Verifying installation..."
|
|
|
|
local failed=false
|
|
|
|
# Check HANZO_BIN is in PATH or add it temporarily
|
|
export PATH="$HANZO_BIN:$PATH"
|
|
|
|
# Check dev CLI if installed
|
|
if [[ "$COMPONENTS" == *"dev"* ]]; then
|
|
if [[ -x "$HANZO_BIN/dev" ]]; then
|
|
log_success "dev CLI binary found at $HANZO_BIN/dev"
|
|
elif check_command dev; then
|
|
log_success "dev CLI found in PATH"
|
|
else
|
|
log_warn "dev CLI not found - may need shell restart"
|
|
failed=true
|
|
fi
|
|
fi
|
|
|
|
# Check Hanzo CLI if installed
|
|
if [[ "$COMPONENTS" == *"cli"* ]]; then
|
|
if check_command hanzo; then
|
|
log_success "Hanzo CLI found in PATH"
|
|
else
|
|
log_warn "Hanzo CLI not found - may need shell restart"
|
|
failed=true
|
|
fi
|
|
fi
|
|
|
|
# Check MCP if installed
|
|
if [[ "$COMPONENTS" == *"mcp"* ]]; then
|
|
if check_command hanzo-mcp 2>/dev/null || npm list -g @hanzo/mcp >/dev/null 2>&1; then
|
|
log_success "MCP tools installed"
|
|
else
|
|
log_warn "MCP tools may not be fully installed"
|
|
fi
|
|
fi
|
|
|
|
# Check Docker services
|
|
if [[ "$SKIP_DOCKER" == false ]]; then
|
|
if [[ "$COMPONENTS" == *"llm"* ]] && [[ -f "$HANZO_DATA/llm/compose.yml" ]]; then
|
|
log_success "LLM Gateway config ready at $HANZO_DATA/llm"
|
|
fi
|
|
if [[ "$COMPONENTS" == *"chat"* ]] && [[ -f "$HANZO_DATA/chat/compose.yml" ]]; then
|
|
log_success "Hanzo Chat config ready at $HANZO_DATA/chat"
|
|
fi
|
|
fi
|
|
|
|
# Check config
|
|
if [[ -f "$HANZO_CONFIG/config.toml" ]]; then
|
|
log_success "Configuration file created"
|
|
fi
|
|
|
|
if [[ "$failed" == "true" ]]; then
|
|
log_warn "Some verifications failed - installation may be incomplete"
|
|
return 1
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
|
|
# Main installation function
|
|
main() {
|
|
# Parse args first (before banner, to handle --quiet, --version, --help early)
|
|
parse_args "$@"
|
|
|
|
# Handle uninstall
|
|
if [[ "$UNINSTALL" == "true" ]]; then
|
|
uninstall_hanzo
|
|
exit 0
|
|
fi
|
|
|
|
print_banner
|
|
|
|
log_info "Installation mode: $MODE"
|
|
log_info "Components: $COMPONENTS"
|
|
log_info "Skip Docker: $SKIP_DOCKER"
|
|
[[ "$QUIET" != "true" ]] && echo ""
|
|
|
|
# Mark that cleanup may be needed
|
|
CLEANUP_NEEDED=true
|
|
|
|
# Detect platform
|
|
detect_platform
|
|
|
|
# Create directories
|
|
create_directories
|
|
|
|
# Install base dependencies
|
|
install_system_deps
|
|
install_nodejs
|
|
|
|
# Install Python if CLI component requested
|
|
if [[ "$COMPONENTS" == *"cli"* ]]; then
|
|
install_python
|
|
fi
|
|
|
|
# Install optional dependencies based on mode
|
|
if [[ "$MODE" == "dev" ]]; then
|
|
install_rust
|
|
fi
|
|
|
|
# Install Docker if needed
|
|
if [[ "$COMPONENTS" == *"llm"* ]] || [[ "$COMPONENTS" == *"chat"* ]]; then
|
|
install_docker
|
|
fi
|
|
|
|
# Install components
|
|
local install_failed=false
|
|
for component in ${COMPONENTS//,/ }; do
|
|
case "$component" in
|
|
dev)
|
|
install_dev || install_failed=true
|
|
;;
|
|
llm)
|
|
install_llm || install_failed=true
|
|
;;
|
|
chat)
|
|
install_chat || install_failed=true
|
|
;;
|
|
cli)
|
|
install_hanzo_cli || install_failed=true
|
|
;;
|
|
mcp)
|
|
install_mcp || install_failed=true
|
|
;;
|
|
hanzod)
|
|
install_hanzod || install_failed=true
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [[ "$install_failed" == "true" ]]; then
|
|
log_warn "Some components failed to install"
|
|
fi
|
|
|
|
# Setup shell and config
|
|
setup_shell
|
|
generate_config
|
|
|
|
# Verify installation
|
|
verify_installation || true
|
|
|
|
# Mark cleanup not needed (successful install)
|
|
CLEANUP_NEEDED=false
|
|
|
|
# Done
|
|
print_completion
|
|
}
|
|
|
|
# Run main function
|
|
main "$@"
|