#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" PLUGINS_DIR="$PROJECT_ROOT/plugins" ENV_FILE="$PROJECT_ROOT/.env" # ── Helpers ────────────────────────────────────────────────────────────────── RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[0;33m' CYAN='\033[0;36m'; NC='\033[0m' log() { printf "${CYAN}[setup]${NC} %s\n" "$*"; } ok() { printf "${GREEN} ✓${NC} %s\n" "$*"; } warn() { printf "${YELLOW} ⚠${NC} %s\n" "$*"; } err() { printf "${RED} ✗${NC} %s\n" "$*"; } usage() { cat < Path to .env file (default: /.env) --list List available plugins and exit --uninstall Uninstall plugins instead of installing --skip-deps Skip npm install step -h, --help Show this help Plugins: PaddedCell, ContractorAgent, Dirigent, HarborForge.OpenclawPlugin, Yonexus.Server Sensitive configuration is read from .env (see .env.example for reference). EOF exit 0 } # ── Parse args ─────────────────────────────────────────────────────────────── UNINSTALL=false SKIP_DEPS=false SELECTED_PLUGINS=() while [[ $# -gt 0 ]]; do case "$1" in --env) ENV_FILE="$2"; shift 2 ;; --list) ls "$PLUGINS_DIR"; exit 0 ;; --uninstall) UNINSTALL=true; shift ;; --skip-deps) SKIP_DEPS=true; shift ;; -h|--help) usage ;; -*) err "Unknown option: $1"; usage ;; *) SELECTED_PLUGINS+=("$1"); shift ;; esac done # ── Load .env ──────────────────────────────────────────────────────────────── if [[ -f "$ENV_FILE" ]]; then log "Loading env from $ENV_FILE" set -a # shellcheck disable=SC1090 source "$ENV_FILE" set +a ok "Loaded $(grep -cE '^[A-Z_]+=' "$ENV_FILE") variables" else warn "No .env file found at $ENV_FILE — proceeding with current environment" fi # ── Submodules ─────────────────────────────────────────────────────────────── log "Ensuring git submodules are initialised" cd "$PROJECT_ROOT" git submodule update --init --recursive ok "Submodules ready" # ── Resolve plugin list ───────────────────────────────────────────────────── ALL_PLUGINS=() for d in "$PLUGINS_DIR"/*/; do ALL_PLUGINS+=("$(basename "$d")") done if [[ ${#SELECTED_PLUGINS[@]} -eq 0 ]]; then SELECTED_PLUGINS=("${ALL_PLUGINS[@]}") fi for p in "${SELECTED_PLUGINS[@]}"; do if [[ ! -d "$PLUGINS_DIR/$p" ]]; then err "Plugin not found: $p" exit 1 fi done # ── Per-plugin install/uninstall ───────────────────────────────────────────── install_deps() { local dir="$1" if [[ "$SKIP_DEPS" == true ]]; then return; fi if [[ -f "$dir/package.json" ]]; then log " npm install (root)" (cd "$dir" && npm install --no-audit --no-fund 2>&1 | tail -3) fi if [[ -f "$dir/plugin/package.json" ]]; then log " npm install (plugin/)" (cd "$dir/plugin" && npm install --no-audit --no-fund 2>&1 | tail -3) fi } find_install_script() { local dir="$1" for candidate in \ "$dir/scripts/install.mjs" \ "$dir/install.mjs" \ "$dir/scripts/install.js" \ "$dir/install.js"; do if [[ -f "$candidate" ]]; then echo "$candidate" return fi done } run_plugin() { local name="$1" local dir="$PLUGINS_DIR/$name" local action="install" [[ "$UNINSTALL" == true ]] && action="uninstall" echo log "[$action] $name" if [[ "$UNINSTALL" == true ]]; then local script script=$(find_install_script "$dir") if [[ -n "$script" ]]; then node "$script" --uninstall || warn "$name uninstall script returned non-zero" else warn "No install script found for $name — skipping" fi return fi # Install dependencies install_deps "$dir" # Plugin-specific env setup (pass through to install scripts via env) case "$name" in HarborForge.OpenclawPlugin) export HF_BACKEND_URL="${HF_BACKEND_URL:-}" export HF_API_KEY="${HF_API_KEY:-}" export HF_MONITOR_IDENTIFIER="${HF_MONITOR_IDENTIFIER:-}" ;; Yonexus.Server) export YONEXUS_PORT="${YONEXUS_PORT:-}" export YONEXUS_SECRET="${YONEXUS_SECRET:-}" ;; ContractorAgent) export CONTRACTOR_BRIDGE_PORT="${CONTRACTOR_BRIDGE_PORT:-18800}" export CONTRACTOR_BRIDGE_API_KEY="${CONTRACTOR_BRIDGE_API_KEY:-}" ;; esac # Run install script local script script=$(find_install_script "$dir") if [[ -n "$script" ]]; then log " Running $(basename "$script")" node "$script" --install || { err "$name install script failed" return 1 } ok "$name installed" else # Some plugins (like PaddedCell) may have a different layout if [[ -f "$dir/package.json" ]] && grep -q '"install-plugin"' "$dir/package.json" 2>/dev/null; then log " Running npm run install-plugin" (cd "$dir" && npm run install-plugin) || { err "$name install failed" return 1 } ok "$name installed" elif [[ -f "$dir/package.json" ]] && grep -q '"postinstall"' "$dir/package.json" 2>/dev/null; then log " Running npm run postinstall" (cd "$dir" && npm run postinstall) || { err "$name install failed" return 1 } ok "$name installed" else warn "No install script found for $name — submodule present but not auto-installable" fi fi } # ── Main ───────────────────────────────────────────────────────────────────── FAILURES=0 for plugin in "${SELECTED_PLUGINS[@]}"; do run_plugin "$plugin" || ((FAILURES++)) || true done echo if [[ $FAILURES -gt 0 ]]; then err "$FAILURES plugin(s) had errors" exit 1 else ok "All plugins processed successfully" fi