Ryan Malloy 44ed9936b7 Initial commit: Claude Code Project Tracker
Add comprehensive development intelligence system that tracks:
- Development sessions with automatic start/stop
- Full conversation history with semantic search
- Tool usage and file operation analytics
- Think time and engagement analysis
- Git activity correlation
- Learning pattern recognition
- Productivity insights and metrics

Features:
- FastAPI backend with SQLite database
- Modern web dashboard with interactive charts
- Claude Code hook integration for automatic tracking
- Comprehensive test suite with 100+ tests
- Complete API documentation (OpenAPI/Swagger)
- Privacy-first design with local data storage

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-11 02:59:21 -06:00

251 lines
7.8 KiB
JavaScript

/**
* API Client for Claude Code Project Tracker
*/
class ApiClient {
constructor(baseUrl = '') {
this.baseUrl = baseUrl;
}
async request(endpoint, options = {}) {
const url = `${this.baseUrl}/api${endpoint}`;
const config = {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
};
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error(`API request failed: ${endpoint}`, error);
throw error;
}
}
// Projects API
async getProjects(limit = 50, offset = 0) {
return this.request(`/projects?limit=${limit}&offset=${offset}`);
}
async getProject(projectId) {
return this.request(`/projects/${projectId}`);
}
async getProjectTimeline(projectId, startDate = null, endDate = null) {
let url = `/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()}`;
}
return this.request(url);
}
async getProjectStats(projectId, days = 30) {
return this.request(`/projects/${projectId}/stats?days=${days}`);
}
// Analytics API
async getProductivityMetrics(projectId = null, days = 30) {
let url = `/analytics/productivity?days=${days}`;
if (projectId) {
url += `&project_id=${projectId}`;
}
return this.request(url);
}
async getDevelopmentPatterns(projectId = null, days = 30) {
let url = `/analytics/patterns?days=${days}`;
if (projectId) {
url += `&project_id=${projectId}`;
}
return this.request(url);
}
async getLearningInsights(projectId = null, days = 30) {
let url = `/analytics/learning?days=${days}`;
if (projectId) {
url += `&project_id=${projectId}`;
}
return this.request(url);
}
async getAnalyticsSummary(projectId = null) {
let url = '/analytics/summary';
if (projectId) {
url += `?project_id=${projectId}`;
}
return this.request(url);
}
// Conversations API
async searchConversations(query, projectId = null, limit = 20) {
let url = `/conversations/search?query=${encodeURIComponent(query)}&limit=${limit}`;
if (projectId) {
url += `&project_id=${projectId}`;
}
return this.request(url);
}
async getConversation(conversationId) {
return this.request(`/conversations/${conversationId}`);
}
// Activities API
async getActivities(sessionId = null, toolName = null, limit = 50, offset = 0) {
let url = `/activities?limit=${limit}&offset=${offset}`;
if (sessionId) url += `&session_id=${sessionId}`;
if (toolName) url += `&tool_name=${toolName}`;
return this.request(url);
}
async getToolUsageStats(sessionId = null, projectId = null, days = 30) {
let url = `/activities/stats/tools?days=${days}`;
if (sessionId) url += `&session_id=${sessionId}`;
if (projectId) url += `&project_id=${projectId}`;
return this.request(url);
}
async getLanguageUsageStats(projectId = null, days = 30) {
let url = `/activities/stats/languages?days=${days}`;
if (projectId) url += `&project_id=${projectId}`;
return this.request(url);
}
// Waiting Periods API
async getWaitingPeriods(sessionId = null, projectId = null, limit = 50, offset = 0) {
let url = `/waiting/periods?limit=${limit}&offset=${offset}`;
if (sessionId) url += `&session_id=${sessionId}`;
if (projectId) url += `&project_id=${projectId}`;
return this.request(url);
}
async getEngagementStats(sessionId = null, projectId = null, days = 7) {
let url = `/waiting/stats/engagement?days=${days}`;
if (sessionId) url += `&session_id=${sessionId}`;
if (projectId) url += `&project_id=${projectId}`;
return this.request(url);
}
// Git Operations API
async getGitOperations(sessionId = null, projectId = null, operation = null, limit = 50, offset = 0) {
let url = `/git/operations?limit=${limit}&offset=${offset}`;
if (sessionId) url += `&session_id=${sessionId}`;
if (projectId) url += `&project_id=${projectId}`;
if (operation) url += `&operation=${operation}`;
return this.request(url);
}
async getCommitStats(projectId = null, days = 30) {
let url = `/git/stats/commits?days=${days}`;
if (projectId) url += `&project_id=${projectId}`;
return this.request(url);
}
async getGitActivityStats(projectId = null, days = 30) {
let url = `/git/stats/activity?days=${days}`;
if (projectId) url += `&project_id=${projectId}`;
return this.request(url);
}
// Sessions API
async getSession(sessionId) {
return this.request(`/sessions/${sessionId}`);
}
// Health check
async healthCheck() {
return this.request('/../health');
}
}
// Create global API client instance
const apiClient = new ApiClient();
// Utility functions for common operations
const ApiUtils = {
formatDuration: (minutes) => {
if (minutes < 60) {
return `${minutes}m`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
return remainingMinutes > 0 ? `${hours}h ${remainingMinutes}m` : `${hours}h`;
},
formatDate: (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit'
});
},
formatRelativeTime: (dateString) => {
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffMinutes = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMinutes / 60);
const diffDays = Math.floor(diffHours / 24);
if (diffMinutes < 1) return 'just now';
if (diffMinutes < 60) return `${diffMinutes}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
if (diffDays < 7) return `${diffDays}d ago`;
return date.toLocaleDateString();
},
getToolIcon: (toolName) => {
const icons = {
'Edit': 'fas fa-edit',
'Write': 'fas fa-file-alt',
'Read': 'fas fa-eye',
'Bash': 'fas fa-terminal',
'Grep': 'fas fa-search',
'Glob': 'fas fa-folder-open',
'Task': 'fas fa-cogs',
'WebFetch': 'fas fa-globe'
};
return icons[toolName] || 'fas fa-tool';
},
getToolColor: (toolName) => {
const colors = {
'Edit': 'success',
'Write': 'primary',
'Read': 'info',
'Bash': 'secondary',
'Grep': 'warning',
'Glob': 'info',
'Task': 'dark',
'WebFetch': 'primary'
};
return colors[toolName] || 'secondary';
},
getEngagementBadge: (score) => {
if (score >= 0.8) return { class: 'success', text: 'High' };
if (score >= 0.5) return { class: 'warning', text: 'Medium' };
return { class: 'danger', text: 'Low' };
},
truncateText: (text, maxLength = 100) => {
if (!text || text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
}
};