Metadata-Version: 2.5
Name: claudehub-monitor
Version: 0.2.0
Summary: Desktop and LAN web dashboard for Claude Code hook events
Author: Pandiyaraj Karuppasamy
License-Expression: MIT
License-File: LICENSE
Keywords: claude,claude-code,dashboard,monitor,pyside6
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Win32 (MS Windows)
Classifier: Environment :: X11 Applications :: Qt
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: User Interfaces
Requires-Python: >=3.11
Requires-Dist: anthropic>=0.40
Requires-Dist: pyside6>=6.6
Provides-Extra: hooks
Requires-Dist: claudehub-hooks>=0.1.0; extra == 'hooks'
Provides-Extra: test
Requires-Dist: pytest>=7.0; extra == 'test'
Description-Content-Type: text/markdown

<!--
Author: Pandiyaraj Karuppasamy
-->

# Claudehub Monitor

A desktop **and** LAN web dashboard that shows what Claude Code is doing in real
time — across every repo and machine on your network.

`claudehub-monitor` is the central collector: Claude Code hooks POST lifecycle
events to it over HTTP, and it surfaces them as an animated desktop card, system
sounds, a scrolling history, a usage quota toast, and a live web page you can
open from your phone.

```
  Claude Code (any repo / any machine)
        │  hook fires  →  claudehub-hooks  →  HTTP POST /event
        ▼
  claudehub-monitor  ──►  desktop flash card + toasts + sounds + history (SQLite)
        │
        └─►  web dashboard  ◄── browser on the LAN (SSE live updates)
```

## Quick start

1. **Start the monitor** (once per machine):

```bash
pip install claudehub-monitor
claudehub          # short alias
claudehub-monitor  # full name (same command)
```

2. **Wire hooks into each repo** you use with Claude Code:

```bash
pip install claudehub-hooks
claudehub-hooks install
claudehub-hooks install --url http://192.168.1.50:7070/event   # remote monitor
```

3. Open Claude Code in that repo — events flow to the monitor automatically.

Test hook connectivity:

```bash
python .claude/hooks/claude_hook.py --test
```

## Install

**Monitor** (`claudehub-monitor`):

```bash
pip install claudehub-monitor
claudehub          # short alias
claudehub-monitor  # full name (same command)
```

**Hooks** (`claudehub-hooks` — separate package, stdlib only):

```bash
pip install claudehub-hooks
claudehub-hooks install
```

## Components

| Package / path | Role |
|----------------|------|
| `claudehub-monitor` (`claudehub/monitor.py`) | Monitor: HTTP server, desktop UI, web dashboard, SQLite store. |
| `claudehub-hooks` | Pip installer that writes `.claude/hooks/claude_hook.py` + `settings.json` entries. |
| `claudehub/dashboard.html` | Web dashboard page (served at `/`). |
| `monitor_config.json` | Example monitor config (copy to `~/.claude/monitor_config.json`). |
| `.claude/hooks/monitor_config.json` | Per-repo hook transport config (written by `claudehub-hooks install`). |
| `~/.claude/claude_events.db` | SQLite history + per-session status (auto-created). |

## Hook events

`claudehub-hooks install` registers these Claude Code hooks:

| Event | When it fires | Monitor behavior |
|-------|---------------|------------------|
| `SessionStart` | Session begins | Card shows “Session Started”; history entry |
| `UserPromptSubmit` | User sends a message | **Thinking** — ring animation, persistent toast |
| `PostToolUse` | Tool used (matcher: `Edit\|Write\|NotebookEdit\|MultiEdit\|Read`) | **Reading** (Read) or **Editing** (others); brief flash → back to thinking |
| `Stop` | Turn completes | **Done** — green tick, ding, clears session |
| `StopFailure` | API / runtime error | **Error** — red card, pong sound |
| `Notification` | Claude needs input | **Waiting** — persistent ask toast |
| `PermissionRequest` | Allow/deny dialog shown | **Waiting** — permission toast + orange card |
| `PermissionGranted` | User approved a tool | Resumes **Thinking**; history shows “Allowed ✓” |
| `PermissionDenied` | User denied a tool | Resumes **Thinking**; history shows “Denied ✗” |
| `PreToolUse` | Tool about to run | Resolves pending permission → **Thinking** (implicit allow for sub-tools) |

Hook self-test (`claude_hook.py --test`) sends `__test__` — shown on the card as a purple “Test” flash (not recorded in history).

### Event payload (from hooks)

