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