#!/usr/bin/env bash # homeai-agent/setup.sh — OpenClaw agent, HTTP bridge, skills, reminder daemon # # Components: # - OpenClaw gateway — AI agent runtime (port 8080) # - OpenClaw HTTP bridge — HA ↔ OpenClaw translator (port 8081) # - 13 skills — home-assistant, image-generation, voice-assistant, # vtube-studio, memory, service-monitor, character, # routine, music, workflow, gitea, calendar, mode # - Reminder daemon — fires TTS when reminders are due # # Prerequisites: # - Ollama running (port 11434) # - Home Assistant reachable (HA_TOKEN set in .env) # - Wyoming TTS running (port 10301) # - homeai-voice-env venv exists (for bridge + reminder daemon) # - At least one character JSON in ~/homeai-data/characters/ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" source "${REPO_DIR}/scripts/common.sh" log_section "P4: Agent (OpenClaw + HTTP Bridge + Skills)" detect_platform # ─── Load environment ──────────────────────────────────────────────────────── ENV_FILE="${REPO_DIR}/.env" if [[ -f "$ENV_FILE" ]]; then log_info "Loading .env..." load_env "$ENV_FILE" else log_warn "No .env found at ${ENV_FILE} — API keys may be missing" fi # ─── Prerequisite checks ──────────────────────────────────────────────────── log_info "Checking prerequisites..." require_command node "brew install node" require_command openclaw "npm install -g openclaw" VOICE_ENV="${HOME}/homeai-voice-env" if [[ ! -d "$VOICE_ENV" ]]; then die "homeai-voice-env not found at $VOICE_ENV — run homeai-voice/setup.sh first" fi # Check key services (non-fatal) for check in "http://localhost:11434:Ollama" "http://localhost:10301:Wyoming-TTS"; do url="${check%%:*}"; name="${check##*:}" if curl -sf "$url" -o /dev/null 2>/dev/null; then log_success "$name reachable" else log_warn "$name not reachable at $url" fi done # Check required env vars MISSING_KEYS=() [[ -z "${HA_TOKEN:-}" ]] && MISSING_KEYS+=("HA_TOKEN") [[ -z "${ANTHROPIC_API_KEY:-}" ]] && MISSING_KEYS+=("ANTHROPIC_API_KEY") if [[ ${#MISSING_KEYS[@]} -gt 0 ]]; then log_warn "Missing env vars: ${MISSING_KEYS[*]} — set these in ${ENV_FILE}" fi # ─── Ensure data directories ───────────────────────────────────────────────── DATA_DIR="${HOME}/homeai-data" for dir in characters memories memories/personal conversations routines; do mkdir -p "${DATA_DIR}/${dir}" done log_success "Data directories verified" # ─── OpenClaw config ───────────────────────────────────────────────────────── OPENCLAW_DIR="${HOME}/.openclaw" OPENCLAW_CONFIG="${OPENCLAW_DIR}/openclaw.json" if [[ ! -f "$OPENCLAW_CONFIG" ]]; then die "OpenClaw config not found at $OPENCLAW_CONFIG — run: openclaw doctor --fix" fi log_success "OpenClaw config exists at $OPENCLAW_CONFIG" # Verify Anthropic provider is configured if ! grep -q '"anthropic"' "$OPENCLAW_CONFIG" 2>/dev/null; then log_warn "Anthropic provider not found in openclaw.json — add it for Claude support" fi # ─── Install skills ────────────────────────────────────────────────────────── SKILLS_SRC="${SCRIPT_DIR}/skills" SKILLS_DEST="${OPENCLAW_DIR}/skills" if [[ -d "$SKILLS_SRC" ]]; then log_info "Syncing skills..." mkdir -p "$SKILLS_DEST" for skill_dir in "$SKILLS_SRC"/*/; do skill_name="$(basename "$skill_dir")" dest="${SKILLS_DEST}/${skill_name}" if [[ -L "$dest" ]]; then log_info " ${skill_name} (symlinked)" elif [[ -d "$dest" ]]; then # Replace copy with symlink rm -rf "$dest" ln -s "$skill_dir" "$dest" log_step "${skill_name} → symlinked" else ln -s "$skill_dir" "$dest" log_step "${skill_name} → installed" fi done log_success "Skills synced ($(ls -d "$SKILLS_DEST"/*/ 2>/dev/null | wc -l | tr -d ' ') total)" else log_warn "No skills directory at $SKILLS_SRC" fi # ─── Install launchd services (macOS) ──────────────────────────────────────── if [[ "$OS_TYPE" == "macos" ]]; then log_info "Installing launchd agents..." LAUNCHD_DIR="${SCRIPT_DIR}/launchd" AGENTS_DIR="${HOME}/Library/LaunchAgents" mkdir -p "$AGENTS_DIR" # Inject API keys into plists that need them _inject_plist_key() { local plist="$1" key="$2" value="$3" if [[ -n "$value" ]] && grep -q "${key}" "$plist" 2>/dev/null; then # Use python for reliable XML-safe replacement python3 -c " import sys, re with open('$plist') as f: content = f.read() pattern = r'(${key}\s*)[^<]*()' content = re.sub(pattern, r'\g<1>${value}\g<2>', content) with open('$plist', 'w') as f: f.write(content) " fi } # Update API keys in plist source files before linking OPENCLAW_PLIST="${LAUNCHD_DIR}/com.homeai.openclaw.plist" BRIDGE_PLIST="${LAUNCHD_DIR}/com.homeai.openclaw-bridge.plist" if [[ -f "$OPENCLAW_PLIST" ]]; then _inject_plist_key "$OPENCLAW_PLIST" "ANTHROPIC_API_KEY" "${ANTHROPIC_API_KEY:-}" _inject_plist_key "$OPENCLAW_PLIST" "OPENAI_API_KEY" "${OPENAI_API_KEY:-}" _inject_plist_key "$OPENCLAW_PLIST" "HA_TOKEN" "${HA_TOKEN:-}" _inject_plist_key "$OPENCLAW_PLIST" "HASS_TOKEN" "${HA_TOKEN:-}" _inject_plist_key "$OPENCLAW_PLIST" "GITEA_TOKEN" "${GITEA_TOKEN:-}" _inject_plist_key "$OPENCLAW_PLIST" "N8N_API_KEY" "${N8N_API_KEY:-}" fi if [[ -f "$BRIDGE_PLIST" ]]; then _inject_plist_key "$BRIDGE_PLIST" "ANTHROPIC_API_KEY" "${ANTHROPIC_API_KEY:-}" _inject_plist_key "$BRIDGE_PLIST" "ELEVENLABS_API_KEY" "${ELEVENLABS_API_KEY:-}" fi # Symlink and load each plist for plist in "$LAUNCHD_DIR"/*.plist; do [[ ! -f "$plist" ]] && continue plist_name="$(basename "$plist")" plist_label="${plist_name%.plist}" dest="${AGENTS_DIR}/${plist_name}" # Unload if already running launchctl bootout "gui/$(id -u)/${plist_label}" 2>/dev/null || true # Symlink source → LaunchAgents ln -sf "$(cd "$(dirname "$plist")" && pwd)/${plist_name}" "$dest" # Load launchctl bootstrap "gui/$(id -u)" "$dest" 2>/dev/null && \ log_success " ${plist_label} → loaded" || \ log_warn " ${plist_label} → failed to load (check: launchctl print gui/$(id -u)/${plist_label})" done fi # ─── Smoke test ────────────────────────────────────────────────────────────── log_info "Running smoke tests..." sleep 2 # Give services a moment to start # Check gateway if curl -sf "http://localhost:8080" -o /dev/null 2>/dev/null; then log_success "OpenClaw gateway responding on :8080" else log_warn "OpenClaw gateway not responding on :8080 — check: tail /tmp/homeai-openclaw.log" fi # Check bridge if curl -sf "http://localhost:8081/status" -o /dev/null 2>/dev/null; then log_success "HTTP bridge responding on :8081" else log_warn "HTTP bridge not responding on :8081 — check: tail /tmp/homeai-openclaw-bridge.log" fi # ─── Summary ───────────────────────────────────────────────────────────────── print_summary "Agent Setup Complete" \ "OpenClaw gateway" "http://localhost:8080" \ "HTTP bridge" "http://localhost:8081" \ "OpenClaw config" "$OPENCLAW_CONFIG" \ "Skills directory" "$SKILLS_DEST" \ "Character data" "${DATA_DIR}/characters/" \ "Memory data" "${DATA_DIR}/memories/" \ "Reminder data" "${DATA_DIR}/reminders.json" \ "Gateway log" "/tmp/homeai-openclaw.log" \ "Bridge log" "/tmp/homeai-openclaw-bridge.log" cat <<'EOF' To reload a service after editing its plist: launchctl bootout gui/$(id -u)/com.homeai. launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.homeai..plist To test the agent: curl -X POST http://localhost:8081/api/agent/message \ -H 'Content-Type: application/json' \ -d '{"message":"say hello","agent":"main"}' EOF