#!/bin/bash set -e SKILL_DIR="$(dirname "$(dirname "$0")")" CLAUDE_PORT="${CLAUDE_PORT:-8888}" CLAUDE_MODEL="${CLAUDE_MODEL:-claude-sonnet-4-20250514}" CLAUDE_MAX_TURNS="${CLAUDE_MAX_TURNS:-10}" SKILLS_DIR="${SKILLS_DIR:-/skills}" SYSTEM_PROMPT_FILE="$SKILL_DIR/prompts/system.md" # Create HTTP handler create_handler() { cat > /tmp/claude_handler.sh << 'HANDLER' #!/bin/bash SKILL_DIR="${SKILL_DIR}" CLAUDE_MODEL="${CLAUDE_MODEL:-claude-sonnet-4-20250514}" CLAUDE_MAX_TURNS="${CLAUDE_MAX_TURNS:-10}" SKILLS_DIR="${SKILLS_DIR:-/skills}" SYSTEM_PROMPT_FILE="${SYSTEM_PROMPT_FILE}" # Read HTTP request read -r request_line method=$(echo "$request_line" | cut -d' ' -f1) path=$(echo "$request_line" | cut -d' ' -f2) # Read headers content_length=0 while read -r header; do header=$(echo "$header" | tr -d '\r') [ -z "$header" ] && break if [[ "$header" =~ ^[Cc]ontent-[Ll]ength:\ *([0-9]+) ]]; then content_length="${BASH_REMATCH[1]}" fi done # Read body body="" if [ "$content_length" -gt 0 ]; then body=$(head -c "$content_length") fi # Response helper 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" } # Health check if [ "$path" = "/health" ]; then if command -v claude &>/dev/null; then send_response "200 OK" "application/json" '{"status":"ok","claude":"available"}' else send_response "503 Service Unavailable" "application/json" '{"status":"error","claude":"not found"}' fi exit 0 fi # Execute endpoint (one-shot) if [ "$path" = "/execute" ] && [ "$method" = "POST" ]; then prompt=$(echo "$body" | jq -r '.prompt // empty') if [ -z "$prompt" ]; then send_response "400 Bad Request" "application/json" '{"error":"Missing prompt field"}' exit 0 fi # Build claude command cd "$SKILLS_DIR" # Run claude with system prompt result=$(claude --print \ --model "$CLAUDE_MODEL" \ --max-turns "$CLAUDE_MAX_TURNS" \ --system-prompt "$(cat "$SYSTEM_PROMPT_FILE")" \ --allowedTools "Bash,Read,Write,Edit,Glob,Grep" \ "$prompt" 2>&1) || { error_msg=$(echo "$result" | jq -Rs '.') send_response "500 Internal Server Error" "application/json" "{\"success\":false,\"error\":$error_msg}" exit 0 } # Return result result_json=$(echo "$result" | jq -Rs '.') send_response "200 OK" "application/json" "{\"success\":true,\"result\":$result_json}" exit 0 fi # Chat endpoint (simplified, non-streaming for now) if [ "$path" = "/chat" ] && [ "$method" = "POST" ]; then message=$(echo "$body" | jq -r '.message // empty') session_id=$(echo "$body" | jq -r '.session_id // empty') if [ -z "$message" ]; then send_response "400 Bad Request" "application/json" '{"error":"Missing message field"}' exit 0 fi cd "$SKILLS_DIR" # Build command claude_args=( "--print" "--model" "$CLAUDE_MODEL" "--max-turns" "1" "--system-prompt" "$(cat "$SYSTEM_PROMPT_FILE")" "--allowedTools" "Bash,Read,Write,Edit,Glob,Grep" ) # Resume session if provided if [ -n "$session_id" ]; then claude_args+=("--resume" "$session_id") fi result=$(claude "${claude_args[@]}" "$message" 2>&1) || { error_msg=$(echo "$result" | jq -Rs '.') send_response "500 Internal Server Error" "application/json" "{\"success\":false,\"error\":$error_msg}" exit 0 } result_json=$(echo "$result" | jq -Rs '.') send_response "200 OK" "application/json" "{\"success\":true,\"response\":$result_json}" exit 0 fi # Skills list if [ "$path" = "/skills" ] && [ "$method" = "GET" ]; then skills="[]" for skill_dir in "$SKILLS_DIR"/*/; do skill_name=$(basename "$skill_dir") skill_md="$skill_dir/SKILL.md" if [ -f "$skill_md" ]; then desc=$(yq -r '.description // "No description"' < <(sed -n '/^---$/,/^---$/p' "$skill_md" | sed '1d;$d') 2>/dev/null || echo "") skills=$(echo "$skills" | jq --arg name "$skill_name" --arg desc "$desc" '. + [{"name":$name,"description":$desc}]') fi done send_response "200 OK" "application/json" "$skills" exit 0 fi # Not found send_response "404 Not Found" "application/json" '{"error":"Endpoints: POST /execute, POST /chat, GET /skills, GET /health"}' HANDLER chmod +x /tmp/claude_handler.sh # Export vars for handler export SKILL_DIR export CLAUDE_MODEL export CLAUDE_MAX_TURNS export SKILLS_DIR export SYSTEM_PROMPT_FILE } # Serve HTTP API serve_api() { echo "Starting Claude HTTP API on port $CLAUDE_PORT..." echo "Model: $CLAUDE_MODEL" echo "Max turns: $CLAUDE_MAX_TURNS" echo "Working directory: $SKILLS_DIR" exec socat TCP-LISTEN:$CLAUDE_PORT,reuseaddr,fork EXEC:"/tmp/claude_handler.sh" } create_handler serve_api