thoth-cli 0.2.23 → 0.2.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -34,19 +34,19 @@ The binary downloads automatically on first install (~18MB).
34
34
 
35
35
  ```bash
36
36
  # Natal chart
37
- thoth chart --date 1991-07-08 --time 14:06 --city "New York"
37
+ thoth chart --date 1879-03-14 --time 11:30 --city "New York"
38
38
 
39
39
  # Current transits
40
- thoth transit --natal-date 1991-07-08 --natal-time 14:06 --city "New York"
40
+ thoth transit --natal-date 1879-03-14 --natal-time 11:30 --city "New York"
41
41
 
42
42
  # Solar return for 2026
43
- thoth solar-return --natal-date 1991-07-08 --natal-time 14:06 --city "New York" --year 2026
43
+ thoth solar-return --natal-date 1879-03-14 --natal-time 11:30 --city "New York" --year 2026
44
44
 
45
45
  # Horary divination
46
46
  thoth horary --question "Should I take the job?" --city "New York"
47
47
 
48
48
  # Relationship synastry
49
- thoth synastry --date1 1991-07-08 --time1 14:06 --city1 "NYC" \
49
+ thoth synastry --date1 1879-03-14 --time1 11:30 --city1 "NYC" \
50
50
  --date2 1990-03-15 --time2 09:30 --city2 "LA"
51
51
 
52
52
  # Moon phase
@@ -68,9 +68,9 @@ thoth key
68
68
  Calculate a complete birth chart.
69
69
 
70
70
  ```bash
71
- thoth chart --date 1991-07-08 --time 14:06 --city "New York"
72
- thoth chart --date 1991-07-08 --time 14:06 --lat 40.7128 --lng -74.0060
73
- thoth chart --date 1991-07-08 --time 14:06 --city "New York" --svg-file chart.svg
71
+ thoth chart --date 1879-03-14 --time 11:30 --city "New York"
72
+ thoth chart --date 1879-03-14 --time 11:30 --lat 40.7128 --lng -74.0060
73
+ thoth chart --date 1879-03-14 --time 11:30 --city "New York" --svg-file chart.svg
74
74
  ```
75
75
 
76
76
  | Flag | Description | Required |
@@ -94,8 +94,8 @@ thoth chart --date 1991-07-08 --time 14:06 --city "New York" --svg-file chart.sv
94
94
  Calculate transits to a natal chart.
95
95
 
96
96
  ```bash
97
- thoth transit --natal-date 1991-07-08 --natal-time 14:06 --city "New York"
98
- thoth transit --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" --orb 1
97
+ thoth transit --natal-date 1879-03-14 --natal-time 11:30 --city "New York"
98
+ thoth transit --natal-date 1879-03-14 --natal-time 11:30 --city "NYC" --orb 1
99
99
  ```
100
100
 
101
101
  | Flag | Description | Required |
@@ -115,7 +115,7 @@ thoth transit --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" --orb 1
115
115
  Calculate solar return chart for a specific year.
116
116
 
117
117
  ```bash
118
- thoth solar-return --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" --year 2026
118
+ thoth solar-return --natal-date 1879-03-14 --natal-time 11:30 --city "NYC" --year 2026
119
119
  ```
120
120
 
121
121
  | Flag | Description | Required |
@@ -133,7 +133,7 @@ thoth solar-return --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" --yea
133
133
  Calculate next lunar return from a given date.
134
134
 
135
135
  ```bash
136
- thoth lunar-return --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" --from 2026-03-01
136
+ thoth lunar-return --natal-date 1879-03-14 --natal-time 11:30 --city "NYC" --from 2026-03-01
137
137
  ```
138
138
 
139
139
  | Flag | Description | Required |
@@ -151,7 +151,7 @@ thoth lunar-return --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" --fro
151
151
  Calculate synastry aspects between two charts.
152
152
 
153
153
  ```bash
154
- thoth synastry --date1 1991-07-08 --time1 14:06 --city1 "NYC" \
154
+ thoth synastry --date1 1879-03-14 --time1 11:30 --city1 "NYC" \
155
155
  --date2 1990-03-15 --time2 09:30 --city2 "LA"
156
156
  ```
157
157
 
@@ -171,7 +171,7 @@ thoth synastry --date1 1991-07-08 --time1 14:06 --city1 "NYC" \
171
171
  Calculate composite (midpoint) chart for a relationship.
172
172
 
173
173
  ```bash
