UNPKG

virtue-cli

Version:

Personal character development CLI tool for tracking virtues and philosophical alignment

227 lines • 10.1 kB
import { intro, outro, text, log, spinner, isCancel } from '@clack/prompts'; import { ASCIIArt } from '../utils/ascii-art.js'; import { AutoSaveManager } from '../services/AutoSaveManager.js'; import { AutoExportManager } from '../services/AutoExportManager.js'; import { sleep, formatDate, formatDateDisplay, calculateAlignment, calculateCoherence, getCoherenceLevel, validateScore } from '../utils/helpers.js'; import { getTheme } from '../utils/themes.js'; export class DailyCheckin { db; profile; theme = getTheme(); startTime = Date.now(); autoSaveManager; autoExportManager; constructor(db, profile) { this.db = db; this.profile = profile; this.autoSaveManager = new AutoSaveManager(db); this.autoExportManager = new AutoExportManager(db); } async run() { console.clear(); intro(this.theme.primary(`šŸŒ… Daily Virtue Check-in - ${formatDateDisplay()}`)); // Check if already completed today const today = formatDate(); const existingEntry = this.db.getDailyEntryByDate(this.profile.id, today); if (existingEntry) { log.warning('You\'ve already completed today\'s check-in!'); const coherence = existingEntry.coherence_score; const level = getCoherenceLevel(coherence); log.info(`Today's coherence: ${level.icon} ${(coherence * 100).toFixed(1)}% (${level.level})`); outro(this.theme.muted('Come back tomorrow for your next check-in')); return; } // Get virtues const virtues = this.db.getVirtuesForProfile(this.profile.id); if (virtues.length === 0) { log.error('No virtues found for this profile. Please add virtues first.'); return; } // Score each virtue const scores = {}; for (const [index, virtue] of virtues.entries()) { await this.scoreVirtue(virtue, scores, index + 1, virtues.length); // Check for cancellation if (isCancel(scores[virtue.id])) { outro(this.theme.muted('Check-in cancelled')); return; } } // Get optional notes const notes = await this.captureNotes(); // Calculate coherence const weights = {}; virtues.forEach(v => weights[v.id] = v.priority_weight); const coherence = calculateCoherence(scores, weights); // Show results await this.showResults(coherence, scores, virtues); // Save entry const completionTime = Math.round((Date.now() - this.startTime) / 1000); await this.saveEntry(scores, notes, coherence, completionTime); outro(this.theme.success('āœ“ Daily check-in complete!')); } async runQuick() { console.clear(); intro(this.theme.primary(`⚔ Quick Check-in - ${formatDateDisplay()}`)); // Check if already completed today const today = formatDate(); const existingEntry = this.db.getDailyEntryByDate(this.profile.id, today); if (existingEntry) { log.warning('You\'ve already completed today\'s check-in!'); return; } const virtues = this.db.getVirtuesForProfile(this.profile.id); if (virtues.length === 0) { log.error('No virtues found for this profile.'); return; } // Quick scoring - just overall alignment log.info('Rate your overall virtue alignment today (0-10)'); const overallScore = await text({ message: 'Overall alignment:', placeholder: '7', validate: validateScore }); if (isCancel(overallScore)) { outro(this.theme.muted('Quick check-in cancelled')); return; } const score = parseInt(overallScore); // Apply same score to all virtues with slight variation const scores = {}; for (const virtue of virtues) { const variation = (Math.random() - 0.5) * 2; // -1 to 1 const adjustedScore = Math.max(0, Math.min(10, score + variation)); scores[virtue.id] = { want: adjustedScore, pull: adjustedScore * (0.8 + Math.random() * 0.2) // 80-100% of want }; } const weights = {}; virtues.forEach(v => weights[v.id] = v.priority_weight); const coherence = calculateCoherence(scores, weights); const level = getCoherenceLevel(coherence); log.success(`Quick check-in recorded! Coherence: ${level.icon} ${(coherence * 100).toFixed(1)}%`); // Save entry await this.saveEntry(scores, 'Quick check-in', coherence, 30); } async scoreVirtue(virtue, scores, current, total) { console.clear(); // Progress indicator const progress = Math.round((current / total) * 100); console.log(this.theme.muted(`Progress: ${ASCIIArt.progressBar(progress)} ${current}/${total}\n`)); // Virtue header console.log(this.theme.primary(`${virtue.emoji} ${virtue.name}`)); console.log(this.theme.muted('─'.repeat(50))); console.log(virtue.definition); console.log(this.theme.muted('─'.repeat(50))); // Want score console.log(this.theme.secondary('\nšŸ’­ Psychological Want (aspiration/intention):')); console.log(this.theme.muted('How much do you WANT to embody this virtue today?')); const want = await text({ message: 'Score (0-10):', placeholder: '5', validate: validateScore }); if (isCancel(want)) { scores[virtue.id] = { want: 0, pull: 0 }; return; } // Pull score console.log(this.theme.secondary('\nšŸŽÆ Physiological Pull (automatic behavior):')); console.log(this.theme.muted('How much do you feel PULLED toward these behaviors?')); const pull = await text({ message: 'Score (0-10):', placeholder: '5', validate: validateScore }); if (isCancel(pull)) { scores[virtue.id] = { want: parseInt(want), pull: 0 }; return; } const wantScore = parseInt(want); const pullScore = parseInt(pull); scores[virtue.id] = { want: wantScore, pull: pullScore }; // Show alignment const alignment = calculateAlignment(wantScore, pullScore); const alignmentBar = ASCIIArt.alignmentBar(alignment); console.log(`\n⚔ Alignment: ${alignmentBar} ${(alignment * 100).toFixed(1)}%`); await sleep(1000); // Brief pause for reflection } async captureNotes() { console.log('\n'); const notes = await text({ message: 'šŸ“ Any reflections or notes? (optional)', placeholder: 'Press enter to skip...' }); if (isCancel(notes) || !notes) { return null; } return notes; } async showResults(coherence, scores, virtues) { const s = spinner(); s.start(this.theme.primary('🧮 Calculating daily coherence...')); await sleep(1500); s.stop(); console.clear(); console.log(this.theme.success('šŸŽ‰ Daily Check-in Complete!\n')); // Overall coherence const level = getCoherenceLevel(coherence); const coherenceBar = ASCIIArt.coherenceBar(coherence); console.log(this.theme.primary('Overall Coherence')); console.log(`${level.icon} ${coherenceBar} ${(coherence * 100).toFixed(1)}% (${level.level})`); // Individual virtue breakdown console.log('\nā”Œā”€ Virtue Alignment Analysis ─┐'); console.log('│ │'); for (const virtue of virtues) { const score = scores[virtue.id]; if (score) { const alignment = calculateAlignment(score.want, score.pull); const percentage = (alignment * 100).toFixed(1); const bar = ASCIIArt.progressBar(alignment * 100, 10); const name = virtue.name.padEnd(18); console.log(`│ ${name} ${percentage.padStart(5)}% │ ${bar}`); } } console.log('│ │'); console.log('ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜'); // Trend comparison const previousEntry = this.db.getLatestEntry(this.profile.id); if (previousEntry) { const trend = ASCIIArt.getTrendIndicator(coherence, previousEntry.coherence_score); console.log(`\nTrend: ${trend}`); } // Streak information const streak = this.db.calculateCurrentStreak(this.profile.id) + 1; // +1 for today console.log(this.theme.primary(`\nšŸ”„ Current streak: ${streak} days`)); await sleep(2000); // Let user see results } async saveEntry(scores, notes, coherence, completionTime) { const entry = await this.db.createDailyEntry({ profile_id: this.profile.id, date: formatDate(), virtue_scores: JSON.stringify(scores), notes, coherence_score: coherence, completion_time_seconds: completionTime }); // Log session await this.db.createSession({ profile_id: this.profile.id, session_type: 'daily', duration_seconds: completionTime }); // Auto-save if configured await this.autoSaveManager.saveDailyEntry(this.profile, entry); // Auto-export if configured const exportResult = await this.autoExportManager.triggerDailyExport(this.profile.id, entry); if (exportResult.success && exportResult.filePath) { log.success(`āœ“ Exported to: ${exportResult.filePath}`); } else if (!exportResult.success && exportResult.error) { log.warning(`āš ļø Export failed: ${exportResult.error}`); } } } //# sourceMappingURL=DailyCheckin.js.map