klio 1.5.6 → 1.5.7
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 +19 -0
- package/ephe/se00010s.se1 +0 -0
- package/package.json +1 -1
- package/src/astrology/astrologyConstants.js +29 -0
- package/src/astrology/astrologyService.js +185 -52
- package/src/cli/cli.js +72 -33
- package/src/cli/cliService.js +18 -1
- package/src/config/configService.js +50 -1
package/README.md
CHANGED
|
@@ -36,6 +36,25 @@ npm install -g klio
|
|
|
36
36
|
klio [options]
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
+
### Planet Selection
|
|
40
|
+
|
|
41
|
+
By default, Klio uses traditional planets plus Chiron. You can customize which planets and asteroids are active:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Show currently active planets
|
|
45
|
+
klio --active-planets
|
|
46
|
+
|
|
47
|
+
# Set custom active planets (comma-separated)
|
|
48
|
+
klio --planets "Sun,Moon,Mercury,Venus,Chiron,Hygiea"
|
|
49
|
+
|
|
50
|
+
klio --planets "Sun,Moon,Mercury,Venus,Mars,Jupiter,Saturn,Uranus,Neptune,Pluto"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Available planets: Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Chiron, Hygiea
|
|
54
|
+
|
|
55
|
+
> **Note:** By default, only Chiron is active among asteroids. Other asteroids like Hygiea must be explicitly enabled.
|
|
56
|
+
=======
|
|
57
|
+
|
|
39
58
|
### Web Interface
|
|
40
59
|
|
|
41
60
|
AstroCLI includes a web-based GUI that you can launch with the `--gui` flag:
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -13,6 +13,25 @@ const planets = {
|
|
|
13
13
|
chiron: 15
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
// Asteroid constants for Swiss Ephemeris
|
|
17
|
+
// Note: These use the SE_AST_OFFSET (10000) + asteroid number
|
|
18
|
+
const asteroidConstants = {
|
|
19
|
+
ceres: 10001, // Ceres = 10000 + 1
|
|
20
|
+
pallas: 10002, // Pallas = 10000 + 2
|
|
21
|
+
hygiea: 10010, // Hygiea = 10000 + 10
|
|
22
|
+
juno: 10003, // Juno = 10000 + 3
|
|
23
|
+
vesta: 10004 // Vesta = 10000 + 4
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Asteroid names for Swiss Ephemeris (used as string identifiers)
|
|
27
|
+
const asteroidNames = {
|
|
28
|
+
ceres: 'Ceres',
|
|
29
|
+
pallas: 'Pallas',
|
|
30
|
+
hygiea: 'Hygiea',
|
|
31
|
+
juno: 'Juno',
|
|
32
|
+
vesta: 'Vesta'
|
|
33
|
+
};
|
|
34
|
+
|
|
16
35
|
// Zodiac signs (English)
|
|
17
36
|
const signs = [
|
|
18
37
|
'Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
|
|
@@ -54,8 +73,18 @@ const dignities = {
|
|
|
54
73
|
15: { sign: 'Virgo', exaltation: 'Pisces', fall: 'Virgo', detriment: 'Pisces' } // Chiron
|
|
55
74
|
};
|
|
56
75
|
|
|
76
|
+
// Asteroid dignities (using asteroid constants as keys)
|
|
77
|
+
const asteroidDignities = {
|
|
78
|
+
10001: { sign: 'Taurus', exaltation: 'Virgo', fall: 'Scorpio', detriment: 'Scorpio' }, // Ceres
|
|
79
|
+
10002: { sign: 'Libra', exaltation: 'Aries', fall: 'Libra', detriment: 'Aries' }, // Pallas
|
|
80
|
+
10010: { sign: 'Virgo', exaltation: 'Pisces', fall: 'Virgo', detriment: 'Pisces' } // Hygiea
|
|
81
|
+
};
|
|
82
|
+
|
|
57
83
|
module.exports = {
|
|
58
84
|
planets,
|
|
85
|
+
asteroidNames,
|
|
86
|
+
asteroidConstants,
|
|
87
|
+
asteroidDignities,
|
|
59
88
|
signs,
|
|
60
89
|
elements,
|
|
61
90
|
signTypes,
|
|
@@ -3,7 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const moment = require('moment-timezone');
|
|
4
4
|
const csvParser = require('csv-parser');
|
|
5
5
|
const { parse } = require('csv-parse/sync');
|
|
6
|
-
const { planets, signs, elements, signTypes, decans, dignities } = require('./astrologyConstants');
|
|
6
|
+
const { planets, asteroidNames, asteroidConstants, asteroidDignities, signs, elements, signTypes, decans, dignities } = require('./astrologyConstants');
|
|
7
7
|
const { loadConfig } = require('../config/configService');
|
|
8
8
|
const path = require('path');
|
|
9
9
|
|
|
@@ -49,6 +49,17 @@ function getCurrentTimeInTimezone() {
|
|
|
49
49
|
};
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
// Function to get active planets (filtered by configuration)
|
|
53
|
+
function getActivePlanets() {
|
|
54
|
+
try {
|
|
55
|
+
const { getActivePlanets } = require('../config/configService');
|
|
56
|
+
return getActivePlanets();
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Fallback to default active planets if config service is not available
|
|
59
|
+
return ['sun', 'moon', 'mercury', 'venus', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune', 'pluto', 'chiron'];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
52
63
|
// Function to load birth data from configuration
|
|
53
64
|
function getBirthDataFromConfig(userId = null) {
|
|
54
65
|
try {
|
|
@@ -377,7 +388,14 @@ function getPlanetHouse(planetLongitude, houseCusps) {
|
|
|
377
388
|
// Function to calculate astrological data
|
|
378
389
|
function getAstrologicalData(planetName, customDate = null) {
|
|
379
390
|
const planet = planets[planetName];
|
|
380
|
-
|
|
391
|
+
const isAsteroid = asteroidNames[planetName] !== undefined;
|
|
392
|
+
let asteroidId = null;
|
|
393
|
+
|
|
394
|
+
if (isAsteroid) {
|
|
395
|
+
asteroidId = asteroidConstants[planetName];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
if (planet === undefined && !isAsteroid) {
|
|
381
399
|
console.error(`Invalid planet: ${planetName}. Available planets:`, Object.keys(planets).join(', '));
|
|
382
400
|
process.exit(1);
|
|
383
401
|
}
|
|
@@ -433,16 +451,53 @@ function getAstrologicalData(planetName, customDate = null) {
|
|
|
433
451
|
|
|
434
452
|
const julianDay = calculateJulianDayUTC({ year: calcYear, month: calcMonth, day: calcDay, hour: calcHour, minute: calcMinute }, timezoneOffsetMinutes);
|
|
435
453
|
const flag = swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED;
|
|
436
|
-
let result
|
|
454
|
+
let result;
|
|
437
455
|
|
|
438
|
-
if (
|
|
439
|
-
//
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
456
|
+
if (isAsteroid) {
|
|
457
|
+
// Handle asteroid calculations using proper Swiss Ephemeris constants
|
|
458
|
+
if (asteroidId) {
|
|
459
|
+
// Use the same swe_calc_ut function but with asteroid ID
|
|
460
|
+
result = swisseph.swe_calc_ut(julianDay, asteroidId, flag);
|
|
461
|
+
|
|
462
|
+
if (result.error && result.error.includes('not found')) {
|
|
463
|
+
// Fallback to Moshier if ephemeris files are missing
|
|
464
|
+
const moshierFlag = swisseph.SEFLG_MOSEPH | swisseph.SEFLG_SPEED;
|
|
465
|
+
result = swisseph.swe_calc_ut(julianDay, asteroidId, moshierFlag);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (result.error) {
|
|
469
|
+
console.warn(`⚠️ Could not calculate asteroid ${planetName}: ${result.error}`);
|
|
470
|
+
// Fallback to placeholder data if calculation fails
|
|
471
|
+
result = {
|
|
472
|
+
longitude: 0,
|
|
473
|
+
latitude: 0,
|
|
474
|
+
distance: 0,
|
|
475
|
+
longitudeSpeed: 0
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
console.warn(`⚠️ Unknown asteroid: ${planetName}`);
|
|
480
|
+
// Fallback to placeholder data for unknown asteroids
|
|
481
|
+
result = {
|
|
482
|
+
longitude: 0,
|
|
483
|
+
latitude: 0,
|
|
484
|
+
distance: 0,
|
|
485
|
+
longitudeSpeed: 0
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
// Handle regular planet calculations
|
|
490
|
+
result = swisseph.swe_calc_ut(julianDay, planet, flag);
|
|
443
491
|
|
|
444
|
-
|
|
445
|
-
|
|
492
|
+
if (result.error && result.error.includes('not found')) {
|
|
493
|
+
// Fallback to Moshier if ephemeris files are missing
|
|
494
|
+
const moshierFlag = swisseph.SEFLG_MOSEPH | swisseph.SEFLG_SPEED;
|
|
495
|
+
result = swisseph.swe_calc_ut(julianDay, planet, moshierFlag);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (result.error) {
|
|
499
|
+
throw new Error(result.error);
|
|
500
|
+
}
|
|
446
501
|
}
|
|
447
502
|
|
|
448
503
|
const longitude = result.longitude;
|
|
@@ -452,16 +507,20 @@ function getAstrologicalData(planetName, customDate = null) {
|
|
|
452
507
|
const element = elements[signIndex];
|
|
453
508
|
const decan = decans[Math.floor((longitude % 30) / 10)];
|
|
454
509
|
|
|
455
|
-
|
|
510
|
+
// Get dignity info - use asteroid dignities for asteroids, regular dignities for planets
|
|
511
|
+
const dignityInfo = isAsteroid ? asteroidDignities[asteroidId] : dignities[planet];
|
|
456
512
|
let dignity = 'Neutral';
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
513
|
+
|
|
514
|
+
if (dignityInfo) {
|
|
515
|
+
if (sign === dignityInfo.sign) {
|
|
516
|
+
dignity = 'Ruler';
|
|
517
|
+
} else if (sign === dignityInfo.exaltation) {
|
|
518
|
+
dignity = 'Exaltation';
|
|
519
|
+
} else if (sign === dignityInfo.fall) {
|
|
520
|
+
dignity = 'Fall';
|
|
521
|
+
} else if (sign === dignityInfo.detriment) {
|
|
522
|
+
dignity = 'Detriment';
|
|
523
|
+
}
|
|
465
524
|
}
|
|
466
525
|
|
|
467
526
|
return {
|
|
@@ -510,8 +569,16 @@ function getCriticalPlanets(customDate = null) {
|
|
|
510
569
|
const offsetMinutes = timezone ? getTimezoneOffset(timeData, timezone) : -new Date().getTimezoneOffset();
|
|
511
570
|
const julianDay = calculateJulianDayUTC(timeData, offsetMinutes);
|
|
512
571
|
|
|
513
|
-
//
|
|
572
|
+
// Get active planets from configuration
|
|
573
|
+
const activePlanets = getActivePlanets();
|
|
574
|
+
|
|
575
|
+
// Calculate positions of active planets only
|
|
514
576
|
for (const [name, planetId] of Object.entries(planets)) {
|
|
577
|
+
// Skip planets that are not in the active list
|
|
578
|
+
if (!activePlanets.includes(name)) {
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
|
|
515
582
|
const flag = swisseph.SEFLG_SWIEPH | swisseph.SEFLG_SPEED;
|
|
516
583
|
let result = swisseph.swe_calc_ut(julianDay, planetId, flag);
|
|
517
584
|
|
|
@@ -772,12 +839,12 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
|
|
|
772
839
|
// Function to calculate all active aspects between all planets
|
|
773
840
|
function getAllActiveAspects(dateComponents) {
|
|
774
841
|
const allAspects = [];
|
|
775
|
-
const
|
|
842
|
+
const activePlanets = getActivePlanets();
|
|
776
843
|
const seenAspects = new Set(); // To avoid duplicates
|
|
777
844
|
|
|
778
|
-
// Calculate aspects for
|
|
779
|
-
for (let i = 0; i <
|
|
780
|
-
const planet1 =
|
|
845
|
+
// Calculate aspects for active planet pairs only
|
|
846
|
+
for (let i = 0; i < activePlanets.length; i++) {
|
|
847
|
+
const planet1 = activePlanets[i];
|
|
781
848
|
const aspects = calculatePlanetAspects(planet1, dateComponents, true); // Always use Huber orbs
|
|
782
849
|
|
|
783
850
|
aspects.forEach(aspect => {
|
|
@@ -1349,16 +1416,25 @@ async function calculatePersonalTransits(transitDate = null, birthData = null) {
|
|
|
1349
1416
|
const birthJulianDay = calculateJulianDayUTC(birthData, getTimezoneOffset(birthData, birthData.location?.timezone || 'Europe/Zurich'));
|
|
1350
1417
|
const houses = await calculateHouses(birthJulianDay, 'K', true, birthData.location || null); // Always Koch house system for transits
|
|
1351
1418
|
|
|
1352
|
-
//
|
|
1419
|
+
// Get active planets from configuration
|
|
1420
|
+
const activePlanets = getActivePlanets();
|
|
1421
|
+
|
|
1422
|
+
// Calculate current planet positions (Transit) for active planets only
|
|
1353
1423
|
const transitPlanets = {};
|
|
1354
1424
|
for (const [name, planetId] of Object.entries(planets)) {
|
|
1425
|
+
if (!activePlanets.includes(name)) {
|
|
1426
|
+
continue;
|
|
1427
|
+
}
|
|
1355
1428
|
const data = getAstrologicalData(name, transitDate);
|
|
1356
1429
|
transitPlanets[name] = data;
|
|
1357
1430
|
}
|
|
1358
1431
|
|
|
1359
|
-
// Calculate birth planet positions (Radix)
|
|
1432
|
+
// Calculate birth planet positions (Radix) for active planets only
|
|
1360
1433
|
const birthPlanets = {};
|
|
1361
1434
|
for (const [name, planetId] of Object.entries(planets)) {
|
|
1435
|
+
if (!activePlanets.includes(name)) {
|
|
1436
|
+
continue;
|
|
1437
|
+
}
|
|
1362
1438
|
const data = getAstrologicalData(name, birthData);
|
|
1363
1439
|
birthPlanets[name] = data;
|
|
1364
1440
|
}
|
|
@@ -1399,16 +1475,25 @@ function calculatePersonalTransitAspects(transitDate = null, birthData = null, t
|
|
|
1399
1475
|
transitDate = getCurrentTimeInTimezone();
|
|
1400
1476
|
}
|
|
1401
1477
|
|
|
1402
|
-
//
|
|
1478
|
+
// Get active planets from configuration
|
|
1479
|
+
const activePlanets = getActivePlanets();
|
|
1480
|
+
|
|
1481
|
+
// Calculate current planet positions (Transit) for active planets only
|
|
1403
1482
|
const transitPlanets = {};
|
|
1404
1483
|
for (const [name, planetId] of Object.entries(planets)) {
|
|
1484
|
+
if (!activePlanets.includes(name)) {
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1405
1487
|
const data = getAstrologicalData(name, transitDate);
|
|
1406
1488
|
transitPlanets[name] = data.longitude;
|
|
1407
1489
|
}
|
|
1408
1490
|
|
|
1409
|
-
// Calculate birth planet positions (Radix)
|
|
1491
|
+
// Calculate birth planet positions (Radix) for active planets only
|
|
1410
1492
|
const birthPlanets = {};
|
|
1411
1493
|
for (const [name, planetId] of Object.entries(planets)) {
|
|
1494
|
+
if (!activePlanets.includes(name)) {
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1412
1497
|
const data = getAstrologicalData(name, birthData);
|
|
1413
1498
|
birthPlanets[name] = data.longitude;
|
|
1414
1499
|
}
|
|
@@ -1606,15 +1691,23 @@ function analyzeElementDistribution(dateComponents, useBirthData = false) {
|
|
|
1606
1691
|
|
|
1607
1692
|
const planetElements = {};
|
|
1608
1693
|
|
|
1609
|
-
//
|
|
1694
|
+
// Get active planets from configuration
|
|
1695
|
+
const activePlanets = getActivePlanets();
|
|
1696
|
+
|
|
1697
|
+
// Calculate elements of active planets only
|
|
1610
1698
|
for (const [name, planetId] of Object.entries(planets)) {
|
|
1699
|
+
// Skip planets that are not in the active list
|
|
1700
|
+
if (!activePlanets.includes(name)) {
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1611
1704
|
const data = getAstrologicalData(name, dateComponents);
|
|
1612
1705
|
elementCounts[data.element]++;
|
|
1613
1706
|
planetElements[name] = data.element;
|
|
1614
1707
|
}
|
|
1615
1708
|
|
|
1616
|
-
// Calculate total number of planets
|
|
1617
|
-
const totalPlanets =
|
|
1709
|
+
// Calculate total number of active planets
|
|
1710
|
+
const totalPlanets = activePlanets.length;
|
|
1618
1711
|
|
|
1619
1712
|
// Calculate percentages
|
|
1620
1713
|
const elementPercentages = {};
|
|
@@ -1859,7 +1952,7 @@ function parseFilterCriteria(filterString) {
|
|
|
1859
1952
|
const filterConditions = filterString.split(',').map(cond => cond.trim());
|
|
1860
1953
|
|
|
1861
1954
|
const criteria = [];
|
|
1862
|
-
const operatorRegex = /^(.+?)\s*(
|
|
1955
|
+
const operatorRegex = /^(.+?)\s*(>=|<=|!=|=|>|<)\s*(.+)$/;
|
|
1863
1956
|
|
|
1864
1957
|
for (const condition of filterConditions) {
|
|
1865
1958
|
const operatorMatch = condition.match(operatorRegex);
|
|
@@ -1872,23 +1965,33 @@ function parseFilterCriteria(filterString) {
|
|
|
1872
1965
|
continue;
|
|
1873
1966
|
}
|
|
1874
1967
|
|
|
1875
|
-
// Check for
|
|
1876
|
-
const
|
|
1877
|
-
if (
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1968
|
+
// Check for wildcard patterns in the value (new enhanced syntax)
|
|
1969
|
+
const wildcardMatch = condition.match(/^([^:]+):(.+)$/);
|
|
1970
|
+
if (wildcardMatch) {
|
|
1971
|
+
const column = wildcardMatch[1].trim();
|
|
1972
|
+
const value = wildcardMatch[2].trim();
|
|
1973
|
+
|
|
1974
|
+
// Remove * and ! from the value for contains/excludes operations
|
|
1975
|
+
const cleanValue = value.replace(/^[*!]+/, '').replace(/[*!]+$/, '');
|
|
1976
|
+
|
|
1977
|
+
// Check if value contains * or starts with !
|
|
1978
|
+
if (value.includes('*') || value.startsWith('!')) {
|
|
1979
|
+
// Use * operator for contains matching (remove * from value)
|
|
1980
|
+
// Use ! operator for excludes matching (remove ! from value)
|
|
1981
|
+
const operator = value.startsWith('!') ? '!' : '*';
|
|
1982
|
+
criteria.push({
|
|
1983
|
+
column: column,
|
|
1984
|
+
operator: operator,
|
|
1985
|
+
value: cleanValue
|
|
1986
|
+
});
|
|
1987
|
+
continue;
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
// Regular equality match
|
|
1888
1991
|
criteria.push({
|
|
1889
|
-
column:
|
|
1890
|
-
operator: '
|
|
1891
|
-
value:
|
|
1992
|
+
column: column,
|
|
1993
|
+
operator: '=',
|
|
1994
|
+
value: value
|
|
1892
1995
|
});
|
|
1893
1996
|
continue;
|
|
1894
1997
|
}
|
|
@@ -2074,6 +2177,13 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2074
2177
|
const trimmed = value != null ? value.toString().trim() : '';
|
|
2075
2178
|
return /^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
2076
2179
|
});
|
|
2180
|
+
|
|
2181
|
+
// Second pass alternative: look for DD.MM.YYYY format (optional time)
|
|
2182
|
+
const ddMmYyyyDotColumns = Object.keys(data).filter(key => {
|
|
2183
|
+
const value = data[key];
|
|
2184
|
+
const trimmed = value != null ? value.toString().trim() : '';
|
|
2185
|
+
return /^\d{2}\.\d{2}\.\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
2186
|
+
});
|
|
2077
2187
|
|
|
2078
2188
|
// Third pass: look for Unix timestamps (10 or 13 digits)
|
|
2079
2189
|
const unixTimestampColumns = Object.keys(data).filter(key => {
|
|
@@ -2093,8 +2203,8 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2093
2203
|
return moment(trimmed, moment.ISO_8601, true).isValid();
|
|
2094
2204
|
});
|
|
2095
2205
|
|
|
2096
|
-
// Prioritize columns: YYYY-MM-DD, DD/MM/YYYY, Unix timestamps, then ISO dates
|
|
2097
|
-
datetimeColumns.push(...yyyyMmDdColumns, ...ddMmYyyyColumns, ...unixTimestampColumns, ...isoDateColumns);
|
|
2206
|
+
// Prioritize columns: YYYY-MM-DD, DD/MM/YYYY, DD.MM.YYYY, Unix timestamps, then ISO dates
|
|
2207
|
+
datetimeColumns.push(...yyyyMmDdColumns, ...ddMmYyyyColumns, ...ddMmYyyyDotColumns, ...unixTimestampColumns, ...isoDateColumns);
|
|
2098
2208
|
|
|
2099
2209
|
if (datetimeColumns.length > 0) {
|
|
2100
2210
|
datetimeValue = data[datetimeColumns[0]];
|
|
@@ -2121,6 +2231,15 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2121
2231
|
if (datetime.isValid() && !hasTime) {
|
|
2122
2232
|
datetime = datetime.hour(12).minute(0);
|
|
2123
2233
|
}
|
|
2234
|
+
} else if (/^\d{2}\.\d{2}\.\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(datetimeValueTrimmed)) {
|
|
2235
|
+
// Handle as DD.MM.YYYY (optional time)
|
|
2236
|
+
const hasTime = /\d{1,2}:\d{2}/.test(datetimeValueTrimmed);
|
|
2237
|
+
// Replace dots with slashes to use the same parsing logic
|
|
2238
|
+
const datetimeValueWithSlashes = datetimeValueTrimmed.replace(/\./g, '/');
|
|
2239
|
+
datetime = moment(datetimeValueWithSlashes, ['DD/MM/YYYY HH:mm:ss', 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY'], true);
|
|
2240
|
+
if (datetime.isValid() && !hasTime) {
|
|
2241
|
+
datetime = datetime.hour(12).minute(0);
|
|
2242
|
+
}
|
|
2124
2243
|
} else {
|
|
2125
2244
|
// Handle as ISO-8601 date
|
|
2126
2245
|
datetime = moment(datetimeValueTrimmed);
|
|
@@ -2324,9 +2443,11 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2324
2443
|
const isYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
|
|
2325
2444
|
// Check for DD/MM/YYYY date format
|
|
2326
2445
|
const isDDMMYYYY = /^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
2446
|
+
// Check for DD.MM.YYYY date format
|
|
2447
|
+
const isDDMMYYYYDot = /^\d{2}\.\d{2}\.\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
2327
2448
|
// Check for Unix-Timestamp (number with 10 or 13 digits)
|
|
2328
2449
|
const isUnixTimestamp = /^\d{10,13}$/.test(trimmed);
|
|
2329
|
-
return isISO || isYYYYMMDD || isDDMMYYYY || isUnixTimestamp;
|
|
2450
|
+
return isISO || isYYYYMMDD || isDDMMYYYY || isDDMMYYYYDot || isUnixTimestamp;
|
|
2330
2451
|
});
|
|
2331
2452
|
|
|
2332
2453
|
if (datetimeColumns.length > 0) {
|
|
@@ -2354,6 +2475,15 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2354
2475
|
if (datetime.isValid() && !hasTime) {
|
|
2355
2476
|
datetime = datetime.hour(12).minute(0);
|
|
2356
2477
|
}
|
|
2478
|
+
} else if (/^\d{2}\.\d{2}\.\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(datetimeValueTrimmed)) {
|
|
2479
|
+
// Handle as DD.MM.YYYY (optional time)
|
|
2480
|
+
const hasTime = /\d{1,2}:\d{2}/.test(datetimeValueTrimmed);
|
|
2481
|
+
// Replace dots with slashes to use the same parsing logic
|
|
2482
|
+
const datetimeValueWithSlashes = datetimeValueTrimmed.replace(/\./g, '/');
|
|
2483
|
+
datetime = moment(datetimeValueWithSlashes, ['DD/MM/YYYY HH:mm:ss', 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY'], true);
|
|
2484
|
+
if (datetime.isValid() && !hasTime) {
|
|
2485
|
+
datetime = datetime.hour(12).minute(0);
|
|
2486
|
+
}
|
|
2357
2487
|
} else {
|
|
2358
2488
|
// Handle as ISO-8601 date
|
|
2359
2489
|
datetime = moment(datetimeValueTrimmed);
|
|
@@ -3053,7 +3183,10 @@ module.exports = {
|
|
|
3053
3183
|
calculateNextPlanetIngress,
|
|
3054
3184
|
calculateAstrologicalAngles,
|
|
3055
3185
|
longitudeToSignDegree,
|
|
3056
|
-
calculateTransitFrequency
|
|
3186
|
+
calculateTransitFrequency,
|
|
3187
|
+
// Filter functions for testing
|
|
3188
|
+
parseFilterCriteria,
|
|
3189
|
+
applyFilter
|
|
3057
3190
|
};
|
|
3058
3191
|
|
|
3059
3192
|
// Function to calculate how often a transit occurs between two planets
|
package/src/cli/cli.js
CHANGED
|
@@ -393,7 +393,7 @@ program
|
|
|
393
393
|
.option('--z <count>', 'Shows future aspects between two planets (Format: --z <count> planet1 aspectType planet2)')
|
|
394
394
|
.option('--csv <filepath>', 'Analyzes a CSV file with ISO-Datetime values or Unix timestamps')
|
|
395
395
|
.option('--date-col <column>', 'Specifies the column name containing date values (e.g., --date-col "Buchungsdatum")')
|
|
396
|
-
.option('--filter <column:value>', 'Filters CSV data by column:value (e.g., --filter "Item:coffee")')
|
|
396
|
+
.option('--filter <column:value>', 'Filters CSV data by column:value (e.g., --filter "Item:coffee", "Item:*coffee*", "Item:!bad*")')
|
|
397
397
|
.option('--title <title>', 'Title for the chart image (generates PNG image when provided)')
|
|
398
398
|
.option('--in [count]', 'Shows next planet ingress (entering new sign). Optional count for multiple ingresses')
|
|
399
399
|
.option('--wiki <occupation>', 'Fetches people from Wikidata by occupation and checks for specific aspects (Format: planet1 aspectType planet2 --wiki <occupation> [limit])')
|
|
@@ -401,6 +401,8 @@ program
|
|
|
401
401
|
.option('--gui-port <port>', 'Specify custom port for GUI server')
|
|
402
402
|
.option('--gui-verbose', 'Enable verbose logging for GUI server')
|
|
403
403
|
.option('--o', 'Shows how often this transit occurs in the sky (frequency calculation)')
|
|
404
|
+
.option('--planets <list>', 'Select active planets and asteroids (comma-separated, e.g., "Sun,Moon,Mercury,Venus,Hygiea")')
|
|
405
|
+
.option('--active-planets', 'Show the currently active planets')
|
|
404
406
|
.description('Shows astrological data for a planet')
|
|
405
407
|
.action(async (planetArg, planet2Arg, options) => {
|
|
406
408
|
// If planet2Arg is an object, it contains the options (commander behavior)
|
|
@@ -875,46 +877,68 @@ program
|
|
|
875
877
|
const houses = await calculateHouses(birthJulianDay, getHouseSystemCode(houseSystem), true);
|
|
876
878
|
const transitDateDisplay = transitDate || getCurrentTimeInTimezone();
|
|
877
879
|
|
|
878
|
-
//
|
|
880
|
+
// Collect all transit aspects first
|
|
881
|
+
const allAspects = [];
|
|
879
882
|
for (const [planetName] of Object.entries(planets)) {
|
|
880
883
|
const transitPlanetData = getAstrologicalData(planetName, transitDateDisplay);
|
|
881
884
|
const transitHouse = getPlanetHouse(parseFloat(transitPlanetData.degreeInSign) + (signs.indexOf(transitPlanetData.sign) * 30), houses.house);
|
|
882
885
|
const transitAspectsData = calculatePersonalTransitAspects(transitDateDisplay, birthData, planetName);
|
|
883
886
|
const transitAspects = transitAspectsData ? transitAspectsData.aspects : [];
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
'Trine': 120,
|
|
895
|
-
'Sextile': 60
|
|
896
|
-
};
|
|
897
|
-
const targetAngle = aspectAngles[aspect.type];
|
|
898
|
-
const phase = determineAspectPhase(planetName, aspect.birthPlanet, transitDateDisplay, aspect.type, targetAngle);
|
|
899
|
-
|
|
900
|
-
// Exact time calculation removed
|
|
901
|
-
const dateTimeStr = '-';
|
|
902
|
-
|
|
903
|
-
const transitPlanetFormatted = planetName.charAt(0).toUpperCase() + planetName.slice(1);
|
|
904
|
-
const aspectTypeFormatted = aspect.type.padEnd(11);
|
|
905
|
-
const aspectPlanetFormatted = aspect.birthPlanet.charAt(0).toUpperCase() + aspect.birthPlanet.slice(1);
|
|
906
|
-
const orbFormatted = aspect.orb.padEnd(4);
|
|
907
|
-
const transitPosFormatted = `${transitPlanetData.sign} ${transitPlanetData.degreeInSign}°`;
|
|
908
|
-
const transitHouseFormatted = transitHouse.toString().padEnd(11);
|
|
909
|
-
const birthHouseFormatted = birthHouse.toString().padEnd(11);
|
|
910
|
-
const phaseFormatted = phase === 'separativ' ? 'separativ'.padEnd(11) : phase.padEnd(11);
|
|
911
|
-
const dateTimeFormatted = dateTimeStr.padEnd(22);
|
|
912
|
-
|
|
913
|
-
console.log(`| ${transitPlanetFormatted.padEnd(10)} | ${aspectTypeFormatted} | ${aspectPlanetFormatted.padEnd(10)} | ${orbFormatted}° | ${transitPosFormatted.padEnd(16)} | ${transitHouseFormatted} | ${birthHouseFormatted} | ${phaseFormatted} |`);
|
|
887
|
+
|
|
888
|
+
// Add planet data to each aspect for later display
|
|
889
|
+
transitAspects.forEach(aspect => {
|
|
890
|
+
allAspects.push({
|
|
891
|
+
planetName: planetName,
|
|
892
|
+
transitPlanetData: transitPlanetData,
|
|
893
|
+
transitHouse: transitHouse,
|
|
894
|
+
aspect: aspect,
|
|
895
|
+
birthPlanetData: getAstrologicalData(aspect.birthPlanet, birthData),
|
|
896
|
+
birthHouse: getPlanetHouse(parseFloat(getAstrologicalData(aspect.birthPlanet, birthData).degreeInSign) + (signs.indexOf(getAstrologicalData(aspect.birthPlanet, birthData).sign) * 30), houses.house)
|
|
914
897
|
});
|
|
915
|
-
}
|
|
898
|
+
});
|
|
916
899
|
}
|
|
917
900
|
|
|
901
|
+
// Sort all aspects by orb (smallest orb first)
|
|
902
|
+
allAspects.sort((a, b) => {
|
|
903
|
+
return parseFloat(a.aspect.orb) - parseFloat(b.aspect.orb);
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
// Display sorted aspects
|
|
907
|
+
allAspects.forEach(aspectData => {
|
|
908
|
+
const planetName = aspectData.planetName;
|
|
909
|
+
const transitPlanetData = aspectData.transitPlanetData;
|
|
910
|
+
const transitHouse = aspectData.transitHouse;
|
|
911
|
+
const aspect = aspectData.aspect;
|
|
912
|
+
const birthPlanetData = aspectData.birthPlanetData;
|
|
913
|
+
const birthHouse = aspectData.birthHouse;
|
|
914
|
+
|
|
915
|
+
// Determine aspect phase
|
|
916
|
+
const aspectAngles = {
|
|
917
|
+
'Conjunction': 0,
|
|
918
|
+
'Opposition': 180,
|
|
919
|
+
'Square': 90,
|
|
920
|
+
'Trine': 120,
|
|
921
|
+
'Sextile': 60
|
|
922
|
+
};
|
|
923
|
+
const targetAngle = aspectAngles[aspect.type];
|
|
924
|
+
const phase = determineAspectPhase(planetName, aspect.birthPlanet, transitDateDisplay, aspect.type, targetAngle);
|
|
925
|
+
|
|
926
|
+
// Exact time calculation removed
|
|
927
|
+
const dateTimeStr = '-';
|
|
928
|
+
|
|
929
|
+
const transitPlanetFormatted = planetName.charAt(0).toUpperCase() + planetName.slice(1);
|
|
930
|
+
const aspectTypeFormatted = aspect.type.padEnd(11);
|
|
931
|
+
const aspectPlanetFormatted = aspect.birthPlanet.charAt(0).toUpperCase() + aspect.birthPlanet.slice(1);
|
|
932
|
+
const orbFormatted = aspect.orb.padEnd(4);
|
|
933
|
+
const transitPosFormatted = `${transitPlanetData.sign} ${transitPlanetData.degreeInSign}°`;
|
|
934
|
+
const transitHouseFormatted = transitHouse.toString().padEnd(11);
|
|
935
|
+
const birthHouseFormatted = birthHouse.toString().padEnd(11);
|
|
936
|
+
const phaseFormatted = phase === 'separativ' ? 'separativ'.padEnd(11) : phase.padEnd(11);
|
|
937
|
+
const dateTimeFormatted = dateTimeStr.padEnd(22);
|
|
938
|
+
|
|
939
|
+
console.log(`| ${transitPlanetFormatted.padEnd(10)} | ${aspectTypeFormatted} | ${aspectPlanetFormatted.padEnd(10)} | ${orbFormatted}° | ${transitPosFormatted.padEnd(16)} | ${transitHouseFormatted} | ${birthHouseFormatted} | ${phaseFormatted} |`);
|
|
940
|
+
});
|
|
941
|
+
|
|
918
942
|
console.log('================================================================================');
|
|
919
943
|
console.log(`\nAnalyse-Datum: ${transitDateDisplay.day}.${transitDateDisplay.month}.${transitDateDisplay.year}`);
|
|
920
944
|
console.log(`Geburtsdatum: ${birthData.day}.${birthData.month}.${birthData.year} ${birthData.hour}:${birthData.minute.toString().padStart(2, '0')}`);
|
|
@@ -1207,6 +1231,21 @@ program
|
|
|
1207
1231
|
return;
|
|
1208
1232
|
}
|
|
1209
1233
|
|
|
1234
|
+
// Set active planets if --planets option is specified (no planet required)
|
|
1235
|
+
if (options.planets) {
|
|
1236
|
+
const { setActivePlanets } = require('../config/configService');
|
|
1237
|
+
setActivePlanets(options.planets);
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
// Show active planets if --active-planets option is specified (no planet required)
|
|
1242
|
+
if (options.activePlanets) {
|
|
1243
|
+
const { getActivePlanets } = require('../config/configService');
|
|
1244
|
+
const activePlanets = getActivePlanets();
|
|
1245
|
+
console.log('Active planets:', activePlanets.join(', '));
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1210
1249
|
// Set AI model if --ai option is specified (no planet required)
|
|
1211
1250
|
if (options.ai) {
|
|
1212
1251
|
setAIModel(options.ai);
|
package/src/cli/cliService.js
CHANGED
|
@@ -160,13 +160,15 @@ class CLIService {
|
|
|
160
160
|
.option('--v <count>', 'Shows past aspects between two planets (Format: --v <count> planet1 aspectType planet2)')
|
|
161
161
|
.option('--z <count>', 'Shows future aspects between two planets (Format: --z <count> planet1 aspectType planet2)')
|
|
162
162
|
.option('--csv <filepath>', 'Analyzes a CSV file with ISO-Datetime values or Unix timestamps')
|
|
163
|
-
.option('--filter <column:value>', 'Filters CSV data by column:value (e.g., --filter "Item:coffee")')
|
|
163
|
+
.option('--filter <column:value>', 'Filters CSV data by column:value (e.g., --filter "Item:coffee", "Item:*coffee*", "Item:!bad*")')
|
|
164
164
|
.option('--title <title>', 'Title for the chart image (generates PNG image when provided)')
|
|
165
165
|
.option('--in [count]', 'Shows next planet ingress (entering new sign). Optional count for multiple ingresses')
|
|
166
166
|
.option('--wiki <occupation>', 'Fetches people from Wikidata by occupation and checks for specific aspects (Format: planet1 aspectType planet2 --wiki <occupation> [limit])')
|
|
167
167
|
.option('--gui', 'Launches the web interface for command history (port 37421)')
|
|
168
168
|
.option('--gui-port <port>', 'Specify custom port for GUI server')
|
|
169
169
|
.option('--o', 'Shows how often this transit occurs in the sky (frequency calculation)')
|
|
170
|
+
.option('--planets <list>', 'Select active planets and asteroids (comma-separated, e.g., "Sun,Moon,Mercury,Venus,Hygiea")')
|
|
171
|
+
.option('--active-planets', 'Show the currently active planets')
|
|
170
172
|
.description('Shows astrological data for a planet')
|
|
171
173
|
.action(this.handleCommand.bind(this));
|
|
172
174
|
}
|
|
@@ -652,6 +654,21 @@ class CLIService {
|
|
|
652
654
|
return { success: true, output: output.join('\n') };
|
|
653
655
|
}
|
|
654
656
|
|
|
657
|
+
// Set active planets if --planets option is specified (no planet required)
|
|
658
|
+
if (actualOptions.planets) {
|
|
659
|
+
const { setActivePlanets } = require('../config/configService');
|
|
660
|
+
setActivePlanets(actualOptions.planets, userId);
|
|
661
|
+
return { success: true, output: output.join('\n') };
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// Show active planets if --active-planets option is specified (no planet required)
|
|
665
|
+
if (actualOptions.activePlanets) {
|
|
666
|
+
const { getActivePlanets } = require('../config/configService');
|
|
667
|
+
const activePlanets = getActivePlanets(userId);
|
|
668
|
+
console.log('Active planets:', activePlanets.join(', '));
|
|
669
|
+
return { success: true, output: output.join('\n') };
|
|
670
|
+
}
|
|
671
|
+
|
|
655
672
|
// Delete person if --delete-person option is specified (no planet required)
|
|
656
673
|
if (actualOptions.deletePerson) {
|
|
657
674
|
deletePerson(actualOptions.deletePerson, userId);
|
|
@@ -456,6 +456,52 @@ function setAIModel(modelName, userId = null) {
|
|
|
456
456
|
return false;
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
// Function to set active planets
|
|
460
|
+
function setActivePlanets(planetList, userId = null) {
|
|
461
|
+
const config = loadConfig(userId) || {
|
|
462
|
+
currentLocation: null,
|
|
463
|
+
birthData: null,
|
|
464
|
+
setupDate: new Date().toISOString()
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
// Get available planets for validation
|
|
468
|
+
const { planets } = require('../astrology/astrologyConstants');
|
|
469
|
+
const availablePlanets = Object.keys(planets);
|
|
470
|
+
|
|
471
|
+
// Convert comma-separated string to array and normalize planet names
|
|
472
|
+
const planetsArray = planetList.split(',').map(p => p.trim().toLowerCase());
|
|
473
|
+
|
|
474
|
+
// Validate planet names
|
|
475
|
+
const invalidPlanets = planetsArray.filter(p => !availablePlanets.includes(p));
|
|
476
|
+
if (invalidPlanets.length > 0) {
|
|
477
|
+
console.error(`Error: Invalid planet names: ${invalidPlanets.join(', ')}`);
|
|
478
|
+
console.error(`Available planets: ${availablePlanets.join(', ')}`);
|
|
479
|
+
return false;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
config.activePlanets = planetsArray;
|
|
483
|
+
|
|
484
|
+
if (saveConfig(config, userId)) {
|
|
485
|
+
console.log(`Active planets set: ${planetsArray.join(', ')}`);
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Function to get active planets (with defaults)
|
|
492
|
+
function getActivePlanets(userId = null) {
|
|
493
|
+
const config = loadConfig(userId);
|
|
494
|
+
|
|
495
|
+
// Default active planets (traditional planets + Chiron, excluding other asteroids)
|
|
496
|
+
const defaultActivePlanets = ['sun', 'moon', 'mercury', 'venus', 'mars', 'jupiter', 'saturn', 'uranus', 'neptune', 'pluto', 'chiron'];
|
|
497
|
+
|
|
498
|
+
if (config && config.activePlanets && Array.isArray(config.activePlanets)) {
|
|
499
|
+
return config.activePlanets;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return defaultActivePlanets;
|
|
503
|
+
}
|
|
504
|
+
|
|
459
505
|
// Function to set a custom system prompt
|
|
460
506
|
function setSystemPrompt(prompt, userId = null) {
|
|
461
507
|
const config = loadConfig(userId) || {
|
|
@@ -903,5 +949,8 @@ module.exports = {
|
|
|
903
949
|
showNotes,
|
|
904
950
|
searchNotesBySituation,
|
|
905
951
|
// Add helper functions for user-specific operations
|
|
906
|
-
getConfigPath
|
|
952
|
+
getConfigPath,
|
|
953
|
+
// Planet selection functions
|
|
954
|
+
setActivePlanets,
|
|
955
|
+
getActivePlanets
|
|
907
956
|
};
|