#!/bin/bash set -e HOMEPAGE_PORT="${HOMEPAGE_PORT:-3000}" HOMEPAGE_TITLE="${HOMEPAGE_TITLE:-VibeStack}" SKILLS_DIR="${SKILLS_DIR:-/skills}" # Known ports for skills declare -A KNOWN_PORTS=( ["caddy"]="80" ["ttyd"]="7681" ["metrics"]="9090" ["loki"]="3100" ["duckdb"]="8432" ["claude"]="8888" ["openclaw"]="18789" ["supervisor"]="9001" ["homepage"]="3000" ) declare -A HEALTH_PORTS=( ["caddy"]="2019" ["ttyd"]="7681" ["metrics"]="9090" ["loki"]="3100" ["duckdb"]="8432" ["claude"]="8888" ["openclaw"]="18789" ["supervisor"]="9001" ) # Create handler script create_handler() { cat > /tmp/homepage_handler.sh << 'HANDLER' #!/bin/bash HOMEPAGE_TITLE="${HOMEPAGE_TITLE:-VibeStack}" SKILLS_DIR="${SKILLS_DIR:-/skills}" # Read request read -r request_line path=$(echo "$request_line" | cut -d' ' -f2) while read -r header; do header=$(echo "$header" | tr -d '\r') [ -z "$header" ] && break done send_response() { local status="$1" local content_type="$2" local body="$3" local body_length=${#body} printf "HTTP/1.1 %s\r\n" "$status" printf "Content-Type: %s\r\n" "$content_type" printf "Content-Length: %d\r\n" "$body_length" printf "Connection: close\r\n" printf "\r\n" printf "%s" "$body" } # Get skill info get_skills_json() { local skills="[]" for skill_dir in "$SKILLS_DIR"/*/; do local name=$(basename "$skill_dir") local skill_md="$skill_dir/SKILL.md" [ ! -f "$skill_md" ] && continue # Parse SKILL.md local frontmatter=$(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d') local desc=$(echo "$frontmatter" | yq -r '.description // "No description"' 2>/dev/null || echo "") local metrics_port=$(echo "$frontmatter" | yq -r '.metadata.vibestack."metrics-port" // empty' 2>/dev/null || echo "") local is_main=$(echo "$frontmatter" | yq -r '.metadata.vibestack.main // false' 2>/dev/null || echo "false") # Get port local port="" case "$name" in caddy) port="80" ;; ttyd) port="7681" ;; metrics) port="9090" ;; loki) port="3100" ;; duckdb) port="8432" ;; claude) port="8888" ;; openclaw) port="18789" ;; supervisor) port="9001" ;; homepage) port="3000" ;; esac # Check health local health_port="$port" [ "$name" = "caddy" ] && health_port="2019" local status="unknown" if [ -n "$health_port" ]; then if curl -sf --max-time 1 "http://localhost:$health_port/health" >/dev/null 2>&1 || \ curl -sf --max-time 1 "http://localhost:$health_port/" >/dev/null 2>&1; then status="up" else status="down" fi fi skills=$(echo "$skills" | jq \ --arg name "$name" \ --arg desc "$desc" \ --arg port "$port" \ --arg status "$status" \ --arg main "$is_main" \ --arg metrics "$metrics_port" \ '. + [{name:$name, description:$desc, port:$port, status:$status, main:($main=="true"), metrics_port:$metrics}]') done echo "$skills" } # Generate HTML generate_html() { local skills=$(get_skills_json) cat << HTML $HOMEPAGE_TITLE

$HOMEPAGE_TITLE

HTML } # Route requests case "$path" in /health) send_response "200 OK" "application/json" '{"status":"ok"}' ;; /api/skills) skills=$(get_skills_json) send_response "200 OK" "application/json" "$skills" ;; *) html=$(generate_html) send_response "200 OK" "text/html; charset=utf-8" "$html" ;; esac HANDLER chmod +x /tmp/homepage_handler.sh export HOMEPAGE_TITLE export SKILLS_DIR } # Serve serve() { echo "Starting Homepage on port $HOMEPAGE_PORT..." exec socat TCP-LISTEN:$HOMEPAGE_PORT,reuseaddr,fork EXEC:"/tmp/homepage_handler.sh" } create_handler serve