#!/usr/bin/env bash # ── Workflow Ops Panel ───────────────────────────────────────────────── # Current-state dashboard for review, dispatch, and freshness. # This intentionally reflects the post-loop, Hermes-sidecar workflow. # ─────────────────────────────────────────────────────────────────────── set -euo pipefail B='\033[1m' D='\033[2m' R='\033[0m' U='\033[4m' G='\033[32m' Y='\033[33m' RD='\033[31m' M='\033[35m' OK="${G}●${R}" WARN="${Y}●${R}" FAIL="${RD}●${R}" 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; panel will use unauthenticated API calls" >&2 echo "" echo -e " ${B}${M}◈ WORKFLOW OPERATIONS${R} ${D}$(date '+%a %b %d %H:%M:%S')${R}" echo -e " ${D}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${R}" echo "" echo -e " ${B}${U}SERVICES${R}" echo "" GW_PID=$(pgrep -f "hermes.*gateway.*run" 2>/dev/null | head -1 || true) if [ -n "${GW_PID:-}" ]; then echo -e " ${OK} Hermes Gateway ${D}pid $GW_PID${R}" else echo -e " ${FAIL} Hermes Gateway ${RD}down${R}" fi if curl -s --max-time 3 "$GITEA_URL/api/v1/version" >/dev/null 2>&1; then echo -e " ${OK} Gitea ${D}${GITEA_URL}${R}" else echo -e " ${FAIL} Gitea ${RD}unreachable${R}" fi if hermes cron list >/dev/null 2>&1; then echo -e " ${OK} Hermes Cron ${D}reachable${R}" else echo -e " ${WARN} Hermes Cron ${Y}not responding${R}" fi FRESHNESS_OUTPUT=$("$HOME/.hermes/bin/pipeline-freshness.sh" 2>/dev/null || true) FRESHNESS_STATUS=$(printf '%s\n' "$FRESHNESS_OUTPUT" | awk -F= '/^status=/{print $2}') FRESHNESS_REASON=$(printf '%s\n' "$FRESHNESS_OUTPUT" | awk -F= '/^reason=/{print $2}') if [ "$FRESHNESS_STATUS" = "ok" ]; then echo -e " ${OK} Export Freshness ${D}${FRESHNESS_REASON:-within freshness window}${R}" elif [ -n "$FRESHNESS_STATUS" ]; then echo -e " ${WARN} Export Freshness ${Y}${FRESHNESS_REASON:-lagging}${R}" else echo -e " ${WARN} Export Freshness ${Y}unknown${R}" fi echo "" python3 - "$GITEA_URL" "$TOKEN" "$CORE_REPOS" <<'PY' import json import sys import urllib.error import urllib.request from datetime import datetime, timedelta, timezone base = sys.argv[1].rstrip("/") token = sys.argv[2] repos = sys.argv[3].split() headers = {"Authorization": f"token {token}"} if token else {} def fetch(path): req = urllib.request.Request(f"{base}{path}", headers=headers) with urllib.request.urlopen(req, timeout=5) as resp: return json.loads(resp.read().decode()) def short(repo): return repo.split("/", 1)[1] issues = [] pulls = [] review_queue = [] errors = [] for repo in repos: try: repo_pulls = fetch(f"/api/v1/repos/{repo}/pulls?state=open&limit=20") for pr in repo_pulls: pr["_repo"] = repo pulls.append(pr) repo_issues = fetch(f"/api/v1/repos/{repo}/issues?state=open&limit=50&type=issues") for issue in repo_issues: issue["_repo"] = repo issues.append(issue) repo_pull_issues = fetch(f"/api/v1/repos/{repo}/issues?state=open&limit=50&type=pulls") for item in repo_pull_issues: assignees = [a.get("login", "") for a in (item.get("assignees") or [])] if any(name in assignees for name in ("Timmy", "allegro")): item["_repo"] = repo review_queue.append(item) except urllib.error.URLError as exc: errors.append(f"{repo}: {exc.reason}") except Exception as exc: # pragma: no cover - defensive panel path errors.append(f"{repo}: {exc}") print(" \033[1m\033[4mREVIEW QUEUE\033[0m\n") if not review_queue: print(" \033[2m(clear)\033[0m\n") else: for item in review_queue[:8]: names = ",".join(a.get("login", "") for a in (item.get("assignees") or [])) print(f" #{item['number']:<4d} {short(item['_repo']):12s} {names[:20]:20s} {item['title'][:44]}") print() print(" \033[1m\033[4mOPEN PRS\033[0m\n") if not pulls: print(" \033[2m(none open)\033[0m\n") else: for pr in pulls[:8]: print(f" #{pr['number']:<4d} {short(pr['_repo']):12s} {pr['user']['login'][:12]:12s} {pr['title'][:48]}") print() print(" \033[1m\033[4mDISPATCH QUEUES\033[0m\n") queue_agents = [ ("allegro", "dispatch"), ("codex-agent", "cleanup"), ("groq", "fast ship"), ("claude", "refactor"), ("ezra", "archive"), ("perplexity", "research"), ("KimiClaw", "digest"), ] for agent, label in queue_agents: assigned = [ issue for issue in issues if agent in [a.get("login", "") for a in (issue.get("assignees") or [])] ] print(f" {agent:12s} {len(assigned):2d} \033[2m{label}\033[0m") print() unassigned = [issue for issue in issues if not issue.get("assignees")] stale_cutoff = (datetime.now(timezone.utc) - timedelta(days=2)).strftime("%Y-%m-%d") stale_prs = [pr for pr in pulls if pr.get("updated_at", "")[:10] < stale_cutoff] overloaded = [] for agent in ("allegro", "codex-agent", "groq", "claude", "ezra", "perplexity", "KimiClaw"): count = sum( 1 for issue in issues if agent in [a.get("login", "") for a in (issue.get("assignees") or [])] ) if count > 3: overloaded.append((agent, count)) print(" \033[1m\033[4mWARNINGS\033[0m\n") warns = [] if len(unassigned) > 10: warns.append(f"{len(unassigned)} unassigned issues across core repos") if stale_prs: warns.append(f"{len(stale_prs)} open PRs look stale and may need a review nudge") for agent, count in overloaded: warns.append(f"{agent} has {count} assigned issues; rebalance dispatch") if warns: for warn in warns: print(f" \033[33m⚠ {warn}\033[0m") else: print(" \033[2m(no major workflow warnings)\033[0m") if errors: print("\n \033[1m\033[4mFETCH ERRORS\033[0m\n") for err in errors[:4]: print(f" \033[31m{err}\033[0m") PY echo "" echo -e " ${D}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${R}" echo -e " ${D}repos: $(printf '%s' "$CORE_REPOS" | wc -w | tr -d ' ') refresh via watch or rerun script${R}"