UNPKG

virtue-cli

Version:

Personal character development CLI tool for tracking virtues and philosophical alignment

336 lines 13.7 kB
import { ExportManager } from './ExportManager.js'; import { writeFileSync, existsSync, mkdirSync, accessSync, constants } from 'fs'; import { join, dirname } from 'path'; import { log } from '@clack/prompts'; export class AutoExportManager { database; exportManager; constructor(database, exportManager) { this.database = database; this.exportManager = exportManager || new ExportManager(database); } /** * Trigger automatic export after daily check-in */ async triggerDailyExport(profileId, dailyEntry) { const profile = this.database.getProfile(profileId); if (!profile || !profile.auto_export_enabled) { return { success: true }; // No export needed } const config = this.getExportConfig(profile); if (!config.enabled || config.frequency !== 'daily') { return { success: true }; // Export not configured for daily } try { return await this.performExport(profile, 'daily', { dailyEntry }); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; log.warning(`Auto export failed: ${errorMessage}`); return { success: false, error: errorMessage }; } } /** * Trigger weekly export */ async triggerWeeklyExport(profileId) { const profile = this.database.getProfile(profileId); if (!profile || !profile.auto_export_enabled) { return { success: true }; } const config = this.getExportConfig(profile); if (!config.enabled) { return { success: true }; } try { return await this.performExport(profile, 'weekly'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, error: errorMessage }; } } /** * Trigger monthly export */ async triggerMonthlyExport(profileId) { const profile = this.database.getProfile(profileId); if (!profile || !profile.auto_export_enabled) { return { success: true }; } const config = this.getExportConfig(profile); if (!config.enabled) { return { success: true }; } try { return await this.performExport(profile, 'monthly'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return { success: false, error: errorMessage }; } } /** * Configure automatic exports for a profile */ configureAutoExport(profileId, config) { try { const updates = {}; if (config.enabled !== undefined) { updates.auto_export_enabled = config.enabled; } if (config.targetPath !== undefined) { // Validate target path if (config.targetPath && !this.validateExportPath(config.targetPath)) { throw new Error(`Invalid export path: ${config.targetPath}`); } updates.export_target_path = config.targetPath; } if (config.format !== undefined) { updates.export_format = config.format; } if (config.frequency !== undefined) { updates.export_frequency = config.frequency; } const result = this.database.updateProfile(profileId, updates); return result !== null; } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; log.warning(`Failed to configure auto export: ${errorMessage}`); return false; } } /** * Get current export configuration for a profile */ getExportConfig(profile) { return { enabled: profile.auto_export_enabled || false, targetPath: profile.export_target_path || '', format: profile.export_format || 'summary', frequency: profile.export_frequency || 'daily' }; } /** * Test export configuration (validate paths, permissions, etc.) */ testExportConfig(config) { if (!config.enabled) { return { valid: true }; } if (!config.targetPath) { return { valid: false, error: 'Export target path is required' }; } if (!this.validateExportPath(config.targetPath)) { return { valid: false, error: 'Export target path is not accessible or writable' }; } return { valid: true }; } /** * Get export history for a profile */ getExportHistory(profileId) { // For now, return basic info from profile // In the future, this could track detailed export history const profile = this.database.getProfile(profileId); if (!profile || !profile.last_export_date) { return []; } return [{ date: profile.last_export_date, type: 'daily', status: 'success' }]; } /** * Perform the actual export operation */ async performExport(profile, exportType, context) { const config = this.getExportConfig(profile); if (!config.targetPath) { throw new Error('Export target path not configured'); } // Ensure target directory exists this.ensureDirectoryExists(config.targetPath); // Generate export content based on type and format const content = await this.generateExportContent(profile, exportType, config.format, context); // Determine filename const filename = this.generateFilename(exportType, config.format); const subdirectory = this.getSubdirectory(exportType); const fullPath = join(config.targetPath, subdirectory, filename); // Ensure subdirectory exists this.ensureDirectoryExists(dirname(fullPath)); // Write file writeFileSync(fullPath, content, 'utf-8'); // Update last export date this.database.updateProfile(profile.id, { last_export_date: new Date().toISOString() }); return { success: true, filePath: fullPath }; } /** * Generate export content based on type and format */ async generateExportContent(profile, exportType, format, context) { const now = new Date(); switch (exportType) { case 'daily': return await this.generateDailyContent(profile, format, now, context?.dailyEntry); case 'weekly': return await this.generateWeeklyContent(profile, format, now); case 'monthly': return await this.generateMonthlyContent(profile, format, now); default: throw new Error(`Unknown export type: ${exportType}`); } } /** * Generate daily export content */ async generateDailyContent(profile, format, date, dailyEntry) { // Use the daily entry date if available, otherwise use the provided date const actualDate = dailyEntry?.date ? new Date(dailyEntry.date) : date; const dateStr = actualDate.toISOString().split('T')[0]; const displayDate = actualDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); if (format === 'summary') { return await this.generateDailySummary(profile, displayDate, dateStr, dailyEntry); } else if (format === 'detailed') { return await this.generateDailyDetailed(profile, displayDate, dateStr, dailyEntry); } else if (format === 'journal') { return await this.generateDailyJournal(profile, displayDate, dateStr, dailyEntry); } throw new Error(`Unknown format: ${format}`); } /** * Generate daily summary format */ async generateDailySummary(profile, displayDate, dateStr, dailyEntry) { const virtues = this.database.getVirtuesForProfile(profile.id); let content = `# Virtue Tracking - ${dateStr}\n\n`; if (dailyEntry) { const scores = JSON.parse(dailyEntry.virtue_scores); content += `## Today's Scores\n`; for (const virtue of virtues) { const score = scores[virtue.id]; if (score) { const gap = score.want - score.pull; const gapStr = gap > 0 ? `+${gap}` : `${gap}`; content += `- **${virtue.name}**: Want ${score.want}/10, Pull ${score.pull}/10 (Gap: ${gapStr})\n`; } } content += `\n## Overall Coherence: ${Math.round(dailyEntry.coherence_score * 100)}%\n\n`; if (dailyEntry.notes) { content += `## Notes\n${dailyEntry.notes}\n\n`; } } // Add trend analysis if we have historical data // Note: This is a simplified implementation - full trend analysis would need more database methods content += `## 7-Day Trend\n`; content += `- Tracking Consistency: Recent check-ins available\n`; content += `- Trend Analysis: Available in detailed reports\n`; content += `\n---\n*Generated automatically by Virtue CLI*\n`; return content; } /** * Generate weekly and monthly content (simplified for now) */ async generateWeeklyContent(profile, format, date) { const weekStart = new Date(date); weekStart.setDate(date.getDate() - date.getDay()); const weekEnd = new Date(weekStart); weekEnd.setDate(weekStart.getDate() + 6); const weekStr = `${weekStart.toLocaleDateString()} - ${weekEnd.toLocaleDateString()}`; return `# Weekly Virtue Review - ${weekStr}\n\n## Summary\n\nWeekly review content coming soon...\n\n---\n*Generated automatically by Virtue CLI*\n`; } async generateMonthlyContent(profile, format, date) { const monthStr = date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' }); return `# Monthly Virtue Review - ${monthStr}\n\n## Summary\n\nMonthly review content coming soon...\n\n---\n*Generated automatically by Virtue CLI*\n`; } /** * Generate detailed and journal formats (simplified for now) */ async generateDailyDetailed(profile, displayDate, dateStr, dailyEntry) { // Start with summary content and add more detail const summaryContent = await this.generateDailySummary(profile, displayDate, dateStr, dailyEntry); const detailedContent = summaryContent.replace('# Virtue Tracking', '# Detailed Virtue Report'); return `${detailedContent}\n\n## Detailed Analytics\n\n- Comprehensive analysis available\n- Historical trend data\n- Progress metrics and insights\n\n---\n*Generated automatically by Virtue CLI*\n`; } async generateDailyJournal(profile, displayDate, dateStr, dailyEntry) { const content = await this.generateDailySummary(profile, displayDate, dateStr, dailyEntry); // Add journal-style prompts let journalContent = content + `\n## Reflection Prompts\n\n`; journalContent += `### What virtue did I embody most strongly today?\n\n`; journalContent += `### What challenges did I face in living my values?\n\n`; journalContent += `### How can I improve tomorrow?\n\n`; journalContent += `### What am I grateful for in my virtue journey?\n\n`; return journalContent; } /** * Validate export path accessibility and writability */ validateExportPath(path) { try { // Check if path exists, if not try to create it if (!existsSync(path)) { mkdirSync(path, { recursive: true }); } // Test write access accessSync(path, constants.W_OK); return true; } catch { return false; } } /** * Ensure directory exists */ ensureDirectoryExists(path) { if (!existsSync(path)) { mkdirSync(path, { recursive: true }); } } /** * Generate filename based on export type and format */ generateFilename(exportType, format) { const now = new Date(); const dateStr = now.toISOString().split('T')[0]; switch (exportType) { case 'daily': return `${dateStr}-virtue-${format}.md`; case 'weekly': const year = now.getFullYear(); const weekNum = this.getWeekNumber(now); return `${year}-week-${weekNum.toString().padStart(2, '0')}-review.md`; case 'monthly': const monthStr = now.toISOString().substring(0, 7); // YYYY-MM return `${monthStr}-monthly-review.md`; default: return `${dateStr}-export.md`; } } /** * Get subdirectory for export type */ getSubdirectory(exportType) { return exportType; } /** * Get week number of the year */ getWeekNumber(date) { const firstDayOfYear = new Date(date.getFullYear(), 0, 1); const pastDaysOfYear = (date.getTime() - firstDayOfYear.getTime()) / 86400000; return Math.ceil((pastDaysOfYear + firstDayOfYear.getDay() + 1) / 7); } } //# sourceMappingURL=AutoExportManager.js.map