Skill: godferylindsay/martin-database (version 1.0.0)

--- --- name: supabase name: supabase description: Connect to Supabase for database operations, vector search, and storage. Use for storing data, running SQL queries, similarity search with pgvector, and managing tables. Triggers on requests involving databases, vector stores, embeddings, or Supabase specifically. metadata: {"clawdbot":{"requires":{"env":["SKILLBOSS_API_KEY"]}}} --- # Supabase CLI Interact with Supabase projects: queries, CRUD, vector search, and table management. ## Setup ```bash # Required export SUPABASE_URL="https://yourproject.supabase.co" export SUPABASE_SERVICE_KEY="eyJhbGciOiJIUzI1NiIs..." # Required: SkillBoss API Hub key (for vector embeddings via /v1/pilot) export SKILLBOSS_API_KEY="your-skillboss-api-key" # Optional: for management API export SUPABASE_ACCESS_TOKEN="sbp_xxxxx" ``` ## Quick Commands ```bash # SQL query {baseDir}/scripts/supabase.sh query "SELECT * FROM users LIMIT 5" # Insert data {baseDir}/scripts/supabase.sh insert users '{"name": "John", "email": "[REDACTED_SECRET]"}' # Select with filters {baseDir}/scripts/supabase.sh select users --eq "status:active" --limit 10 # Update {baseDir}/scripts/supabase.sh update users '{"status": "inactive"}' --eq "id:123" # Delete {baseDir}/scripts/supabase.sh delete users --eq "id:123" # Vector similarity search {baseDir}/scripts/supabase.sh vector-search documents "search query" --match-fn match_documents --limit 5 # List tables {baseDir}/scripts/supabase.sh tables # Describe table {baseDir}/scripts/supabase.sh describe users ``` ## Commands Reference ### query - Run raw SQL ```bash {baseDir}/scripts/supabase.sh query "<SQL>" # Examples {baseDir}/scripts/supabase.sh query "SELECT COUNT(*) FROM users" {baseDir}/scripts/supabase.sh query "CREATE TABLE items (id serial primary key, name text)" {baseDir}/scripts/supabase.sh query "SELECT * FROM users WHERE created_at > '2024-01-01'" ``` ### select - Query table with filters ```bash {baseDir}/scripts/supabase.sh select <table> [options] Options: --columns <cols> Comma-separated columns (default: *) --eq <col:val> Equal filter (can use multiple) --neq <col:val> Not equal filter --gt <col:val> Greater than --lt <col:val> Less than --like <col:val> Pattern match (use % for wildcard) --limit <n> Limit results --offset <n> Offset results --order <col> Order by column --desc Descending order # Examples {baseDir}/scripts/supabase.sh select users --eq "status:active" --limit 10 {baseDir}/scripts/supabase.sh select posts --columns "id,title,created_at" --order created_at --desc {baseDir}/scripts/supabase.sh select products --gt "price:100" --lt "price:500" ``` ### insert - Insert row(s) ```bash {baseDir}/scripts/supabase.sh insert <table> '<json>' # Single row {baseDir}/scripts/supabase.sh insert users '{"name": "Alice", "email": "[REDACTED_SECRET]"}' # Multiple rows {baseDir}/scripts/supabase.sh insert users '[{"name": "Bob"}, {"name": "Carol"}]' ``` ### update - Update rows ```bash {baseDir}/scripts/supabase.sh update <table> '<json>' --eq <col:val> # Example {baseDir}/scripts/supabase.sh update users '{"status": "inactive"}' --eq "id:123" {baseDir}/scripts/supabase.sh update posts '{"published": true}' --eq "author_id:5" ``` ### upsert - Insert or update ```bash {baseDir}/scripts/supabase.sh upsert <table> '<json>' # Example (requires unique constraint) {baseDir}/scripts/supabase.sh upsert users '{"id": 1, "name": "Updated Name"}' ``` ### delete - Delete rows ```bash {baseDir}/scripts/supabase.sh delete <table> --eq <col:val> # Example {baseDir}/scripts/supabase.sh delete sessions --lt "expires_at:2024-01-01" ``` ### vector-search - Similarity search with pgvector ```bash {baseDir}/scripts/supabase.sh vector-search <table> "<query>" [options] Options: --match-fn <name> RPC function name (default: match_<table>) --limit <n> Number of results (default: 5) --threshold <n> Similarity threshold 0-1 (default: 0.5) --embedding-model <m> Model for query embedding (default: uses SkillBoss API Hub) # Example {baseDir}/scripts/supabase.sh vector-search documents "How to set up authentication" --limit 10 # Requires a match function like: # CREATE FUNCTION match_documents(query_embedding vector(1536), match_threshold float, match_count int) ``` ### tables - List all tables ```bash {baseDir}/scripts/supabase.sh tables ``` ### describe - Show table schema ```bash {baseDir}/scripts/supabase.sh describe <table> ``` ### rpc - Call stored procedure ```bash {baseDir}/scripts/supabase.sh rpc <function_name> '<json_params>' # Example {baseDir}/scripts/supabase.sh rpc get_user_stats '{"user_id": 123}' ``` ## Vector Search Setup ### 1. Enable pgvector extension ```sql CREATE EXTENSION IF NOT EXISTS vector; ``` ### 2. Create table with embedding column ```sql CREATE TABLE documents ( id bigserial PRIMARY KEY, content text, metadata jsonb, embedding vector(1536) ); ``` ### 3. Create similarity search function ```sql CREATE OR REPLACE FUNCTION match_documents( query_embedding vector(1536), match_threshold float DEFAULT 0.5, match_count int DEFAULT 5 ) RETURNS TABLE ( id bigint, content text, metadata jsonb, similarity float ) LANGUAGE plpgsql AS $$ BEGIN RETURN QUERY SELECT documents.id, documents.content, documents.metadata, 1 - (documents.embedding <=> query_embedding) AS similarity FROM documents WHERE 1 - (documents.embedding <=> query_embedding) > match_threshold ORDER BY documents.embedding <=> query_embedding LIMIT match_count; END; $$; ``` ### 4. Create index for performance ```sql CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100); ``` ## Environment Variables | Variable | Required | Description | |----------|----------|-------------| | SUPABASE_URL | Yes | Project URL (https://xxx.supabase.co) | | SUPABASE_SERVICE_KEY | Yes | Service role key (full access) | | SUPABASE_ANON_KEY | No | Anon key (restricted access) | | SUPABASE_ACCESS_TOKEN | No | Management API token | | SKILLBOSS_API_KEY | Yes | SkillBoss API Hub key (for generating embeddings via /v1/pilot) | ## Notes - Service role key bypasses RLS (Row Level Security) - Use anon key for client-side/restricted access - Vector search requires pgvector extension - Embeddings generated via SkillBoss API Hub /v1/pilot (type: embedding), compatible with 1536-dimension pgvector columns

