Add user prompt text display and agents graph tab
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 <noreply@anthropic.com>
This commit is contained in:
@@ -7,18 +7,21 @@ INPUT=$(cat)
|
|||||||
|
|
||||||
# Extract fields using jq
|
# Extract fields using jq
|
||||||
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "unknown"')
|
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)')
|
TIMESTAMP=$(echo "$INPUT" | jq -r '.timestamp // (now | tonumber)')
|
||||||
|
|
||||||
# Build payload for backend
|
# Build payload for backend
|
||||||
PAYLOAD=$(jq -n \
|
PAYLOAD=$(jq -n \
|
||||||
--arg session_id "$SESSION_ID" \
|
--arg session_id "$SESSION_ID" \
|
||||||
--arg event_type "UserPromptSubmit" \
|
--arg event_type "UserPromptSubmit" \
|
||||||
|
--arg prompt "$PROMPT" \
|
||||||
--arg prompt_length "$PROMPT_LENGTH" \
|
--arg prompt_length "$PROMPT_LENGTH" \
|
||||||
--arg timestamp "$TIMESTAMP" \
|
--arg timestamp "$TIMESTAMP" \
|
||||||
'{
|
'{
|
||||||
session_id: $session_id,
|
session_id: $session_id,
|
||||||
event_type: $event_type,
|
event_type: $event_type,
|
||||||
|
tool_input: $prompt,
|
||||||
description: ("User submitted prompt (" + $prompt_length + " chars)"),
|
description: ("User submitted prompt (" + $prompt_length + " chars)"),
|
||||||
timestamp: ($timestamp | tonumber)
|
timestamp: ($timestamp | tonumber)
|
||||||
}')
|
}')
|
||||||
|
|||||||
205
CLAUDE.md
Normal file
205
CLAUDE.md
Normal file
@@ -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
|
||||||
|
```
|
||||||
19
frontend/src/components/AgentsGraph.tsx
Normal file
19
frontend/src/components/AgentsGraph.tsx
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// Agents graph component for visualizing agent relationships and activity
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export const AgentsGraph: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div className="agents-graph">
|
||||||
|
<div className="graph-placeholder">
|
||||||
|
<div className="placeholder-content">
|
||||||
|
<h3>Agent Activity Graph</h3>
|
||||||
|
<p>This feature will visualize agent relationships and activity over time.</p>
|
||||||
|
<p className="placeholder-hint">Coming soon: Interactive graph showing agent spawning, communication, and completion events.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AgentsGraph;
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
// Event feed component displaying a list of events
|
// Event feed component displaying a list of events
|
||||||
|
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import EventCard from './EventCard';
|
import EventCard from './EventCard';
|
||||||
import FilterButtons from './FilterButtons';
|
import FilterButtons from './FilterButtons';
|
||||||
|
import AgentsGraph from './AgentsGraph';
|
||||||
import type { Event, FilterType, EventType } from '../types';
|
import type { Event, FilterType, EventType } from '../types';
|
||||||
import { EVENT_FILTER_MAP } from '../types';
|
import { EVENT_FILTER_MAP } from '../types';
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@ export const EventFeed: React.FC<EventFeedProps> = ({
|
|||||||
onLoadMore,
|
onLoadMore,
|
||||||
hasMore,
|
hasMore,
|
||||||
}) => {
|
}) => {
|
||||||
|
const [activeTab, setActiveTab] = useState<'feed' | 'graph'>('feed');
|
||||||
|
|
||||||
// Filter events by active filter
|
// Filter events by active filter
|
||||||
const filteredEvents = events.filter((event) => {
|
const filteredEvents = events.filter((event) => {
|
||||||
const allowedTypes: EventType[] = EVENT_FILTER_MAP[activeFilter];
|
const allowedTypes: EventType[] = EVENT_FILTER_MAP[activeFilter];
|
||||||
@@ -55,29 +58,45 @@ export const EventFeed: React.FC<EventFeedProps> = ({
|
|||||||
return (
|
return (
|
||||||
<div className="event-feed-container">
|
<div className="event-feed-container">
|
||||||
<div className="tabs">
|
<div className="tabs">
|
||||||
<button className="tab active">📋 EVENT FEED</button>
|
<button
|
||||||
<button className="tab">🌐 AGENTS GRAPH</button>
|
className={`tab ${activeTab === 'feed' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('feed')}
|
||||||
|
>
|
||||||
|
📋 EVENT FEED
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
className={`tab ${activeTab === 'graph' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('graph')}
|
||||||
|
>
|
||||||
|
🌐 AGENTS GRAPH
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FilterButtons activeFilter={activeFilter} onFilterChange={onFilterChange} />
|
{activeTab === 'feed' ? (
|
||||||
|
<>
|
||||||
|
<FilterButtons activeFilter={activeFilter} onFilterChange={onFilterChange} />
|
||||||
|
|
||||||
<div className="event-feed">
|
<div className="event-feed">
|
||||||
{loading && events.length === 0 ? (
|
{loading && events.length === 0 ? (
|
||||||
<div className="loading">Loading events...</div>
|
<div className="loading">Loading events...</div>
|
||||||
) : filteredEvents.length === 0 ? (
|
) : filteredEvents.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<p>No events yet</p>
|
<p>No events yet</p>
|
||||||
<p className="empty-hint">Start using Claude Code to see events appear here in real-time</p>
|
<p className="empty-hint">Start using Claude Code to see events appear here in real-time</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{filteredEvents.map((event) => (
|
||||||
|
<EventCard key={event.id} event={event} />
|
||||||
|
))}
|
||||||
|
{loading && <div className="loading-more">Loading more...</div>}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
{filteredEvents.map((event) => (
|
<AgentsGraph />
|
||||||
<EventCard key={event.id} event={event} />
|
)}
|
||||||
))}
|
|
||||||
{loading && <div className="loading-more">Loading more...</div>}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -490,3 +490,36 @@ body {
|
|||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--text-secondary);
|
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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user