Hooks POST JSON to `POST /event`:

```jsonc
{
  "event": "PostToolUse",
  "datetime": "<ISO-8601 UTC>",
  "session_id": "<str>",
  "host": "<hostname>",
  "cwd": "<working dir>",
  "files": ["<path>"],           // PostToolUse
  "prompt": "<first 500 chars>", // UserPromptSubmit
  "tool_name": "<tool>",         // PostToolUse / Permission* / PreToolUse
  "command": "<cmd>",            // Permission* / PreToolUse
  "notification_type": "<type>", // Notification
  "error_type": "<type>",        // StopFailure
  "error_message": "<msg>"       // StopFailure
}
```

## Status model

Each surfaced status maps to colour, icon, and animation on the desktop:

| Status | Trigger (hook event) | Colour | Desktop animation |
|--------|----------------------|--------|-------------------|
| Thinking | `UserPromptSubmit` | blue | rotating arc ring |
| Reading | `PostToolUse` (Read) | teal | sweeping scan bar |
| Editing | `PostToolUse` (Edit/Write/…) | blue | rippling bars |
| Waiting | `PermissionRequest` / `Notification` | amber | persistent ask toast |
| Allowed | `PermissionGranted` / `PreToolUse` (after approve) | green | ding; back to thinking |
| Denied | `PermissionDenied` | red | thud; back to thinking |
| Done | `Stop` | green | ✓ tick + ding |
| Error | `StopFailure` | red | pong |

Read/Edit briefly flash, then the card returns to **Thinking** until the turn
ends (`Stop`) — mirroring how Claude actually works. The desktop card shows the
status **name**; the history table and web feed show the matching **icon**.

## Usage toast

When Claude Code OAuth credentials exist (`~/.claude/.credentials.json`), a fixed
top-right toast shows subscription quota:

- **5-hour session** and **7-day weekly** utilization (%)
- Reset times for each window
- Refreshed every 5 minutes (one minimal Haiku probe call per refresh)

If credentials are missing, the toast is skipped.

## Run it

```bat
claudehub
claudehub-monitor
python -m claudehub
```

The app starts **hidden in the system tray** by default (the HTTP server and web
dashboard run regardless). Double-click the tray icon to open the window;
"Minimize to Tray" or closing the window hides it again; the tray menu's **Quit**
exits. Only one instance runs per machine.

## Web dashboard (view on the go)

With the monitor running, open from any device on the LAN:

```
http://<monitor-ip>:7070/
```

It shows a live grid of active sessions, the last prompt, and a recent-activity
feed — pushed over **Server-Sent Events** (auto-reconnecting), with a JSON
snapshot for the initial load.

**Group by repo**: check "Group by repo" in the header to re-arrange the
session grid into one section per repository (derived from each hook's
reported `cwd`), rather than one flat grid. Useful once several repos are
reporting to the same monitor at once — a session with no `cwd` on record
groups under "(unknown)" rather than being dropped.

**History & search**: the collapsed "History & search" panel below the recent-
activity feed queries the full event history (not just the last few events),
filterable by host, repo, event type, and date range. It only fetches once
opened, so a normal dashboard view pays nothing for it.

