- Core calculations (Life Path, Expression, Soul Urge, Birthday) - Advanced numbers (Maturity, Personality, Hidden Passion, Karmic Lessons) - Timing cycles and optimal days finder - Compatibility analysis and name optimizer - Telos integration for personal development - Professional PDF report generation - Profile management system - Security fix: Add .claude/ to .gitignore 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
620 lines
21 KiB
TypeScript
Executable file
620 lines
21 KiB
TypeScript
Executable file
#!/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<string, number>;
|
|
vowelBreakdown: Record<string, number>;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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<string, number> = {};
|
|
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<string, number> = {};
|
|
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;
|
|
}
|
|
|
|
/**
|
|
* Main CLI handler
|
|
*/
|
|
function main() {
|
|
const args = process.argv.slice(2);
|
|
|
|
// 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 + 1]) {
|
|
name = args[i + 1];
|
|
i++;
|
|
} else if (args[i] === '--birthdate' && 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] === '--cycles' || args[i] === '-c') {
|
|
showCycles = 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
|
|
|
|
Usage:
|
|
bun numerology.ts --profile <id> [options]
|
|
bun numerology.ts --name "Full Name" --birthdate "mm/dd/yyyy" [options]
|
|
|
|
Options:
|
|
-p, --profile ID Use saved profile
|
|
--name NAME Full name (required if no profile)
|
|
--birthdate DATE Birthdate in mm/dd/yyyy format (required if no profile)
|
|
--format FORMAT Output format: terminal, markdown, json (default: terminal)
|
|
--detailed, -d Show detailed meanings (terminal format only)
|
|
--advanced, -a Show advanced numbers (Maturity, Personality, Hidden Passion, etc.)
|
|
--cycles, -c Show timing cycles (Personal & Universal Year/Month/Day)
|
|
--date DATE Date for cycle calculation (default: today, format: mm/dd/yyyy)
|
|
--help, -h Show this help message
|
|
|
|
Examples:
|
|
# With profile
|
|
bun numerology.ts --profile john
|
|
bun numerology.ts --profile john --detailed --advanced --cycles
|
|
|
|
# With name/birthdate
|
|
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
|
|
bun numerology.ts --name "Alice Wonder" --birthdate "7/7/1977" --detailed
|
|
`);
|
|
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) {
|
|
main();
|
|
}
|
|
|
|
export { calculate, formatTerminal, formatMarkdown, formatJson, type NumerologyResult };
|