#!/bin/bash # tavern — Tabby Tavern interaction toolkit for Basecamp # Usage: tavern [args] # # Commands: # status Check all Tabby Tavern services # models List TabbyAPI EXL3 models # chat "msg" Chat with TabbyAPI EXL3 model # ollama-models List Ollama models in Tabby Tavern # ollama-chat "msg" [model] Chat with Tabby Tavern's Ollama # pull Pull a model into Tabby Tavern's Ollama # search "query" Search via SearXNG # mcp List MCPO tools # mcp Call an MCPO tool (POST to /host-master/) # open-webui Show Open WebUI URL # sillytavern Show SillyTavern URL # logs Tail logs from a Tabby Tavern service # ports Show all service ports # NOTE: no secrets are baked into this fallback script. Run the connect screen # (tavern rediscover) to generate a tavern configured with your real endpoints # and keys; that generated script is stored chmod 600 in basecamp's own state. TABBYAPI_URL="${TABBYAPI_URL:-http://tabbyapi:5000}" TABBYAPI_KEY="${TABBYAPI_KEY:-}" OLLAMA_TAVERN="${OLLAMA_TAVERN:-http://ollama:11434}" SEARXNG_URL="${SEARXNG_URL:-http://searxng:8080}" MCPO_URL="${MCPO_URL:-http://mcpo:8000}" MCPO_KEY="${MCPO_KEY:-}" cmd="${1:-help}" shift 2>/dev/null case "$cmd" in status) echo "=== Tabby Tavern Service Status ===" for svc in "TabbyAPI:tabbyapi:5000" "Ollama:ollama:11434" "Open-WebUI:open-webui:8080" "SillyTavern:sillytavern:8000" "SearXNG:searxng:8080" "MCPO:mcpo:8000"; do NAME=$(echo $svc | cut -d: -f1) HOST=$(echo $svc | cut -d: -f2) PORT=$(echo $svc | cut -d: -f3) CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 http://$HOST:$PORT/ 2>/dev/null || echo "000") if [ "$CODE" = "000" ]; then echo " DOWN $NAME" else echo " UP $NAME (HTTP $CODE) → http://$HOST:$PORT" fi done ;; models) echo "=== TabbyAPI EXL3 Models ===" [ -z "$TABBYAPI_KEY" ] && echo " (no API key configured -- run: tavern rediscover)" curl -s "$TABBYAPI_URL/v1/models" -H "Authorization: Bearer $TABBYAPI_KEY" 2>/dev/null | python3 -c " import sys,json d=json.load(sys.stdin) for m in d.get('data',[]): print(f' {m[\"id\"]}') if not d.get('data'): print(' (no models loaded)') " 2>/dev/null || echo " Cannot reach TabbyAPI" ;; chat) MSG="${1:-Hello}" MAX="${2:-256}" echo "=== TabbyAPI Chat ===" [ -z "$TABBYAPI_KEY" ] && echo " (no API key configured -- run: tavern rediscover)" # Get model name MODEL=$(curl -s "$TABBYAPI_URL/v1/models" -H "Authorization: Bearer $TABBYAPI_KEY" 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['data'][0]['id'] if d.get('data') else '')" 2>/dev/null) if [ -z "$MODEL" ]; then echo "No models loaded in TabbyAPI" exit 1 fi echo "Model: $MODEL" echo "You: $MSG" echo -n "AI: " curl -s "$TABBYAPI_URL/v1/chat/completions" \ -H "Authorization: Bearer $TABBYAPI_KEY" \ -H "Content-Type: application/json" \ -d "{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"$MSG\"}],\"max_tokens\":$MAX}" 2>/dev/null | python3 -c " import sys,json d=json.load(sys.stdin) print(d['choices'][0]['message']['content']) " 2>/dev/null || echo "(error)" ;; ollama-models) echo "=== Tabby Tavern Ollama Models ===" curl -s "$OLLAMA_TAVERN/api/tags" 2>/dev/null | python3 -c " import sys,json d=json.load(sys.stdin) for m in d.get('models',[]): size_mb = m['size']//1024//1024 print(f' {m[\"name\"]:30s} {size_mb:>6d} MB') if not d.get('models'): print(' (no models)') " 2>/dev/null || echo " Cannot reach Tabby Tavern Ollama" ;; ollama-chat) MSG="${1:-Hello}" MODEL="${2:-llama3.1:8b}" echo "=== Ollama Chat ($MODEL) ===" echo "You: $MSG" echo -n "AI: " curl -s "$OLLAMA_TAVERN/api/chat" \ -H "Content-Type: application/json" \ -d "$(PAYLOAD=$(printf '%s\n' "$MSG" | python3 -c 'import sys,json; print(json.dumps({"model":sys.argv[1],"messages":[{"role":"user","content":sys.stdin.read().strip()}],"stream":False}))' "$MODEL") && echo "$PAYLOAD")" 2>/dev/null | python3 -c " import sys,json d=json.load(sys.stdin) print(d.get('message',{}).get('content','(no response)')) " 2>/dev/null || echo "(error)" ;; pull) MODEL="${1:?Usage: tavern pull }" echo "Pulling $MODEL into Tabby Tavern Ollama..." curl -s "$OLLAMA_TAVERN/api/pull" -d "{\"name\":\"$MODEL\"}" 2>/dev/null echo "Done." ;; search) QUERY="${1:?Usage: tavern search \"query\"}" NUM="${2:-5}" echo "=== SearXNG Search: \"$QUERY\" ===" QENC=$(QUERY="$QUERY" python3 -c "import os,urllib.parse; print(urllib.parse.quote(os.environ['QUERY']))" 2>/dev/null) curl -s "$SEARXNG_URL/search?q=$QENC&format=json" 2>/dev/null | python3 -c " import sys,json d=json.load(sys.stdin) for i,r in enumerate(d.get('results',[])[:$NUM],1): print(f' {i}. {r.get(\"title\",\"\")}') print(f' {r.get(\"url\",\"\")}') print(f' {r.get(\"content\",\"\")[:120]}') print() " 2>/dev/null || echo " Search failed" ;; mcp) if [ -z "$1" ]; then echo "=== MCPO Available Tools ===" curl -s "http://mcpo:8000/host-master/openapi.json" 2>/dev/null | python3 -c " import sys,json d=json.load(sys.stdin) for path,methods in d.get('paths',{}).items(): for method,info in methods.items(): print(f' {method.upper():4s} /host-master{path:30s} — {info.get(\"summary\",\"\")}') " 2>/dev/null || echo " Cannot reach MCPO" else TOOL="$1" shift BODY="${1:-{}}" echo "=== MCPO Tool: $TOOL ===" curl -s -X POST "http://mcpo:8000/host-master/$TOOL" \ -H "Authorization: Bearer $MCPO_KEY" \ -H "Content-Type: application/json" \ -d "$BODY" 2>/dev/null | python3 -c " import sys,json try: d=json.load(sys.stdin) print(json.dumps(d, indent=2)) except: print(sys.stdin.read()) " 2>/dev/null fi ;; open-webui) echo "Open WebUI: http://localhost:3000 (inside network: http://open-webui:8080)" ;; sillytavern) echo "SillyTavern: http://localhost:8000 (inside network: http://sillytavern:8000)" ;; logs) SVC="${1:?Usage: tavern logs }" echo "Tailing logs for $SVC... (Ctrl+C to stop)" # We can't docker exec from inside the container, but we can curl health echo "(Log access requires docker exec from host — use: docker logs tabby-tavern-${SVC}-1 --tail 50 -f)" ;; ports) echo "=== Tabby Tavern Port Map ===" echo " TabbyAPI :5000 (EXL3 inference)" echo " Open WebUI :3000 (LLM workspace)" echo " SillyTavern :8000 (Character chat, auth: admin/tabby)" echo " SearXNG :8080 (Private search)" echo " MCPO :8001 host / :8000 in-network (MCP OpenAPI proxy)" echo " Ollama Tavern :11435 (GGUF backend)" echo " Basecamp Ollama :11436 (this container)" ;; help|--help|-h) echo "tavern — Tabby Tavern interaction toolkit" echo "" echo "Commands:" echo " status Check all services" echo " models List TabbyAPI EXL3 models" echo " chat \"msg\" [tokens] Chat with TabbyAPI EXL3" echo " ollama-models List Tabby Tavern Ollama models" echo " ollama-chat \"msg\" [model] Chat with Tavern Ollama" echo " pull Pull model into Tavern Ollama" echo " search \"query\" [n] Search via SearXNG" echo " mcp List MCPO tools" echo " mcp [json] Call an MCPO tool" echo " open-webui Show Open WebUI URL" echo " sillytavern Show SillyTavern URL" echo " ports Show all ports" ;; *) echo "Unknown command: $cmd" echo "Run: tavern help" exit 1 ;; esac