#!/usr/bin/env bash # ── Timmy Status Sidebar ─────────────────────────────────────────────── # Compact current-state view for the local Hermes + Timmy workflow. # ─────────────────────────────────────────────────────────────────────── set -euo pipefail resolve_gitea_url() { if [ -n "${GITEA_URL:-}" ]; then printf '%s\n' "${GITEA_URL%/}" return 0 fi if [ -f "$HOME/.hermes/gitea_api" ]; then python3 - "$HOME/.hermes/gitea_api" <<'PY' from pathlib import Path import sys raw = Path(sys.argv[1]).read_text().strip().rstrip("/") print(raw[:-7] if raw.endswith("/api/v1") else raw) PY return 0 fi if [ -f "$HOME/.config/gitea/base-url" ]; then tr -d '[:space:]' < "$HOME/.config/gitea/base-url" return 0 fi echo "ERROR: set GITEA_URL or create ~/.hermes/gitea_api" >&2 return 1 } resolve_ops_token() { local token_file for token_file in \ "$HOME/.config/gitea/timmy-token" \ "$HOME/.hermes/gitea_token_vps" \ "$HOME/.hermes/gitea_token_timmy"; do if [ -f "$token_file" ]; then tr -d '[:space:]' < "$token_file" return 0 fi done return 1 } GITEA_URL="$(resolve_gitea_url)" CORE_REPOS="${CORE_REPOS:-Timmy_Foundation/the-nexus Timmy_Foundation/timmy-home Timmy_Foundation/timmy-config Timmy_Foundation/hermes-agent}" TOKEN="$(resolve_ops_token || true)" [ -z "$TOKEN" ] && echo "WARN: no approved Timmy Gitea token found; status sidebar will use unauthenticated API calls" >&2 B='\033[1m' D='\033[2m' R='\033[0m' G='\033[32m' Y='\033[33m' RD='\033[31m' C='\033[36m' COLS=$(tput cols 2>/dev/null || echo 48) hr() { printf "${D}"; printf '─%.0s' $(seq 1 "$COLS"); printf "${R}\n"; } while true; do clear echo -e "${B}${C} TIMMY STATUS${R} ${D}$(date '+%H:%M:%S')${R}" hr python3 - "$HOME/.timmy" "$HOME/.hermes" <<'PY' import json import sys from pathlib import Path timmy = Path(sys.argv[1]) hermes = Path(sys.argv[2]) last_tick = timmy / "heartbeat" / "last_tick.json" model_health = hermes / "model_health.json" checkpoint = timmy / "twitter-archive" / "checkpoint.json" if last_tick.exists(): try: tick = json.loads(last_tick.read_text()) sev = tick.get("decision", {}).get("severity", "?") tick_id = tick.get("tick_id", "?") print(f" heartbeat {tick_id} severity={sev}") except Exception: print(" heartbeat unreadable") else: print(" heartbeat missing") if model_health.exists(): try: health = json.loads(model_health.read_text()) provider_ok = health.get("api_responding") inference_ok = health.get("inference_ok") models = len(health.get("models_loaded", []) or []) print(f" model api={provider_ok} inference={inference_ok} models={models}") except Exception: print(" model unreadable") else: print(" model missing") if checkpoint.exists(): try: cp = json.loads(checkpoint.read_text()) print(f" archive batches={cp.get('batches_completed', '?')} next={cp.get('next_offset', '?')} phase={cp.get('phase', '?')}") except Exception: print(" archive unreadable") else: print(" archive missing") PY hr echo -e " ${B}freshness${R}" ~/.hermes/bin/pipeline-freshness.sh 2>/dev/null | sed 's/^/ /' || echo -e " ${Y}unknown${R}" hr echo -e " ${B}review queue${R}" python3 - "$GITEA_URL" "$TOKEN" "$CORE_REPOS" <<'PY' import json import sys import urllib.request base = sys.argv[1].rstrip("/") token = sys.argv[2] repos = sys.argv[3].split() headers = {"Authorization": f"token {token}"} if token else {} count = 0 for repo in repos: try: req = urllib.request.Request(f"{base}/api/v1/repos/{repo}/issues?state=open&limit=50&type=pulls", headers=headers) with urllib.request.urlopen(req, timeout=5) as resp: items = json.loads(resp.read().decode()) for item in items: assignees = [a.get("login", "") for a in (item.get("assignees") or [])] if any(name in assignees for name in ("Timmy", "allegro")): print(f" {repo.split('/',1)[1]:12s} #{item['number']:<4d} {item['title'][:28]}") count += 1 if count >= 6: raise SystemExit except SystemExit: break except Exception: continue if count == 0: print(" (clear)") PY hr echo -e " ${B}unassigned${R}" python3 - "$GITEA_URL" "$TOKEN" "$CORE_REPOS" <<'PY' import json import sys import urllib.request base = sys.argv[1].rstrip("/") token = sys.argv[2] repos = sys.argv[3].split() headers = {"Authorization": f"token {token}"} if token else {} count = 0 for repo in repos: try: req = urllib.request.Request(f"{base}/api/v1/repos/{repo}/issues?state=open&limit=50&type=issues", headers=headers) with urllib.request.urlopen(req, timeout=5) as resp: items = json.loads(resp.read().decode()) for item in items: if not item.get("assignees"): print(f" {repo.split('/',1)[1]:12s} #{item['number']:<4d} {item['title'][:28]}") count += 1 if count >= 6: raise SystemExit except SystemExit: break except Exception: continue if count == 0: print(" (none)") PY hr sleep 10 done