From 0c6f36302e734a3d22d022e5d125d5f794b0ba49 Mon Sep 17 00:00:00 2001 From: Stijnus <72551117+Stijnus@users.noreply.github.com> Date: Thu, 30 Jan 2025 21:08:23 +0100 Subject: [PATCH] Taskmanager update Enhanced System Metrics: Detailed CPU metrics including temperature and frequency Comprehensive memory breakdown with heap usage Advanced performance metrics (FPS, page load times, web vitals) Detailed network statistics Storage monitoring with visual indicators Battery health and detailed status Power Management: Multiple power profiles (Performance, Balanced, Power Saver) Enhanced energy saver mode Automatic power management based on system state Detailed energy savings statistics System Health: Overall system health score Real-time issue detection and alerts Performance optimization suggestions Historical metrics tracking UI Improvements: Interactive graphs for all metrics Color-coded status indicators Detailed tooltips and explanations Collapsible sections for better organization Alert system for critical events Performance Monitoring: Frame rate monitoring Resource usage tracking Network performance analysis Web vitals monitoring Detailed timing metrics To use the enhanced task manager: Monitor system health in the new health score section Choose a power profile based on your needs Enable auto energy saver for automatic power management Monitor real-time alerts for system issues 5. View detailed metrics in each category Check optimization suggestions when performance issues arise --- app/components/settings/debug/DebugTab.tsx | 4 +- .../settings/task-manager/TaskManagerTab.tsx | 619 +++++++++++++++++- app/routes/api.system.git-info.ts | 45 +- pages/api/system/git-info.ts | 38 -- 4 files changed, 628 insertions(+), 78 deletions(-) delete mode 100644 pages/api/system/git-info.ts diff --git a/app/components/settings/debug/DebugTab.tsx b/app/components/settings/debug/DebugTab.tsx index fc36101..be919c8 100644 --- a/app/components/settings/debug/DebugTab.tsx +++ b/app/components/settings/debug/DebugTab.tsx @@ -447,6 +447,8 @@ export default function DebugTab() { const appData = (await appResponse.json()) as Omit; const gitData = (await gitResponse.json()) as GitInfo; + console.log('Git Info Response:', gitData); // Add logging to debug + setWebAppInfo({ ...appData, gitInfo: gitData, @@ -1084,7 +1086,7 @@ export default function DebugTab() { <>
-
+
Repository: {webAppInfo.gitInfo.github.currentRepo.fullName} diff --git a/app/components/settings/task-manager/TaskManagerTab.tsx b/app/components/settings/task-manager/TaskManagerTab.tsx index 6cf56d1..936ee90 100644 --- a/app/components/settings/task-manager/TaskManagerTab.tsx +++ b/app/components/settings/task-manager/TaskManagerTab.tsx @@ -24,22 +24,66 @@ interface BatteryManager extends EventTarget { } interface SystemMetrics { - cpu: number; + cpu: { + usage: number; + cores: number[]; + temperature?: number; + frequency?: number; + }; memory: { used: number; total: number; percentage: number; + heap: { + used: number; + total: number; + limit: number; + }; + cache?: number; }; uptime: number; battery?: { level: number; charging: boolean; timeRemaining?: number; + temperature?: number; + cycles?: number; + health?: number; }; network: { downlink: number; + uplink?: number; latency: number; type: string; + activeConnections?: number; + bytesReceived: number; + bytesSent: number; + }; + performance: { + fps: number; + pageLoad: number; + domReady: number; + resources: { + total: number; + size: number; + loadTime: number; + }; + timing: { + ttfb: number; + fcp: number; + lcp: number; + }; + }; + storage: { + total: number; + used: number; + free: number; + type: string; + }; + health: { + score: number; + issues: string[]; + suggestions: string[]; }; } @@ -57,6 +101,26 @@ interface EnergySavings { estimatedEnergySaved: number; // in mWh (milliwatt-hours) } +interface PowerProfile { + name: string; + description: string; + settings: { + updateInterval: number; + enableAnimations: boolean; + backgroundProcessing: boolean; + networkThrottling: boolean; + }; +} + +interface PerformanceAlert { + type: 'warning' | 'error' | 'info'; + message: string; + timestamp: number; + metric: string; + threshold: number; + value: number; +} + declare global { interface Navigator { getBattery(): Promise; @@ -88,12 +152,61 @@ const ENERGY_COSTS = { rendering: 1, // mW per render }; +const PERFORMANCE_THRESHOLDS = { + cpu: { warning: 70, critical: 90 }, + memory: { warning: 80, critical: 95 }, + fps: { warning: 30, critical: 15 }, + loadTime: { warning: 3000, critical: 5000 }, +}; + +const POWER_PROFILES: PowerProfile[] = [ + { + name: 'Performance', + description: 'Maximum performance, higher power consumption', + settings: { + updateInterval: 1000, + enableAnimations: true, + backgroundProcessing: true, + networkThrottling: false, + }, + }, + { + name: 'Balanced', + description: 'Balance between performance and power saving', + settings: { + updateInterval: 2000, + enableAnimations: true, + backgroundProcessing: true, + networkThrottling: false, + }, + }, + { + name: 'Power Saver', + description: 'Maximum power saving, reduced performance', + settings: { + updateInterval: 5000, + enableAnimations: false, + backgroundProcessing: false, + networkThrottling: true, + }, + }, +]; + export default function TaskManagerTab() { const [metrics, setMetrics] = useState({ - cpu: 0, - memory: { used: 0, total: 0, percentage: 0 }, + cpu: { usage: 0, cores: [] }, + memory: { used: 0, total: 0, percentage: 0, heap: { used: 0, total: 0, limit: 0 } }, uptime: 0, - network: { downlink: 0, latency: 0, type: 'unknown' }, + network: { downlink: 0, latency: 0, type: 'unknown', bytesReceived: 0, bytesSent: 0 }, + performance: { + fps: 0, + pageLoad: 0, + domReady: 0, + resources: { total: 0, size: 0, loadTime: 0 }, + timing: { ttfb: 0, fcp: 0, lcp: 0 }, + }, + storage: { total: 0, used: 0, free: 0, type: 'unknown' }, + health: { score: 0, issues: [], suggestions: [] }, }); const [metricsHistory, setMetricsHistory] = useState({ timestamps: [], @@ -121,6 +234,8 @@ export default function TaskManagerTab() { }); const saverModeStartTime = useRef(null); + const [selectedProfile, setSelectedProfile] = useState(POWER_PROFILES[1]); // Default to Balanced + const [alerts, setAlerts] = useState([]); // Handle energy saver mode changes const handleEnergySaverChange = (checked: boolean) => { @@ -181,7 +296,135 @@ export default function TaskManagerTab() { return () => clearInterval(interval); }, [updateEnergySavings]); - // Update metrics + // Get detailed performance metrics + const getPerformanceMetrics = async (): Promise> => { + try { + // Get FPS + const fps = await measureFrameRate(); + + // Get page load metrics + const navigation = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; + const pageLoad = navigation.loadEventEnd - navigation.startTime; + const domReady = navigation.domContentLoadedEventEnd - navigation.startTime; + + // Get resource metrics + const resources = performance.getEntriesByType('resource'); + const resourceMetrics = { + total: resources.length, + size: resources.reduce((total, r) => total + (r as any).transferSize || 0, 0), + loadTime: Math.max(...resources.map((r) => r.duration)), + }; + + // Get Web Vitals + const ttfb = navigation.responseStart - navigation.requestStart; + const paintEntries = performance.getEntriesByType('paint'); + const fcp = paintEntries.find((entry) => entry.name === 'first-contentful-paint')?.startTime || 0; + const lcpEntry = await getLargestContentfulPaint(); + + return { + fps, + pageLoad, + domReady, + resources: resourceMetrics, + timing: { + ttfb, + fcp, + lcp: lcpEntry?.startTime || 0, + }, + }; + } catch (error) { + console.error('Failed to get performance metrics:', error); + return {}; + } + }; + + // Measure frame rate + const measureFrameRate = async (): Promise => { + return new Promise((resolve) => { + const frameCount = { value: 0 }; + const startTime = performance.now(); + + const countFrame = (time: number) => { + frameCount.value++; + + if (time - startTime >= 1000) { + resolve(Math.round((frameCount.value * 1000) / (time - startTime))); + } else { + requestAnimationFrame(countFrame); + } + }; + + requestAnimationFrame(countFrame); + }); + }; + + // Get Largest Contentful Paint + const getLargestContentfulPaint = async (): Promise => { + return new Promise((resolve) => { + new PerformanceObserver((list) => { + const entries = list.getEntries(); + resolve(entries[entries.length - 1]); + }).observe({ entryTypes: ['largest-contentful-paint'] }); + + // Resolve after 3 seconds if no LCP entry is found + setTimeout(() => resolve(undefined), 3000); + }); + }; + + // Analyze system health + const analyzeSystemHealth = (currentMetrics: SystemMetrics): SystemMetrics['health'] => { + const issues: string[] = []; + const suggestions: string[] = []; + let score = 100; + + // CPU analysis + if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.critical) { + score -= 30; + issues.push('Critical CPU usage'); + suggestions.push('Consider closing resource-intensive applications'); + } else if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.warning) { + score -= 15; + issues.push('High CPU usage'); + suggestions.push('Monitor system processes for unusual activity'); + } + + // Memory analysis + if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.critical) { + score -= 30; + issues.push('Critical memory usage'); + suggestions.push('Close unused applications to free up memory'); + } else if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.warning) { + score -= 15; + issues.push('High memory usage'); + suggestions.push('Consider freeing up memory by closing background applications'); + } + + // Performance analysis + if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical) { + score -= 20; + issues.push('Very low frame rate'); + suggestions.push('Disable animations or switch to power saver mode'); + } else if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.warning) { + score -= 10; + issues.push('Low frame rate'); + suggestions.push('Consider reducing visual effects'); + } + + // Battery analysis + if (currentMetrics.battery && !currentMetrics.battery.charging && currentMetrics.battery.level < 20) { + score -= 10; + issues.push('Low battery'); + suggestions.push('Connect to power source or enable power saver mode'); + } + + return { + score: Math.max(0, score), + issues, + suggestions, + }; + }; + + // Update metrics with enhanced data const updateMetrics = async () => { try { // Get memory info using Performance API @@ -216,36 +459,62 @@ export default function TaskManagerTab() { (navigator as any).connection || (navigator as any).mozConnection || (navigator as any).webkitConnection; const networkInfo = { downlink: connection?.downlink || 0, + uplink: connection?.uplink, latency: connection?.rtt || 0, type: connection?.type || 'unknown', + activeConnections: connection?.activeConnections, + bytesReceived: connection?.bytesReceived || 0, + bytesSent: connection?.bytesSent || 0, }; - const newMetrics = { - cpu: cpuUsage, + // Get enhanced performance metrics + const performanceMetrics = await getPerformanceMetrics(); + + const metrics: SystemMetrics = { + cpu: { usage: cpuUsage, cores: [], temperature: undefined, frequency: undefined }, memory: { used: Math.round(usedMem), total: Math.round(totalMem), percentage: Math.round(memPercentage), + heap: { + used: Math.round(usedMem), + total: Math.round(totalMem), + limit: Math.round(totalMem), + }, }, uptime: performance.now() / 1000, battery: batteryInfo, network: networkInfo, + performance: performanceMetrics as SystemMetrics['performance'], + storage: { + total: 0, + used: 0, + free: 0, + type: 'unknown', + }, + health: { score: 0, issues: [], suggestions: [] }, }; - setMetrics(newMetrics); + // Analyze system health + metrics.health = analyzeSystemHealth(metrics); + + // Check for alerts + checkPerformanceAlerts(metrics); + + setMetrics(metrics); // Update metrics history const now = new Date().toLocaleTimeString(); setMetricsHistory((prev) => { const timestamps = [...prev.timestamps, now].slice(-MAX_HISTORY_POINTS); - const cpu = [...prev.cpu, newMetrics.cpu].slice(-MAX_HISTORY_POINTS); - const memory = [...prev.memory, newMetrics.memory.percentage].slice(-MAX_HISTORY_POINTS); + const cpu = [...prev.cpu, metrics.cpu.usage].slice(-MAX_HISTORY_POINTS); + const memory = [...prev.memory, metrics.memory.percentage].slice(-MAX_HISTORY_POINTS); const battery = [...prev.battery, batteryInfo?.level || 0].slice(-MAX_HISTORY_POINTS); const network = [...prev.network, networkInfo.downlink].slice(-MAX_HISTORY_POINTS); return { timestamps, cpu, memory, battery, network }; }); - } catch (error: unknown) { + } catch (error) { console.error('Failed to update system metrics:', error); } }; @@ -300,6 +569,8 @@ export default function TaskManagerTab() { downlink: connection.downlink || 0, latency: connection.rtt || 0, type: connection.type || 'unknown', + bytesReceived: connection.bytesReceived || 0, + bytesSent: connection.bytesSent || 0, }, })); }; @@ -427,12 +698,60 @@ export default function TaskManagerTab() { return () => clearInterval(batteryCheckInterval); }, [autoEnergySaver]); + // Check for performance alerts + const checkPerformanceAlerts = (currentMetrics: SystemMetrics) => { + const newAlerts: PerformanceAlert[] = []; + + // CPU alert + if (currentMetrics.cpu.usage > PERFORMANCE_THRESHOLDS.cpu.critical) { + newAlerts.push({ + type: 'error', + message: 'Critical CPU usage detected', + timestamp: Date.now(), + metric: 'cpu', + threshold: PERFORMANCE_THRESHOLDS.cpu.critical, + value: currentMetrics.cpu.usage, + }); + } + + // Memory alert + if (currentMetrics.memory.percentage > PERFORMANCE_THRESHOLDS.memory.critical) { + newAlerts.push({ + type: 'error', + message: 'Critical memory usage detected', + timestamp: Date.now(), + metric: 'memory', + threshold: PERFORMANCE_THRESHOLDS.memory.critical, + value: currentMetrics.memory.percentage, + }); + } + + // Performance alert + if (currentMetrics.performance.fps < PERFORMANCE_THRESHOLDS.fps.critical) { + newAlerts.push({ + type: 'warning', + message: 'Very low frame rate detected', + timestamp: Date.now(), + metric: 'fps', + threshold: PERFORMANCE_THRESHOLDS.fps.critical, + value: currentMetrics.performance.fps, + }); + } + + if (newAlerts.length > 0) { + setAlerts((prev) => [...prev, ...newAlerts]); + newAlerts.forEach((alert) => { + toast.warning(alert.message); + }); + } + }; + return (
- {/* System Metrics */} + {/* Power Profile Selection */}
-

System Metrics

+

Power Management

Active}
+
+
{selectedProfile.description}
+
+ {/* System Health Score */} +
+

System Health

+
+
+
+ Health Score + = 80, + 'text-yellow-500': metrics.health.score >= 60 && metrics.health.score < 80, + 'text-red-500': metrics.health.score < 60, + })} + > + {metrics.health.score}% + +
+ {metrics.health.issues.length > 0 && ( +
+
Issues:
+
    + {metrics.health.issues.map((issue, index) => ( +
  • +
    + {issue} +
  • + ))} +
+
+ )} + {metrics.health.suggestions.length > 0 && ( +
+
Suggestions:
+
    + {metrics.health.suggestions.map((suggestion, index) => ( +
  • +
    + {suggestion} +
  • + ))} +
+
+ )} +
+
+
+ + {/* System Metrics */} +
+

