UNPKG

virtue-tracker

Version:

Personal character development CLI tool for tracking virtues and philosophical alignment

544 lines 23.3 kB
import { intro, outro, text, select, confirm, multiselect, note, log, spinner } from '@clack/prompts'; import { sleep } from '../utils/helpers.js'; import { getTheme } from '../utils/themes.js'; import { philosophicalTemplates } from './templates/philosophical-templates.js'; import { ClaudeService } from '../services/ClaudeService.js'; export class ProfileManager { db; theme = getTheme(); claudeService; constructor(db) { this.db = db; this.claudeService = new ClaudeService(); } async createProfile() { intro(this.theme.primary('🧭 Create Your Virtue Framework')); // Get profile name const name = await text({ message: 'What would you like to name this profile?', placeholder: 'Personal Journey', validate: (value) => { if (!value || value.trim().length === 0) { return 'Profile name is required'; } } }); // Choose approach const approach = await select({ message: 'How would you like to create your virtue framework?', options: [ { value: 'guided', label: '🤖 AI-Guided Discovery', hint: 'Answer questions to derive personalized virtues' }, { value: 'template', label: '📚 Philosophical Template', hint: 'Start with a traditional framework' }, { value: 'custom', label: '✏️ Custom Framework', hint: 'Define your own virtues from scratch' } ] }); let virtues = []; let template = 'custom'; switch (approach) { case 'guided': const responses = await this.conductPhilosophicalDiscovery(); virtues = await this.deriveVirtuesFromResponses(responses); template = 'ai-derived'; break; case 'template': const selectedTemplate = await this.selectPhilosophicalTemplate(); virtues = selectedTemplate.virtues; template = selectedTemplate.name; break; case 'custom': virtues = await this.createCustomVirtues(); break; } // Create profile const profile = await this.db.createProfile({ name: name, philosophical_template: template, ascii_theme: 'default', color_scheme: 'blue' }); // Create virtues for (const virtue of virtues) { await this.db.createVirtue({ ...virtue, profile_id: profile.id }); } outro(this.theme.success(`✓ Profile "${profile.name}" created successfully!`)); return profile; } async conductPhilosophicalDiscovery() { const responses = []; note('💭 Quick Value Discovery\n\n' + 'We\'ll ask 4-6 crisp questions to understand your moral priorities.\n' + 'No long essays - just quick choices that reveal your authentic values.', 'About this process'); // Question 1: Binary choice const q1 = await select({ message: 'Would you rather be known as someone who always tells the truth, or someone who\'s always kind?', options: [ { value: 'truth', label: 'Truth' }, { value: 'kindness', label: 'Kindness' } ] }); responses.push({ question: 'Truth vs Kindness', answer: q1 }); // Question 2: Stack ranking values note('Next, you\'ll stack-rank 7 core values. This will help us understand your priority hierarchy.', 'Value Stack-Ranking'); const values = ['Fairness', 'Loyalty', 'Freedom', 'Compassion', 'Integrity', 'Wisdom', 'Courage']; const rankedValues = []; const remainingValues = [...values]; for (let i = 0; i < values.length; i++) { const choice = await select({ message: `Select your ${i === 0 ? 'highest' : i === values.length - 1 ? 'lowest' : `${i + 1}${this.getOrdinalSuffix(i + 1)}`} priority value:`, options: remainingValues.map(value => ({ value, label: value })) }); rankedValues.push(choice); remainingValues.splice(remainingValues.indexOf(choice), 1); } responses.push({ question: 'Value Rankings', answer: rankedValues }); // Question 3: Scenario const q3 = await select({ message: 'In difficult times, you tend to rely most on:', options: [ { value: 'logic', label: 'Logic and reason' }, { value: 'intuition', label: 'Intuition and feeling' }, { value: 'principles', label: 'Established principles' }, { value: 'relationships', label: 'Support from others' } ] }); responses.push({ question: 'Decision Making', answer: q3 }); // Question 4: Growth focus const q4 = await select({ message: 'What aspect of personal growth excites you most?', options: [ { value: 'intellectual', label: 'Intellectual development' }, { value: 'emotional', label: 'Emotional intelligence' }, { value: 'spiritual', label: 'Spiritual connection' }, { value: 'social', label: 'Social impact' } ] }); responses.push({ question: 'Growth Focus', answer: q4 }); // AI-generated follow-up questions if Claude is available if (this.claudeService.isConfigured()) { const s = spinner(); s.start(this.theme.primary('🤖 Generating personalized follow-up questions...')); try { const aiQuestions = await this.claudeService.generatePhilosophicalQuestions(responses); s.stop(this.theme.success('✓ Questions generated')); if (aiQuestions.length > 0) { note('Based on your responses, here are some deeper questions to explore your values further.', 'AI-Generated Questions'); for (const [index, question] of aiQuestions.entries()) { const answer = await text({ message: `${index + 5}. ${question}`, placeholder: 'Share your thoughts...', validate: (value) => { if (!value || value.trim().length < 10) { return 'Please provide a thoughtful response (at least 10 characters)'; } } }); if (answer) { responses.push({ question, answer: answer }); } } } } catch { s.stop(this.theme.error('✗ Failed to generate questions')); log.warning('Continuing with standard assessment...'); } } return responses; } async deriveVirtuesFromResponses(responses) { const s = spinner(); if (this.claudeService.isConfigured()) { s.start(this.theme.primary('🤖 Using AI to analyze your moral foundations...')); await sleep(1500); try { const virtues = await this.claudeService.deriveVirtuesFromResponses(responses); s.stop(this.theme.success('✓ AI analysis complete')); // Display AI-derived virtues console.log('\n' + this.theme.success('✨ Your AI-Derived Virtue Framework:')); for (const virtue of virtues) { console.log(`\n${virtue.emoji} ${this.theme.primary(virtue.name)}`); console.log(this.theme.muted(virtue.definition)); } const confirm = await select({ message: '\nHow do these AI-generated virtues resonate with you?', options: [ { value: 'perfect', label: '✨ Perfect! Let\'s use these' }, { value: 'adjust', label: '🔧 Good, but I\'d like to adjust them' }, { value: 'restart', label: '🔄 Let\'s try a different approach' } ] }); if (confirm === 'adjust') { return await this.editVirtues(virtues); } else if (confirm === 'restart') { return await this.createCustomVirtues(); } return virtues; } catch { s.stop(this.theme.error('✗ AI analysis failed')); log.warning('Falling back to manual virtue derivation...'); } } // Fallback to manual derivation s.start(this.theme.primary('🧮 Analyzing your responses...')); await sleep(2000); s.stop(this.theme.success('✓ Analysis complete')); // Simple derivation logic based on responses const virtues = []; // Analyze responses to create personalized virtues const truthVsKindness = responses.find(r => r.question === 'Truth vs Kindness')?.answer; const rankedValues = responses.find(r => r.question === 'Value Rankings')?.answer; const growthFocus = responses.find(r => r.question === 'Growth Focus')?.answer; // Primary virtue based on truth vs kindness if (truthVsKindness === 'truth') { virtues.push({ name: 'Intellectual Honesty', definition: 'The pursuit of truth through reason and evidence, embracing uncertainty with grace', priority_weight: 1.0, template_source: 'ai-derived', emoji: '🧠' }); } else { virtues.push({ name: 'Compassionate Action', definition: 'Acting with kindness and empathy, reducing suffering wherever possible', priority_weight: 1.0, template_source: 'ai-derived', emoji: '🤝' }); } // Add virtues based on top values const valueVirtueMap = { fairness: { name: 'Justice', definition: 'Treating all beings with equity and standing up for what is right', priority_weight: 0.9, template_source: 'ai-derived', emoji: '⚖️' }, loyalty: { name: 'Faithful Commitment', definition: 'Honoring relationships and promises with steadfast dedication', priority_weight: 0.9, template_source: 'ai-derived', emoji: '🤝' }, freedom: { name: 'Autonomous Growth', definition: 'Pursuing personal liberation while respecting others\' freedom', priority_weight: 0.9, template_source: 'ai-derived', emoji: '🦋' }, compassion: { name: 'Empathetic Understanding', definition: 'Deeply connecting with others\' experiences and responding with care', priority_weight: 0.9, template_source: 'ai-derived', emoji: '💝' }, integrity: { name: 'Authentic Consistency', definition: 'Aligning actions with values, maintaining wholeness of character', priority_weight: 0.9, template_source: 'ai-derived', emoji: '💎' }, wisdom: { name: 'Practical Wisdom', definition: 'Applying knowledge with discernment for the benefit of all', priority_weight: 0.9, template_source: 'ai-derived', emoji: '🦉' }, courage: { name: 'Moral Courage', definition: 'Acting rightly despite fear, standing firm in your convictions', priority_weight: 0.9, template_source: 'ai-derived', emoji: '🦁' } }; // Add top 3 ranked values as virtues (skip if already added) let addedCount = 0; if (rankedValues && rankedValues.length > 0) { for (let i = 0; i < Math.min(3, rankedValues.length); i++) { const value = rankedValues[i].toLowerCase(); const virtue = valueVirtueMap[value]; if (virtue && !virtues.some(v => v.name === virtue.name)) { virtues.push({ ...virtue, priority_weight: 0.9 - (addedCount * 0.15) // Higher weight for higher-ranked values }); addedCount++; } } } // Add virtue based on growth focus if (growthFocus === 'intellectual' && !virtues.some(v => v.name.includes('Intellectual'))) { virtues.push({ name: 'Intellectual Curiosity', definition: 'Continuously seeking knowledge and understanding with an open mind', priority_weight: 0.7, template_source: 'ai-derived', emoji: '📚' }); } else if (growthFocus === 'emotional') { virtues.push({ name: 'Emotional Resilience', definition: 'Processing emotions skillfully while maintaining inner balance', priority_weight: 0.7, template_source: 'ai-derived', emoji: '🌊' }); } else if (growthFocus === 'spiritual') { virtues.push({ name: 'Mindful Presence', definition: 'Cultivating awareness and connection to the present moment', priority_weight: 0.7, template_source: 'ai-derived', emoji: '🧘' }); } else if (growthFocus === 'social') { virtues.push({ name: 'Social Leadership', definition: 'Inspiring positive change and serving the common good', priority_weight: 0.7, template_source: 'ai-derived', emoji: '🌟' }); } // Display derived virtues console.log('\n' + this.theme.success('✨ Your Personalized Virtue Framework:')); for (const virtue of virtues) { console.log(`\n${virtue.emoji} ${this.theme.primary(virtue.name)}`); console.log(this.theme.muted(virtue.definition)); } const confirm = await select({ message: '\nHow do these virtues resonate with you?', options: [ { value: 'perfect', label: '✨ Perfect! Let\'s use these' }, { value: 'adjust', label: '🔧 Good, but I\'d like to adjust them' }, { value: 'restart', label: '🔄 Let\'s try a different approach' } ] }); if (confirm === 'adjust') { // Allow editing virtues return await this.editVirtues(virtues); } else if (confirm === 'restart') { return await this.createCustomVirtues(); } return virtues; } async selectPhilosophicalTemplate() { const template = await select({ message: 'Choose a philosophical tradition:', options: Object.entries(philosophicalTemplates).map(([key, template]) => ({ value: key, label: template.name, hint: template.description })) }); const selected = philosophicalTemplates[template]; note(`${selected.name}\n\n${selected.description}\n\nVirtues:\n${selected.virtues.map(v => `• ${v.emoji} ${v.name}`).join('\n')}`, 'Selected Framework'); return { name: template, virtues: selected.virtues }; } async createCustomVirtues() { const virtues = []; let addMore = true; while (addMore && virtues.length < 7) { const name = await text({ message: `Virtue ${virtues.length + 1} name:`, placeholder: 'e.g., Intellectual Honesty', validate: (value) => { if (!value || value.trim().length === 0) { return 'Virtue name is required'; } } }); const definition = await text({ message: 'Brief definition:', placeholder: 'e.g., The pursuit of truth through reason and evidence', validate: (value) => { if (!value || value.trim().length === 0) { return 'Definition is required'; } } }); const emoji = await text({ message: 'Choose an emoji for this virtue:', placeholder: '🎯', initialValue: '🎯' }); virtues.push({ name: name, definition: definition, priority_weight: 1.0 - (virtues.length * 0.1), template_source: 'custom', emoji: emoji || '🎯' }); if (virtues.length < 7) { addMore = await confirm({ message: 'Add another virtue?', initialValue: virtues.length < 3 // Encourage at least 3 virtues }); } } return virtues; } async editVirtues(virtues) { const action = await select({ message: 'What would you like to do?', options: [ { value: 'edit', label: '✏️ Edit existing virtues' }, { value: 'add', label: '➕ Add more virtues' }, { value: 'remove', label: '➖ Remove virtues' }, { value: 'done', label: '✅ Done editing' } ] }); switch (action) { case 'edit': const virtueToEdit = await select({ message: 'Which virtue to edit?', options: virtues.map((v, i) => ({ value: i, label: `${v.emoji} ${v.name}` })) }); const idx = virtueToEdit; const newName = await text({ message: 'New name:', initialValue: virtues[idx].name }); const newDefinition = await text({ message: 'New definition:', initialValue: virtues[idx].definition }); virtues[idx].name = newName; virtues[idx].definition = newDefinition; break; case 'add': const newVirtues = await this.createCustomVirtues(); virtues.push(...newVirtues); break; case 'remove': if (virtues.length <= 1) { log.warning('You must have at least one virtue'); break; } const toRemove = await multiselect({ message: 'Select virtues to remove:', options: virtues.map((v, i) => ({ value: i, label: `${v.emoji} ${v.name}` })) }); virtues = virtues.filter((_, i) => !toRemove.includes(i)); break; case 'done': return virtues; } // Recurse if not done if (action === 'done') { return virtues; } else { return await this.editVirtues(virtues); } } async manage(currentProfile) { const profiles = this.db.getAllProfiles(); const action = await select({ message: 'Profile Management', options: [ ...(currentProfile ? [{ value: 'current', label: `📍 Current: ${currentProfile.name}`, hint: 'View current profile' }] : []), { value: 'create', label: '➕ Create new profile' }, { value: 'switch', label: '🔄 Switch profile', hint: `${profiles.length} profiles available` }, { value: 'back', label: '⬅️ Back to main menu' } ] }); switch (action) { case 'current': await this.viewProfile(currentProfile); break; case 'create': await this.createProfile(); break; case 'switch': await this.switchProfile(); break; } } async viewProfile(profile) { const virtues = this.db.getVirtuesForProfile(profile.id); const streak = this.db.calculateCurrentStreak(profile.id); const avgCoherence = this.db.calculateAverageCoherence(profile.id); note(`Name: ${profile.name}\n` + `Template: ${profile.philosophical_template}\n` + `Created: ${new Date(profile.created_at).toLocaleDateString()}\n\n` + `Stats:\n` + `• Current Streak: ${streak} days\n` + `• Average Coherence: ${(avgCoherence * 100).toFixed(1)}%\n\n` + `Virtues (${virtues.length}):\n` + virtues.map(v => `${v.emoji} ${v.name}`).join('\n'), 'Profile Details'); } async switchProfile() { const profiles = this.db.getAllProfiles(); if (profiles.length === 0) { log.warning('No profiles available. Create one first.'); return; } const selected = await select({ message: 'Select profile:', options: profiles.map(p => ({ value: p.id, label: p.name, hint: `${p.philosophical_template} • Created ${new Date(p.created_at).toLocaleDateString()}` })) }); // In a real app, we'd update the current profile in the VirtueTracker log.success(`Switched to profile: ${profiles.find(p => p.id === selected)?.name}`); } getOrdinalSuffix(num) { const ones = num % 10; const tens = num % 100; if (tens >= 11 && tens <= 13) { return 'th'; } switch (ones) { case 1: return 'st'; case 2: return 'nd'; case 3: return 'rd'; default: return 'th'; } } } //# sourceMappingURL=ProfileManager.js.map