From aradotso-trending-skills-37
Serves real-time React/Vite dashboard monitoring Hermes AI agent consciousness from ~/.hermes/ files via FastAPI backend with WebSocket and file watcher.
npx claudepluginhub joshuarweaver/cascade-ai-ml-agents-misc-1 --plugin aradotso-trending-skills-37This skill uses the workspace's default tool permissions.
```markdown
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Generates original PNG/PDF visual art via design philosophy manifestos for posters, graphics, and static designs on user request.
---
name: hermes-hudui-consciousness-monitor
description: Web UI consciousness monitor for Hermes AI agent with persistent memory — FastAPI backend + React frontend dashboard
triggers:
- set up hermes hud web ui
- monitor hermes agent in browser
- hermes hudui dashboard
- show hermes agent consciousness monitor
- hermes web ui not working
- add token cost tracking hermes
- hermes hud websocket updates
- configure hermes hudui themes
---
# ☤ Hermes HUD Web UI
> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.
A browser-based consciousness monitor for the Hermes AI agent. Reads from `~/.hermes/` data files and serves a real-time React dashboard via a FastAPI backend with WebSocket support.
## Architecture Overview
React Frontend (Vite + SWR) ↓ /api/* + WebSocket /ws FastAPI Backend (Python 3.11+) ↓ collectors/*.py + cache + file watcher ~/.hermes/ (agent data files)
- **Backend**: FastAPI with collectors, mtime-based cache, watchfiles watcher
- **Frontend**: React + Vite + SWR with silent background updates and auto-reconnect WebSocket
- **Data source**: `~/.hermes/` directory — no database, no external APIs
## Installation
### Quick Install
```bash
git clone https://github.com/joeynyc/hermes-hudui.git
cd hermes-hudui
python3.11 -m venv venv
source venv/bin/activate
./install.sh
hermes-hudui
python3.11 -m venv venv
source venv/bin/activate
# Install Python package
pip install -e .
# Build frontend and copy to backend static dir
cd frontend
npm install
npm run build
cp -r dist/* ../backend/static/
# Start server
hermes-hudui
source venv/bin/activate
hermes-hudui
pip install hermes-hudui[tui]
~/.hermes/| Command | Description |
|---|---|
hermes-hudui | Start the web server on port 3001 |
./install.sh | Full install: venv setup + pip install + frontend build |
Collectors are Python modules in backend/collectors/ that read ~/.hermes/ and return dataclasses. Each collector corresponds to a dashboard panel.
# backend/collectors/identity.py
from dataclasses import dataclass
from pathlib import Path
import json
HERMES_DIR = Path.home() / ".hermes"
@dataclass
class IdentityData:
designation: str
substrate: str
runtime: str
days_conscious: int
brain_size_mb: float
def collect_identity() -> IdentityData:
config_path = HERMES_DIR / "config.json"
if not config_path.exists():
return IdentityData(
designation="Unknown",
substrate="unknown",
runtime="unknown",
days_conscious=0,
brain_size_mb=0.0,
)
data = json.loads(config_path.read_text())
return IdentityData(
designation=data.get("designation", "Hermes"),
substrate=data.get("substrate", "unknown"),
runtime=data.get("runtime", "unknown"),
days_conscious=data.get("days_conscious", 0),
brain_size_mb=data.get("brain_size_mb", 0.0),
)
# backend/main.py (simplified pattern)
from fastapi import FastAPI
from backend.collectors.identity import collect_identity
from backend.cache import cached
app = FastAPI()
@app.get("/api/identity")
async def identity():
return cached("identity", collect_identity)
The cache uses mtime-based invalidation. TTLs by data type:
| Data Type | TTL |
|---|---|
| Sessions | 30s |
| Skills | 60s |
| Patterns | 60s |
| Profiles | 45s |
from backend.cache import cached
# Simple usage — key + callable
result = cached("sessions", collect_sessions)
# With TTL override
result = cached("identity", collect_identity, ttl=10)
The file watcher (watchfiles) monitors ~/.hermes/ and invalidates relevant cache keys when files change, triggering WebSocket broadcasts to all clients.
The WebSocket endpoint at /ws broadcasts data_changed events when ~/.hermes/ files change.
# backend/ws.py (simplified)
from fastapi import WebSocket
import asyncio
import json
connected_clients: list[WebSocket] = []
async def broadcast_change(event: str = "data_changed"):
message = json.dumps({"type": event})
dead = []
for ws in connected_clients:
try:
await ws.send_text(message)
except Exception:
dead.append(ws)
for ws in dead:
connected_clients.remove(ws)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
connected_clients.append(websocket)
try:
while True:
await websocket.receive_text() # keep alive
except Exception:
connected_clients.remove(websocket)
Each panel fetches its own API endpoint with keepPreviousData to avoid loading flashes.
// frontend/src/hooks/useIdentity.ts
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
export function useIdentity() {
return useSWR("/api/identity", fetcher, {
keepPreviousData: true,
revalidateOnFocus: false,
});
}
Auto-reconnects with exponential backoff and triggers SWR revalidation on data_changed events.
// frontend/src/hooks/useHermesSocket.ts
import { useEffect, useRef } from "react";
import { mutate } from "swr";
export function useHermesSocket() {
const wsRef = useRef<WebSocket | null>(null);
const retryDelay = useRef(1000);
function connect() {
const ws = new WebSocket(`ws://${location.host}/ws`);
wsRef.current = ws;
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
if (msg.type === "data_changed") {
// Revalidate all SWR keys
mutate(() => true, undefined, { revalidate: true });
}
};
ws.onclose = () => {
setTimeout(() => {
retryDelay.current = Math.min(retryDelay.current * 2, 30000);
connect();
}, retryDelay.current);
};
ws.onopen = () => {
retryDelay.current = 1000; // reset on success
};
}
useEffect(() => {
connect();
return () => wsRef.current?.close();
}, []);
}
// frontend/src/panels/IdentityPanel.tsx
import { useIdentity } from "../hooks/useIdentity";
export function IdentityPanel() {
const { data, isLoading } = useIdentity();
// Show stale data while refreshing — no loading flash
if (!data) return <div className="panel-loading">Loading…</div>;
return (
<div className="panel">
<h2>Identity</h2>
<dl>
<dt>Designation</dt>
<dd>{data.designation}</dd>
<dt>Days Conscious</dt>
<dd>{data.days_conscious}</dd>
<dt>Brain Size</dt>
<dd>{data.brain_size_mb.toFixed(1)} MB</dd>
</dl>
</div>
);
}
Costs are calculated from token counts using hardcoded per-model pricing in the backend.
| Provider | Model | Input | Output | Cache Read |
|---|---|---|---|---|
| Anthropic | Claude Opus 4 | $15/M | $75/M | $1.50/M |
| Anthropic | Claude Sonnet 4 | $3/M | $15/M | $0.30/M |
| Anthropic | Claude Haiku 3.5 | $0.80/M | $4/M | $0.08/M |
| OpenAI | GPT-4o | $2.50/M | $10/M | $1.25/M |
| OpenAI | o1 | $15/M | $60/M | $7.50/M |
| DeepSeek | V3 | $0.27/M | $1.10/M | $0.07/M |
| xAI | Grok 3 | $3/M | $15/M | $0.75/M |
| Gemini 2.5 Pro | $1.25/M | $10/M | $0.31/M |
Unknown models fall back to Claude Opus pricing. Local/free models are detected and priced at $0.
# backend/collectors/costs.py (pattern)
PRICING = {
"claude-opus-4": {"input": 15.0, "output": 75.0, "cache_read": 1.50},
"claude-sonnet-4": {"input": 3.0, "output": 15.0, "cache_read": 0.30},
"gpt-4o": {"input": 2.50, "output": 10.0, "cache_read": 1.25},
# ... etc
}
def calculate_cost(model: str, input_tokens: int, output_tokens: int, cache_tokens: int = 0) -> float:
# Detect local/free models
if any(tag in model.lower() for tag in ["local", "ollama", "lmstudio"]):
return 0.0
pricing = PRICING.get(model, PRICING["claude-opus-4"]) # fallback
cost = (
(input_tokens / 1_000_000) * pricing["input"]
+ (output_tokens / 1_000_000) * pricing["output"]
+ (cache_tokens / 1_000_000) * pricing["cache_read"]
)
return round(cost, 6)
Switch themes with the t key or the theme picker UI.
| Theme | Key | Description |
|---|---|---|
| Neural Awakening | ai | Cyan/blue on deep navy |
| Blade Runner | blade-runner | Amber/orange on warm black |
| fsociety | fsociety | Green on pure black |
| Anime | anime | Purple/violet on indigo |
CRT scanline overlay is togglable via the theme picker.
// frontend/src/theme.ts
type Theme = "ai" | "blade-runner" | "fsociety" | "anime";
export function setTheme(theme: Theme) {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("hermes-theme", theme);
}
export function getTheme(): Theme {
return (localStorage.getItem("hermes-theme") as Theme) ?? "ai";
}
| Key | Action |
|---|---|
1–9, 0 | Switch dashboard tabs |
t | Toggle theme picker |
Ctrl+K | Open command palette |
| Tab | Data Source | Description |
|---|---|---|
| Identity | ~/.hermes/config.json | Designation, substrate, uptime |
| What I Know | Session logs | Conversations, messages, actions, skills |
| What I Remember | Memory files | Capacity bars, user profile, corrections |
| What I See | API key files | Key presence, service health |
| What I'm Learning | Skills directory | Recently modified skills with categories |
| What I'm Working On | Project files | Active projects, dirty file status |
| While You Sleep | Cron config | Scheduled jobs |
| How I Think | Tool logs | Tool usage patterns with gradient bars |
| My Rhythm | Activity logs | Daily activity sparkline |
| Growth Delta | Snapshots | Diffs showing what changed |
| Token Costs | Token logs | Per-model USD cost with daily trend |
# 1. Create collector
# backend/collectors/my_feature.py
from dataclasses import dataclass
from pathlib import Path
HERMES_DIR = Path.home() / ".hermes"
@dataclass
class MyFeatureData:
value: str
def collect_my_feature() -> MyFeatureData:
path = HERMES_DIR / "my_feature.json"
if not path.exists():
return MyFeatureData(value="none")
import json
return MyFeatureData(**json.loads(path.read_text()))
# 2. Register route in backend/main.py
from backend.collectors.my_feature import collect_my_feature
from backend.cache import cached
@app.get("/api/my-feature")
async def my_feature():
return cached("my_feature", collect_my_feature, ttl=30)
# 3. Create frontend hook
# frontend/src/hooks/useMyFeature.ts
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then(r => r.json());
export const useMyFeature = () =>
useSWR("/api/my-feature", fetcher, { keepPreviousData: true });
~/.hermes/ Existsfrom pathlib import Path
HERMES_DIR = Path.home() / ".hermes"
if not HERMES_DIR.exists():
raise RuntimeError(
"No ~/.hermes/ directory found. "
"Make sure the Hermes agent has run at least once."
)
def safe_read_json(path: Path, default=None):
try:
return json.loads(path.read_text()) if path.exists() else default
except (json.JSONDecodeError, OSError):
return default
cd frontend
npm run build
cp -r dist/* ../backend/static/
During development, run the Vite dev server separately (proxy configured in vite.config.ts to forward /api and /ws to the FastAPI backend on port 3001).
# Terminal 1 — backend
source venv/bin/activate
hermes-hudui
# Terminal 2 — frontend dev server with HMR
cd frontend
npm run dev
# Opens http://localhost:5173 with proxy to :3001
# Find and kill the process using port 3001
lsof -ti:3001 | xargs kill -9
hermes-hudui
The frontend static files may not be built or copied:
cd frontend
npm install
npm run build
cp -r dist/* ../backend/static/
hermes-hudui
hermes-hudui)# Check that ~/.hermes/ exists and has files
ls ~/.hermes/
# Check backend logs for collector errors
hermes-hudui # errors printed to stdout
watchfiles Not Detecting Changespip install --upgrade watchfiles
On Linux, you may need to increase inotify limits:
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
The install script detects WSL automatically. If the browser can't connect:
# Get WSL IP
hostname -I | awk '{print $1}'
# Open http://<wsl-ip>:3001 instead of localhost
python3.11 --version # must be 3.11+
python3.11 -m venv venv # explicitly use 3.11
| Platform | Status |
|---|---|
| macOS | Native via ./install.sh |
| Linux | Native via ./install.sh |
| Windows | Via WSL only |
| WSL | Auto-detected by install script |
Both read from the same ~/.hermes/ directory independently. The Web UI (hermes-hudui) is fully standalone — it ships its own collectors. It adds features the TUI lacks:
Ctrl+K)Install both side by side:
pip install hermes-hud # TUI version
pip install hermes-hudui[tui] # Web UI + TUI bridge