From 48d2caf57cf1cb56eaf63281a807b2dcad831b69 Mon Sep 17 00:00:00 2001 From: "felix.zoesch" Date: Mon, 15 Dec 2025 10:51:38 +0100 Subject: [PATCH] Add user prompt text display and agents graph tab MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Features: - User prompt hook now captures and displays actual prompt text - Added tab switching between Event Feed and Agents Graph - Created AgentsGraph component with placeholder - Added CSS styling for agents graph view 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- .claude/hooks/user_prompt.sh | 5 +- CLAUDE.md | 205 ++++++++++++++++++++++++ frontend/src/components/AgentsGraph.tsx | 19 +++ frontend/src/components/EventFeed.tsx | 59 ++++--- frontend/src/styles/index.css | 33 ++++ 5 files changed, 300 insertions(+), 21 deletions(-) create mode 100644 CLAUDE.md create mode 100644 frontend/src/components/AgentsGraph.tsx diff --git a/.claude/hooks/user_prompt.sh b/.claude/hooks/user_prompt.sh index af447c3..2a07e33 100755 --- a/.claude/hooks/user_prompt.sh +++ b/.claude/hooks/user_prompt.sh @@ -7,18 +7,21 @@ INPUT=$(cat) # Extract fields using jq SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"') -PROMPT_LENGTH=$(echo "$INPUT" | jq -r '.prompt_length // 0') +PROMPT=$(echo "$INPUT" | jq -r '.prompt // .text // ""') +PROMPT_LENGTH=${#PROMPT} TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp // (now | tonumber)') # Build payload for backend PAYLOAD=$(jq -n \ --arg session_id "$SESSION_ID" \ --arg event_type "UserPromptSubmit" \ + --arg prompt "$PROMPT" \ --arg prompt_length "$PROMPT_LENGTH" \ --arg timestamp "$TIMESTAMP" \ '{ session_id: $session_id, event_type: $event_type, + tool_input: $prompt, description: ("User submitted prompt (" + $prompt_length + " chars)"), timestamp: ($timestamp | tonumber) }') diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..fed8e06 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,205 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Claude Code Monitor is a real-time monitoring dashboard for tracking Claude Code activity through a hook-based event capture system. It consists of a FastAPI backend, React/TypeScript frontend, and shell scripts that integrate with Claude Code's hook system. + +## Architecture + +### Event Flow +``` +Claude Code → Hook Scripts (.claude/hooks/*.sh) + → FastAPI Backend (Python/SQLAlchemy/WebSockets) + → SQLite Database + → WebSocket broadcast → React Frontend +``` + +### Key Components + +**Backend** (`backend/app/`): +- `main.py` - FastAPI application entry point with CORS, routers, health checks +- `database.py` - SQLAlchemy models (Event, Session, Statistics, ToolUsage), database initialization +- `websocket.py` - ConnectionManager class for broadcasting events to connected clients +- `crud.py` - Database CRUD operations and statistics calculations +- `schemas.py` - Pydantic models for request/response validation +- `api/events.py` - Event creation and listing endpoints +- `api/statistics.py` - Statistics aggregation endpoints +- `api/websocket_endpoint.py` - WebSocket connection handling + +**Frontend** (`frontend/src/`): +- `App.tsx` - Main component orchestrating WebSocket, events, statistics, and filtering +- `hooks/useWebSocket.ts` - WebSocket connection management with auto-reconnect +- `hooks/useEvents.ts` - Event list management with pagination +- `hooks/useStatistics.ts` - Statistics state management +- `types/index.ts` - TypeScript type definitions and filter mappings +- `components/` - React components for UI (Header, EventFeed, StatisticsCards, etc.) + +**Hook Scripts** (`.claude/hooks/`): +Six bash scripts that capture Claude Code events and POST JSON to the backend via curl: +- `pre_tool_use.sh` / `post_tool_use.sh` - Tool execution tracking +- `session_start.sh` / `session_end.sh` - Session lifecycle +- `subagent_stop.sh` - Agent completion tracking +- `user_prompt.sh` - User prompt submission tracking + +All hooks use `jq` for JSON parsing and send data asynchronously (non-blocking) to `http://localhost:8000/api/events`. + +## Common Commands + +### Development + +**Backend** (requires Python 3.11+): +```bash +cd backend +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +python -m app.main # Runs on http://localhost:8000 +``` + +**Frontend** (requires Node.js): +```bash +cd frontend +npm install +npm run dev # Runs on http://localhost:5173 +npm run build # Production build +npm run lint # ESLint check +``` + +### Docker Deployment + +```bash +# Build and start both services +docker-compose up --build + +# Start in detached mode +docker-compose up -d + +# View logs +docker-compose logs -f backend +docker-compose logs -f frontend + +# Stop services +docker-compose down + +# Reset database (stops containers, removes database, restarts) +docker-compose down && rm -f data/claude_monitor.db* && docker-compose up -d +``` + +### Testing Hook Installation + +```bash +# Install hooks to ~/.claude/hooks/ +./install_hooks.sh + +# Test hook manually +echo '{"session_id":"test","tool_name":"Bash","timestamp":1234567890}' | ~/.claude/hooks/post_tool_use.sh + +# Verify backend received it +curl http://localhost:8000/api/events | jq +``` + +## Database Schema + +**SQLAlchemy 2.0 Compatibility**: Uses explicit `text()` for raw SQL, proper relationship configurations, and modern declarative syntax. + +**Tables**: +- `events` - All captured events with indexes on session_id, event_type, tool_name, timestamp +- `sessions` - Session tracking with start/end times and event counts +- `statistics` - Single-row cache table (id=1) for dashboard stats +- `tool_usage` - Per-tool usage counters with first/last usage timestamps + +**SQLite Configuration**: WAL mode enabled for better concurrency in `init_db()`. + +## Event Types + +| Event Type | Source Hook | Description | Frontend Filter | +|------------|-------------|-------------|-----------------| +| `PreToolUse` | pre_tool_use.sh | Before tool execution | TOOLS | +| `PostToolUse` | post_tool_use.sh | After tool execution | TOOLS | +| `SessionStart` | session_start.sh | Session started | SESSIONS | +| `SessionEnd` | session_end.sh | Session ended | SESSIONS | +| `SubagentStop` | subagent_stop.sh | Agent completed | AGENTS | +| `UserPromptSubmit` | user_prompt.sh | User prompt submitted | PROMPTS | + +## Port Configuration + +- **Backend**: 8000 (FastAPI/Uvicorn) +- **Frontend (dev)**: 5173 (Vite dev server) +- **Frontend (prod)**: 3000 (nginx in Docker) + +Change ports in `docker-compose.yml` if conflicts occur. + +## Key Implementation Details + +### WebSocket Protocol +- Backend broadcasts three message types: `event_created`, `stats_updated`, `stats_reset` +- Frontend auto-reconnects on disconnect with exponential backoff +- Heartbeat ping/pong every 30 seconds to maintain connection + +### Frontend State Management +- Events stored in reverse chronological order (newest first) +- Pagination loads 50 events at a time +- Filter changes trigger new API fetch (events reset) +- Statistics updated via WebSocket broadcasts, not polling + +### Hook Performance +- All hooks run curl in background (`&`) to avoid blocking Claude Code +- 2-second timeout on HTTP requests +- Silent mode prevents stderr noise in Claude Code output +- Uses `jq` for JSON construction to handle escaping correctly + +### TypeScript Build +The frontend uses strict TypeScript with proper type definitions in `types/index.ts`. When making changes: +- Run `npm run build` to check for type errors before committing +- Event types must match backend Pydantic models +- Filter mappings in `EVENT_FILTER_MAP` must stay synchronized with backend event types + +## Configuration Files + +- `docker-compose.yml` - Service orchestration, volumes, network setup +- `backend/requirements.txt` - Python dependencies (FastAPI, SQLAlchemy 2.0, uvicorn, websockets) +- `frontend/package.json` - Node dependencies (React 18, Vite, TypeScript, Axios) +- `frontend/vite.config.ts` - Vite build configuration with React plugin +- `frontend/tsconfig.json` - TypeScript compiler options + +## Debugging + +### Backend Issues +```bash +# Check backend health +curl http://localhost:8000/health + +# View API documentation +open http://localhost:8000/docs + +# Check database +sqlite3 data/claude_monitor.db "SELECT COUNT(*) FROM events;" + +# Tail backend logs +docker-compose logs -f backend +``` + +### Frontend Issues +```bash +# Check WebSocket connection in browser console +# Should show "WebSocket connected" message + +# Verify API connectivity +curl http://localhost:8000/api/statistics + +# Check for CORS errors in browser DevTools Network tab +``` + +### Hook Issues +```bash +# Verify jq is installed +which jq # Should print path, not "not found" + +# Check hooks are executable +ls -la ~/.claude/hooks/ + +# Test hook with verbose output (remove --silent from curl) +echo '{"session_id":"test"}' | ~/.claude/hooks/session_start.sh +``` diff --git a/frontend/src/components/AgentsGraph.tsx b/frontend/src/components/AgentsGraph.tsx new file mode 100644 index 0000000..d65aca3 --- /dev/null +++ b/frontend/src/components/AgentsGraph.tsx @@ -0,0 +1,19 @@ +// Agents graph component for visualizing agent relationships and activity + +import React from 'react'; + +export const AgentsGraph: React.FC = () => { + return ( +
+
+
+

