diff --git a/openclaw/install.sh b/openclaw/install.sh new file mode 100755 index 00000000..e2013be8 --- /dev/null +++ b/openclaw/install.sh @@ -0,0 +1,296 @@ +#!/usr/bin/env bash +set -euo pipefail + +# claude-mem OpenClaw Plugin Installer +# Installs the claude-mem persistent memory plugin for OpenClaw gateways. +# Usage: bash install.sh [--non-interactive] + +############################################################################### +# Constants +############################################################################### + +readonly MIN_BUN_VERSION="1.1.14" +readonly INSTALLER_VERSION="1.0.0" +readonly NON_INTERACTIVE="${1:-}" + +############################################################################### +# Color utilities — auto-detect terminal color support +############################################################################### + +if [[ -t 1 ]] && [[ "${TERM:-}" != "dumb" ]]; then + readonly COLOR_RED='\033[0;31m' + readonly COLOR_GREEN='\033[0;32m' + readonly COLOR_YELLOW='\033[0;33m' + readonly COLOR_BLUE='\033[0;34m' + readonly COLOR_MAGENTA='\033[0;35m' + readonly COLOR_CYAN='\033[0;36m' + readonly COLOR_BOLD='\033[1m' + readonly COLOR_RESET='\033[0m' +else + readonly COLOR_RED='' + readonly COLOR_GREEN='' + readonly COLOR_YELLOW='' + readonly COLOR_BLUE='' + readonly COLOR_MAGENTA='' + readonly COLOR_CYAN='' + readonly COLOR_BOLD='' + readonly COLOR_RESET='' +fi + +info() { echo -e "${COLOR_BLUE}ℹ${COLOR_RESET} $*"; } +success() { echo -e "${COLOR_GREEN}✓${COLOR_RESET} $*"; } +warn() { echo -e "${COLOR_YELLOW}⚠${COLOR_RESET} $*"; } +error() { echo -e "${COLOR_RED}✗${COLOR_RESET} $*" >&2; } + +prompt_user() { + if [[ "$NON_INTERACTIVE" == "--non-interactive" ]]; then + error "Cannot prompt in non-interactive mode: $*" + return 1 + fi + if [[ ! -t 0 ]]; then + error "Cannot prompt when stdin is not a terminal: $*" + return 1 + fi + echo -en "${COLOR_CYAN}?${COLOR_RESET} $* " +} + +############################################################################### +# Banner +############################################################################### + +print_banner() { + echo -e "${COLOR_MAGENTA}${COLOR_BOLD}" + cat << 'BANNER' + ┌─────────────────────────────────────────┐ + │ claude-mem × OpenClaw │ + │ Persistent Memory Plugin Installer │ + └─────────────────────────────────────────┘ +BANNER + echo -e "${COLOR_RESET}" + info "Installer v${INSTALLER_VERSION}" + echo "" +} + +############################################################################### +# Platform detection +############################################################################### + +PLATFORM="" +IS_WSL=false + +detect_platform() { + local uname_out + uname_out="$(uname -s)" + + case "${uname_out}" in + Darwin*) + PLATFORM="macos" + ;; + Linux*) + if grep -qi microsoft /proc/version 2>/dev/null; then + PLATFORM="linux" + IS_WSL=true + else + PLATFORM="linux" + fi + ;; + MINGW*|MSYS*|CYGWIN*) + PLATFORM="windows" + ;; + *) + error "Unsupported platform: ${uname_out}" + exit 1 + ;; + esac + + info "Detected platform: ${PLATFORM}${IS_WSL:+ (WSL)}" +} + +############################################################################### +# Version comparison — returns 0 if $1 >= $2 +############################################################################### + +version_gte() { + local v1="$1" v2="$2" + local -a parts1 parts2 + IFS='.' read -ra parts1 <<< "$v1" + IFS='.' read -ra parts2 <<< "$v2" + + for i in 0 1 2; do + local p1="${parts1[$i]:-0}" + local p2="${parts2[$i]:-0}" + if (( p1 > p2 )); then return 0; fi + if (( p1 < p2 )); then return 1; fi + done + return 0 +} + +############################################################################### +# Bun detection and installation +# Translated from plugin/scripts/smart-install.js patterns +############################################################################### + +BUN_PATH="" + +find_bun_path() { + # Try PATH first + if command -v bun &>/dev/null; then + BUN_PATH="$(command -v bun)" + return 0 + fi + + # Check common installation paths (handles fresh installs before PATH reload) + local -a bun_paths=( + "${HOME}/.bun/bin/bun" + "/usr/local/bin/bun" + "/opt/homebrew/bin/bun" + ) + + for candidate in "${bun_paths[@]}"; do + if [[ -x "$candidate" ]]; then + BUN_PATH="$candidate" + return 0 + fi + done + + BUN_PATH="" + return 1 +} + +check_bun() { + if ! find_bun_path; then + return 1 + fi + + # Verify minimum version + local bun_version + bun_version="$("$BUN_PATH" --version 2>/dev/null)" || return 1 + + if version_gte "$bun_version" "$MIN_BUN_VERSION"; then + success "Bun ${bun_version} found at ${BUN_PATH}" + return 0 + else + warn "Bun ${bun_version} is below minimum required version ${MIN_BUN_VERSION}" + return 1 + fi +} + +install_bun() { + info "Installing Bun runtime..." + + if ! curl -fsSL https://bun.sh/install | bash; then + error "Failed to install Bun automatically" + error "Please install manually:" + error " curl -fsSL https://bun.sh/install | bash" + error " Or: brew install oven-sh/bun/bun (macOS)" + error "Then restart your terminal and re-run this installer." + exit 1 + fi + + # Re-detect after install (installer may have placed it in ~/.bun/bin) + if ! find_bun_path; then + error "Bun installation completed but binary not found in expected locations" + error "Please restart your terminal and re-run this installer." + exit 1 + fi + + local bun_version + bun_version="$("$BUN_PATH" --version 2>/dev/null)" || true + success "Bun ${bun_version} installed at ${BUN_PATH}" +} + +############################################################################### +# uv detection and installation +# Translated from plugin/scripts/smart-install.js patterns +############################################################################### + +UV_PATH="" + +find_uv_path() { + # Try PATH first + if command -v uv &>/dev/null; then + UV_PATH="$(command -v uv)" + return 0 + fi + + # Check common installation paths (handles fresh installs before PATH reload) + local -a uv_paths=( + "${HOME}/.local/bin/uv" + "${HOME}/.cargo/bin/uv" + "/usr/local/bin/uv" + "/opt/homebrew/bin/uv" + ) + + for candidate in "${uv_paths[@]}"; do + if [[ -x "$candidate" ]]; then + UV_PATH="$candidate" + return 0 + fi + done + + UV_PATH="" + return 1 +} + +check_uv() { + if ! find_uv_path; then + return 1 + fi + + local uv_version + uv_version="$("$UV_PATH" --version 2>/dev/null)" || return 1 + success "uv ${uv_version} found at ${UV_PATH}" + return 0 +} + +install_uv() { + info "Installing uv (Python package manager for Chroma support)..." + + if ! curl -LsSf https://astral.sh/uv/install.sh | sh; then + error "Failed to install uv automatically" + error "Please install manually:" + error " curl -LsSf https://astral.sh/uv/install.sh | sh" + error " Or: brew install uv (macOS)" + error "Then restart your terminal and re-run this installer." + exit 1 + fi + + # Re-detect after install + if ! find_uv_path; then + error "uv installation completed but binary not found in expected locations" + error "Please restart your terminal and re-run this installer." + exit 1 + fi + + local uv_version + uv_version="$("$UV_PATH" --version 2>/dev/null)" || true + success "uv ${uv_version} installed at ${UV_PATH}" +} + +############################################################################### +# Main +############################################################################### + +main() { + print_banner + detect_platform + + # --- Step 1: Bun --- + echo "" + info "Checking dependencies..." + echo "" + + if ! check_bun; then + install_bun + fi + + # --- Step 2: uv --- + if ! check_uv; then + install_uv + fi + + echo "" + success "All dependencies satisfied" +} + +main "$@"