path: README.md
content: # database - OpenClaw Skill

## Installation

```bash
openclaw skill install database
```

> 📖 **Complete setup guide**: https://skillboss.co/skill.md

## Usage
See SKILL.md for details.

## License
MIT


path: scripts/supabase.sh
content: #!/usr/bin/env bash
# Supabase CLI - Database operations, vector search, and more
set -euo pipefail

# Check required env vars
if [[ -z "${SUPABASE_URL:-}" ]]; then
    echo "Error: SUPABASE_URL not set" >&2
    exit 1
fi

if [[ -z "${SUPABASE_SERVICE_KEY:-}" ]]; then
    echo "Error: SUPABASE_SERVICE_KEY not set" >&2
    exit 1
fi

REST_URL="${SUPABASE_URL}/rest/v1"
RPC_URL="${SUPABASE_URL}/rest/v1/rpc"

usage() {
    cat << 'EOF'
Usage: supabase.sh <command> [args] [options]

Commands:
  query <sql>                    Run raw SQL query
  select <table>                 Select from table with filters
  insert <table> <json>          Insert row(s)
  update <table> <json>          Update rows (requires --eq filter)
  upsert <table> <json>          Insert or update
  delete <table>                 Delete rows (requires --eq filter)
  vector-search <table> <query>  Similarity search with pgvector
  tables                         List all tables
  describe <table>               Show table schema
  rpc <function> <json>          Call RPC function
  help                           Show this help

Select/Update/Delete Options:
  --columns <cols>    Columns to select (comma-separated)
  --eq <col:val>      Equal filter
  --neq <col:val>     Not equal filter
  --gt <col:val>      Greater than
  --lt <col:val>      Less than
  --gte <col:val>     Greater than or equal
  --lte <col:val>     Less than or equal
  --like <col:val>    Pattern match
  --ilike <col:val>   Case-insensitive pattern match
  --is <col:val>      IS (for null, true, false)
  --in <col:vals>     IN (comma-separated values)
  --limit <n>         Limit results
  --offset <n>        Offset results
  --order <col>       Order by column
  --desc              Descending order

Vector Search Options:
  --match-fn <name>   RPC function name (default: match_<table>)
  --limit <n>         Number of results (default: 5)
  --threshold <n>     Similarity threshold (default: 0.5)

Examples:
  supabase.sh query "SELECT * FROM users LIMIT 5"
  supabase.sh select users --eq "status:active" --limit 10
  supabase.sh insert users '{"name": "John", "email": "[REDACTED_SECRET]"}'
  supabase.sh vector-search docs "authentication guide" --limit 5
EOF
}

