virtue-cli
Version:
Personal character development CLI tool for tracking virtues and philosophical alignment
336 lines • 13.7 kB
JavaScript
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