Compare commits
2 Commits
166247bf70
...
50c80596d0
Author | SHA1 | Date | |
---|---|---|---|
50c80596d0 | |||
bec1606c86 |
87
.dockerignore
Normal file
87
.dockerignore
Normal 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/
|
32
.env.example
32
.env.example
@ -1,10 +1,18 @@
|
||||
# Database
|
||||
DATABASE_URL=sqlite+aiosqlite:///./data/tracker.db
|
||||
# Claude Code Tracker Configuration
|
||||
|
||||
# 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_PORT=8000
|
||||
DEBUG=true
|
||||
|
||||
# Data storage path (host directory to bind mount)
|
||||
DATA_PATH=./data
|
||||
|
||||
# Security (generate with: openssl rand -hex 32)
|
||||
SECRET_KEY=your-secret-key-here
|
||||
@ -17,3 +25,19 @@ ANALYTICS_BATCH_SIZE=1000
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
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
345
DOCKER.md
Normal 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
53
Dockerfile
Normal 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
214
README.md
@ -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
|
||||
- **Conversations** - Full dialogue history with Claude for context and learning analysis
|
||||
- **Code Changes** - File modifications, tool usage, and command executions
|
||||
- **Thinking Patterns** - Wait times between interactions to understand your workflow
|
||||
- **Git Activity** - Repository changes, commits, and branch operations
|
||||
- **Productivity Metrics** - Engagement levels, output quality, and learning velocity
|
||||
### comprehensive.json
|
||||
Complete hook setup with all available hook types.
|
||||
- All performance monitoring
|
||||
- Code quality tracking
|
||||
- Learning analytics
|
||||
- Collaboration insights
|
||||
- Project intelligence
|
||||
|
||||
## Architecture
|
||||
### essential.json
|
||||
Minimal setup for core functionality.
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Claude Code │───▶│ Hook System │───▶│ FastAPI Server │
|
||||
│ (your IDE) │ │ │ │ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
|
||||
│ Web Dashboard │◀───│ Analytics │◀───│ SQLite Database │
|
||||
│ │ │ Engine │ │ │
|
||||
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
||||
```
|
||||
### developer.json
|
||||
Focused on development workflow and code quality.
|
||||
- Essential hooks
|
||||
- Performance monitoring
|
||||
- Code quality checks
|
||||
|
||||
### 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
|
||||
- **SQLite Database**: Local storage for all tracking data
|
||||
- **Hook Integration**: Claude Code hooks that capture development events
|
||||
- **Analytics Engine**: Processes raw data into meaningful insights
|
||||
- **Web Dashboard**: Interactive interface for exploring your development patterns
|
||||
### research.json
|
||||
Optimized for learning and exploration.
|
||||
- Essential hooks
|
||||
- Learning tracking
|
||||
- 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
|
||||
- Automatic project detection and session management
|
||||
- Working directory and git branch context
|
||||
- Session duration and engagement analysis
|
||||
## Installation
|
||||
|
||||
### 💬 Conversation Intelligence
|
||||
- Full dialogue history with semantic search
|
||||
- Problem-solving pattern recognition
|
||||
- Learning topic identification and progress tracking
|
||||
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!
|
||||
|
||||
### 📊 Development Analytics
|
||||
- Productivity metrics and engagement scoring
|
||||
- Tool usage patterns and optimization insights
|
||||
- Cross-project learning and code reuse analysis
|
||||
## Available Hook Variables
|
||||
|
||||
### 🔍 Advanced Insights
|
||||
- Think time analysis and flow state detection
|
||||
- Git activity correlation with conversations
|
||||
- Skill development velocity tracking
|
||||
- Workflow optimization recommendations
|
||||
Each hook can use these variables that Claude Code provides:
|
||||
|
||||
## 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
|
||||
- **No External Services**: No data transmission to third parties
|
||||
- **Full Control**: Complete ownership of your development history
|
||||
- **Selective Tracking**: Configurable hook activation per project
|
||||
### 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
|
||||
|
||||
## 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**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
### 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
|
||||
|
||||
2. **Start the Tracking Server**
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
### Performance Variables
|
||||
- `$MEMORY_MB` - Memory usage in MB
|
||||
- `$DURATION_MS` - Duration in milliseconds
|
||||
- `$THRESHOLD_EXCEEDED` - Boolean for threshold alerts
|
||||
|
||||
3. **Configure Claude Code Hooks**
|
||||
```bash
|
||||
# Add hooks to your Claude Code settings
|
||||
cp config/claude-hooks.json ~/.config/claude-code/
|
||||
```
|
||||
And many more! Each hook type has specific variables available.
|
||||
|
||||
4. **Access Dashboard**
|
||||
```
|
||||
Open http://localhost:8000 in your browser
|
||||
```
|
||||
## Customization
|
||||
|
||||
## 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
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
## Troubleshooting
|
||||
|
||||
## 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
|
||||
- [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
|
||||
For more help, see the documentation at `/dashboard/docs/hook-setup`.
|
||||
|
@ -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])
|
||||
async def search_conversations(
|
||||
query: str = Query(..., description="Search query"),
|
||||
|
619
app/api/hooks.py
Normal file
619
app/api/hooks.py
Normal 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
528
app/api/importer.py
Normal 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
254
app/api/tool_calls.py
Normal 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)}"
|
||||
)
|
@ -47,3 +47,88 @@ async def dashboard_conversations(request: Request, db: AsyncSession = Depends(g
|
||||
"request": request,
|
||||
"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
|
||||
})
|
9
app/dashboard/static/claude-hooks-basic.json
Normal file
9
app/dashboard/static/claude-hooks-basic.json
Normal 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\"}'"
|
||||
}
|
||||
}
|
50
app/dashboard/static/claude-hooks-comprehensive.json
Normal file
50
app/dashboard/static/claude-hooks-comprehensive.json
Normal 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"
|
||||
}
|
||||
}
|
25
app/dashboard/static/claude-hooks-developer.json
Normal file
25
app/dashboard/static/claude-hooks-developer.json
Normal 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"
|
||||
}
|
||||
}
|
16
app/dashboard/static/claude-hooks-essential.json
Normal file
16
app/dashboard/static/claude-hooks-essential.json
Normal 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"
|
||||
}
|
||||
}
|
30
app/dashboard/static/claude-hooks-power_user.json
Normal file
30
app/dashboard/static/claude-hooks-power_user.json
Normal 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"
|
||||
}
|
||||
}
|
29
app/dashboard/static/claude-hooks-research.json
Normal file
29
app/dashboard/static/claude-hooks-research.json
Normal 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
app/dashboard/static/claude-hooks-team.json
Normal file
29
app/dashboard/static/claude-hooks-team.json
Normal 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"
|
||||
}
|
||||
}
|
@ -92,6 +92,14 @@ class ApiClient {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
let url = `/conversations/search?query=${encodeURIComponent(query)}&limit=${limit}`;
|
||||
if (projectId) {
|
||||
|
@ -47,12 +47,24 @@
|
||||
Conversations
|
||||
</a>
|
||||
</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 class="navbar-nav">
|
||||
<li class="nav-item">
|
||||
<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
|
||||
</a>
|
||||
</li>
|
||||
|
@ -29,6 +29,10 @@
|
||||
<i class="fas fa-search me-1"></i>
|
||||
Search
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="clearSearch()">
|
||||
<i class="fas fa-times me-1"></i>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Search through your conversation history with Claude Code
|
||||
@ -58,9 +62,10 @@
|
||||
<div class="card-body">
|
||||
<div id="conversation-results">
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="fas fa-search fa-3x mb-3"></i>
|
||||
<h5>Search Your Conversations</h5>
|
||||
<p>Enter a search term to find relevant conversations with Claude.</p>
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading conversations...</span>
|
||||
</div>
|
||||
<p class="mt-2">Loading your recent conversations...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -73,6 +78,7 @@
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadProjects();
|
||||
loadRecentConversations();
|
||||
});
|
||||
|
||||
async function loadProjects() {
|
||||
@ -86,17 +92,91 @@ async function loadProjects() {
|
||||
option.textContent = project.name;
|
||||
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) {
|
||||
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) {
|
||||
if (event.key === 'Enter') {
|
||||
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() {
|
||||
const query = document.getElementById('search-query').value.trim();
|
||||
const projectId = document.getElementById('project-filter').value || null;
|
||||
@ -164,9 +244,11 @@ function displaySearchResults(results, query) {
|
||||
<i class="fas fa-clock me-1"></i>
|
||||
${ApiUtils.formatRelativeTime(result.timestamp)}
|
||||
</small>
|
||||
<span class="badge bg-success ms-2">
|
||||
${(result.relevance_score * 100).toFixed(0)}% match
|
||||
</span>
|
||||
${query ? `
|
||||
<span class="badge bg-success ms-2">
|
||||
${(result.relevance_score * 100).toFixed(0)}% match
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${result.user_prompt ? `
|
||||
@ -175,7 +257,7 @@ function displaySearchResults(results, query) {
|
||||
<i class="fas fa-user me-1"></i>
|
||||
You:
|
||||
</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>
|
||||
` : ''}
|
||||
|
||||
@ -185,11 +267,11 @@ function displaySearchResults(results, query) {
|
||||
<i class="fas fa-robot me-1"></i>
|
||||
Claude:
|
||||
</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>
|
||||
` : ''}
|
||||
|
||||
${result.context && result.context.length ? `
|
||||
${result.context && result.context.length && query ? `
|
||||
<div class="mt-2">
|
||||
<small class="text-muted">Context snippets:</small>
|
||||
${result.context.map(snippet => `
|
||||
|
540
app/dashboard/templates/docs/api-reference.html
Normal file
540
app/dashboard/templates/docs/api-reference.html
Normal 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>
|
||||
.then(r => r.json())<br>
|
||||
.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 %}
|
562
app/dashboard/templates/docs/data-import.html
Normal file
562
app/dashboard/templates/docs/data-import.html
Normal 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 %}
|
415
app/dashboard/templates/docs/docker-deployment.html
Normal file
415
app/dashboard/templates/docs/docker-deployment.html
Normal 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 <repository-url> 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 %}
|
784
app/dashboard/templates/docs/faq.html
Normal file
784
app/dashboard/templates/docs/faq.html
Normal 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 %}
|
354
app/dashboard/templates/docs/getting-started.html
Normal file
354
app/dashboard/templates/docs/getting-started.html
Normal 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 %}
|
531
app/dashboard/templates/docs/hook-reference.html
Normal file
531
app/dashboard/templates/docs/hook-reference.html
Normal 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 %}
|
483
app/dashboard/templates/docs/hook-setup.html
Normal file
483
app/dashboard/templates/docs/hook-setup.html
Normal 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 %}
|
362
app/dashboard/templates/docs/index.html
Normal file
362
app/dashboard/templates/docs/index.html
Normal 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 %}
|
505
app/dashboard/templates/import.html
Normal file
505
app/dashboard/templates/import.html
Normal 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 %}
|
353
app/dashboard/templates/project_stats.html
Normal file
353
app/dashboard/templates/project_stats.html
Normal 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 %}
|
214
app/dashboard/templates/project_timeline.html
Normal file
214
app/dashboard/templates/project_timeline.html
Normal 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 %}
|
@ -56,9 +56,16 @@ async def init_database():
|
||||
# Import all models to ensure they're registered
|
||||
from app.models import (
|
||||
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
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
|
@ -9,6 +9,12 @@ from .conversation import Conversation
|
||||
from .activity import Activity
|
||||
from .waiting_period import WaitingPeriod
|
||||
from .git_operation import GitOperation
|
||||
from .tool_call import ToolCall
|
||||
from .hooks import (
|
||||
HookEvent, ToolError, WaitingPeriodNew, PerformanceMetric,
|
||||
CodeQualityEvent, WorkflowEvent, LearningEvent,
|
||||
EnvironmentEvent, CollaborationEvent, ProjectIntelligence
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
@ -18,4 +24,15 @@ __all__ = [
|
||||
"Activity",
|
||||
"WaitingPeriod",
|
||||
"GitOperation",
|
||||
"ToolCall",
|
||||
"HookEvent",
|
||||
"ToolError",
|
||||
"WaitingPeriodNew",
|
||||
"PerformanceMetric",
|
||||
"CodeQualityEvent",
|
||||
"WorkflowEvent",
|
||||
"LearningEvent",
|
||||
"EnvironmentEvent",
|
||||
"CollaborationEvent",
|
||||
"ProjectIntelligence",
|
||||
]
|
@ -46,7 +46,7 @@ class Activity(Base, TimestampMixin):
|
||||
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
|
||||
|
||||
# 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)
|
||||
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]:
|
||||
"""Get the command that was executed (for Bash activities)."""
|
||||
if self.tool_name == "Bash" and self.metadata:
|
||||
return self.metadata.get("command")
|
||||
if self.tool_name == "Bash" and self.activity_metadata:
|
||||
return self.activity_metadata.get("command")
|
||||
return None
|
||||
|
||||
def get_search_pattern(self) -> Optional[str]:
|
||||
"""Get the search pattern (for Grep activities)."""
|
||||
if self.tool_name == "Grep" and self.metadata:
|
||||
return self.metadata.get("pattern")
|
||||
if self.tool_name == "Grep" and self.activity_metadata:
|
||||
return self.activity_metadata.get("pattern")
|
||||
return None
|
||||
|
||||
def get_task_type(self) -> Optional[str]:
|
||||
"""Get the task type (for Task activities)."""
|
||||
if self.tool_name == "Task" and self.metadata:
|
||||
return self.metadata.get("task_type")
|
||||
if self.tool_name == "Task" and self.activity_metadata:
|
||||
return self.activity_metadata.get("task_type")
|
||||
return None
|
225
app/models/hooks.py
Normal file
225
app/models/hooks.py
Normal 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")
|
@ -36,6 +36,7 @@ class Project(Base, TimestampMixin):
|
||||
total_time_minutes: 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)
|
||||
tool_calls_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
||||
|
||||
# Relationships
|
||||
sessions: Mapped[List["Session"]] = relationship(
|
||||
@ -60,10 +61,11 @@ class Project(Base, TimestampMixin):
|
||||
return self.languages[0]
|
||||
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."""
|
||||
self.total_sessions += 1
|
||||
self.total_time_minutes += session_duration_minutes
|
||||
self.files_modified_count += files_count
|
||||
self.lines_changed_count += lines_count
|
||||
self.tool_calls_count += tool_calls_count
|
||||
self.last_session = func.now()
|
@ -74,6 +74,12 @@ class Session(Base, TimestampMixin):
|
||||
cascade="all, delete-orphan",
|
||||
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:
|
||||
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)
|
||||
for activity in self.activities
|
||||
)
|
||||
total_tool_calls = len(self.tool_calls)
|
||||
self.project.update_stats(
|
||||
session_duration_minutes=self.duration_minutes or 0,
|
||||
files_count=unique_files,
|
||||
lines_count=total_lines
|
||||
lines_count=total_lines,
|
||||
tool_calls_count=total_tool_calls
|
||||
)
|
||||
|
||||
def add_activity(self) -> None:
|
||||
|
33
app/models/tool_call.py
Normal file
33
app/models/tool_call.py
Normal 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
9
claude-hooks-basic.json
Normal 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\"}'"
|
||||
}
|
||||
}
|
50
claude-hooks-comprehensive.json
Normal file
50
claude-hooks-comprehensive.json
Normal 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"
|
||||
}
|
||||
}
|
25
claude-hooks-developer.json
Normal file
25
claude-hooks-developer.json
Normal 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"
|
||||
}
|
||||
}
|
16
claude-hooks-essential.json
Normal file
16
claude-hooks-essential.json
Normal 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"
|
||||
}
|
||||
}
|
30
claude-hooks-power_user.json
Normal file
30
claude-hooks-power_user.json
Normal 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"
|
||||
}
|
||||
}
|
29
claude-hooks-research.json
Normal file
29
claude-hooks-research.json
Normal 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
29
claude-hooks-team.json
Normal 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
53
docker-compose.yml
Normal 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
123
docker-deploy.sh
Executable 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
281
generate_hook_config.py
Normal 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
154
import_claude_data.py
Normal 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
49
init_db.py
Normal 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
19
main.py
@ -9,7 +9,7 @@ from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.responses import RedirectResponse
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -18,11 +18,21 @@ async def lifespan(app: FastAPI):
|
||||
"""Application lifespan management."""
|
||||
# Startup
|
||||
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
|
||||
|
||||
# Shutdown
|
||||
print("Shutting down...")
|
||||
await close_database()
|
||||
try:
|
||||
await close_database()
|
||||
except Exception as e:
|
||||
print(f"Error during shutdown: {e}")
|
||||
|
||||
|
||||
# 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(projects.router, prefix="/api", tags=["Projects"])
|
||||
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
|
||||
app.include_router(dashboard_router, tags=["Dashboard"])
|
||||
|
124
pyproject.toml
Normal file
124
pyproject.toml
Normal 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",
|
||||
]
|
109
recalculate_project_stats.py
Normal file
109
recalculate_project_stats.py
Normal 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())
|
@ -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
|
Loading…
x
Reference in New Issue
Block a user