Skip to content

Commit ebab824

Browse files
committed
feat: add score, moon-extended, transit-scan, ephemeris-multi (v0.2.17)
New commands: - score: Relationship compatibility scoring - moon-extended: Detailed moon with eclipses, sunrise/sunset - transit-scan: Find transit hits over a date range - ephemeris-multi: Track multiple planets over time
1 parent a3b19fa commit ebab824

File tree

8 files changed

+1008
-7
lines changed

8 files changed

+1008
-7
lines changed

packages/cli/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "thoth-cli",
3-
"version": "0.2.16",
3+
"version": "0.2.17",
44
"description": "𓅝 Astrological calculations from the command line. Swiss Ephemeris precision. Built for humans and agents.",
55
"author": "AKLO <aklo@aklolabs.com>",
66
"license": "MIT",

packages/cli/scripts/postinstall.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import https from 'https';
1414

1515
const __dirname = dirname(fileURLToPath(import.meta.url));
1616

17-
const VERSION = '0.2.16';
17+
const VERSION = '0.2.17';
1818
const REPO = 'aklo360/thoth-cli';
1919

2020
// Use jsDelivr CDN for faster downloads (mirrors GitHub releases)

packages/cli/src/bin.ts

Lines changed: 176 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@ import { writeFileSync } from 'fs';
1111
import {
1212
chart, transit, moon, ephemeris, version,
1313
solarReturn, lunarReturn, synastry, progressions, ephemerisRange,
14-
composite, solarArc, horary
14+
composite, solarArc, horary, score, moonExtended, transitScan, ephemerisMulti
1515
} from './lib/core.js';
1616
import {
1717
formatChart, formatTransits, formatMoon, formatEphemeris,
1818
formatSolarReturn, formatLunarReturn, formatSynastry,
1919
formatProgressions, formatEphemerisRange,
20-
formatComposite, formatSolarArc, formatHorary
20+
formatComposite, formatSolarArc, formatHorary,
21+
formatScore, formatMoonExtended, formatTransitScan, formatEphemerisMulti
2122
} from './lib/format.js';
2223
import { isError } from './types.js';
2324

@@ -80,7 +81,7 @@ EPHEMERIS & MOON
8081
REFERENCE
8182
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8283
thoth key # full symbol reference`)
83-
.version('0.2.16');
84+
.version('0.2.17');
8485

8586
// Chart command
8687
program
@@ -884,9 +885,180 @@ program
884885
console.log('');
885886
});
886887

