Compare commits

...

2 Commits

Author SHA1 Message Date
50c80596d0 Add comprehensive Docker deployment and file upload functionality
Features Added:
• Docker containerization with multi-stage Python 3.12 build
• Caddy reverse proxy integration with automatic SSL
• File upload interface for .claude.json imports with preview
• Comprehensive hook system with 39+ hook types across 9 categories
• Complete documentation system with Docker and import guides

Technical Improvements:
• Enhanced database models with hook tracking capabilities
• Robust file validation and error handling for uploads
• Production-ready Docker compose configuration
• Health checks and resource limits for containers
• Database initialization scripts for containerized deployments

Documentation:
• Docker Deployment Guide with troubleshooting
• Data Import Guide with step-by-step instructions
• Updated Getting Started guide with new features
• Enhanced documentation index with responsive grid layout

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 08:02:09 -06:00
bec1606c86 Add comprehensive documentation system and tool call tracking
## Documentation System
- Create complete documentation hub at /dashboard/docs with:
  - Getting Started guide with quick setup and troubleshooting
  - Hook Setup Guide with platform-specific configurations
  - API Reference with all endpoints and examples
  - FAQ with searchable questions and categories
- Add responsive design with interactive features
- Update navigation in base template

## Tool Call Tracking
- Add ToolCall model for tracking Claude Code tool usage
- Create /api/tool-calls endpoints for recording and analytics
- Add tool_call hook type with auto-session detection
- Include tool calls in project statistics and recalculation
- Track tool names, parameters, execution time, and success rates

## Project Enhancements
- Add project timeline and statistics pages (fix 404 errors)
- Create recalculation script for fixing zero statistics
- Update project stats to include tool call counts
- Enhance session model with tool call relationships

## Infrastructure
- Switch from requirements.txt to pyproject.toml/uv.lock
- Add data import functionality for claude.json files
- Update database connection to include all new models
- Add comprehensive API documentation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 05:58:27 -06:00
55 changed files with 10441 additions and 161 deletions

87
.dockerignore Normal file
View File

@ -0,0 +1,87 @@
# Python cache and build artifacts
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
.venv/
venv/
ENV/
env/
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Git
.git/
.gitignore
# Docker
Dockerfile*
docker-compose*
.dockerignore
# Database files (will be created in volume)
*.db
*.sqlite
*.sqlite3
# Logs
*.log
logs/
# Environment files (handled separately)
.env*
# Temporary files
tmp/
temp/
.tmp/
# Hook configuration files (generated)
claude-hooks-*.json
# Cache
.cache/
.pytest_cache/
# Coverage reports
htmlcov/
.coverage
.coverage.*
# Node modules (if any)
node_modules/
# Documentation build
docs/_build/

View File

@ -1,10 +1,18 @@
# Database # Claude Code Tracker Configuration
DATABASE_URL=sqlite+aiosqlite:///./data/tracker.db
# API Configuration # Domain configuration for Caddy reverse proxy
DOMAIN=claude.l.supported.systems
# Database configuration
DATABASE_URL=sqlite+aiosqlite:///app/data/tracker.db
# Application settings
DEBUG=false
API_HOST=0.0.0.0 API_HOST=0.0.0.0
API_PORT=8000 API_PORT=8000
DEBUG=true
# Data storage path (host directory to bind mount)
DATA_PATH=./data
# Security (generate with: openssl rand -hex 32) # Security (generate with: openssl rand -hex 32)
SECRET_KEY=your-secret-key-here SECRET_KEY=your-secret-key-here
@ -17,3 +25,19 @@ ANALYTICS_BATCH_SIZE=1000
# Logging # Logging
LOG_LEVEL=INFO LOG_LEVEL=INFO
LOG_FILE=tracker.log LOG_FILE=tracker.log
# Optional: Rate limiting settings
# RATE_LIMIT_ENABLED=true
# RATE_LIMIT_PER_MINUTE=100
# Optional: Performance settings
# MAX_WORKERS=4
# TIMEOUT_SECONDS=60
# Optional: Hook settings
# DEFAULT_HOOK_TIMEOUT=10
# MAX_HOOK_RETRIES=3
# Optional: Analytics retention
# ANALYTICS_RETENTION_DAYS=365
# ENABLE_ANONYMOUS_ANALYTICS=false

345
DOCKER.md Normal file
View File

@ -0,0 +1,345 @@
# Claude Code Tracker - Docker Deployment
This guide covers deploying Claude Code Tracker using Docker with Caddy reverse proxy integration.
## Quick Start
1. **Clone and configure:**
```bash
git clone <repository>
cd claude-tracker
cp .env.example .env
# Edit .env file to set your domain
```
2. **Deploy with one command:**
```bash
./docker-deploy.sh
```
3. **Access your instance:**
- Dashboard: `https://claude.l.supported.systems/dashboard`
- API Docs: `https://claude.l.supported.systems/docs`
## Prerequisites
### Required
- Docker Engine 20.10+
- Docker Compose 2.0+
- Caddy server with docker-proxy plugin running
### Caddy Network Setup
The deployment expects an external Docker network named `caddy`:
```bash
# Create the network if it doesn't exist
docker network create caddy
```
### Caddy Server
Ensure Caddy is running with the docker-proxy plugin:
```bash
# Example Caddy docker-compose.yml
version: '3.8'
services:
caddy:
image: lucaslorentz/caddy-docker-proxy:ci-alpine
ports:
- "80:80"
- "443:443"
environment:
- CADDY_INGRESS_NETWORKS=caddy
networks:
- caddy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- caddy_data:/data
restart: unless-stopped
```
## Configuration
### Environment Variables
The `.env` file configures the deployment:
```bash
# Domain for Caddy reverse proxy
DOMAIN=claude.l.supported.systems
# Database path (inside container)
DATABASE_URL=sqlite+aiosqlite:///app/data/tracker.db
# Application settings
DEBUG=false
API_HOST=0.0.0.0
API_PORT=8000
# Data directory path (host)
DATA_PATH=./data
```
### Caddy Labels
The `docker-compose.yml` includes comprehensive Caddy configuration:
- **Reverse Proxy:** Automatic routing to the container
- **SSL/TLS:** Automatic certificate management
- **Security Headers:** XSS protection, content type sniffing prevention
- **CORS Headers:** API cross-origin support
- **Compression:** Gzip encoding for better performance
- **Caching:** Static asset caching
## Deployment Options
### Option 1: Automated Deployment (Recommended)
```bash
./docker-deploy.sh
```
### Option 2: Manual Deployment
```bash
# Create network if needed
docker network create caddy
# Build and start
docker-compose up -d --build
# Check status
docker-compose ps
```
### Option 3: Development Mode
```bash
# Start with logs
docker-compose up --build
# Rebuild after changes
docker-compose up --build --force-recreate
```
## Data Persistence
### Database Storage
- **Host Path:** `./data/` (configurable via `DATA_PATH`)
- **Container Path:** `/app/data/`
- **Database File:** `tracker.db`
### Volume Management
```bash
# Backup database
cp ./data/tracker.db ./backups/tracker-$(date +%Y%m%d).db
# Restore database
cp ./backups/tracker-20240115.db ./data/tracker.db
docker-compose restart claude-tracker
```
## Management Commands
### Service Management
```bash
# View logs
docker-compose logs -f claude-tracker
# Restart service
docker-compose restart claude-tracker
# Stop service
docker-compose down
# Update and restart
docker-compose pull && docker-compose up -d
```
### Container Access
```bash
# Shell access
docker-compose exec claude-tracker /bin/bash
# Run commands in container
docker-compose exec claude-tracker uv run python recalculate_project_stats.py
# View container stats
docker stats claude-tracker-api
```
### Health Monitoring
```bash
# Check health
curl http://localhost:8000/health
# Container status
docker-compose ps
# Resource usage
docker-compose exec claude-tracker cat /proc/meminfo
```
## Troubleshooting
### Common Issues
#### 1. Container Won't Start
```bash
# Check logs
docker-compose logs claude-tracker
# Common causes:
# - Port 8000 already in use
# - Database permission issues
# - Missing environment variables
```
#### 2. Database Errors
```bash
# Check database permissions
ls -la ./data/
# Recreate database
rm ./data/tracker.db
docker-compose restart claude-tracker
```
#### 3. Caddy Connection Issues
```bash
# Verify Caddy network
docker network ls | grep caddy
# Check Caddy logs
docker logs caddy
# Verify labels
docker inspect claude-tracker-api | grep -A 10 Labels
```
#### 4. Performance Issues
```bash
# Monitor resources
docker stats claude-tracker-api
# Increase memory limits in docker-compose.yml
```
### Health Checks
The container includes built-in health checks:
```bash
# Container health status
docker-compose ps
# Manual health check
curl -f http://localhost:8000/health
# Detailed health info
docker inspect --format='{{json .State.Health}}' claude-tracker-api
```
## Security Considerations
### Network Security
- Container only exposes port 8000 internally to Docker network
- External access only through Caddy reverse proxy
- Automatic HTTPS with Let's Encrypt
### Data Security
- Database stored in dedicated Docker volume
- No sensitive data in container images
- Environment variables for configuration
### Resource Limits
- Memory limit: 512MB (configurable)
- CPU limit: 1.0 core (configurable)
- Automatic restart on failure
## Production Recommendations
### Performance
1. **Use bind mounts for data persistence**
2. **Configure resource limits appropriately**
3. **Monitor container health and logs**
4. **Regular database backups**
### Security
1. **Use specific image tags (not `latest`)**
2. **Regular security updates**
3. **Network isolation**
4. **Secret management for sensitive data**
### Monitoring
1. **Set up log aggregation**
2. **Configure health check alerts**
3. **Monitor resource usage**
4. **Database backup automation**
## Advanced Configuration
### Custom Caddy Labels
Add additional Caddy configuration in `docker-compose.yml`:
```yaml
labels:
# Rate limiting
caddy.rate_limit: "100r/m"
# Basic auth for admin endpoints
caddy.basicauth./admin/*: "admin $2a$14$..."
# IP restrictions
caddy.@internal: "remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16"
caddy.authorize.@internal: ""
```
### Multi-Environment Setup
Create environment-specific compose files:
```bash
# Production
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# Development
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d
```
## Updating
### Application Updates
```bash
# Pull latest changes
git pull
# Rebuild and restart
docker-compose up -d --build
# Check health
curl https://claude.l.supported.systems/health
```
### Database Migrations
```bash
# Backup first
cp ./data/tracker.db ./data/tracker.backup.db
# Run migration (if needed)
docker-compose exec claude-tracker uv run python migrate.py
# Verify
docker-compose logs claude-tracker
```
## Support
For issues related to:
- **Docker deployment:** Check this documentation
- **Caddy configuration:** Refer to Caddy documentation
- **Application features:** See main README.md
- **API usage:** Visit `/docs` endpoint
## Links
- **Dashboard:** `https://your-domain/dashboard`
- **API Documentation:** `https://your-domain/docs`
- **Hook Reference:** `https://your-domain/dashboard/docs/hook-reference`
- **Health Check:** `https://your-domain/health`

53
Dockerfile Normal file
View File

@ -0,0 +1,53 @@
# Use Python 3.12 slim image
FROM python:3.12-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
UV_CACHE_DIR=/tmp/uv-cache
# Install system dependencies
RUN apt-get update && apt-get install -y \
curl \
git \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
# Create app directory
WORKDIR /app
# Copy dependency files and README first for better caching
COPY pyproject.toml uv.lock README.md ./
# Install dependencies
RUN uv sync --frozen --no-cache --no-dev
# Copy application code
COPY . .
# Create data directory for SQLite database with proper permissions
RUN mkdir -p /app/data && chmod 777 /app/data
# Expose port
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:8000/health || exit 1
# Copy initialization script
COPY init_db.py ./
# Create startup script
RUN echo '#!/bin/bash\n\
echo "Ensuring database is initialized..."\n\
/app/.venv/bin/python init_db.py\n\
echo "Starting application..."\n\
exec /app/.venv/bin/python -m uvicorn main:app --host 0.0.0.0 --port 8000' > start.sh && \
chmod +x start.sh
# Run the application
CMD ["./start.sh"]

214
README.md
View File