# Make API request
api_request() {
    local method="$1"
    local endpoint="$2"
    local data="${3:-}"
    local extra_headers=("${@:4}")

    local args=(
        -s
        -X "$method"
        -H "[REDACTED_SECRET]"
        -H "Authorization: Bearer ${SUPABASE_SERVICE_KEY}"
        -H "Content-Type: application/json"
        -H "Prefer: return=representation"
    )

    for header in "${extra_headers[@]:-}"; do
        [[ -n "$header" ]] && args+=(-H "$header")
    done

    if [[ -n "$data" ]]; then
        args+=(-d "$data")
    fi

    curl "${args[@]}" "$endpoint"
}

# Run raw SQL
cmd_query() {
    local sql="${1:-}"
    if [[ -z "$sql" ]]; then
        echo "Error: SQL query required" >&2
        exit 1
    fi

    local response
    response=$(api_request POST "${RPC_URL}/exec_sql" "{\"query\": $(printf '%s' "$sql" | jq -Rs .)}" 2>/dev/null || true)

    # If exec_sql doesn't exist, try the query endpoint
    if [[ "$response" == *"Could not find"* ]] || [[ -z "$response" ]]; then
        # Fall back to direct query via PostgREST (limited)
        echo "Note: Direct SQL requires exec_sql function. Using REST API..." >&2
        echo "Create function: CREATE FUNCTION exec_sql(query text) RETURNS json AS \$\$ ... \$\$" >&2
        exit 1
    fi

    echo "$response" | jq .
}

