/** * 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) + '...'; } };