basecamp / tavern.sh
jpanasuk's picture
v2.0 pass 2: one-shot -z fix, 300s pull poll, model-rank boundaries, postgres validation, UI picker, cleanup, HEALTHCHECK
30a8b29
Raw
History Blame Contribute Delete
8.45 kB
#!/bin/bash
# tavern — Tabby Tavern interaction toolkit for Basecamp
# Usage: tavern <command> [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 <model> Pull a model into Tabby Tavern's Ollama
# search "query" Search via SearXNG
# mcp List MCPO tools
# mcp <tool> Call an MCPO tool (POST to /host-master/<tool>)
# open-webui Show Open WebUI URL
# sillytavern Show SillyTavern URL
# logs <service> 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 <model>}"
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 <service>}"
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 <model> Pull model into Tavern Ollama"
echo " search \"query\" [n] Search via SearXNG"
echo " mcp List MCPO tools"
echo " mcp <tool> [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