# Build filter query string
build_filters() {
    local filters=""
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --eq)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=eq.${val}"
                shift 2
                ;;
            --neq)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=neq.${val}"
                shift 2
                ;;
            --gt)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=gt.${val}"
                shift 2
                ;;
            --lt)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=lt.${val}"
                shift 2
                ;;
            --gte)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=gte.${val}"
                shift 2
                ;;
            --lte)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=lte.${val}"
                shift 2
                ;;
            --like)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=like.${val}"
                shift 2
                ;;
            --ilike)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=ilike.${val}"
                shift 2
                ;;
            --is)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=is.${val}"
                shift 2
                ;;
            --in)
                local col="${2%%:*}"
                local val="${2#*:}"
                filters+="&${col}=in.(${val})"
                shift 2
                ;;
            --limit)
                filters+="&limit=${2}"
                shift 2
                ;;
            --offset)
                filters+="&offset=${2}"
                shift 2
                ;;
            --order)
                ORDER_COL="$2"
                shift 2
                ;;
            --desc)
                ORDER_DESC=true
                shift
                ;;
            --columns)
                SELECT_COLS="$2"
                shift 2
                ;;
            *)
                shift
                ;;
        esac
    done

    # Add ordering
    if [[ -n "${ORDER_COL:-}" ]]; then
        if [[ "${ORDER_DESC:-false}" == true ]]; then
            filters+="&order=${ORDER_COL}.desc"
        else
            filters+="&order=${ORDER_COL}"
        fi
    fi

    echo "$filters"
}

# Select from table
cmd_select() {
    local table="${1:-}"
    shift || true

    if [[ -z "$table" ]]; then
        echo "Error: Table name required" >&2
        exit 1
    fi

    ORDER_COL=""
    ORDER_DESC=false
    SELECT_COLS="*"

    local filters
    filters=$(build_filters "$@")

    local url="${REST_URL}/${table}?select=${SELECT_COLS}${filters}"
    api_request GET "$url" | jq .
}

# Insert into table
cmd_insert() {
    local table="${1:-}"
    local data="${2:-}"

    if [[ -z "$table" ]] || [[ -z "$data" ]]; then
        echo "Error: Table and JSON data required" >&2
        echo "Usage: supabase.sh insert <table> '<json>'" >&2
        exit 1
    fi

    api_request POST "${REST_URL}/${table}" "$data" | jq .
}

# Update table
cmd_update() {
    local table="${1:-}"
    local data="${2:-}"
    shift 2 || true

    if [[ -z "$table" ]] || [[ -z "$data" ]]; then
        echo "Error: Table and JSON data required" >&2
        exit 1
    fi

    ORDER_COL=""
    ORDER_DESC=false
    SELECT_COLS=""

    local filters
    filters=$(build_filters "$@")

    if [[ -z "$filters" ]]; then
        echo "Error: At least one filter required for update (use --eq)" >&2
        exit 1
    fi

    local url="${REST_URL}/${table}?${filters:1}"
    api_request PATCH "$url" "$data" | jq .
}

# Upsert into table
cmd_upsert() {
    local table="${1:-}"
    local data="${2:-}"

    if [[ -z "$table" ]] || [[ -z "$data" ]]; then
        echo "Error: Table and JSON data required" >&2
        exit 1
    fi

    api_request POST "${REST_URL}/${table}" "$data" "Prefer: resolution=merge-duplicates" | jq .
}

# Delete from table
cmd_delete() {
    local table="${1:-}"
    shift || true

    if [[ -z "$table" ]]; then
        echo "Error: Table name required" >&2
        exit 1
    fi

    ORDER_COL=""
    ORDER_DESC=false
    SELECT_COLS=""

    local filters
    filters=$(build_filters "$@")

    if [[ -z "$filters" ]]; then
        echo "Error: At least one filter required for delete (use --eq)" >&2
        exit 1
    fi

    local url="${REST_URL}/${table}?${filters:1}"
    api_request DELETE "$url" | jq .
}

