commit d8034a7dd07e5202ffcd6c69da63b61b303a220a Author: Azat Date: Tue Feb 3 00:18:55 2026 +0100 Skill downloader - bash HTTP server using socat diff --git a/SKILL.md b/SKILL.md new file mode 100644 index 0000000..1d290d4 --- /dev/null +++ b/SKILL.md @@ -0,0 +1,89 @@ +--- +name: skill-downloader +description: Download and install skills from git.vibe-overflow.com +metadata: + version: "1.0.0" + vibestack: + main: false +--- + +# Skill Downloader + +Downloads and installs skills from the VibeStack registry at `git.vibe-overflow.com/azat/`. + +## Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `DOWNLOADER_PORT` | `8083` | API port | +| `SKILLS_DIR` | `/skills` | Installation directory | +| `GIT_HOST` | `git.vibe-overflow.com` | Git server | +| `GIT_USER` | `azat` | Git user/org | +| `GIT_TOKEN` | (required) | Auth token | + +## API + +### List Available Skills + +```bash +curl http://localhost:8083/available +``` + +Returns all repos from `git.vibe-overflow.com/azat/`: +```json +{ + "skills": [ + {"name": "redis", "description": "Redis cache server"}, + {"name": "postgres", "description": "PostgreSQL database"}, + {"name": "backup", "description": "Backup and restore"} + ] +} +``` + +### List Installed Skills + +```bash +curl http://localhost:8083/installed +``` + +### Install Skill + +```bash +curl -X POST http://localhost:8083/install/redis +``` + +### Update Skill + +```bash +curl -X POST http://localhost:8083/update/redis +``` + +### Remove Skill + +```bash +curl -X DELETE http://localhost:8083/remove/redis +``` + +### Health + +```bash +curl http://localhost:8083/health +``` + +## Installation Process + +``` +POST /install/redis + │ + ▼ +git clone https://git.vibe-overflow.com/azat/redis /skills/redis + │ + ▼ +bash /skills/redis/scripts/autorun.sh + │ + ▼ +supervisorctl reread && update + │ + ▼ +{"success": true, "name": "redis"} +``` diff --git a/scripts/autorun.sh b/scripts/autorun.sh new file mode 100644 index 0000000..af7b81d --- /dev/null +++ b/scripts/autorun.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -e + +# Install dependencies +install_deps() { + local missing="" + + command -v git &>/dev/null || missing="$missing git" + command -v curl &>/dev/null || missing="$missing curl" + command -v jq &>/dev/null || missing="$missing jq" + command -v socat &>/dev/null || missing="$missing socat" + + if [ -n "$missing" ]; then + echo "Installing:$missing" + apt-get update + apt-get install -y $missing + fi + + echo "Dependencies ready" +} + +install_deps + +echo "Skill downloader setup complete" diff --git a/scripts/run.sh b/scripts/run.sh new file mode 100644 index 0000000..b96409e --- /dev/null +++ b/scripts/run.sh @@ -0,0 +1,222 @@ +#!/bin/bash +set -e + +SKILL_DIR="$(dirname "$(dirname "$0")")" +DOWNLOADER_PORT="${DOWNLOADER_PORT:-8083}" +SKILLS_DIR="${SKILLS_DIR:-/skills}" +GIT_HOST="${GIT_HOST:-git.vibe-overflow.com}" +GIT_USER="${GIT_USER:-azat}" +GIT_TOKEN="${GIT_TOKEN:-}" + +# Create HTTP handler +create_handler() { + cat > /tmp/downloader_handler.sh << 'HANDLER' +#!/bin/bash + +SKILLS_DIR="${SKILLS_DIR:-/skills}" +GIT_HOST="${GIT_HOST:-git.vibe-overflow.com}" +GIT_USER="${GIT_USER:-azat}" +GIT_TOKEN="${GIT_TOKEN:-}" + +# 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 if present +body="" +if [ "$content_length" -gt 0 ]; then + body=$(head -c "$content_length") +fi + +# Response helper +send_response() { + local status="$1" + local body="$2" + local body_length=${#body} + + printf "HTTP/1.1 %s\r\n" "$status" + printf "Content-Type: application/json\r\n" + printf "Content-Length: %d\r\n" "$body_length" + printf "Access-Control-Allow-Origin: *\r\n" + printf "Connection: close\r\n" + printf "\r\n" + printf "%s" "$body" +} + +# Git URL with token +git_url() { + local repo="$1" + if [ -n "$GIT_TOKEN" ]; then + echo "https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_USER}/${repo}.git" + else + echo "https://${GIT_HOST}/${GIT_USER}/${repo}.git" + fi +} + +# Health check +if [ "$path" = "/health" ]; then + send_response "200 OK" '{"status":"ok"}' + exit 0 +fi + +# List available skills from Gitea API +if [ "$path" = "/available" ] && [ "$method" = "GET" ]; then + api_url="https://${GIT_HOST}/api/v1/users/${GIT_USER}/repos" + + if [ -n "$GIT_TOKEN" ]; then + repos=$(curl -s -H "Authorization: token $GIT_TOKEN" "$api_url") + else + repos=$(curl -s "$api_url") + fi + + # Transform to our format + skills=$(echo "$repos" | jq '[.[] | {name: .name, description: .description, url: .html_url}]') + send_response "200 OK" "{\"skills\":$skills}" + exit 0 +fi + +# List installed skills +if [ "$path" = "/installed" ] && [ "$method" = "GET" ]; then + skills="[]" + for skill_dir in "$SKILLS_DIR"/*/; do + [ -d "$skill_dir" ] || continue + skill_name=$(basename "$skill_dir") + [ "$skill_name" = "*" ] && continue + + skill_md="$skill_dir/SKILL.md" + if [ -f "$skill_md" ]; then + desc=$(sed -n '/^description:/s/description: *//p' "$skill_md" | head -1) + skills=$(echo "$skills" | jq --arg name "$skill_name" --arg desc "$desc" '. + [{"name":$name,"description":$desc}]') + else + skills=$(echo "$skills" | jq --arg name "$skill_name" '. + [{"name":$name,"description":""}]') + fi + done + send_response "200 OK" "{\"skills\":$skills}" + exit 0 +fi + +# Install skill: POST /install/{name} +if [[ "$path" =~ ^/install/([a-zA-Z0-9_-]+)$ ]] && [ "$method" = "POST" ]; then + skill_name="${BASH_REMATCH[1]}" + skill_path="$SKILLS_DIR/$skill_name" + + # Check if already installed + if [ -d "$skill_path" ]; then + send_response "409 Conflict" "{\"success\":false,\"error\":\"Skill already installed\",\"code\":\"ALREADY_EXISTS\"}" + exit 0 + fi + + # Clone + url=$(git_url "$skill_name") + if ! git clone "$url" "$skill_path" 2>&1; then + send_response "404 Not Found" "{\"success\":false,\"error\":\"Failed to clone repository\",\"code\":\"CLONE_FAILED\"}" + exit 0 + fi + + # Run autorun.sh if exists + autorun="$skill_path/scripts/autorun.sh" + if [ -f "$autorun" ]; then + chmod +x "$autorun" + if ! bash "$autorun" 2>&1; then + send_response "500 Internal Server Error" "{\"success\":false,\"error\":\"Autorun failed\",\"code\":\"AUTORUN_FAILED\"}" + exit 0 + fi + fi + + # Make run.sh executable + run_script="$skill_path/scripts/run.sh" + [ -f "$run_script" ] && chmod +x "$run_script" + + # Notify supervisor + supervisorctl reread 2>/dev/null || true + supervisorctl update 2>/dev/null || true + + send_response "200 OK" "{\"success\":true,\"name\":\"$skill_name\"}" + exit 0 +fi + +# Update skill: POST /update/{name} +if [[ "$path" =~ ^/update/([a-zA-Z0-9_-]+)$ ]] && [ "$method" = "POST" ]; then + skill_name="${BASH_REMATCH[1]}" + skill_path="$SKILLS_DIR/$skill_name" + + if [ ! -d "$skill_path" ]; then + send_response "404 Not Found" "{\"success\":false,\"error\":\"Skill not installed\",\"code\":\"NOT_FOUND\"}" + exit 0 + fi + + # Git pull + cd "$skill_path" + if ! git pull 2>&1; then + send_response "500 Internal Server Error" "{\"success\":false,\"error\":\"Git pull failed\",\"code\":\"UPDATE_FAILED\"}" + exit 0 + fi + + # Rerun autorun.sh + autorun="$skill_path/scripts/autorun.sh" + if [ -f "$autorun" ]; then + chmod +x "$autorun" + bash "$autorun" 2>&1 || true + fi + + # Restart skill + supervisorctl restart "$skill_name" 2>/dev/null || true + + send_response "200 OK" "{\"success\":true,\"name\":\"$skill_name\"}" + exit 0 +fi + +# Remove skill: DELETE /remove/{name} +if [[ "$path" =~ ^/remove/([a-zA-Z0-9_-]+)$ ]] && [ "$method" = "DELETE" ]; then + skill_name="${BASH_REMATCH[1]}" + skill_path="$SKILLS_DIR/$skill_name" + + if [ ! -d "$skill_path" ]; then + send_response "404 Not Found" "{\"success\":false,\"error\":\"Skill not installed\",\"code\":\"NOT_FOUND\"}" + exit 0 + fi + + # Stop skill + supervisorctl stop "$skill_name" 2>/dev/null || true + + # Remove directory + rm -rf "$skill_path" + + # Update supervisor + supervisorctl reread 2>/dev/null || true + supervisorctl update 2>/dev/null || true + + send_response "200 OK" "{\"success\":true,\"name\":\"$skill_name\"}" + exit 0 +fi + +# Not found +send_response "404 Not Found" '{"error":"Endpoints: GET /available, GET /installed, POST /install/{name}, POST /update/{name}, DELETE /remove/{name}, GET /health"}' +HANDLER + + chmod +x /tmp/downloader_handler.sh + + # Export vars for handler + export SKILLS_DIR + export GIT_HOST + export GIT_USER + export GIT_TOKEN +} + +echo "Starting Skill Downloader on port $DOWNLOADER_PORT..." +echo "Skills directory: $SKILLS_DIR" +echo "Registry: https://$GIT_HOST/$GIT_USER" + +create_handler +exec socat TCP-LISTEN:$DOWNLOADER_PORT,reuseaddr,fork EXEC:"/tmp/downloader_handler.sh"