888+
// Score command - relationship compatibility
889+
program
890+
.command('score')
891+
.description('Calculate relationship compatibility score')
892+
.requiredOption('--date1 <date>', 'Person 1 birth date (YYYY-MM-DD)')
893+
.requiredOption('--time1 <time>', 'Person 1 birth time (HH:MM)')
894+
.option('--city1 <city>', 'Person 1 city')
895+
.option('--nation1 <nation>', 'Person 1 country code', 'US')
896+
.option('--lat1 <lat>', 'Person 1 latitude', parseFloat)
897+
.option('--lng1 <lng>', 'Person 1 longitude', parseFloat)
898+
.option('--name1 <name>', 'Person 1 name', 'Person 1')
899+
.requiredOption('--date2 <date>', 'Person 2 birth date (YYYY-MM-DD)')
900+
.requiredOption('--time2 <time>', 'Person 2 birth time (HH:MM)')
901+
.option('--city2 <city>', 'Person 2 city')
902+
.option('--nation2 <nation>', 'Person 2 country code', 'US')
903+
.option('--lat2 <lat>', 'Person 2 latitude', parseFloat)
904+
.option('--lng2 <lng>', 'Person 2 longitude', parseFloat)
905+
.option('--name2 <name>', 'Person 2 name', 'Person 2')
906+
.option('--json', 'Output raw JSON')
907+
.action(async (options) => {
908+
const [year1, month1, day1] = options.date1.split('-').map(Number);
909+
const [hour1, minute1] = options.time1.split(':').map(Number);
910+
const [year2, month2, day2] = options.date2.split('-').map(Number);
911+
const [hour2, minute2] = options.time2.split(':').map(Number);
912+
913+
const spinner = ora('Calculating compatibility...').start();
914+
915+
const result = await score({
916+
year1, month1, day1, hour1, minute1,
917+
city1: options.city1, nation1: options.nation1,
918+
lat1: options.lat1, lng1: options.lng1, name1: options.name1,
919+
year2, month2, day2, hour2, minute2,
920+
city2: options.city2, nation2: options.nation2,
921+
lat2: options.lat2, lng2: options.lng2, name2: options.name2,
922+
});
923+
924+
spinner.stop();
925+
926+
if (isError(result)) {
927+
console.error(chalk.red('Error: ' + result.error));
928+
process.exit(1);
929+
}
930+
931+
if (options.json) {
932+
console.log(JSON.stringify(result, null, 2));
933+
} else {
934+
console.log(formatScore(result));
935+
}
936+
});
937+
938+
// Moon extended command
939+
program
940+
.command('moon-extended')
941+
.description('Get detailed moon data with eclipses and sunrise/sunset')
942+
.option('--date <date>', 'Date (YYYY-MM-DD, default: today)')
943+
.option('--lat <lat>', 'Latitude', parseFloat, 40.7128)
944+
.option('--lng <lng>', 'Longitude', parseFloat, -74.0060)
945+
.option('--tz <tz>', 'Timezone', 'America/New_York')
946+
.option('--json', 'Output raw JSON')
947+
.action(async (options) => {
948+
let year, month, day;
949+
if (options.date) {
950+
[year, month, day] = options.date.split('-').map(Number);
951+
}
952+
953+
const spinner = ora('Getting moon details...').start();
954+
955+
const result = await moonExtended({
956+
year, month, day,
957+
lat: options.lat, lng: options.lng, tz: options.tz,
958+
});
959+
960+
spinner.stop();
961+
962+
if (isError(result)) {
963+
console.error(chalk.red('Error: ' + result.error));
964+
process.exit(1);
965+
}
966+
967+
if (options.json) {
968+
console.log(JSON.stringify(result, null, 2));
969+
} else {
970+
console.log(formatMoonExtended(result));
971+
}
972+
});
973+
974+
// Transit scan command
975+
program
976+
.command('transit-scan')
977+
.description('Scan for transit aspects over a date range')
978+
.requiredOption('--natal-date <date>', 'Natal birth date (YYYY-MM-DD)')
979+
.requiredOption('--natal-time <time>', 'Natal birth time (HH:MM)')
980+
.option('--city <city>', 'Natal city')
981+
.option('--nation <nation>', 'Country code', 'US')
982+
.option('--lat <lat>', 'Natal latitude', parseFloat)
983+
.option('--lng <lng>', 'Natal longitude', parseFloat)
984+
.requiredOption('--from <date>', 'Start date (YYYY-MM-DD)')
985+
.requiredOption('--to <date>', 'End date (YYYY-MM-DD)')
986+
.option('--orb <orb>', 'Aspect orb in degrees', parseFloat, 1)
987+
.option('--step <step>', 'Step: day or week', 'day')
988+
.option('--json', 'Output raw JSON')
989+
.action(async (options) => {
990+
const [natalYear, natalMonth, natalDay] = options.natalDate.split('-').map(Number);
991+
const [natalHour, natalMinute] = options.natalTime.split(':').map(Number);
992+
const [startYear, startMonth, startDay] = options.from.split('-').map(Number);
993+
const [endYear, endMonth, endDay] = options.to.split('-').map(Number);
994+
995+
const spinner = ora('Scanning transits...').start();
996+
997+
const result = await transitScan({
998+
natalYear, natalMonth, natalDay, natalHour, natalMinute,
999+
natalCity: options.city, nation: options.nation,
1000+
natalLat: options.lat, natalLng: options.lng,
1001+
startYear, startMonth, startDay,
1002+
endYear, endMonth, endDay,
1003+
orb: options.orb, step: options.step,
1004+
});
1005+
1006+
spinner.stop();
1007+
1008+
if (isError(result)) {
1009+
console.error(chalk.red('Error: ' + result.error));
1010+
process.exit(1);
1011+
}
1012+
1013+
if (options.json) {
1014+
console.log(JSON.stringify(result, null, 2));
1015+
} else {
1016+
console.log(formatTransitScan(result));
1017+
}
1018+
});
1019+
1020+
// Ephemeris multi command
1021+
program
1022+
.command('ephemeris-multi')
1023+
.description('Get ephemeris for multiple bodies over a date range')
1024+
.option('--bodies <bodies>', 'Comma-separated bodies', 'sun,moon,mercury,venus,mars,jupiter,saturn')
1025+
.requiredOption('--from <date>', 'Start date (YYYY-MM-DD)')
1026+
.requiredOption('--to <date>', 'End date (YYYY-MM-DD)')
1027+
.option('--step <step>', 'Step: hour, day, week, month', 'day')
1028+
.option('--lat <lat>', 'Latitude', parseFloat)
1029+
.option('--lng <lng>', 'Longitude', parseFloat)
1030+
.option('--json', 'Output raw JSON')
1031+
.action(async (options) => {
1032+
const [startYear, startMonth, startDay] = options.from.split('-').map(Number);
1033+
const [endYear, endMonth, endDay] = options.to.split('-').map(Number);
1034+
1035+
const spinner = ora('Getting ephemeris data...').start();
1036+
1037+
const result = await ephemerisMulti({
1038+
bodies: options.bodies,
1039+
startYear, startMonth, startDay,
1040+
endYear, endMonth, endDay,
1041+
step: options.step,
1042+
lat: options.lat, lng: options.lng,
1043+
});
1044+
1045+
spinner.stop();
1046+
1047+
if (isError(result)) {
1048+
console.error(chalk.red('Error: ' + result.error));
1049+
process.exit(1);
1050+
}
1051+
1052+
if (options.json) {
1053+
console.log(JSON.stringify(result, null, 2));
1054+
} else {
1055+
console.log(formatEphemerisMulti(result));
1056+
}
1057+
});
1058+
8871059
// Banner
8881060
console.log(chalk.dim(''));
889-
console.log(chalk.yellow(' 𓅝') + chalk.dim(' thoth-cli v0.2.16'));
1061+
console.log(chalk.yellow(' 𓅝') + chalk.dim(' thoth-cli v0.2.17'));
8901062
console.log(chalk.dim(''));
8911063

