thoth-cli 0.1.1 → 0.2.0

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.
@@ -0,0 +1,1211 @@
1
+ // src/lib/core.ts
2
+ import { execa } from "execa";
3
+
4
+ // src/lib/binary.ts
5
+ import { platform, arch } from "os";
6
+ import { join, dirname } from "path";
7
+ import { existsSync } from "fs";
8
+ import { fileURLToPath } from "url";
9
+ var __dirname = dirname(fileURLToPath(import.meta.url));
10
+ function getPlatformKey() {
11
+ const p = platform();
12
+ const a = arch();
13
+ const platformMap = {
14
+ "darwin": "darwin",
15
+ "linux": "linux",
16
+ "win32": "win32"
17
+ };
18
+ const archMap = {
19
+ "x64": "x64",
20
+ "arm64": "arm64"
21
+ };
22
+ const mappedPlatform = platformMap[p];
23
+ const mappedArch = archMap[a];
24
+ if (!mappedPlatform || !mappedArch) {
25
+ throw new Error(`Unsupported platform: ${p}-${a}`);
26
+ }
27
+ return `${mappedPlatform}-${mappedArch}`;
28
+ }
29
+ function getBinaryName() {
30
+ const p = platform();
31
+ return p === "win32" ? "thoth-core.exe" : "thoth-core";
32
+ }
33
+ function getBinaryPath() {
34
+ const platformKey = getPlatformKey();
35
+ const binaryName = getBinaryName();
36
+ const possiblePaths = [
37
+ // Installed via npm (production)
38
+ join(__dirname, "..", "..", "bin", platformKey, binaryName),
39
+ // Development (monorepo)
40
+ join(__dirname, "..", "..", "..", "..", "bin", platformKey, binaryName),
41
+ // Local development
42
+ join(__dirname, "..", "..", "bin", binaryName)
43
+ ];
44
+ for (const p of possiblePaths) {
45
+ if (existsSync(p)) {
46
+ return p;
47
+ }
48
+ }
49
+ return "python";
50
+ }
51
+ function getCommand() {
52
+ const binaryPath = getBinaryPath();
53
+ if (binaryPath === "python") {
54
+ return {
55
+ command: "thoth-core",
56
+ args: []
57
+ };
58
+ }
59
+ return {
60
+ command: binaryPath,
61
+ args: []
62
+ };
63
+ }
64
+
65
+ // src/lib/core.ts
66
+ async function execute(subcommand, args) {
67
+ const { command, args: baseArgs } = getCommand();
68
+ try {
69
+ const result = await execa(command, [...baseArgs, subcommand, ...args], {
70
+ encoding: "utf8",
71
+ reject: false
72
+ });
73
+ if (result.exitCode !== 0) {
74
+ const error = result.stderr || result.stdout;
75
+ try {
76
+ return JSON.parse(error);
77
+ } catch {
78
+ return { error: error || "Unknown error" };
79
+ }
80
+ }
81
+ return JSON.parse(result.stdout);
82
+ } catch (err) {
83
+ return { error: err instanceof Error ? err.message : "Unknown error" };
84
+ }
85
+ }
86
+ async function chart(options) {
87
+ const args = [
88
+ "--year",
89
+ String(options.year),
90
+ "--month",
91
+ String(options.month),
92
+ "--day",
93
+ String(options.day),
94
+ "--hour",
95
+ String(options.hour ?? 12),
96
+ "--minute",
97
+ String(options.minute ?? 0),
98
+ "--name",
99
+ options.name ?? "Subject"
100
+ ];
101
+ if (options.city) {
102
+ args.push("--city", options.city);
103
+ args.push("--nation", options.nation ?? "US");
104
+ } else if (options.lat !== void 0 && options.lng !== void 0) {
105
+ args.push("--lat", String(options.lat));
106
+ args.push("--lng", String(options.lng));
107
+ }
108
+ if (options.svg) {
109
+ args.push("--svg");
110
+ }
111
+ return execute("chart", args);
112
+ }
113
+ async function transit(options) {
114
+ const args = [
115
+ "--natal-year",
116
+ String(options.natalYear),
117
+ "--natal-month",
118
+ String(options.natalMonth),
119
+ "--natal-day",
120
+ String(options.natalDay),
121
+ "--natal-hour",
122
+ String(options.natalHour ?? 12),
123
+ "--natal-minute",
124
+ String(options.natalMinute ?? 0),
125
+ "--orb",
126
+ String(options.orb ?? 3)
127
+ ];
128
+ if (options.natalCity) {
129
+ args.push("--natal-city", options.natalCity);
130
+ args.push("--nation", options.nation ?? "US");
131
+ } else if (options.natalLat !== void 0 && options.natalLng !== void 0) {
132
+ args.push("--natal-lat", String(options.natalLat));
133
+ args.push("--natal-lng", String(options.natalLng));
134
+ }
135
+ if (options.transitYear) args.push("--transit-year", String(options.transitYear));
136
+ if (options.transitMonth) args.push("--transit-month", String(options.transitMonth));
137
+ if (options.transitDay) args.push("--transit-day", String(options.transitDay));
138
+ if (options.svg) args.push("--svg");
139
+ return execute("transit", args);
140
+ }
141
+ async function solarReturn(options) {
142
+ const args = [
143
+ "--natal-year",
144
+ String(options.natalYear),
145
+ "--natal-month",
146
+ String(options.natalMonth),
147
+ "--natal-day",
148
+ String(options.natalDay),
149
+ "--natal-hour",
150
+ String(options.natalHour ?? 12),
151
+ "--natal-minute",
152
+ String(options.natalMinute ?? 0),
153
+ "--return-year",
154
+ String(options.returnYear)
155
+ ];
156
+ if (options.natalCity) {
157
+ args.push("--natal-city", options.natalCity);
158
+ args.push("--nation", options.nation ?? "US");
159
+ } else if (options.natalLat !== void 0 && options.natalLng !== void 0) {
160
+ args.push("--natal-lat", String(options.natalLat));
161
+ args.push("--natal-lng", String(options.natalLng));
162
+ }
163
+ if (options.returnCity) args.push("--return-city", options.returnCity);
164
+ if (options.svg) args.push("--svg");
165
+ return execute("solar-return", args);
166
+ }
167
+ async function lunarReturn(options) {
168
+ const args = [
169
+ "--natal-year",
170
+ String(options.natalYear),
171
+ "--natal-month",
172
+ String(options.natalMonth),
173
+ "--natal-day",
174
+ String(options.natalDay),
175
+ "--natal-hour",
176
+ String(options.natalHour ?? 12),
177
+ "--natal-minute",
178
+ String(options.natalMinute ?? 0),
179
+ "--from-year",
180
+ String(options.fromYear),
181
+ "--from-month",
182
+ String(options.fromMonth),
183
+ "--from-day",
184
+ String(options.fromDay ?? 1)
185
+ ];
186
+ if (options.natalCity) {
187
+ args.push("--natal-city", options.natalCity);
188
+ args.push("--nation", options.nation ?? "US");
189
+ } else if (options.natalLat !== void 0 && options.natalLng !== void 0) {
190
+ args.push("--natal-lat", String(options.natalLat));
191
+ args.push("--natal-lng", String(options.natalLng));
192
+ }
193
+ if (options.returnCity) args.push("--return-city", options.returnCity);
194
+ if (options.svg) args.push("--svg");
195
+ return execute("lunar-return", args);
196
+ }
197
+ async function synastry(options) {
198
+ const args = [
199
+ "--year1",
200
+ String(options.year1),
201
+ "--month1",
202
+ String(options.month1),
203
+ "--day1",
204
+ String(options.day1),
205
+ "--hour1",
206
+ String(options.hour1 ?? 12),
207
+ "--minute1",
208
+ String(options.minute1 ?? 0),
209
+ "--name1",
210
+ options.name1 ?? "Person 1",
211
+ "--year2",
212
+ String(options.year2),
213
+ "--month2",
214
+ String(options.month2),
215
+ "--day2",
216
+ String(options.day2),
217
+ "--hour2",
218
+ String(options.hour2 ?? 12),
219
+ "--minute2",
220
+ String(options.minute2 ?? 0),
221
+ "--name2",
222
+ options.name2 ?? "Person 2",
223
+ "--orb",
224
+ String(options.orb ?? 6)
225
+ ];
226
+ if (options.city1) {
227
+ args.push("--city1", options.city1);
228
+ args.push("--nation1", options.nation1 ?? "US");
229
+ } else if (options.lat1 !== void 0 && options.lng1 !== void 0) {
230
+ args.push("--lat1", String(options.lat1));
231
+ args.push("--lng1", String(options.lng1));
232
+ }
233
+ if (options.city2) {
234
+ args.push("--city2", options.city2);
235
+ args.push("--nation2", options.nation2 ?? "US");
236
+ } else if (options.lat2 !== void 0 && options.lng2 !== void 0) {
237
+ args.push("--lat2", String(options.lat2));
238
+ args.push("--lng2", String(options.lng2));
239
+ }
240
+ if (options.svg) args.push("--svg");
241
+ return execute("synastry", args);
242
+ }
243
+ async function progressions(options) {
244
+ const args = [
245
+ "--natal-year",
246
+ String(options.natalYear),
247
+ "--natal-month",
248
+ String(options.natalMonth),
249
+ "--natal-day",
250
+ String(options.natalDay),
251
+ "--natal-hour",
252
+ String(options.natalHour ?? 12),
253
+ "--natal-minute",
254
+ String(options.natalMinute ?? 0),
255
+ "--target-year",
256
+ String(options.targetYear),
257
+ "--target-month",
258
+ String(options.targetMonth ?? 1),
259
+ "--target-day",
260
+ String(options.targetDay ?? 1)
261
+ ];
262
+ if (options.natalCity) {
263
+ args.push("--natal-city", options.natalCity);
264
+ args.push("--nation", options.nation ?? "US");
265
+ } else if (options.natalLat !== void 0 && options.natalLng !== void 0) {
266
+ args.push("--natal-lat", String(options.natalLat));
267
+ args.push("--natal-lng", String(options.natalLng));
268
+ }
269
+ if (options.svg) args.push("--svg");
270
+ return execute("progressions", args);
271
+ }
272
+ async function ephemerisRange(options) {
273
+ const args = [
274
+ "--body",
275
+ options.body,
276
+ "--start-year",
277
+ String(options.startYear),
278
+ "--start-month",
279
+ String(options.startMonth ?? 1),
280
+ "--start-day",
281
+ String(options.startDay ?? 1),
282
+ "--end-year",
283
+ String(options.endYear),
284
+ "--end-month",
285
+ String(options.endMonth ?? 12),
286
+ "--end-day",
287
+ String(options.endDay ?? 28),
288
+ "--step",
289
+ options.step ?? "month"
290
+ ];
291
+ return execute("ephemeris-range", args);
292
+ }
293
+ async function moon(options = {}) {
294
+ const args = [];
295
+ if (options.year) args.push("--year", String(options.year));
296
+ if (options.month) args.push("--month", String(options.month));
297
+ if (options.day) args.push("--day", String(options.day));
298
+ if (options.lat) args.push("--lat", String(options.lat));
299
+ if (options.lng) args.push("--lng", String(options.lng));
300
+ return execute("moon", args);
301
+ }
302
+ async function ephemeris(options) {
303
+ const args = ["--body", options.body];
304
+ if (options.year) args.push("--year", String(options.year));
305
+ if (options.month) args.push("--month", String(options.month));
306
+ if (options.day) args.push("--day", String(options.day));
307
+ return execute("ephemeris", args);
308
+ }
309
+ async function composite(options) {
310
+ const args = [
311
+ "--year1",
312
+ String(options.year1),
313
+ "--month1",
314
+ String(options.month1),
315
+ "--day1",
316
+ String(options.day1),
317
+ "--hour1",
318
+ String(options.hour1 ?? 12),
319
+ "--minute1",
320
+ String(options.minute1 ?? 0),
321
+ "--name1",
322
+ options.name1 ?? "Person 1",
323
+ "--year2",
324
+ String(options.year2),
325
+ "--month2",
326
+ String(options.month2),
327
+ "--day2",
328
+ String(options.day2),
329
+ "--hour2",
330
+ String(options.hour2 ?? 12),
331
+ "--minute2",
332
+ String(options.minute2 ?? 0),
333
+ "--name2",
334
+ options.name2 ?? "Person 2"
335
+ ];
336
+ if (options.city1) {
337
+ args.push("--city1", options.city1);
338
+ args.push("--nation1", options.nation1 ?? "US");
339
+ } else if (options.lat1 !== void 0 && options.lng1 !== void 0) {
340
+ args.push("--lat1", String(options.lat1));
341
+ args.push("--lng1", String(options.lng1));
342
+ }
343
+ if (options.city2) {
344
+ args.push("--city2", options.city2);
345
+ args.push("--nation2", options.nation2 ?? "US");
346
+ } else if (options.lat2 !== void 0 && options.lng2 !== void 0) {
347
+ args.push("--lat2", String(options.lat2));
348
+ args.push("--lng2", String(options.lng2));
349
+ }
350
+ if (options.svg) args.push("--svg");
351
+ return execute("composite", args);
352
+ }
353
+ async function solarArc(options) {
354
+ const args = [
355
+ "--natal-year",
356
+ String(options.natalYear),
357
+ "--natal-month",
358
+ String(options.natalMonth),
359
+ "--natal-day",
360
+ String(options.natalDay),
361
+ "--natal-hour",
362
+ String(options.natalHour ?? 12),
363
+ "--natal-minute",
364
+ String(options.natalMinute ?? 0),
365
+ "--target-year",
366
+ String(options.targetYear),
367
+ "--target-month",
368
+ String(options.targetMonth ?? 1),
369
+ "--target-day",
370
+ String(options.targetDay ?? 1)
371
+ ];
372
+ if (options.natalCity) {
373
+ args.push("--natal-city", options.natalCity);
374
+ args.push("--nation", options.nation ?? "US");
375
+ } else if (options.natalLat !== void 0 && options.natalLng !== void 0) {
376
+ args.push("--natal-lat", String(options.natalLat));
377
+ args.push("--natal-lng", String(options.natalLng));
378
+ }
379
+ return execute("solar-arc", args);
380
+ }
381
+ async function horary(options) {
382
+ const args = [
383
+ "--question",
384
+ options.question
385
+ ];
386
+ if (options.year) args.push("--year", String(options.year));
387
+ if (options.month) args.push("--month", String(options.month));
388
+ if (options.day) args.push("--day", String(options.day));
389
+ if (options.hour !== void 0) args.push("--hour", String(options.hour));
390
+ if (options.minute !== void 0) args.push("--minute", String(options.minute));
391
+ if (options.city) {
392
+ args.push("--city", options.city);
393
+ args.push("--nation", options.nation ?? "US");
394
+ } else if (options.lat !== void 0 && options.lng !== void 0) {
395
+ args.push("--lat", String(options.lat));
396
+ args.push("--lng", String(options.lng));
397
+ }
398
+ return execute("horary", args);
399
+ }
400
+ async function version() {
401
+ return execute("version", []);
402
+ }
403
+
404
+ // src/lib/format.ts
405
+ import chalk from "chalk";
406
+ var COLORS = {
407
+ // Planets (Sephirotic correspondences)
408
+ sun: chalk.hex("#FFD700"),
409
+ // Gold - Tiphareth
410
+ moon: chalk.hex("#C0C0C0"),
411
+ // Silver - Yesod
412
+ mercury: chalk.hex("#FF8C00"),
413
+ // Orange - Hod
414
+ venus: chalk.hex("#00FF7F"),
415
+ // Green - Netzach
416
+ mars: chalk.hex("#FF0000"),
417
+ // Red - Geburah
418
+ jupiter: chalk.hex("#4169E1"),
419
+ // Royal Blue - Chesed
420
+ saturn: chalk.hex("#4B0082"),
421
+ // Indigo - Binah
422
+ uranus: chalk.hex("#00FFFF"),
423
+ // Electric Cyan - Chokmah
424
+ neptune: chalk.hex("#20B2AA"),
425
+ // Sea Green
426
+ pluto: chalk.hex("#8B0000"),
427
+ // Dark Red
428
+ chiron: chalk.hex("#9932CC"),
429
+ // Purple - wounded healer
430
+ lilith: chalk.hex("#800020"),
431
+ // Burgundy - primal
432
+ northNode: chalk.hex("#FFD700"),
433
+ // Gold
434
+ southNode: chalk.hex("#C0C0C0")
435
+ // Silver
436
+ };
437
+ function getPlanetColor(name) {
438
+ const colorMap = {
439
+ "sun": COLORS.sun,
440
+ "moon": COLORS.moon,
441
+ "mercury": COLORS.mercury,
442
+ "venus": COLORS.venus,
443
+ "mars": COLORS.mars,
444
+ "jupiter": COLORS.jupiter,
445
+ "saturn": COLORS.saturn,
446
+ "uranus": COLORS.uranus,
447
+ "neptune": COLORS.neptune,
448
+ "pluto": COLORS.pluto,
449
+ "chiron": COLORS.chiron,
450
+ "mean_lilith": COLORS.lilith,
451
+ "lilith": COLORS.lilith,
452
+ "true_north_lunar_node": COLORS.northNode,
453
+ "true_south_lunar_node": COLORS.southNode,
454
+ "nn": COLORS.northNode,
455
+ "sn": COLORS.southNode
456
+ };
457
+ return colorMap[name.toLowerCase()] || chalk.white;
458
+ }
459
+ function getZodiacColor(sign) {
460
+ const colorMap = {
461
+ "Ari": COLORS.mars,
462
+ "Tau": COLORS.venus,
463
+ "Gem": COLORS.mercury,
464
+ "Can": COLORS.moon,
465
+ "Leo": COLORS.sun,
466
+ "Vir": COLORS.mercury,
467
+ "Lib": COLORS.venus,
468
+ "Sco": COLORS.pluto,
469
+ "Sag": COLORS.jupiter,
470
+ "Cap": COLORS.saturn,
471
+ "Aqu": COLORS.uranus,
472
+ "Pis": COLORS.neptune
473
+ };
474
+ return colorMap[sign] || chalk.white;
475
+ }
476
+ function getAspectColor(aspect) {
477
+ const colorMap = {
478
+ "conjunction": COLORS.sun,
479
+ // Tiphareth
480
+ "opposition": COLORS.moon,
481
+ // Yesod
482
+ "trine": COLORS.jupiter,
483
+ // Chesed
484
+ "square": COLORS.mars,
485
+ // Geburah
486
+ "sextile": COLORS.venus,
487
+ // Netzach
488
+ "quintile": COLORS.mercury,
489
+ // Hod
490
+ "quincunx": COLORS.uranus
491
+ };
492
+ return colorMap[aspect] || chalk.white;
493
+ }
494
+ var ZODIAC_SYMBOLS = {
495
+ "Ari": "\u2648",
496
+ "Tau": "\u2649",
497
+ "Gem": "\u264A",
498
+ "Can": "\u264B",
499
+ "Leo": "\u264C",
500
+ "Vir": "\u264D",
501
+ "Lib": "\u264E",
502
+ "Sco": "\u264F",
503
+ "Sag": "\u2650",
504
+ "Cap": "\u2651",
505
+ "Aqu": "\u2652",
506
+ "Pis": "\u2653"
507
+ };
508
+ var PLANET_SYMBOLS = {
509
+ "sun": "\u2609",
510
+ "moon": "\u263D",
511
+ "mercury": "\u263F",
512
+ "venus": "\u2640",
513
+ "mars": "\u2642",
514
+ "jupiter": "\u2643",
515
+ "saturn": "\u2644",
516
+ "uranus": "\u2645",
517
+ "neptune": "\u2646",
518
+ "pluto": "\u2647",
519
+ "chiron": "\u26B7",
520
+ "north_node": "\u260A",
521
+ "south_node": "\u260B",
522
+ "true_north_lunar_node": "\u260A",
523
+ "true_south_lunar_node": "\u260B",
524
+ "mean_lilith": "\u26B8",
525
+ "medium_coeli": "MC",
526
+ "imum_coeli": "IC",
527
+ "ascendant": "ASC",
528
+ "descendant": "DSC",
529
+ // Short names from transit aspects
530
+ "nn": "\u260A",
531
+ "sn": "\u260B",
532
+ "lilith": "\u26B8",
533
+ "mc": "MC",
534
+ "ic": "IC",
535
+ "asc": "ASC",
536
+ "dsc": "DSC"
537
+ };
538
+ var ASPECT_SYMBOLS = {
539
+ "conjunction": "\u260C",
540
+ "opposition": "\u260D",
541
+ "trine": "\u25B3",
542
+ "square": "\u25A1",
543
+ "sextile": "\u26B9",
544
+ "quintile": "\u235F",
545
+ "quincunx": "\u26BB",
546
+ "semi-sextile": "\u26BA",
547
+ "semi-square": "\u2220",
548
+ "sesquiquadrate": "\u26BC"
549
+ };
550
+ function getZodiacSymbol(sign) {
551
+ const key = sign.slice(0, 3);
552
+ return ZODIAC_SYMBOLS[key] || sign;
553
+ }
554
+ function getPlanetSymbol(planet) {
555
+ return PLANET_SYMBOLS[planet.toLowerCase()] || planet;
556
+ }
557
+ function getAspectSymbol(aspect) {
558
+ return ASPECT_SYMBOLS[aspect.toLowerCase()] || aspect;
559
+ }
560
+ function formatDegrees(degrees) {
561
+ const d = Math.floor(degrees);
562
+ const m = Math.floor((degrees - d) * 60);
563
+ return `${d}\xB0${m.toString().padStart(2, "0")}'`;
564
+ }
565
+ function formatChart(result) {
566
+ const lines = [];
567
+ lines.push(chalk.bold.white(`
568
+ \u{1315D} Natal Chart: ${result.name}`));
569
+ const dateStr = `${result.datetime.year}-${String(result.datetime.month).padStart(2, "0")}-${String(result.datetime.day).padStart(2, "0")} ${String(result.datetime.hour).padStart(2, "0")}:${String(result.datetime.minute).padStart(2, "0")}`;
570
+ lines.push(chalk.dim(` ${dateStr}`));
571
+ if (result.location.city) {
572
+ lines.push(chalk.dim(` ${result.location.city}`));
573
+ } else {
574
+ lines.push(chalk.dim(` Lat: ${result.location.lat}, Lng: ${result.location.lng}`));
575
+ }
576
+ if (result.lunar_phase) {
577
+ lines.push(chalk.dim(` Born during: ${result.lunar_phase.emoji} ${result.lunar_phase.name}`));
578
+ }
579
+ lines.push("");
580
+ lines.push(chalk.bold.cyan("\u2500\u2500 ANGLES \u2500\u2500"));
581
+ if (result.ascendant.sign) {
582
+ const zColor = getZodiacColor(result.ascendant.sign);
583
+ lines.push(` ${COLORS.mars("ASC")} ${zColor(getZodiacSymbol(result.ascendant.sign) + " " + result.ascendant.sign)} ${chalk.white(formatDegrees(result.ascendant.position || 0))}`);
584
+ }
585
+ if (result.midheaven.sign) {
586
+ const zColor = getZodiacColor(result.midheaven.sign);
587
+ lines.push(` ${COLORS.sun("MC")} ${zColor(getZodiacSymbol(result.midheaven.sign) + " " + result.midheaven.sign)} ${chalk.white(formatDegrees(result.midheaven.position || 0))}`);
588
+ }
589
+ lines.push("");
590
+ lines.push(chalk.bold.cyan("\u2500\u2500 PLANETS \u2500\u2500"));
591
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
592
+ for (const name of planetOrder) {
593
+ const planet = result.planets[name];
594
+ if (planet) {
595
+ const pColor = getPlanetColor(name);
596
+ const zColor = getZodiacColor(planet.sign);
597
+ const symbol = getPlanetSymbol(name);
598
+ const zodiac = getZodiacSymbol(planet.sign);
599
+ const deg = formatDegrees(planet.position);
600
+ const rx = planet.retrograde ? COLORS.mars(" \u211E") : "";
601
+ const house = planet.house ? chalk.dim(` (${planet.house})`) : "";
602
+ lines.push(` ${pColor(symbol)} ${name.padEnd(10)} ${zColor(zodiac + " " + planet.sign)} ${chalk.white(deg)}${rx}${house}`);
603
+ }
604
+ }
605
+ lines.push("");
606
+ lines.push(chalk.bold.cyan("\u2500\u2500 POINTS \u2500\u2500"));
607
+ const pointOrder = ["chiron", "mean_lilith", "true_north_lunar_node", "true_south_lunar_node"];
608
+ const pointNames = {
609
+ "chiron": "Chiron",
610
+ "mean_lilith": "Lilith",
611
+ "true_north_lunar_node": "North Node",
612
+ "true_south_lunar_node": "South Node"
613
+ };
614
+ for (const name of pointOrder) {
615
+ const planet = result.planets[name];
616
+ if (planet) {
617
+ const pColor = getPlanetColor(name);
618
+ const zColor = getZodiacColor(planet.sign);
619
+ const symbol = getPlanetSymbol(name);
620
+ const displayName = pointNames[name] || name;
621
+ const zodiac = getZodiacSymbol(planet.sign);
622
+ const deg = formatDegrees(planet.position);
623
+ const house = planet.house ? chalk.dim(` (${planet.house})`) : "";
624
+ lines.push(` ${pColor(symbol)} ${displayName.padEnd(10)} ${zColor(zodiac + " " + planet.sign)} ${chalk.white(deg)}${house}`);
625
+ }
626
+ }
627
+ lines.push("");
628
+ if (result.houses && Object.keys(result.houses).length > 0) {
629
+ lines.push(chalk.bold.cyan("\u2500\u2500 HOUSES \u2500\u2500"));
630
+ for (let i = 1; i <= 12; i++) {
631
+ const house = result.houses[String(i)];
632
+ if (house && house.sign) {
633
+ const zodiac = getZodiacSymbol(house.sign);
634
+ const deg = formatDegrees(house.position || 0);
635
+ const label = i === 1 ? "(ASC)" : i === 4 ? "(IC)" : i === 7 ? "(DSC)" : i === 10 ? "(MC)" : "";
636
+ lines.push(` ${String(i).padStart(2)} ${chalk.cyan(zodiac)} ${house.sign} ${chalk.dim(deg)} ${chalk.yellow(label)}`);
637
+ }
638
+ }
639
+ lines.push("");
640
+ }
641
+ if (result.elements && result.modes) {
642
+ lines.push(chalk.bold.cyan("\u2500\u2500 BALANCE \u2500\u2500"));
643
+ const elem = result.elements;
644
+ const mode = result.modes;
645
+ lines.push(` ${COLORS.mars("\u{1F702} Fire:")} ${elem.Fire} ${COLORS.venus("\u{1F703} Earth:")} ${elem.Earth} ${COLORS.mercury("\u{1F701} Air:")} ${elem.Air} ${COLORS.moon("\u{1F704} Water:")} ${elem.Water}`);
646
+ lines.push(` ${COLORS.mars("Cardinal:")} ${mode.Cardinal} ${COLORS.sun("Fixed:")} ${mode.Fixed} ${COLORS.mercury("Mutable:")} ${mode.Mutable}`);
647
+ lines.push("");
648
+ }
649
+ if (result.aspects && result.aspects.length > 0) {
650
+ lines.push(chalk.bold.cyan("\u2500\u2500 ASPECTS \u2500\u2500"));
651
+ const aspects = result.aspects.slice(0, 15);
652
+ for (const asp of aspects) {
653
+ const p1Name = asp.planet1.toLowerCase().replace(/ /g, "_");
654
+ const p2Name = asp.planet2.toLowerCase().replace(/ /g, "_");
655
+ const p1Color = getPlanetColor(p1Name);
656
+ const p2Color = getPlanetColor(p2Name);
657
+ const p1 = getPlanetSymbol(p1Name);
658
+ const p2 = getPlanetSymbol(p2Name);
659
+ const aspectSym = getAspectSymbol(asp.aspect);
660
+ const aspectName = asp.aspect.charAt(0).toUpperCase() + asp.aspect.slice(1);
661
+ const aColor = getAspectColor(asp.aspect);
662
+ lines.push(` ${p1Color(p1)} ${asp.planet1.padEnd(10)} ${aColor(aspectSym + " " + aspectName.padEnd(11))} ${p2Color(p2)} ${asp.planet2.padEnd(10)} ${chalk.dim(`${asp.orb}\xB0`)}`);
663
+ }
664
+ lines.push("");
665
+ }
666
+ return lines.join("\n");
667
+ }
668
+ function formatTransits(result) {
669
+ const lines = [];
670
+ lines.push(chalk.bold.white(`
671
+ \u{1315D} Transits`));
672
+ if (result.natal.name) {
673
+ lines.push(chalk.dim(` For: ${result.natal.name}`));
674
+ }
675
+ if (result.natal.city) {
676
+ lines.push(chalk.dim(` Natal: ${result.natal.datetime} \xB7 ${result.natal.city}`));
677
+ } else {
678
+ lines.push(chalk.dim(` Natal: ${result.natal.datetime}`));
679
+ }
680
+ lines.push(chalk.dim(` Transit: ${result.transit.datetime}`));
681
+ if (result.transit.lunar_phase) {
682
+ lines.push(chalk.dim(` Moon: ${result.transit.lunar_phase.emoji} ${result.transit.lunar_phase.name}`));
683
+ }
684
+ lines.push("");
685
+ if (result.transit.planets) {
686
+ lines.push(chalk.bold.cyan("\u2500\u2500 CURRENT SKY \u2500\u2500"));
687
+ const planets = result.transit.planets;
688
+ for (const name of ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"]) {
689
+ const planet = planets[name];
690
+ if (planet) {
691
+ const pColor = getPlanetColor(name);
692
+ const zColor = getZodiacColor(planet.sign);
693
+ const symbol = getPlanetSymbol(name);
694
+ const zodiac = getZodiacSymbol(planet.sign);
695
+ const deg = formatDegrees(planet.position);
696
+ const rx = planet.retrograde ? COLORS.mars("\u211E") : " ";
697
+ const cH = planet.house ? planet.house.replace(/ house/i, "").replace(/first/i, "1").replace(/second/i, "2").replace(/third/i, "3").replace(/fourth/i, "4").replace(/fifth/i, "5").replace(/sixth/i, "6").replace(/seventh/i, "7").replace(/eighth/i, "8").replace(/ninth/i, "9").replace(/tenth/i, "10").replace(/eleventh/i, "11").replace(/twelfth/i, "12") : "?";
698
+ lines.push(` ${pColor(symbol)} ${zColor(zodiac + " " + planet.sign)} ${chalk.white(deg)} ${rx} ${chalk.dim(`${cH}H`)}`);
699
+ }
700
+ }
701
+ lines.push("");
702
+ }
703
+ const transitHouses = result.transit.houses;
704
+ const natalHouses = result.natal_houses;
705
+ if (transitHouses && natalHouses && Object.keys(transitHouses).length > 0) {
706
+ lines.push(chalk.bold.cyan("\u2500\u2500 HOUSES \u2500\u2500"));
707
+ lines.push(chalk.dim(" TRANSIT NATAL"));
708
+ for (let i = 1; i <= 12; i++) {
709
+ const tH = transitHouses[String(i)];
710
+ const nH = natalHouses[String(i)];
711
+ if (tH && nH) {
712
+ const label = i === 1 ? "ASC" : i === 4 ? "IC" : i === 7 ? "DSC" : i === 10 ? "MC" : `${i}`.padStart(2) + "H";
713
+ const tZColor = getZodiacColor(tH.sign);
714
+ const nZColor = getZodiacColor(nH.sign);
715
+ const tZodiac = getZodiacSymbol(tH.sign);
716
+ const nZodiac = getZodiacSymbol(nH.sign);
717
+ const tDeg = formatDegrees(tH.position);
718
+ const nDeg = formatDegrees(nH.position);
719
+ lines.push(` ${chalk.white(label.padStart(3))} ${tZColor(tZodiac + " " + tH.sign)} ${chalk.dim(tDeg)} ${nZColor(nZodiac + " " + nH.sign)} ${chalk.dim(nDeg)}`);
720
+ }
721
+ }
722
+ lines.push("");
723
+ }
724
+ lines.push(chalk.bold.cyan("\u2500\u2500 TRANSITS TO NATAL \u2500\u2500"));
725
+ if (result.aspects.length === 0) {
726
+ lines.push(chalk.dim(" No aspects within orb"));
727
+ } else {
728
+ const shortName = (name) => {
729
+ const map = {
730
+ "sun": "SUN",
731
+ "moon": "MOO",
732
+ "mercury": "MER",
733
+ "venus": "VEN",
734
+ "mars": "MAR",
735
+ "jupiter": "JUP",
736
+ "saturn": "SAT",
737
+ "uranus": "URA",
738
+ "neptune": "NEP",
739
+ "pluto": "PLU",
740
+ "chiron": "CHI",
741
+ "nn": "NN",
742
+ "sn": "SN",
743
+ "lilith": "LIL",
744
+ "mc": "MC",
745
+ "ic": "IC",
746
+ "asc": "ASC",
747
+ "dsc": "DSC"
748
+ };
749
+ return map[name.toLowerCase()] || name.slice(0, 3).toUpperCase();
750
+ };
751
+ for (const aspect of result.aspects) {
752
+ const tPlanet = aspect.transit_planet.toLowerCase().replace(/ /g, "_");
753
+ const nPlanet = aspect.natal_planet.toLowerCase().replace(/ /g, "_");
754
+ const tColor = getPlanetColor(tPlanet);
755
+ const nColor = getPlanetColor(nPlanet);
756
+ const tSym = getPlanetSymbol(tPlanet);
757
+ const nSym = getPlanetSymbol(nPlanet);
758
+ const tName = shortName(aspect.transit_planet);
759
+ const nName = shortName(aspect.natal_planet);
760
+ const aSym = getAspectSymbol(aspect.aspect);
761
+ const aColor = getAspectColor(aspect.aspect);
762
+ const aspectShort = aspect.aspect === "conjunction" ? "CNJ" : aspect.aspect === "opposition" ? "OPP" : aspect.aspect === "trine" ? "TRI" : aspect.aspect === "square" ? "SQR" : aspect.aspect === "sextile" ? "SXT" : aspect.aspect === "quintile" ? "QNT" : aspect.aspect === "quincunx" ? "QCX" : String(aspect.aspect).slice(0, 3).toUpperCase();
763
+ const tH = aspect.transit_house;
764
+ const nH = aspect.natal_house;
765
+ const houses = `${tH || "?"}H\u2192${nH || "?"}H`;
766
+ const orb = aspect.orb.toFixed(2).padStart(5);
767
+ lines.push(` ${tColor(tSym)} ${tName.padEnd(3)} ${aColor(aSym + " " + aspectShort)} ${nColor(nSym)} ${nName.padEnd(3)} ${chalk.dim(orb + "\xB0")} ${chalk.dim(houses)}`);
768
+ }
769
+ }
770
+ lines.push("");
771
+ return lines.join("\n");
772
+ }
773
+ function formatMoon(result) {
774
+ const lines = [];
775
+ const moonEmoji = result.phase.illumination > 50 ? "\u{1F315}" : "\u{1F311}";
776
+ const zColor = getZodiacColor(result.moon.sign);
777
+ lines.push(chalk.bold.white(`
778
+ ${moonEmoji} Moon Phase`));
779
+ lines.push(chalk.dim(` ${result.datetime}`));
780
+ lines.push("");
781
+ lines.push(` ${COLORS.moon("\u263D")} ${chalk.white("Moon in")} ${zColor(result.moon.sign)} ${chalk.white(formatDegrees(result.moon.position))}`);
782
+ lines.push(` ${chalk.white("Phase:")} ${COLORS.moon(result.phase.name)}`);
783
+ lines.push(` ${chalk.white("Illumination:")} ${COLORS.moon(result.phase.illumination.toFixed(1) + "%")}`);
784
+ lines.push("");
785
+ return lines.join("\n");
786
+ }
787
+ function formatEphemeris(result) {
788
+ const lines = [];
789
+ const pColor = getPlanetColor(result.body);
790
+ const zColor = getZodiacColor(result.sign);
791
+ const symbol = getPlanetSymbol(result.body);
792
+ const rx = result.retrograde ? COLORS.mars(" \u211E") : "";
793
+ lines.push(chalk.bold.white(`
794
+ \u{1315D} Ephemeris: ${result.body}`));
795
+ lines.push(chalk.dim(` ${result.datetime}`));
796
+ lines.push("");
797
+ lines.push(` ${pColor(symbol)} ${chalk.white(result.body)} in ${zColor(result.sign)} ${chalk.white(formatDegrees(result.position))}${rx}`);
798
+ lines.push(` ${chalk.dim(`Absolute: ${result.abs_position.toFixed(4)}\xB0`)}`);
799
+ lines.push("");
800
+ return lines.join("\n");
801
+ }
802
+ function formatSolarReturn(result) {
803
+ const lines = [];
804
+ lines.push(chalk.bold.white(`
805
+ \u2609 Solar Return ${result.return_year}`));
806
+ lines.push(chalk.dim(` Natal: ${result.natal_date}`));
807
+ lines.push(chalk.dim(` Exact: ${result.exact_datetime}`));
808
+ if (result.location.city) {
809
+ lines.push(chalk.dim(` Location: ${result.location.city}`));
810
+ }
811
+ if (result.lunar_phase) {
812
+ lines.push(chalk.dim(` Moon Phase: ${result.lunar_phase.emoji} ${result.lunar_phase.name}`));
813
+ }
814
+ lines.push("");
815
+ lines.push(chalk.bold.cyan("\u2500\u2500 ANGLES \u2500\u2500"));
816
+ const ascColor = getZodiacColor(result.ascendant.sign);
817
+ const mcColor = getZodiacColor(result.midheaven.sign);
818
+ lines.push(` ${COLORS.mars("ASC")} ${ascColor(getZodiacSymbol(result.ascendant.sign) + " " + result.ascendant.sign)} ${chalk.white(formatDegrees(result.ascendant.position))}`);
819
+ lines.push(` ${COLORS.sun("MC")} ${mcColor(getZodiacSymbol(result.midheaven.sign) + " " + result.midheaven.sign)} ${chalk.white(formatDegrees(result.midheaven.position))}`);
820
+ lines.push("");
821
+ lines.push(chalk.bold.cyan("\u2500\u2500 PLANETS \u2500\u2500"));
822
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
823
+ for (const name of planetOrder) {
824
+ const planet = result.planets[name];
825
+ if (planet) {
826
+ const pColor = getPlanetColor(name);
827
+ const zColor = getZodiacColor(planet.sign);
828
+ const symbol = getPlanetSymbol(name);
829
+ const zodiac = getZodiacSymbol(planet.sign);
830
+ const deg = formatDegrees(planet.position);
831
+ const rx = planet.retrograde ? COLORS.mars(" \u211E") : "";
832
+ const house = planet.house ? chalk.dim(` (${planet.house})`) : "";
833
+ lines.push(` ${pColor(symbol)} ${name.padEnd(10)} ${zColor(zodiac + " " + planet.sign)} ${chalk.white(deg)}${rx}${house}`);
834
+ }
835
+ }
836
+ lines.push("");
837
+ return lines.join("\n");
838
+ }
839
+ function formatLunarReturn(result) {
840
+ const lines = [];
841
+ lines.push(chalk.bold.white(`
842
+ \u263D Lunar Return`));
843
+ lines.push(chalk.dim(` Natal: ${result.natal_date}`));
844
+ lines.push(chalk.dim(` Natal Moon: ${getZodiacSymbol(result.natal_moon.sign)} ${result.natal_moon.sign} ${formatDegrees(result.natal_moon.position)}`));
845
+ lines.push(chalk.dim(` Search from: ${result.search_from}`));
846
+ lines.push(chalk.green(` Exact: ${result.exact_datetime}`));
847
+ if (result.location.city) {
848
+ lines.push(chalk.dim(` Location: ${result.location.city}`));
849
+ }
850
+ lines.push("");
851
+ lines.push(chalk.bold.cyan("\u2500\u2500 ANGLES \u2500\u2500"));
852
+ const ascColor = getZodiacColor(result.ascendant.sign);
853
+ const mcColor = getZodiacColor(result.midheaven.sign);
854
+ lines.push(` ${COLORS.mars("ASC")} ${ascColor(getZodiacSymbol(result.ascendant.sign) + " " + result.ascendant.sign)} ${chalk.white(formatDegrees(result.ascendant.position))}`);
855
+ lines.push(` ${COLORS.sun("MC")} ${mcColor(getZodiacSymbol(result.midheaven.sign) + " " + result.midheaven.sign)} ${chalk.white(formatDegrees(result.midheaven.position))}`);
856
+ lines.push("");
857
+ lines.push(chalk.bold.cyan("\u2500\u2500 PLANETS \u2500\u2500"));
858
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
859
+ for (const name of planetOrder) {
860
+ const planet = result.planets[name];
861
+ if (planet) {
862
+ const pColor = getPlanetColor(name);
863
+ const zColor = getZodiacColor(planet.sign);
864
+ const symbol = getPlanetSymbol(name);
865
+ const zodiac = getZodiacSymbol(planet.sign);
866
+ const deg = formatDegrees(planet.position);
867
+ const rx = planet.retrograde ? COLORS.mars(" \u211E") : "";
868
+ const house = planet.house ? chalk.dim(` (${planet.house})`) : "";
869
+ lines.push(` ${pColor(symbol)} ${name.padEnd(10)} ${zColor(zodiac + " " + planet.sign)} ${chalk.white(deg)}${rx}${house}`);
870
+ }
871
+ }
872
+ lines.push("");
873
+ return lines.join("\n");
874
+ }
875
+ function formatSynastry(result) {
876
+ const lines = [];
877
+ lines.push(chalk.bold.white(`
878
+ \u{1F491} Synastry`));
879
+ lines.push(chalk.dim(` ${result.person1.name} (${result.person1.date})`));
880
+ lines.push(chalk.dim(` ${result.person2.name} (${result.person2.date})`));
881
+ lines.push(chalk.dim(` Aspects found: ${result.aspect_count}`));
882
+ lines.push("");
883
+ lines.push(chalk.bold.cyan("\u2500\u2500 KEY POSITIONS \u2500\u2500"));
884
+ lines.push(chalk.dim(` ${result.person1.name.slice(0, 10).padEnd(12)} ${result.person2.name.slice(0, 10).padEnd(12)}`));
885
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars"];
886
+ for (const name of planetOrder) {
887
+ const p1 = result.person1.planets[name];
888
+ const p2 = result.person2.planets[name];
889
+ if (p1 && p2) {
890
+ const pColor = getPlanetColor(name);
891
+ const symbol = getPlanetSymbol(name);
892
+ const z1Color = getZodiacColor(p1.sign);
893
+ const z2Color = getZodiacColor(p2.sign);
894
+ lines.push(` ${pColor(symbol)} ${z1Color(p1.sign)} ${formatDegrees(p1.position).padEnd(7)} ${z2Color(p2.sign)} ${formatDegrees(p2.position)}`);
895
+ }
896
+ }
897
+ lines.push("");
898
+ lines.push(chalk.bold.cyan("\u2500\u2500 SYNASTRY ASPECTS \u2500\u2500"));
899
+ if (result.aspects.length === 0) {
900
+ lines.push(chalk.dim(" No aspects within orb"));
901
+ } else {
902
+ for (const asp of result.aspects.slice(0, 20)) {
903
+ const p1Name = asp.planet1.toLowerCase().replace(/ /g, "_");
904
+ const p2Name = asp.planet2.toLowerCase().replace(/ /g, "_");
905
+ const p1Color = getPlanetColor(p1Name);
906
+ const p2Color = getPlanetColor(p2Name);
907
+ const p1Sym = getPlanetSymbol(p1Name);
908
+ const p2Sym = getPlanetSymbol(p2Name);
909
+ const aSym = getAspectSymbol(asp.aspect);
910
+ const aColor = getAspectColor(asp.aspect);
911
+ const aspectName = asp.aspect.charAt(0).toUpperCase() + asp.aspect.slice(1);
912
+ lines.push(` ${p1Color(p1Sym)} ${asp.planet1.slice(0, 8).padEnd(8)} ${aColor(aSym)} ${aColor(aspectName.slice(0, 6).padEnd(6))} ${p2Color(p2Sym)} ${asp.planet2.slice(0, 8).padEnd(8)} ${chalk.dim(asp.orb.toFixed(2) + "\xB0")}`);
913
+ }
914
+ }
915
+ lines.push("");
916
+ return lines.join("\n");
917
+ }
918
+ function formatProgressions(result) {
919
+ const lines = [];
920
+ lines.push(chalk.bold.white(`
921
+ \u{1F319} Secondary Progressions`));
922
+ lines.push(chalk.dim(` Method: ${result.method}`));
923
+ lines.push(chalk.dim(` Natal: ${result.natal_date}`));
924
+ lines.push(chalk.dim(` Target: ${result.target_date} (age ${result.age_at_target})`));
925
+ lines.push(chalk.dim(` Progressed date: ${result.progressed_date}`));
926
+ lines.push("");
927
+ lines.push(chalk.bold.cyan("\u2500\u2500 PROGRESSED ANGLES \u2500\u2500"));
928
+ const ascColor = getZodiacColor(result.progressed_ascendant.sign);
929
+ const mcColor = getZodiacColor(result.progressed_midheaven.sign);
930
+ lines.push(` ${COLORS.mars("ASC")} ${ascColor(getZodiacSymbol(result.progressed_ascendant.sign) + " " + result.progressed_ascendant.sign)} ${chalk.white(formatDegrees(result.progressed_ascendant.position))}`);
931
+ lines.push(` ${COLORS.sun("MC")} ${mcColor(getZodiacSymbol(result.progressed_midheaven.sign) + " " + result.progressed_midheaven.sign)} ${chalk.white(formatDegrees(result.progressed_midheaven.position))}`);
932
+ lines.push("");
933
+ lines.push(chalk.bold.cyan("\u2500\u2500 PROGRESSED vs NATAL \u2500\u2500"));
934
+ lines.push(chalk.dim(" PROGRESSED NATAL"));
935
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars"];
936
+ for (const name of planetOrder) {
937
+ const prog = result.progressed_planets[name];
938
+ const natal = result.natal_planets[name];
939
+ if (prog && natal) {
940
+ const pColor = getPlanetColor(name);
941
+ const symbol = getPlanetSymbol(name);
942
+ const pZColor = getZodiacColor(prog.sign);
943
+ const nZColor = getZodiacColor(natal.sign);
944
+ const rx = prog.retrograde ? COLORS.mars("\u211E") : " ";
945
+ lines.push(` ${pColor(symbol)} ${pZColor(prog.sign)} ${formatDegrees(prog.position).padEnd(7)}${rx} ${nZColor(natal.sign)} ${formatDegrees(natal.position)}`);
946
+ }
947
+ }
948
+ lines.push("");
949
+ if (result.progressed_to_natal_aspects.length > 0) {
950
+ lines.push(chalk.bold.cyan("\u2500\u2500 PROGRESSED \u2192 NATAL ASPECTS \u2500\u2500"));
951
+ for (const asp of result.progressed_to_natal_aspects.slice(0, 10)) {
952
+ const pName = asp.progressed.toLowerCase().replace(/ /g, "_");
953
+ const nName = asp.natal.toLowerCase().replace(/ /g, "_");
954
+ const pColor = getPlanetColor(pName);
955
+ const nColor = getPlanetColor(nName);
956
+ const pSym = getPlanetSymbol(pName);
957
+ const nSym = getPlanetSymbol(nName);
958
+ const aSym = getAspectSymbol(asp.aspect);
959
+ const aColor = getAspectColor(asp.aspect);
960
+ lines.push(` ${pColor(pSym)} ${asp.progressed.slice(0, 8).padEnd(8)} ${aColor(aSym)} ${nColor(nSym)} ${asp.natal.slice(0, 8).padEnd(8)} ${chalk.dim(asp.orb.toFixed(2) + "\xB0")}`);
961
+ }
962
+ lines.push("");
963
+ }
964
+ return lines.join("\n");
965
+ }
966
+ function formatEphemerisRange(result) {
967
+ const lines = [];
968
+ const pColor = getPlanetColor(result.body);
969
+ const symbol = getPlanetSymbol(result.body);
970
+ lines.push(chalk.bold.white(`
971
+ ${pColor(symbol)} ${result.body.charAt(0).toUpperCase() + result.body.slice(1)} Ephemeris`));
972
+ lines.push(chalk.dim(` ${result.range.start} \u2192 ${result.range.end} (${result.range.step})`));
973
+ lines.push("");
974
+ if (result.sign_changes.length > 0) {
975
+ lines.push(chalk.bold.cyan("\u2500\u2500 SIGN CHANGES \u2500\u2500"));
976
+ for (const change of result.sign_changes) {
977
+ const fromColor = getZodiacColor(change.from);
978
+ const toColor = getZodiacColor(change.to);
979
+ lines.push(` ${chalk.white(change.date)} ${fromColor(getZodiacSymbol(change.from) + " " + change.from)} \u2192 ${toColor(getZodiacSymbol(change.to) + " " + change.to)}`);
980
+ }
981
+ lines.push("");
982
+ }
983
+ if (result.retrograde_stations.length > 0) {
984
+ lines.push(chalk.bold.cyan("\u2500\u2500 RETROGRADE STATIONS \u2500\u2500"));
985
+ for (const station of result.retrograde_stations) {
986
+ const zColor = getZodiacColor(station.sign);
987
+ const stationType = station.station === "retrograde" ? COLORS.mars("\u211E RETRO") : COLORS.jupiter("\u2B95 DIRECT");
988
+ lines.push(` ${chalk.white(station.date)} ${stationType} ${zColor(station.sign)} ${formatDegrees(station.position)}`);
989
+ }
990
+ lines.push("");
991
+ }
992
+ lines.push(chalk.bold.cyan("\u2500\u2500 POSITIONS \u2500\u2500"));
993
+ for (const pos of result.positions) {
994
+ const zColor = getZodiacColor(pos.sign);
995
+ const rx = pos.retrograde ? COLORS.mars(" \u211E") : "";
996
+ lines.push(` ${chalk.dim(pos.date)} ${zColor(getZodiacSymbol(pos.sign) + " " + pos.sign)} ${chalk.white(formatDegrees(pos.position))}${rx}`);
997
+ }
998
+ lines.push("");
999
+ return lines.join("\n");
1000
+ }
1001
+ function formatComposite(result) {
1002
+ const lines = [];
1003
+ lines.push(chalk.bold.white(`
1004
+ \u{1F52E} Composite Chart`));
1005
+ lines.push(chalk.dim(` ${result.person1.name} + ${result.person2.name}`));
1006
+ lines.push(chalk.dim(` Method: ${result.method}`));
1007
+ lines.push("");
1008
+ lines.push(chalk.bold.cyan("\u2500\u2500 COMPOSITE ANGLES \u2500\u2500"));
1009
+ const ascColor = getZodiacColor(result.ascendant.sign);
1010
+ const mcColor = getZodiacColor(result.midheaven.sign);
1011
+ lines.push(` ${COLORS.mars("ASC")} ${ascColor(getZodiacSymbol(result.ascendant.sign) + " " + result.ascendant.sign)} ${chalk.white(formatDegrees(result.ascendant.position))}`);
1012
+ lines.push(` ${COLORS.sun("MC")} ${mcColor(getZodiacSymbol(result.midheaven.sign) + " " + result.midheaven.sign)} ${chalk.white(formatDegrees(result.midheaven.position))}`);
1013
+ lines.push("");
1014
+ lines.push(chalk.bold.cyan("\u2500\u2500 COMPOSITE PLANETS \u2500\u2500"));
1015
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
1016
+ for (const name of planetOrder) {
1017
+ const planet = result.planets[name];
1018
+ if (planet) {
1019
+ const pColor = getPlanetColor(name);
1020
+ const zColor = getZodiacColor(planet.sign);
1021
+ const symbol = getPlanetSymbol(name);
1022
+ const zodiac = getZodiacSymbol(planet.sign);
1023
+ const deg = formatDegrees(planet.position);
1024
+ const house = planet.house ? chalk.dim(` (${planet.house})`) : "";
1025
+ lines.push(` ${pColor(symbol)} ${name.padEnd(10)} ${zColor(zodiac + " " + planet.sign)} ${chalk.white(deg)}${house}`);
1026
+ }
1027
+ }
1028
+ lines.push("");
1029
+ lines.push(chalk.dim(" The composite chart represents the relationship as its own entity."));
1030
+ lines.push(chalk.dim(" Each planet is the midpoint between both individuals' placements."));
1031
+ lines.push("");
1032
+ return lines.join("\n");
1033
+ }
1034
+ function formatSolarArc(result) {
1035
+ const lines = [];
1036
+ lines.push(chalk.bold.white(`
1037
+ \u2600\uFE0F Solar Arc Directions`));
1038
+ lines.push(chalk.dim(` Method: ${result.method}`));
1039
+ lines.push(chalk.dim(` Natal: ${result.natal_date}`));
1040
+ lines.push(chalk.dim(` Target: ${result.target_date} (age ${result.age_at_target})`));
1041
+ lines.push(chalk.green(` Solar Arc: ${result.solar_arc.toFixed(2)}\xB0`));
1042
+ lines.push("");
1043
+ lines.push(chalk.bold.cyan("\u2500\u2500 DIRECTED ANGLES \u2500\u2500"));
1044
+ const ascColor = getZodiacColor(result.directed_ascendant.sign);
1045
+ const mcColor = getZodiacColor(result.directed_midheaven.sign);
1046
+ lines.push(` ${COLORS.mars("ASC")} ${ascColor(getZodiacSymbol(result.directed_ascendant.sign) + " " + result.directed_ascendant.sign)} ${chalk.white(formatDegrees(result.directed_ascendant.position))} ${chalk.dim(`\u2190 ${result.directed_ascendant.natal_sign} ${formatDegrees(result.directed_ascendant.natal_position)}`)}`);
1047
+ lines.push(` ${COLORS.sun("MC")} ${mcColor(getZodiacSymbol(result.directed_midheaven.sign) + " " + result.directed_midheaven.sign)} ${chalk.white(formatDegrees(result.directed_midheaven.position))} ${chalk.dim(`\u2190 ${result.directed_midheaven.natal_sign} ${formatDegrees(result.directed_midheaven.natal_position)}`)}`);
1048
+ lines.push("");
1049
+ lines.push(chalk.bold.cyan("\u2500\u2500 DIRECTED PLANETS \u2500\u2500"));
1050
+ lines.push(chalk.dim(" DIRECTED \u2190 NATAL"));
1051
+ const planetOrder = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn", "uranus", "neptune", "pluto"];
1052
+ for (const name of planetOrder) {
1053
+ const planet = result.directed_planets[name];
1054
+ if (planet) {
1055
+ const pColor = getPlanetColor(name);
1056
+ const symbol = getPlanetSymbol(name);
1057
+ const dZColor = getZodiacColor(planet.sign);
1058
+ const nZColor = getZodiacColor(planet.natal_sign);
1059
+ lines.push(` ${pColor(symbol)} ${dZColor(planet.sign)} ${formatDegrees(planet.position).padEnd(7)} \u2190 ${nZColor(planet.natal_sign)} ${formatDegrees(planet.natal_position)}`);
1060
+ }
1061
+ }
1062
+ lines.push("");
1063
+ if (result.directed_to_natal_aspects.length > 0) {
1064
+ lines.push(chalk.bold.cyan("\u2500\u2500 DIRECTED \u2192 NATAL ASPECTS \u2500\u2500"));
1065
+ for (const asp of result.directed_to_natal_aspects.slice(0, 15)) {
1066
+ const dName = asp.directed.toLowerCase();
1067
+ const nName = asp.natal.toLowerCase();
1068
+ const dColor = getPlanetColor(dName);
1069
+ const nColor = getPlanetColor(nName);
1070
+ const dSym = getPlanetSymbol(dName);
1071
+ const nSym = getPlanetSymbol(nName);
1072
+ const aSym = getAspectSymbol(asp.aspect);
1073
+ const aColor = getAspectColor(asp.aspect);
1074
+ lines.push(` ${dColor(dSym)} ${asp.directed.slice(0, 7).padEnd(7)} ${aColor(aSym)} ${nColor(nSym)} ${asp.natal.slice(0, 7).padEnd(7)} ${chalk.dim(asp.orb.toFixed(2) + "\xB0")}`);
1075
+ }
1076
+ lines.push("");
1077
+ }
1078
+ return lines.join("\n");
1079
+ }
1080
+ function formatHorary(result) {
1081
+ const lines = [];
1082
+ lines.push(chalk.bold.white(`
1083
+ \u{1F52E} HORARY CHART`));
1084
+ lines.push(chalk.bold.yellow(` "${result.question}"`));
1085
+ lines.push("");
1086
+ lines.push(chalk.dim(` Cast: ${result.cast_time.datetime}`));
1087
+ if (result.cast_time.city) {
1088
+ lines.push(chalk.dim(` Location: ${result.cast_time.city}`));
1089
+ }
1090
+ lines.push(chalk.dim(` Planetary Hour: ${result.planetary_hour} | Day Ruler: ${result.day_ruler}`));
1091
+ lines.push("");
1092
+ if (result.strictures.length > 0) {
1093
+ lines.push(chalk.bold.red("\u26A0\uFE0F STRICTURES AGAINST JUDGMENT"));
1094
+ for (const stricture of result.strictures) {
1095
+ lines.push(chalk.red(` \u2022 ${stricture}`));
1096
+ }
1097
+ lines.push("");
1098
+ }
1099
+ lines.push(chalk.bold.cyan("\u2500\u2500 THE QUERENT \u2500\u2500"));
1100
+ const qRuler = result.querent.ruler.toLowerCase();
1101
+ const qColor = getPlanetColor(qRuler);
1102
+ const qZColor = getZodiacColor(result.querent.sign);
1103
+ const qPosZColor = getZodiacColor(result.querent.ruler_position.sign);
1104
+ lines.push(` Ascendant: ${qZColor(getZodiacSymbol(result.querent.sign) + " " + result.querent.sign)} ${formatDegrees(result.ascendant.position)}`);
1105
+ lines.push(` You are signified by: ${qColor(getPlanetSymbol(qRuler) + " " + result.querent.ruler)}`);
1106
+ const rx = result.querent.ruler_position.retrograde ? COLORS.mars(" \u211E") : "";
1107
+ lines.push(` ${result.querent.ruler} is in: ${qPosZColor(result.querent.ruler_position.sign)} ${formatDegrees(result.querent.ruler_position.position)}${rx}`);
1108
+ if (result.querent.ruler_position.house) {
1109
+ lines.push(` ${result.querent.ruler} is in: ${chalk.white(result.querent.ruler_position.house)}`);
1110
+ }
1111
+ lines.push("");
1112
+ lines.push(chalk.bold.cyan("\u2500\u2500 THE MOON \u2500\u2500"));
1113
+ const moonZColor = getZodiacColor(result.moon.sign);
1114
+ lines.push(` ${COLORS.moon("\u263D")} Moon in ${moonZColor(getZodiacSymbol(result.moon.sign) + " " + result.moon.sign)} ${formatDegrees(result.moon.position)}`);
1115
+ if (result.moon.house) {
1116
+ lines.push(` House: ${chalk.white(result.moon.house)}`);
1117
+ }
1118
+ if (result.moon.void_of_course) {
1119
+ lines.push(` ${chalk.red("\u26A0\uFE0F VOID OF COURSE")} \u2014 ${chalk.dim(`${result.moon.degrees_until_sign_change.toFixed(1)}\xB0 until sign change`)}`);
1120
+ lines.push(chalk.dim(" (Nothing may come of the matter, or outcome is already fated)"));
1121
+ } else {
1122
+ lines.push(` ${chalk.green("Moon is active")} \u2014 ${chalk.dim(`${result.moon.degrees_until_sign_change.toFixed(1)}\xB0 until sign change`)}`);
1123
+ }
1124
+ if (result.moon.aspects.length > 0) {
1125
+ lines.push("");
1126
+ lines.push(chalk.dim(" Moon aspects:"));
1127
+ for (const asp of result.moon.aspects.slice(0, 5)) {
1128
+ const pColor = getPlanetColor(asp.planet.toLowerCase());
1129
+ const aSym = getAspectSymbol(asp.aspect);
1130
+ const applying = asp.applying ? chalk.green("\u2192 applying") : chalk.dim("\u2190 separating");
1131
+ lines.push(` ${COLORS.moon("\u263D")} ${aSym} ${pColor(asp.planet.padEnd(8))} ${chalk.dim(asp.orb.toFixed(2) + "\xB0")} ${applying}`);
1132
+ }
1133
+ }
1134
+ lines.push("");
1135
+ lines.push(chalk.bold.cyan("\u2500\u2500 HOUSES \u2500\u2500"));
1136
+ lines.push(chalk.dim(" Use these to identify the quesited (thing asked about):"));
1137
+ lines.push("");
1138
+ const importantHouses = ["1", "2", "5", "7", "10", "11"];
1139
+ for (const num of importantHouses) {
1140
+ const house = result.houses[num];
1141
+ if (house) {
1142
+ const hZColor = getZodiacColor(house.sign);
1143
+ const ruler = house.ruler.toLowerCase();
1144
+ const rColor = getPlanetColor(ruler);
1145
+ lines.push(` ${chalk.bold(num.padStart(2))}H ${hZColor(getZodiacSymbol(house.sign))} ${house.sign.padEnd(3)} \u2192 ${rColor(house.ruler.padEnd(7))} ${chalk.dim(house.topic)}`);
1146
+ }
1147
+ }
1148
+ lines.push(chalk.dim(" ... (other houses available in JSON output)"));
1149
+ lines.push("");
1150
+ lines.push(chalk.bold.cyan("\u2500\u2500 KEY PLANETS \u2500\u2500"));
1151
+ const keyPlanets = ["sun", "moon", "mercury", "venus", "mars", "jupiter", "saturn"];
1152
+ for (const name of keyPlanets) {
1153
+ const planet = result.planets[name];
1154
+ if (planet) {
1155
+ const pColor = getPlanetColor(name);
1156
+ const zColor = getZodiacColor(planet.sign);
1157
+ const symbol = getPlanetSymbol(name);
1158
+ const deg = formatDegrees(planet.position);
1159
+ const rx2 = planet.retrograde ? COLORS.mars(" \u211E") : "";
1160
+ const house = planet.house ? chalk.dim(` (${planet.house})`) : "";
1161
+ lines.push(` ${pColor(symbol)} ${name.padEnd(8)} ${zColor(planet.sign)} ${deg}${rx2}${house}`);
1162
+ }
1163
+ }
1164
+ lines.push("");
1165
+ lines.push(chalk.bold.cyan("\u2500\u2500 HOW TO READ \u2500\u2500"));
1166
+ lines.push(chalk.dim(" 1. Identify the quesited's house (what you're asking about)"));
1167
+ lines.push(chalk.dim(" 2. Find that house's ruler (the significator)"));
1168
+ lines.push(chalk.dim(" 3. Look for aspects between querent's ruler and quesited's ruler"));
1169
+ lines.push(chalk.dim(" 4. Applying aspects = future contact; Separating = past"));
1170
+ lines.push(chalk.dim(" 5. Moon's next aspect often shows the outcome"));
1171
+ lines.push("");
1172
+ return lines.join("\n");
1173
+ }
1174
+
1175
+ // src/types.ts
1176
+ function isError(result) {
1177
+ return "error" in result;
1178
+ }
1179
+
1180
+ export {
1181
+ chart,
1182
+ transit,
1183
+ solarReturn,
1184
+ lunarReturn,
1185
+ synastry,
1186
+ progressions,
1187
+ ephemerisRange,
1188
+ moon,
1189
+ ephemeris,
1190
+ composite,
1191
+ solarArc,
1192
+ horary,
1193
+ version,
1194
+ getZodiacSymbol,
1195
+ getPlanetSymbol,
1196
+ getAspectSymbol,
1197
+ formatDegrees,
1198
+ formatChart,
1199
+ formatTransits,
1200
+ formatMoon,
1201
+ formatEphemeris,
1202
+ formatSolarReturn,
1203
+ formatLunarReturn,
1204
+ formatSynastry,
1205
+ formatProgressions,
1206
+ formatEphemerisRange,
1207
+ formatComposite,
1208
+ formatSolarArc,
1209
+ formatHorary,
1210
+ isError
1211
+ };