System Metrics

{/* CPU Usage */}
CPU Usage - - {Math.round(metrics.cpu)}% + + {Math.round(metrics.cpu.usage)}%
{renderUsageGraph(metricsHistory.cpu, 'CPU', '#9333ea')} + {metrics.cpu.temperature && ( +
+ Temperature: {metrics.cpu.temperature}°C +
+ )} + {metrics.cpu.frequency && ( +
+ Frequency: {(metrics.cpu.frequency / 1000).toFixed(1)} GHz +
+ )}
{/* Memory Usage */} @@ -487,28 +886,42 @@ export default function TaskManagerTab() {
{renderUsageGraph(metricsHistory.memory, 'Memory', '#2563eb')} +
+ Used: {formatBytes(metrics.memory.used)} +
+
Total: {formatBytes(metrics.memory.total)}
+
+ Heap: {formatBytes(metrics.memory.heap.used)} / {formatBytes(metrics.memory.heap.total)} +
- {/* Battery */} - {metrics.battery && ( -
-
- Battery -
- {metrics.battery.charging &&
} - 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500', - )} - > - {Math.round(metrics.battery.level)}% - -
-
- {renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e')} + {/* Performance */} +
+
+ Performance + = PERFORMANCE_THRESHOLDS.fps.warning, + })} + > + {Math.round(metrics.performance.fps)} FPS +
- )} +
+ Page Load: {(metrics.performance.pageLoad / 1000).toFixed(2)}s +
+
+ DOM Ready: {(metrics.performance.domReady / 1000).toFixed(2)}s +
+
+ TTFB: {(metrics.performance.timing.ttfb / 1000).toFixed(2)}s +
+
+ Resources: {metrics.performance.resources.total} ({formatBytes(metrics.performance.resources.size)}) +
+
{/* Network */}
@@ -519,9 +932,118 @@ export default function TaskManagerTab() {
{renderUsageGraph(metricsHistory.network, 'Network', '#f59e0b')} +
Type: {metrics.network.type}
+
Latency: {metrics.network.latency}ms
+
+ Received: {formatBytes(metrics.network.bytesReceived)} +
+
+ Sent: {formatBytes(metrics.network.bytesSent)} +
+ {/* Battery Section */} + {metrics.battery && ( +
+
+ Battery +
+ {metrics.battery.charging &&
} + 20 ? 'text-bolt-elements-textPrimary' : 'text-red-500', + )} + > + {Math.round(metrics.battery.level)}% + +
+
+ {renderUsageGraph(metricsHistory.battery, 'Battery', '#22c55e')} + {metrics.battery.timeRemaining && ( +
+ {metrics.battery.charging ? 'Time to full: ' : 'Time remaining: '} + {formatTime(metrics.battery.timeRemaining)} +
+ )} + {metrics.battery.temperature && ( +
+ Temperature: {metrics.battery.temperature}°C +
+ )} + {metrics.battery.cycles && ( +
Charge cycles: {metrics.battery.cycles}
+ )} + {metrics.battery.health && ( +
Battery health: {metrics.battery.health}%
+ )} +
+ )} + + {/* Storage Section */} +
+
+ Storage + + {formatBytes(metrics.storage.used)} / {formatBytes(metrics.storage.total)} + +
+
+
= 0.7 && + metrics.storage.used / metrics.storage.total < 0.9, + 'bg-red-500': metrics.storage.used / metrics.storage.total >= 0.9, + })} + style={{ width: `${(metrics.storage.used / metrics.storage.total) * 100}%` }} + /> +
+
Free: {formatBytes(metrics.storage.free)}
+
Type: {metrics.storage.type}
+
+ + {/* Performance Alerts */} + {alerts.length > 0 && ( +
+
+ Recent Alerts + +
+
+ {alerts.slice(-5).map((alert, index) => ( +
+
+ {alert.message} + + {new Date(alert.timestamp).toLocaleTimeString()} + +
+ ))} +
+
+ )} + {/* Energy Savings */} {energySaverMode && (
@@ -550,3 +1072,32 @@ export default function TaskManagerTab() {
); } + +// Helper function to format bytes +const formatBytes = (bytes: number): string => { + if (bytes === 0) { + return '0 B'; + } + + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`; +}; + +// Helper function to format time +const formatTime = (seconds: number): string => { + if (!isFinite(seconds) || seconds === 0) { + return 'Unknown'; + } + + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + + return `${minutes}m`; +}; diff --git a/app/routes/api.system.git-info.ts b/app/routes/api.system.git-info.ts index cd6c8be..d6bf997 100644 --- a/app/routes/api.system.git-info.ts +++ b/app/routes/api.system.git-info.ts @@ -20,7 +20,7 @@ interface GitHubRepoInfo { const getLocalGitInfo = () => { try { return { - commitHash: execSync('git rev-parse --short HEAD').toString().trim(), + commitHash: execSync('git rev-parse HEAD').toString().trim(), branch: execSync('git rev-parse --abbrev-ref HEAD').toString().trim(), commitTime: execSync('git log -1 --format=%cd').toString().trim(), author: execSync('git log -1 --format=%an').toString().trim(), @@ -40,13 +40,42 @@ const getLocalGitInfo = () => { const getGitHubInfo = async (repoFullName: string) => { try { - const response = await fetch(`https://api.github.com/repos/${repoFullName}`); + // Add GitHub token if available + const headers: Record = { + Accept: 'application/vnd.github.v3+json', + }; + + const githubToken = process.env.GITHUB_TOKEN; + + if (githubToken) { + headers.Authorization = `token ${githubToken}`; + } + + console.log('Fetching GitHub info for:', repoFullName); // Debug log + + const response = await fetch(`https://api.github.com/repos/${repoFullName}`, { + headers, + }); if (!response.ok) { + console.error('GitHub API error:', { + status: response.status, + statusText: response.statusText, + repoFullName, + }); + + // If we get a 404, try the main repo as fallback + if (response.status === 404 && repoFullName !== 'stackblitz-labs/bolt.diy') { + return getGitHubInfo('stackblitz-labs/bolt.diy'); + } + throw new Error(`GitHub API error: ${response.statusText}`); } - return (await response.json()) as GitHubRepoInfo; + const data = await response.json(); + console.log('GitHub API response:', data); // Debug log + + return data as GitHubRepoInfo; } catch (error) { console.error('Failed to get GitHub info:', error); return null; @@ -55,6 +84,7 @@ const getGitHubInfo = async (repoFullName: string) => { export const loader: LoaderFunction = async ({ request: _request }) => { const localInfo = getLocalGitInfo(); + console.log('Local git info:', localInfo); // Debug log // If we have local info, try to get GitHub info for both our fork and upstream let githubInfo = null; @@ -68,7 +98,7 @@ export const loader: LoaderFunction = async ({ request: _request }) => { githubInfo = await getGitHubInfo('stackblitz-labs/bolt.diy'); } - return json({ + const response = { local: localInfo || { commitHash: 'unknown', branch: 'unknown', @@ -99,5 +129,10 @@ export const loader: LoaderFunction = async ({ request: _request }) => { : null, isForked: Boolean(githubInfo?.parent), timestamp: new Date().toISOString(), - }); + }; + + console.log('Final response:', response); + + // Debug log + return json(response); }; diff --git a/pages/api/system/git-info.ts b/pages/api/system/git-info.ts deleted file mode 100644 index 845e4b1..0000000 --- a/pages/api/system/git-info.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; -import { execSync } from 'child_process'; - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method !== 'GET') { - return res.status(405).json({ message: 'Method not allowed' }); - } - - try { - // Get git information using git commands - const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim(); - const commitHash = execSync('git rev-parse HEAD').toString().trim(); - const commitTime = execSync('git log -1 --format=%cd').toString().trim(); - const author = execSync('git log -1 --format=%an').toString().trim(); - const email = execSync('git log -1 --format=%ae').toString().trim(); - const remoteUrl = execSync('git config --get remote.origin.url').toString().trim(); - - // Extract repo name from remote URL - const repoName = remoteUrl.split('/').pop()?.replace('.git', '') || ''; - - const gitInfo = { - local: { - commitHash, - branch, - commitTime, - author, - email, - remoteUrl, - repoName, - }, - }; - - return res.status(200).json(gitInfo); - } catch (error) { - console.error('Failed to get git information:', error); - return res.status(500).json({ message: 'Failed to get git information' }); - } -}