Agent Activity Graph

+

This feature will visualize agent relationships and activity over time.

+

Coming soon: Interactive graph showing agent spawning, communication, and completion events.

+
+
+
+ ); +}; + +export default AgentsGraph; diff --git a/frontend/src/components/EventFeed.tsx b/frontend/src/components/EventFeed.tsx index 9dcf1a0..e1510f8 100644 --- a/frontend/src/components/EventFeed.tsx +++ b/frontend/src/components/EventFeed.tsx @@ -1,8 +1,9 @@ // Event feed component displaying a list of events -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import EventCard from './EventCard'; import FilterButtons from './FilterButtons'; +import AgentsGraph from './AgentsGraph'; import type { Event, FilterType, EventType } from '../types'; import { EVENT_FILTER_MAP } from '../types'; @@ -23,6 +24,8 @@ export const EventFeed: React.FC = ({ onLoadMore, hasMore, }) => { + const [activeTab, setActiveTab] = useState<'feed' | 'graph'>('feed'); + // Filter events by active filter const filteredEvents = events.filter((event) => { const allowedTypes: EventType[] = EVENT_FILTER_MAP[activeFilter]; @@ -55,29 +58,45 @@ export const EventFeed: React.FC = ({ return (
- - + +
- + {activeTab === 'feed' ? ( + <> + -
- {loading && events.length === 0 ? ( -
Loading events...
- ) : filteredEvents.length === 0 ? ( -
-

No events yet

-

Start using Claude Code to see events appear here in real-time

+
+ {loading && events.length === 0 ? ( +
Loading events...
+ ) : filteredEvents.length === 0 ? ( +
+

No events yet

+

Start using Claude Code to see events appear here in real-time

+
+ ) : ( + <> + {filteredEvents.map((event) => ( + + ))} + {loading &&
Loading more...
} + + )}
- ) : ( - <> - {filteredEvents.map((event) => ( - - ))} - {loading &&
Loading more...
} - - )} -
+ + ) : ( + + )}
); }; diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css index 729034e..a773559 100644 --- a/frontend/src/styles/index.css +++ b/frontend/src/styles/index.css @@ -490,3 +490,36 @@ body { font-size: 0.9rem; color: var(--text-secondary); } + +/* Agents Graph */ +.agents-graph { + height: 600px; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; +} + +.graph-placeholder { + text-align: center; + max-width: 600px; +} + +.placeholder-content h3 { + font-size: 1.5rem; + color: var(--cyan); + margin-bottom: 1rem; + letter-spacing: 0.05em; +} + +.placeholder-content p { + color: var(--text-secondary); + margin-bottom: 0.8rem; + line-height: 1.6; +} + +.placeholder-hint { + font-size: 0.9rem; + color: var(--text-secondary); + font-style: italic; +}