#!/usr/bin/env bun /** * Numerology Calculator - TypeScript Edition * * Calculates core numerology numbers: * - Life Path: Your life's purpose and journey * - Expression: Your natural talents and abilities * - Soul Urge: Your inner desires and motivations * - Birthday: Special gifts from your birth day * * Usage: * bun numerology.ts --name "John Doe" --birthdate "5/13/1982" * bun numerology.ts --name "Jane Smith" --birthdate "11/22/1990" --format markdown * bun numerology.ts --name "Bob Jones" --birthdate "3/14/1975" --format json */ import { lifePath, expression, soulUrge, birthday, type NumberMeaning } from './meanings'; import { calculateCycles, type CycleCalculations } from './cycles'; import { personalYear, personalMonth, personalDay, universalYear } from './cycle-meanings'; import { calculateCoreNumbers, calculateAdditionalNumbers, type AdditionalNumbers } from './core-calculator'; import { maturityMeanings, personalityMeanings, hiddenPassionMeanings, karmicLessonMeanings, balanceMeanings } from './additional-meanings'; import { loadProfile } from './profile-manager'; const MASTER_NUMBERS = [11, 22, 33]; interface NumerologyResult { name: string; birthdate: string; lifePath: number; expression: number; soulUrge: number; birthday: number; calculations: { month: number; day: number; year: number; nameBreakdown: Record; vowelBreakdown: Record; }; } /** * Reduces a number to single digit or returns master number */ function reduce(n: number, context: string = "", silent: boolean = false): number { if (MASTER_NUMBERS.includes(n)) { if (context && !silent) { console.error(`✨ Master number found in ${context}: ${n}`); } return n; } const total = String(n).split('').reduce((sum, digit) => sum + parseInt(digit), 0); return total < 10 ? total : reduce(total, context); } /** * Converts name to numerology value */ function nameToNumber(name: string): number { const cleaned = name.toLowerCase().replace(/[^a-z]/g, ''); let total = 0; for (const char of cleaned) { const value = char.charCodeAt(0) - 96; // a=1, b=2, etc. total += value < 10 ? value : reduce(value); } return total; } /** * Checks for master number before further reduction */ function masterCheck(total: number, context: string = "", silent: boolean = false): number { if (MASTER_NUMBERS.includes(total)) { if (context && !silent) { console.error(`✨ Master number found in ${context}: ${total}`); } return total; } const reduced = String(total).split('').reduce((sum, digit) => sum + parseInt(digit), 0); return reduced < 10 ? reduced : reduce(reduced); } /** * Extracts vowels from name */ function extractVowels(name: string): string { return name.toLowerCase().replace(/[^aeiou]/g, ''); } /** * Parse birthdate string (mm/dd/yyyy) */ function parseBirthdate(birthdate: string): { month: number; day: number; year: number } { const parts = birthdate.split('/'); if (parts.length !== 3) { throw new Error('Invalid birthdate format. Use mm/dd/yyyy (e.g., 5/13/1982)'); } return { month: parseInt(parts[0]), day: parseInt(parts[1]), year: parseInt(parts[2]) }; } /** * Calculate all numerology numbers */ function calculate(name: string, birthdate: string, silent: boolean = false): NumerologyResult { // Parse name const nameParts = name.trim().split(/\s+/); if (nameParts.length < 2) { throw new Error('Please provide at least first and last name'); } // Parse birthdate const { month, day, year } = parseBirthdate(birthdate); // Life Path (from birthdate) const monthReduced = reduce(month, "Month", silent); const dayReduced = reduce(day, "Day", silent); const yearReduced = reduce(year, "Year", silent); const birthDateSum = monthReduced + dayReduced + yearReduced; const lifePathNum = reduce(birthDateSum, "Life Path", silent); // Birthday (from day of birth) const birthdayNum = reduce(day, "Birthday", silent); // Expression (from full name) const nameBreakdown: Record = {}; let expressionTotal = 0; // Use only first and last name if more than 4 parts const namesToUse = nameParts.length > 4 ? [nameParts[0], nameParts[nameParts.length - 1]] : nameParts; for (const part of namesToUse) { const value = nameToNumber(part); const checked = masterCheck(value, `Name: ${part}`, silent); nameBreakdown[part] = checked; expressionTotal += checked; } const expressionNum = masterCheck(reduce(expressionTotal, "", silent), "Expression", silent); // Soul Urge (from vowels in name) const vowelBreakdown: Record = {}; let soulUrgeTotal = 0; for (const part of nameParts) { const vowels = extractVowels(part); if (vowels) { const value = nameToNumber(vowels); const checked = masterCheck(value, `Vowels in ${part}`, silent); vowelBreakdown[part] = checked; soulUrgeTotal += checked; } } const soulUrgeNum = masterCheck(reduce(soulUrgeTotal, "", silent), "Soul Urge", silent); return { name, birthdate, lifePath: lifePathNum, expression: expressionNum, soulUrge: soulUrgeNum, birthday: birthdayNum, calculations: { month: monthReduced, day: dayReduced, year: yearReduced, nameBreakdown, vowelBreakdown } }; } /** * Format output as terminal text (with colors) */ function formatTerminal(result: NumerologyResult, detailed: boolean = false): string { const CYAN = '\x1b[36m'; const GREEN = '\x1b[32m'; const YELLOW = '\x1b[33m'; const BLUE = '\x1b[34m'; const RESET = '\x1b[0m'; const BOLD = '\x1b[1m'; let output = `\n${CYAN}${BOLD}╔═══════════════════════════════════════╗${RESET}\n`; output += `${CYAN}${BOLD}║ NUMEROLOGY CHART ║${RESET}\n`; output += `${CYAN}${BOLD}╚═══════════════════════════════════════╝${RESET}\n\n`; output += `${BOLD}Name:${RESET} ${result.name}\n`; output += `${BOLD}Birthdate:${RESET} ${result.birthdate}\n\n`; output += `${GREEN}${BOLD}Core Numbers:${RESET}\n`; output += `${GREEN} 1. Life Path:${RESET} ${result.lifePath}\n`; output += `${GREEN} 2. Expression:${RESET} ${result.expression}\n`; output += `${GREEN} 3. Soul Urge:${RESET} ${result.soulUrge}\n`; output += `${GREEN} 4. Birthday:${RESET} ${result.birthday}\n`; if (detailed) { output += `\n${BLUE}${BOLD}═══════════════════════════════════════${RESET}\n`; output += `${BLUE}${BOLD}DETAILED MEANINGS${RESET}\n`; output += `${BLUE}${BOLD}═══════════════════════════════════════${RESET}\n\n`; // Life Path const lpMeaning = lifePath[result.lifePath]; output += `${YELLOW}${BOLD}Life Path ${result.lifePath}:${RESET} ${lpMeaning.keywords.join(', ')}\n`; output += `${lpMeaning.description}\n\n`; output += `${BOLD}Strengths:${RESET} ${lpMeaning.strengths.join(', ')}\n`; output += `${BOLD}Challenges:${RESET} ${lpMeaning.challenges.join(', ')}\n`; if (lpMeaning.lifePurpose) { output += `${BOLD}Life Purpose:${RESET} ${lpMeaning.lifePurpose}\n`; } output += '\n'; // Expression const exMeaning = expression[result.expression]; output += `${YELLOW}${BOLD}Expression ${result.expression}:${RESET} ${exMeaning.keywords.join(', ')}\n`; output += `${exMeaning.description}\n\n`; // Soul Urge const suMeaning = soulUrge[result.soulUrge]; output += `${YELLOW}${BOLD}Soul Urge ${result.soulUrge}:${RESET} ${suMeaning.keywords.join(', ')}\n`; output += `${suMeaning.description}\n\n`; // Birthday const bdMeaning = birthday[result.birthday]; output += `${YELLOW}${BOLD}Birthday ${result.birthday}:${RESET} ${bdMeaning.keywords.join(', ')}\n`; output += `${bdMeaning.description}\n`; } return output; } /** * Format output as markdown */ function formatMarkdown(result: NumerologyResult): string { let output = `# Numerology Chart\n\n`; output += `**Name:** ${result.name} \n`; output += `**Birthdate:** ${result.birthdate}\n\n`; output += `## Core Numbers\n\n`; output += `1. **Life Path:** ${result.lifePath}\n`; output += `2. **Expression:** ${result.expression}\n`; output += `3. **Soul Urge:** ${result.soulUrge}\n`; output += `4. **Birthday:** ${result.birthday}\n\n`; output += `---\n\n`; output += `## Detailed Meanings\n\n`; // Life Path const lpMeaning = lifePath[result.lifePath]; output += `### Life Path ${result.lifePath}: ${lpMeaning.keywords.join(', ')}\n\n`; output += `${lpMeaning.description}\n\n`; output += `**Strengths:** ${lpMeaning.strengths.join(', ')}\n\n`; output += `**Challenges:** ${lpMeaning.challenges.join(', ')}\n\n`; if (lpMeaning.lifePurpose) { output += `**Life Purpose:** ${lpMeaning.lifePurpose}\n\n`; } if (lpMeaning.careerPaths) { output += `**Career Paths:** ${lpMeaning.careerPaths.join(', ')}\n\n`; } if (lpMeaning.relationships) { output += `**Relationships:** ${lpMeaning.relationships}\n\n`; } if (lpMeaning.spiritualLesson) { output += `**Spiritual Lesson:** ${lpMeaning.spiritualLesson}\n\n`; } // Expression const exMeaning = expression[result.expression]; output += `### Expression ${result.expression}: ${exMeaning.keywords.join(', ')}\n\n`; output += `${exMeaning.description}\n\n`; output += `**Strengths:** ${exMeaning.strengths.join(', ')}\n\n`; output += `**Challenges:** ${exMeaning.challenges.join(', ')}\n\n`; // Soul Urge const suMeaning = soulUrge[result.soulUrge]; output += `### Soul Urge ${result.soulUrge}: ${suMeaning.keywords.join(', ')}\n\n`; output += `${suMeaning.description}\n\n`; output += `**Strengths:** ${suMeaning.strengths.join(', ')}\n\n`; output += `**Challenges:** ${suMeaning.challenges.join(', ')}\n\n`; // Birthday const bdMeaning = birthday[result.birthday]; output += `### Birthday ${result.birthday}: ${bdMeaning.keywords.join(', ')}\n\n`; output += `${bdMeaning.description}\n\n`; output += `**Strengths:** ${bdMeaning.strengths.join(', ')}\n\n`; output += `**Challenges:** ${bdMeaning.challenges.join(', ')}\n\n`; return output; } /** * Format output as JSON */ function formatJson(result: NumerologyResult): string { const output = { ...result, meanings: { lifePath: lifePath[result.lifePath], expression: expression[result.expression], soulUrge: soulUrge[result.soulUrge], birthday: birthday[result.birthday] } }; return JSON.stringify(output, null, 2); } /** * Format cycles output for terminal */ function formatCyclesTerminal(cycles: CycleCalculations): string { const MAGENTA = '\x1b[35m'; const CYAN = '\x1b[36m'; const YELLOW = '\x1b[33m'; const RESET = '\x1b[0m'; const BOLD = '\x1b[1m'; let output = `\n${MAGENTA}${BOLD}═══════════════════════════════════════${RESET}\n`; output += `${MAGENTA}${BOLD}TIMING CYCLES${RESET}\n`; output += `${MAGENTA}${BOLD}═══════════════════════════════════════${RESET}\n\n`; output += `${BOLD}Date:${RESET} ${cycles.targetDate}\n\n`; // Universal Cycles output += `${CYAN}${BOLD}Universal Energy (Global):${RESET}\n`; output += ` Year: ${cycles.universal.year} ${universalYear[cycles.universal.year]}\n`; output += ` Month: ${cycles.universal.month}\n`; output += ` Day: ${cycles.universal.day}\n\n`; // Personal Cycles output += `${CYAN}${BOLD}Personal Energy (Your Rhythm):${RESET}\n`; output += ` Year: ${cycles.personal.year}\n`; output += ` Month: ${cycles.personal.month}\n`; output += ` Day: ${cycles.personal.day} ${personalDay[cycles.personal.day]}\n\n`; // Personal Year meaning const pyMeaning = personalYear[cycles.personal.year]; output += `${YELLOW}${BOLD}Personal Year ${cycles.personal.year}: ${pyMeaning.keywords.join(', ')}${RESET}\n`; output += `${pyMeaning.theme}\n\n`; output += `${BOLD}Best For:${RESET} ${pyMeaning.bestFor.slice(0, 3).join(', ')}\n`; output += `${BOLD}Avoid:${RESET} ${pyMeaning.avoidDoing.slice(0, 2).join(', ')}\n\n`; // Personal Month meaning const pmMeaning = personalMonth[cycles.personal.month]; output += `${YELLOW}${BOLD}Personal Month ${cycles.personal.month}: ${pmMeaning.keywords.join(', ')}${RESET}\n`; output += `${pmMeaning.theme}\n`; return output; } /** * Format cycles output for markdown */ function formatCyclesMarkdown(cycles: CycleCalculations): string { let output = `\n---\n\n# Timing Cycles\n\n`; output += `**Date:** ${cycles.targetDate}\n\n`; output += `## Universal Energy (Global)\n\n`; output += `- **Year:** ${cycles.universal.year}\n`; output += ` - ${universalYear[cycles.universal.year]}\n`; output += `- **Month:** ${cycles.universal.month}\n`; output += `- **Day:** ${cycles.universal.day}\n\n`; output += `## Personal Energy (Your Rhythm)\n\n`; output += `- **Year:** ${cycles.personal.year}\n`; output += `- **Month:** ${cycles.personal.month}\n`; output += `- **Day:** ${cycles.personal.day}\n`; output += ` - ${personalDay[cycles.personal.day]}\n\n`; // Personal Year meaning const pyMeaning = personalYear[cycles.personal.year]; output += `## Personal Year ${cycles.personal.year}\n\n`; output += `> *${pyMeaning.keywords.join(' • ')}*\n\n`; output += `**Theme:** ${pyMeaning.theme}\n\n`; output += `### Opportunities\n`; pyMeaning.opportunities.forEach(o => output += `- ${o}\n`); output += `\n### Challenges\n`; pyMeaning.challenges.forEach(c => output += `- ${c}\n`); output += `\n**Advice:** ${pyMeaning.advice}\n\n`; output += `**Best For:** ${pyMeaning.bestFor.join(', ')}\n\n`; output += `**Avoid Doing:** ${pyMeaning.avoidDoing.join(', ')}\n\n`; // Personal Month meaning const pmMeaning = personalMonth[cycles.personal.month]; output += `## Personal Month ${cycles.personal.month}\n\n`; output += `> *${pmMeaning.keywords.join(' • ')}*\n\n`; output += `**Theme:** ${pmMeaning.theme}\n\n`; output += `**Advice:** ${pmMeaning.advice}\n\n`; return output; } /** * Format advanced numbers for terminal output */ function formatAdvancedTerminal(additional: AdditionalNumbers, detailed: boolean = false): string { const PURPLE = '\x1b[35m'; const GREEN = '\x1b[32m'; const YELLOW = '\x1b[33m'; const RED = '\x1b[31m'; const RESET = '\x1b[0m'; const BOLD = '\x1b[1m'; let output = `\n${PURPLE}${BOLD}═══════════════════════════════════════${RESET}\n`; output += `${PURPLE}${BOLD}ADVANCED NUMBERS${RESET}\n`; output += `${PURPLE}${BOLD}═══════════════════════════════════════${RESET}\n\n`; // Maturity const maturityMeaning = maturityMeanings[additional.maturity]; output += `${GREEN} 5. Maturity:${RESET} ${additional.maturity} (${maturityMeaning.keywords.slice(0, 3).join(', ')})\n`; if (detailed) { output += ` ${maturityMeaning.description}\n`; } // Personality const personalityMeaning = personalityMeanings[additional.personality]; output += `${GREEN} 6. Personality:${RESET} ${additional.personality} (${personalityMeaning.keywords.slice(0, 3).join(', ')})\n`; if (detailed) { output += ` ${personalityMeaning.description}\n`; } // Hidden Passion if (additional.hiddenPassion) { const hiddenPassionMeaning = hiddenPassionMeanings[additional.hiddenPassion]; output += `${GREEN} 7. Hidden Passion:${RESET} ${additional.hiddenPassion} (${hiddenPassionMeaning.keywords.slice(0, 2).join(', ')})\n`; if (detailed) { output += ` ${hiddenPassionMeaning.description}\n`; } } // Balance const balanceMeaning = balanceMeanings[additional.balance]; output += `${GREEN} 8. Balance:${RESET} ${additional.balance} (${balanceMeaning.keywords.slice(0, 2).join(', ')})\n`; if (detailed) { output += ` ${balanceMeaning.description}\n`; } // Karmic Lessons if (additional.karmicLessons.length > 0) { output += `${RED} 9. Karmic Lessons:${RESET} ${additional.karmicLessons.join(', ')}\n`; if (detailed) { output += ` Areas to develop: ${additional.karmicLessons.map(n => karmicLessonMeanings[n].keywords[0]).join(', ')}\n`; } } else { output += `${GREEN} 9. Karmic Lessons:${RESET} None (All numbers 1-9 present)\n`; } output += '\n'; return output; } /** * Router function - delegates to individual scripts */ async function routeToScript(scriptName: string, args: string[]): Promise { const scriptPath = new URL(`./${scriptName}`, import.meta.url).pathname; const proc = Bun.spawn(['bun', scriptPath, ...args], { stdout: 'inherit', stderr: 'inherit', stdin: 'inherit', }); const exitCode = await proc.exited; process.exit(exitCode); } /** * Main CLI handler */ async function main() { const args = process.argv.slice(2); // Check for routing commands first (before parsing other args) for (let i = 0; i < args.length; i++) { // Week Cycles (handle both -c and combined flags like -cd) if (args[i] === '--cycles-week' || args[i] === '-c' || args[i].startsWith('-c')) { // For combined flags like -cd, expand them properly let scriptArgs = args.filter((_, idx) => idx !== i); // If it's a combined flag (like -cd), extract the additional flags if (args[i].startsWith('-c') && args[i].length > 2) { const extraFlags = args[i].slice(2).split('').map(f => `-${f}`); scriptArgs = [...extraFlags, ...scriptArgs]; } await routeToScript('cycles-week.ts', scriptArgs); return; } // Year Ahead if (args[i] === '--year-ahead' || args[i] === '-y') { const scriptArgs = args.filter((_, idx) => idx !== i); await routeToScript('year-ahead.ts', scriptArgs); return; } // Optimal Days if (args[i] === '--optimal-days') { const scriptArgs = args.filter((_, idx) => idx !== i); await routeToScript('optimal-days.ts', scriptArgs); return; } // Pinnacles if (args[i] === '--pinnacles') { const scriptArgs = args.filter((_, idx) => idx !== i); await routeToScript('pinnacles.ts', scriptArgs); return; } // Name Optimizer if (args[i] === '--name-optimizer') { const scriptArgs = args.filter((_, idx) => idx !== i); await routeToScript('name-optimizer.ts', scriptArgs); return; } // Compatibility if (args[i] === '--compatibility') { const scriptArgs = args.filter((_, idx) => idx !== i); await routeToScript('compatibility.ts', scriptArgs); return; } } // No routing command found - proceed with core numerology calculation // Parse arguments let name = ''; let birthdate = ''; let profileId = ''; let format: 'terminal' | 'markdown' | 'json' = 'terminal'; let detailed = false; let showCycles = false; let cycleDate = ''; let showAdvanced = false; for (let i = 0; i < args.length; i++) { if ((args[i] === '--profile' || args[i] === '-p') && args[i + 1]) { profileId = args[i + 1]; i++; } else if ((args[i] === '--name' || args[i] === '-n') && args[i + 1]) { name = args[i + 1]; i++; } else if ((args[i] === '--birthdate' || args[i] === '-b') && args[i + 1]) { birthdate = args[i + 1]; i++; } else if (args[i] === '--format' && args[i + 1]) { const fmt = args[i + 1].toLowerCase(); if (fmt === 'terminal' || fmt === 'markdown' || fmt === 'json') { format = fmt; } i++; } else if (args[i] === '--detailed' || args[i] === '-d') { detailed = true; } else if (args[i] === '--date' && args[i + 1]) { cycleDate = args[i + 1]; i++; } else if (args[i] === '--advanced' || args[i] === '-a') { showAdvanced = true; } else if (args[i] === '--help' || args[i] === '-h') { console.log(` Numerology Calculator - Unified CLI CORE NUMBERS: numerology -p Core numerology numbers numerology -n "Name" -b "mm/dd/yyyy" Core numbers with name/birthdate Options: -p, --profile ID Use saved profile -n, --name NAME Full name (required if no profile) -b, --birthdate DATE Birthdate in mm/dd/yyyy format (required if no profile) -d, --detailed Show detailed meanings -a, --advanced Show advanced numbers (Maturity, Personality, etc.) --format FORMAT Output format: terminal, markdown, json --date DATE Date for cycle calculation (mm/dd/yyyy) SPECIALIZED TOOLS: -c, --cycles-week Weekly personal day cycles -y, --year-ahead Full year month-by-month overview --optimal-days Find best days for specific activities --pinnacles Life stages & 9-year pinnacle cycles --name-optimizer Find powerful name variations --compatibility Compare two people's charts Examples: # Core numbers numerology -p rob numerology -p rob -d -a numerology -n "John Doe" -b "5/13/1982" # Specialized tools numerology -c -p rob # This week's cycles numerology -y -p rob # Year ahead numerology --pinnacles -p rob # Life pinnacles numerology --optimal-days -p rob --day 1 # Find all Personal Day 1s numerology --compatibility --p1 "John:1/1/1980" --p2 "Jane:2/2/1985" For detailed help on each tool, use: numerology --help `); process.exit(0); } } // Load profile if specified if (profileId) { const profile = loadProfile(profileId); if (!profile) { console.error(`Error: Profile '${profileId}' not found`); console.error('List profiles with: bun profile.ts list'); process.exit(1); } name = profile.name; birthdate = profile.birthdate; } // Validate inputs if (!name || !birthdate) { console.error('Error: Either --profile or both --name and --birthdate are required'); console.error('Use --help for usage information'); process.exit(1); } try { // Calculate (silent mode for JSON to avoid console pollution) const silent = format === 'json'; const result = calculate(name, birthdate, silent); // Calculate advanced numbers if requested let advancedNumbers: AdditionalNumbers | undefined; if (showAdvanced) { const coreNumbers = calculateCoreNumbers(name, birthdate); advancedNumbers = calculateAdditionalNumbers(name, coreNumbers); } // Calculate cycles if requested let cycles: CycleCalculations | undefined; if (showCycles) { cycles = calculateCycles(birthdate, cycleDate || undefined); } // Format and output let output: string; switch (format) { case 'markdown': output = formatMarkdown(result); if (cycles) { output += formatCyclesMarkdown(cycles); } break; case 'json': output = formatJson(result); break; default: output = formatTerminal(result, detailed); if (advancedNumbers) { output += formatAdvancedTerminal(advancedNumbers, detailed); } if (cycles) { output += formatCyclesTerminal(cycles); } } console.log(output); } catch (error) { console.error(`Error: ${error instanceof Error ? error.message : String(error)}`); process.exit(1); } } // Run if called directly if (import.meta.main) { await main(); } export { calculate, formatTerminal, formatMarkdown, formatJson, type NumerologyResult };