Real-time dashboard for monitoring cc-connect on macOS — processes, sessions, token usage, errors, trends, and alerts.
🌏 中文用户:README.zh-CN.md · Chinese users: see README.zh-CN.md.
A zero-dependency, raw-socket HTTP server that watches Claude Code + Codex CLI bridges and renders a glass-morphism dashboard with sub-second refresh. Designed to surface the failure modes that hide when you only look at the bridge status (dead sessions, near-limit context, jsonl pollution, watchdog alerts).
When you operate cc-connect as a Feishu bridge for Claude Code / Codex, the failure modes that actually matter are not "is the process up" — they are:
- A session is stuck on a half-acknowledged tool call (Codex 2013)
- A rollout has been deleted but the session JSON still points at it (Codex 32600)
- A Claude session has silently grown past 200K context (token budget gone)
- A Codex
.jsonlhas bloated to 10 MB and the session is near limit - The bridge is alive but its Feishu WebSocket dropped and it is not receiving messages
This dashboard answers all of those in one screen, with a 4-tier refresh architecture so each layer updates at the rate it actually needs.
| Tier | Interval | What | Endpoint |
|---|---|---|---|
| Realtime | 1 s | Marquee + process / token counters | GET /api/realtime |
| Full | 5 s | Sessions table, error panel, system health | GET /api/full |
| Alerts | 30 s | Evaluate alert state, dedup, write to file, optional Feishu push | internal thread |
| Snapshots | 1 min | Persist metrics to SQLite for trend chart | GET /api/snapshots |
- Glass-morphism cards (top): cc-connect / Codex / Claude / Errors
- 60-LED marquee with sheen animation: process activity per second
- Token usage bar with real
usage.input_tokens(notchars/3.5estimates — those are off by 3-4×) - Session table: click any row to inspect the last N messages of its
.jsonl - Error panel: original English log + side-by-side Chinese translation for the common Codex/Codex panic patterns
- Trend charts (Chart.js): context %, CPU, memory, load avg, error count, Feishu connections
- 🔴 RED — process dead,
9820HTTP non-200, no Feishu long-poll, Claude session over context window - 🟡 YELLOW — Codex/Claude near-limit (≥ 80 %), new
level=ERRORin cc-connect log, watchdog stale > 30 min - 10-minute dedup (key = scenario + entity) so the master is not spammed
- Push to Feishu (optional, requires
FEISHU_APP_ID/SECRET/OPEN_ID) and always write tomonitor.alert(for HEARTBEAT-based delivery)
# 1. Drop the files
git clone <this-repo>
cd CC-Agent-Monitor
# 2. Install the launchd service
cp docs/com.cc-agent-monitor.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.cc-agent-monitor.plist
launchctl kickstart -k gui/$(id -u)/com.cc-agent-monitor
# 3. Open the dashboard
open http://localhost:9870/That's it. No pip install, no virtualenv, no Node. Pure Python 3.8+ standard library.
All knobs are environment variables (set them in the plist's EnvironmentVariables dict).
| Variable | Default | Purpose |
|---|---|---|
MONITOR_HOST |
0.0.0.0 |
Bind address |
MONITOR_PORT |
9870 |
HTTP port |
FEISHU_APP_ID |
(empty) | Enable alert push to Feishu |
FEISHU_APP_SECRET |
(empty) | Enable alert push to Feishu |
FEISHU_OPEN_ID |
(empty) | Target Feishu user |
If any of the three Feishu vars is missing, alerts are written to monitor.alert and the host's HEARTBEAT-based delivery is expected to surface them.
+-------------------+
| browser (any) |
| fetch /api/* |
+---------+---------+
| HTTP
v
+---------------------------------------------------+
| server.py |
| |
| +----------+ +-----------+ +---------+ +----+ |
| | realtime | | alerts | |snapshot | |HTTP| |
| | 1s loop | | 30s loop | | 60s loop| | rxd| |
| +----+-----+ +-----+-----+ +----+----+ +----+ |
| | | | |
| +-------+-----+-------------+ |
| | |
| v |
| +-------------+ |
| | collector.py| |
| | (stat-only) | |
| +------+------+ |
+---------------|----------------------------------+
|
v
+-------+-------+--------+----------+
| .cc-connect | .codex | .claude | system
+---------------+--------+----------+
collector.py is stat-only for the 1 s path (os.stat + pgrep) — it never reads file bodies on the hot path, so the cost is sub-millisecond. Heavier reads (.jsonl tail, last_token_usage extraction) are on the 5 s path.
Token percentages come straight from the API usage fields, not character estimates:
# Claude (Anthropic): input_tokens does NOT include cache; sum all three
usage = event["message"]["usage"]
tokens = (usage.get("input_tokens", 0) +
usage.get("cache_read_input_tokens", 0) +
usage.get("cache_creation_input_tokens", 0))
# Codex (OpenAI): input_tokens DOES include cache; use last_token_usage
if event["type"] == "event_msg" and event["payload"]["type"] == "token_count":
last = event["payload"]["info"]["last_token_usage"]
tokens = last["input_tokens"]chars/3.5 estimation is 3-4× off in both directions (overstates Claude, understates Codex). See docs/token-accuracy.md for measurements.
- Write a
collect_*()function incollector.pyreturning a JSON-serializable dict. Keep the 1 s pathstat-only. - Add the key to the
_cache_fulldict insidecollect_full(). - Add a tile in
_HTMLand arender*()JavaScript function.
The dashboard is one Python file and one HTML string. No build step.
# Smoke test the collector (no server)
python3 collector.py
# Verbose server log
MONITOR_DEBUG=1 python3 server.pyCC-Agent-Monitor/
├── server.py # raw-socket HTTP + 4 background threads
├── collector.py # zero-dep data collection (stat-only hot path)
├── docs/
│ ├── com.cc-agent-monitor.plist
│ └── dashboard.png
├── README.md
├── LICENSE
└── .gitignore
MIT — see LICENSE.
Built for monitoring cc-connect, a Feishu bridge for Claude Code / Codex CLI.
