UNPKG

woaru

Version:

Universal Project Setup Autopilot - Analyze and automatically configure development tools for ANY programming language

208 lines 7.2 kB
import fs from 'fs-extra'; import * as path from 'path'; import * as os from 'os'; import { t } from '../config/i18n.js'; export class UsageTracker { static instance; usageFile; stats = {}; constructor() { // Store usage data in ~/.woaru/usage.json const woaruDir = path.join(os.homedir(), '.woaru'); this.usageFile = path.join(woaruDir, 'usage.json'); // Load stats asynchronously when first accessed this.loadStats().catch(() => { // Ignore errors during initialization this.stats = {}; }); } static getInstance() { if (!UsageTracker.instance) { try { UsageTracker.instance = new UsageTracker(); } catch { console.warn(t('usage_tracker.failed_to_initialize')); // Create a fallback instance with basic functionality UsageTracker.instance = Object.create(UsageTracker.prototype); UsageTracker.instance.stats = {}; UsageTracker.instance.usageFile = path.join(os.homedir(), '.woaru', 'usage.json'); } } return UsageTracker.instance; } /** * Load usage statistics from disk */ async loadStats() { try { if (await fs.pathExists(this.usageFile)) { // Check if file is empty first const fileStats = await fs.stat(this.usageFile); if (fileStats.size === 0) { console.warn(t('usage_tracker.file_empty')); this.stats = {}; return; } // Try to read the file content manually first const fileContent = await fs.readFile(this.usageFile, 'utf-8'); if (!fileContent.trim()) { console.warn(t('usage_tracker.file_empty')); this.stats = {}; return; } // Now try to parse as JSON const data = JSON.parse(fileContent); // Validate that the data is a valid object if (data && typeof data === 'object' && !Array.isArray(data)) { this.stats = data; } else { console.warn(t('usage_tracker.invalid_format')); this.stats = {}; } } } catch (error) { if (error instanceof SyntaxError) { console.warn(t('usage_tracker.invalid_json')); } else { console.warn(t('usage_tracker.failed_to_load'), error instanceof Error ? error.message : error); } // Reset stats and recreate file this.stats = {}; // Try to recreate the file with valid empty JSON try { await fs.ensureDir(path.dirname(this.usageFile)); await fs.writeJson(this.usageFile, {}, { spaces: 2 }); } catch (writeError) { console.warn(t('usage_tracker.could_not_recreate'), writeError instanceof Error ? writeError.message : writeError); } } } /** * Save usage statistics to disk */ async saveStats() { try { await fs.ensureDir(path.dirname(this.usageFile)); await fs.writeJson(this.usageFile, this.stats, { spaces: 2 }); } catch (error) { console.warn(t('usage_tracker.failed_to_save'), error); } } /** * Track a successful LLM API request */ async trackRequest(llmId, tokensUsed = 0, estimatedCost = 0) { if (!this.stats[llmId]) { this.stats[llmId] = { totalRequests: 0, totalTokensUsed: 0, totalCost: 0, lastUsed: new Date().toISOString(), firstUsed: new Date().toISOString(), errorCount: 0, }; } const now = new Date().toISOString(); this.stats[llmId].totalRequests += 1; this.stats[llmId].totalTokensUsed += tokensUsed; this.stats[llmId].totalCost += estimatedCost; this.stats[llmId].lastUsed = now; await this.saveStats(); } /** * Track a failed LLM API request */ async trackError(llmId) { if (!this.stats[llmId]) { this.stats[llmId] = { totalRequests: 0, totalTokensUsed: 0, totalCost: 0, lastUsed: new Date().toISOString(), firstUsed: new Date().toISOString(), errorCount: 0, }; } this.stats[llmId].errorCount += 1; this.stats[llmId].lastUsed = new Date().toISOString(); await this.saveStats(); } /** * Ensure stats are loaded before accessing */ async ensureLoaded() { try { await this.loadStats(); } catch { console.warn(t('usage_tracker.failed_ensure_load')); this.stats = {}; } } /** * Get usage statistics for a specific LLM */ async getUsageStats(llmId) { await this.ensureLoaded(); return this.stats[llmId] || null; } /** * Get all usage statistics */ async getAllUsageStats() { await this.ensureLoaded(); return { ...this.stats }; } /** * Get total usage across all LLMs */ async getTotalUsage() { await this.ensureLoaded(); const allStats = Object.values(this.stats); return { totalRequests: allStats.reduce((sum, stat) => sum + stat.totalRequests, 0), totalTokensUsed: allStats.reduce((sum, stat) => sum + stat.totalTokensUsed, 0), totalCost: allStats.reduce((sum, stat) => sum + stat.totalCost, 0), totalErrors: allStats.reduce((sum, stat) => sum + stat.errorCount, 0), activeProviders: allStats.filter(stat => stat.totalRequests > 0).length, }; } /** * Reset all usage statistics */ async resetStats() { this.stats = {}; await this.saveStats(); } /** * Get formatted usage summary for display */ async getFormattedSummary() { const total = await this.getTotalUsage(); const lines = []; lines.push(`Total Requests: ${total.totalRequests}`); lines.push(`Total Tokens: ${total.totalTokensUsed.toLocaleString()}`); lines.push(`Total Cost: $${total.totalCost.toFixed(4)}`); lines.push(`Total Errors: ${total.totalErrors}`); lines.push(`Active Providers: ${total.activeProviders}`); return lines; } /** * Export usage data for backup or analysis */ async exportUsageData(outputFile) { const exportData = { exportedAt: new Date().toISOString(), totalUsage: this.getTotalUsage(), providerStats: this.stats, }; await fs.writeJson(outputFile, exportData, { spaces: 2 }); } } //# sourceMappingURL=UsageTracker.js.map