#!/usr/bin/env bash # ============================================================================ # BLACKROAD OS, INC. - PROPRIETARY AND CONFIDENTIAL # Copyright (c) 2025-2026 BlackRoad OS, Inc. All Rights Reserved. # ============================================================================ # br-ai - BlackRoad AI Hub # Unified AI gateway: local fleet, cloud APIs, model routing, multi-provider # Usage: br-ai [args] set -eo pipefail source "$HOME/.blackroad/config/nodes.sh" 2>/dev/null || true AI_DIR="$HOME/.blackroad/ai" AI_DB="$AI_DIR/hub.db" HISTORY_DIR="$AI_DIR/history" MODELS_REGISTRY="$AI_DIR/models.json" mkdir -p "$AI_DIR" "$HISTORY_DIR" # Provider backends declare -A PROVIDERS=( [ollama]="local" [anthropic]="cloud" [openai]="cloud" [gemini]="cloud" [grok]="cloud" ) # Fleet Ollama nodes (priority order) OLLAMA_NODES=(cecilia lucidia alice) # Cloud API keys (from env) ANTHROPIC_KEY="${ANTHROPIC_API_KEY:-}" OPENAI_KEY="${OPENAI_API_KEY:-}" GEMINI_KEY="${GOOGLE_AI_API_KEY:-${GEMINI_API_KEY:-}}" GROK_KEY="${XAI_API_KEY:-}" # SQL helper _sql() { sqlite3 "$AI_DB" "$@" 2>/dev/null; } _sql_escape() { echo "$1" | sed "s/'/''/g"; } # Initialize hub database init_db() { _sql <<'SQL' CREATE TABLE IF NOT EXISTS queries ( id TEXT PRIMARY KEY, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, provider TEXT, model TEXT, node TEXT, prompt_len INTEGER, response_len INTEGER, latency_ms INTEGER, status TEXT, cost_estimate REAL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS models ( name TEXT PRIMARY KEY, provider TEXT, node TEXT, size TEXT, capabilities TEXT, last_seen DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_q_provider ON queries(provider); CREATE INDEX IF NOT EXISTS idx_q_ts ON queries(timestamp); PRAGMA journal_mode=WAL; PRAGMA busy_timeout=5000; SQL } # ============================================================================ # PROVIDER FUNCTIONS # ============================================================================ # Find best Ollama node find_ollama() { for node in "${OLLAMA_NODES[@]}"; do local ip="${NODE_IP[$node]:-}" [[ -z "$ip" ]] && continue if curl -sf --connect-timeout 2 "http://${ip}:11434/api/tags" &>/dev/null; then echo "$node" return 0 fi done return 1 } # Query Ollama (local fleet) query_ollama() { local model="$1" prompt="$2" node="${3:-}" [[ -z "$node" ]] && node=$(find_ollama) || { echo "No Ollama nodes available" >&2; return 1; } local ip="${NODE_IP[$node]}" local start_ms=$(($(date +%s) * 1000)) local response response=$(curl -sf --max-time 120 "http://${ip}:11434/api/generate" \ -d "{\"model\":\"$model\",\"prompt\":$(printf '%s' "$prompt" | jq -Rs .),\"stream\":false}" 2>/dev/null) local end_ms=$(($(date +%s) * 1000)) local latency=$((end_ms - start_ms)) local text text=$(echo "$response" | jq -r '.response // empty' 2>/dev/null) if [[ -n "$text" ]]; then # Log query local id="q_$(date +%s)_$(openssl rand -hex 4)" _sql "INSERT INTO queries (id, provider, model, node, prompt_len, response_len, latency_ms, status) VALUES ('$id', 'ollama', '$(_sql_escape "$model")', '$node', ${#prompt}, ${#text}, $latency, 'success')" 2>/dev/null || true echo "$text" else return 1 fi } # Query Anthropic (Claude) query_anthropic() { local model="${1:-claude-sonnet-4-6}" prompt="$2" [[ -z "$ANTHROPIC_KEY" ]] && { echo "ANTHROPIC_API_KEY not set" >&2; return 1; } local response response=$(curl -sf --max-time 120 "https://api.anthropic.com/v1/messages" \ -H "x-api-key: $ANTHROPIC_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d "$(jq -n --arg model "$model" --arg prompt "$prompt" \ '{model: $model, max_tokens: 4096, messages: [{role: "user", content: $prompt}]}')" 2>/dev/null) echo "$response" | jq -r '.content[0].text // empty' 2>/dev/null } # Query OpenAI query_openai() { local model="${1:-gpt-4o}" prompt="$2" [[ -z "$OPENAI_KEY" ]] && { echo "OPENAI_API_KEY not set" >&2; return 1; } local response response=$(curl -sf --max-time 120 "https://api.openai.com/v1/chat/completions" \ -H "Authorization: Bearer $OPENAI_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg model "$model" --arg prompt "$prompt" \ '{model: $model, messages: [{role: "user", content: $prompt}]}')" 2>/dev/null) echo "$response" | jq -r '.choices[0].message.content // empty' 2>/dev/null } # Query Gemini query_gemini() { local model="${1:-gemini-1.5-pro}" prompt="$2" [[ -z "$GEMINI_KEY" ]] && { echo "GOOGLE_AI_API_KEY not set" >&2; return 1; } local response response=$(curl -sf --max-time 120 \ "https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_KEY}" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg prompt "$prompt" \ '{contents: [{parts: [{text: $prompt}]}]}')" 2>/dev/null) echo "$response" | jq -r '.candidates[0].content.parts[0].text // empty' 2>/dev/null } # Query Grok (xAI) query_grok() { local model="${1:-grok-beta}" prompt="$2" [[ -z "$GROK_KEY" ]] && { echo "XAI_API_KEY not set" >&2; return 1; } local response response=$(curl -sf --max-time 120 "https://api.x.ai/v1/chat/completions" \ -H "Authorization: Bearer $GROK_KEY" \ -H "Content-Type: application/json" \ -d "$(jq -n --arg model "$model" --arg prompt "$prompt" \ '{model: $model, messages: [{role: "user", content: $prompt}]}')" 2>/dev/null) echo "$response" | jq -r '.choices[0].message.content // empty' 2>/dev/null } # ============================================================================ # SMART ROUTING # ============================================================================ # Auto-route: try local first, fallback to cloud smart_query() { local prompt="$1" local model="${2:-auto}" local provider="${3:-auto}" if [[ "$provider" == "auto" ]]; then # Try local Ollama first (free, fast, private) local node node=$(find_ollama 2>/dev/null) && { local ollama_model="${model}" [[ "$model" == "auto" ]] && ollama_model="llama3.2" local result result=$(query_ollama "$ollama_model" "$prompt" "$node" 2>/dev/null) if [[ -n "$result" ]]; then printf '%b[ollama@%s → %s]%b\n' "$BLUE" "$node" "$ollama_model" "$RESET" >&2 echo "$result" return 0 fi } # Fallback chain: Anthropic → OpenAI → Gemini → Grok for cloud_provider in anthropic openai gemini grok; do local cloud_model case "$cloud_provider" in anthropic) [[ -z "$ANTHROPIC_KEY" ]] && continue; cloud_model="claude-sonnet-4-6" ;; openai) [[ -z "$OPENAI_KEY" ]] && continue; cloud_model="gpt-4o" ;; gemini) [[ -z "$GEMINI_KEY" ]] && continue; cloud_model="gemini-1.5-pro" ;; grok) [[ -z "$GROK_KEY" ]] && continue; cloud_model="grok-beta" ;; esac local result result=$(query_${cloud_provider} "$cloud_model" "$prompt" 2>/dev/null) if [[ -n "$result" ]]; then printf '%b[%s → %s]%b\n' "$VIOLET" "$cloud_provider" "$cloud_model" "$RESET" >&2 echo "$result" return 0 fi done printf '%bNo AI providers available%b\n' "$RED" "$RESET" >&2 return 1 else # Direct provider routing case "$provider" in ollama) query_ollama "${model:-llama3.2}" "$prompt" ;; anthropic) query_anthropic "${model:-claude-sonnet-4-6}" "$prompt" ;; openai) query_openai "${model:-gpt-4o}" "$prompt" ;; gemini) query_gemini "${model:-gemini-1.5-pro}" "$prompt" ;; grok) query_grok "${model:-grok-beta}" "$prompt" ;; *) echo "Unknown provider: $provider" >&2; return 1 ;; esac fi } # ============================================================================ # COMMANDS # ============================================================================ cmd_query() { local provider="auto" model="auto" while [[ $# -gt 0 ]]; do case "$1" in -p|--provider) provider="$2"; shift 2 ;; -m|--model) model="$2"; shift 2 ;; *) break ;; esac done local prompt="$*" [[ -z "$prompt" ]] && { echo "Usage: br-ai query [-p provider] [-m model] \"prompt\"" >&2; return 1; } smart_query "$prompt" "$model" "$provider" } cmd_chat() { local provider="auto" model="auto" while [[ $# -gt 0 ]]; do case "$1" in -p|--provider) provider="$2"; shift 2 ;; -m|--model) model="$2"; shift 2 ;; *) break ;; esac done printf '%bBlackRoad AI Chat%b [provider: %s, model: %s]\n' "$PINK" "$RESET" "$provider" "$model" printf 'Type your message (Ctrl+D to exit)\n\n' while printf '%b> %b' "$AMBER" "$RESET" && IFS= read -r line; do [[ -z "$line" ]] && continue local response response=$(smart_query "$line" "$model" "$provider" 2>/dev/null) if [[ -n "$response" ]]; then echo "$response" else printf '%bNo response%b\n' "$RED" "$RESET" fi echo done } cmd_models() { printf '%b%-20s %-12s %-12s %-8s%b\n' "$BLUE" "MODEL" "PROVIDER" "NODE" "STATUS" "$RESET" printf '%-20s %-12s %-12s %-8s\n' "─────" "────────" "────" "──────" # Local Ollama models for node in "${OLLAMA_NODES[@]}"; do local ip="${NODE_IP[$node]:-}" [[ -z "$ip" ]] && continue local tags tags=$(curl -sf --connect-timeout 2 "http://${ip}:11434/api/tags" 2>/dev/null) if [[ -n "$tags" ]]; then echo "$tags" | jq -r '.models[]?.name // empty' 2>/dev/null | while read -r m; do printf '%-20s %-12s %-12s %b%-8s%b\n' "$m" "ollama" "$node" "$GREEN" "ready" "$RESET" done fi done # Cloud providers [[ -n "$ANTHROPIC_KEY" ]] && printf '%-20s %-12s %-12s %b%-8s%b\n' "claude-*" "anthropic" "cloud" "$GREEN" "ready" "$RESET" [[ -n "$OPENAI_KEY" ]] && printf '%-20s %-12s %-12s %b%-8s%b\n' "gpt-4o" "openai" "cloud" "$GREEN" "ready" "$RESET" [[ -n "$GEMINI_KEY" ]] && printf '%-20s %-12s %-12s %b%-8s%b\n' "gemini-1.5-pro" "gemini" "cloud" "$GREEN" "ready" "$RESET" [[ -n "$GROK_KEY" ]] && printf '%-20s %-12s %-12s %b%-8s%b\n' "grok-beta" "grok" "cloud" "$GREEN" "ready" "$RESET" } cmd_providers() { printf '%b%-12s %-8s %-30s%b\n' "$BLUE" "PROVIDER" "STATUS" "DETAILS" "$RESET" printf '%-12s %-8s %-30s\n' "────────" "──────" "───────" # Ollama fleet for node in "${OLLAMA_NODES[@]}"; do local ip="${NODE_IP[$node]:-}" [[ -z "$ip" ]] && continue local count count=$(curl -sf --connect-timeout 2 "http://${ip}:11434/api/tags" 2>/dev/null | jq '.models | length' 2>/dev/null) if [[ -n "$count" ]]; then printf '%-12s %b%-8s%b %s (%s, %s models)\n' "ollama" "$GREEN" "UP" "$RESET" "$node" "$ip" "$count" else printf '%-12s %b%-8s%b %s (%s)\n' "ollama" "$RED" "DOWN" "$RESET" "$node" "$ip" fi done # Cloud local -A cloud_keys=([anthropic]="$ANTHROPIC_KEY" [openai]="$OPENAI_KEY" [gemini]="$GEMINI_KEY" [grok]="$GROK_KEY") for p in anthropic openai gemini grok; do if [[ -n "${cloud_keys[$p]}" ]]; then printf '%-12s %b%-8s%b API key configured\n' "$p" "$GREEN" "READY" "$RESET" else printf '%-12s %b%-8s%b No API key\n' "$p" "$AMBER" "NO KEY" "$RESET" fi done } cmd_stats() { init_db 2>/dev/null printf '%bAI Hub Statistics%b\n\n' "$PINK" "$RESET" local total; total=$(_sql "SELECT COUNT(*) FROM queries" 2>/dev/null || echo "0") local today; today=$(_sql "SELECT COUNT(*) FROM queries WHERE date(timestamp)=date('now')" 2>/dev/null || echo "0") local avg_lat; avg_lat=$(_sql "SELECT COALESCE(AVG(latency_ms),0) FROM queries WHERE status='success'" 2>/dev/null || echo "0") printf ' Total queries: %s\n' "$total" printf ' Today: %s\n' "$today" printf ' Avg latency: %sms\n\n' "$avg_lat" printf ' %bBy provider:%b\n' "$BLUE" "$RESET" _sql "SELECT provider, COUNT(*), AVG(latency_ms) FROM queries GROUP BY provider ORDER BY COUNT(*) DESC" 2>/dev/null | \ while IFS='|' read -r prov cnt lat; do printf ' %-12s %d queries (avg %dms)\n' "$prov" "$cnt" "${lat%.*}" done } cmd_benchmark() { local prompt="${1:-Explain what a Raspberry Pi is in one sentence.}" printf '%bBenchmarking all providers...%b\n\n' "$AMBER" "$RESET" # Ollama nodes for node in "${OLLAMA_NODES[@]}"; do local ip="${NODE_IP[$node]:-}" [[ -z "$ip" ]] && continue printf ' ollama@%-10s ' "$node" local start=$SECONDS local result result=$(query_ollama "llama3.2" "$prompt" "$node" 2>/dev/null) local elapsed=$(( SECONDS - start )) if [[ -n "$result" ]]; then printf '%b%ds%b (%d chars)\n' "$GREEN" "$elapsed" "$RESET" "${#result}" else printf '%bfailed%b\n' "$RED" "$RESET" fi done # Cloud providers for p in anthropic openai gemini grok; do local key_var="${p^^}_KEY" # Map to the right variable case "$p" in anthropic) [[ -z "$ANTHROPIC_KEY" ]] && continue ;; openai) [[ -z "$OPENAI_KEY" ]] && continue ;; gemini) [[ -z "$GEMINI_KEY" ]] && continue ;; grok) [[ -z "$GROK_KEY" ]] && continue ;; esac printf ' %-18s ' "$p" local start=$SECONDS local result result=$(query_${p} "" "$prompt" 2>/dev/null) local elapsed=$(( SECONDS - start )) if [[ -n "$result" ]]; then printf '%b%ds%b (%d chars)\n' "$GREEN" "$elapsed" "$RESET" "${#result}" else printf '%bfailed%b\n' "$RED" "$RESET" fi done } cmd_hub() { printf '%b╔══════════════════════════════════════════════════════════╗%b\n' "$PINK" "$RESET" printf '%b║ BLACKROAD AI HUB ║%b\n' "$PINK" "$RESET" printf '%b║ Local-First Sovereign Intelligence ║%b\n' "$PINK" "$RESET" printf '%b╚══════════════════════════════════════════════════════════╝%b\n\n' "$PINK" "$RESET" cmd_providers echo "" cmd_stats } usage() { cat </dev/null case "${1:-}" in query|q) shift; cmd_query "$@" ;; chat|c) shift; cmd_chat "$@" ;; models|m) cmd_models ;; providers|p) cmd_providers ;; stats|s) cmd_stats ;; benchmark|b) shift; cmd_benchmark "$@" ;; hub|h) cmd_hub ;; init) init_db ;; -h|--help|help|"") usage ;; *) # Treat everything else as a query cmd_query "$@" ;; esac