174
- thoth composite --date1 1991-07-08 --time1 14:06 --city1 "NYC" \
174
+ thoth composite --date1 1879-03-14 --time1 11:30 --city1 "NYC" \
175
175
  --date2 1990-03-15 --time2 09:30 --city2 "LA"
176
176
  ```
177
177
 
@@ -184,7 +184,7 @@ Same options as synastry. Creates a merged chart representing the relationship i
184
184
  Calculate secondary progressions (day-for-a-year method).
185
185
 
186
186
  ```bash
187
- thoth progressions --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" \
187
+ thoth progressions --natal-date 1879-03-14 --natal-time 11:30 --city "NYC" \
188
188
  --target-date 2026-03-01
189
189
  ```
190
190
 
@@ -203,7 +203,7 @@ thoth progressions --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" \
203
203
  Calculate solar arc directions (all planets advance by Sun's arc).
204
204
 
205
205
  ```bash
206
- thoth solar-arc --natal-date 1991-07-08 --natal-time 14:06 --city "NYC" \
206
+ thoth solar-arc --natal-date 1879-03-14 --natal-time 11:30 --city "NYC" \
207
207
  --target-date 2026-03-01
208
208
  ```
209
209
 
@@ -320,7 +320,7 @@ thoth-cli uses **Sephirotic colors** based on Kabbalistic correspondences:
320
320
  All commands support `--json` for programmatic use:
321
321
 
322
322
  ```bash
323
- thoth chart --date 1991-07-08 --time 14:06 --city "NYC" --json
323
+ thoth chart --date 1879-03-14 --time 11:30 --city "NYC" --json
324
324
  thoth horary --question "Test" --city "NYC" --json
325
325
  ```
326
326
 
@@ -367,7 +367,7 @@ npm install
367
367
  npm run build
368
368
 
369
369
  # Test
370
- node dist/bin.js chart --date 1991-07-08 --time 14:06 --city "New York"
370
+ node dist/bin.js chart --date 1879-03-14 --time 11:30 --city "New York"
371
371
  ```
372
372
 
373
373
  ---
Binary file
package/dist/bin.js CHANGED
@@ -1,6 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ calculateGematria,
4
+ calculateNumerology,
5
+ calculatePersonalCycle,
3
6
  chart,
7
+ compareGematria,
4
8
  composite,
5
9
  ephemeris,
6
10
  ephemerisMulti,
@@ -10,10 +14,15 @@ import {
10
14
  formatEphemeris,
11
15
  formatEphemerisMulti,
12
16
  formatEphemerisRange,
17
+ formatGematria,
18
+ formatGematriaCompare,
19
+ formatGematriaLookup,
13
20
  formatHorary,
14
21
  formatLunarReturn,
15
22
  formatMoon,
16
23
  formatMoonExtended,
24
+ formatNumerology,
25
+ formatNumerologyYear,
17
26
  formatProgressions,
18
27
  formatScore,
19
28
  formatSolarArc,
@@ -27,6 +36,7 @@ import {
27
36
  formatTransits,
28
37
  horary,
29
38
  isError,
39
+ lookupGematria,
30
40
  lunarReturn,
31
41
  moon,
32
42
  moonExtended,
@@ -42,7 +52,7 @@ import {
42
52
  transit,
43
53
  transitScan,
44
54
  version
45
- } from "./chunk-X7IGMJUH.js";
55
+ } from "./chunk-7N4ZNTPI.js";
46
56
 
47
57
  // src/bin.ts
48
58
  import { Command } from "commander";
@@ -101,6 +111,20 @@ EPHEMERIS & MOON
101
111
  thoth ephemeris --body saturn --date 2027-01-15 # saturn on specific date
102
112
  thoth ephemeris-range --body pluto --from 2024-01-01 --to 2030-01-01 --step month
103
113
 
114
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
115
+ GEMATRIA
116
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
117
+ thoth gematria "AKLO" # all systems
118
+ thoth gematria "\u05D0\u05D4\u05D1\u05D4" # Hebrew text
119
+ thoth gematria "Love" --compare "Will" # compare two words
120
+ thoth gematria-lookup 93 # find words matching number
121
+
122
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
123
+ NUMEROLOGY
124
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
125
+ thoth numerology --name "John Doe" --date 1991-07-08 # full profile
126
+ thoth numerology-year --date 1991-07-08 # personal year cycles
127
+
104
128
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
105
129
  REFERENCE
106
130
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
@@ -859,7 +883,268 @@ program.command("tarot-spreads").alias("spreads").description("List available ta
859
883
  console.log(formatTarotSpreads(result));
860
884
  }
861
885
  });