8921064
program.parse();

packages/cli/src/lib/core.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ import type {
1818
CompositeResult,
1919
SolarArcResult,
2020
HoraryResult,
21+
ScoreResult,
22+
MoonExtendedResult,
23+
TransitScanResult,
24+
EphemerisMultiResult,
2125
ChartOptions,
2226
TransitOptions,
2327
MoonOptions,
@@ -30,6 +34,10 @@ import type {
3034
CompositeOptions,
3135
SolarArcOptions,
3236
HoraryOptions,
37+
ScoreOptions,
38+
MoonExtendedOptions,
39+
TransitScanOptions,
40+
EphemerisMultiOptions,
3341
ThothResult,
3442
} from '../types.js';
3543

@@ -385,3 +393,109 @@ export async function horary(options: HoraryOptions): Promise<ThothResult<Horary
385393
export async function version(): Promise<ThothResult<{ version: string }>> {
386394
return execute<{ version: string }>('version', []);
387395
}
396+
397+
/**
398+
* Calculate relationship compatibility score
399+
*/
400+
export async function score(options: ScoreOptions): Promise<ThothResult<ScoreResult>> {
401+
const args = [
402+
'--year1', String(options.year1),
403+
'--month1', String(options.month1),
404+
'--day1', String(options.day1),
405+
'--hour1', String(options.hour1 ?? 12),
406+
'--minute1', String(options.minute1 ?? 0),
407+
'--name1', options.name1 ?? 'Person 1',
408+
'--year2', String(options.year2),
409+
'--month2', String(options.month2),
410+
'--day2', String(options.day2),
411+
'--hour2', String(options.hour2 ?? 12),
412+
'--minute2', String(options.minute2 ?? 0),
413+
'--name2', options.name2 ?? 'Person 2',
414+
];
415+
416+
if (options.city1) {
417+
args.push('--city1', options.city1);
418+
args.push('--nation1', options.nation1 ?? 'US');
419+
} else if (options.lat1 !== undefined && options.lng1 !== undefined) {
420+
args.push('--lat1', String(options.lat1));
421+
args.push('--lng1', String(options.lng1));
422+
}
423+
424+
if (options.city2) {
425+
args.push('--city2', options.city2);
426+
args.push('--nation2', options.nation2 ?? 'US');
427+
} else if (options.lat2 !== undefined && options.lng2 !== undefined) {
428+
args.push('--lat2', String(options.lat2));
429+
args.push('--lng2', String(options.lng2));
430+
}
431+
432+
return execute<ScoreResult>('score', args);
433+
}
434+
435+
/**
436+
* Get extended moon data with eclipses
437+
*/
438+
export async function moonExtended(options: MoonExtendedOptions): Promise<ThothResult<MoonExtendedResult>> {
439+
const args: string[] = [];
440+
441+
if (options.year) args.push('--year', String(options.year));
442+
if (options.month) args.push('--month', String(options.month));
443+
if (options.day) args.push('--day', String(options.day));
444+
if (options.lat !== undefined) args.push('--lat', String(options.lat));
445+
if (options.lng !== undefined) args.push('--lng', String(options.lng));
446+
if (options.tz) args.push('--tz', options.tz);
447+
448+
return execute<MoonExtendedResult>('moon-extended', args);
449+
}
450+
451+
/**
452+
* Scan for transits over a date range
453+
*/
454+
export async function transitScan(options: TransitScanOptions): Promise<ThothResult<TransitScanResult>> {
455+
const args = [
456+
'--natal-year', String(options.natalYear),
457+
'--natal-month', String(options.natalMonth),
458+
'--natal-day', String(options.natalDay),
459+
'--natal-hour', String(options.natalHour ?? 12),
460+
'--natal-minute', String(options.natalMinute ?? 0),
461+
'--start-year', String(options.startYear),
462+
'--start-month', String(options.startMonth ?? 1),
463+
'--start-day', String(options.startDay ?? 1),
464+
'--end-year', String(options.endYear),
465+
'--end-month', String(options.endMonth ?? 12),
466+
'--end-day', String(options.endDay ?? 28),
467+
'--orb', String(options.orb ?? 1),
468+
'--step', options.step ?? 'day',
469+
];
470+
471+
if (options.natalCity) {
472+
args.push('--natal-city', options.natalCity);
473+
args.push('--nation', options.nation ?? 'US');
474+
} else if (options.natalLat !== undefined && options.natalLng !== undefined) {
475+
args.push('--natal-lat', String(options.natalLat));
476+
args.push('--natal-lng', String(options.natalLng));
477+
}
478+
479+
return execute<TransitScanResult>('transit-scan', args);
480+
}
481+
482+
/**
483+
* Get multi-body ephemeris over a date range
484+
*/
485+
export async function ephemerisMulti(options: EphemerisMultiOptions): Promise<ThothResult<EphemerisMultiResult>> {
486+
const args = [
487+
'--bodies', options.bodies ?? 'sun,moon,mercury,venus,mars,jupiter,saturn',
488+
'--start-year', String(options.startYear),
489+
'--start-month', String(options.startMonth ?? 1),
490+
'--start-day', String(options.startDay ?? 1),
491+
'--end-year', String(options.endYear),
492+
'--end-month', String(options.endMonth ?? 12),
493+
'--end-day', String(options.endDay ?? 28),
494+
'--step', options.step ?? 'day',
495+
];
496+
497+
if (options.lat !== undefined) args.push('--lat', String(options.lat));
498+
if (options.lng !== undefined) args.push('--lng', String(options.lng));
499+
500+
return execute<EphemerisMultiResult>('ephemeris-multi', args);
501+
}

0 commit comments

Comments
 (0)