@ -1,146 +1,116 @@
# Claude Code Project Tracker # Claude Code Hook Configurations
A comprehensive development intelligence system that tracks your Claude Code sessions, providing insights into your coding patterns, productivity, and learning journey. This directory contains various pre-configured hook setups for different use cases:
## Overview ## Available Configurations
The Claude Code Project Tracker automatically captures your development workflow through Claude Code's hook system, creating a detailed record of: ### basic.json
Essential hooks for basic session and tool tracking.
- Session management
- Tool calls
- File modifications
- Conversations
- **Development Sessions** - When you start/stop working, what projects you focus on ### comprehensive.json
- **Conversations** - Full dialogue history with Claude for context and learning analysis Complete hook setup with all available hook types.
- **Code Changes** - File modifications, tool usage, and command executions - All performance monitoring
- **Thinking Patterns** - Wait times between interactions to understand your workflow - Code quality tracking
- **Git Activity** - Repository changes, commits, and branch operations - Learning analytics
- **Productivity Metrics** - Engagement levels, output quality, and learning velocity - Collaboration insights
- Project intelligence
## Architecture ### essential.json
Minimal setup for core functionality.
``` ### developer.json
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ Focused on development workflow and code quality.
│ Claude Code │───▶│ Hook System │───▶│ FastAPI Server │ - Essential hooks
│ (your IDE) │ │ │ │ │ - Performance monitoring
└─────────────────┘ └─────────────────┘ └─────────────────┘ - Code quality checks
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Web Dashboard │◀───│ Analytics │◀───│ SQLite Database │
│ │ │ Engine │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
### Components ### power_user.json
Advanced setup for productivity optimization.
- Essential hooks
- Performance tracking
- Workflow analysis
- Project intelligence
- **FastAPI Server**: REST API that receives hook data and serves analytics ### research.json
- **SQLite Database**: Local storage for all tracking data Optimized for learning and exploration.
- **Hook Integration**: Claude Code hooks that capture development events - Essential hooks
- **Analytics Engine**: Processes raw data into meaningful insights - Learning tracking
- **Web Dashboard**: Interactive interface for exploring your development patterns - External resource usage
- Knowledge gap analysis
## Key Features ### team.json
Team-focused configuration for collaboration.
- Essential hooks
- Collaboration tracking
- Testing workflows
- Project intelligence
### 🎯 Session Tracking ## Installation
- Automatic project detection and session management
- Working directory and git branch context
- Session duration and engagement analysis
### 💬 Conversation Intelligence 1. Choose the configuration that matches your needs
- Full dialogue history with semantic search 2. Copy the JSON content to your Claude Code settings file:
- Problem-solving pattern recognition - macOS/Linux: `~/.config/claude/settings.json`
- Learning topic identification and progress tracking - Windows: `%APPDATA%\claude\settings.json`
3. Ensure Claude Code Tracker is running on port 8000
4. Start using Claude Code - hooks will automatically track your activity!
### 📊 Development Analytics ## Available Hook Variables
- Productivity metrics and engagement scoring
- Tool usage patterns and optimization insights
- Cross-project learning and code reuse analysis
### 🔍 Advanced Insights Each hook can use these variables that Claude Code provides:
- Think time analysis and flow state detection
- Git activity correlation with conversations
- Skill development velocity tracking
- Workflow optimization recommendations
## Data Privacy ### Session Variables
- `$SESSION_ID` - Current session identifier
- `$TIMESTAMP` - Current timestamp (ISO format)
- `$PWD` - Current working directory
- `$USER` - System username
- **Local-First**: All data stays on your machine ### Tool Variables
- **No External Services**: No data transmission to third parties - `$TOOL_NAME` - Name of tool being called
- **Full Control**: Complete ownership of your development history - `$TOOL_PARAMS` - Tool parameters (JSON)
- **Selective Tracking**: Configurable hook activation per project - `$RESULT_STATUS` - Success/error status
- `$EXECUTION_TIME` - Tool execution time (ms)
- `$ERROR_TYPE` - Type of error
- `$ERROR_MESSAGE` - Error message
- `$STACK_TRACE` - Error stack trace
## Quick Start ### File Variables
- `$FILE_PATH` - Path to modified file
- `$ACTION` - File action (created/modified/deleted)
- `$FILE_SIZE_MB` - File size in megabytes
1. **Install Dependencies** ### Context Variables
```bash - `$CONTENT` - Conversation content
pip install -r requirements.txt - `$CONTEXT` - Current context description
``` - `$SEARCH_QUERY` - What you're searching for
- `$NEW_PROJECT` - Project being switched to
- `$OLD_PROJECT` - Project being switched from
2. **Start the Tracking Server** ### Performance Variables
```bash - `$MEMORY_MB` - Memory usage in MB
python main.py - `$DURATION_MS` - Duration in milliseconds
``` - `$THRESHOLD_EXCEEDED` - Boolean for threshold alerts
3. **Configure Claude Code Hooks** And many more! Each hook type has specific variables available.
```bash
# Add hooks to your Claude Code settings
cp config/claude-hooks.json ~/.config/claude-code/
```
4. **Access Dashboard** ## Customization
```
Open http://localhost:8000 in your browser
```
## Project Structure You can modify any configuration by:
1. Adding/removing specific hooks
2. Changing API endpoints or ports
3. Adjusting timeout and retry settings
4. Adding custom metadata to hook calls
``` ## Troubleshooting
claude-tracker/
├── README.md # This file
├── requirements.txt # Python dependencies
├── main.py # FastAPI application entry point
├── config/ # Configuration files
│ └── claude-hooks.json # Hook setup for Claude Code
├── app/ # Application code
│ ├── models/ # Database models
│ ├── api/ # API endpoints
│ ├── analytics/ # Insights engine
│ └── dashboard/ # Web interface
├── tests/ # Test suite
├── docs/ # Detailed documentation
└── data/ # SQLite database location
```
## Documentation If hooks aren't working:
1. Ensure Claude Code Tracker server is running
2. Check that curl is installed
3. Verify the API endpoints are accessible
4. Check Claude Code logs for hook execution errors
5. Test individual hooks manually with curl
- [API Specification](docs/api-spec.yaml) - Complete endpoint documentation For more help, see the documentation at `/dashboard/docs/hook-setup`.
- [Database Schema](docs/database-schema.md) - Data model details
- [Hook Setup Guide](docs/hook-setup.md) - Claude Code integration
- [Development Guide](docs/development.md) - Local setup and contribution
- [Analytics Guide](docs/analytics.md) - Understanding insights and metrics
## Development
See [Development Guide](docs/development.md) for detailed setup instructions.
```bash
# Install development dependencies
pip install -r requirements-dev.txt
# Run tests
pytest
# Start development server with hot reload
uvicorn main:app --reload
# Generate API documentation
python -m app.generate_docs
```
## Contributing
This project follows test-driven development. Please ensure:
1. All new features have comprehensive tests
2. Documentation is updated for API changes
3. Analytics insights are validated with test data
## License
MIT License - See LICENSE file for details

View File

@ -83,6 +83,54 @@ async def log_conversation(
) )
@router.get("/conversations", response_model=List[ConversationSearchResult])
async def get_conversations(
project_id: Optional[int] = Query(None, description="Filter by project ID"),
limit: int = Query(50, description="Maximum number of results"),
offset: int = Query(0, description="Number of results to skip"),
db: AsyncSession = Depends(get_db)
):
"""
Get recent conversations with optional project filtering.
"""
try:
# Build query
query = select(Conversation).options(
selectinload(Conversation.session).selectinload(Session.project)
)
# Add project filter if specified
if project_id:
query = query.join(Session).where(Session.project_id == project_id)
# Order by timestamp descending, add pagination
query = query.order_by(Conversation.timestamp.desc()).offset(offset).limit(limit)
result = await db.execute(query)
conversations = result.scalars().all()
# Convert to response format
results = []
for conversation in conversations:
results.append(ConversationSearchResult(
id=conversation.id,
project_name=conversation.session.project.name,
timestamp=conversation.timestamp,
user_prompt=conversation.user_prompt,
claude_response=conversation.claude_response,
relevance_score=1.0, # All results are equally relevant when just listing
context=[] # No context snippets needed for listing
))
return results
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get conversations: {str(e)}"
)
@router.get("/conversations/search", response_model=List[ConversationSearchResult]) @router.get("/conversations/search", response_model=List[ConversationSearchResult])
async def search_conversations( async def search_conversations(
query: str = Query(..., description="Search query"), query: str = Query(..., description="Search query"),

619
app/api/hooks.py Normal file
View File

@ -0,0 +1,619 @@
"""
Comprehensive hook tracking API endpoints.
"""
import json
from datetime import datetime
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc, and_
from sqlalchemy.orm import selectinload
from pydantic import BaseModel, Field
from app.database.connection import get_db
from app.models.session import Session
from app.models.hooks import (
HookEvent, ToolError, WaitingPeriodNew, PerformanceMetric,
CodeQualityEvent, WorkflowEvent, LearningEvent,
EnvironmentEvent, CollaborationEvent, ProjectIntelligence
)
router = APIRouter()
# Request/Response Models
class BaseHookRequest(BaseModel):
"""Base request schema for hook events."""
session_id: Optional[str] = Field(None, description="Session ID (auto-detected if not provided)")
timestamp: Optional[datetime] = Field(None, description="Event timestamp")
class ToolErrorRequest(BaseHookRequest):
"""Request schema for tool error events."""
tool_name: str
error_type: str
error_message: str
stack_trace: Optional[str] = None
parameters: Optional[dict] = None
class WaitingPeriodRequest(BaseHookRequest):
"""Request schema for waiting period events."""
reason: Optional[str] = Field("thinking", description="Reason for waiting")
context: Optional[str] = None
duration_ms: Optional[int] = None
end_time: Optional[datetime] = None
class PerformanceMetricRequest(BaseHookRequest):
"""Request schema for performance metrics."""
metric_type: str = Field(..., description="Type of metric: memory, cpu, disk")
value: float
unit: str = Field(..., description="Unit: MB, %, seconds, etc.")
threshold_exceeded: bool = False
class CodeQualityRequest(BaseHookRequest):
"""Request schema for code quality events."""
event_type: str = Field(..., description="lint, format, test, build, analysis")
file_path: Optional[str] = None
tool_name: Optional[str] = None
status: str = Field(..., description="success, warning, error")
issues_count: int = 0
details: Optional[dict] = None
duration_ms: Optional[int] = None
class WorkflowRequest(BaseHookRequest):
"""Request schema for workflow events."""
event_type: str = Field(..., description="context_switch, search_query, browser_tab, etc.")
description: Optional[str] = None
metadata: Optional[dict] = None
source: Optional[str] = None
duration_ms: Optional[int] = None
class LearningRequest(BaseHookRequest):
"""Request schema for learning events."""
event_type: str = Field(..., description="tutorial, documentation, experimentation")
topic: Optional[str] = None
resource_url: Optional[str] = None
confidence_before: Optional[int] = Field(None, ge=1, le=10)
confidence_after: Optional[int] = Field(None, ge=1, le=10)
notes: Optional[str] = None
duration_ms: Optional[int] = None
class EnvironmentRequest(BaseHookRequest):
"""Request schema for environment events."""
event_type: str = Field(..., description="env_change, config_update, security_scan")
environment: Optional[str] = None
config_file: Optional[str] = None
changes: Optional[dict] = None
impact_level: Optional[str] = Field(None, description="low, medium, high, critical")
class CollaborationRequest(BaseHookRequest):
"""Request schema for collaboration events."""
event_type: str = Field(..., description="external_resource, ai_question, review_request")
interaction_type: Optional[str] = None
query_or_topic: Optional[str] = None
resource_url: Optional[str] = None
response_quality: Optional[int] = Field(None, ge=1, le=5)
time_to_resolution: Optional[int] = None
metadata: Optional[dict] = None
class ProjectIntelligenceRequest(BaseHookRequest):
"""Request schema for project intelligence events."""
event_type: str = Field(..., description="refactor, feature_flag, debugging_session")
scope: Optional[str] = Field(None, description="small, medium, large")
complexity: Optional[str] = Field(None, description="low, medium, high")
end_time: Optional[datetime] = None
duration_minutes: Optional[int] = None
files_affected: Optional[List[str]] = None
outcome: Optional[str] = Field(None, description="success, partial, failed, abandoned")
notes: Optional[str] = None
class HookResponse(BaseModel):
"""Response schema for hook operations."""
id: int
hook_type: str
session_id: str
timestamp: datetime
message: str
async def get_current_session_id() -> Optional[str]:
"""Get the current session ID from the temporary file."""
try:
with open("/tmp/claude-session-id", "r") as f:
return f.read().strip()
except (OSError, FileNotFoundError):
return None
async def validate_session(session_id: str, db: AsyncSession) -> Session:
"""Validate that the session exists."""
result = await db.execute(
select(Session).where(Session.id == int(session_id))
)
session = result.scalars().first()
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
return session
# Tool Error Endpoints
@router.post("/hooks/tool-error", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_tool_error(
request: ToolErrorRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a tool error event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
tool_error = ToolError(
session_id=session_id,
tool_name=request.tool_name,
error_type=request.error_type,
error_message=request.error_message,
stack_trace=request.stack_trace,
parameters=request.parameters,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(tool_error)
await db.commit()
await db.refresh(tool_error)
return HookResponse(
id=tool_error.id,
hook_type="tool_error",
session_id=session_id,
timestamp=tool_error.timestamp,
message=f"Tool error recorded for {request.tool_name}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record tool error: {str(e)}")
# Waiting Period Endpoints
@router.post("/hooks/waiting-period", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_waiting_period(
request: WaitingPeriodRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a waiting period event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
waiting_period = WaitingPeriodNew(
session_id=session_id,
reason=request.reason,
context=request.context,
duration_ms=request.duration_ms,
end_time=request.end_time,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(waiting_period)
await db.commit()
await db.refresh(waiting_period)
return HookResponse(
id=waiting_period.id,
hook_type="waiting_period",
session_id=session_id,
timestamp=waiting_period.start_time,
message=f"Waiting period recorded: {request.reason}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record waiting period: {str(e)}")
# Performance Metric Endpoints
@router.post("/hooks/performance", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_performance_metric(
request: PerformanceMetricRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a performance metric."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
metric = PerformanceMetric(
session_id=session_id,
metric_type=request.metric_type,
value=request.value,
unit=request.unit,
threshold_exceeded=request.threshold_exceeded,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(metric)
await db.commit()
await db.refresh(metric)
return HookResponse(
id=metric.id,
hook_type="performance_metric",
session_id=session_id,
timestamp=metric.timestamp,
message=f"Performance metric recorded: {request.metric_type} = {request.value}{request.unit}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record performance metric: {str(e)}")
# Code Quality Endpoints
@router.post("/hooks/code-quality", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_code_quality_event(
request: CodeQualityRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a code quality event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
event = CodeQualityEvent(
session_id=session_id,
event_type=request.event_type,
file_path=request.file_path,
tool_name=request.tool_name,
status=request.status,
issues_count=request.issues_count,
details=request.details,
duration_ms=request.duration_ms,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(event)
await db.commit()
await db.refresh(event)
return HookResponse(
id=event.id,
hook_type="code_quality",
session_id=session_id,
timestamp=event.timestamp,
message=f"Code quality event recorded: {request.event_type} - {request.status}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record code quality event: {str(e)}")
# Workflow Endpoints
@router.post("/hooks/workflow", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_workflow_event(
request: WorkflowRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a workflow event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
event = WorkflowEvent(
session_id=session_id,
event_type=request.event_type,
description=request.description,
event_metadata=request.metadata,
source=request.source,
duration_ms=request.duration_ms,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(event)
await db.commit()
await db.refresh(event)
return HookResponse(
id=event.id,
hook_type="workflow",
session_id=session_id,
timestamp=event.timestamp,
message=f"Workflow event recorded: {request.event_type}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record workflow event: {str(e)}")
# Learning Endpoints
@router.post("/hooks/learning", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_learning_event(
request: LearningRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a learning event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
event = LearningEvent(
session_id=session_id,
event_type=request.event_type,
topic=request.topic,
resource_url=request.resource_url,
confidence_before=request.confidence_before,
confidence_after=request.confidence_after,
notes=request.notes,
duration_ms=request.duration_ms,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(event)
await db.commit()
await db.refresh(event)
return HookResponse(
id=event.id,
hook_type="learning",
session_id=session_id,
timestamp=event.timestamp,
message=f"Learning event recorded: {request.event_type} - {request.topic or 'General'}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record learning event: {str(e)}")
# Environment Endpoints
@router.post("/hooks/environment", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_environment_event(
request: EnvironmentRequest,
db: AsyncSession = Depends(get_db)
):
"""Record an environment event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
event = EnvironmentEvent(
session_id=session_id,
event_type=request.event_type,
environment=request.environment,
config_file=request.config_file,
changes=request.changes,
impact_level=request.impact_level,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(event)
await db.commit()
await db.refresh(event)
return HookResponse(
id=event.id,
hook_type="environment",
session_id=session_id,
timestamp=event.timestamp,
message=f"Environment event recorded: {request.event_type}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record environment event: {str(e)}")
# Collaboration Endpoints
@router.post("/hooks/collaboration", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_collaboration_event(
request: CollaborationRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a collaboration event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
event = CollaborationEvent(
session_id=session_id,
event_type=request.event_type,
interaction_type=request.interaction_type,
query_or_topic=request.query_or_topic,
resource_url=request.resource_url,
response_quality=request.response_quality,
time_to_resolution=request.time_to_resolution,
event_metadata=request.metadata,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(event)
await db.commit()
await db.refresh(event)
return HookResponse(
id=event.id,
hook_type="collaboration",
session_id=session_id,
timestamp=event.timestamp,
message=f"Collaboration event recorded: {request.event_type}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record collaboration event: {str(e)}")
# Project Intelligence Endpoints
@router.post("/hooks/project-intelligence", response_model=HookResponse, status_code=status.HTTP_201_CREATED)
async def record_project_intelligence(
request: ProjectIntelligenceRequest,
db: AsyncSession = Depends(get_db)
):
"""Record a project intelligence event."""
try:
session_id = request.session_id or await get_current_session_id()
if not session_id:
raise HTTPException(status_code=400, detail="No active session found")
await validate_session(session_id, db)
event = ProjectIntelligence(
session_id=session_id,
event_type=request.event_type,
scope=request.scope,
complexity=request.complexity,
end_time=request.end_time,
duration_minutes=request.duration_minutes,
files_affected=request.files_affected,
outcome=request.outcome,
notes=request.notes,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(event)
await db.commit()
await db.refresh(event)
return HookResponse(
id=event.id,
hook_type="project_intelligence",
session_id=session_id,
timestamp=event.timestamp,
message=f"Project intelligence recorded: {request.event_type}"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to record project intelligence: {str(e)}")
# Analytics Endpoints
@router.get("/hooks/analytics/summary", response_model=Dict[str, Any])
async def get_hook_analytics_summary(
project_id: Optional[int] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
db: AsyncSession = Depends(get_db)
):
"""Get comprehensive analytics summary across all hook types."""
try:
# Base filters
filters = []
if start_date:
filters.append(ToolError.timestamp >= start_date)
if end_date:
filters.append(ToolError.timestamp <= end_date)
if project_id:
filters.append(Session.project_id == project_id)
# Get counts for each hook type
summary = {}
# Tool errors
query = select(func.count(ToolError.id))
if project_id:
query = query.join(Session)
if filters:
query = query.where(and_(*filters))
result = await db.execute(query)
summary["tool_errors"] = result.scalar()
# Add similar queries for other hook types...
# (Similar pattern for each hook type)
return summary
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get analytics summary: {str(e)}")
@router.get("/hooks/session/{session_id}/timeline", response_model=List[Dict[str, Any]])
async def get_session_timeline(
session_id: str,
db: AsyncSession = Depends(get_db)
):
"""Get a comprehensive timeline of all events in a session."""
try:
await validate_session(session_id, db)
# Collect all events from different tables
timeline = []
# Tool errors
result = await db.execute(
select(ToolError).where(ToolError.session_id == session_id)
)
for event in result.scalars().all():
timeline.append({
"type": "tool_error",
"timestamp": event.timestamp,
"data": {
"tool_name": event.tool_name,
"error_type": event.error_type,
"error_message": event.error_message
}
})
# Add other event types to timeline...
# (Similar pattern for each event type)
# Sort by timestamp
timeline.sort(key=lambda x: x["timestamp"])
return timeline
except HTTPException:
raise
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to get session timeline: {str(e)}")

528
app/api/importer.py Normal file
View File

@ -0,0 +1,528 @@
"""
Data importer for Claude Code .claude.json file.
This module provides functionality to import historical data from the
.claude.json configuration file into the project tracker.
"""
import json
import os
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Any
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.database.connection import get_db
from app.models.project import Project
from app.models.session import Session
from app.models.conversation import Conversation
router = APIRouter()
class ClaudeJsonImporter:
"""Importer for .claude.json data."""
def __init__(self, db: AsyncSession):
self.db = db
async def import_from_file(self, file_path: str) -> Dict[str, Any]:
"""Import data from .claude.json file."""
if not os.path.exists(file_path):
raise FileNotFoundError(f"Claude configuration file not found: {file_path}")
try:
with open(file_path, 'r', encoding='utf-8') as f:
claude_data = json.load(f)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
return await self._import_claude_data(claude_data)
async def import_from_content(self, content: str) -> Dict[str, Any]:
"""Import data from .claude.json file content."""
try:
claude_data = json.loads(content)
except json.JSONDecodeError as e:
raise ValueError(f"Invalid JSON in Claude configuration file: {e}")
return await self._import_claude_data(claude_data)
async def _import_claude_data(self, claude_data: Dict[str, Any]) -> Dict[str, Any]:
"""Common import logic for both file and content imports."""
results = {
"projects_imported": 0,
"sessions_estimated": 0,
"conversations_imported": 0,
"errors": []
}
# Import basic usage statistics
await self._import_usage_stats(claude_data, results)
# Import projects and their history
if "projects" in claude_data:
await self._import_projects(claude_data["projects"], results)
return results
async def _import_usage_stats(self, claude_data: Dict[str, Any], results: Dict[str, Any]):
"""Import basic usage statistics."""
# We could create a synthetic "Claude Code Usage" project to track overall stats
if claude_data.get("numStartups") and claude_data.get("firstStartTime"):
try:
first_start = datetime.fromisoformat(
claude_data["firstStartTime"].replace('Z', '+00:00')
)
# Create a synthetic project for overall Claude Code usage
usage_project = await self._get_or_create_project(
name="Claude Code Usage Statistics",
path="<system>",
description="Imported usage statistics from .claude.json"
)
# Estimate session distribution over time
num_startups = claude_data["numStartups"]
days_since_first = (datetime.now() - first_start.replace(tzinfo=None)).days
if days_since_first > 0:
# Create estimated sessions spread over the usage period
await self._create_estimated_sessions(
usage_project,
first_start.replace(tzinfo=None),
num_startups,
days_since_first
)
results["sessions_estimated"] = num_startups
except Exception as e:
results["errors"].append(f"Failed to import usage stats: {e}")
async def _import_projects(self, projects_data: Dict[str, Any], results: Dict[str, Any]):
"""Import project data from .claude.json."""
for project_path, project_info in projects_data.items():
try:
# Skip system paths or non-meaningful paths
if project_path in ["<system>", "/", "/tmp"]:
continue
# Extract project name from path
project_name = Path(project_path).name or "Unknown Project"
# Create or get existing project
project = await self._get_or_create_project(
name=project_name,
path=project_path
)
results["projects_imported"] += 1
# Import conversation history if available
if "history" in project_info and isinstance(project_info["history"], list):
conversation_count = await self._import_project_history(
project,
project_info["history"]
)
results["conversations_imported"] += conversation_count
except Exception as e:
results["errors"].append(f"Failed to import project {project_path}: {e}")
async def _get_or_create_project(
self,
name: str,
path: str,
description: Optional[str] = None
) -> Project:
"""Get existing project or create new one."""
# Check if project already exists
result = await self.db.execute(
select(Project).where(Project.path == path)
)
existing_project = result.scalars().first()
if existing_project:
return existing_project
# Try to detect languages from path
languages = self._detect_languages(path)
# Create new project
project = Project(
name=name,
path=path,
languages=languages
)
self.db.add(project)
await self.db.commit()
await self.db.refresh(project)
return project
def _detect_languages(self, project_path: str) -> Optional[List[str]]:
"""Attempt to detect programming languages from project directory."""
languages = []
try:
if os.path.exists(project_path) and os.path.isdir(project_path):
# Look for common files to infer languages
files = os.listdir(project_path)
# Python
if any(f.endswith(('.py', '.pyx', '.pyi')) for f in files) or 'requirements.txt' in files:
languages.append('python')
# JavaScript/TypeScript
if any(f.endswith(('.js', '.jsx', '.ts', '.tsx')) for f in files) or 'package.json' in files:
if any(f.endswith(('.ts', '.tsx')) for f in files):
languages.append('typescript')
else:
languages.append('javascript')
# Go
if any(f.endswith('.go') for f in files) or 'go.mod' in files:
languages.append('go')
# Rust
if any(f.endswith('.rs') for f in files) or 'Cargo.toml' in files:
languages.append('rust')
# Java
if any(f.endswith('.java') for f in files) or 'pom.xml' in files:
languages.append('java')
except (OSError, PermissionError):
# If we can't read the directory, that's okay
pass
return languages if languages else None
async def _create_estimated_sessions(
self,
project: Project,
first_start: datetime,
num_startups: int,
days_since_first: int
):
"""Create estimated sessions based on startup count."""
# Check if we already have sessions for this project
existing_sessions = await self.db.execute(
select(Session).where(
Session.project_id == project.id,
Session.session_type == "startup"
)
)
if existing_sessions.scalars().first():
return # Sessions already exist, skip creation
# Don't create too many sessions - limit to reasonable estimates
max_sessions = min(num_startups, 50) # Cap at 50 sessions
# Distribute sessions over the time period
if days_since_first > 0:
sessions_per_day = max_sessions / days_since_first
for i in range(max_sessions):
# Spread sessions over the time period
days_offset = int(i / sessions_per_day) if sessions_per_day > 0 else i
session_time = first_start + timedelta(days=days_offset)
# Estimate session duration (30-180 minutes)
import random
duration = random.randint(30, 180)
session = Session(
project_id=project.id,
start_time=session_time,
end_time=session_time + timedelta(minutes=duration),
session_type="startup",
working_directory=project.path,
duration_minutes=duration,
activity_count=random.randint(5, 25), # Estimated activity
conversation_count=random.randint(2, 8) # Estimated conversations
)
self.db.add(session)
await self.db.commit()
async def _import_project_history(
self,
project: Project,
history: List[Dict[str, Any]]
) -> int:
"""Import conversation history for a project."""
# Check if we already have history conversations for this project
existing_conversations = await self.db.execute(
select(Conversation).where(
Conversation.context.like('%"imported_from": ".claude.json"%'),
Conversation.session.has(Session.project_id == project.id)
)
)
if existing_conversations.scalars().first():
return 0 # History already imported, skip
conversation_count = 0
# Create a synthetic session for imported history
history_session = Session(
project_id=project.id,
start_time=datetime.now() - timedelta(days=30), # Assume recent
session_type="history_import", # Different type to avoid conflicts
working_directory=project.path,
activity_count=len(history),
conversation_count=len(history)
)
self.db.add(history_session)
await self.db.commit()
await self.db.refresh(history_session)
# Import each history entry as a conversation
for i, entry in enumerate(history[:20]): # Limit to 20 entries
try:
display_text = entry.get("display", "")
if display_text:
conversation = Conversation(
session_id=history_session.id,
timestamp=history_session.start_time + timedelta(minutes=i * 5),
user_prompt=display_text,
exchange_type="user_prompt",
context={"imported_from": ".claude.json"}
)
self.db.add(conversation)
conversation_count += 1
except Exception as e:
# Skip problematic entries
continue
if conversation_count > 0:
await self.db.commit()
return conversation_count
@router.post("/import/claude-json")
async def import_claude_json(
file_path: Optional[str] = None,
db: AsyncSession = Depends(get_db)
):
"""
Import data from .claude.json file.
If no file_path is provided, tries to find .claude.json in the user's home directory.
"""
if not file_path:
# Try default location
home_path = Path.home() / ".claude.json"
file_path = str(home_path)
try:
importer = ClaudeJsonImporter(db)
results = await importer.import_from_file(file_path)
return {
"success": True,
"message": "Import completed successfully",
"results": results
}
except FileNotFoundError as e:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Claude configuration file not found: {e}"
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid file format: {e}"
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Import failed: {e}"
)
@router.post("/import/claude-json/upload")
async def import_claude_json_upload(
file: UploadFile = File(...),
db: AsyncSession = Depends(get_db)
):
"""
Import data from uploaded .claude.json file.
"""
# Validate file type
if file.filename and not file.filename.endswith('.json'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be a JSON file (.json)"
)
# Check file size (limit to 10MB)
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File too large. Maximum size is 10MB."
)
try:
# Decode file content
file_content = content.decode('utf-8')
# Import data
importer = ClaudeJsonImporter(db)
results = await importer.import_from_content(file_content)
return {
"success": True,
"message": "Import completed successfully",
"file_name": file.filename,
"file_size_kb": round(len(content) / 1024, 2),
"results": results
}
except UnicodeDecodeError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be UTF-8 encoded"
)
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid file format: {e}"
)
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Import failed: {e}"
)
@router.get("/import/claude-json/preview")
async def preview_claude_json_import(
file_path: Optional[str] = None
):
"""
Preview what would be imported from .claude.json file without actually importing.
"""
if not file_path:
home_path = Path.home() / ".claude.json"
file_path = str(home_path)
if not os.path.exists(file_path):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Claude configuration file not found"
)
try:
with open(file_path, 'r', encoding='utf-8') as f:
claude_data = json.load(f)
except json.JSONDecodeError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid JSON in Claude configuration file: {e}"
)
preview = {
"file_path": file_path,
"file_size_mb": round(os.path.getsize(file_path) / (1024 * 1024), 2),
"claude_usage": {
"num_startups": claude_data.get("numStartups", 0),
"first_start_time": claude_data.get("firstStartTime"),
"prompt_queue_use_count": claude_data.get("promptQueueUseCount", 0)
},
"projects": {
"total_count": len(claude_data.get("projects", {})),
"paths": list(claude_data.get("projects", {}).keys())[:10], # Show first 10
"has_more": len(claude_data.get("projects", {})) > 10
},
"history_entries": 0
}
# Count total history entries across all projects
if "projects" in claude_data:
total_history = sum(
len(proj.get("history", []))
for proj in claude_data["projects"].values()
)
preview["history_entries"] = total_history
return preview
@router.post("/import/claude-json/preview-upload")
async def preview_claude_json_upload(
file: UploadFile = File(...)
):
"""
Preview what would be imported from uploaded .claude.json file without actually importing.
"""
# Validate file type
if file.filename and not file.filename.endswith('.json'):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be a JSON file (.json)"
)
# Check file size (limit to 10MB)
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB
content = await file.read()
if len(content) > MAX_FILE_SIZE:
raise HTTPException(
status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE,
detail="File too large. Maximum size is 10MB."
)
try:
# Decode and parse file content
file_content = content.decode('utf-8')
claude_data = json.loads(file_content)
except UnicodeDecodeError:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="File must be UTF-8 encoded"
)
except json.JSONDecodeError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Invalid JSON in Claude configuration file: {e}"
)
preview = {
"file_name": file.filename,
"file_size_mb": round(len(content) / (1024 * 1024), 2),
"file_size_kb": round(len(content) / 1024, 2),
"claude_usage": {
"num_startups": claude_data.get("numStartups", 0),
"first_start_time": claude_data.get("firstStartTime"),
"prompt_queue_use_count": claude_data.get("promptQueueUseCount", 0)
},
"projects": {
"total_count": len(claude_data.get("projects", {})),
"paths": list(claude_data.get("projects", {}).keys())[:10], # Show first 10
"has_more": len(claude_data.get("projects", {})) > 10
},
"history_entries": 0
}
# Count total history entries across all projects
if "projects" in claude_data:
total_history = sum(
len(proj.get("history", []))
for proj in claude_data["projects"].values()
)
preview["history_entries"] = total_history
return preview

254
app/api/tool_calls.py Normal file
View File

@ -0,0 +1,254 @@
"""
Tool call tracking API endpoints.
"""
import json
from datetime import datetime
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func, desc
from sqlalchemy.orm import selectinload
from pydantic import BaseModel, Field
from app.database.connection import get_db
from app.models.session import Session
from app.models.tool_call import ToolCall
router = APIRouter()
class ToolCallRequest(BaseModel):
"""Request schema for recording a tool call."""
session_id: Optional[str] = Field(None, description="Session ID (auto-detected if not provided)")
tool_name: str = Field(..., description="Name of the tool being called")
parameters: Optional[dict] = Field(None, description="Tool parameters as JSON object")
result_status: Optional[str] = Field("success", description="Result status: success, error, timeout")
error_message: Optional[str] = Field(None, description="Error message if failed")
execution_time_ms: Optional[int] = Field(None, description="Execution time in milliseconds")
timestamp: Optional[datetime] = Field(None, description="Timestamp of the tool call")
class ToolCallResponse(BaseModel):
"""Response schema for tool call operations."""
id: int
session_id: str
tool_name: str
result_status: str
timestamp: datetime
message: str
class ToolUsageStats(BaseModel):
"""Statistics about tool usage."""
tool_name: str
total_calls: int
success_calls: int
error_calls: int
avg_execution_time_ms: Optional[float]
success_rate: float
async def get_current_session_id() -> Optional[str]:
"""Get the current session ID from the temporary file."""
try:
with open("/tmp/claude-session-id", "r") as f:
return f.read().strip()
except (OSError, FileNotFoundError):
return None
@router.post("/tool-calls", response_model=ToolCallResponse, status_code=status.HTTP_201_CREATED)
async def record_tool_call(
request: ToolCallRequest,
db: AsyncSession = Depends(get_db)
):
"""
Record a tool call made during a Claude Code session.
This endpoint is called by Claude Code hooks when tools are used.
It automatically detects the current session if not provided.
"""
try:
# Get session ID
session_id = request.session_id
if not session_id:
session_id = await get_current_session_id()
if not session_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="No active session found. Please provide session_id or ensure a session is running."
)
# Verify session exists
result = await db.execute(
select(Session).where(Session.id == int(session_id))
)
session = result.scalars().first()
if not session:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Session {session_id} not found"
)
# Create tool call record
tool_call = ToolCall(
session_id=str(session_id),
tool_name=request.tool_name,
parameters=json.dumps(request.parameters) if request.parameters else None,
result_status=request.result_status or "success",
error_message=request.error_message,
execution_time_ms=request.execution_time_ms,
timestamp=request.timestamp or datetime.utcnow()
)
db.add(tool_call)
await db.commit()
await db.refresh(tool_call)
return ToolCallResponse(
id=tool_call.id,
session_id=tool_call.session_id,
tool_name=tool_call.tool_name,
result_status=tool_call.result_status,
timestamp=tool_call.timestamp,
message=f"Tool call '{request.tool_name}' recorded successfully"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to record tool call: {str(e)}"
)
@router.get("/tool-calls/session/{session_id}", response_model=List[dict])
async def get_session_tool_calls(
session_id: str,
db: AsyncSession = Depends(get_db)
):
"""Get all tool calls for a specific session."""
try:
result = await db.execute(
select(ToolCall)
.where(ToolCall.session_id == session_id)
.order_by(desc(ToolCall.timestamp))
)
tool_calls = result.scalars().all()
return [
{
"id": tc.id,
"tool_name": tc.tool_name,
"parameters": json.loads(tc.parameters) if tc.parameters else None,
"result_status": tc.result_status,
"error_message": tc.error_message,
"execution_time_ms": tc.execution_time_ms,
"timestamp": tc.timestamp
}
for tc in tool_calls
]
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get tool calls: {str(e)}"
)
@router.get("/tool-calls/stats", response_model=List[ToolUsageStats])
async def get_tool_usage_stats(
project_id: Optional[int] = None,
start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None,
db: AsyncSession = Depends(get_db)
):
"""Get tool usage statistics with optional filtering."""
try:
# Base query
query = select(
ToolCall.tool_name,
func.count(ToolCall.id).label("total_calls"),
func.sum(func.case([(ToolCall.result_status == "success", 1)], else_=0)).label("success_calls"),
func.sum(func.case([(ToolCall.result_status == "error", 1)], else_=0)).label("error_calls"),
func.avg(ToolCall.execution_time_ms).label("avg_execution_time_ms")
)
# Apply filters
if project_id:
query = query.join(Session).where(Session.project_id == project_id)
if start_date:
query = query.where(ToolCall.timestamp >= start_date)
if end_date:
query = query.where(ToolCall.timestamp <= end_date)
# Group by tool name
query = query.group_by(ToolCall.tool_name).order_by(desc("total_calls"))
result = await db.execute(query)
rows = result.all()
stats = []
for row in rows:
success_rate = (row.success_calls / row.total_calls * 100) if row.total_calls > 0 else 0
stats.append(ToolUsageStats(
tool_name=row.tool_name,
total_calls=row.total_calls,
success_calls=row.success_calls,
error_calls=row.error_calls,
avg_execution_time_ms=float(row.avg_execution_time_ms) if row.avg_execution_time_ms else None,
success_rate=round(success_rate, 2)
))
return stats
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get tool usage stats: {str(e)}"
)
@router.get("/tool-calls/popular", response_model=List[dict])
async def get_popular_tools(
limit: int = 10,
project_id: Optional[int] = None,
db: AsyncSession = Depends(get_db)
):
"""Get the most frequently used tools."""
try:
query = select(
ToolCall.tool_name,
func.count(ToolCall.id).label("usage_count"),
func.max(ToolCall.timestamp).label("last_used")
)
if project_id:
query = query.join(Session).where(Session.project_id == project_id)
query = query.group_by(ToolCall.tool_name).order_by(desc("usage_count")).limit(limit)
result = await db.execute(query)
rows = result.all()
return [
{
"tool_name": row.tool_name,
"usage_count": row.usage_count,
"last_used": row.last_used
}
for row in rows
]
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get popular tools: {str(e)}"
)

View File

@ -47,3 +47,88 @@ async def dashboard_conversations(request: Request, db: AsyncSession = Depends(g
"request": request, "request": request,
"title": "Conversations - Claude Code Tracker" "title": "Conversations - Claude Code Tracker"
}) })
@dashboard_router.get("/dashboard/import", response_class=HTMLResponse)
async def dashboard_import(request: Request, db: AsyncSession = Depends(get_db)):
"""Data import page."""
return templates.TemplateResponse("import.html", {
"request": request,
"title": "Import Data - Claude Code Tracker"
})
@dashboard_router.get("/dashboard/projects/{project_id}/timeline", response_class=HTMLResponse)
async def dashboard_project_timeline(request: Request, project_id: int, db: AsyncSession = Depends(get_db)):
"""Project timeline page."""
return templates.TemplateResponse("project_timeline.html", {
"request": request,
"title": f"Timeline - Project {project_id}",
"project_id": project_id
})
@dashboard_router.get("/dashboard/projects/{project_id}/stats", response_class=HTMLResponse)
async def dashboard_project_stats(request: Request, project_id: int, db: AsyncSession = Depends(get_db)):
"""Project statistics page."""
return templates.TemplateResponse("project_stats.html", {
"request": request,
"title": f"Statistics - Project {project_id}",
"project_id": project_id
})
@dashboard_router.get("/dashboard/docs", response_class=HTMLResponse)
async def dashboard_docs(request: Request, db: AsyncSession = Depends(get_db)):
"""Documentation overview page."""
return templates.TemplateResponse("docs/index.html", {
"request": request,
"title": "Documentation - Claude Code Tracker"
})
@dashboard_router.get("/dashboard/docs/{section}", response_class=HTMLResponse)
async def dashboard_docs_section(request: Request, section: str, db: AsyncSession = Depends(get_db)):
"""Documentation section pages."""
# Map section names to templates and titles
sections = {
"getting-started": {
"template": "docs/getting-started.html",
"title": "Getting Started - Claude Code Tracker"
},
"data-import": {
"template": "docs/data-import.html",
"title": "Data Import Guide - Claude Code Tracker"
},
"docker-deployment": {
"template": "docs/docker-deployment.html",
"title": "Docker Deployment Guide - Claude Code Tracker"
},
"hook-setup": {
"template": "docs/hook-setup.html",
"title": "Hook Setup Guide - Claude Code Tracker"
},
"hook-reference": {
"template": "docs/hook-reference.html",
"title": "Complete Hook Reference - Claude Code Tracker"
},
"api-reference": {
"template": "docs/api-reference.html",
"title": "API Reference - Claude Code Tracker"
},
"faq": {
"template": "docs/faq.html",
"title": "FAQ - Claude Code Tracker"
}
}
if section not in sections:
from fastapi import HTTPException
raise HTTPException(status_code=404, detail=f"Documentation section '{section}' not found")
section_info = sections[section]
return templates.TemplateResponse(section_info["template"], {
"request": request,
"title": section_info["title"],
"section": section
})

View File

@ -0,0 +1,9 @@
{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\"}'"
}
}

View File

@ -0,0 +1,50 @@
{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\", \"parameters\": $TOOL_PARAMS}'",
"waiting_period_start": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'",
"waiting_period_end": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'",
"memory_usage": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'",
"large_file_warning": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"file_size\", \"value\": $FILE_SIZE_MB, \"unit\": \"MB\", \"threshold_exceeded\": true}'",
"code_analysis": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"analysis\", \"file_path\": \"$FILE_PATH\", \"tool_name\": \"$ANALYSIS_TOOL\", \"status\": \"$STATUS\", \"issues_count\": $ISSUES_COUNT, \"details\": $DETAILS}'",
"test_execution": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"test\", \"tool_name\": \"$TEST_FRAMEWORK\", \"status\": \"$STATUS\", \"details\": {\"passed\": $PASSED_COUNT, \"failed\": $FAILED_COUNT}, \"duration_ms\": $DURATION_MS}'",
"build_process": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"build\", \"tool_name\": \"$BUILD_TOOL\", \"status\": \"$STATUS\", \"duration_ms\": $DURATION_MS}'",
"dependency_change": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"dependency_change\", \"config_file\": \"$PACKAGE_FILE\", \"changes\": {\"action\": \"$ACTION\", \"package\": \"$PACKAGE_NAME\", \"version\": \"$VERSION\"}}'",
"context_switch": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"context_switch\", \"description\": \"Switched to $NEW_PROJECT\", \"metadata\": {\"from\": \"$OLD_PROJECT\", \"to\": \"$NEW_PROJECT\"}}'",
"search_query": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"search_query\", \"description\": \"$SEARCH_QUERY\", \"metadata\": {\"search_type\": \"$SEARCH_TYPE\", \"context\": \"$CONTEXT\"}}'",
"browser_tab": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"browser_tab\", \"description\": \"$TAB_TITLE\", \"metadata\": {\"url\": \"$TAB_URL\", \"category\": \"$TAB_CATEGORY\"}}'",
"copy_paste": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"copy_paste\", \"description\": \"Code copied/pasted\", \"metadata\": {\"action\": \"$ACTION\", \"source\": \"$SOURCE\", \"lines\": $LINE_COUNT}}'",
"external_resource": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"external_resource\", \"interaction_type\": \"$RESOURCE_TYPE\", \"resource_url\": \"$URL\", \"query_or_topic\": \"$TOPIC\"}'",
"ai_question": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"ai_question\", \"query_or_topic\": \"$QUESTION\", \"metadata\": {\"question_type\": \"$QUESTION_TYPE\", \"complexity\": \"$COMPLEXITY\"}}'",
"code_explanation": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"code_explanation\", \"query_or_topic\": \"$CODE_CONTEXT\", \"metadata\": {\"explanation_type\": \"$TYPE\", \"code_lines\": $CODE_LINES}}'",
"review_request": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"review_request\", \"query_or_topic\": \"$REVIEW_SCOPE\", \"metadata\": {\"files_count\": $FILES_COUNT, \"review_type\": \"$REVIEW_TYPE\"}}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'",
"environment_change": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"env_change\", \"environment\": \"$NEW_ENV\", \"impact_level\": \"$IMPACT_LEVEL\"}'",
"config_update": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"config_update\", \"config_file\": \"$CONFIG_FILE\", \"changes\": $CONFIG_CHANGES, \"impact_level\": \"$IMPACT_LEVEL\"}'",
"security_scan": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"security_scan\", \"changes\": {\"tool\": \"$SCAN_TOOL\", \"vulnerabilities\": $VULN_COUNT, \"severity\": \"$MAX_SEVERITY\"}}'",
"performance_benchmark": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"performance_benchmark\", \"changes\": {\"benchmark_type\": \"$BENCHMARK_TYPE\", \"results\": $BENCHMARK_RESULTS}}'",
"learning_session": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"learning_session\", \"topic\": \"$LEARNING_TOPIC\", \"confidence_before\": $CONFIDENCE_BEFORE, \"confidence_after\": $CONFIDENCE_AFTER, \"duration_ms\": $DURATION_MS}'",
"tutorial_follow": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"tutorial\", \"topic\": \"$TUTORIAL_TOPIC\", \"resource_url\": \"$TUTORIAL_URL\", \"notes\": \"$TUTORIAL_NOTES\"}'",
"experimentation": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"experimentation\", \"topic\": \"$EXPERIMENT_TYPE\", \"notes\": \"$EXPERIMENT_DESCRIPTION\"}'",
"knowledge_gap": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"knowledge_gap\", \"topic\": \"$UNKNOWN_CONCEPT\", \"notes\": \"$GAP_DESCRIPTION\"}'",
"manual_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"manual_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"test_type\": \"$TEST_TYPE\", \"scenarios\": $SCENARIO_COUNT}, \"duration_ms\": $DURATION_MS}'",
"bug_reproduction": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"bug_reproduction\", \"complexity\": \"$BUG_COMPLEXITY\", \"notes\": \"$BUG_DESCRIPTION\", \"outcome\": \"$REPRODUCTION_RESULT\"}'",
"edge_case_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"edge_case_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"edge_cases\": $EDGE_CASE_COUNT, \"findings\": \"$FINDINGS\"}}'",
"user_feedback": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"user_feedback\", \"query_or_topic\": \"$FEEDBACK_TOPIC\", \"response_quality\": $FEEDBACK_RATING, \"metadata\": {\"feedback_type\": \"$FEEDBACK_TYPE\", \"urgency\": \"$URGENCY\"}}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,25 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"waiting_period_start": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'",
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\", \"parameters\": $TOOL_PARAMS}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"test_execution": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"test\", \"tool_name\": \"$TEST_FRAMEWORK\", \"status\": \"$STATUS\", \"details\": {\"passed\": $PASSED_COUNT, \"failed\": $FAILED_COUNT}, \"duration_ms\": $DURATION_MS}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"waiting_period_end": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'",
"large_file_warning": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"file_size\", \"value\": $FILE_SIZE_MB, \"unit\": \"MB\", \"threshold_exceeded\": true}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"memory_usage": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'",
"code_analysis": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"analysis\", \"file_path\": \"$FILE_PATH\", \"tool_name\": \"$ANALYSIS_TOOL\", \"status\": \"$STATUS\", \"issues_count\": $ISSUES_COUNT, \"details\": $DETAILS}'",
"dependency_change": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"dependency_change\", \"config_file\": \"$PACKAGE_FILE\", \"changes\": {\"action\": \"$ACTION\", \"package\": \"$PACKAGE_NAME\", \"version\": \"$VERSION\"}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"build_process": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"build\", \"tool_name\": \"$BUILD_TOOL\", \"status\": \"$STATUS\", \"duration_ms\": $DURATION_MS}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,16 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,30 @@
{
"hooks": {
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"browser_tab": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"browser_tab\", \"description\": \"$TAB_TITLE\", \"metadata\": {\"url\": \"$TAB_URL\", \"category\": \"$TAB_CATEGORY\"}}'",
"search_query": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"search_query\", \"description\": \"$SEARCH_QUERY\", \"metadata\": {\"search_type\": \"$SEARCH_TYPE\", \"context\": \"$CONTEXT\"}}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"waiting_period_end": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"memory_usage": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'",
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\", \"parameters\": $TOOL_PARAMS}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"copy_paste": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"copy_paste\", \"description\": \"Code copied/pasted\", \"metadata\": {\"action\": \"$ACTION\", \"source\": \"$SOURCE\", \"lines\": $LINE_COUNT}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"waiting_period_start": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"large_file_warning": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"file_size\", \"value\": $FILE_SIZE_MB, \"unit\": \"MB\", \"threshold_exceeded\": true}'",
"context_switch": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"context_switch\", \"description\": \"Switched to $NEW_PROJECT\", \"metadata\": {\"from\": \"$OLD_PROJECT\", \"to\": \"$NEW_PROJECT\"}}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,29 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"code_explanation": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"code_explanation\", \"query_or_topic\": \"$CODE_CONTEXT\", \"metadata\": {\"explanation_type\": \"$TYPE\", \"code_lines\": $CODE_LINES}}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"learning_session": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"learning_session\", \"topic\": \"$LEARNING_TOPIC\", \"confidence_before\": $CONFIDENCE_BEFORE, \"confidence_after\": $CONFIDENCE_AFTER, \"duration_ms\": $DURATION_MS}'",
"tutorial_follow": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"tutorial\", \"topic\": \"$TUTORIAL_TOPIC\", \"resource_url\": \"$TUTORIAL_URL\", \"notes\": \"$TUTORIAL_NOTES\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"ai_question": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"ai_question\", \"query_or_topic\": \"$QUESTION\", \"metadata\": {\"question_type\": \"$QUESTION_TYPE\", \"complexity\": \"$COMPLEXITY\"}}'",
"external_resource": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"external_resource\", \"interaction_type\": \"$RESOURCE_TYPE\", \"resource_url\": \"$URL\", \"query_or_topic\": \"$TOPIC\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"experimentation": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"experimentation\", \"topic\": \"$EXPERIMENT_TYPE\", \"notes\": \"$EXPERIMENT_DESCRIPTION\"}'",
"knowledge_gap": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"knowledge_gap\", \"topic\": \"$UNKNOWN_CONCEPT\", \"notes\": \"$GAP_DESCRIPTION\"}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"review_request": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"review_request\", \"query_or_topic\": \"$REVIEW_SCOPE\", \"metadata\": {\"files_count\": $FILES_COUNT, \"review_type\": \"$REVIEW_TYPE\"}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,29 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"code_explanation": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"code_explanation\", \"query_or_topic\": \"$CODE_CONTEXT\", \"metadata\": {\"explanation_type\": \"$TYPE\", \"code_lines\": $CODE_LINES}}'",
"edge_case_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"edge_case_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"edge_cases\": $EDGE_CASE_COUNT, \"findings\": \"$FINDINGS\"}}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"ai_question": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"ai_question\", \"query_or_topic\": \"$QUESTION\", \"metadata\": {\"question_type\": \"$QUESTION_TYPE\", \"complexity\": \"$COMPLEXITY\"}}'",
"external_resource": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"external_resource\", \"interaction_type\": \"$RESOURCE_TYPE\", \"resource_url\": \"$URL\", \"query_or_topic\": \"$TOPIC\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"manual_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"manual_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"test_type\": \"$TEST_TYPE\", \"scenarios\": $SCENARIO_COUNT}, \"duration_ms\": $DURATION_MS}'",
"bug_reproduction": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"bug_reproduction\", \"complexity\": \"$BUG_COMPLEXITY\", \"notes\": \"$BUG_DESCRIPTION\", \"outcome\": \"$REPRODUCTION_RESULT\"}'",
"user_feedback": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"user_feedback\", \"query_or_topic\": \"$FEEDBACK_TOPIC\", \"response_quality\": $FEEDBACK_RATING, \"metadata\": {\"feedback_type\": \"$FEEDBACK_TYPE\", \"urgency\": \"$URGENCY\"}}'",
"review_request": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"review_request\", \"query_or_topic\": \"$REVIEW_SCOPE\", \"metadata\": {\"files_count\": $FILES_COUNT, \"review_type\": \"$REVIEW_TYPE\"}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -92,6 +92,14 @@ class ApiClient {
} }
// Conversations API // Conversations API
async getConversations(projectId = null, limit = 50, offset = 0) {
let url = `/conversations?limit=${limit}&offset=${offset}`;
if (projectId) {
url += `&project_id=${projectId}`;
}
return this.request(url);
}
async searchConversations(query, projectId = null, limit = 20) { async searchConversations(query, projectId = null, limit = 20) {
let url = `/conversations/search?query=${encodeURIComponent(query)}&limit=${limit}`; let url = `/conversations/search?query=${encodeURIComponent(query)}&limit=${limit}`;
if (projectId) { if (projectId) {

View File

@ -47,12 +47,24 @@
Conversations Conversations
</a> </a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="/dashboard/import">
<i class="fas fa-download me-1"></i>
Import Data
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/dashboard/docs">
<i class="fas fa-book me-1"></i>
Docs
</a>
</li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/docs" target="_blank"> <a class="nav-link" href="/docs" target="_blank">
<i class="fas fa-book me-1"></i> <i class="fas fa-external-link-alt me-1"></i>
API Docs API Docs
</a> </a>
</li> </li>

View File

@ -29,6 +29,10 @@
<i class="fas fa-search me-1"></i> <i class="fas fa-search me-1"></i>
Search Search
</button> </button>
<button class="btn btn-outline-secondary" type="button" onclick="clearSearch()">
<i class="fas fa-times me-1"></i>
Clear
</button>
</div> </div>
<div class="form-text"> <div class="form-text">
Search through your conversation history with Claude Code Search through your conversation history with Claude Code
@ -58,9 +62,10 @@
<div class="card-body"> <div class="card-body">
<div id="conversation-results"> <div id="conversation-results">
<div class="text-center text-muted py-5"> <div class="text-center text-muted py-5">
<i class="fas fa-search fa-3x mb-3"></i> <div class="spinner-border text-primary" role="status">
<h5>Search Your Conversations</h5> <span class="visually-hidden">Loading conversations...</span>
<p>Enter a search term to find relevant conversations with Claude.</p> </div>
<p class="mt-2">Loading your recent conversations...</p>
</div> </div>
</div> </div>
</div> </div>
@ -73,6 +78,7 @@
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadProjects(); loadProjects();
loadRecentConversations();
}); });
async function loadProjects() { async function loadProjects() {
@ -86,17 +92,91 @@ async function loadProjects() {
option.textContent = project.name; option.textContent = project.name;
select.appendChild(option); select.appendChild(option);
}); });
// Add event listener for project filtering
select.addEventListener('change', function() {
const selectedProjectId = this.value ? parseInt(this.value) : null;
loadConversationsForProject(selectedProjectId);
});
} catch (error) { } catch (error) {
console.error('Failed to load projects:', error); console.error('Failed to load projects:', error);
} }
} }
async function loadRecentConversations() {
await loadConversationsForProject(null);
}
async function loadConversationsForProject(projectId = null) {
const resultsContainer = document.getElementById('conversation-results');
const resultsTitle = document.getElementById('results-title');
// Show loading state
resultsContainer.innerHTML = `
<div class="text-center text-muted py-3">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading conversations...</p>
</div>
`;
try {
// Update title based on filtering
if (projectId) {
const projects = await apiClient.getProjects();
const project = projects.find(p => p.id === projectId);
resultsTitle.textContent = `Conversations - ${project ? project.name : 'Selected Project'}`;
} else {
resultsTitle.textContent = 'Recent Conversations';
}
const conversations = await apiClient.getConversations(projectId, 50);
if (!conversations.length) {
const noResultsMessage = projectId ?
`No conversations found for this project.` :
`No conversation history has been recorded yet.`;
resultsContainer.innerHTML = `
<div class="text-center text-muted py-5">
<i class="fas fa-comments fa-3x mb-3"></i>
<h5>No Conversations Found</h5>
<p>${noResultsMessage}</p>
${!projectId ? '<p class="small text-muted">Conversations will appear here as you use Claude Code.</p>' : ''}
</div>
`;
return;
}
displaySearchResults(conversations, null);
} catch (error) {
console.error('Failed to load conversations:', error);
resultsContainer.innerHTML = `
<div class="text-center text-danger py-4">
<i class="fas fa-exclamation-triangle fa-2x mb-2"></i>
<p>Failed to load conversations. Please try again.</p>
</div>
`;
}
}
function handleSearchKeypress(event) { function handleSearchKeypress(event) {
if (event.key === 'Enter') { if (event.key === 'Enter') {
searchConversations(); searchConversations();
} }
} }
function clearSearch() {
// Clear the search input
document.getElementById('search-query').value = '';
// Return to filtered/unfiltered conversation list based on current project filter
const projectId = document.getElementById('project-filter').value || null;
loadConversationsForProject(projectId ? parseInt(projectId) : null);
}
async function searchConversations() { async function searchConversations() {
const query = document.getElementById('search-query').value.trim(); const query = document.getElementById('search-query').value.trim();
const projectId = document.getElementById('project-filter').value || null; const projectId = document.getElementById('project-filter').value || null;
@ -164,9 +244,11 @@ function displaySearchResults(results, query) {
<i class="fas fa-clock me-1"></i> <i class="fas fa-clock me-1"></i>
${ApiUtils.formatRelativeTime(result.timestamp)} ${ApiUtils.formatRelativeTime(result.timestamp)}
</small> </small>
<span class="badge bg-success ms-2"> ${query ? `
${(result.relevance_score * 100).toFixed(0)}% match <span class="badge bg-success ms-2">
</span> ${(result.relevance_score * 100).toFixed(0)}% match
</span>
` : ''}
</div> </div>
${result.user_prompt ? ` ${result.user_prompt ? `
@ -175,7 +257,7 @@ function displaySearchResults(results, query) {
<i class="fas fa-user me-1"></i> <i class="fas fa-user me-1"></i>
You: You:
</strong> </strong>
<p class="mb-1">${highlightSearchTerms(ApiUtils.truncateText(result.user_prompt, 200), query)}</p> <p class="mb-1">${query ? highlightSearchTerms(ApiUtils.truncateText(result.user_prompt, 200), query) : ApiUtils.truncateText(result.user_prompt, 200)}</p>
</div> </div>
` : ''} ` : ''}
@ -185,11 +267,11 @@ function displaySearchResults(results, query) {
<i class="fas fa-robot me-1"></i> <i class="fas fa-robot me-1"></i>
Claude: Claude:
</strong> </strong>
<p class="mb-1">${highlightSearchTerms(ApiUtils.truncateText(result.claude_response, 200), query)}</p> <p class="mb-1">${query ? highlightSearchTerms(ApiUtils.truncateText(result.claude_response, 200), query) : ApiUtils.truncateText(result.claude_response, 200)}</p>
</div> </div>
` : ''} ` : ''}
${result.context && result.context.length ? ` ${result.context && result.context.length && query ? `
<div class="mt-2"> <div class="mt-2">
<small class="text-muted">Context snippets:</small> <small class="text-muted">Context snippets:</small>
${result.context.map(snippet => ` ${result.context.map(snippet => `

View File

@ -0,0 +1,540 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
<li class="breadcrumb-item active" aria-current="page">API Reference</li>
</ol>
</nav>
<h2><i class="fas fa-code me-3"></i>API Reference</h2>
<p class="lead text-muted">Complete reference for the Claude Code Tracker REST API</p>
</div>
<div>
<a href="/docs" target="_blank" class="btn btn-primary">
<i class="fas fa-external-link-alt me-1"></i>
Interactive Docs
</a>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Base URL -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-server me-2 text-primary"></i>Base URL</h3>
<div class="bg-dark text-light p-3 rounded">
<code>http://localhost:8000</code>
</div>
<p class="text-muted mt-2 mb-0">All API endpoints are relative to this base URL. The server runs on port 8000 by default.</p>
</div>
</div>
<!-- Authentication -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-key me-2 text-warning"></i>Authentication</h3>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Currently, the API does not require authentication. All endpoints are publicly accessible when the server is running.
</div>
</div>
</div>
<!-- Projects API -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h3 class="mb-0"><i class="fas fa-folder-open me-2"></i>Projects API</h3>
</div>
<div class="card-body">
<!-- Get Projects -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/projects</code>
</div>
<p class="text-muted">Retrieve all projects with their statistics.</p>
<h6>Response Example:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>[
{
"id": 1,
"name": "claude-tracker",
"path": "/home/user/claude-tracker",
"total_sessions": 5,
"total_time_minutes": 240,
"files_modified_count": 15,
"lines_changed_count": 500,
"last_session": "2024-01-15T10:30:00",
"created_at": "2024-01-10T09:00:00"
}
]</code></pre>
</div>
</div>
<!-- Get Project Details -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/projects/{project_id}</code>
</div>
<p class="text-muted">Get detailed information about a specific project.</p>
<h6>Parameters:</h6>
<ul>
<li><code>project_id</code> (integer, required): Project ID</li>
</ul>
</div>
<!-- Project Statistics -->
<div>
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/projects/{project_id}/stats</code>
</div>
<p class="text-muted">Get comprehensive statistics for a project including time series data.</p>
</div>
</div>
</div>
<!-- Sessions API -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="fas fa-play-circle me-2"></i>Sessions API</h3>
</div>
<div class="card-body">
<!-- Start Session -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
<code>/api/sessions/start</code>
</div>
<p class="text-muted">Start a new coding session.</p>
<h6>Request Body:</h6>
<div class="bg-dark text-light p-3 rounded mb-3">
<pre><code>{
"project_path": "/path/to/project",
"start_time": "2024-01-15T10:30:00",
"user": "username"
}</code></pre>
</div>
<h6>Response:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"session_id": "12345",
"project_id": 1,
"message": "Session started successfully"
}</code></pre>
</div>
</div>
<!-- End Session -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
<code>/api/sessions/end</code>
</div>
<p class="text-muted">End an active coding session.</p>
<h6>Request Body:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"session_id": "12345",
"end_time": "2024-01-15T12:30:00"
}</code></pre>
</div>
</div>
<!-- Get Sessions -->
<div>
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/sessions</code>
</div>
<p class="text-muted">Retrieve sessions with optional filtering.</p>
<h6>Query Parameters:</h6>
<ul>
<li><code>project_id</code> (integer, optional): Filter by project</li>
<li><code>start_date</code> (date, optional): Sessions after this date</li>
<li><code>end_date</code> (date, optional): Sessions before this date</li>
<li><code>limit</code> (integer, optional): Maximum number of results</li>
</ul>
</div>
</div>
</div>
<!-- Conversations API -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-info text-white">
<h3 class="mb-0"><i class="fas fa-comments me-2"></i>Conversations API</h3>
</div>
<div class="card-body">
<!-- Create Conversation -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
<code>/api/conversations</code>
</div>
<p class="text-muted">Record a new conversation message.</p>
<h6>Request Body:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"session_id": "12345",
"content": "User message or assistant response",
"role": "user|assistant",
"timestamp": "2024-01-15T10:35:00"
}</code></pre>
</div>
</div>
<!-- Search Conversations -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/conversations/search</code>
</div>
<p class="text-muted">Search conversations by content, project, or date.</p>
<h6>Query Parameters:</h6>
<ul>
<li><code>q</code> (string): Search query for content</li>
<li><code>project_id</code> (integer): Filter by project</li>
<li><code>start_date</code> (date): Messages after this date</li>
<li><code>end_date</code> (date): Messages before this date</li>
<li><code>role</code> (string): Filter by role (user/assistant)</li>
<li><code>limit</code> (integer): Maximum results (default: 50)</li>
</ul>
</div>
<!-- Get Conversations -->
<div>
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/conversations</code>
</div>
<p class="text-muted">Retrieve conversations with pagination.</p>
</div>
</div>
</div>
<!-- Activities API -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-warning text-dark">
<h3 class="mb-0"><i class="fas fa-file-edit me-2"></i>Activities API</h3>
</div>
<div class="card-body">
<!-- Record Activity -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
<code>/api/activities</code>
</div>
<p class="text-muted">Record a file modification activity.</p>
<h6>Request Body:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"session_id": "12345",
"file_path": "/path/to/modified/file.py",
"action": "created|modified|deleted",
"lines_added": 10,
"lines_removed": 5,
"timestamp": "2024-01-15T10:40:00"
}</code></pre>
</div>
</div>
<!-- Get Activities -->
<div>
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/activities</code>
</div>
<p class="text-muted">Retrieve file modification activities.</p>
<h6>Query Parameters:</h6>
<ul>
<li><code>session_id</code> (string): Filter by session</li>
<li><code>project_id</code> (integer): Filter by project</li>
<li><code>file_path</code> (string): Filter by file path</li>
<li><code>action</code> (string): Filter by action type</li>
</ul>
</div>
</div>
</div>
<!-- Tool Calls API -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-dark text-white">
<h3 class="mb-0"><i class="fas fa-tools me-2"></i>Tool Calls API</h3>
</div>
<div class="card-body">
<!-- Record Tool Call -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
<code>/api/tool-calls</code>
</div>
<p class="text-muted">Record a tool call made during a Claude Code session.</p>
<h6>Request Body:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"tool_name": "Read",
"parameters": {
"file_path": "/path/to/file.py",
"limit": 100
},
"result_status": "success",
"execution_time_ms": 250,
"timestamp": "2024-01-15T10:45:00"
}</code></pre>
</div>
</div>
<!-- Get Tool Usage Stats -->
<div class="border-bottom pb-4 mb-4">
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/tool-calls/stats</code>
</div>
<p class="text-muted">Get tool usage statistics with optional filtering.</p>
<h6>Query Parameters:</h6>
<ul>
<li><code>project_id</code> (integer, optional): Filter by project</li>
<li><code>start_date</code> (date, optional): Tools used after this date</li>
<li><code>end_date</code> (date, optional): Tools used before this date</li>
</ul>
</div>
<!-- Get Session Tool Calls -->
<div>
<div class="d-flex align-items-center mb-3">
<span class="badge bg-success me-2 px-3">GET</span>
<code>/api/tool-calls/session/{session_id}</code>
</div>
<p class="text-muted">Get all tool calls for a specific session.</p>
</div>
</div>
</div>
<!-- Data Import API -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-secondary text-white">
<h3 class="mb-0"><i class="fas fa-upload me-2"></i>Data Import API</h3>
</div>
<div class="card-body">
<!-- Import Claude Data -->
<div>
<div class="d-flex align-items-center mb-3">
<span class="badge bg-warning text-dark me-2 px-3">POST</span>
<code>/api/import/claude-data</code>
</div>
<p class="text-muted">Import data from a Claude.json file.</p>
<h6>Request:</h6>
<p>Multipart form data with file upload:</p>
<div class="bg-dark text-light p-3 rounded">
<pre><code>Content-Type: multipart/form-data
file: [claude.json file]</code></pre>
</div>
<h6>Response:</h6>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"success": true,
"projects_imported": 15,
"sessions_imported": 42,
"conversations_imported": 158,
"activities_imported": 89,
"errors": []
}</code></pre>
</div>
</div>
</div>
</div>
<!-- Error Responses -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-exclamation-triangle me-2 text-danger"></i>Error Responses</h3>
<p class="text-muted mb-4">The API uses standard HTTP status codes and returns error details in JSON format.</p>
<h5>Common Status Codes:</h5>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Code</th>
<th>Meaning</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="badge bg-success">200</span></td>
<td>OK</td>
<td>Request successful</td>
</tr>
<tr>
<td><span class="badge bg-info">201</span></td>
<td>Created</td>
<td>Resource created successfully</td>
</tr>
<tr>
<td><span class="badge bg-warning text-dark">400</span></td>
<td>Bad Request</td>
<td>Invalid request data</td>
</tr>
<tr>
<td><span class="badge bg-danger">404</span></td>
<td>Not Found</td>
<td>Resource not found</td>
</tr>
<tr>
<td><span class="badge bg-danger">500</span></td>
<td>Server Error</td>
<td>Internal server error</td>
</tr>
</tbody>
</table>
</div>
<h5>Error Response Format:</h5>
<div class="bg-dark text-light p-3 rounded">
<pre><code>{
"error": true,
"message": "Detailed error description",
"code": "ERROR_CODE",
"details": {
"field": "Additional error context"
}
}</code></pre>
</div>
</div>
</div>
<!-- Rate Limiting -->
<div class="card border-0 shadow-sm">
<div class="card-body">
<h3><i class="fas fa-tachometer-alt me-2 text-info"></i>Rate Limiting</h3>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Currently, no rate limiting is implemented. However, it's recommended to avoid making excessive requests to maintain optimal performance.
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 2rem;">
<!-- Quick Links -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-external-link-alt me-2"></i>Quick Links</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="/docs" target="_blank" class="btn btn-primary">
<i class="fas fa-code me-2"></i>
Interactive API Docs
</a>
<a href="http://localhost:8000/api/projects" target="_blank" class="btn btn-outline-success">
<i class="fas fa-folder-open me-2"></i>
Test Projects API
</a>
</div>
</div>
</div>
<!-- Table of Contents -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="fas fa-list me-2"></i>API Endpoints</h6>
</div>
<div class="card-body">
<nav class="nav nav-pills flex-column">
<a class="nav-link py-1 px-2 text-sm" href="#projects">Projects API</a>
<a class="nav-link py-1 px-2 text-sm" href="#sessions">Sessions API</a>
<a class="nav-link py-1 px-2 text-sm" href="#conversations">Conversations API</a>
<a class="nav-link py-1 px-2 text-sm" href="#activities">Activities API</a>
<a class="nav-link py-1 px-2 text-sm" href="#tool-calls">Tool Calls API</a>
<a class="nav-link py-1 px-2 text-sm" href="#import">Data Import API</a>
<a class="nav-link py-1 px-2 text-sm" href="#errors">Error Responses</a>
</nav>
</div>
</div>
<!-- Code Examples -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h6 class="mb-0"><i class="fas fa-code me-2"></i>Code Examples</h6>
</div>
<div class="card-body">
<h6>JavaScript (Fetch)</h6>
<div class="bg-dark text-light p-2 rounded mb-3">
<code class="small">
fetch('/api/projects')<br>
&nbsp;&nbsp;.then(r => r.json())<br>
&nbsp;&nbsp;.then(data => console.log(data))
</code>
</div>
<h6>Python (Requests)</h6>
<div class="bg-dark text-light p-2 rounded mb-3">
<code class="small">
import requests<br>
r = requests.get('http://localhost:8000/api/projects')<br>
print(r.json())
</code>
</div>
<h6>cURL</h6>
<div class="bg-dark text-light p-2 rounded">
<code class="small">
curl -X GET http://localhost:8000/api/projects
</code>
</div>
</div>
</div>
<!-- SDK Information -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-info text-white">
<h6 class="mb-0"><i class="fas fa-puzzle-piece me-2"></i>SDK & Libraries</h6>
</div>
<div class="card-body">
<p class="small text-muted mb-3">Currently, there are no official SDKs. Use standard HTTP libraries for your preferred language.</p>
<h6>Recommended Libraries:</h6>
<ul class="list-unstyled small">
<li class="mb-1"><strong>JavaScript:</strong> fetch, axios</li>
<li class="mb-1"><strong>Python:</strong> requests, httpx</li>
<li class="mb-1"><strong>Go:</strong> net/http</li>
<li class="mb-1"><strong>Java:</strong> OkHttp, HttpClient</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,562 @@
{% extends "docs/base.html" %}
{% block title %}Data Import Guide - Claude Code Project Tracker{% endblock %}
{% block doc_content %}
<div class="row">
<div class="col-lg-8">
<h1 class="mb-4">
<i class="fas fa-cloud-upload-alt text-primary me-2"></i>
Data Import Guide
</h1>
<p class="lead">
Learn how to import your Claude Code usage history using the <code>~/.claude.json</code> file.
</p>
<!-- Overview -->
<section class="mb-5">
<h2><i class="fas fa-info-circle me-2"></i>What is .claude.json?</h2>
<p>
The <code>~/.claude.json</code> file is automatically created by Claude Code and contains your usage history,
including project paths, conversation summaries, and usage statistics.
</p>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">Typical Location</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-4">
<strong>macOS:</strong><br>
<code>~/.claude.json</code>
</div>
<div class="col-md-4">
<strong>Linux:</strong><br>
<code>~/.claude.json</code>
</div>
<div class="col-md-4">
<strong>Windows:</strong><br>
<code>%USERPROFILE%\.claude.json</code>
</div>
</div>
</div>
</div>
</section>
<!-- Import Methods -->
<section class="mb-5">
<h2><i class="fas fa-upload me-2"></i>Import Methods</h2>
<p>There are two ways to import your Claude Code data:</p>
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-cloud-upload-alt me-1"></i>File Upload</h5>
</div>
<div class="card-body">
<p><strong>Recommended method</strong></p>
<ul>
<li>Upload file directly through web interface</li>
<li>Works with any .claude.json file</li>
<li>Preview before importing</li>
<li>No server file system access needed</li>
</ul>
<p class="text-success mb-0"><i class="fas fa-check me-1"></i>Best for most users</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-secondary text-white">
<h5 class="mb-0"><i class="fas fa-folder-open me-1"></i>File Path</h5>
</div>
<div class="card-body">
<p><strong>Server-side import</strong></p>
<ul>
<li>Specify path to .claude.json file</li>
<li>Uses default ~/.claude.json if no path given</li>
<li>Requires file system access</li>
<li>Good for server deployments</li>
</ul>
<p class="text-info mb-0"><i class="fas fa-info me-1"></i>For advanced users</p>
</div>
</div>
</div>
</div>
</section>
<!-- File Upload Guide -->
<section class="mb-5">
<h2><i class="fas fa-step-forward me-2"></i>File Upload Step-by-Step</h2>
<div class="accordion" id="uploadStepsAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="step1">
<button class="accordion-button" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStep1">
Step 1: Locate your .claude.json file
</button>
</h2>
<div id="collapseStep1" class="accordion-collapse collapse show" data-bs-parent="#uploadStepsAccordion">
<div class="accordion-body">
<p>Find the .claude.json file on your system:</p>
<div class="row">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<strong>macOS/Linux</strong>
</div>
<div class="card-body">
<pre><code class="language-bash"># Open terminal and check if file exists
ls -la ~/.claude.json
# Or use finder/file manager
open ~/.claude.json</code></pre>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<strong>Windows</strong>
</div>
<div class="card-body">
<pre><code class="language-cmd"># Open Command Prompt
dir %USERPROFILE%\.claude.json
# Or use File Explorer
explorer %USERPROFILE%</code></pre>
</div>
</div>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-lightbulb me-1"></i>
<strong>Tip:</strong> If the file doesn't exist, use Claude Code for a few sessions to generate usage data first.
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="step2">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStep2">
Step 2: Access the Import Page
</button>
</h2>
<div id="collapseStep2" class="accordion-collapse collapse" data-bs-parent="#uploadStepsAccordion">
<div class="accordion-body">
<p>Navigate to the data import page:</p>
<ol>
<li>Go to your Claude Code Tracker dashboard</li>
<li>Click <strong>"Import Data"</strong> in the navigation menu</li>
<li>Ensure the <strong>"Upload File"</strong> tab is selected (default)</li>
</ol>
<div class="alert alert-success">
<i class="fas fa-link me-1"></i>
<strong>Direct Link:</strong> <a href="/dashboard/import" class="alert-link">Go to Import Page</a>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="step3">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStep3">
Step 3: Select and Upload File
</button>
</h2>
<div id="collapseStep3" class="accordion-collapse collapse" data-bs-parent="#uploadStepsAccordion">
<div class="accordion-body">
<p>Upload your .claude.json file:</p>
<ol>
<li>Click <strong>"Select .claude.json file"</strong> button</li>
<li>Navigate to and select your .claude.json file</li>
<li>File information will be displayed (name, size, modified date)</li>
<li>The upload buttons will become enabled</li>
</ol>
<div class="card mt-3">
<div class="card-header">
<strong>File Validation</strong>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Must be a .json file</li>
<li>Maximum size: 10 MB</li>
<li>Must be UTF-8 encoded</li>
<li>Must contain valid JSON</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="step4">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStep4">
Step 4: Preview Import (Recommended)
</button>
</h2>
<div id="collapseStep4" class="accordion-collapse collapse" data-bs-parent="#uploadStepsAccordion">
<div class="accordion-body">
<p>Before importing, preview what data will be imported:</p>
<ol>
<li>Click <strong>"Preview Upload"</strong> button</li>
<li>Review the import summary showing:
<ul>
<li>Number of projects to be imported</li>
<li>Total history entries</li>
<li>Claude usage statistics</li>
<li>Sample project paths</li>
</ul>
</li>
<li>Verify the data looks correct</li>
</ol>
<div class="alert alert-warning">
<i class="fas fa-eye me-1"></i>
<strong>Preview Benefits:</strong> Catch potential issues before importing and verify data integrity.
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="step5">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseStep5">
Step 5: Import Data
</button>
</h2>
<div id="collapseStep5" class="accordion-collapse collapse" data-bs-parent="#uploadStepsAccordion">
<div class="accordion-body">
<p>Complete the import process:</p>
<ol>
<li>Click <strong>"Import Upload"</strong> button (or "Looks good - Import this data" after preview)</li>
<li>Confirm the import when prompted</li>
<li>Wait for the import to complete</li>
<li>Review the success summary showing imported counts</li>
<li>Use the provided links to view your data</li>
</ol>
<div class="card mt-3">
<div class="card-header">
<strong>Import Results</strong>
</div>
<div class="card-body">
<p>After successful import, you'll see:</p>
<ul class="mb-0">
<li>Number of projects imported</li>
<li>Number of estimated sessions created</li>
<li>Number of conversations imported</li>
<li>Any warnings or errors encountered</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- What Gets Imported -->
<section class="mb-5">
<h2><i class="fas fa-database me-2"></i>What Gets Imported</h2>
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-folder me-1"></i>Projects</h5>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Project directory paths</li>
<li>Automatically detected programming languages</li>
<li>Project names (derived from directory names)</li>
<li>Creation timestamps (estimated)</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="fas fa-chart-line me-1"></i>Usage Statistics</h5>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Total Claude Code startup count</li>
<li>First usage timestamp</li>
<li>Prompt queue usage statistics</li>
<li>Estimated session distribution over time</li>
</ul>
</div>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-info text-white">
<h5 class="mb-0"><i class="fas fa-comments me-1"></i>Conversations</h5>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Conversation topic summaries from project history</li>
<li>Estimated timestamps</li>
<li>Association with imported projects</li>
<li>Import metadata for tracking</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header bg-warning text-dark">
<h5 class="mb-0"><i class="fas fa-clock me-1"></i>Sessions</h5>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Estimated sessions based on startup count</li>
<li>Distributed over your usage period</li>
<li>Random but realistic session durations</li>
<li>Associated activity and conversation counts</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!-- Data Privacy & Security -->
<section class="mb-5">
<h2><i class="fas fa-shield-alt me-2"></i>Data Privacy & Security</h2>
<div class="alert alert-success">
<h5><i class="fas fa-lock me-1"></i>Your Data Stays Local</h5>
<ul class="mb-0">
<li>All data is processed and stored locally on your server</li>
<li>No external services are contacted during import</li>
<li>Upload is handled securely over HTTPS</li>
<li>Files are processed in memory and not stored on disk</li>
<li>Only the structured data is saved to your database</li>
</ul>
</div>
<div class="row mt-3">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<strong>File Security</strong>
</div>
<div class="card-body">
<ul class="mb-0">
<li>10 MB upload size limit</li>
<li>JSON format validation</li>
<li>UTF-8 encoding requirement</li>
<li>No executable code processing</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<strong>Data Handling</strong>
</div>
<div class="card-body">
<ul class="mb-0">
<li>Files processed in memory only</li>
<li>No temporary file creation</li>
<li>Immediate cleanup after processing</li>
<li>Error handling for malformed data</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!-- Troubleshooting -->
<section class="mb-5">
<h2><i class="fas fa-tools me-2"></i>Troubleshooting</h2>
<div class="accordion" id="troubleshootingAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="troubleFile">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTroubleFile">
"File must be a JSON file" error
</button>
</h2>
<div id="collapseTroubleFile" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Cause:</strong> The selected file doesn't have a .json extension.</p>
<p><strong>Solution:</strong></p>
<ul>
<li>Ensure you're selecting the correct .claude.json file</li>
<li>Check the file extension is exactly ".json"</li>
<li>On some systems, file extensions might be hidden</li>
<li>Try copying the file and renaming it to ensure .json extension</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="troubleSize">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTroubleSize">
"File too large" error
</button>
</h2>
<div id="collapseTroubleSize" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Cause:</strong> The .claude.json file is larger than 10 MB.</p>
<p><strong>Solutions:</strong></p>
<ul>
<li>Use the "File Path" import method instead</li>
<li>Contact your administrator to increase upload limits</li>
<li>Try importing a newer, smaller version of the file</li>
<li>Clean up the .claude.json file by backing up old data</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="troubleJson">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTroubleJson">
"Invalid JSON" error
</button>
</h2>
<div id="collapseTroubleJson" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Cause:</strong> The file contains malformed JSON data.</p>
<p><strong>Solutions:</strong></p>
<ul>
<li>Check if the file was corrupted during transfer</li>
<li>Try re-generating the file by using Claude Code again</li>
<li>Use a JSON validator to check the file structure</li>
<li>Ensure the file wasn't partially downloaded</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="troubleImport">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTroubleImport">
Import completed but no data appears
</button>
</h2>
<div id="collapseTroubleImport" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Possible causes and solutions:</strong></p>
<ul>
<li><strong>Empty file:</strong> The .claude.json file might be empty or contain no project data</li>
<li><strong>Duplicate import:</strong> Data might already exist and wasn't imported again (this is normal)</li>
<li><strong>Filtering:</strong> Check if any filters are applied in the project view</li>
<li><strong>Refresh:</strong> Try refreshing the page or clicking the "Refresh" button</li>
<li><strong>Check import results:</strong> Review the import summary for any warnings or errors</li>
</ul>
</div>
</div>
</div>
</div>
</section>
<!-- Advanced Topics -->
<section class="mb-5">
<h2><i class="fas fa-cogs me-2"></i>Advanced Topics</h2>
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Multiple Imports</h5>
</div>
<div class="card-body">
<p>The import system prevents duplicates:</p>
<ul>
<li>Projects are identified by path</li>
<li>Duplicate projects won't be re-created</li>
<li>Safe to import multiple times</li>
<li>New data will be added to existing projects</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Data Estimation</h5>
</div>
<div class="card-body">
<p>Since .claude.json has limited data:</p>
<ul>
<li>Session times are estimated</li>
<li>Durations are randomized realistically</li>
<li>Activity counts are approximated</li>
<li>Language detection uses file system</li>
</ul>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card sticky-top" style="top: 1rem;">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-rocket me-1"></i>Quick Start</h5>
</div>
<div class="card-body">
<ol class="mb-3">
<li>Find your <code>~/.claude.json</code> file</li>
<li><a href="/dashboard/import">Go to Import Page</a></li>
<li>Click "Select .claude.json file"</li>
<li>Choose your file</li>
<li>Click "Preview Upload"</li>
<li>Click "Import Upload"</li>
</ol>
<div class="alert alert-success">
<i class="fas fa-lightbulb me-1"></i>
<strong>Pro Tip:</strong> Always preview before importing to catch any issues early.
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-question-circle me-1"></i>Need Help?</h5>
</div>
<div class="card-body">
<p>Common import questions:</p>
<ul class="mb-0">
<li><a href="#troubleshootingAccordion">Troubleshooting Guide</a></li>
<li><a href="/dashboard/docs/faq">FAQ Page</a></li>
<li><a href="/dashboard/docs/api-reference">API Documentation</a></li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,415 @@
{% extends "docs/base.html" %}
{% block title %}Docker Deployment - Claude Code Project Tracker{% endblock %}
{% block doc_content %}
<div class="row">
<div class="col-lg-8">
<h1 class="mb-4">
<i class="fab fa-docker text-primary me-2"></i>
Docker Deployment Guide
</h1>
<p class="lead">
Deploy Claude Code Project Tracker using Docker with Caddy reverse proxy for production environments.
</p>
<!-- Prerequisites -->
<section class="mb-5">
<h2><i class="fas fa-check-circle me-2"></i>Prerequisites</h2>
<div class="alert alert-info">
<p class="mb-2"><strong>Required:</strong></p>
<ul class="mb-0">
<li>Docker and Docker Compose installed</li>
<li>Caddy with docker-proxy plugin</li>
<li>External Caddy network configured</li>
<li>Domain name (or local development domain)</li>
</ul>
</div>
</section>
<!-- Quick Start -->
<section class="mb-5">
<h2><i class="fas fa-rocket me-2"></i>Quick Start</h2>
<p>Follow these steps to get Claude Code Tracker running with Docker:</p>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">1. Clone and Configure</h5>
</div>
<div class="card-body">
<pre><code class="language-bash"># Clone the repository
git clone &lt;repository-url&gt; claude-tracker
cd claude-tracker
# Copy environment template
cp .env.example .env</code></pre>
</div>
</div>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">2. Configure Environment</h5>
</div>
<div class="card-body">
<p>Edit the <code>.env</code> file with your settings:</p>
<pre><code class="language-bash"># Domain configuration
DOMAIN=claude.l.supported.systems
# Database settings
DATABASE_URL=sqlite+aiosqlite:////app/data/tracker.db
# Application settings
DEBUG=false
PYTHONPATH=/app</code></pre>
</div>
</div>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">3. Deploy with Docker Compose</h5>
</div>
<div class="card-body">
<pre><code class="language-bash"># Build and start the container
docker-compose up -d
# View logs
docker-compose logs -f
# Check status
docker-compose ps</code></pre>
</div>
</div>
</section>
<!-- Docker Configuration -->
<section class="mb-5">
<h2><i class="fas fa-cog me-2"></i>Docker Configuration</h2>
<h4 class="mt-4">Dockerfile</h4>
<p>The application uses a multi-stage Docker build with Python 3.12 and uv package manager:</p>
<div class="card mb-3">
<div class="card-body">
<pre><code class="language-dockerfile"># Multi-stage build with Python 3.12
FROM python:3.12-slim
# Install uv package manager
COPY --from=ghcr.io/astral-sh/uv:latest /uv /bin/uv
# Application setup
WORKDIR /app
COPY . .
# Install dependencies and create virtual environment
RUN uv venv && uv pip install -r requirements.txt
# Database initialization script
COPY init_db.py ./
# Startup script with database initialization
CMD ["./start.sh"]</code></pre>
</div>
</div>
<h4 class="mt-4">docker-compose.yml</h4>
<p>The Docker Compose configuration includes Caddy integration:</p>
<div class="card mb-3">
<div class="card-body">
<pre><code class="language-yaml">services:
claude-tracker:
build:
context: .
dockerfile: Dockerfile
container_name: claude-tracker-api
restart: unless-stopped
environment:
- DATABASE_URL=sqlite+aiosqlite:////app/data/tracker.db
- DEBUG=true
- PYTHONPATH=/app
volumes:
- ./data:/app/data
- ./app/dashboard/static:/app/app/dashboard/static:ro
networks:
- caddy
labels:
# Caddy reverse proxy configuration
caddy: ${DOMAIN:-claude.l.supported.systems}
caddy.reverse_proxy: "{{upstreams 8000}}"
expose:
- 8000
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
caddy:
external: true
name: caddy</code></pre>
</div>
</div>
</section>
<!-- Caddy Integration -->
<section class="mb-5">
<h2><i class="fas fa-globe me-2"></i>Caddy Integration</h2>
<p>The application integrates with Caddy using docker-proxy labels for automatic SSL and reverse proxy configuration.</p>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">Caddy Labels Explained</h5>
</div>
<div class="card-body">
<ul>
<li><code>caddy: ${DOMAIN}</code> - Sets the domain for the service</li>
<li><code>caddy.reverse_proxy: "{{upstreams 8000}}"</code> - Proxies to port 8000</li>
<li>Automatic SSL certificate generation</li>
<li>Health check integration</li>
<li>Load balancing support</li>
</ul>
</div>
</div>
</section>
<!-- Database & Persistence -->
<section class="mb-5">
<h2><i class="fas fa-database me-2"></i>Database & Persistence</h2>
<p>The application uses SQLite with proper volume mounting for data persistence:</p>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">Volume Configuration</h5>
</div>
<div class="card-body">
<ul>
<li><code>./data:/app/data</code> - Database and file storage</li>
<li><code>./app/dashboard/static:/app/app/dashboard/static:ro</code> - Static assets (read-only)</li>
</ul>
<div class="alert alert-warning mt-3">
<i class="fas fa-exclamation-triangle me-1"></i>
<strong>Important:</strong> Ensure the <code>./data</code> directory exists and has proper permissions before starting the container.
</div>
</div>
</div>
</section>
<!-- Management Commands -->
<section class="mb-5">
<h2><i class="fas fa-terminal me-2"></i>Management Commands</h2>
<div class="row">
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Container Management</h5>
</div>
<div class="card-body">
<pre><code class="language-bash"># Start services
docker-compose up -d
# Stop services
docker-compose down
# Restart services
docker-compose restart
# View logs
docker-compose logs -f
# Check status
docker-compose ps</code></pre>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card h-100">
<div class="card-header">
<h5 class="mb-0">Database Management</h5>
</div>
<div class="card-body">
<pre><code class="language-bash"># Execute shell in container
docker-compose exec claude-tracker sh
# Initialize database manually
docker-compose exec claude-tracker python init_db.py
# Backup database
cp ./data/tracker.db ./data/tracker.db.backup
# View database size
du -h ./data/tracker.db</code></pre>
</div>
</div>
</div>
</div>
</section>
<!-- Monitoring & Health -->
<section class="mb-5">
<h2><i class="fas fa-heartbeat me-2"></i>Monitoring & Health</h2>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">Health Checks</h5>
</div>
<div class="card-body">
<p>The container includes built-in health checks:</p>
<ul>
<li><strong>Endpoint:</strong> <code>http://localhost:8000/health</code></li>
<li><strong>Interval:</strong> 30 seconds</li>
<li><strong>Timeout:</strong> 10 seconds</li>
<li><strong>Retries:</strong> 3 attempts</li>
<li><strong>Start Period:</strong> 30 seconds</li>
</ul>
<pre><code class="language-bash"># Check health status
docker-compose ps
curl https://your-domain.com/health</code></pre>
</div>
</div>
<div class="card mb-3">
<div class="card-header bg-light">
<h5 class="mb-0">Resource Limits</h5>
</div>
<div class="card-body">
<p>Configured resource limits:</p>
<ul>
<li><strong>CPU Limit:</strong> 1.0 core</li>
<li><strong>Memory Limit:</strong> 512MB</li>
<li><strong>CPU Reservation:</strong> 0.25 core</li>
<li><strong>Memory Reservation:</strong> 128MB</li>
</ul>
</div>
</div>
</section>
<!-- Troubleshooting -->
<section class="mb-5">
<h2><i class="fas fa-tools me-2"></i>Troubleshooting</h2>
<div class="accordion" id="troubleshootingAccordion">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseOne">
Container keeps restarting
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<pre><code class="language-bash"># Check logs for errors
docker-compose logs claude-tracker
# Check database permissions
ls -la ./data/
# Verify database initialization
docker-compose exec claude-tracker python init_db.py</code></pre>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTwo">
502 Bad Gateway errors
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<pre><code class="language-bash"># Check if container is running
docker-compose ps
# Verify health check
docker-compose exec claude-tracker curl localhost:8000/health
# Check Caddy network connectivity
docker network ls | grep caddy</code></pre>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapseThree">
Database connection issues
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<pre><code class="language-bash"># Create data directory if missing
mkdir -p ./data
chmod 755 ./data
# Remove corrupted database
rm ./data/tracker.db
# Restart container to reinitialize
docker-compose restart</code></pre>
</div>
</div>
</div>
</div>
</section>
<!-- Security Considerations -->
<section class="mb-5">
<h2><i class="fas fa-shield-alt me-2"></i>Security Considerations</h2>
<div class="alert alert-warning">
<h5><i class="fas fa-exclamation-triangle me-1"></i>Production Security</h5>
<ul class="mb-0">
<li>Set <code>DEBUG=false</code> in production</li>
<li>Use strong domain names and SSL certificates</li>
<li>Regularly backup the database</li>
<li>Monitor container logs for suspicious activity</li>
<li>Keep Docker images updated</li>
<li>Limit container resource usage</li>
</ul>
</div>
</section>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="card sticky-top" style="top: 1rem;">
<div class="card-header">
<h5 class="mb-0"><i class="fas fa-list me-1"></i>Quick Reference</h5>
</div>
<div class="card-body">
<h6>Essential Commands</h6>
<div class="code-block mb-3">
<pre><code># Deploy
docker-compose up -d
# Logs
docker-compose logs -f
# Stop
docker-compose down</code></pre>
</div>
<h6>Key Files</h6>
<ul class="list-unstyled">
<li><i class="fas fa-file me-1"></i><code>.env</code></li>
<li><i class="fas fa-file me-1"></i><code>docker-compose.yml</code></li>
<li><i class="fas fa-file me-1"></i><code>Dockerfile</code></li>
<li><i class="fas fa-folder me-1"></i><code>./data/</code></li>
</ul>
<h6>Default Ports</h6>
<ul class="list-unstyled">
<li><i class="fas fa-network-wired me-1"></i>8000 (Internal)</li>
<li><i class="fas fa-globe me-1"></i>80/443 (Caddy)</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,784 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
<li class="breadcrumb-item active" aria-current="page">FAQ</li>
</ol>
</nav>
<h2><i class="fas fa-question-circle me-3"></i>Frequently Asked Questions</h2>
<p class="lead text-muted">Common questions and troubleshooting guide</p>
</div>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Search FAQ -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<div class="input-group">
<span class="input-group-text">
<i class="fas fa-search"></i>
</span>
<input type="text" class="form-control" placeholder="Search FAQ..." id="faqSearch">
</div>
</div>
</div>
<!-- General Questions -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h3 class="mb-0"><i class="fas fa-info-circle me-2"></i>General Questions</h3>
</div>
<div class="card-body">
<div class="accordion" id="generalAccordion">
<!-- Q1 -->
<div class="accordion-item border-0 faq-item" data-keywords="what is claude code tracker purpose features">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#general1">
<i class="fas fa-question me-2 text-primary"></i>
What is Claude Code Tracker and what does it do?
</button>
</h2>
<div id="general1" class="accordion-collapse collapse" data-bs-parent="#generalAccordion">
<div class="accordion-body">
<p>Claude Code Tracker is a development intelligence system that helps you understand and analyze your coding activity when using Claude Code. It provides:</p>
<ul>
<li><strong>Session Tracking:</strong> Monitor your development sessions and time spent</li>
<li><strong>Conversation History:</strong> Search and browse through all your Claude conversations</li>
<li><strong>Project Analytics:</strong> Get insights into file modifications, lines changed, and productivity patterns</li>
<li><strong>Timeline Visualization:</strong> See your development progress over time</li>
<li><strong>Data Privacy:</strong> All data stays local on your machine</li>
</ul>
</div>
</div>
</div>
<!-- Q2 -->
<div class="accordion-item border-0 faq-item" data-keywords="data privacy local cloud storage security">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#general2">
<i class="fas fa-shield-alt me-2 text-success"></i>
Is my data secure? Where is it stored?
</button>
</h2>
<div id="general2" class="accordion-collapse collapse" data-bs-parent="#generalAccordion">
<div class="accordion-body">
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
<strong>Yes, your data is completely secure!</strong>
</div>
<p>Claude Code Tracker prioritizes your privacy:</p>
<ul>
<li><strong>Local Storage:</strong> All data is stored locally in an SQLite database on your machine</li>
<li><strong>No Cloud Services:</strong> No data is sent to external servers or cloud services</li>
<li><strong>No Network Dependencies:</strong> Works completely offline after initial setup</li>
<li><strong>Your Control:</strong> You have full control over your data and can delete it anytime</li>
</ul>
<p class="mb-0"><strong>Database Location:</strong> <code>./data/tracker.db</code> in your installation directory</p>
</div>
</div>
</div>
<!-- Q3 -->
<div class="accordion-item border-0 faq-item" data-keywords="requirements system python version dependencies">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#general3">
<i class="fas fa-server me-2 text-info"></i>
What are the system requirements?
</button>
</h2>
<div id="general3" class="accordion-collapse collapse" data-bs-parent="#generalAccordion">
<div class="accordion-body">
<div class="row">
<div class="col-md-6">
<h6>Minimum Requirements:</h6>
<ul>
<li>Python 3.8 or higher</li>
<li>2GB RAM</li>
<li>100MB free disk space</li>
<li>Modern web browser</li>
</ul>
</div>
<div class="col-md-6">
<h6>Supported Platforms:</h6>
<ul>
<li>Windows 10/11</li>
<li>macOS 10.15+</li>
<li>Linux (Ubuntu, CentOS, etc.)</li>
</ul>
</div>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle me-2"></i>
Claude Code CLI must be installed and configured separately.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Setup & Installation -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="fas fa-download me-2"></i>Setup & Installation</h3>
</div>
<div class="card-body">
<div class="accordion" id="setupAccordion">
<!-- Q4 -->
<div class="accordion-item border-0 faq-item" data-keywords="installation setup install getting started first time">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#setup1">
<i class="fas fa-play-circle me-2 text-success"></i>
How do I install and set up Claude Code Tracker?
</button>
</h2>
<div id="setup1" class="accordion-collapse collapse" data-bs-parent="#setupAccordion">
<div class="accordion-body">
<ol>
<li class="mb-2"><strong>Download:</strong> Clone or download the repository</li>
<li class="mb-2"><strong>Install Dependencies:</strong> Run <code>pip install -r requirements.txt</code></li>
<li class="mb-2"><strong>Start Server:</strong> Run <code>python main.py</code></li>
<li class="mb-2"><strong>Access Dashboard:</strong> Open <code>http://localhost:8000/dashboard</code></li>
<li class="mb-2"><strong>Import Data:</strong> Use the <a href="/dashboard/import">Import Data</a> page to load your existing Claude data</li>
</ol>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
See the <a href="/dashboard/docs/getting-started">Getting Started Guide</a> for detailed instructions.
</div>
</div>
</div>
</div>
<!-- Q5 -->
<div class="accordion-item border-0 faq-item" data-keywords="claude json file location find import data">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#setup2">
<i class="fas fa-file-code me-2 text-warning"></i>
Where can I find my claude.json file?
</button>
</h2>
<div id="setup2" class="accordion-collapse collapse" data-bs-parent="#setupAccordion">
<div class="accordion-body">
<p>The claude.json file contains your Claude Code conversation history. Location varies by operating system:</p>
<div class="row">
<div class="col-md-4 mb-3">
<div class="card border-0 bg-light">
<div class="card-body p-3 text-center">
<i class="fab fa-apple fa-2x text-secondary mb-2"></i>
<h6>macOS</h6>
<code class="small">~/.claude.json</code>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card border-0 bg-light">
<div class="card-body p-3 text-center">
<i class="fab fa-linux fa-2x text-warning mb-2"></i>
<h6>Linux</h6>
<code class="small">~/.claude.json</code>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card border-0 bg-light">
<div class="card-body p-3 text-center">
<i class="fab fa-windows fa-2x text-primary mb-2"></i>
<h6>Windows</h6>
<code class="small">%USERPROFILE%\.claude.json</code>
</div>
</div>
</div>
</div>
<div class="bg-dark text-light p-3 rounded mt-3">
<h6 class="text-light">Quick commands to locate the file:</h6>
<p class="mb-2"><strong>macOS/Linux:</strong> <code>ls -la ~/.claude.json</code></p>
<p class="mb-0"><strong>Windows:</strong> <code>dir %USERPROFILE%\.claude.json</code></p>
</div>
</div>
</div>
</div>
<!-- Q6 -->
<div class="accordion-item border-0 faq-item" data-keywords="hooks setup configuration not working automatic tracking">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#setup3">
<i class="fas fa-link me-2 text-info"></i>
How do I set up hooks for automatic tracking?
</button>
</h2>
<div id="setup3" class="accordion-collapse collapse" data-bs-parent="#setupAccordion">
<div class="accordion-body">
<p>Hooks enable real-time tracking of your Claude Code sessions. Here's the quick setup:</p>
<ol>
<li class="mb-2">Locate your Claude Code settings file:
<ul>
<li>macOS/Linux: <code>~/.config/claude/settings.json</code></li>
<li>Windows: <code>%APPDATA%\claude\settings.json</code></li>
</ul>
</li>
<li class="mb-2">Add hook configuration to the settings file</li>
<li class="mb-2">Ensure Claude Code Tracker server is running</li>
<li>Test by starting a new Claude Code session</li>
</ol>
<div class="alert alert-success">
<i class="fas fa-book me-2"></i>
<strong>Detailed Instructions:</strong> See the <a href="/dashboard/docs/hook-setup">Hook Setup Guide</a> for complete configuration examples.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Troubleshooting -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-warning text-dark">
<h3 class="mb-0"><i class="fas fa-tools me-2"></i>Troubleshooting</h3>
</div>
<div class="card-body">
<div class="accordion" id="troubleshootingAccordion">
<!-- Q7 -->
<div class="accordion-item border-0 faq-item" data-keywords="import fails error upload data corrupted file">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble1">
<i class="fas fa-exclamation-triangle me-2 text-danger"></i>
Data import is failing. What should I do?
</button>
</h2>
<div id="trouble1" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Common causes and solutions:</strong></p>
<div class="row">
<div class="col-md-6">
<h6><i class="fas fa-file-alt me-2 text-warning"></i>File Issues:</h6>
<ul>
<li>Verify the JSON file is valid</li>
<li>Check file permissions (readable)</li>
<li>Ensure file is not corrupted</li>
<li>Try with a smaller JSON file first</li>
</ul>
</div>
<div class="col-md-6">
<h6><i class="fas fa-server me-2 text-info"></i>System Issues:</h6>
<ul>
<li>Check available disk space (need ~100MB)</li>
<li>Verify database permissions</li>
<li>Restart the server and try again</li>
<li>Check server logs for detailed errors</li>
</ul>
</div>
</div>
<div class="bg-dark text-light p-3 rounded mt-3">
<h6 class="text-light">Validate your JSON file:</h6>
<code>python -m json.tool ~/.claude.json > /dev/null && echo "Valid JSON" || echo "Invalid JSON"</code>
</div>
</div>
</div>
</div>
<!-- Q8 -->
<div class="accordion-item border-0 faq-item" data-keywords="statistics zero empty projects showing wrong data recalculate">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble2">
<i class="fas fa-chart-line me-2 text-primary"></i>
Project statistics are showing zeros. How to fix?
</button>
</h2>
<div id="trouble2" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p>This commonly happens after importing data. The statistics need to be recalculated.</p>
<div class="alert alert-success">
<i class="fas fa-wrench me-2"></i>
<strong>Quick Fix:</strong> Run the recalculation script
</div>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light">Run this command in your installation directory:</h6>
<code>python recalculate_project_stats.py</code>
</div>
<p>This script will:</p>
<ul>
<li>Recalculate session counts and duration</li>
<li>Count file modifications and line changes</li>
<li>Update last session times</li>
<li>Fix all project statistics</li>
</ul>
<p class="mb-0"><strong>Note:</strong> The process may take a few minutes for large datasets.</p>
</div>
</div>
</div>
<!-- Q9 -->
<div class="accordion-item border-0 faq-item" data-keywords="hooks not working automatic tracking session start end">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble3">
<i class="fas fa-unlink me-2 text-warning"></i>
Hooks are not working. Sessions aren't being tracked automatically.
</button>
</h2>
<div id="trouble3" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Checklist to troubleshoot hooks:</strong></p>
<div class="row">
<div class="col-md-6">
<h6>Server Status:</h6>
<ul>
<li>✅ Tracker server is running</li>
<li>✅ Server accessible at localhost:8000</li>
<li>✅ No firewall blocking connections</li>
<li>✅ Check server logs for errors</li>
</ul>
</div>
<div class="col-md-6">
<h6>Configuration:</h6>
<ul>
<li>✅ Settings.json file exists</li>
<li>✅ Hook URLs are correct</li>
<li>✅ JSON syntax is valid</li>
<li>✅ curl is installed</li>
</ul>
</div>
</div>
<div class="bg-dark text-light p-3 rounded mt-3">
<h6 class="text-light">Test hook manually:</h6>
<code>curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{"project_path": "/test", "start_time": "2024-01-01T12:00:00"}'</code>
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-book me-2"></i>
See the <a href="/dashboard/docs/hook-setup">Hook Setup Guide</a> for detailed troubleshooting steps.
</div>
</div>
</div>
</div>
<!-- Q10 -->
<div class="accordion-item border-0 faq-item" data-keywords="server won't start port error permission database">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#trouble4">
<i class="fas fa-server me-2 text-danger"></i>
The server won't start. What's wrong?
</button>
</h2>
<div id="trouble4" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Common issues and solutions:</strong></p>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Error</th>
<th>Cause</th>
<th>Solution</th>
</tr>
</thead>
<tbody>
<tr>
<td>Port 8000 in use</td>
<td>Another service using port</td>
<td>Kill process or use different port</td>
</tr>
<tr>
<td>Permission denied</td>
<td>Insufficient file permissions</td>
<td>Check directory permissions</td>
</tr>
<tr>
<td>Module not found</td>
<td>Missing dependencies</td>
<td>Run <code>pip install -r requirements.txt</code></td>
</tr>
<tr>
<td>Database error</td>
<td>Corrupted database</td>
<td>Delete <code>data/tracker.db</code> and restart</td>
</tr>
</tbody>
</table>
</div>
<div class="bg-dark text-light p-3 rounded mt-3">
<h6 class="text-light">Check what's using port 8000:</h6>
<p class="mb-2"><strong>macOS/Linux:</strong> <code>lsof -i :8000</code></p>
<p class="mb-0"><strong>Windows:</strong> <code>netstat -ano | findstr :8000</code></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Usage Questions -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-info text-white">
<h3 class="mb-0"><i class="fas fa-users me-2"></i>Usage Questions</h3>
</div>
<div class="card-body">
<div class="accordion" id="usageAccordion">
<!-- Q11 -->
<div class="accordion-item border-0 faq-item" data-keywords="search conversations find messages history filter">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#usage1">
<i class="fas fa-search me-2 text-primary"></i>
How do I search through my conversations?
</button>
</h2>
<div id="usage1" class="accordion-collapse collapse" data-bs-parent="#usageAccordion">
<div class="accordion-body">
<p>The <a href="/dashboard/conversations">Conversations</a> page provides powerful search capabilities:</p>
<div class="row">
<div class="col-md-6">
<h6>Search Options:</h6>
<ul>
<li><strong>Text Search:</strong> Search message content</li>
<li><strong>Project Filter:</strong> Filter by specific project</li>
<li><strong>Date Range:</strong> Filter by time period</li>
<li><strong>Role Filter:</strong> User vs Assistant messages</li>
</ul>
</div>
<div class="col-md-6">
<h6>Search Tips:</h6>
<ul>
<li>Use quotes for exact phrases</li>
<li>Search is case-insensitive</li>
<li>Combine filters for precise results</li>
<li>Use wildcards (*) for partial matches</li>
</ul>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-lightbulb me-2"></i>
<strong>Pro Tip:</strong> Use the project filter to focus on conversations from specific coding projects.
</div>
</div>
</div>
</div>
<!-- Q12 -->
<div class="accordion-item border-0 faq-item" data-keywords="export data backup conversations sessions save">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#usage2">
<i class="fas fa-download me-2 text-success"></i>
Can I export my data or create backups?
</button>
</h2>
<div id="usage2" class="accordion-collapse collapse" data-bs-parent="#usageAccordion">
<div class="accordion-body">
<p><strong>Yes! You have full control over your data:</strong></p>
<div class="row">
<div class="col-md-6">
<h6>Database Backup:</h6>
<p>The complete database is stored in:</p>
<code class="d-block p-2 bg-light rounded">./data/tracker.db</code>
<p class="mt-2 small text-muted">Simply copy this file to create a complete backup.</p>
</div>
<div class="col-md-6">
<h6>API Export:</h6>
<p>Use the API to export specific data:</p>
<ul class="small">
<li>GET /api/projects (all projects)</li>
<li>GET /api/sessions (all sessions)</li>
<li>GET /api/conversations (all conversations)</li>
</ul>
</div>
</div>
<div class="alert alert-success">
<i class="fas fa-shield-alt me-2"></i>
<strong>Recommendation:</strong> Regularly backup the <code>data/</code> directory to preserve your development history.
</div>
</div>
</div>
</div>
<!-- Q13 -->
<div class="accordion-item border-0 faq-item" data-keywords="delete remove data project session conversation clear">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#usage3">
<i class="fas fa-trash me-2 text-danger"></i>
How do I delete specific projects or conversations?
</button>
</h2>
<div id="usage3" class="accordion-collapse collapse" data-bs-parent="#usageAccordion">
<div class="accordion-body">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Note:</strong> Currently, there's no built-in UI for deleting individual items. This feature is planned for future releases.
</div>
<p><strong>Workarounds:</strong></p>
<ol>
<li class="mb-2"><strong>Database Access:</strong> Use an SQLite client to directly modify the database</li>
<li class="mb-2"><strong>Fresh Start:</strong> Delete the entire database file and re-import</li>
<li class="mb-2"><strong>API (Advanced):</strong> Use custom scripts with the API endpoints</li>
</ol>
<div class="bg-dark text-light p-3 rounded">
<h6 class="text-light">Nuclear option - Clear all data:</h6>
<code>rm data/tracker.db && python main.py</code>
<br><small class="text-muted">This will create a fresh database on next startup</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Performance & Optimization -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-secondary text-white">
<h3 class="mb-0"><i class="fas fa-tachometer-alt me-2"></i>Performance & Optimization</h3>
</div>
<div class="card-body">
<div class="accordion" id="performanceAccordion">
<!-- Q14 -->
<div class="accordion-item border-0 faq-item" data-keywords="slow performance large datasets optimize speed">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf1">
<i class="fas fa-clock me-2 text-warning"></i>
The dashboard is slow with large amounts of data. How to optimize?
</button>
</h2>
<div id="perf1" class="accordion-collapse collapse" data-bs-parent="#performanceAccordion">
<div class="accordion-body">
<p><strong>Optimization strategies for large datasets:</strong></p>
<div class="row">
<div class="col-md-6">
<h6>Quick Fixes:</h6>
<ul>
<li>Use date filters to limit results</li>
<li>Filter by specific projects</li>
<li>Close unused browser tabs</li>
<li>Restart the server periodically</li>
</ul>
</div>
<div class="col-md-6">
<h6>Advanced Options:</h6>
<ul>
<li>Increase server memory allocation</li>
<li>Use pagination for large results</li>
<li>Consider archiving old data</li>
<li>Run server on more powerful hardware</li>
</ul>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
<strong>Typical Performance:</strong> The system handles 10,000+ conversations efficiently. Performance degrades with 50,000+ entries.
</div>
</div>
</div>
</div>
<!-- Q15 -->
<div class="accordion-item border-0 faq-item" data-keywords="disk space database size storage cleanup">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf2">
<i class="fas fa-hdd me-2 text-info"></i>
How much disk space does the tracker use?
</button>
</h2>
<div id="perf2" class="accordion-collapse collapse" data-bs-parent="#performanceAccordion">
<div class="accordion-body">
<p><strong>Typical storage requirements:</strong></p>
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>Data Size</th>
<th>Conversations</th>
<th>Disk Usage</th>
<th>Performance</th>
</tr>
</thead>
<tbody>
<tr>
<td>Small</td>
<td>< 1,000</td>
<td>< 10MB</td>
<td>Excellent</td>
</tr>
<tr>
<td>Medium</td>
<td>1,000 - 10,000</td>
<td>10-100MB</td>
<td>Good</td>
</tr>
<tr>
<td>Large</td>
<td>10,000 - 50,000</td>
<td>100MB-1GB</td>
<td>Fair</td>
</tr>
<tr>
<td>Very Large</td>
<td>> 50,000</td>
<td>> 1GB</td>
<td>Consider archiving</td>
</tr>
</tbody>
</table>
</div>
<div class="bg-dark text-light p-3 rounded mt-3">
<h6 class="text-light">Check current database size:</h6>
<p class="mb-2"><strong>Unix:</strong> <code>du -h data/tracker.db</code></p>
<p class="mb-0"><strong>Windows:</strong> <code>dir data\tracker.db</code></p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 2rem;">
<!-- Quick Help -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-life-ring me-2"></i>Quick Help</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="/dashboard/docs/getting-started" class="btn btn-outline-primary btn-sm">
<i class="fas fa-rocket me-1"></i>
Getting Started
</a>
<a href="/dashboard/docs/hook-setup" class="btn btn-outline-success btn-sm">
<i class="fas fa-link me-1"></i>
Hook Setup Guide
</a>
<a href="/dashboard/docs/api-reference" class="btn btn-outline-info btn-sm">
<i class="fas fa-code me-1"></i>
API Reference
</a>
</div>
</div>
</div>
<!-- Categories -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="fas fa-tags me-2"></i>Categories</h6>
</div>
<div class="card-body">
<div class="d-flex flex-wrap gap-2">
<span class="badge bg-primary" data-filter="general">General</span>
<span class="badge bg-success" data-filter="setup">Setup</span>
<span class="badge bg-warning" data-filter="troubleshooting">Troubleshooting</span>
<span class="badge bg-info" data-filter="usage">Usage</span>
<span class="badge bg-secondary" data-filter="performance">Performance</span>
</div>
</div>
</div>
<!-- Common Issues -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="fas fa-exclamation-triangle me-2"></i>Top Issues</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
<li class="mb-2">
<i class="fas fa-circle text-danger me-2" style="font-size: 0.5rem;"></i>
<a href="#trouble2" class="text-decoration-none small">Statistics showing zeros</a>
</li>
<li class="mb-2">
<i class="fas fa-circle text-warning me-2" style="font-size: 0.5rem;"></i>
<a href="#trouble1" class="text-decoration-none small">Import failing</a>
</li>
<li class="mb-2">
<i class="fas fa-circle text-info me-2" style="font-size: 0.5rem;"></i>
<a href="#trouble3" class="text-decoration-none small">Hooks not working</a>
</li>
<li class="mb-0">
<i class="fas fa-circle text-secondary me-2" style="font-size: 0.5rem;"></i>
<a href="#trouble4" class="text-decoration-none small">Server won't start</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
// FAQ Search functionality
document.getElementById('faqSearch').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
const faqItems = document.querySelectorAll('.faq-item');
faqItems.forEach(item => {
const keywords = item.dataset.keywords.toLowerCase();
const text = item.textContent.toLowerCase();
if (keywords.includes(searchTerm) || text.includes(searchTerm) || searchTerm === '') {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
// Category filtering
document.querySelectorAll('[data-filter]').forEach(badge => {
badge.addEventListener('click', function() {
const filter = this.dataset.filter.toLowerCase();
const faqItems = document.querySelectorAll('.faq-item');
// Toggle active state
document.querySelectorAll('[data-filter]').forEach(b => b.classList.remove('active'));
this.classList.add('active');
faqItems.forEach(item => {
const keywords = item.dataset.keywords.toLowerCase();
if (keywords.includes(filter) || filter === '') {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,354 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
<li class="breadcrumb-item active" aria-current="page">Getting Started</li>
</ol>
</nav>
<h2><i class="fas fa-rocket me-3"></i>Getting Started</h2>
<p class="lead text-muted">Learn the basics and get Claude Code Tracker up and running</p>
</div>
<div class="text-muted">
<i class="fas fa-clock me-1"></i>
5 min read
</div>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Overview -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-info-circle me-2 text-primary"></i>What is Claude Code Tracker?</h3>
<p>Claude Code Tracker is a development intelligence system that helps you understand and analyze your coding activity when using Claude Code. It automatically tracks your sessions, conversations, file modifications, and provides insights into your development patterns.</p>
<div class="row mt-4">
<div class="col-md-6">
<h5><i class="fas fa-check-circle me-2 text-success"></i>Key Benefits</h5>
<ul class="list-unstyled">
<li class="mb-2"><i class="fas fa-chart-line text-primary me-2"></i>Track development productivity</li>
<li class="mb-2"><i class="fas fa-history text-info me-2"></i>Browse conversation history</li>
<li class="mb-2"><i class="fas fa-folder text-warning me-2"></i>Analyze project patterns</li>
<li class="mb-2"><i class="fas fa-shield-alt text-success me-2"></i>Keep data local and private</li>
</ul>
</div>
<div class="col-md-6">
<h5><i class="fas fa-cogs me-2 text-info"></i>Core Features</h5>
<ul class="list-unstyled">
<li class="mb-2"><i class="fas fa-upload text-secondary me-2"></i>Import existing Claude data</li>
<li class="mb-2"><i class="fas fa-link text-success me-2"></i>Real-time hook integration</li>
<li class="mb-2"><i class="fas fa-search text-primary me-2"></i>Advanced search and filtering</li>
<li class="mb-2"><i class="fas fa-chart-bar text-warning me-2"></i>Visual analytics and insights</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Quick Start Steps -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h3 class="mb-0"><i class="fas fa-play-circle me-2"></i>Quick Start Guide</h3>
</div>
<div class="card-body">
<!-- Step 1: Import Data -->
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
<div class="bg-primary rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
<strong>1</strong>
</div>
<div class="flex-grow-1">
<h5 class="mb-2">Import Your Existing Data</h5>
<p class="text-muted mb-3">Start by importing your existing Claude Code conversation history from your <code>~/.claude.json</code> file.</p>
<div class="bg-light p-3 rounded mb-3">
<h6><i class="fas fa-cloud-upload-alt me-2"></i>File Upload Method (Recommended):</h6>
<ol class="mb-2">
<li>Navigate to the <a href="/dashboard/import">Import Data</a> page</li>
<li>Click "Select .claude.json file" and choose your file</li>
<li>Click "Preview Upload" to review what will be imported</li>
<li>Click "Import Upload" to complete the import</li>
</ol>
<div class="alert alert-success mt-2 mb-0">
<i class="fas fa-lightbulb me-1"></i>
<strong>New:</strong> File upload method works with any .claude.json file and provides detailed previews before importing.
</div>
</div>
<div class="alert alert-info mb-0">
<i class="fas fa-info-circle me-2"></i>
<strong>Location:</strong> Your Claude data file is typically located at <code>~/.claude.json</code> on macOS/Linux or <code>%USERPROFILE%\.claude.json</code> on Windows.
</div>
</div>
</div>
<!-- Step 2: Set Up Hooks -->
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
<div class="bg-success rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
<strong>2</strong>
</div>
<div class="flex-grow-1">
<h5 class="mb-2">Configure Claude Code Hooks</h5>
<p class="text-muted mb-3">Set up hooks to automatically track new sessions and conversations in real-time.</p>
<div class="bg-light p-3 rounded mb-3">
<h6><i class="fas fa-code me-2"></i>Hook Configuration:</h6>
<p class="mb-2">Add this configuration to your Claude Code settings:</p>
<pre class="bg-dark text-light p-3 rounded"><code>{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations"
}
}</code></pre>
</div>
<div class="alert alert-warning mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Note:</strong> Detailed hook setup instructions are available in the <a href="/dashboard/docs/hook-setup">Hook Setup Guide</a>.
</div>
</div>
</div>
<!-- Step 3: Explore Dashboard -->
<div class="d-flex align-items-start">
<div class="bg-info rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
<strong>3</strong>
</div>
<div class="flex-grow-1">
<h5 class="mb-2">Explore Your Data</h5>
<p class="text-muted mb-3">Navigate through the dashboard to discover insights about your development activity.</p>
<div class="row">
<div class="col-md-6">
<div class="card border-0 bg-light">
<div class="card-body p-3">
<h6><a href="/dashboard/projects" class="text-decoration-none"><i class="fas fa-folder-open me-2"></i>Projects</a></h6>
<small class="text-muted">View project statistics, timelines, and detailed analytics</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border-0 bg-light">
<div class="card-body p-3">
<h6><a href="/dashboard/conversations" class="text-decoration-none"><i class="fas fa-comments me-2"></i>Conversations</a></h6>
<small class="text-muted">Search and browse through all your Claude conversations</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- System Requirements -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-server me-2 text-info"></i>System Requirements</h3>
<div class="row">
<div class="col-md-6">
<h5>Software Requirements</h5>
<ul class="list-unstyled">
<li class="mb-2">
<i class="fab fa-python text-primary me-2"></i>
<strong>Python 3.8+</strong>
</li>
<li class="mb-2">
<i class="fas fa-terminal text-secondary me-2"></i>
<strong>Claude Code CLI</strong>
</li>
<li class="mb-2">
<i class="fas fa-globe text-info me-2"></i>
<strong>Web browser</strong> (modern browser recommended)
</li>
</ul>
</div>
<div class="col-md-6">
<h5>Hardware Recommendations</h5>
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-memory text-warning me-2"></i>
<strong>2GB+ RAM</strong>
</li>
<li class="mb-2">
<i class="fas fa-hdd text-success me-2"></i>
<strong>100MB+ disk space</strong>
</li>
<li class="mb-2">
<i class="fas fa-network-wired text-primary me-2"></i>
<strong>Network access</strong> for web interface
</li>
</ul>
</div>
</div>
</div>
</div>
<!-- Troubleshooting -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-tools me-2 text-warning"></i>Common Issues</h3>
<div class="accordion" id="troubleshootingAccordion">
<!-- Issue 1 -->
<div class="accordion-item border-0">
<h2 class="accordion-header" id="issue1">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapse1">
<i class="fas fa-file-import me-2 text-danger"></i>
Import fails or shows errors
</button>
</h2>
<div id="collapse1" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Possible causes:</strong></p>
<ul>
<li>Invalid or corrupted <code>.claude.json</code> file</li>
<li>Insufficient disk space</li>
<li>File permissions issues</li>
</ul>
<p><strong>Solutions:</strong></p>
<ul>
<li>Verify the JSON file is valid using a JSON validator</li>
<li>Check available disk space (need ~100MB minimum)</li>
<li>Ensure the application has read/write permissions</li>
</ul>
</div>
</div>
</div>
<!-- Issue 2 -->
<div class="accordion-item border-0">
<h2 class="accordion-header" id="issue2">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapse2">
<i class="fas fa-link me-2 text-warning"></i>
Hooks not working
</button>
</h2>
<div id="collapse2" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>Check these items:</strong></p>
<ul>
<li>Claude Code Tracker server is running on port 8000</li>
<li>Hook URLs are correctly configured</li>
<li>Network connectivity between Claude Code and the tracker</li>
<li>Hook syntax is valid JSON</li>
</ul>
</div>
</div>
</div>
<!-- Issue 3 -->
<div class="accordion-item border-0">
<h2 class="accordion-header" id="issue3">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapse3">
<i class="fas fa-chart-line me-2 text-info"></i>
Statistics showing zeros
</button>
</h2>
<div id="collapse3" class="accordion-collapse collapse" data-bs-parent="#troubleshootingAccordion">
<div class="accordion-body">
<p><strong>This can happen after import. Run the recalculation script:</strong></p>
<pre class="bg-dark text-light p-3 rounded"><code>python recalculate_project_stats.py</code></pre>
<p class="mt-2">This will recalculate all project statistics from your imported data.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 2rem;">
<!-- Quick Actions -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="fas fa-rocket me-2"></i>Quick Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="/dashboard/import" class="btn btn-success">
<i class="fas fa-upload me-2"></i>
Import Data Now
</a>
<a href="/dashboard/docs/hook-setup" class="btn btn-outline-primary">
<i class="fas fa-link me-2"></i>
Setup Hooks
</a>
<a href="/dashboard/projects" class="btn btn-outline-info">
<i class="fas fa-folder-open me-2"></i>
View Projects
</a>
</div>
</div>
</div>
<!-- Table of Contents -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="fas fa-list me-2"></i>Contents</h6>
</div>
<div class="card-body">
<nav class="nav nav-pills flex-column">
<a class="nav-link py-1 px-2 text-sm" href="#overview">What is Claude Code Tracker?</a>
<a class="nav-link py-1 px-2 text-sm" href="#quickstart">Quick Start Guide</a>
<a class="nav-link py-1 px-2 text-sm" href="#requirements">System Requirements</a>
<a class="nav-link py-1 px-2 text-sm" href="#troubleshooting">Common Issues</a>
</nav>
</div>
</div>
<!-- Related Links -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="fas fa-external-link-alt me-2"></i>Related</h6>
</div>
<div class="card-body">
<ul class="list-unstyled mb-0">
<li class="mb-2">
<a href="/dashboard/docs/hook-setup" class="text-decoration-none">
<i class="fas fa-link me-2 text-success"></i>
Hook Setup Guide
</a>
</li>
<li class="mb-2">
<a href="/dashboard/docs/api-reference" class="text-decoration-none">
<i class="fas fa-code me-2 text-info"></i>
API Reference
</a>
</li>
<li class="mb-2">
<a href="/dashboard/docs/faq" class="text-decoration-none">
<i class="fas fa-question-circle me-2 text-warning"></i>
FAQ
</a>
</li>
<li>
<a href="/docs" target="_blank" class="text-decoration-none">
<i class="fas fa-external-link-alt me-2 text-primary"></i>
API Documentation
</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,531 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
<li class="breadcrumb-item active" aria-current="page">Complete Hook Reference</li>
</ol>
</nav>
<h2><i class="fas fa-book-open me-3"></i>Complete Hook Reference</h2>
<p class="lead text-muted">Comprehensive guide to all available Claude Code hooks</p>
</div>
<div class="text-muted">
<i class="fas fa-info-circle me-1"></i>
30+ Hook Types
</div>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Quick Overview -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-lightning-bolt me-2 text-primary"></i>Hook Categories Overview</h3>
<p>Claude Code Tracker supports comprehensive development intelligence through these hook categories:</p>
<div class="row">
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-primary bg-opacity-10">
<div class="card-body p-3 text-center">
<i class="fas fa-play-circle fa-2x text-primary mb-2"></i>
<h6>Essential (5)</h6>
<small class="text-muted">Core session & tool tracking</small>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-warning bg-opacity-10">
<div class="card-body p-3 text-center">
<i class="fas fa-tachometer-alt fa-2x text-warning mb-2"></i>
<h6>Performance (5)</h6>
<small class="text-muted">System & debug monitoring</small>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-success bg-opacity-10">
<div class="card-body p-3 text-center">
<i class="fas fa-code-branch fa-2x text-success mb-2"></i>
<h6>Code Quality (4)</h6>
<small class="text-muted">Linting, testing, builds</small>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-info bg-opacity-10">
<div class="card-body p-3 text-center">
<i class="fas fa-project-diagram fa-2x text-info mb-2"></i>
<h6>Workflow (4)</h6>
<small class="text-muted">Development patterns</small>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-secondary bg-opacity-10">
<div class="card-body p-3 text-center">
<i class="fas fa-users fa-2x text-secondary mb-2"></i>
<h6>Collaboration (4)</h6>
<small class="text-muted">External interactions</small>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-dark bg-opacity-10">
<div class="card-body p-3 text-center">
<i class="fas fa-brain fa-2x text-dark mb-2"></i>
<h6>Intelligence (5)</h6>
<small class="text-muted">High-level project insights</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Essential Hooks -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h3 class="mb-0"><i class="fas fa-play-circle me-2"></i>Essential Hooks</h3>
</div>
<div class="card-body">
<p class="text-muted mb-4">Core hooks that every user should enable for basic session and development tracking.</p>
<div class="row">
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><code>session_start</code></h6>
<p class="small text-muted mb-2">Triggered when a new Claude Code session begins</p>
<div class="badge bg-success">Required</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><code>session_end</code></h6>
<p class="small text-muted mb-2">Triggered when a Claude Code session ends</p>
<div class="badge bg-success">Required</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><code>conversation_update</code></h6>
<p class="small text-muted mb-2">Logs conversation messages and interactions</p>
<div class="badge bg-warning">Recommended</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><code>tool_call</code></h6>
<p class="small text-muted mb-2">Tracks which tools are used and their performance</p>
<div class="badge bg-warning">Recommended</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><code>file_modified</code></h6>
<p class="small text-muted mb-2">Records file creations, modifications, and deletions</p>
<div class="badge bg-info">Optional</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Performance & Debugging Hooks -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-warning text-dark">
<h3 class="mb-0"><i class="fas fa-tachometer-alt me-2"></i>Performance & Debugging</h3>
</div>
<div class="card-body">
<p class="text-muted mb-4">Monitor system performance and track debugging activities.</p>
<div class="accordion" id="performanceAccordion">
<div class="accordion-item border-0">
<h2 class="accordion-header">
<button class="accordion-button bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf1">
<i class="fas fa-exclamation-triangle me-2 text-danger"></i>
<code>tool_error</code> - Track failed tool calls
</button>
</h2>
<div id="perf1" class="accordion-collapse collapse show" data-bs-parent="#performanceAccordion">
<div class="accordion-body">
<p><strong>Purpose:</strong> Record when tools fail with detailed error information.</p>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light">Hook Configuration:</h6>
<code class="small">
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\"}'"
</code>
</div>
<p><strong>Variables:</strong> <code>$TOOL_NAME</code>, <code>$ERROR_TYPE</code>, <code>$ERROR_MESSAGE</code>, <code>$STACK_TRACE</code></p>
</div>
</div>
</div>
<div class="accordion-item border-0">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf2">
<i class="fas fa-clock me-2 text-primary"></i>
<code>waiting_period_start/end</code> - Track thinking time
</button>
</h2>
<div id="perf2" class="accordion-collapse collapse" data-bs-parent="#performanceAccordion">
<div class="accordion-body">
<p><strong>Purpose:</strong> Monitor periods when Claude is processing vs actively working.</p>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light">Hook Configurations:</h6>
<code class="small d-block mb-2">
"waiting_period_start": "curl -X POST .../api/hooks/waiting-period -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'"
</code>
<code class="small d-block">
"waiting_period_end": "curl -X POST .../api/hooks/waiting-period -d '{\"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'"
</code>
</div>
<p><strong>Use Cases:</strong> Identify bottlenecks, optimize prompts, understand processing patterns</p>
</div>
</div>
</div>
<div class="accordion-item border-0">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#perf3">
<i class="fas fa-memory me-2 text-info"></i>
<code>memory_usage</code> - Monitor system resources
</button>
</h2>
<div id="perf3" class="accordion-collapse collapse" data-bs-parent="#performanceAccordion">
<div class="accordion-body">
<p><strong>Purpose:</strong> Track memory, CPU, and disk usage during development.</p>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light">Hook Configuration:</h6>
<code class="small">
"memory_usage": "curl -X POST .../api/hooks/performance -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'"
</code>
</div>
<p><strong>Metrics:</strong> memory, cpu, disk, network</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Code Quality Hooks -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="fas fa-code-branch me-2"></i>Code Quality & Testing</h3>
</div>
<div class="card-body">
<p class="text-muted mb-4">Track linting, testing, builds, and dependency changes.</p>
<div class="row">
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-search me-2 text-primary"></i><code>code_analysis</code></h6>
<p class="small text-muted">ESLint, Prettier, type checking results</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-vial me-2 text-success"></i><code>test_execution</code></h6>
<p class="small text-muted">Unit tests, integration tests, coverage</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-hammer me-2 text-warning"></i><code>build_process</code></h6>
<p class="small text-muted">Compilation, bundling, deployment</p>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-boxes me-2 text-info"></i><code>dependency_change</code></h6>
<p class="small text-muted">Package installs, updates, removals</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Workflow Hooks -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-info text-white">
<h3 class="mb-0"><i class="fas fa-project-diagram me-2"></i>Development Workflow</h3>
</div>
<div class="card-body">
<p class="text-muted mb-4">Understand development patterns and workflow efficiency.</p>
<div class="row">
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-exchange-alt me-2 text-primary"></i><code>context_switch</code></h6>
<p class="small text-muted">Track project/context changes</p>
<div class="small">
<strong>Impact:</strong> Measure multitasking overhead
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-search me-2 text-success"></i><code>search_query</code></h6>
<p class="small text-muted">What you search for in code/files</p>
<div class="small">
<strong>Insights:</strong> Common patterns, knowledge gaps
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-globe me-2 text-warning"></i><code>browser_tab</code></h6>
<p class="small text-muted">Documentation, StackOverflow visits</p>
<div class="small">
<strong>Analysis:</strong> Research patterns, learning topics
</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-copy me-2 text-info"></i><code>copy_paste</code></h6>
<p class="small text-muted">Code copying patterns</p>
<div class="small">
<strong>Quality:</strong> Identify repetitive work
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Pre-configured Setups -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-dark text-white">
<h3 class="mb-0"><i class="fas fa-download me-2"></i>Pre-configured Hook Setups</h3>
</div>
<div class="card-body">
<p class="text-muted mb-4">Download ready-to-use configurations for different development styles:</p>
<div class="row">
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-primary bg-opacity-10">
<div class="card-body text-center">
<i class="fas fa-rocket fa-2x text-primary mb-2"></i>
<h6>Essential</h6>
<p class="small text-muted">Core tracking only</p>
<a href="/static/claude-hooks-essential.json" download class="btn btn-sm btn-primary">
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-success bg-opacity-10">
<div class="card-body text-center">
<i class="fas fa-code fa-2x text-success mb-2"></i>
<h6>Developer</h6>
<p class="small text-muted">Code quality focused</p>
<a href="/static/claude-hooks-developer.json" download class="btn btn-sm btn-success">
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-warning bg-opacity-10">
<div class="card-body text-center">
<i class="fas fa-bolt fa-2x text-warning mb-2"></i>
<h6>Power User</h6>
<p class="small text-muted">Advanced analytics</p>
<a href="/static/claude-hooks-power_user.json" download class="btn btn-sm btn-warning">
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-info bg-opacity-10">
<div class="card-body text-center">
<i class="fas fa-graduation-cap fa-2x text-info mb-2"></i>
<h6>Research</h6>
<p class="small text-muted">Learning focused</p>
<a href="/static/claude-hooks-research.json" download class="btn btn-sm btn-info">
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-secondary bg-opacity-10">
<div class="card-body text-center">
<i class="fas fa-users fa-2x text-secondary mb-2"></i>
<h6>Team</h6>
<p class="small text-muted">Collaboration tracking</p>
<a href="/static/claude-hooks-team.json" download class="btn btn-sm btn-secondary">
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-3">
<div class="card border-0 bg-dark bg-opacity-10">
<div class="card-body text-center">
<i class="fas fa-cogs fa-2x text-dark mb-2"></i>
<h6>Comprehensive</h6>
<p class="small text-muted">All hooks enabled</p>
<a href="/static/claude-hooks-comprehensive.json" download class="btn btn-sm btn-dark">
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 2rem;">
<!-- Quick Navigation -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="fas fa-list me-2"></i>Hook Categories</h5>
</div>
<div class="card-body">
<nav class="nav nav-pills flex-column">
<a class="nav-link py-1 px-2 text-sm" href="#essential">Essential (5)</a>
<a class="nav-link py-1 px-2 text-sm" href="#performance">Performance (5)</a>
<a class="nav-link py-1 px-2 text-sm" href="#code-quality">Code Quality (4)</a>
<a class="nav-link py-1 px-2 text-sm" href="#workflow">Workflow (4)</a>
<a class="nav-link py-1 px-2 text-sm" href="#collaboration">Collaboration (4)</a>
<a class="nav-link py-1 px-2 text-sm" href="#intelligence">Intelligence (5)</a>
<a class="nav-link py-1 px-2 text-sm" href="#environment">Environment (4)</a>
<a class="nav-link py-1 px-2 text-sm" href="#learning">Learning (4)</a>
<a class="nav-link py-1 px-2 text-sm" href="#testing">Testing (4)</a>
</nav>
</div>
</div>
<!-- Hook Statistics -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h6 class="mb-0"><i class="fas fa-chart-bar me-2"></i>Hook Statistics</h6>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="small">Total Hooks Available:</span>
<span class="badge bg-primary">39</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="small">API Endpoints:</span>
<span class="badge bg-success">10</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2">
<span class="small">Pre-built Configs:</span>
<span class="badge bg-info">7</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="small">Hook Variables:</span>
<span class="badge bg-warning">25+</span>
</div>
</div>
</div>
<!-- Quick Setup -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="fas fa-zap me-2"></i>Quick Setup</h6>
</div>
<div class="card-body">
<ol class="small">
<li>Download a pre-configured setup above</li>
<li>Copy JSON content to your Claude settings</li>
<li>Ensure tracker is running on port 8000</li>
<li>Start Claude Code - hooks activate automatically!</li>
</ol>
<div class="d-grid gap-2 mt-3">
<a href="/dashboard/docs/hook-setup" class="btn btn-sm btn-primary">
<i class="fas fa-cogs me-1"></i>Setup Guide
</a>
<a href="/dashboard/docs/api-reference" class="btn btn-sm btn-outline-info">
<i class="fas fa-code me-1"></i>API Reference
</a>
</div>
</div>
</div>
<!-- Variable Reference -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-info text-white">
<h6 class="mb-0"><i class="fas fa-dollar-sign me-2"></i>Key Variables</h6>
</div>
<div class="card-body">
<div class="small">
<div class="mb-2">
<code>$SESSION_ID</code>
<div class="text-muted">Current session ID</div>
</div>
<div class="mb-2">
<code>$TOOL_NAME</code>
<div class="text-muted">Tool being called</div>
</div>
<div class="mb-2">
<code>$TIMESTAMP</code>
<div class="text-muted">Current timestamp</div>
</div>
<div class="mb-2">
<code>$FILE_PATH</code>
<div class="text-muted">Modified file path</div>
</div>
<div class="mb-2">
<code>$ERROR_MESSAGE</code>
<div class="text-muted">Error details</div>
</div>
<div>
<a href="/dashboard/docs/hook-setup#variables" class="text-decoration-none small">
<i class="fas fa-external-link-alt me-1"></i>
View all variables
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,483 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<div>
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/dashboard/docs">Documentation</a></li>
<li class="breadcrumb-item active" aria-current="page">Hook Setup Guide</li>
</ol>
</nav>
<h2><i class="fas fa-link me-3"></i>Hook Setup Guide</h2>
<p class="lead text-muted">Configure Claude Code hooks for automatic activity tracking</p>
</div>
<div class="text-muted">
<i class="fas fa-clock me-1"></i>
10 min read
</div>
</div>
<div class="row">
<div class="col-lg-8">
<!-- Overview -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-info-circle me-2 text-primary"></i>What are Claude Code Hooks?</h3>
<p>Claude Code hooks are callback mechanisms that allow external applications (like this tracker) to receive notifications about various events in your Claude Code sessions. They enable real-time tracking of your development activity without manual intervention.</p>
<div class="alert alert-info">
<i class="fas fa-lightbulb me-2"></i>
<strong>Benefits:</strong> Hooks provide automatic, real-time data collection, ensuring you never miss tracking a session or conversation.
</div>
</div>
</div>
<!-- Hook Types -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h3 class="mb-0"><i class="fas fa-list me-2"></i>Available Hook Types</h3>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<h5><i class="fas fa-play-circle me-2 text-success"></i>session_start</h5>
<p class="text-muted small mb-2">Triggered when a new Claude Code session begins</p>
<div class="badge bg-success">Required</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<h5><i class="fas fa-stop-circle me-2 text-danger"></i>session_end</h5>
<p class="text-muted small mb-2">Triggered when a Claude Code session ends</p>
<div class="badge bg-success">Required</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<h5><i class="fas fa-comments me-2 text-info"></i>conversation_update</h5>
<p class="text-muted small mb-2">Triggered when conversation messages are added</p>
<div class="badge bg-warning">Recommended</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<h5><i class="fas fa-file-edit me-2 text-warning"></i>file_modified</h5>
<p class="text-muted small mb-2">Triggered when files are created or modified</p>
<div class="badge bg-info">Optional</div>
</div>
</div>
</div>
<div class="col-md-6 mb-3">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<h5><i class="fas fa-tools me-2 text-primary"></i>tool_call</h5>
<p class="text-muted small mb-2">Triggered when Claude Code tools are used</p>
<div class="badge bg-warning">Recommended</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Setup Instructions -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h3 class="mb-0"><i class="fas fa-wrench me-2"></i>Setup Instructions</h3>
</div>
<div class="card-body">
<!-- Step 1: Ensure Server Running -->
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
<div class="bg-primary rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
<strong>1</strong>
</div>
<div class="flex-grow-1">
<h5 class="mb-2">Ensure Claude Code Tracker is Running</h5>
<p class="text-muted mb-3">The tracker server must be running to receive hook notifications.</p>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light"><i class="fas fa-terminal me-2"></i>Start the server:</h6>
<code>python main.py</code>
<br><small class="text-muted">Server runs on http://localhost:8000 by default</small>
</div>
<div class="alert alert-info mb-0">
<i class="fas fa-info-circle me-2"></i>
Keep the server running while using Claude Code to ensure hooks work properly.
</div>
</div>
</div>
<!-- Step 2: Locate Settings File -->
<div class="d-flex align-items-start mb-4 pb-4 border-bottom">
<div class="bg-warning rounded-circle text-dark d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
<strong>2</strong>
</div>
<div class="flex-grow-1">
<h5 class="mb-2">Locate Claude Code Settings</h5>
<p class="text-muted mb-3">Find your Claude Code configuration file to add hooks.</p>
<div class="row">
<div class="col-md-4 mb-3">
<div class="card border-0 bg-light">
<div class="card-body p-3">
<h6><i class="fab fa-apple me-2"></i>macOS</h6>
<code class="small">~/.config/claude/settings.json</code>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card border-0 bg-light">
<div class="card-body p-3">
<h6><i class="fab fa-linux me-2"></i>Linux</h6>
<code class="small">~/.config/claude/settings.json</code>
</div>
</div>
</div>
<div class="col-md-4 mb-3">
<div class="card border-0 bg-light">
<div class="card-body p-3">
<h6><i class="fab fa-windows me-2"></i>Windows</h6>
<code class="small">%APPDATA%\claude\settings.json</code>
</div>
</div>
</div>
</div>
<div class="alert alert-warning mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Note:</strong> If the file doesn't exist, create it with the hook configuration below.
</div>
</div>
</div>
<!-- Step 3: Add Hook Configuration -->
<div class="d-flex align-items-start">
<div class="bg-success rounded-circle text-white d-flex align-items-center justify-content-center me-3" style="width: 40px; height: 40px; min-width: 40px;">
<strong>3</strong>
</div>
<div class="flex-grow-1">
<h5 class="mb-2">Add Hook Configuration</h5>
<p class="text-muted mb-3">Add the hook configuration to your Claude Code settings file.</p>
<div class="nav nav-tabs" id="configTabs" role="tablist">
<button class="nav-link active" id="basic-tab" data-bs-toggle="tab" data-bs-target="#basic" type="button">
<i class="fas fa-rocket me-1"></i>Basic Setup
</button>
<button class="nav-link" id="advanced-tab" data-bs-toggle="tab" data-bs-target="#advanced" type="button">
<i class="fas fa-cogs me-1"></i>Advanced Setup
</button>
<button class="nav-link" id="custom-tab" data-bs-toggle="tab" data-bs-target="#custom" type="button">
<i class="fas fa-edit me-1"></i>Custom Port
</button>
</div>
<div class="tab-content" id="configTabsContent">
<!-- Basic Setup -->
<div class="tab-pane fade show active" id="basic" role="tabpanel">
<div class="bg-dark text-light p-3 rounded mt-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="text-light mb-0">Basic Configuration</h6>
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('basic-config')">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
<pre id="basic-config" class="mb-0"><code>{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME}'"
}
}</code></pre>
</div>
</div>
<!-- Advanced Setup -->
<div class="tab-pane fade" id="advanced" role="tabpanel">
<div class="bg-dark text-light p-3 rounded mt-3">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="text-light mb-0">Advanced Configuration</h6>
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('advanced-config')">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
<pre id="advanced-config" class="mb-0"><code>{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'"
},
"hook_settings": {
"timeout": 5,
"retry_count": 3,
"async": true
}
}</code></pre>
</div>
</div>
<!-- Custom Port -->
<div class="tab-pane fade" id="custom" role="tabpanel">
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle me-2"></i>
If you're running the tracker on a different port, update the URLs accordingly.
</div>
<div class="bg-dark text-light p-3 rounded">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="text-light mb-0">Custom Port Configuration</h6>
<button class="btn btn-sm btn-outline-light" onclick="copyToClipboard('custom-config')">
<i class="fas fa-copy me-1"></i>Copy
</button>
</div>
<pre id="custom-config" class="mb-0"><code>{
"hooks": {
"session_start": "curl -X POST http://localhost:YOUR_PORT/api/sessions/start -H 'Content-Type: application/json' -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\"}'",
"session_end": "curl -X POST http://localhost:YOUR_PORT/api/sessions/end -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:YOUR_PORT/api/conversations -H 'Content-Type: application/json' -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\"}'",
"tool_call": "curl -X POST http://localhost:YOUR_PORT/api/tool-calls -H 'Content-Type: application/json' -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\"}'"
}
}</code></pre>
<small class="text-muted">Replace YOUR_PORT with your actual port number</small>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Available Variables -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-dollar-sign me-2 text-info"></i>Available Variables</h3>
<p class="text-muted mb-4">Claude Code provides these variables that you can use in your hook commands:</p>
<div class="row">
<div class="col-md-6">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-clock text-primary me-2"></i>Session Variables</h6>
<ul class="list-unstyled mb-0">
<li class="mb-2">
<code>$SESSION_ID</code>
<br><small class="text-muted">Unique session identifier</small>
</li>
<li class="mb-2">
<code>$TIMESTAMP</code>
<br><small class="text-muted">Current timestamp (ISO format)</small>
</li>
<li>
<code>$PWD</code>
<br><small class="text-muted">Current working directory</small>
</li>
</ul>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card border-0 bg-light">
<div class="card-body">
<h6><i class="fas fa-user text-success me-2"></i>Context Variables</h6>
<ul class="list-unstyled mb-0">
<li class="mb-2">
<code>$USER</code>
<br><small class="text-muted">Current system user</small>
</li>
<li class="mb-2">
<code>$CONTENT</code>
<br><small class="text-muted">Conversation content</small>
</li>
<li class="mb-2">
<code>$FILE_PATH</code>
<br><small class="text-muted">Modified file path</small>
</li>
<li class="mb-2">
<code>$TOOL_NAME</code>
<br><small class="text-muted">Name of tool being called</small>
</li>
<li class="mb-2">
<code>$TOOL_PARAMS</code>
<br><small class="text-muted">Tool parameters (JSON object)</small>
</li>
<li>
<code>$RESULT_STATUS</code>
<br><small class="text-muted">Success/error status</small>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Testing Hooks -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h3><i class="fas fa-vial me-2 text-warning"></i>Testing Your Hook Setup</h3>
<div class="alert alert-success">
<i class="fas fa-check-circle me-2"></i>
<strong>Quick Test:</strong> Start a new Claude Code session after setting up hooks. You should see new data appearing in the tracker dashboard.
</div>
<h5>Manual Testing</h5>
<p>You can test individual hooks manually to ensure they work:</p>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light">Test session start hook:</h6>
<code>curl -X POST http://localhost:8000/api/sessions/start -H 'Content-Type: application/json' -d '{"project_path": "/path/to/project", "start_time": "2024-01-01T12:00:00"}'</code>
</div>
<div class="bg-dark text-light p-3 rounded mb-3">
<h6 class="text-light">Check server logs:</h6>
<code>tail -f logs/tracker.log</code>
</div>
<h5 class="mt-4">Troubleshooting</h5>
<div class="accordion" id="testingAccordion">
<div class="accordion-item border-0">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTest1">
Hook commands not executing
</button>
</h2>
<div id="collapseTest1" class="accordion-collapse collapse" data-bs-parent="#testingAccordion">
<div class="accordion-body">
<ul>
<li>Check that curl is installed on your system</li>
<li>Verify the tracker server is running</li>
<li>Test the hook URL manually in a browser or with curl</li>
<li>Check Claude Code logs for hook execution errors</li>
</ul>
</div>
</div>
</div>
<div class="accordion-item border-0">
<h2 class="accordion-header">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#collapseTest2">
Data not appearing in tracker
</button>
</h2>
<div id="collapseTest2" class="accordion-collapse collapse" data-bs-parent="#testingAccordion">
<div class="accordion-body">
<ul>
<li>Check the tracker server logs for incoming requests</li>
<li>Verify the JSON payload format matches API expectations</li>
<li>Ensure the database is writable</li>
<li>Try refreshing the dashboard after a few minutes</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<div class="sticky-top" style="top: 2rem;">
<!-- Quick Reference -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-success text-white">
<h5 class="mb-0"><i class="fas fa-bolt me-2"></i>Quick Reference</h5>
</div>
<div class="card-body">
<h6>Default Server URL</h6>
<code class="d-block mb-3 p-2 bg-light rounded">http://localhost:8000</code>
<h6>Settings File Location</h6>
<div class="small">
<div class="mb-2"><strong>macOS/Linux:</strong></div>
<code class="d-block mb-2 p-2 bg-light rounded small">~/.config/claude/settings.json</code>
<div class="mb-2"><strong>Windows:</strong></div>
<code class="d-block p-2 bg-light rounded small">%APPDATA%\claude\settings.json</code>
</div>
</div>
</div>
<!-- Table of Contents -->
<div class="card border-0 shadow-sm mb-4">
<div class="card-header bg-light">
<h6 class="mb-0"><i class="fas fa-list me-2"></i>Contents</h6>
</div>
<div class="card-body">
<nav class="nav nav-pills flex-column">
<a class="nav-link py-1 px-2 text-sm" href="#overview">What are hooks?</a>
<a class="nav-link py-1 px-2 text-sm" href="#types">Hook types</a>
<a class="nav-link py-1 px-2 text-sm" href="#setup">Setup instructions</a>
<a class="nav-link py-1 px-2 text-sm" href="#variables">Available variables</a>
<a class="nav-link py-1 px-2 text-sm" href="#testing">Testing hooks</a>
</nav>
</div>
</div>
<!-- Help -->
<div class="card border-0 shadow-sm">
<div class="card-header bg-warning text-dark">
<h6 class="mb-0"><i class="fas fa-life-ring me-2"></i>Need Help?</h6>
</div>
<div class="card-body">
<p class="small text-muted mb-3">Still having trouble with hooks?</p>
<div class="d-grid gap-2">
<a href="/dashboard/docs/faq" class="btn btn-outline-warning btn-sm">
<i class="fas fa-question-circle me-1"></i>
Check FAQ
</a>
<a href="/dashboard/docs/api-reference" class="btn btn-outline-info btn-sm">
<i class="fas fa-code me-1"></i>
API Reference
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function copyToClipboard(elementId) {
const element = document.getElementById(elementId);
const text = element.textContent;
navigator.clipboard.writeText(text).then(function() {
// Show success feedback
const button = element.parentElement.querySelector('button');
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check me-1"></i>Copied!';
button.classList.add('btn-success');
button.classList.remove('btn-outline-light');
setTimeout(function() {
button.innerHTML = originalText;
button.classList.remove('btn-success');
button.classList.add('btn-outline-light');
}, 2000);
});
}
</script>
{% endblock %}

View File

@ -0,0 +1,362 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2><i class="fas fa-book me-3"></i>Documentation</h2>
<div class="text-muted">
<i class="fas fa-info-circle me-1"></i>
Comprehensive guide to using Claude Code Tracker
</div>
</div>
<div class="row">
<!-- Getting Started -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-primary bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-rocket fa-lg text-primary"></i>
</div>
</div>
<h5 class="card-title text-center">Getting Started</h5>
<p class="card-text text-center text-muted flex-grow-1">
Learn the basics of Claude Code Tracker and get up and running quickly.
</p>
<div class="text-center">
<a href="/dashboard/docs/getting-started" class="btn btn-primary">
<i class="fas fa-arrow-right me-1"></i>
Read Guide
</a>
</div>
</div>
</div>
</div>
<!-- Data Import -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-secondary bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-cloud-upload-alt fa-lg text-secondary"></i>
</div>
</div>
<h5 class="card-title text-center">Data Import Guide</h5>
<p class="card-text text-center text-muted flex-grow-1">
Step-by-step guide to importing your .claude.json file using file upload or file paths.
</p>
<div class="text-center">
<a href="/dashboard/docs/data-import" class="btn btn-secondary">
<i class="fas fa-arrow-right me-1"></i>
Import Data
</a>
</div>
</div>
</div>
</div>
<!-- Docker Deployment -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-info bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fab fa-docker fa-lg text-info"></i>
</div>
</div>
<h5 class="card-title text-center">Docker Deployment</h5>
<p class="card-text text-center text-muted flex-grow-1">
Deploy Claude Code Tracker using Docker with Caddy reverse proxy integration.
</p>
<div class="text-center">
<a href="/dashboard/docs/docker-deployment" class="btn btn-info">
<i class="fas fa-arrow-right me-1"></i>
Deploy
</a>
</div>
</div>
</div>
</div>
<!-- Hook Setup -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-success bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-link fa-lg text-success"></i>
</div>
</div>
<h5 class="card-title text-center">Hook Setup Guide</h5>
<p class="card-text text-center text-muted flex-grow-1">
Configure Claude Code hooks to automatically track your development activity.
</p>
<div class="text-center">
<a href="/dashboard/docs/hook-setup" class="btn btn-success">
<i class="fas fa-arrow-right me-1"></i>
Setup Hooks
</a>
</div>
</div>
</div>
</div>
<!-- Hook Reference -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-dark bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-book-open fa-lg text-dark"></i>
</div>
</div>
<h5 class="card-title text-center">Hook Reference</h5>
<p class="card-text text-center text-muted flex-grow-1">
Complete reference for all 39+ available Claude Code hooks and configurations.
</p>
<div class="text-center">
<a href="/dashboard/docs/hook-reference" class="btn btn-dark">
<i class="fas fa-arrow-right me-1"></i>
Browse Hooks
</a>
</div>
</div>
</div>
</div>
<!-- API Reference -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-warning bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-code fa-lg text-warning"></i>
</div>
</div>
<h5 class="card-title text-center">API Reference</h5>
<p class="card-text text-center text-muted flex-grow-1">
Detailed API documentation for integrating with Claude Code Tracker.
</p>
<div class="text-center">
<a href="/dashboard/docs/api-reference" class="btn btn-warning">
<i class="fas fa-arrow-right me-1"></i>
View API
</a>
</div>
</div>
</div>
</div>
<!-- FAQ -->
<div class="col-md-6 col-lg-4 col-xl-3 mb-4">
<div class="card h-100 border-0 shadow-sm">
<div class="card-body d-flex flex-column">
<div class="text-center mb-3">
<div class="bg-danger bg-opacity-10 rounded-circle mx-auto d-flex align-items-center justify-content-center" style="width: 60px; height: 60px;">
<i class="fas fa-question-circle fa-lg text-danger"></i>
</div>
</div>
<h5 class="card-title text-center">FAQ</h5>
<p class="card-text text-center text-muted flex-grow-1">
Frequently asked questions and troubleshooting guide.
</p>
<div class="text-center">
<a href="/dashboard/docs/faq" class="btn btn-danger">
<i class="fas fa-arrow-right me-1"></i>
Get Help
</a>
</div>
</div>
</div>
</div>
</div>
<!-- Quick Start Section -->
<div class="row mt-5">
<div class="col-md-12">
<div class="card border-0 shadow-sm">
<div class="card-header bg-light">
<h4 class="mb-0"><i class="fas fa-lightning-bolt me-2"></i>Quick Start</h4>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-8">
<p class="lead">Get started with Claude Code Tracker in just a few steps:</p>
<ol class="list-unstyled">
<li class="mb-2">
<span class="badge bg-primary rounded-pill me-2">1</span>
<strong>Import existing data</strong> from your <code>~/.claude.json</code> file
</li>
<li class="mb-2">
<span class="badge bg-primary rounded-pill me-2">2</span>
<strong>Set up hooks</strong> to automatically track new sessions
</li>
<li class="mb-2">
<span class="badge bg-primary rounded-pill me-2">3</span>
<strong>Explore your data</strong> through the dashboard and analytics
</li>
</ol>
</div>
<div class="col-md-4 text-center">
<div class="d-grid gap-2">
<a href="/dashboard/import" class="btn btn-success btn-lg">
<i class="fas fa-upload me-2"></i>
Import Data Now
</a>
<a href="/dashboard/docs/getting-started" class="btn btn-outline-primary">
<i class="fas fa-book me-2"></i>
Read Full Guide
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Features Overview -->
<div class="row mt-5">
<div class="col-md-12">
<h3 class="mb-4"><i class="fas fa-star me-2"></i>Key Features</h3>
</div>
</div>
<div class="row">
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="bg-primary bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-chart-line text-primary"></i>
</div>
<div>
<h6 class="card-title">Development Analytics</h6>
<p class="card-text small text-muted">Track sessions, time spent, and productivity trends across all your projects.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="bg-success bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-comments text-success"></i>
</div>
<div>
<h6 class="card-title">Conversation History</h6>
<p class="card-text small text-muted">Search and browse through all your Claude Code conversations with powerful filtering.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="bg-info bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-timeline text-info"></i>
</div>
<div>
<h6 class="card-title">Project Timelines</h6>
<p class="card-text small text-muted">Visualize your development journey with chronological project timelines.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="bg-warning bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-link text-warning"></i>
</div>
<div>
<h6 class="card-title">Hook Integration</h6>
<p class="card-text small text-muted">Automatically capture data through Claude Code hooks for real-time tracking.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="bg-secondary bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-database text-secondary"></i>
</div>
<div>
<h6 class="card-title">Data Import/Export</h6>
<p class="card-text small text-muted">Import existing data from Claude.json and export your analytics.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 col-lg-4 mb-4">
<div class="card border-0 bg-light h-100">
<div class="card-body">
<div class="d-flex align-items-start">
<div class="bg-danger bg-opacity-10 rounded p-2 me-3">
<i class="fas fa-shield-alt text-danger"></i>
</div>
<div>
<h6 class="card-title">Privacy First</h6>
<p class="card-text small text-muted">All data stays local - no external services or cloud dependencies.</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- External Links -->
<div class="row mt-5">
<div class="col-md-12">
<div class="card border-0 bg-primary text-white">
<div class="card-body">
<div class="row align-items-center">
<div class="col-md-8">
<h5 class="card-title mb-2">
<i class="fas fa-external-link-alt me-2"></i>
Additional Resources
</h5>
<p class="card-text mb-0">
Explore the interactive API documentation and source code
</p>
</div>
<div class="col-md-4 text-end">
<a href="/docs" target="_blank" class="btn btn-light me-2">
<i class="fas fa-book me-1"></i>
API Docs
</a>
<a href="https://github.com/claude/claude-tracker" target="_blank" class="btn btn-outline-light">
<i class="fab fa-github me-1"></i>
GitHub
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,505 @@
{% extends "base.html" %}
{% block title %}Import Data - Claude Code Project Tracker{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>
<i class="fas fa-download me-2"></i>
Import Claude Code Data
</h1>
</div>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h5 class="mb-0">
<i class="fas fa-file-import me-2"></i>
Import from .claude.json
</h5>
</div>
<div class="card-body">
<p class="text-muted">
Import your historical Claude Code usage data from the <code>~/.claude.json</code> file.
This will create projects and estimate sessions based on your past usage.
</p>
<!-- Import Method Tabs -->
<ul class="nav nav-pills mb-3" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="upload-tab" data-bs-toggle="pill"
data-bs-target="#upload-panel" type="button" role="tab">
<i class="fas fa-cloud-upload-alt me-1"></i>
Upload File
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="path-tab" data-bs-toggle="pill"
data-bs-target="#path-panel" type="button" role="tab">
<i class="fas fa-folder-open me-1"></i>
File Path
</button>
</li>
</ul>
<div class="tab-content">
<!-- Upload File Panel -->
<div class="tab-pane fade show active" id="upload-panel" role="tabpanel">
<div class="mb-3">
<label for="file-upload" class="form-label">Select .claude.json file</label>
<input type="file" class="form-control" id="file-upload"
accept=".json" onchange="handleFileSelect(event)">
<div class="form-text">
Upload your <code>.claude.json</code> file directly from your computer
</div>
</div>
<div id="file-info" class="alert alert-info" style="display: none;">
<div id="file-details"></div>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-primary" onclick="previewUpload()"
id="preview-upload-btn" disabled>
<i class="fas fa-eye me-1"></i>
Preview Upload
</button>
<button type="button" class="btn btn-primary" onclick="runUpload()"
id="import-upload-btn" disabled>
<i class="fas fa-cloud-upload-alt me-1"></i>
Import Upload
</button>
</div>
</div>
<!-- File Path Panel -->
<div class="tab-pane fade" id="path-panel" role="tabpanel">
<form id="import-form">
<div class="mb-3">
<label for="file-path" class="form-label">File Path (optional)</label>
<input type="text" class="form-control" id="file-path"
placeholder="Leave empty to use ~/.claude.json">
<div class="form-text">
If left empty, will try to import from the default location: <code>~/.claude.json</code>
</div>
</div>
<div class="d-flex gap-2">
<button type="button" class="btn btn-outline-primary" onclick="previewImport()">
<i class="fas fa-eye me-1"></i>
Preview Import
</button>
<button type="button" class="btn btn-primary" onclick="runImport()">
<i class="fas fa-download me-1"></i>
Import Data
</button>
</div>
</form>
</div>
</div>
<!-- Results -->
<div id="import-results" class="mt-4" style="display: none;">
<hr>
<div id="results-content"></div>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-info-circle me-2"></i>
What gets imported?
</h6>
</div>
<div class="card-body">
<ul class="list-unstyled">
<li class="mb-2">
<i class="fas fa-folder text-primary me-2"></i>
<strong>Projects</strong><br>
<small class="text-muted">All project directories you've used with Claude Code</small>
</li>
<li class="mb-2">
<i class="fas fa-chart-line text-success me-2"></i>
<strong>Usage Statistics</strong><br>
<small class="text-muted">Total startups and estimated session distribution</small>
</li>
<li class="mb-2">
<i class="fas fa-comments text-info me-2"></i>
<strong>History Entries</strong><br>
<small class="text-muted">Brief conversation topics from your history</small>
</li>
<li class="mb-2">
<i class="fas fa-code text-warning me-2"></i>
<strong>Language Detection</strong><br>
<small class="text-muted">Automatically detect programming languages used</small>
</li>
</ul>
<div class="alert alert-info">
<i class="fas fa-shield-alt me-1"></i>
<strong>Privacy:</strong> All data stays local. No external services are used.
</div>
</div>
</div>
<div class="card mt-3">
<div class="card-header">
<h6 class="mb-0">
<i class="fas fa-exclamation-triangle me-2"></i>
Important Notes
</h6>
</div>
<div class="card-body">
<ul class="small text-muted">
<li>Import creates estimated data based on available information</li>
<li>Timestamps are estimated based on usage patterns</li>
<li>This is safe to run multiple times (won't create duplicates)</li>
<li>Large files may take a few moments to process</li>
</ul>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
let selectedFile = null;
function handleFileSelect(event) {
const file = event.target.files[0];
selectedFile = file;
if (file) {
// Validate file type
if (!file.name.endsWith('.json')) {
showError('Please select a JSON file (.json)');
resetFileUpload();
return;
}
// Check file size (10MB limit)
const maxSize = 10 * 1024 * 1024; // 10MB
if (file.size > maxSize) {
showError('File too large. Maximum size is 10MB.');
resetFileUpload();
return;
}
// Show file info
const fileInfo = document.getElementById('file-info');
const fileDetails = document.getElementById('file-details');
fileDetails.innerHTML = `
<strong>Selected file:</strong> ${file.name}<br>
<strong>Size:</strong> ${(file.size / 1024).toFixed(1)} KB<br>
<strong>Modified:</strong> ${new Date(file.lastModified).toLocaleString()}
`;
fileInfo.style.display = 'block';
// Enable buttons
document.getElementById('preview-upload-btn').disabled = false;
document.getElementById('import-upload-btn').disabled = false;
} else {
resetFileUpload();
}
}
function resetFileUpload() {
selectedFile = null;
document.getElementById('file-info').style.display = 'none';
document.getElementById('preview-upload-btn').disabled = true;
document.getElementById('import-upload-btn').disabled = true;
}
async function previewUpload() {
if (!selectedFile) {
showError('Please select a file first');
return;
}
const resultsDiv = document.getElementById('import-results');
const resultsContent = document.getElementById('results-content');
// Show loading
resultsContent.innerHTML = `
<div class="text-center py-3">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Previewing...</span>
</div>
<p class="mt-2 text-muted">Analyzing uploaded file...</p>
</div>
`;
resultsDiv.style.display = 'block';
try {
const formData = new FormData();
formData.append('file', selectedFile);
const response = await fetch('/api/import/claude-json/preview-upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
showPreviewResults(data, true); // Pass true to indicate this is an upload
} else {
showError(data.detail || 'Preview failed');
}
} catch (error) {
showError('Network error: ' + error.message);
}
}
async function runUpload() {
if (!selectedFile) {
showError('Please select a file first');
return;
}
// Confirm import
if (!confirm('This will import data into your tracker database. Continue?')) {
return;
}
const resultsDiv = document.getElementById('import-results');
const resultsContent = document.getElementById('results-content');
// Show loading
resultsContent.innerHTML = `
<div class="text-center py-3">
<div class="spinner-border text-success" role="status">
<span class="visually-hidden">Importing...</span>
</div>
<p class="mt-2 text-muted">Importing uploaded file...</p>
</div>
`;
resultsDiv.style.display = 'block';
try {
const formData = new FormData();
formData.append('file', selectedFile);
const response = await fetch('/api/import/claude-json/upload', {
method: 'POST',
body: formData
});
const data = await response.json();
if (response.ok) {
showImportResults(data, true); // Pass true to indicate this is an upload
} else {
showError(data.detail || 'Import failed');
}
} catch (error) {
showError('Network error: ' + error.message);
}
}
async function previewImport() {
const filePath = document.getElementById('file-path').value.trim();
const resultsDiv = document.getElementById('import-results');
const resultsContent = document.getElementById('results-content');
// Show loading
resultsContent.innerHTML = `
<div class="text-center py-3">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Previewing...</span>
</div>
<p class="mt-2 text-muted">Analyzing .claude.json file...</p>
</div>
`;
resultsDiv.style.display = 'block';
try {
const params = new URLSearchParams();
if (filePath) {
params.append('file_path', filePath);
}
const response = await fetch(`/api/import/claude-json/preview?${params}`);
const data = await response.json();
if (response.ok) {
showPreviewResults(data);
} else {
showError(data.detail || 'Preview failed');
}
} catch (error) {
showError('Network error: ' + error.message);
}
}
async function runImport() {
const filePath = document.getElementById('file-path').value.trim();
const resultsDiv = document.getElementById('import-results');
const resultsContent = document.getElementById('results-content');
// Confirm import
if (!confirm('This will import data into your tracker database. Continue?')) {
return;
}
// Show loading
resultsContent.innerHTML = `
<div class="text-center py-3">
<div class="spinner-border text-success" role="status">
<span class="visually-hidden">Importing...</span>
</div>
<p class="mt-2 text-muted">Importing Claude Code data...</p>
</div>
`;
resultsDiv.style.display = 'block';
try {
const requestBody = {};
if (filePath) {
requestBody.file_path = filePath;
}
const response = await fetch('/api/import/claude-json', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(requestBody)
});
const data = await response.json();
if (response.ok) {
showImportResults(data);
} else {
showError(data.detail || 'Import failed');
}
} catch (error) {
showError('Network error: ' + error.message);
}
}
function showPreviewResults(data, isUpload = false) {
const html = `
<div class="alert alert-info">
<h6><i class="fas fa-eye me-1"></i> Import Preview</h6>
<hr>
<div class="row">
<div class="col-md-6">
<p><strong>File:</strong> <code>${isUpload ? data.file_name : data.file_path}</code></p>
<p><strong>Size:</strong> ${data.file_size_mb} MB</p>
</div>
<div class="col-md-6">
<p><strong>Projects:</strong> ${data.projects.total_count}</p>
<p><strong>History entries:</strong> ${data.history_entries}</p>
</div>
</div>
<div class="mt-3">
<strong>Claude Usage Statistics:</strong>
<ul class="mt-2">
<li>Total startups: ${data.claude_usage.num_startups}</li>
<li>First start: ${data.claude_usage.first_start_time || 'N/A'}</li>
<li>Prompt queue uses: ${data.claude_usage.prompt_queue_use_count}</li>
</ul>
</div>
${data.projects.paths.length > 0 ? `
<div class="mt-3">
<strong>Sample projects:</strong>
<ul class="mt-2">
${data.projects.paths.map(path => `<li><code>${path}</code></li>`).join('')}
${data.projects.has_more ? '<li><em>... and more</em></li>' : ''}
</ul>
</div>
` : ''}
</div>
<div class="text-center">
<button class="btn btn-success" onclick="${isUpload ? 'runUpload()' : 'runImport()'}">
<i class="fas fa-check me-1"></i>
Looks good - Import this data
</button>
</div>
`;
document.getElementById('results-content').innerHTML = html;
}
function showImportResults(data, isUpload = false) {
const results = data.results;
const hasErrors = results.errors && results.errors.length > 0;
let successMessage = '<h6><i class="fas fa-check me-1"></i> Import Completed Successfully!</h6>';
if (isUpload) {
successMessage += `<p class="mb-0"><strong>File:</strong> ${data.file_name} (${data.file_size_kb} KB)</p>`;
}
const html = `
<div class="alert alert-success">
${successMessage}
<hr>
<div class="row">
<div class="col-md-4 text-center">
<div class="display-6 text-primary">${results.projects_imported}</div>
<small>Projects</small>
</div>
<div class="col-md-4 text-center">
<div class="display-6 text-success">${results.sessions_estimated}</div>
<small>Sessions</small>
</div>
<div class="col-md-4 text-center">
<div class="display-6 text-info">${results.conversations_imported}</div>
<small>Conversations</small>
</div>
</div>
</div>
${hasErrors ? `
<div class="alert alert-warning">
<h6><i class="fas fa-exclamation-triangle me-1"></i> Warnings</h6>
<ul class="mb-0">
${results.errors.map(error => `<li>${error}</li>`).join('')}
</ul>
</div>
` : ''}
<div class="text-center">
<a href="/dashboard" class="btn btn-primary">
<i class="fas fa-tachometer-alt me-1"></i>
View Dashboard
</a>
<a href="/dashboard/projects" class="btn btn-outline-primary ms-2">
<i class="fas fa-folder me-1"></i>
View Projects
</a>
</div>
`;
document.getElementById('results-content').innerHTML = html;
}
function showError(message) {
const html = `
<div class="alert alert-danger">
<h6><i class="fas fa-exclamation-circle me-1"></i> Error</h6>
<p class="mb-0">${message}</p>
</div>
`;
document.getElementById('results-content').innerHTML = html;
}
</script>
{% endblock %}

View File

@ -0,0 +1,353 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Project Statistics</h2>
<a href="/dashboard/projects" class="btn btn-outline-secondary">← Back to Projects</a>
</div>
<div id="project-info" class="card mb-4">
<div class="card-body">
<h5 class="card-title">Loading project information...</h5>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Time Period</h5>
</div>
<div class="card-body">
<select id="dayRange" class="form-select">
<option value="7">Last 7 days</option>
<option value="30" selected>Last 30 days</option>
<option value="90">Last 90 days</option>
<option value="365">Last year</option>
<option value="0">All time</option>
</select>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Summary</h5>
</div>
<div class="card-body" id="summary-stats">
Loading...
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Session Statistics</h5>
</div>
<div class="card-body" id="session-stats">
Loading...
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Activity Breakdown</h5>
</div>
<div class="card-body" id="activity-stats">
Loading...
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Tool Usage</h5>
</div>
<div class="card-body">
<canvas id="toolsChart"></canvas>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h5>Language Usage</h5>
</div>
<div class="card-body">
<canvas id="languagesChart"></canvas>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mb-4">
<div class="card">
<div class="card-header">
<h5>Productivity Trends</h5>
</div>
<div class="card-body">
<canvas id="trendsChart"></canvas>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script>
const projectId = {{ project_id }};
let statsData = {};
let charts = {};
async function loadProjectStats() {
try {
const days = document.getElementById('dayRange').value;
const response = await fetch(`/api/projects/${projectId}/stats?days=${days}`);
const data = await response.json();
if (response.ok) {
statsData = data;
displayStats();
updateCharts();
} else {
throw new Error(data.detail || 'Failed to load stats');
}
} catch (error) {
console.error('Error loading stats:', error);
document.getElementById('summary-stats').innerHTML = `
<div class="alert alert-danger">
Failed to load statistics: ${error.message}
</div>
`;
}
}
async function loadProjectInfo() {
try {
const response = await fetch(`/api/projects/${projectId}`);
const project = await response.json();
if (response.ok) {
document.getElementById('project-info').innerHTML = `
<div class="card-body">
<h5 class="card-title">${project.name}</h5>
<p class="card-text">
<strong>Path:</strong> ${project.path}<br>
<strong>Git Repository:</strong> ${project.git_repo || 'Not a git repo'}<br>
<strong>Languages:</strong> ${project.languages ? project.languages.join(', ') : 'Unknown'}
</p>
</div>
`;
}
} catch (error) {
console.error('Error loading project info:', error);
}
}
function displayStats() {
// Summary stats
document.getElementById('summary-stats').innerHTML = `
<div class="row text-center">
<div class="col-md-3">
<h4 class="text-primary">${statsData.session_statistics.total_sessions}</h4>
<small>Sessions</small>
</div>
<div class="col-md-3">
<h4 class="text-success">${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10}h</h4>
<small>Total Time</small>
</div>
<div class="col-md-3">
<h4 class="text-warning">${statsData.activity_statistics.total}</h4>
<small>Activities</small>
</div>
<div class="col-md-3">
<h4 class="text-info">${statsData.conversation_statistics.total}</h4>
<small>Conversations</small>
</div>
</div>
`;
// Session stats
document.getElementById('session-stats').innerHTML = `
<p><strong>Total Sessions:</strong> ${statsData.session_statistics.total_sessions}</p>
<p><strong>Total Time:</strong> ${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10} hours</p>
<p><strong>Average Session:</strong> ${statsData.session_statistics.average_session_length_minutes} minutes</p>
<p><strong>Daily Average Time:</strong> ${statsData.summary.daily_average_time} minutes</p>
<p><strong>Daily Average Sessions:</strong> ${statsData.summary.daily_average_sessions}</p>
`;
// Activity stats
const topTools = Object.entries(statsData.activity_statistics.by_tool)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([tool, count]) => `<li>${tool}: ${count}</li>`)
.join('');
const topLangs = Object.entries(statsData.activity_statistics.by_language)
.sort((a, b) => b[1] - a[1])
.slice(0, 5)
.map(([lang, count]) => `<li>${lang}: ${count}</li>`)
.join('');
document.getElementById('activity-stats').innerHTML = `
<p><strong>Total Activities:</strong> ${statsData.activity_statistics.total}</p>
<p><strong>Most Used Tool:</strong> ${statsData.summary.most_used_tool || 'None'}</p>
<p><strong>Primary Language:</strong> ${statsData.summary.primary_language || 'None'}</p>
<div class="row mt-3">
<div class="col-md-6">
<strong>Top Tools:</strong>
<ul class="small">${topTools || '<li>No data</li>'}</ul>
</div>
<div class="col-md-6">
<strong>Languages:</strong>
<ul class="small">${topLangs || '<li>No data</li>'}</ul>
</div>
</div>
`;
}
function updateCharts() {
// Destroy existing charts
Object.values(charts).forEach(chart => chart.destroy());
charts = {};
// Tools chart
const toolData = Object.entries(statsData.activity_statistics.by_tool)
.sort((a, b) => b[1] - a[1])
.slice(0, 10);
if (toolData.length > 0) {
charts.tools = new Chart(document.getElementById('toolsChart'), {
type: 'doughnut',
data: {
labels: toolData.map(([tool]) => tool),
datasets: [{
data: toolData.map(([, count]) => count),
backgroundColor: [
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
'#FF9F40', '#C9CBCF', '#4BC0C0', '#FF6384', '#36A2EB'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: true
}
});
}
// Languages chart
const langData = Object.entries(statsData.activity_statistics.by_language)
.sort((a, b) => b[1] - a[1]);
if (langData.length > 0) {
charts.languages = new Chart(document.getElementById('languagesChart'), {
type: 'bar',
data: {
labels: langData.map(([lang]) => lang),
datasets: [{
label: 'Activities',
data: langData.map(([, count]) => count),
backgroundColor: '#36A2EB'
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// Trends chart
if (statsData.productivity_trends && statsData.productivity_trends.length > 0) {
charts.trends = new Chart(document.getElementById('trendsChart'), {
type: 'line',
data: {
labels: statsData.productivity_trends.map(day => day.date),
datasets: [
{
label: 'Sessions',
data: statsData.productivity_trends.map(day => day.sessions),
borderColor: '#FF6384',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
yAxisID: 'y'
},
{
label: 'Time (minutes)',
data: statsData.productivity_trends.map(day => day.time_minutes),
borderColor: '#36A2EB',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: true,
interaction: {
mode: 'index',
intersect: false,
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Date'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
title: {
display: true,
text: 'Sessions'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
title: {
display: true,
text: 'Time (minutes)'
},
grid: {
drawOnChartArea: false,
},
}
}
}
});
}
}
// Event listeners
document.getElementById('dayRange').addEventListener('change', loadProjectStats);
// Load initial data
loadProjectInfo();
loadProjectStats();
</script>
{% endblock %}

View File

@ -0,0 +1,214 @@
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-md-12">
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>Project Timeline</h2>
<a href="/dashboard/projects" class="btn btn-outline-secondary">← Back to Projects</a>
</div>
<div id="project-info" class="card mb-4">
<div class="card-body">
<h5 class="card-title">Loading project information...</h5>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Timeline Events</h5>
<div class="row mt-3">
<div class="col-md-6">
<input type="date" id="startDate" class="form-control" placeholder="Start Date">
</div>
<div class="col-md-6">
<input type="date" id="endDate" class="form-control" placeholder="End Date">
</div>
</div>
</div>
<div class="card-body">
<div id="timeline-container" class="timeline">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const projectId = {{ project_id }};
let timelineData = [];
async function loadProjectTimeline() {
try {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
let url = `/api/projects/${projectId}/timeline`;
const params = new URLSearchParams();
if (startDate) params.append('start_date', startDate);
if (endDate) params.append('end_date', endDate);
if (params.toString()) url += '?' + params.toString();
const response = await fetch(url);
const data = await response.json();
if (response.ok) {
timelineData = data;
displayProjectInfo(data.project);
displayTimeline(data.timeline);
} else {
throw new Error(data.detail || 'Failed to load timeline');
}
} catch (error) {
console.error('Error loading timeline:', error);
document.getElementById('timeline-container').innerHTML = `
<div class="alert alert-danger">
Failed to load timeline: ${error.message}
</div>
`;
}
}
function displayProjectInfo(project) {
document.getElementById('project-info').innerHTML = `
<div class="card-body">
<h5 class="card-title">${project.name}</h5>
<p class="card-text">
<strong>Path:</strong> ${project.path}<br>
<strong>Git Repository:</strong> ${project.git_repo || 'Not a git repo'}<br>
<strong>Languages:</strong> ${project.languages ? project.languages.join(', ') : 'Unknown'}<br>
<strong>Total Sessions:</strong> ${project.total_sessions}<br>
<strong>Total Time:</strong> ${project.total_time_minutes} minutes<br>
<strong>Last Activity:</strong> ${new Date(project.last_activity).toLocaleString()}
</p>
</div>
`;
}
function displayTimeline(timeline) {
if (!timeline || timeline.length === 0) {
document.getElementById('timeline-container').innerHTML = `
<div class="alert alert-info">No timeline events found for the selected period.</div>
`;
return;
}
const timelineHtml = timeline.map(event => {
const timestamp = new Date(event.timestamp).toLocaleString();
const typeClass = getEventTypeClass(event.type);
const icon = getEventIcon(event.type);
return `
<div class="timeline-item mb-3">
<div class="row">
<div class="col-md-2 text-end">
<small class="text-muted">${timestamp}</small>
</div>
<div class="col-md-1 text-center">
<span class="badge ${typeClass}">${icon}</span>
</div>
<div class="col-md-9">
<div class="card border-start border-3 border-${typeClass.replace('bg-', '')}">
<div class="card-body py-2">
<h6 class="card-title mb-1">${formatEventTitle(event)}</h6>
<p class="card-text small text-muted mb-0">${formatEventDetails(event)}</p>
</div>
</div>
</div>
</div>
</div>
`;
}).join('');
document.getElementById('timeline-container').innerHTML = timelineHtml;
}
function getEventTypeClass(type) {
const classes = {
'session_start': 'bg-success',
'session_end': 'bg-secondary',
'conversation': 'bg-primary',
'activity': 'bg-warning',
'git_operation': 'bg-info'
};
return classes[type] || 'bg-light';
}
function getEventIcon(type) {
const icons = {
'session_start': '▶️',
'session_end': '⏹️',
'conversation': '💬',
'activity': '🔧',
'git_operation': '📝'
};
return icons[type] || '📌';
}
function formatEventTitle(event) {
switch (event.type) {
case 'session_start':
return `Session Started (${event.data.session_type || 'unknown'})`;
case 'session_end':
return `Session Ended (${event.data.duration_minutes || 0}m)`;
case 'conversation':
return `Conversation: ${event.data.exchange_type || 'unknown'}`;
case 'activity':
return `${event.data.tool_name}: ${event.data.action}`;
case 'git_operation':
return `Git: ${event.data.operation}`;
default:
return event.type;
}
}
function formatEventDetails(event) {
switch (event.type) {
case 'session_start':
return `Branch: ${event.data.git_branch || 'unknown'}, Directory: ${event.data.working_directory || 'unknown'}`;
case 'session_end':
return `Activities: ${event.data.activity_count || 0}, Conversations: ${event.data.conversation_count || 0}`;
case 'conversation':
return event.data.user_prompt || 'No prompt available';
case 'activity':
return `File: ${event.data.file_path || 'unknown'}, Lines: ${event.data.lines_changed || 0}, Success: ${event.data.success}`;
case 'git_operation':
return `${event.data.commit_message || 'No message'} (${event.data.files_changed || 0} files, ${event.data.lines_changed || 0} lines)`;
default:
return JSON.stringify(event.data);
}
}
// Event listeners
document.getElementById('startDate').addEventListener('change', loadProjectTimeline);
document.getElementById('endDate').addEventListener('change', loadProjectTimeline);
// Load initial data
loadProjectTimeline();
</script>
<style>
.timeline-item {
position: relative;
}
.timeline {
padding-left: 1rem;
}
.border-success { border-color: #198754 !important; }
.border-secondary { border-color: #6c757d !important; }
.border-primary { border-color: #0d6efd !important; }
.border-warning { border-color: #ffc107 !important; }
.border-info { border-color: #0dcaf0 !important; }
</style>
{% endblock %}

View File

@ -56,9 +56,16 @@ async def init_database():
# Import all models to ensure they're registered # Import all models to ensure they're registered
from app.models import ( from app.models import (
Project, Session, Conversation, Activity, Project, Session, Conversation, Activity,
WaitingPeriod, GitOperation WaitingPeriod, GitOperation, ToolCall,
HookEvent, ToolError, WaitingPeriodNew, PerformanceMetric,
CodeQualityEvent, WorkflowEvent, LearningEvent,
EnvironmentEvent, CollaborationEvent, ProjectIntelligence
) )
# Initialize hook relationships
from app.models.hooks import add_hook_relationships
add_hook_relationships()
# Create all tables # Create all tables
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)

View File

@ -9,6 +9,12 @@ from .conversation import Conversation
from .activity import Activity from .activity import Activity
from .waiting_period import WaitingPeriod from .waiting_period import WaitingPeriod
from .git_operation import GitOperation from .git_operation import GitOperation
from .tool_call import ToolCall
from .hooks import (
HookEvent, ToolError, WaitingPeriodNew, PerformanceMetric,
CodeQualityEvent, WorkflowEvent, LearningEvent,
EnvironmentEvent, CollaborationEvent, ProjectIntelligence
)
__all__ = [ __all__ = [
"Base", "Base",
@ -18,4 +24,15 @@ __all__ = [
"Activity", "Activity",
"WaitingPeriod", "WaitingPeriod",
"GitOperation", "GitOperation",
"ToolCall",
"HookEvent",
"ToolError",
"WaitingPeriodNew",
"PerformanceMetric",
"CodeQualityEvent",
"WorkflowEvent",
"LearningEvent",
"EnvironmentEvent",
"CollaborationEvent",
"ProjectIntelligence",
] ]

View File

@ -46,7 +46,7 @@ class Activity(Base, TimestampMixin):
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True) file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
# Metadata and results # Metadata and results
metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True) activity_metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True)
success: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) success: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True) error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
@ -145,18 +145,18 @@ class Activity(Base, TimestampMixin):
def get_command_executed(self) -> Optional[str]: def get_command_executed(self) -> Optional[str]:
"""Get the command that was executed (for Bash activities).""" """Get the command that was executed (for Bash activities)."""
if self.tool_name == "Bash" and self.metadata: if self.tool_name == "Bash" and self.activity_metadata:
return self.metadata.get("command") return self.activity_metadata.get("command")
return None return None
def get_search_pattern(self) -> Optional[str]: def get_search_pattern(self) -> Optional[str]:
"""Get the search pattern (for Grep activities).""" """Get the search pattern (for Grep activities)."""
if self.tool_name == "Grep" and self.metadata: if self.tool_name == "Grep" and self.activity_metadata:
return self.metadata.get("pattern") return self.activity_metadata.get("pattern")
return None return None
def get_task_type(self) -> Optional[str]: def get_task_type(self) -> Optional[str]:
"""Get the task type (for Task activities).""" """Get the task type (for Task activities)."""
if self.tool_name == "Task" and self.metadata: if self.tool_name == "Task" and self.activity_metadata:
return self.metadata.get("task_type") return self.activity_metadata.get("task_type")
return None return None

225
app/models/hooks.py Normal file
View File

@ -0,0 +1,225 @@
"""
Comprehensive hook tracking models for Claude Code sessions.
"""
from datetime import datetime
from typing import Optional, Dict, Any
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean, Float, JSON
from sqlalchemy.orm import relationship
from app.models.base import Base
class HookEvent(Base):
"""
Base model for all hook events with common fields.
"""
__tablename__ = "hook_events"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
hook_type = Column(String(50), nullable=False, index=True)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
data = Column(JSON, nullable=True) # Flexible JSON data for hook-specific information
# Relationships
session = relationship("Session", back_populates="hook_events")
def __repr__(self):
return f"<HookEvent(id={self.id}, type={self.hook_type}, session={self.session_id})>"
class ToolError(Base):
"""Track failed tool calls with error details."""
__tablename__ = "tool_errors"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
tool_name = Column(String(100), nullable=False, index=True)
error_type = Column(String(100), nullable=False, index=True)
error_message = Column(Text, nullable=False)
stack_trace = Column(Text, nullable=True)
parameters = Column(JSON, nullable=True)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<ToolError(id={self.id}, tool={self.tool_name}, error={self.error_type})>"
class WaitingPeriodNew(Base):
"""Track periods when Claude is thinking vs actively working."""
__tablename__ = "waiting_periods_new"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
start_time = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
end_time = Column(DateTime, nullable=True)
duration_ms = Column(Integer, nullable=True)
reason = Column(String(100), nullable=True, index=True) # thinking, processing, waiting_for_input
context = Column(Text, nullable=True) # What was happening when waiting started
session = relationship("Session")
def __repr__(self):
return f"<WaitingPeriodNew(id={self.id}, reason={self.reason}, duration={self.duration_ms}ms)>"
class PerformanceMetric(Base):
"""Monitor system performance during development."""
__tablename__ = "performance_metrics"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
metric_type = Column(String(50), nullable=False, index=True) # memory, cpu, disk
value = Column(Float, nullable=False)
unit = Column(String(20), nullable=False) # MB, %, seconds
threshold_exceeded = Column(Boolean, default=False)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<PerformanceMetric(id={self.id}, type={self.metric_type}, value={self.value}{self.unit})>"
class CodeQualityEvent(Base):
"""Track code quality analysis, linting, and testing."""
__tablename__ = "code_quality_events"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
event_type = Column(String(50), nullable=False, index=True) # lint, format, test, build, analysis
file_path = Column(Text, nullable=True)
tool_name = Column(String(100), nullable=True) # eslint, prettier, pytest, etc.
status = Column(String(20), nullable=False, index=True) # success, warning, error
issues_count = Column(Integer, default=0)
details = Column(JSON, nullable=True) # Specific issues, test results, etc.
duration_ms = Column(Integer, nullable=True)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<CodeQualityEvent(id={self.id}, type={self.event_type}, status={self.status})>"
class WorkflowEvent(Base):
"""Track development workflow patterns."""
__tablename__ = "workflow_events"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
event_type = Column(String(50), nullable=False, index=True) # context_switch, search_query, browser_tab, etc.
description = Column(Text, nullable=True)
event_metadata = Column(JSON, nullable=True) # Event-specific data
source = Column(String(100), nullable=True) # Where the event came from
duration_ms = Column(Integer, nullable=True) # For events with duration
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<WorkflowEvent(id={self.id}, type={self.event_type})>"
class LearningEvent(Base):
"""Track learning sessions and knowledge acquisition."""
__tablename__ = "learning_events"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
event_type = Column(String(50), nullable=False, index=True) # tutorial, documentation, experimentation
topic = Column(String(200), nullable=True, index=True) # What was being learned
resource_url = Column(Text, nullable=True) # Documentation URL, tutorial link
confidence_before = Column(Integer, nullable=True) # 1-10 scale
confidence_after = Column(Integer, nullable=True) # 1-10 scale
notes = Column(Text, nullable=True)
duration_ms = Column(Integer, nullable=True)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<LearningEvent(id={self.id}, topic={self.topic})>"
class EnvironmentEvent(Base):
"""Track environment and configuration changes."""
__tablename__ = "environment_events"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
event_type = Column(String(50), nullable=False, index=True) # env_change, config_update, security_scan
environment = Column(String(50), nullable=True, index=True) # dev, staging, prod
config_file = Column(Text, nullable=True)
changes = Column(JSON, nullable=True) # What changed
impact_level = Column(String(20), nullable=True) # low, medium, high, critical
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<EnvironmentEvent(id={self.id}, type={self.event_type}, env={self.environment})>"
class CollaborationEvent(Base):
"""Track collaboration and external interactions."""
__tablename__ = "collaboration_events"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
event_type = Column(String(50), nullable=False, index=True) # external_resource, ai_question, review_request
interaction_type = Column(String(50), nullable=True) # documentation, stackoverflow, api_docs
query_or_topic = Column(Text, nullable=True)
resource_url = Column(Text, nullable=True)
response_quality = Column(Integer, nullable=True) # 1-5 rating
time_to_resolution = Column(Integer, nullable=True) # minutes
event_metadata = Column(JSON, nullable=True)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<CollaborationEvent(id={self.id}, type={self.event_type})>"
class ProjectIntelligence(Base):
"""Track high-level project development patterns."""
__tablename__ = "project_intelligence"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
event_type = Column(String(50), nullable=False, index=True) # refactor, feature_flag, debugging_session
scope = Column(String(20), nullable=True, index=True) # small, medium, large
complexity = Column(String(20), nullable=True, index=True) # low, medium, high
start_time = Column(DateTime, nullable=False, default=datetime.utcnow)
end_time = Column(DateTime, nullable=True)
duration_minutes = Column(Integer, nullable=True)
files_affected = Column(JSON, nullable=True) # List of file paths
outcome = Column(String(20), nullable=True) # success, partial, failed, abandoned
notes = Column(Text, nullable=True)
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
session = relationship("Session")
def __repr__(self):
return f"<ProjectIntelligence(id={self.id}, type={self.event_type}, scope={self.scope})>"
# Add hook_events relationship to Session model
def add_hook_relationships():
"""Add hook event relationships to the Session model."""
from app.models.session import Session
Session.hook_events = relationship("HookEvent", back_populates="session", cascade="all, delete-orphan")
Session.tool_errors = relationship("ToolError", cascade="all, delete-orphan")
Session.waiting_periods_new = relationship("WaitingPeriodNew", cascade="all, delete-orphan")
Session.performance_metrics = relationship("PerformanceMetric", cascade="all, delete-orphan")
Session.code_quality_events = relationship("CodeQualityEvent", cascade="all, delete-orphan")
Session.workflow_events = relationship("WorkflowEvent", cascade="all, delete-orphan")
Session.learning_events = relationship("LearningEvent", cascade="all, delete-orphan")
Session.environment_events = relationship("EnvironmentEvent", cascade="all, delete-orphan")
Session.collaboration_events = relationship("CollaborationEvent", cascade="all, delete-orphan")
Session.project_intelligence = relationship("ProjectIntelligence", cascade="all, delete-orphan")

View File

@ -36,6 +36,7 @@ class Project(Base, TimestampMixin):
total_time_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False) total_time_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
files_modified_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) files_modified_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
lines_changed_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) lines_changed_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
tool_calls_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
# Relationships # Relationships
sessions: Mapped[List["Session"]] = relationship( sessions: Mapped[List["Session"]] = relationship(
@ -60,10 +61,11 @@ class Project(Base, TimestampMixin):
return self.languages[0] return self.languages[0]
return None return None
def update_stats(self, session_duration_minutes: int, files_count: int, lines_count: int) -> None: def update_stats(self, session_duration_minutes: int, files_count: int, lines_count: int, tool_calls_count: int = 0) -> None:
"""Update project statistics after a session.""" """Update project statistics after a session."""
self.total_sessions += 1 self.total_sessions += 1
self.total_time_minutes += session_duration_minutes self.total_time_minutes += session_duration_minutes
self.files_modified_count += files_count self.files_modified_count += files_count
self.lines_changed_count += lines_count self.lines_changed_count += lines_count
self.tool_calls_count += tool_calls_count
self.last_session = func.now() self.last_session = func.now()

View File

@ -74,6 +74,12 @@ class Session(Base, TimestampMixin):
cascade="all, delete-orphan", cascade="all, delete-orphan",
order_by="GitOperation.timestamp" order_by="GitOperation.timestamp"
) )
tool_calls: Mapped[List["ToolCall"]] = relationship(
"ToolCall",
back_populates="session",
cascade="all, delete-orphan",
order_by="ToolCall.timestamp"
)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"<Session(id={self.id}, project_id={self.project_id}, type='{self.session_type}')>" return f"<Session(id={self.id}, project_id={self.project_id}, type='{self.session_type}')>"
@ -111,10 +117,12 @@ class Session(Base, TimestampMixin):
(activity.lines_added or 0) + (activity.lines_removed or 0) (activity.lines_added or 0) + (activity.lines_removed or 0)
for activity in self.activities for activity in self.activities
) )
total_tool_calls = len(self.tool_calls)
self.project.update_stats( self.project.update_stats(
session_duration_minutes=self.duration_minutes or 0, session_duration_minutes=self.duration_minutes or 0,
files_count=unique_files, files_count=unique_files,
lines_count=total_lines lines_count=total_lines,
tool_calls_count=total_tool_calls
) )
def add_activity(self) -> None: def add_activity(self) -> None:

33
app/models/tool_call.py Normal file
View File

@ -0,0 +1,33 @@
"""
Tool call tracking model for Claude Code sessions.
"""
from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import relationship
from app.models.base import Base
class ToolCall(Base):
"""
Tracks individual tool calls made during Claude Code sessions.
This helps analyze which tools are used most frequently and their success rates.
"""
__tablename__ = "tool_calls"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
tool_name = Column(String(100), nullable=False, index=True)
parameters = Column(Text, nullable=True) # JSON string of tool parameters
result_status = Column(String(20), nullable=True, index=True) # success, error, timeout
error_message = Column(Text, nullable=True)
execution_time_ms = Column(Integer, nullable=True) # Tool execution time in milliseconds
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
# Relationships
session = relationship("Session", back_populates="tool_calls")
def __repr__(self):
return f"<ToolCall(id={self.id}, tool={self.tool_name}, session={self.session_id})>"

9
claude-hooks-basic.json Normal file
View File

@ -0,0 +1,9 @@
{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\"}'"
}
}

View File

@ -0,0 +1,50 @@
{
"hooks": {
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\", \"parameters\": $TOOL_PARAMS}'",
"waiting_period_start": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'",
"waiting_period_end": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'",
"memory_usage": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'",
"large_file_warning": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"file_size\", \"value\": $FILE_SIZE_MB, \"unit\": \"MB\", \"threshold_exceeded\": true}'",
"code_analysis": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"analysis\", \"file_path\": \"$FILE_PATH\", \"tool_name\": \"$ANALYSIS_TOOL\", \"status\": \"$STATUS\", \"issues_count\": $ISSUES_COUNT, \"details\": $DETAILS}'",
"test_execution": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"test\", \"tool_name\": \"$TEST_FRAMEWORK\", \"status\": \"$STATUS\", \"details\": {\"passed\": $PASSED_COUNT, \"failed\": $FAILED_COUNT}, \"duration_ms\": $DURATION_MS}'",
"build_process": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"build\", \"tool_name\": \"$BUILD_TOOL\", \"status\": \"$STATUS\", \"duration_ms\": $DURATION_MS}'",
"dependency_change": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"dependency_change\", \"config_file\": \"$PACKAGE_FILE\", \"changes\": {\"action\": \"$ACTION\", \"package\": \"$PACKAGE_NAME\", \"version\": \"$VERSION\"}}'",
"context_switch": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"context_switch\", \"description\": \"Switched to $NEW_PROJECT\", \"metadata\": {\"from\": \"$OLD_PROJECT\", \"to\": \"$NEW_PROJECT\"}}'",
"search_query": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"search_query\", \"description\": \"$SEARCH_QUERY\", \"metadata\": {\"search_type\": \"$SEARCH_TYPE\", \"context\": \"$CONTEXT\"}}'",
"browser_tab": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"browser_tab\", \"description\": \"$TAB_TITLE\", \"metadata\": {\"url\": \"$TAB_URL\", \"category\": \"$TAB_CATEGORY\"}}'",
"copy_paste": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"copy_paste\", \"description\": \"Code copied/pasted\", \"metadata\": {\"action\": \"$ACTION\", \"source\": \"$SOURCE\", \"lines\": $LINE_COUNT}}'",
"external_resource": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"external_resource\", \"interaction_type\": \"$RESOURCE_TYPE\", \"resource_url\": \"$URL\", \"query_or_topic\": \"$TOPIC\"}'",
"ai_question": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"ai_question\", \"query_or_topic\": \"$QUESTION\", \"metadata\": {\"question_type\": \"$QUESTION_TYPE\", \"complexity\": \"$COMPLEXITY\"}}'",
"code_explanation": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"code_explanation\", \"query_or_topic\": \"$CODE_CONTEXT\", \"metadata\": {\"explanation_type\": \"$TYPE\", \"code_lines\": $CODE_LINES}}'",
"review_request": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"review_request\", \"query_or_topic\": \"$REVIEW_SCOPE\", \"metadata\": {\"files_count\": $FILES_COUNT, \"review_type\": \"$REVIEW_TYPE\"}}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'",
"environment_change": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"env_change\", \"environment\": \"$NEW_ENV\", \"impact_level\": \"$IMPACT_LEVEL\"}'",
"config_update": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"config_update\", \"config_file\": \"$CONFIG_FILE\", \"changes\": $CONFIG_CHANGES, \"impact_level\": \"$IMPACT_LEVEL\"}'",
"security_scan": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"security_scan\", \"changes\": {\"tool\": \"$SCAN_TOOL\", \"vulnerabilities\": $VULN_COUNT, \"severity\": \"$MAX_SEVERITY\"}}'",
"performance_benchmark": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"performance_benchmark\", \"changes\": {\"benchmark_type\": \"$BENCHMARK_TYPE\", \"results\": $BENCHMARK_RESULTS}}'",
"learning_session": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"learning_session\", \"topic\": \"$LEARNING_TOPIC\", \"confidence_before\": $CONFIDENCE_BEFORE, \"confidence_after\": $CONFIDENCE_AFTER, \"duration_ms\": $DURATION_MS}'",
"tutorial_follow": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"tutorial\", \"topic\": \"$TUTORIAL_TOPIC\", \"resource_url\": \"$TUTORIAL_URL\", \"notes\": \"$TUTORIAL_NOTES\"}'",
"experimentation": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"experimentation\", \"topic\": \"$EXPERIMENT_TYPE\", \"notes\": \"$EXPERIMENT_DESCRIPTION\"}'",
"knowledge_gap": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"knowledge_gap\", \"topic\": \"$UNKNOWN_CONCEPT\", \"notes\": \"$GAP_DESCRIPTION\"}'",
"manual_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"manual_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"test_type\": \"$TEST_TYPE\", \"scenarios\": $SCENARIO_COUNT}, \"duration_ms\": $DURATION_MS}'",
"bug_reproduction": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"bug_reproduction\", \"complexity\": \"$BUG_COMPLEXITY\", \"notes\": \"$BUG_DESCRIPTION\", \"outcome\": \"$REPRODUCTION_RESULT\"}'",
"edge_case_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"edge_case_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"edge_cases\": $EDGE_CASE_COUNT, \"findings\": \"$FINDINGS\"}}'",
"user_feedback": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"user_feedback\", \"query_or_topic\": \"$FEEDBACK_TOPIC\", \"response_quality\": $FEEDBACK_RATING, \"metadata\": {\"feedback_type\": \"$FEEDBACK_TYPE\", \"urgency\": \"$URGENCY\"}}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,25 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"waiting_period_start": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'",
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\", \"parameters\": $TOOL_PARAMS}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"test_execution": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"test\", \"tool_name\": \"$TEST_FRAMEWORK\", \"status\": \"$STATUS\", \"details\": {\"passed\": $PASSED_COUNT, \"failed\": $FAILED_COUNT}, \"duration_ms\": $DURATION_MS}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"waiting_period_end": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'",
"large_file_warning": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"file_size\", \"value\": $FILE_SIZE_MB, \"unit\": \"MB\", \"threshold_exceeded\": true}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"memory_usage": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'",
"code_analysis": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"analysis\", \"file_path\": \"$FILE_PATH\", \"tool_name\": \"$ANALYSIS_TOOL\", \"status\": \"$STATUS\", \"issues_count\": $ISSUES_COUNT, \"details\": $DETAILS}'",
"dependency_change": "curl -X POST http://localhost:8000/api/hooks/environment -H \"Content-Type: application/json\" -d '{\"event_type\": \"dependency_change\", \"config_file\": \"$PACKAGE_FILE\", \"changes\": {\"action\": \"$ACTION\", \"package\": \"$PACKAGE_NAME\", \"version\": \"$VERSION\"}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"build_process": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"build\", \"tool_name\": \"$BUILD_TOOL\", \"status\": \"$STATUS\", \"duration_ms\": $DURATION_MS}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,16 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,30 @@
{
"hooks": {
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"browser_tab": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"browser_tab\", \"description\": \"$TAB_TITLE\", \"metadata\": {\"url\": \"$TAB_URL\", \"category\": \"$TAB_CATEGORY\"}}'",
"search_query": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"search_query\", \"description\": \"$SEARCH_QUERY\", \"metadata\": {\"search_type\": \"$SEARCH_TYPE\", \"context\": \"$CONTEXT\"}}'",
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"waiting_period_end": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"duration_ms\": $DURATION_MS, \"end_time\": \"$TIMESTAMP\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"memory_usage": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"memory\", \"value\": $MEMORY_MB, \"unit\": \"MB\", \"threshold_exceeded\": $THRESHOLD_EXCEEDED}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'",
"tool_error": "curl -X POST http://localhost:8000/api/hooks/tool-error -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"error_type\": \"$ERROR_TYPE\", \"error_message\": \"$ERROR_MESSAGE\", \"stack_trace\": \"$STACK_TRACE\", \"parameters\": $TOOL_PARAMS}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"copy_paste": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"copy_paste\", \"description\": \"Code copied/pasted\", \"metadata\": {\"action\": \"$ACTION\", \"source\": \"$SOURCE\", \"lines\": $LINE_COUNT}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"waiting_period_start": "curl -X POST http://localhost:8000/api/hooks/waiting-period -H \"Content-Type: application/json\" -d '{\"reason\": \"thinking\", \"context\": \"$CONTEXT\"}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"large_file_warning": "curl -X POST http://localhost:8000/api/hooks/performance -H \"Content-Type: application/json\" -d '{\"metric_type\": \"file_size\", \"value\": $FILE_SIZE_MB, \"unit\": \"MB\", \"threshold_exceeded\": true}'",
"context_switch": "curl -X POST http://localhost:8000/api/hooks/workflow -H \"Content-Type: application/json\" -d '{\"event_type\": \"context_switch\", \"description\": \"Switched to $NEW_PROJECT\", \"metadata\": {\"from\": \"$OLD_PROJECT\", \"to\": \"$NEW_PROJECT\"}}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

View File

@ -0,0 +1,29 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"code_explanation": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"code_explanation\", \"query_or_topic\": \"$CODE_CONTEXT\", \"metadata\": {\"explanation_type\": \"$TYPE\", \"code_lines\": $CODE_LINES}}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"learning_session": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"learning_session\", \"topic\": \"$LEARNING_TOPIC\", \"confidence_before\": $CONFIDENCE_BEFORE, \"confidence_after\": $CONFIDENCE_AFTER, \"duration_ms\": $DURATION_MS}'",
"tutorial_follow": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"tutorial\", \"topic\": \"$TUTORIAL_TOPIC\", \"resource_url\": \"$TUTORIAL_URL\", \"notes\": \"$TUTORIAL_NOTES\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"ai_question": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"ai_question\", \"query_or_topic\": \"$QUESTION\", \"metadata\": {\"question_type\": \"$QUESTION_TYPE\", \"complexity\": \"$COMPLEXITY\"}}'",
"external_resource": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"external_resource\", \"interaction_type\": \"$RESOURCE_TYPE\", \"resource_url\": \"$URL\", \"query_or_topic\": \"$TOPIC\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"experimentation": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"experimentation\", \"topic\": \"$EXPERIMENT_TYPE\", \"notes\": \"$EXPERIMENT_DESCRIPTION\"}'",
"knowledge_gap": "curl -X POST http://localhost:8000/api/hooks/learning -H \"Content-Type: application/json\" -d '{\"event_type\": \"knowledge_gap\", \"topic\": \"$UNKNOWN_CONCEPT\", \"notes\": \"$GAP_DESCRIPTION\"}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"review_request": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"review_request\", \"query_or_topic\": \"$REVIEW_SCOPE\", \"metadata\": {\"files_count\": $FILES_COUNT, \"review_type\": \"$REVIEW_TYPE\"}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

29
claude-hooks-team.json Normal file
View File

@ -0,0 +1,29 @@
{
"hooks": {
"conversation_update": "curl -X POST http://localhost:8000/api/conversations -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"content\": \"$CONTENT\", \"timestamp\": \"$TIMESTAMP\"}'",
"refactor_end": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"end_time\": \"$TIMESTAMP\", \"duration_minutes\": $DURATION_MIN, \"outcome\": \"$OUTCOME\", \"notes\": \"$NOTES\"}'",
"code_explanation": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"code_explanation\", \"query_or_topic\": \"$CODE_CONTEXT\", \"metadata\": {\"explanation_type\": \"$TYPE\", \"code_lines\": $CODE_LINES}}'",
"edge_case_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"edge_case_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"edge_cases\": $EDGE_CASE_COUNT, \"findings\": \"$FINDINGS\"}}'",
"feature_flag": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"feature_flag\", \"scope\": \"$SCOPE\", \"notes\": \"Feature flag: $FLAG_NAME - $ACTION\"}'",
"tool_call": "curl -X POST http://localhost:8000/api/tool-calls -H \"Content-Type: application/json\" -d '{\"tool_name\": \"$TOOL_NAME\", \"parameters\": $TOOL_PARAMS, \"result_status\": \"$RESULT_STATUS\", \"execution_time_ms\": $EXECUTION_TIME, \"timestamp\": \"$TIMESTAMP\"}'",
"session_start": "curl -X POST http://localhost:8000/api/sessions/start -H \"Content-Type: application/json\" -d '{\"project_path\": \"$PWD\", \"start_time\": \"$TIMESTAMP\", \"user\": \"$USER\"}'",
"ai_question": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"ai_question\", \"query_or_topic\": \"$QUESTION\", \"metadata\": {\"question_type\": \"$QUESTION_TYPE\", \"complexity\": \"$COMPLEXITY\"}}'",
"external_resource": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"external_resource\", \"interaction_type\": \"$RESOURCE_TYPE\", \"resource_url\": \"$URL\", \"query_or_topic\": \"$TOPIC\"}'",
"debugging_session": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"debugging_session\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST, \"notes\": \"$DEBUG_CONTEXT\"}'",
"session_end": "curl -X POST http://localhost:8000/api/sessions/end -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"end_time\": \"$TIMESTAMP\"}'",
"manual_testing": "curl -X POST http://localhost:8000/api/hooks/code-quality -H \"Content-Type: application/json\" -d '{\"event_type\": \"manual_test\", \"status\": \"$TEST_RESULT\", \"details\": {\"test_type\": \"$TEST_TYPE\", \"scenarios\": $SCENARIO_COUNT}, \"duration_ms\": $DURATION_MS}'",
"bug_reproduction": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"bug_reproduction\", \"complexity\": \"$BUG_COMPLEXITY\", \"notes\": \"$BUG_DESCRIPTION\", \"outcome\": \"$REPRODUCTION_RESULT\"}'",
"user_feedback": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"user_feedback\", \"query_or_topic\": \"$FEEDBACK_TOPIC\", \"response_quality\": $FEEDBACK_RATING, \"metadata\": {\"feedback_type\": \"$FEEDBACK_TYPE\", \"urgency\": \"$URGENCY\"}}'",
"review_request": "curl -X POST http://localhost:8000/api/hooks/collaboration -H \"Content-Type: application/json\" -d '{\"event_type\": \"review_request\", \"query_or_topic\": \"$REVIEW_SCOPE\", \"metadata\": {\"files_count\": $FILES_COUNT, \"review_type\": \"$REVIEW_TYPE\"}}'",
"file_modified": "curl -X POST http://localhost:8000/api/activities -H \"Content-Type: application/json\" -d '{\"session_id\": \"$SESSION_ID\", \"file_path\": \"$FILE_PATH\", \"action\": \"$ACTION\", \"timestamp\": \"$TIMESTAMP\"}'",
"refactor_start": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"refactor\", \"scope\": \"$SCOPE\", \"complexity\": \"$COMPLEXITY\", \"files_affected\": $FILES_LIST}'",
"documentation_update": "curl -X POST http://localhost:8000/api/hooks/project-intelligence -H \"Content-Type: application/json\" -d '{\"event_type\": \"documentation_update\", \"scope\": \"small\", \"files_affected\": [\"$DOC_FILE\"], \"notes\": \"$UPDATE_DESCRIPTION\"}'"
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": true,
"log_errors": true,
"log_file": "~/.claude-hooks.log"
}
}

53
docker-compose.yml Normal file
View File

@ -0,0 +1,53 @@
services:
claude-tracker:
build:
context: .
dockerfile: Dockerfile
container_name: claude-tracker-api
restart: unless-stopped
environment:
- DATABASE_URL=sqlite+aiosqlite:////app/data/tracker.db
- DEBUG=true
- PYTHONPATH=/app
volumes:
- ./data:/app/data
- ./app/dashboard/static:/app/app/dashboard/static:ro
networks:
- caddy
labels:
# Caddy reverse proxy configuration
caddy: ${DOMAIN:-claude.l.supported.systems}
caddy.reverse_proxy: "{{upstreams 8000}}"
# Security and CORS headers for API
# caddy.header./api/*: |
# X-Content-Type-Options nosniff
# X-Frame-Options DENY
# X-XSS-Protection "1; mode=block"
# Referrer-Policy strict-origin-when-cross-origin
# Access-Control-Allow-Origin *
# Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
# Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
ports:
- "8000:8000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
deploy:
resources:
limits:
cpus: '1.0'
memory: 512M
reservations:
cpus: '0.25'
memory: 128M
networks:
caddy:
external: true
name: caddy

123
docker-deploy.sh Executable file
View File

@ -0,0 +1,123 @@
#!/bin/bash
# Claude Code Tracker Docker Deployment Script
set -e
echo "🚀 Deploying Claude Code Tracker with Docker..."
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if .env file exists
if [ ! -f ".env" ]; then
print_warning ".env file not found. Creating from .env.example..."
cp .env.example .env
print_status "Please edit .env file to configure your domain and other settings"
fi
# Check if Caddy network exists
if ! docker network ls | grep -q "caddy"; then
print_warning "Caddy network not found. Creating external network..."
docker network create caddy
print_success "Created 'caddy' external network"
else
print_status "Caddy network already exists"
fi
# Create data directory if it doesn't exist
if [ ! -d "./data" ]; then
print_status "Creating data directory..."
mkdir -p ./data
fi
# Set permissions for data directory
chmod 755 ./data
print_status "Building and starting containers..."
# Build and start the containers
docker-compose up -d --build
# Wait for the service to be ready
print_status "Waiting for service to be ready..."
sleep 10
# Check if the service is healthy
if docker-compose ps | grep -q "Up (healthy)"; then
print_success "Claude Code Tracker is running and healthy!"
else
print_warning "Service is starting up. Checking logs..."
docker-compose logs --tail=20 claude-tracker
fi
# Display useful information
echo ""
echo "📋 Deployment Information:"
echo "=========================="
print_status "Container name: claude-tracker-api"
print_status "Internal port: 8000"
# Read domain from .env file
if [ -f ".env" ]; then
DOMAIN=$(grep "^DOMAIN=" .env | cut -d'=' -f2)
if [ -n "$DOMAIN" ]; then
print_status "External URL: https://$DOMAIN"
else
print_status "External URL: Configure DOMAIN in .env file"
fi
fi
echo ""
echo "🔧 Management Commands:"
echo "======================"
echo "View logs: docker-compose logs -f claude-tracker"
echo "Restart service: docker-compose restart claude-tracker"
echo "Stop service: docker-compose down"
echo "Update service: docker-compose pull && docker-compose up -d"
echo "Shell access: docker-compose exec claude-tracker /bin/bash"
echo ""
echo "📊 Health Check:"
echo "================"
echo "Container status: docker-compose ps"
echo "Health endpoint: curl http://localhost:8000/health"
echo "API documentation: https://$DOMAIN/docs"
echo "Dashboard: https://$DOMAIN/dashboard"
echo ""
echo "🎯 Hook Configuration:"
echo "====================="
echo "Download pre-built hook configs from:"
echo "https://$DOMAIN/dashboard/docs/hook-reference"
echo ""
print_success "Deployment completed! 🎉"
# Check if Caddy is running
if docker ps | grep -q caddy; then
print_success "Caddy reverse proxy detected"
else
print_warning "Caddy reverse proxy not running. Start it to access via domain."
fi

281
generate_hook_config.py Normal file
View File

@ -0,0 +1,281 @@
#!/usr/bin/env python3
"""
Generate comprehensive Claude Code hook configurations.
"""
import json
from typing import Dict, Any
def generate_basic_config() -> Dict[str, Any]:
"""Generate basic hook configuration."""
return {
"hooks": {
# Session Management
"session_start": 'curl -X POST http://localhost:8000/api/sessions/start -H "Content-Type: application/json" -d \'{"project_path": "$PWD", "start_time": "$TIMESTAMP"}\'',
"session_end": 'curl -X POST http://localhost:8000/api/sessions/end -H "Content-Type: application/json" -d \'{"session_id": "$SESSION_ID", "end_time": "$TIMESTAMP"}\'',
# Core Interactions
"conversation_update": 'curl -X POST http://localhost:8000/api/conversations -H "Content-Type: application/json" -d \'{"session_id": "$SESSION_ID", "content": "$CONTENT"}\'',
"tool_call": 'curl -X POST http://localhost:8000/api/tool-calls -H "Content-Type: application/json" -d \'{"tool_name": "$TOOL_NAME", "parameters": $TOOL_PARAMS, "result_status": "$RESULT_STATUS", "execution_time_ms": $EXECUTION_TIME}\'',
# File Operations
"file_modified": 'curl -X POST http://localhost:8000/api/activities -H "Content-Type: application/json" -d \'{"session_id": "$SESSION_ID", "file_path": "$FILE_PATH", "action": "$ACTION"}\'',
}
}
def generate_comprehensive_config() -> Dict[str, Any]:
"""Generate comprehensive hook configuration with all hook types."""
return {
"hooks": {
# Session Management
"session_start": 'curl -X POST http://localhost:8000/api/sessions/start -H "Content-Type: application/json" -d \'{"project_path": "$PWD", "start_time": "$TIMESTAMP", "user": "$USER"}\'',
"session_end": 'curl -X POST http://localhost:8000/api/sessions/end -H "Content-Type: application/json" -d \'{"session_id": "$SESSION_ID", "end_time": "$TIMESTAMP"}\'',
# Core Interactions
"conversation_update": 'curl -X POST http://localhost:8000/api/conversations -H "Content-Type: application/json" -d \'{"session_id": "$SESSION_ID", "content": "$CONTENT", "timestamp": "$TIMESTAMP"}\'',
"tool_call": 'curl -X POST http://localhost:8000/api/tool-calls -H "Content-Type: application/json" -d \'{"tool_name": "$TOOL_NAME", "parameters": $TOOL_PARAMS, "result_status": "$RESULT_STATUS", "execution_time_ms": $EXECUTION_TIME, "timestamp": "$TIMESTAMP"}\'',
# File Operations
"file_modified": 'curl -X POST http://localhost:8000/api/activities -H "Content-Type: application/json" -d \'{"session_id": "$SESSION_ID", "file_path": "$FILE_PATH", "action": "$ACTION", "timestamp": "$TIMESTAMP"}\'',
# Performance & Debugging
"tool_error": 'curl -X POST http://localhost:8000/api/hooks/tool-error -H "Content-Type: application/json" -d \'{"tool_name": "$TOOL_NAME", "error_type": "$ERROR_TYPE", "error_message": "$ERROR_MESSAGE", "stack_trace": "$STACK_TRACE", "parameters": $TOOL_PARAMS}\'',
"waiting_period_start": 'curl -X POST http://localhost:8000/api/hooks/waiting-period -H "Content-Type: application/json" -d \'{"reason": "thinking", "context": "$CONTEXT"}\'',
"waiting_period_end": 'curl -X POST http://localhost:8000/api/hooks/waiting-period -H "Content-Type: application/json" -d \'{"reason": "thinking", "duration_ms": $DURATION_MS, "end_time": "$TIMESTAMP"}\'',
"memory_usage": 'curl -X POST http://localhost:8000/api/hooks/performance -H "Content-Type: application/json" -d \'{"metric_type": "memory", "value": $MEMORY_MB, "unit": "MB", "threshold_exceeded": $THRESHOLD_EXCEEDED}\'',
"large_file_warning": 'curl -X POST http://localhost:8000/api/hooks/performance -H "Content-Type: application/json" -d \'{"metric_type": "file_size", "value": $FILE_SIZE_MB, "unit": "MB", "threshold_exceeded": true}\'',
# Code Quality & Analysis
"code_analysis": 'curl -X POST http://localhost:8000/api/hooks/code-quality -H "Content-Type: application/json" -d \'{"event_type": "analysis", "file_path": "$FILE_PATH", "tool_name": "$ANALYSIS_TOOL", "status": "$STATUS", "issues_count": $ISSUES_COUNT, "details": $DETAILS}\'',
"test_execution": 'curl -X POST http://localhost:8000/api/hooks/code-quality -H "Content-Type: application/json" -d \'{"event_type": "test", "tool_name": "$TEST_FRAMEWORK", "status": "$STATUS", "details": {"passed": $PASSED_COUNT, "failed": $FAILED_COUNT}, "duration_ms": $DURATION_MS}\'',
"build_process": 'curl -X POST http://localhost:8000/api/hooks/code-quality -H "Content-Type: application/json" -d \'{"event_type": "build", "tool_name": "$BUILD_TOOL", "status": "$STATUS", "duration_ms": $DURATION_MS}\'',
"dependency_change": 'curl -X POST http://localhost:8000/api/hooks/environment -H "Content-Type: application/json" -d \'{"event_type": "dependency_change", "config_file": "$PACKAGE_FILE", "changes": {"action": "$ACTION", "package": "$PACKAGE_NAME", "version": "$VERSION"}}\'',
# Development Workflow
"context_switch": 'curl -X POST http://localhost:8000/api/hooks/workflow -H "Content-Type: application/json" -d \'{"event_type": "context_switch", "description": "Switched to $NEW_PROJECT", "metadata": {"from": "$OLD_PROJECT", "to": "$NEW_PROJECT"}}\'',
"search_query": 'curl -X POST http://localhost:8000/api/hooks/workflow -H "Content-Type: application/json" -d \'{"event_type": "search_query", "description": "$SEARCH_QUERY", "metadata": {"search_type": "$SEARCH_TYPE", "context": "$CONTEXT"}}\'',
"browser_tab": 'curl -X POST http://localhost:8000/api/hooks/workflow -H "Content-Type: application/json" -d \'{"event_type": "browser_tab", "description": "$TAB_TITLE", "metadata": {"url": "$TAB_URL", "category": "$TAB_CATEGORY"}}\'',
"copy_paste": 'curl -X POST http://localhost:8000/api/hooks/workflow -H "Content-Type: application/json" -d \'{"event_type": "copy_paste", "description": "Code copied/pasted", "metadata": {"action": "$ACTION", "source": "$SOURCE", "lines": $LINE_COUNT}}\'',
# Collaboration & Communication
"external_resource": 'curl -X POST http://localhost:8000/api/hooks/collaboration -H "Content-Type: application/json" -d \'{"event_type": "external_resource", "interaction_type": "$RESOURCE_TYPE", "resource_url": "$URL", "query_or_topic": "$TOPIC"}\'',
"ai_question": 'curl -X POST http://localhost:8000/api/hooks/collaboration -H "Content-Type: application/json" -d \'{"event_type": "ai_question", "query_or_topic": "$QUESTION", "metadata": {"question_type": "$QUESTION_TYPE", "complexity": "$COMPLEXITY"}}\'',
"code_explanation": 'curl -X POST http://localhost:8000/api/hooks/collaboration -H "Content-Type: application/json" -d \'{"event_type": "code_explanation", "query_or_topic": "$CODE_CONTEXT", "metadata": {"explanation_type": "$TYPE", "code_lines": $CODE_LINES}}\'',
"review_request": 'curl -X POST http://localhost:8000/api/hooks/collaboration -H "Content-Type: application/json" -d \'{"event_type": "review_request", "query_or_topic": "$REVIEW_SCOPE", "metadata": {"files_count": $FILES_COUNT, "review_type": "$REVIEW_TYPE"}}\'',
# Project Intelligence
"refactor_start": 'curl -X POST http://localhost:8000/api/hooks/project-intelligence -H "Content-Type: application/json" -d \'{"event_type": "refactor", "scope": "$SCOPE", "complexity": "$COMPLEXITY", "files_affected": $FILES_LIST}\'',
"refactor_end": 'curl -X POST http://localhost:8000/api/hooks/project-intelligence -H "Content-Type: application/json" -d \'{"event_type": "refactor", "end_time": "$TIMESTAMP", "duration_minutes": $DURATION_MIN, "outcome": "$OUTCOME", "notes": "$NOTES"}\'',
"feature_flag": 'curl -X POST http://localhost:8000/api/hooks/project-intelligence -H "Content-Type: application/json" -d \'{"event_type": "feature_flag", "scope": "$SCOPE", "notes": "Feature flag: $FLAG_NAME - $ACTION"}\'',
"debugging_session": 'curl -X POST http://localhost:8000/api/hooks/project-intelligence -H "Content-Type: application/json" -d \'{"event_type": "debugging_session", "complexity": "$COMPLEXITY", "files_affected": $FILES_LIST, "notes": "$DEBUG_CONTEXT"}\'',
"documentation_update": 'curl -X POST http://localhost:8000/api/hooks/project-intelligence -H "Content-Type: application/json" -d \'{"event_type": "documentation_update", "scope": "small", "files_affected": ["$DOC_FILE"], "notes": "$UPDATE_DESCRIPTION"}\'',
# Environment & Setup
"environment_change": 'curl -X POST http://localhost:8000/api/hooks/environment -H "Content-Type: application/json" -d \'{"event_type": "env_change", "environment": "$NEW_ENV", "impact_level": "$IMPACT_LEVEL"}\'',
"config_update": 'curl -X POST http://localhost:8000/api/hooks/environment -H "Content-Type: application/json" -d \'{"event_type": "config_update", "config_file": "$CONFIG_FILE", "changes": $CONFIG_CHANGES, "impact_level": "$IMPACT_LEVEL"}\'',
"security_scan": 'curl -X POST http://localhost:8000/api/hooks/environment -H "Content-Type: application/json" -d \'{"event_type": "security_scan", "changes": {"tool": "$SCAN_TOOL", "vulnerabilities": $VULN_COUNT, "severity": "$MAX_SEVERITY"}}\'',
"performance_benchmark": 'curl -X POST http://localhost:8000/api/hooks/environment -H "Content-Type: application/json" -d \'{"event_type": "performance_benchmark", "changes": {"benchmark_type": "$BENCHMARK_TYPE", "results": $BENCHMARK_RESULTS}}\'',
# Learning & Knowledge
"learning_session": 'curl -X POST http://localhost:8000/api/hooks/learning -H "Content-Type: application/json" -d \'{"event_type": "learning_session", "topic": "$LEARNING_TOPIC", "confidence_before": $CONFIDENCE_BEFORE, "confidence_after": $CONFIDENCE_AFTER, "duration_ms": $DURATION_MS}\'',
"tutorial_follow": 'curl -X POST http://localhost:8000/api/hooks/learning -H "Content-Type: application/json" -d \'{"event_type": "tutorial", "topic": "$TUTORIAL_TOPIC", "resource_url": "$TUTORIAL_URL", "notes": "$TUTORIAL_NOTES"}\'',
"experimentation": 'curl -X POST http://localhost:8000/api/hooks/learning -H "Content-Type: application/json" -d \'{"event_type": "experimentation", "topic": "$EXPERIMENT_TYPE", "notes": "$EXPERIMENT_DESCRIPTION"}\'',
"knowledge_gap": 'curl -X POST http://localhost:8000/api/hooks/learning -H "Content-Type: application/json" -d \'{"event_type": "knowledge_gap", "topic": "$UNKNOWN_CONCEPT", "notes": "$GAP_DESCRIPTION"}\'',
# Quality Assurance
"manual_testing": 'curl -X POST http://localhost:8000/api/hooks/code-quality -H "Content-Type: application/json" -d \'{"event_type": "manual_test", "status": "$TEST_RESULT", "details": {"test_type": "$TEST_TYPE", "scenarios": $SCENARIO_COUNT}, "duration_ms": $DURATION_MS}\'',
"bug_reproduction": 'curl -X POST http://localhost:8000/api/hooks/project-intelligence -H "Content-Type: application/json" -d \'{"event_type": "bug_reproduction", "complexity": "$BUG_COMPLEXITY", "notes": "$BUG_DESCRIPTION", "outcome": "$REPRODUCTION_RESULT"}\'',
"edge_case_testing": 'curl -X POST http://localhost:8000/api/hooks/code-quality -H "Content-Type: application/json" -d \'{"event_type": "edge_case_test", "status": "$TEST_RESULT", "details": {"edge_cases": $EDGE_CASE_COUNT, "findings": "$FINDINGS"}}\'',
"user_feedback": 'curl -X POST http://localhost:8000/api/hooks/collaboration -H "Content-Type: application/json" -d \'{"event_type": "user_feedback", "query_or_topic": "$FEEDBACK_TOPIC", "response_quality": $FEEDBACK_RATING, "metadata": {"feedback_type": "$FEEDBACK_TYPE", "urgency": "$URGENCY"}}\'',
},
"hook_settings": {
"timeout": 10,
"retry_count": 3,
"async": True,
"log_errors": True,
"log_file": "~/.claude-hooks.log"
}
}
def generate_selective_config(categories: list) -> Dict[str, Any]:
"""Generate selective hook configuration based on categories."""
full_config = generate_comprehensive_config()
category_hooks = {
"essential": ["session_start", "session_end", "conversation_update", "tool_call", "file_modified"],
"performance": ["tool_error", "waiting_period_start", "waiting_period_end", "memory_usage", "large_file_warning"],
"code_quality": ["code_analysis", "test_execution", "build_process", "dependency_change"],
"workflow": ["context_switch", "search_query", "browser_tab", "copy_paste"],
"collaboration": ["external_resource", "ai_question", "code_explanation", "review_request"],
"intelligence": ["refactor_start", "refactor_end", "feature_flag", "debugging_session", "documentation_update"],
"environment": ["environment_change", "config_update", "security_scan", "performance_benchmark"],
"learning": ["learning_session", "tutorial_follow", "experimentation", "knowledge_gap"],
"testing": ["manual_testing", "bug_reproduction", "edge_case_testing", "user_feedback"]
}
selected_hooks = set()
for category in categories:
if category in category_hooks:
selected_hooks.update(category_hooks[category])
return {
"hooks": {
hook: full_config["hooks"][hook]
for hook in selected_hooks
if hook in full_config["hooks"]
},
"hook_settings": full_config["hook_settings"]
}
def main():
"""Generate all hook configurations."""
configs = {
"basic": generate_basic_config(),
"comprehensive": generate_comprehensive_config(),
"essential": generate_selective_config(["essential"]),
"developer": generate_selective_config(["essential", "performance", "code_quality"]),
"power_user": generate_selective_config(["essential", "performance", "workflow", "intelligence"]),
"research": generate_selective_config(["essential", "learning", "collaboration", "intelligence"]),
"team": generate_selective_config(["essential", "collaboration", "testing", "intelligence"])
}
# Generate configuration files
for config_name, config_data in configs.items():
filename = f"claude-hooks-{config_name}.json"
with open(filename, "w") as f:
json.dump(config_data, f, indent=2)
print(f"Generated {filename}")
# Generate README for configurations
readme_content = """# Claude Code Hook Configurations
This directory contains various pre-configured hook setups for different use cases:
## Available Configurations
### basic.json
Essential hooks for basic session and tool tracking.
- Session management
- Tool calls
- File modifications
- Conversations
### comprehensive.json
Complete hook setup with all available hook types.
- All performance monitoring
- Code quality tracking
- Learning analytics
- Collaboration insights
- Project intelligence
### essential.json
Minimal setup for core functionality.
### developer.json
Focused on development workflow and code quality.
- Essential hooks
- Performance monitoring
- Code quality checks
### power_user.json
Advanced setup for productivity optimization.
- Essential hooks
- Performance tracking
- Workflow analysis
- Project intelligence
### research.json
Optimized for learning and exploration.
- Essential hooks
- Learning tracking
- External resource usage
- Knowledge gap analysis
### team.json
Team-focused configuration for collaboration.
- Essential hooks
- Collaboration tracking
- Testing workflows
- Project intelligence
## Installation
1. Choose the configuration that matches your needs
2. Copy the JSON content to your Claude Code settings file:
- macOS/Linux: `~/.config/claude/settings.json`
- Windows: `%APPDATA%\\claude\\settings.json`
3. Ensure Claude Code Tracker is running on port 8000
4. Start using Claude Code - hooks will automatically track your activity!
## Available Hook Variables
Each hook can use these variables that Claude Code provides:
### Session Variables
- `$SESSION_ID` - Current session identifier
- `$TIMESTAMP` - Current timestamp (ISO format)
- `$PWD` - Current working directory
- `$USER` - System username
### Tool Variables
- `$TOOL_NAME` - Name of tool being called
- `$TOOL_PARAMS` - Tool parameters (JSON)
- `$RESULT_STATUS` - Success/error status
- `$EXECUTION_TIME` - Tool execution time (ms)
- `$ERROR_TYPE` - Type of error
- `$ERROR_MESSAGE` - Error message
- `$STACK_TRACE` - Error stack trace
### File Variables
- `$FILE_PATH` - Path to modified file
- `$ACTION` - File action (created/modified/deleted)
- `$FILE_SIZE_MB` - File size in megabytes
### Context Variables
- `$CONTENT` - Conversation content
- `$CONTEXT` - Current context description
- `$SEARCH_QUERY` - What you're searching for
- `$NEW_PROJECT` - Project being switched to
- `$OLD_PROJECT` - Project being switched from
### Performance Variables
- `$MEMORY_MB` - Memory usage in MB
- `$DURATION_MS` - Duration in milliseconds
- `$THRESHOLD_EXCEEDED` - Boolean for threshold alerts
And many more! Each hook type has specific variables available.
## Customization
You can modify any configuration by:
1. Adding/removing specific hooks
2. Changing API endpoints or ports
3. Adjusting timeout and retry settings
4. Adding custom metadata to hook calls
## Troubleshooting
If hooks aren't working:
1. Ensure Claude Code Tracker server is running
2. Check that curl is installed
3. Verify the API endpoints are accessible
4. Check Claude Code logs for hook execution errors
5. Test individual hooks manually with curl
For more help, see the documentation at `/dashboard/docs/hook-setup`.
"""
with open("README.md", "w") as f:
f.write(readme_content)
print("Generated README.md with configuration guide")
print(f"\\nGenerated {len(configs)} hook configurations:")
for name in configs.keys():
print(f" - claude-hooks-{name}.json")
if __name__ == "__main__":
main()

154
import_claude_data.py Normal file
View File

@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""
Command-line tool to import data from .claude.json file.
Usage:
python import_claude_data.py [--file path/to/.claude.json] [--preview]
"""
import asyncio
import argparse
import sys
from pathlib import Path
from app.database.connection import async_session_maker, init_database
from app.api.importer import ClaudeJsonImporter
async def preview_import(file_path: str):
"""Preview what would be imported."""
print(f"🔍 Previewing import from: {file_path}")
print("=" * 50)
try:
import json
import os
if not os.path.exists(file_path):
print(f"❌ File not found: {file_path}")
return False
with open(file_path, 'r', encoding='utf-8') as f:
claude_data = json.load(f)
file_size_mb = round(os.path.getsize(file_path) / (1024 * 1024), 2)
print(f"📁 File size: {file_size_mb} MB")
print(f"🚀 Total Claude Code startups: {claude_data.get('numStartups', 0)}")
print(f"📅 First start time: {claude_data.get('firstStartTime', 'N/A')}")
print(f"📊 Prompt queue uses: {claude_data.get('promptQueueUseCount', 0)}")
if "projects" in claude_data:
projects = claude_data["projects"]
print(f"📂 Projects found: {len(projects)}")
# Show first few project paths
paths = list(projects.keys())[:5]
for path in paths:
history_count = len(projects[path].get("history", []))
print(f"{path} ({history_count} history entries)")
if len(projects) > 5:
print(f" ... and {len(projects) - 5} more projects")
# Count total history entries
total_history = sum(len(proj.get("history", [])) for proj in projects.values())
print(f"💬 Total conversation history entries: {total_history}")
print("\n✅ Preview completed successfully!")
return True
except Exception as e:
print(f"❌ Preview failed: {e}")
return False
async def run_import(file_path: str):
"""Run the actual import process."""
print(f"📥 Starting import from: {file_path}")
print("=" * 50)
# Initialize database tables first
print("🔧 Initializing database...")
await init_database()
print("✅ Database initialized")
async with async_session_maker() as db:
try:
importer = ClaudeJsonImporter(db)
results = await importer.import_from_file(file_path)
print("🎉 Import completed successfully!")
print(f"📂 Projects imported: {results['projects_imported']}")
print(f"⏱️ Sessions estimated: {results['sessions_estimated']}")
print(f"💬 Conversations imported: {results['conversations_imported']}")
if results['errors']:
print(f"\n⚠️ Warnings/Errors ({len(results['errors'])}):")
for error in results['errors']:
print(f"{error}")
print("\n🚀 You can now view your imported data at: http://localhost:8000")
return True
except Exception as e:
print(f"❌ Import failed: {e}")
return False
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Import Claude Code data from .claude.json file",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python import_claude_data.py --preview
python import_claude_data.py --file ~/.claude.json
python import_claude_data.py --file ~/.claude.json --preview
"""
)
parser.add_argument(
"--file",
type=str,
default=None,
help="Path to .claude.json file (default: ~/.claude.json)"
)
parser.add_argument(
"--preview",
action="store_true",
help="Preview what would be imported without actually importing"
)
args = parser.parse_args()
# Determine file path
if args.file:
file_path = args.file
else:
file_path = str(Path.home() / ".claude.json")
print("🤖 Claude Code Project Tracker - Data Importer")
print("=" * 50)
if args.preview:
success = asyncio.run(preview_import(file_path))
else:
print("⚠️ This will import data into your Claude Code Project Tracker database.")
print(" Make sure the tracker server is not running during import.")
print()
confirm = input("Continue with import? (y/N): ").lower().strip()
if confirm != 'y':
print("Import cancelled.")
sys.exit(0)
success = asyncio.run(run_import(file_path))
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()

49
init_db.py Normal file
View File

@ -0,0 +1,49 @@
#!/usr/bin/env python3
"""
Database initialization script for Claude Code Tracker.
This script ensures the database is properly initialized before the application starts.
"""
import asyncio
import os
import sys
from pathlib import Path
# Add the app directory to the Python path
sys.path.append(str(Path(__file__).parent))
from app.database.connection import init_database, engine
async def ensure_database_initialized():
"""Ensure the database is properly initialized."""
try:
print("Initializing database...")
# Create data directory if it doesn't exist
data_dir = Path("/app/data")
data_dir.mkdir(parents=True, exist_ok=True)
# Set permissions on data directory
os.chmod(str(data_dir), 0o777)
# Initialize database
await init_database()
print("Database initialization completed successfully!")
return True
except Exception as e:
print(f"Database initialization failed: {e}")
# Don't exit - let the app try to continue
return False
finally:
# Clean up engine
try:
await engine.dispose()
except:
pass
if __name__ == "__main__":
asyncio.run(ensure_database_initialized())

19
main.py
View File

@ -9,7 +9,7 @@ from fastapi.staticfiles import StaticFiles
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
from app.database.connection import init_database, close_database from app.database.connection import init_database, close_database
from app.api import sessions, conversations, activities, waiting, git, projects, analytics from app.api import sessions, conversations, activities, waiting, git, projects, analytics, importer, tool_calls, hooks
from app.dashboard.routes import dashboard_router from app.dashboard.routes import dashboard_router
@ -18,11 +18,21 @@ async def lifespan(app: FastAPI):
"""Application lifespan management.""" """Application lifespan management."""
# Startup # Startup
print("Starting Claude Code Project Tracker...") print("Starting Claude Code Project Tracker...")
await init_database() try:
await init_database()
print("Database initialized successfully!")
except Exception as e:
print(f"Database initialization failed: {e}")
print("Application will continue with limited functionality...")
yield yield
# Shutdown # Shutdown
print("Shutting down...") print("Shutting down...")
await close_database() try:
await close_database()
except Exception as e:
print(f"Error during shutdown: {e}")
# Create FastAPI app # Create FastAPI app
@ -50,6 +60,9 @@ app.include_router(waiting.router, prefix="/api", tags=["Waiting Periods"])
app.include_router(git.router, prefix="/api", tags=["Git Operations"]) app.include_router(git.router, prefix="/api", tags=["Git Operations"])
app.include_router(projects.router, prefix="/api", tags=["Projects"]) app.include_router(projects.router, prefix="/api", tags=["Projects"])
app.include_router(analytics.router, prefix="/api", tags=["Analytics"]) app.include_router(analytics.router, prefix="/api", tags=["Analytics"])
app.include_router(importer.router, prefix="/api", tags=["Data Import"])
app.include_router(tool_calls.router, prefix="/api", tags=["Tool Calls"])
app.include_router(hooks.router, prefix="/api", tags=["Hooks"])
# Include dashboard routes # Include dashboard routes
app.include_router(dashboard_router, tags=["Dashboard"]) app.include_router(dashboard_router, tags=["Dashboard"])

124
pyproject.toml Normal file
View File

@ -0,0 +1,124 @@
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "claude-code-tracker"
version = "1.0.0"
description = "Development intelligence system for tracking Claude Code projects and productivity"
authors = [
{name = "Claude Code Tracker", email = "claude-code@example.com"}
]
readme = "README.md"
license = {text = "MIT"}
requires-python = ">=3.9"
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Web Environment",
"Framework :: FastAPI",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Software Development :: Libraries :: Python Modules",
"Topic :: Software Development :: Quality Assurance",
]
dependencies = [
"fastapi>=0.104.0",
"uvicorn[standard]>=0.24.0",
"sqlalchemy>=2.0.20",
"aiosqlite>=0.19.0",
"pydantic>=2.5.0",
"jinja2>=3.1.0",
"python-multipart>=0.0.6",
"python-jose[cryptography]>=3.3.0",
"passlib[bcrypt]>=1.7.4",
"python-dateutil>=2.8.0",
"typing-extensions>=4.8.0",
]
[project.optional-dependencies]
dev = [
"pytest>=7.4.0",
"pytest-asyncio>=0.21.0",
"httpx>=0.24.0",
"black>=23.0.0",
"isort>=5.12.0",
"flake8>=6.0.0",
"mypy>=1.5.0",
]
[project.urls]
Homepage = "https://git.supported.systems/claude/claude-code-tracker"
Repository = "https://git.supported.systems/claude/claude-code-tracker.git"
Issues = "https://git.supported.systems/claude/claude-code-tracker/issues"
[project.scripts]
claude-tracker = "main:main"
import-claude-data = "import_claude_data:main"
[tool.hatch.build.targets.wheel]
packages = ["app"]
[tool.black]
line-length = 88
target-version = ["py39", "py310", "py311", "py312"]
include = '\.pyi?$'
extend-exclude = '''
/(
# directories
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| build
| dist
)/
'''
[tool.isort]
profile = "black"
multi_line_output = 3
line_length = 88
known_first_party = ["app"]
[tool.mypy]
python_version = "3.9"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
disallow_untyped_decorators = true
no_implicit_optional = true
warn_redundant_casts = true
warn_unused_ignores = true
warn_no_return = true
warn_unreachable = true
strict_equality = true
[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"--strict-markers",
"--strict-config",
"--disable-warnings",
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
]

View File

@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""
Script to recalculate project statistics from imported sessions and conversations.
Run this after importing data to fix zero statistics on the projects page.
"""
import asyncio
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.connection import engine, async_session_maker
from app.models.project import Project
from app.models.session import Session
from app.models.conversation import Conversation
from app.models.activity import Activity
from app.models.tool_call import ToolCall
async def recalculate_project_stats():
"""Recalculate statistics for all projects based on imported data."""
async with async_session_maker() as db:
try:
# Get all projects
result = await db.execute(select(Project))
projects = result.scalars().all()
print(f"Found {len(projects)} projects to recalculate")
for project in projects:
print(f"\nRecalculating stats for project: {project.name}")
# Get all sessions for this project
session_result = await db.execute(
select(Session).where(Session.project_id == project.id)
)
sessions = session_result.scalars().all()
session_ids = [s.id for s in sessions] if sessions else []
# Calculate session-based statistics
total_sessions = len(sessions)
total_time_minutes = sum(
s.calculated_duration_minutes or 0 for s in sessions
)
# Find last session time
last_session_time = None
if sessions:
last_session_time = max(s.start_time for s in sessions)
# Calculate activity-based statistics
files_modified_count = 0
lines_changed_count = 0
tool_calls_count = 0
if session_ids:
activity_result = await db.execute(
select(Activity).where(Activity.session_id.in_(session_ids))
)
activities = activity_result.scalars().all()
# Count unique files modified
unique_files = set()
total_lines_changed = 0
for activity in activities:
if activity.file_path:
unique_files.add(activity.file_path)
if activity.total_lines_changed:
total_lines_changed += activity.total_lines_changed
files_modified_count = len(unique_files)
lines_changed_count = total_lines_changed
# Count tool calls
session_ids_str = [str(sid) for sid in session_ids]
tool_calls_result = await db.execute(
select(ToolCall).where(ToolCall.session_id.in_(session_ids_str))
)
tool_calls = tool_calls_result.scalars().all()
tool_calls_count = len(tool_calls)
# Update project statistics
project.total_sessions = total_sessions
project.total_time_minutes = total_time_minutes
project.files_modified_count = files_modified_count
project.lines_changed_count = lines_changed_count
project.tool_calls_count = tool_calls_count
if last_session_time:
project.last_session = last_session_time
print(f" Sessions: {total_sessions}")
print(f" Time: {total_time_minutes} minutes")
print(f" Files: {files_modified_count}")
print(f" Lines: {lines_changed_count}")
print(f" Tool calls: {tool_calls_count}")
print(f" Last session: {last_session_time}")
# Commit all changes
await db.commit()
print(f"\nSuccessfully recalculated statistics for {len(projects)} projects")
except Exception as e:
await db.rollback()
print(f"Error recalculating project stats: {e}")
raise
finally:
await engine.dispose()
if __name__ == "__main__":
asyncio.run(recalculate_project_stats())

View File

@ -1,11 +0,0 @@
fastapi==0.104.1
uvicorn[standard]==0.24.0
sqlalchemy==2.0.23
aiosqlite==0.19.0
pydantic==2.5.0
jinja2==3.1.2
python-multipart==0.0.6
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
python-dateutil==2.8.2
typing-extensions==4.8.0

1499
uv.lock generated Normal file

File diff suppressed because it is too large Load Diff