886
+ program.command("gematria <text>").description("Calculate gematria values for text").option("-s, --system <system>", "Only show specific system (hebrew-standard, hebrew-ordinal, hebrew-reduced, greek, english-ordinal, english-reduced, english-sumerian, english-reverse)").option("-c, --compare <text2>", "Compare with another word/phrase").option("--json", "Output raw JSON").action(async (text, options) => {
887
+ if (options.compare) {
888
+ const result = compareGematria(text, options.compare, options.system);
889
+ if (options.json) {
890
+ console.log(JSON.stringify(result, null, 2));
891
+ } else {
892
+ console.log(formatGematriaCompare(result));
893
+ }
894
+ } else {
895
+ const result = calculateGematria(text, options.system);
896
+ if (options.json) {
897
+ console.log(JSON.stringify(result, null, 2));
898
+ } else {
899
+ console.log(formatGematria(result));
900
+ }
901
+ }
902
+ });
903
+ program.command("gematria-lookup <number>").description("Find words/concepts matching a gematria value").option("-s, --system <system>", "Filter by system (hebrew, greek, english)").option("-l, --limit <n>", "Maximum results", parseInt).option("--json", "Output raw JSON").action(async (number, options) => {
904
+ const num = parseInt(number, 10);
905
+ if (isNaN(num)) {
906
+ console.error(chalk.red("Error: Please provide a valid number"));
907
+ process.exit(1);
908
+ }
909
+ const result = lookupGematria(num, options.system, options.limit);
910
+ if (options.json) {
911
+ console.log(JSON.stringify(result, null, 2));
912
+ } else {
913
+ console.log(formatGematriaLookup(result));
914
+ }
915
+ });
916
+ program.command("numerology [input]").description("Calculate core numerology numbers").option("-d, --date <date>", "Birth date (YYYY-MM-DD)").option("-n, --name <name>", "Full birth name").option("--json", "Output raw JSON").action(async (input, options) => {
917
+ let name = options.name;
918
+ let date = options.date;
919
+ if (input) {
920
+ if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
921
+ date = date || input;
922
+ } else {
923
+ name = name || input;
924
+ }
925
+ }
926
+ if (!name && !date) {
927
+ console.error(chalk.red("Error: Provide --name and/or --date, or a positional argument"));
928
+ process.exit(1);
929
+ }
930
+ const result = calculateNumerology({ name, date });
931
+ if (options.json) {
932
+ console.log(JSON.stringify(result, null, 2));
933
+ } else {
934
+ console.log(formatNumerology(result));
935
+ }
936
+ });
937
+ program.command("numerology-year").description("Calculate Personal Year, Month, and Day numbers").requiredOption("-d, --date <date>", "Birth date (YYYY-MM-DD)").option("-t, --target-date <date>", "Target date (YYYY-MM-DD, default: today)").option("--json", "Output raw JSON").action(async (options) => {
938
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(options.date)) {
939
+ console.error(chalk.red("Error: Date must be in YYYY-MM-DD format"));
940
+ process.exit(1);
941
+ }
942
+ const result = calculatePersonalCycle(options.date, options.targetDate);
943
+ if (options.json) {
944
+ console.log(JSON.stringify(result, null, 2));
945
+ } else {
946
+ console.log(formatNumerologyYear(result));
947
+ }
948
+ });
949
+ var ASPECTS = [
950
+ { name: "conjunction", symbol: "\u260C", angle: 0, orb: 8 },
951
+ { name: "sextile", symbol: "\u26B9", angle: 60, orb: 4 },
952
+ { name: "square", symbol: "\u25A1", angle: 90, orb: 6 },
953
+ { name: "trine", symbol: "\u25B3", angle: 120, orb: 6 },
954
+ { name: "opposition", symbol: "\u260D", angle: 180, orb: 8 }
955
+ ];
956
+ function findAspect(pos1, pos2) {
957
+ const diff = Math.abs(pos1 - pos2);
958
+ const angle = diff > 180 ? 360 - diff : diff;
959
+ for (const aspect of ASPECTS) {
960
+ const orbDiff = Math.abs(angle - aspect.angle);
961
+ if (orbDiff <= aspect.orb) {
962
+ return { name: aspect.name, symbol: aspect.symbol, orb: Math.round(orbDiff * 10) / 10 };
963
+ }
964
+ }
965
+ return null;
966
+ }
967
+ function getMoonPhase(sunPos, moonPos) {
968
+ let angle = moonPos - sunPos;
969
+ if (angle < 0) angle += 360;
970
+ if (angle < 22.5 || angle >= 337.5) return { name: "New Moon", icon: "\u{1F311}" };
971
+ if (angle < 67.5) return { name: "Waxing Crescent", icon: "\u{1F312}" };
972
+ if (angle < 112.5) return { name: "First Quarter", icon: "\u{1F313}" };
973
+ if (angle < 157.5) return { name: "Waxing Gibbous", icon: "\u{1F314}" };
974
+ if (angle < 202.5) return { name: "Full Moon", icon: "\u{1F315}" };
975
+ if (angle < 247.5) return { name: "Waning Gibbous", icon: "\u{1F316}" };
976
+ if (angle < 292.5) return { name: "Last Quarter", icon: "\u{1F317}" };
977
+ return { name: "Waning Crescent", icon: "\u{1F318}" };
978
+ }
979
+ program.command("electional").description("Comprehensive electional astrology scan \u2014 moon phases, aspects, retrogrades, VOC").requiredOption("--start <date>", "Start date (YYYY-MM-DD)").requiredOption("--end <date>", "End date (YYYY-MM-DD)").option("--step <step>", "Granularity: day, week (default: day)", "day").option("--city <city>", "City for local context (optional)").option("--nation <nation>", "Country code (default: US)", "US").option("--lat <lat>", "Latitude (optional)", parseFloat).option("--lng <lng>", "Longitude (optional)", parseFloat).option("--json", "Output raw JSON").action(async (options) => {
980
+ const spinner = ora("Scanning date range (one efficient query)...").start();
981
+ const startDate = new Date(options.start);
982
+ const endDate = new Date(options.end);
983
+ if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) {
984
+ spinner.stop();
985
+ console.error(chalk.red("Error: Invalid date format. Use YYYY-MM-DD"));
986
+ process.exit(1);
987
+ }
988
+ if (endDate <= startDate) {
989
+ spinner.stop();
990
+ console.error(chalk.red("Error: End date must be after start date"));
991
+ process.exit(1);
992
+ }
993
+ const [startYear, startMonth, startDay] = options.start.split("-").map(Number);
994
+ const [endYear, endMonth, endDay] = options.end.split("-").map(Number);
995
+ const location = options.city ? `${options.city}, ${options.nation}` : options.lat && options.lng ? `${options.lat}\xB0, ${options.lng}\xB0` : null;
996
+ const multiResult = await ephemerisMulti({
997
+ bodies: "sun,moon,mercury,venus,mars,jupiter,saturn,uranus,neptune,pluto",
998
+ startYear,
999
+ startMonth,
1000
+ startDay,
1001
+ endYear,
1002
+ endMonth,
1003
+ endDay,
1004
+ step: options.step,
1005
+ lat: options.lat,
1006
+ lng: options.lng
1007
+ });
1008
+ spinner.stop();
1009
+ if (isError(multiResult)) {
1010
+ console.error(chalk.red(`Error: ${multiResult.error}`));
1011
+ process.exit(1);
1012
+ }
1013
+ const positions = multiResult.positions || [];
1014
+ const days = Math.ceil((endDate.getTime() - startDate.getTime()) / (1e3 * 60 * 60 * 24));
1015
+ const dailyData = [];
1016
+ const keyDates = [];
1017
+ const retrogradeWindows = {};
1018
+ let prevMoonSign = "";
1019
+ for (const pos of positions) {
1020
+ const dateStr = pos.datetime.split("T")[0];
1021
+ const sun = pos.sun;
1022
+ const moonData = pos.moon;
1023
+ const phase = getMoonPhase(sun.abs_position, moonData.abs_position);
1024
+ const sunMoonAngle = Math.abs(moonData.abs_position - sun.abs_position);
1025
+ const normalizedAngle = sunMoonAngle > 180 ? 360 - sunMoonAngle : sunMoonAngle;
1026
+ if (normalizedAngle < 5) {
1027
+ keyDates.push({ date: dateStr, event: `New Moon in ${moonData.sign}`, quality: "beginnings" });
1028
+ } else if (Math.abs(normalizedAngle - 180) < 5) {
1029
+ keyDates.push({ date: dateStr, event: `Full Moon in ${moonData.sign}`, quality: "culmination" });
1030
+ }
1031
+ if (prevMoonSign && prevMoonSign !== moonData.sign) {
1032
+ keyDates.push({ date: dateStr, event: `Moon enters ${moonData.sign}`, quality: "ingress" });
1033
+ }
1034
+ prevMoonSign = moonData.sign;
1035
+ const planets = {};
1036
+ const planetNames = ["mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
1037
+ for (const pName of planetNames) {
1038
+ const p = pos[pName];
1039
+ if (p) {
1040
+ planets[pName] = { sign: p.sign, degree: p.position, retrograde: p.retrograde, absPos: p.abs_position };
1041
+ if (p.retrograde) {
1042
+ if (!retrogradeWindows[pName]) {
1043
+ retrogradeWindows[pName] = { start: dateStr, sign: p.sign };
1044
+ }
1045
+ retrogradeWindows[pName].end = dateStr;
1046
+ }
1047
+ }
1048
+ }
1049
+ const aspects = [];
1050
+ const allBodies = [{ name: "moon", pos: moonData.abs_position }, ...Object.entries(planets).map(([n, d]) => ({ name: n, pos: d.absPos }))];
1051
+ for (let i = 0; i < allBodies.length; i++) {
1052
+ for (let j = i + 1; j < allBodies.length; j++) {
1053
+ const asp = findAspect(allBodies[i].pos, allBodies[j].pos);
1054
+ if (asp) {
1055
+ aspects.push({
1056
+ planet1: allBodies[i].name,
1057
+ planet2: allBodies[j].name,
1058
+ aspect: asp.name,
1059
+ symbol: asp.symbol,
1060
+ orb: asp.orb
1061
+ });
1062
+ }
1063
+ }
1064
+ }
1065
+ const moonAspects = aspects.filter((a) => a.planet1 === "moon" || a.planet2 === "moon");
1066
+ const tightMoonAspects = moonAspects.filter((a) => a.orb < 3);
1067
+ const vocMoon = tightMoonAspects.length === 0;
1068
+ let score2 = 0;
1069
+ for (const asp of aspects) {
1070
+ if (asp.aspect === "trine" || asp.aspect === "sextile") score2 += 1;
1071
+ if (asp.aspect === "square") score2 -= 1;
1072
+ if (asp.aspect === "opposition") score2 -= 0.5;
1073
+ if ((asp.planet1 === "jupiter" || asp.planet2 === "jupiter") && (asp.aspect === "trine" || asp.aspect === "sextile")) score2 += 1;
1074
+ if ((asp.planet1 === "venus" || asp.planet2 === "venus") && (asp.aspect === "trine" || asp.aspect === "sextile")) score2 += 0.5;
1075
+ }
1076
+ if (planets.mercury?.retrograde) score2 -= 2;
1077
+ const quality = score2 >= 4 ? "excellent" : score2 >= 2 ? "good" : score2 >= 0 ? "neutral" : score2 >= -2 ? "challenging" : "difficult";
1078
+ dailyData.push({
1079
+ date: dateStr,
1080
+ moon: { sign: moonData.sign, degree: moonData.position, phase: phase.name, phaseIcon: phase.icon },
1081
+ planets: Object.fromEntries(Object.entries(planets).map(([k, v]) => [k, { sign: v.sign, degree: v.degree, retrograde: v.retrograde }])),
1082
+ aspects,
1083
+ vocMoon,
1084
+ quality
1085
+ });
1086
+ }
1087
+ const output = {
1088
+ range: { start: options.start, end: options.end, days, step: options.step, location },
1089
+ dailyData,
1090
+ keyDates,
1091
+ retrogradeWindows,
1092
+ summary: {
1093
+ excellentDays: dailyData.filter((d) => d.quality === "excellent").map((d) => d.date),
1094
+ goodDays: dailyData.filter((d) => d.quality === "good").map((d) => d.date),
1095
+ avoidDays: dailyData.filter((d) => d.quality === "difficult").map((d) => d.date),
1096
+ vocMoonDays: dailyData.filter((d) => d.vocMoon).map((d) => d.date)
1097
+ }
1098
+ };
1099
+ if (options.json) {
1100
+ console.log(JSON.stringify(output, null, 2));
1101
+ } else {
1102
+ console.log(chalk.yellow("\n\u{1315D}") + chalk.dim(" Electional Astrology Scan"));
1103
+ console.log(chalk.dim(` ${output.range.start} to ${output.range.end} (${output.range.days} days)
1104
+ `));
1105
+ console.log(chalk.dim("\u2500\u2500 SUMMARY \u2500\u2500"));
1106
+ if (output.summary.excellentDays.length > 0) {
1107
+ console.log(chalk.green(` \u2605 Excellent: ${output.summary.excellentDays.join(", ")}`));
1108
+ }
1109
+ if (output.summary.goodDays.length > 0) {
1110
+ console.log(chalk.cyan(` \u2713 Good: ${output.summary.goodDays.join(", ")}`));
1111
+ }
1112
+ if (output.summary.avoidDays.length > 0) {
1113
+ console.log(chalk.red(` \u2717 Avoid: ${output.summary.avoidDays.join(", ")}`));
1114
+ }
1115
+ if (output.summary.vocMoonDays.length > 0) {
1116
+ console.log(chalk.yellow(` \u25EF VOC Moon: ${output.summary.vocMoonDays.join(", ")}`));
1117
+ }
1118
+ const rxPlanets = Object.entries(output.retrogradeWindows);
1119
+ if (rxPlanets.length > 0) {
1120
+ console.log(chalk.dim("\n\u2500\u2500 RETROGRADES \u2500\u2500"));
1121
+ for (const [planet, window] of rxPlanets) {
1122
+ console.log(chalk.red(` \u211E ${planet.charAt(0).toUpperCase() + planet.slice(1)} in ${window.sign} (${window.start} \u2013 ${window.end})`));
1123
+ }
1124
+ }
1125
+ if (output.keyDates.length > 0) {
1126
+ console.log(chalk.dim("\n\u2500\u2500 KEY DATES \u2500\u2500"));
1127
+ for (const kd of output.keyDates) {
1128
+ const icon = kd.quality === "beginnings" ? "\u{1F311}" : kd.quality === "culmination" ? "\u{1F315}" : kd.quality === "ingress" ? "\u2192" : "\u2728";
1129
+ console.log(` ${kd.date} ${icon} ${kd.event}`);
1130
+ }
1131
+ }
1132
+ console.log(chalk.dim("\n\u2500\u2500 DAILY POSITIONS \u2500\u2500"));
1133
+ for (const day of dailyData) {
1134
+ const qColor = day.quality === "excellent" ? chalk.green : day.quality === "good" ? chalk.cyan : day.quality === "challenging" ? chalk.yellow : day.quality === "difficult" ? chalk.red : chalk.white;
1135
+ const qIcon = day.quality === "excellent" ? "\u2605" : day.quality === "good" ? "\u2713" : day.quality === "challenging" ? "!" : day.quality === "difficult" ? "\u2717" : "\xB7";
1136
+ const vocFlag = day.vocMoon ? chalk.yellow(" [VOC]") : "";
1137
+ console.log(qColor(` ${day.date} ${qIcon} ${day.moon.phaseIcon} Moon ${day.moon.sign} ${day.moon.degree.toFixed(0)}\xB0${vocFlag}`));
1138
+ const tightAspects = day.aspects.filter((a) => a.orb < 2);
1139
+ if (tightAspects.length > 0) {
1140
+ const aspStr = tightAspects.map((a) => `${a.planet1}${a.symbol}${a.planet2}`).join(" ");
1141
+ console.log(chalk.dim(` ${aspStr}`));
1142
+ }
1143
+ }
1144
+ console.log("");
1145
+ }
1146
+ });
862
1147
  console.log(chalk.dim(""));
863
- console.log(chalk.yellow(" \u{1315D}") + chalk.dim(" thoth-cli v0.2.23"));
1148
+ console.log(chalk.yellow(" \u{1315D}") + chalk.dim(" thoth-cli v0.2.27"));
864
1149
  console.log(chalk.dim(""));
865
1150
  program.parse();