**Subagent count**: when a session launches one or more subagents (Claude
Code's `Task` tool), its card shows a small "+N agents running" badge for as
long as any are still in flight. This is a count, not a breakdown — see
"Subagent visibility" below for why the hook payload doesn't support more.

Open TCP 7070 on the monitor machine's firewall:

```powershell
New-NetFirewallRule -DisplayName "Claude Monitor" -Direction Inbound `
  -Protocol TCP -LocalPort 7070 -Action Allow
```

## HTTP API

| Method / path | Purpose |
|---------------|---------|
| `POST /event` | Ingest one hook event (from `claudehub-hooks` dispatcher). |
| `GET /` | The web dashboard. |
| `GET /api/status` | Active sessions snapshot + last prompt (JSON). |
| `GET /api/events` | Recent events feed (JSON). |
| `GET /api/history` | Paginated event history with filters — see below (JSON). |
| `GET /events/stream` | Live event stream (SSE). |
| `GET /health` | `{"status":"ok"}`. |

### `GET /api/history` query parameters

Queries the full `events` table directly, not the small capped in-memory feed
`/api/events` reads from.

| Param | Meaning |
|-------|---------|
| `host` | Exact match on reporting hostname. |
| `repo` | Exact match on the derived repo label (see per-repo grouping above). |
| `event` | Exact match on event name, e.g. `PostToolUse`. |
| `session_id` | Exact match on session id. |
| `since` / `until` | ISO-8601 UTC bounds on when the event was received. |
| `limit` | Rows per page, default 50, capped at 500. |
| `offset` | Rows to skip, for paging. |

Response: `{"events": [...], "total": <int>, "limit": <int>, "offset": <int>}`.

Note: `since`/`until` filter on a column (`received_at_iso`) added in 0.2.0.
Events recorded by an older version have it unset and are excluded from any
date-filtered query rather than matching incorrectly — the older
`received_at` column is local time-of-day only (no date), so it cannot
support a range filter at all.

## Configuration

### Monitor (`claudehub-monitor`)

Copy `monitor_config.json` to `~/.claude/monitor_config.json`, or set
`CLAUDE_MONITOR_CONFIG` to point at a file:

```jsonc
{
  "transport": { "http": { "enabled": true, "url": "http://127.0.0.1:7070/event", "timeout_s": 2.0 } },
  "monitor":   { "http_host": "0.0.0.0", "http_port": 7070, "history_max": 50, "start_hidden": true },
  "logging":   { "level": "INFO", "file": "", "max_bytes": 1048576, "backup_count": 5 },
  "sounds":    { "enabled": true },
  "notifications": { "enabled": false }
}
```

`notifications.enabled` (default `false`) turns on Windows action-center
notifications for **Waiting** and **Error** states, in addition to — not
instead of — the existing sound and in-app toast. Uses the tray icon already
created for the system-tray feature, so there's no extra dependency; it is a
no-op if there's no system tray available.

Monitor env vars (override the file): `CLAUDE_MONITOR_CONFIG`, `CLAUDE_MONITOR_PORT`,
`CLAUDE_MONITOR_HOST`, `CLAUDE_MONITOR_SOUNDS`, `CLAUDE_MONITOR_NOTIFY`,
`CLAUDE_MONITOR_LOG_LEVEL`, `CLAUDE_MONITOR_LOG_FILE`.

Logs and the SQLite database default to `~/.claude/`.

### Hooks (`claudehub-hooks`)

Per-repo transport config lives at `.claude/hooks/monitor_config.json` (created
by `claudehub-hooks install`). Point hooks at a monitor on another machine:

```json
{
  "transport": {
    "http": { "url": "http://192.168.1.50:7070/event" }
  }
}
```

Hook env vars (override the file):

| Variable | Default | Meaning |
|----------|---------|---------|
| `CLAUDE_HUB_URL` | `http://127.0.0.1:7070/event` | Monitor endpoint |
| `CLAUDE_HUB_TIMEOUT` | `2.0` | HTTP timeout (seconds) |
| `CLAUDE_HUB_HTTP_ENABLED` | `true` | Set to `false` to silence all hooks |
| `CLAUDE_HUB_CONFIG` | — | Path to a `monitor_config.json` override |
| `CLAUDE_HOOK_LOG_LEVEL` | `INFO` | Dispatcher log level |
| `CLAUDE_HOOK_LOG_FILE` | `~/.claude/claude_hook.log` | Dispatcher log path |

## Subagent visibility

The card and dashboard show a **count** of subagents currently in flight under
a session ("+2 agents running") — incremented on the `PreToolUse` for Claude
Code's `Task` tool, decremented on the matching `PostToolUse`.

This is deliberately a count, not a per-subagent breakdown. A subagent's own
inner tool calls (its own Reads, Edits, etc.) do not reach this monitor's
hooks at all — only the *outer* call, the parent session invoking the `Task`
tool, produces a `PreToolUse`/`PostToolUse` pair. Building true nested
visibility (each subagent's own activity, individually) is not something the
current Claude Code hook contract supports from the monitor side; it would
need a change on that side, not here.

## Multi-instance & reopen recovery

The monitor tracks status **per session** in SQLite, so:

- Several Claude instances (local or remote) can report at once — the card shows
  the latest, the history/web distinguish by host + session.
- If you close and reopen the monitor mid-turn, it restores the in-progress
  state from `session_status`; a 2-second watchdog idles a session that goes
  silent.

## Requirements

- Python 3.11+ (monitor); Python 3.10+ (hooks)
- `claudehub-monitor`: PySide6 and anthropic (installed automatically)
- `claudehub-hooks`: stdlib only
- Windows for sounds (`winsound`); silently disabled elsewhere.
