klio 1.5.5 → 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 +23 -0
- package/ephe/se00010s.se1 +0 -0
- package/package.json +1 -1
- package/src/astrology/astrologyConstants.js +29 -0
- package/src/astrology/astrologyService.js +246 -45
- package/src/cli/cli.js +79 -35
- 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:
|
|
@@ -118,8 +137,12 @@ It's possible to analyze a csv with a column of either ISO date time or unix tim
|
|
|
118
137
|
|
|
119
138
|
- **Show house and sign distribution of the datetime column**: `klio [planet] --csv <file-path>`
|
|
120
139
|
- **Show aspect type distribution between two planets:** `klio [planet1] [planet2] --csv <file-path> --a`
|
|
140
|
+
- **Specify date column name:** `klio [planet] --csv <file-path> --date-col "Buchungsdatum"` (e.g., `--date-col "Date"`)
|
|
121
141
|
- **Filter CSV data by column value:** `klio [planet] --csv <file-path> --filter "column:value"` (e.g., `--filter "Item:coffee"`)
|
|
142
|
+
- **Filter CSV data by contains operator:** `klio [planet] --csv <file-path> --filter "column*text"` (e.g., `--filter "Buchungstext*Gutschrift"`)
|
|
143
|
+
- **Filter CSV data by excludes operator:** `klio [planet] --csv <file-path> --filter "column!text"` (e.g., `--filter "Buchungstext!Gutschrift"`)
|
|
122
144
|
- **Filter CSV data by multiple conditions:** `klio [planet] --csv <file-path> --filter "column1:value1,column2:value2"` (e.g., `--filter "FTR:H,HomeTeam:Liverpool"`)
|
|
145
|
+
- **Filter CSV data with comparison operators:** `klio [planet] --csv <file-path> --filter "column>value"` (e.g., `--filter "Amount>100"`)
|
|
123
146
|
- **Creating a bar chart**: Create a bar chart and save the image to the downloads' folder. The image shows the aspect distribution of your csv datetime values: `klio moon sun --csv /home/user/Downloads/coffee.csv --filter "Item:cookie" --a --title "Eaten cookies during sun-moon aspects"`
|
|
124
147
|
|
|
125
148
|
- The command also returns a Chi-Square.
|
|
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 = {};
|
|
@@ -1872,6 +1965,37 @@ function parseFilterCriteria(filterString) {
|
|
|
1872
1965
|
continue;
|
|
1873
1966
|
}
|
|
1874
1967
|
|
|
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
|
|
1991
|
+
criteria.push({
|
|
1992
|
+
column: column,
|
|
1993
|
+
operator: '=',
|
|
1994
|
+
value: value
|
|
1995
|
+
});
|
|
1996
|
+
continue;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1875
1999
|
const parts = condition.split(':');
|
|
1876
2000
|
if (parts.length !== 2) {
|
|
1877
2001
|
console.warn(`Invalid filter format. Expected "column:value" or "column > value" but got "${condition}"`);
|
|
@@ -1919,6 +2043,12 @@ function applyFilter(records, filterCriteria) {
|
|
|
1919
2043
|
return bothNumeric ? recordNum < valueNum : false;
|
|
1920
2044
|
case '<=':
|
|
1921
2045
|
return bothNumeric ? recordNum <= valueNum : false;
|
|
2046
|
+
case '*':
|
|
2047
|
+
// Contains operator - check if record value contains the search string
|
|
2048
|
+
return recordValueStr.includes(valueStr);
|
|
2049
|
+
case '!':
|
|
2050
|
+
// Excludes operator - check if record value does NOT contain the search string
|
|
2051
|
+
return !recordValueStr.includes(valueStr);
|
|
1922
2052
|
default:
|
|
1923
2053
|
return false;
|
|
1924
2054
|
}
|
|
@@ -1941,7 +2071,7 @@ function applyFilter(records, filterCriteria) {
|
|
|
1941
2071
|
}
|
|
1942
2072
|
}
|
|
1943
2073
|
|
|
1944
|
-
function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'koch', analyzeAspects = false, partnerPlanet = null, filterCriteria = null) {
|
|
2074
|
+
function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'koch', analyzeAspects = false, partnerPlanet = null, filterCriteria = null, dateColumn = null) {
|
|
1945
2075
|
return new Promise(async (resolve, reject) => {
|
|
1946
2076
|
const results = [];
|
|
1947
2077
|
let pendingOperations = 0;
|
|
@@ -2014,8 +2144,25 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2014
2144
|
|
|
2015
2145
|
// Process each row
|
|
2016
2146
|
for (const data of filteredRecords) {
|
|
2017
|
-
//
|
|
2018
|
-
|
|
2147
|
+
// Use specified date column if provided, otherwise auto-detect
|
|
2148
|
+
let datetimeColumns = [];
|
|
2149
|
+
let datetimeValue = null;
|
|
2150
|
+
|
|
2151
|
+
if (dateColumn) {
|
|
2152
|
+
// Use the specified date column
|
|
2153
|
+
if (data.hasOwnProperty(dateColumn)) {
|
|
2154
|
+
datetimeValue = data[dateColumn];
|
|
2155
|
+
datetimeColumns = [dateColumn];
|
|
2156
|
+
} else {
|
|
2157
|
+
console.warn(`⚠️ Specified date column "${dateColumn}" not found in CSV. Trying auto-detection...`);
|
|
2158
|
+
// Fall back to auto-detection
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
// If no date column was specified or it wasn't found, auto-detect
|
|
2163
|
+
if (datetimeColumns.length === 0) {
|
|
2164
|
+
// Look for a column with datetime values, prioritizing specific date formats
|
|
2165
|
+
datetimeColumns = [];
|
|
2019
2166
|
|
|
2020
2167
|
// First pass: look for YYYY-MM-DD format (most specific)
|
|
2021
2168
|
const yyyyMmDdColumns = Object.keys(data).filter(key => {
|
|
@@ -2030,6 +2177,13 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2030
2177
|
const trimmed = value != null ? value.toString().trim() : '';
|
|
2031
2178
|
return /^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
2032
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
|
+
});
|
|
2033
2187
|
|
|
2034
2188
|
// Third pass: look for Unix timestamps (10 or 13 digits)
|
|
2035
2189
|
const unixTimestampColumns = Object.keys(data).filter(key => {
|
|
@@ -2049,11 +2203,15 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2049
2203
|
return moment(trimmed, moment.ISO_8601, true).isValid();
|
|
2050
2204
|
});
|
|
2051
2205
|
|
|
2052
|
-
|
|
2053
|
-
|
|
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);
|
|
2208
|
+
|
|
2209
|
+
if (datetimeColumns.length > 0) {
|
|
2210
|
+
datetimeValue = data[datetimeColumns[0]];
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2054
2213
|
|
|
2055
|
-
if (datetimeColumns.length > 0) {
|
|
2056
|
-
const datetimeValue = data[datetimeColumns[0]];
|
|
2214
|
+
if (datetimeColumns.length > 0 && datetimeValue !== null) {
|
|
2057
2215
|
const datetimeValueTrimmed = datetimeValue != null ? datetimeValue.toString().trim() : '';
|
|
2058
2216
|
let datetime;
|
|
2059
2217
|
|
|
@@ -2073,6 +2231,15 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2073
2231
|
if (datetime.isValid() && !hasTime) {
|
|
2074
2232
|
datetime = datetime.hour(12).minute(0);
|
|
2075
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
|
+
}
|
|
2076
2243
|
} else {
|
|
2077
2244
|
// Handle as ISO-8601 date
|
|
2078
2245
|
datetime = moment(datetimeValueTrimmed);
|
|
@@ -2249,9 +2416,25 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2249
2416
|
|
|
2250
2417
|
// Process each record
|
|
2251
2418
|
for (const data of filteredRecords) {
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2419
|
+
// Use specified date column if provided, otherwise auto-detect
|
|
2420
|
+
let datetimeColumns = [];
|
|
2421
|
+
let datetimeValue = null;
|
|
2422
|
+
|
|
2423
|
+
if (dateColumn) {
|
|
2424
|
+
// Use the specified date column
|
|
2425
|
+
if (data.hasOwnProperty(dateColumn)) {
|
|
2426
|
+
datetimeValue = data[dateColumn];
|
|
2427
|
+
datetimeColumns = [dateColumn];
|
|
2428
|
+
} else {
|
|
2429
|
+
console.warn(`⚠️ Specified date column "${dateColumn}" not found in CSV. Trying auto-detection...`);
|
|
2430
|
+
// Fall back to auto-detection
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
// If no date column was specified or it wasn't found, auto-detect
|
|
2435
|
+
if (datetimeColumns.length === 0) {
|
|
2436
|
+
// Look for a column with ISO-Datetime values, YYYY-MM-DD dates, DD/MM/YYYY dates (optional time), or Unix-Timestamps
|
|
2437
|
+
datetimeColumns = Object.keys(data).filter(key => {
|
|
2255
2438
|
const value = data[key];
|
|
2256
2439
|
const trimmed = value != null ? value.toString().trim() : '';
|
|
2257
2440
|
// Check for ISO-8601 date
|
|
@@ -2260,13 +2443,19 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2260
2443
|
const isYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
|
|
2261
2444
|
// Check for DD/MM/YYYY date format
|
|
2262
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);
|
|
2263
2448
|
// Check for Unix-Timestamp (number with 10 or 13 digits)
|
|
2264
2449
|
const isUnixTimestamp = /^\d{10,13}$/.test(trimmed);
|
|
2265
|
-
return isISO || isYYYYMMDD || isDDMMYYYY || isUnixTimestamp;
|
|
2450
|
+
return isISO || isYYYYMMDD || isDDMMYYYY || isDDMMYYYYDot || isUnixTimestamp;
|
|
2266
2451
|
});
|
|
2267
2452
|
|
|
2268
|
-
|
|
2269
|
-
|
|
2453
|
+
if (datetimeColumns.length > 0) {
|
|
2454
|
+
datetimeValue = data[datetimeColumns[0]];
|
|
2455
|
+
}
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
if (datetimeColumns.length > 0 && datetimeValue !== null) {
|
|
2270
2459
|
const datetimeValueTrimmed = datetimeValue != null ? datetimeValue.toString().trim() : '';
|
|
2271
2460
|
let datetime;
|
|
2272
2461
|
|
|
@@ -2286,6 +2475,15 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2286
2475
|
if (datetime.isValid() && !hasTime) {
|
|
2287
2476
|
datetime = datetime.hour(12).minute(0);
|
|
2288
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
|
+
}
|
|
2289
2487
|
} else {
|
|
2290
2488
|
// Handle as ISO-8601 date
|
|
2291
2489
|
datetime = moment(datetimeValueTrimmed);
|
|
@@ -2985,7 +3183,10 @@ module.exports = {
|
|
|
2985
3183
|
calculateNextPlanetIngress,
|
|
2986
3184
|
calculateAstrologicalAngles,
|
|
2987
3185
|
longitudeToSignDegree,
|
|
2988
|
-
calculateTransitFrequency
|
|
3186
|
+
calculateTransitFrequency,
|
|
3187
|
+
// Filter functions for testing
|
|
3188
|
+
parseFilterCriteria,
|
|
3189
|
+
applyFilter
|
|
2989
3190
|
};
|
|
2990
3191
|
|
|
2991
3192
|
// Function to calculate how often a transit occurs between two planets
|
package/src/cli/cli.js
CHANGED
|
@@ -392,7 +392,8 @@ program
|
|
|
392
392
|
.option('--v <count>', 'Shows past aspects between two planets (Format: --v <count> planet1 aspectType planet2)')
|
|
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
|
-
.option('--
|
|
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", "Item:*coffee*", "Item:!bad*")')
|
|
396
397
|
.option('--title <title>', 'Title for the chart image (generates PNG image when provided)')
|
|
397
398
|
.option('--in [count]', 'Shows next planet ingress (entering new sign). Optional count for multiple ingresses')
|
|
398
399
|
.option('--wiki <occupation>', 'Fetches people from Wikidata by occupation and checks for specific aspects (Format: planet1 aspectType planet2 --wiki <occupation> [limit])')
|
|
@@ -400,6 +401,8 @@ program
|
|
|
400
401
|
.option('--gui-port <port>', 'Specify custom port for GUI server')
|
|
401
402
|
.option('--gui-verbose', 'Enable verbose logging for GUI server')
|
|
402
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')
|
|
403
406
|
.description('Shows astrological data for a planet')
|
|
404
407
|
.action(async (planetArg, planet2Arg, options) => {
|
|
405
408
|
// If planet2Arg is an object, it contains the options (commander behavior)
|
|
@@ -874,46 +877,68 @@ program
|
|
|
874
877
|
const houses = await calculateHouses(birthJulianDay, getHouseSystemCode(houseSystem), true);
|
|
875
878
|
const transitDateDisplay = transitDate || getCurrentTimeInTimezone();
|
|
876
879
|
|
|
877
|
-
//
|
|
880
|
+
// Collect all transit aspects first
|
|
881
|
+
const allAspects = [];
|
|
878
882
|
for (const [planetName] of Object.entries(planets)) {
|
|
879
883
|
const transitPlanetData = getAstrologicalData(planetName, transitDateDisplay);
|
|
880
884
|
const transitHouse = getPlanetHouse(parseFloat(transitPlanetData.degreeInSign) + (signs.indexOf(transitPlanetData.sign) * 30), houses.house);
|
|
881
885
|
const transitAspectsData = calculatePersonalTransitAspects(transitDateDisplay, birthData, planetName);
|
|
882
886
|
const transitAspects = transitAspectsData ? transitAspectsData.aspects : [];
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
'Trine': 120,
|
|
894
|
-
'Sextile': 60
|
|
895
|
-
};
|
|
896
|
-
const targetAngle = aspectAngles[aspect.type];
|
|
897
|
-
const phase = determineAspectPhase(planetName, aspect.birthPlanet, transitDateDisplay, aspect.type, targetAngle);
|
|
898
|
-
|
|
899
|
-
// Exact time calculation removed
|
|
900
|
-
const dateTimeStr = '-';
|
|
901
|
-
|
|
902
|
-
const transitPlanetFormatted = planetName.charAt(0).toUpperCase() + planetName.slice(1);
|
|
903
|
-
const aspectTypeFormatted = aspect.type.padEnd(11);
|
|
904
|
-
const aspectPlanetFormatted = aspect.birthPlanet.charAt(0).toUpperCase() + aspect.birthPlanet.slice(1);
|
|
905
|
-
const orbFormatted = aspect.orb.padEnd(4);
|
|
906
|
-
const transitPosFormatted = `${transitPlanetData.sign} ${transitPlanetData.degreeInSign}°`;
|
|
907
|
-
const transitHouseFormatted = transitHouse.toString().padEnd(11);
|
|
908
|
-
const birthHouseFormatted = birthHouse.toString().padEnd(11);
|
|
909
|
-
const phaseFormatted = phase === 'separativ' ? 'separativ'.padEnd(11) : phase.padEnd(11);
|
|
910
|
-
const dateTimeFormatted = dateTimeStr.padEnd(22);
|
|
911
|
-
|
|
912
|
-
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)
|
|
913
897
|
});
|
|
914
|
-
}
|
|
898
|
+
});
|
|
915
899
|
}
|
|
916
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
|
+
|
|
917
942
|
console.log('================================================================================');
|
|
918
943
|
console.log(`\nAnalyse-Datum: ${transitDateDisplay.day}.${transitDateDisplay.month}.${transitDateDisplay.year}`);
|
|
919
944
|
console.log(`Geburtsdatum: ${birthData.day}.${birthData.month}.${birthData.year} ${birthData.hour}:${birthData.minute.toString().padStart(2, '0')}`);
|
|
@@ -1206,6 +1231,21 @@ program
|
|
|
1206
1231
|
return;
|
|
1207
1232
|
}
|
|
1208
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
|
+
|
|
1209
1249
|
// Set AI model if --ai option is specified (no planet required)
|
|
1210
1250
|
if (options.ai) {
|
|
1211
1251
|
setAIModel(options.ai);
|
|
@@ -1742,7 +1782,9 @@ program
|
|
|
1742
1782
|
const analyzeAspects = actualOptions.a || false;
|
|
1743
1783
|
const filterCriteria = actualOptions.filter;
|
|
1744
1784
|
|
|
1745
|
-
|
|
1785
|
+
const dateColumn = actualOptions.dateCol;
|
|
1786
|
+
|
|
1787
|
+
analyzeCSVWithDatetime(actualOptions.csv, planet, houseSystem, analyzeAspects, planet2, filterCriteria, dateColumn)
|
|
1746
1788
|
.then(({ results, houseDistribution, signDistribution, aspectStatistics }) => {
|
|
1747
1789
|
if (results.length === 0) {
|
|
1748
1790
|
console.log('No valid ISO-Datetime values found in the CSV file.');
|
|
@@ -2785,7 +2827,9 @@ program
|
|
|
2785
2827
|
const analyzeAspects = actualOptions.a || false;
|
|
2786
2828
|
const filterCriteria = actualOptions.filter;
|
|
2787
2829
|
|
|
2788
|
-
|
|
2830
|
+
const dateColumn = actualOptions.dateCol;
|
|
2831
|
+
|
|
2832
|
+
analyzeCSVWithDatetime(actualOptions.csv, planet, houseSystem, analyzeAspects, planet2, filterCriteria, dateColumn)
|
|
2789
2833
|
.then(({ results, houseDistribution, signDistribution, aspectStatistics }) => {
|
|
2790
2834
|
if (results.length === 0) {
|
|
2791
2835
|
console.log('No valid ISO-Datetime values found in the CSV file.');
|
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
|
};
|