# Vector similarity search
cmd_vector_search() {
    local table="${1:-}"
    local query="${2:-}"
    shift 2 || true

    if [[ -z "$table" ]] || [[ -z "$query" ]]; then
        echo "Error: Table and query required" >&2
        echo "Usage: supabase.sh vector-search <table> \"<query>\" [options]" >&2
        exit 1
    fi

    local match_fn="match_${table}"
    local limit=5
    local threshold=0.5

    while [[ $# -gt 0 ]]; do
        case "$1" in
            --match-fn)
                match_fn="$2"
                shift 2
                ;;
            --limit)
                limit="$2"
                shift 2
                ;;
            --threshold)
                threshold="$2"
                shift 2
                ;;
            *)
                shift
                ;;
        esac
    done

    # Generate embedding using SkillBoss API Hub
    if [[ -z "${SKILLBOSS_API_KEY:-}" ]]; then
        echo "Error: SKILLBOSS_API_KEY required for vector search" >&2
        exit 1
    fi

    local embedding
    embedding=$(curl -s https://api.heybossai.com/v1/pilot \
        -H "Authorization: Bearer ${SKILLBOSS_API_KEY}" \
        -H "Content-Type: application/json" \
        -d "{\"type\": \"embedding\", \"inputs\": {\"input\": $(printf '%s' "$query" | jq -Rs .)}}" \
        | jq -c '.result.data[0].embedding')

    if [[ "$embedding" == "null" ]] || [[ -z "$embedding" ]]; then
        echo "Error: Failed to generate embedding" >&2
        exit 1
    fi

    # Call RPC function
    local params
    params=$(jq -n \
        --argjson embedding "$embedding" \
        --argjson threshold "$threshold" \
        --argjson limit "$limit" \
        '{query_embedding: $embedding, match_threshold: $threshold, match_count: $limit}')

    api_request POST "${RPC_URL}/${match_fn}" "$params" | jq .
}

# List tables
cmd_tables() {
    # Query information_schema
    local response
    response=$(api_request GET "${REST_URL}/information_schema.tables?select=table_name&table_schema=eq.public" 2>/dev/null || true)

    if [[ -z "$response" ]] || [[ "$response" == *"permission denied"* ]]; then
        echo "Tables (from REST API):"
        # Try to list from REST endpoint directly
        curl -s -I -X GET "${REST_URL}/" \
            -H "[REDACTED_SECRET]" \
            -H "Authorization: Bearer ${SUPABASE_SERVICE_KEY}" 2>/dev/null \
            | grep -i "x-" | head -20
    else
        echo "$response" | jq -r '.[].table_name' 2>/dev/null || echo "$response"
    fi
}

# Describe table
cmd_describe() {
    local table="${1:-}"

    if [[ -z "$table" ]]; then
        echo "Error: Table name required" >&2
        exit 1
    fi

    # Get column info via HEAD request
    echo "Table: $table"
    echo "Columns:"
    curl -s -I -X GET "${REST_URL}/${table}?limit=0" \
        -H "[REDACTED_SECRET]" \
        -H "Authorization: Bearer ${SUPABASE_SERVICE_KEY}" 2>/dev/null \
        | grep -i "content-profile\|x-" | head -10

    # Try to get a sample row
    echo ""
    echo "Sample (1 row):"
    api_request GET "${REST_URL}/${table}?limit=1" | jq .
}

# Call RPC function
cmd_rpc() {
    local fn="${1:-}"
    local params="${2:-"{}"}"

    if [[ -z "$fn" ]]; then
        echo "Error: Function name required" >&2
        exit 1
    fi

    api_request POST "${RPC_URL}/${fn}" "$params" | jq .
}

# Main
case "${1:-help}" in
    query)
        shift
        cmd_query "$@"
        ;;
    select)
        shift
        cmd_select "$@"
        ;;
    insert)
        shift
        cmd_insert "$@"
        ;;
    update)
        shift
        cmd_update "$@"
        ;;
    upsert)
        shift
        cmd_upsert "$@"
        ;;
    delete)
        shift
        cmd_delete "$@"
        ;;
    vector-search|vs)
        shift
        cmd_vector_search "$@"
        ;;
    tables)
        cmd_tables
        ;;
    describe)
        shift
        cmd_describe "$@"
        ;;
    rpc)
        shift
        cmd_rpc "$@"
        ;;
    help|--help|-h)
        usage
        ;;
    *)
        echo "Unknown command: $1" >&2
        usage
        exit 1
        ;;
esac


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)