virtue-cli
Version:
Personal character development CLI tool for tracking virtues and philosophical alignment
467 lines (459 loc) ⢠23.6 kB
JavaScript
import { intro, outro, select, text, confirm, log, spinner } from '@clack/prompts';
import { ClaudeService } from '../services/ClaudeService.js';
import { AutoSaveManager } from '../services/AutoSaveManager.js';
import { calculateAlignment, calculateCoherence, formatDateDisplay } from '../utils/helpers.js';
import picocolors from 'picocolors';
export class MonthlyDeepDive {
db;
profile;
claudeService;
autoSaveManager;
constructor(db, profile) {
this.db = db;
this.profile = profile;
this.claudeService = new ClaudeService();
this.autoSaveManager = new AutoSaveManager(db);
}
async run() {
intro(picocolors.cyan('š Monthly Deep-Dive Assessment'));
try {
// Check if there's enough data for a meaningful assessment
const hasEnoughData = await this.checkDataSufficiency();
if (!hasEnoughData) {
log.warning('Not enough data for a comprehensive assessment. Need at least 5 entries this month.');
outro(picocolors.yellow('š” Complete more daily check-ins and return for your deep-dive!'));
return;
}
const assessmentType = await select({
message: 'What type of monthly assessment would you like?',
options: [
{ value: 'comprehensive', label: 'šÆ Comprehensive Review', hint: 'Full analysis with AI insights and intervention planning' },
{ value: 'reflection', label: 'š§ Guided Reflection', hint: 'Structured self-reflection on the month' },
{ value: 'progress', label: 'š Progress Analysis', hint: 'Focus on growth and achievement patterns' },
{ value: 'planning', label: 'š Next Month Planning', hint: 'Set intentions and goals for upcoming month' }
]
});
switch (assessmentType) {
case 'comprehensive':
await this.runComprehensiveAssessment();
break;
case 'reflection':
await this.runGuidedReflection();
break;
case 'progress':
await this.runProgressAnalysis();
break;
case 'planning':
await this.runMonthlyPlanning();
break;
}
}
catch (error) {
log.error(`Assessment failed: ${error}`);
outro(picocolors.red('Assessment failed'));
}
}
async checkDataSufficiency() {
const currentMonth = new Date().toISOString().slice(0, 7); // YYYY-MM
const monthStart = new Date(currentMonth + '-01');
const entries = this.db.getEntriesForProfile(this.profile.id, monthStart);
return entries.length >= 5;
}
async runComprehensiveAssessment() {
const s = spinner();
s.start('Analyzing your monthly data...');
const monthlyAnalysis = await this.generateMonthlyAnalysis();
s.stop('Analysis complete!');
// Display analysis
this.displayMonthlyOverview(monthlyAnalysis);
this.displayVirtueAnalysis(monthlyAnalysis);
// AI-powered insights if available
if (this.claudeService.isConfigured()) {
const aiInsights = await this.generateAIInsights(monthlyAnalysis);
this.displayAIInsights(aiInsights);
// Intervention planning
const wantsIntervention = await confirm({
message: 'Would you like to create an intervention plan for next month?',
initialValue: true
});
if (wantsIntervention && typeof wantsIntervention !== 'symbol') {
const interventionPlan = await this.createInterventionPlan(monthlyAnalysis);
this.displayInterventionPlan(interventionPlan);
await this.saveDeepDive(monthlyAnalysis, interventionPlan);
}
}
else {
log.info('š” AI insights and intervention planning available when run via Claude Code');
await this.saveDeepDive(monthlyAnalysis, null);
}
outro(picocolors.green('š Monthly assessment complete! Keep growing!'));
}
async runGuidedReflection() {
console.log(picocolors.bold('\nš§ Guided Monthly Reflection'));
console.log('ā'.repeat(50));
const reflectionQuestions = [
'What virtue did you embody most naturally this month?',
'Which virtue challenged you the most, and what did you learn?',
'Describe a moment this month when you felt most aligned with your values.',
'What patterns do you notice in your daily check-ins?',
'How has your understanding of virtue ethics deepened?',
'What would you tell your past self from the beginning of this month?',
'What intention do you want to set for next month?'
];
const reflections = [];
for (let i = 0; i < reflectionQuestions.length; i++) {
const question = reflectionQuestions[i];
console.log(picocolors.cyan(`\n${i + 1}. ${question}`));
const reflection = await text({
message: 'Your reflection:',
placeholder: 'Take your time to reflect deeply...'
});
if (typeof reflection === 'string') {
reflections.push(reflection);
}
}
// Display reflection summary
console.log(picocolors.bold('\nš Your Monthly Reflection Summary'));
console.log('ā'.repeat(50));
reflectionQuestions.forEach((question, index) => {
console.log(picocolors.yellow(`\n${index + 1}. ${question}`));
console.log(picocolors.gray(reflections[index] || 'No response'));
});
const saveReflection = await confirm({
message: 'Save this reflection to your monthly records?',
initialValue: true
});
if (saveReflection && typeof saveReflection !== 'symbol') {
const monthlyAnalysis = await this.generateMonthlyAnalysis();
await this.saveDeepDive(monthlyAnalysis, null, reflections);
}
outro(picocolors.green('š Beautiful reflection completed!'));
}
async runProgressAnalysis() {
const s = spinner();
s.start('Analyzing your progress patterns...');
const monthlyAnalysis = await this.generateMonthlyAnalysis();
s.stop('Progress analysis complete!');
console.log(picocolors.bold('\nš Monthly Progress Analysis'));
console.log('ā'.repeat(50));
// Overall progress
console.log(picocolors.bold('\nšÆ Overall Progress'));
console.log(`š Total Check-ins: ${picocolors.cyan(monthlyAnalysis.monthData.totalEntries.toString())}`);
console.log(`šŖ Average Coherence: ${this.formatCoherence(monthlyAnalysis.monthData.averageCoherence)}`);
console.log(`ā” Consistency Score: ${this.formatCoherence(monthlyAnalysis.monthData.consistencyScore)}`);
// Best and challenging days
console.log(picocolors.bold('\nš Highlights'));
console.log(`š Best Day: ${formatDateDisplay(new Date(monthlyAnalysis.monthData.bestDay.date))} (${this.formatCoherence(monthlyAnalysis.monthData.bestDay.coherence)})`);
console.log(`šŖ Growth Day: ${formatDateDisplay(new Date(monthlyAnalysis.monthData.challengingDay.date))} (${this.formatCoherence(monthlyAnalysis.monthData.challengingDay.coherence)})`);
// Virtue progress
console.log(picocolors.bold('\nšÆ Virtue Progress'));
monthlyAnalysis.virtueAnalysis.forEach(va => {
const trendIcon = va.trend === 'improving' ? 'š' : va.trend === 'declining' ? 'š' : 'š';
const trendColor = va.trend === 'improving' ? picocolors.green : va.trend === 'declining' ? picocolors.red : picocolors.yellow;
console.log(`${va.virtue.emoji} ${va.virtue.name}: ${this.formatCoherence(va.monthlyAverage)} ${trendIcon} ${trendColor(va.trend)}`);
});
// Growth areas
if (monthlyAnalysis.overallThemes.length > 0) {
console.log(picocolors.bold('\nš± Growth Themes'));
monthlyAnalysis.overallThemes.forEach(theme => {
console.log(`⢠${theme}`);
});
}
outro(picocolors.green('š Progress analysis complete!'));
}
async runMonthlyPlanning() {
console.log(picocolors.bold('\nš Next Month Planning'));
console.log('ā'.repeat(50));
// Get current month analysis for context
const monthlyAnalysis = await this.generateMonthlyAnalysis();
// Select focus virtue for next month
const virtueOptions = monthlyAnalysis.virtueAnalysis.map(va => ({
value: va.virtue.id,
label: `${va.virtue.emoji} ${va.virtue.name}`,
hint: `Current: ${(va.monthlyAverage * 100).toFixed(1)}% - ${va.trend}`
}));
const focusVirtueId = await select({
message: 'Which virtue would you like to focus on next month?',
options: virtueOptions
});
const focusVirtue = monthlyAnalysis.virtueAnalysis.find(va => va.virtue.id === focusVirtueId)?.virtue;
if (!focusVirtue)
return;
// Set specific goals
const goals = await text({
message: `What specific goals do you have for ${focusVirtue.name} next month?`,
placeholder: 'e.g., Practice daily meditation, Read one philosophy book...'
});
// Daily practices
const dailyPractices = await text({
message: 'What daily practices will support this virtue?',
placeholder: 'e.g., 10 minutes of reflection, mindful walking...'
});
// Accountability measures
const accountability = await text({
message: 'How will you track progress and stay accountable?',
placeholder: 'e.g., Weekly check-ins, journal entries, friend updates...'
});
// Display plan
console.log(picocolors.bold(`\nšÆ Your ${focusVirtue.name} Focus Plan`));
console.log('ā'.repeat(50));
console.log(`šŖ Focus Virtue: ${focusVirtue.emoji} ${focusVirtue.name}`);
console.log(`š Definition: ${focusVirtue.definition}`);
console.log(`šÆ Goals: ${typeof goals === 'string' ? goals : 'Not specified'}`);
console.log(`š
Daily Practices: ${typeof dailyPractices === 'string' ? dailyPractices : 'Not specified'}`);
console.log(`š Accountability: ${typeof accountability === 'string' ? accountability : 'Not specified'}`);
// Save plan
const savePlan = await confirm({
message: 'Save this plan to your monthly records?',
initialValue: true
});
if (savePlan && typeof savePlan !== 'symbol') {
const interventionPlan = {
focusVirtue: focusVirtue.name,
goals: typeof goals === 'string' ? [goals] : [],
dailyPractices: typeof dailyPractices === 'string' ? [dailyPractices] : [],
weeklyReflections: [],
monthlyMilestones: [],
accountabilityMeasures: typeof accountability === 'string' ? [accountability] : []
};
await this.saveDeepDive(monthlyAnalysis, interventionPlan);
}
outro(picocolors.green('š Monthly plan created! Good luck with your focused practice!'));
}
async generateMonthlyAnalysis() {
const currentMonth = new Date().toISOString().slice(0, 7);
const monthStart = new Date(currentMonth + '-01');
const rawEntries = this.db.getEntriesForProfile(this.profile.id, monthStart);
const entries = this.parseEntries(rawEntries);
const virtues = this.db.getVirtuesForProfile(this.profile.id);
// Calculate month data
const coherences = entries.map(entry => {
const scores = {};
entry.virtue_scores.forEach((vs) => {
scores[vs.virtue_id] = { want: vs.want_score, pull: vs.pull_score };
});
return { date: entry.date, coherence: calculateCoherence(scores) };
});
const averageCoherence = coherences.length > 0
? coherences.reduce((sum, val) => sum + val.coherence, 0) / coherences.length
: 0;
const bestDay = coherences.reduce((best, current) => current.coherence > best.coherence ? current : best, { date: '', coherence: 0 });
const challengingDay = coherences.reduce((worst, current) => current.coherence < worst.coherence ? current : worst, { date: '', coherence: 1 });
// Calculate consistency (how regular the check-ins are)
const daysInMonth = new Date(new Date().getFullYear(), new Date().getMonth() + 1, 0).getDate();
const consistencyScore = entries.length / daysInMonth;
// Virtue analysis
const virtueAnalysis = virtues.map(virtue => {
const virtueEntries = entries.filter(entry => entry.virtue_scores.some((vs) => vs.virtue_id === virtue.id));
const alignments = virtueEntries.map(entry => {
const score = entry.virtue_scores.find((vs) => vs.virtue_id === virtue.id);
return score ? calculateAlignment(score.want_score, score.pull_score) : 0;
}).filter(alignment => alignment > 0);
const monthlyAverage = alignments.length > 0
? alignments.reduce((sum, val) => sum + val, 0) / alignments.length
: 0;
// Simple trend calculation (first half vs second half)
const halfPoint = Math.floor(alignments.length / 2);
const firstHalf = alignments.slice(0, halfPoint);
const secondHalf = alignments.slice(halfPoint);
const firstAvg = firstHalf.length > 0 ? firstHalf.reduce((s, v) => s + v, 0) / firstHalf.length : 0;
const secondAvg = secondHalf.length > 0 ? secondHalf.reduce((s, v) => s + v, 0) / secondHalf.length : 0;
let trend = 'stable';
if (secondAvg > firstAvg + 0.1)
trend = 'improving';
else if (secondAvg < firstAvg - 0.1)
trend = 'declining';
return {
virtue,
monthlyAverage,
trend,
keyInsights: [],
growthAreas: []
};
});
return {
monthData: {
month: currentMonth,
totalEntries: entries.length,
averageCoherence,
bestDay,
challengingDay,
consistencyScore
},
virtueAnalysis,
overallThemes: this.identifyThemes(entries, virtues),
challengingPatterns: [],
strengthPatterns: []
};
}
parseEntries(rawEntries) {
return rawEntries.map(entry => ({
...entry,
virtue_scores: typeof entry.virtue_scores === 'string'
? JSON.parse(entry.virtue_scores)
: entry.virtue_scores
}));
}
identifyThemes(entries, virtues) {
const themes = [];
if (entries.length >= 15) {
themes.push('Consistent practice and commitment');
}
// Check for virtue-specific patterns
virtues.forEach(virtue => {
const virtueEntries = entries.filter(entry => entry.virtue_scores.some((vs) => vs.virtue_id === virtue.id));
if (virtueEntries.length > entries.length * 0.8) {
themes.push(`Strong focus on ${virtue.name}`);
}
});
return themes;
}
async generateAIInsights(analysis) {
const prompt = this.buildMonthlyInsightPrompt(analysis);
return await this.claudeService.generateMonthlyIntervention(prompt);
}
buildMonthlyInsightPrompt(analysis) {
return `You are a wise virtue ethics coach analyzing someone's monthly virtue tracking data.
MONTHLY SUMMARY:
- Profile: ${this.profile.name} (${this.profile.philosophical_template})
- Month: ${analysis.monthData.month}
- Total Check-ins: ${analysis.monthData.totalEntries}
- Average Coherence: ${(analysis.monthData.averageCoherence * 100).toFixed(1)}%
- Consistency: ${(analysis.monthData.consistencyScore * 100).toFixed(1)}%
VIRTUE PERFORMANCE:
${analysis.virtueAnalysis.map(va => `- ${va.virtue.name}: ${(va.monthlyAverage * 100).toFixed(1)}% (${va.trend})`).join('\n')}
THEMES IDENTIFIED:
${analysis.overallThemes.join('\n')}
Provide a comprehensive monthly assessment including:
1. Celebration of growth and achievements
2. Compassionate analysis of challenges
3. Key insights about their virtue development
4. Patterns you notice in their practice
5. Wisdom for continued growth
Be encouraging, specific to their data, and philosophically grounded. Use markdown formatting. 300-400 words.`;
}
async createInterventionPlan(analysis) {
// Find the virtue that needs most attention
const challengingVirtue = analysis.virtueAnalysis
.sort((a, b) => a.monthlyAverage - b.monthlyAverage)[0];
if (this.claudeService.isConfigured()) {
const prompt = `Create a focused intervention plan for improving ${challengingVirtue.virtue.name}.
Current performance: ${(challengingVirtue.monthlyAverage * 100).toFixed(1)}%
Definition: ${challengingVirtue.virtue.definition}
Trend: ${challengingVirtue.trend}
Create:
1. 3 specific, measurable goals for next month
2. 3 daily practices (5-15 minutes each)
3. 3 weekly reflection questions
4. 3 monthly milestones to track progress
5. 3 accountability measures
Format as JSON with arrays for each category.`;
try {
const aiPlan = await this.claudeService.generateInsights(prompt);
// Try to parse JSON response
const jsonMatch = aiPlan.match(/\{[\s\S]*\}/);
if (jsonMatch) {
const parsed = JSON.parse(jsonMatch[0]);
return {
focusVirtue: challengingVirtue.virtue.name,
goals: parsed.goals || [],
dailyPractices: parsed.dailyPractices || [],
weeklyReflections: parsed.weeklyReflections || [],
monthlyMilestones: parsed.monthlyMilestones || [],
accountabilityMeasures: parsed.accountabilityMeasures || []
};
}
}
catch {
// Fall back to manual plan creation
}
}
// Fallback intervention plan
return {
focusVirtue: challengingVirtue.virtue.name,
goals: [
`Improve ${challengingVirtue.virtue.name} alignment by 15%`,
'Practice daily reflection on this virtue',
'Apply this virtue in one specific life area'
],
dailyPractices: [
'5-minute morning intention setting',
'Evening reflection on virtue practice',
'Mindful application during daily activities'
],
weeklyReflections: [
'How did I embody this virtue this week?',
'What challenges did I face?',
'What progress do I notice?'
],
monthlyMilestones: [
'Week 1: Establish daily practices',
'Week 2: Consistent application',
'Week 3: Deeper integration',
'Week 4: Sustained improvement'
],
accountabilityMeasures: [
'Daily virtue tracking scores',
'Weekly written reflections',
'Monthly coherence improvement'
]
};
}
displayMonthlyOverview(analysis) {
console.log(picocolors.bold('\nš
Monthly Overview'));
console.log('ā'.repeat(50));
console.log(`š Check-ins: ${picocolors.cyan(analysis.monthData.totalEntries.toString())}`);
console.log(`šÆ Average Coherence: ${this.formatCoherence(analysis.monthData.averageCoherence)}`);
console.log(`ā” Consistency: ${this.formatCoherence(analysis.monthData.consistencyScore)}`);
console.log(`š Best Day: ${formatDateDisplay(new Date(analysis.monthData.bestDay.date))} (${this.formatCoherence(analysis.monthData.bestDay.coherence)})`);
console.log(`šŖ Growth Day: ${formatDateDisplay(new Date(analysis.monthData.challengingDay.date))} (${this.formatCoherence(analysis.monthData.challengingDay.coherence)})`);
}
displayVirtueAnalysis(analysis) {
console.log(picocolors.bold('\nšÆ Virtue Analysis'));
console.log('ā'.repeat(50));
analysis.virtueAnalysis.forEach(va => {
const trendIcon = va.trend === 'improving' ? 'š' : va.trend === 'declining' ? 'š' : 'š';
const trendColor = va.trend === 'improving' ? picocolors.green : va.trend === 'declining' ? picocolors.red : picocolors.yellow;
console.log(`${va.virtue.emoji} ${va.virtue.name}: ${this.formatCoherence(va.monthlyAverage)} ${trendIcon} ${trendColor(va.trend)}`);
});
}
displayAIInsights(insights) {
console.log(picocolors.bold('\nš§ AI Insights'));
console.log('ā'.repeat(50));
console.log(insights);
}
displayInterventionPlan(plan) {
console.log(picocolors.bold(`\nšŖ Intervention Plan: ${plan.focusVirtue}`));
console.log('ā'.repeat(50));
console.log(picocolors.bold('\nšÆ Goals:'));
plan.goals.forEach(goal => console.log(`⢠${goal}`));
console.log(picocolors.bold('\nš
Daily Practices:'));
plan.dailyPractices.forEach(practice => console.log(`⢠${practice}`));
console.log(picocolors.bold('\nš¤ Weekly Reflections:'));
plan.weeklyReflections.forEach(reflection => console.log(`⢠${reflection}`));
console.log(picocolors.bold('\nš Monthly Milestones:'));
plan.monthlyMilestones.forEach(milestone => console.log(`⢠${milestone}`));
console.log(picocolors.bold('\nš Accountability:'));
plan.accountabilityMeasures.forEach(measure => console.log(`⢠${measure}`));
}
async saveDeepDive(analysis, interventionPlan, reflections) {
const deepDiveData = {
analysis,
interventionPlan,
reflections: reflections || [],
createdAt: new Date().toISOString()
};
this.db.createMonthlyDeepDiveSimple(this.profile.id, analysis.monthData.month, JSON.stringify(deepDiveData), interventionPlan ? JSON.stringify(interventionPlan) : null);
// Auto-save if configured
await this.autoSaveManager.saveMonthlyDeepDive(this.profile, analysis.monthData.month, deepDiveData);
log.success('Monthly assessment saved to your records!');
}
formatCoherence(coherence) {
const percentage = (coherence * 100).toFixed(1);
const color = coherence >= 0.8 ? picocolors.green :
coherence >= 0.6 ? picocolors.yellow : picocolors.red;
const icon = coherence >= 0.8 ? 'š¢' : coherence >= 0.6 ? 'š”' : 'š“';
return `${color(percentage + '%')} ${icon}`;
}
}
//# sourceMappingURL=MonthlyDeepDive.js.map