klio 1.5.2 → 1.5.4
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 +70 -3
- package/package.json +1 -1
- package/src/astrology/astrologyService.js +189 -59
- package/src/cli/cli.js +211 -1
- package/src/cli/cliService.js +1 -1
- package/src/config/configService.js +133 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Klio
|
|
1
|
+
# Klio
|
|
2
2
|
|
|
3
3
|
A command-line tool for astrological calculations, health analysis, and personalized astrology insights.
|
|
4
4
|
|
|
@@ -144,8 +144,6 @@ Then, instead using `--i` for the commands from above you can use `--wp <id>` or
|
|
|
144
144
|
- **Transits**: `klio --tr` - Shows personal transits based on birth data. Combine with `--wp <id or --i>` and optional `--d <"DD.MM.YYYY HH:MM>`
|
|
145
145
|
- **Transit Houses**: `klio --s --tra --i`
|
|
146
146
|
|
|
147
|
-
|
|
148
|
-
|
|
149
147
|
### Past and Future Aspects
|
|
150
148
|
|
|
151
149
|
- **Past aspects**: `klio --v <count> [planet1] [aspect-type] [planet2]` - Shows past aspects (Format: --v <count> planet1 aspectType planet2) Available aspect types: c, o, s, t, se (conjunction, opposition, square, trine, sextile)
|
|
@@ -187,6 +185,75 @@ When were the files created on a system folder and in which house was the planet
|
|
|
187
185
|
- **House filters**: `--h1` through `--h12` - Filters files with planet in specific houses
|
|
188
186
|
|
|
189
187
|
|
|
188
|
+
## Note Taking
|
|
189
|
+
|
|
190
|
+
Klio includes a note-taking system:
|
|
191
|
+
|
|
192
|
+
### Basic Note Taking
|
|
193
|
+
|
|
194
|
+
- **Add a note to any astrological situation**: `klio [planet] --note "your observation"`
|
|
195
|
+
- Example: `klio saturn --note "Boundaries, Limitations..."`
|
|
196
|
+
- Example: `klio moon --note "Full moon in Scorpio"`
|
|
197
|
+
|
|
198
|
+
- **List all notes**: `klio --notes` - Shows all saved notes with timestamps and astrological context
|
|
199
|
+
|
|
200
|
+
- **Search notes**: `klio --search-notes "keyword"` - Searches notes by astrological situation
|
|
201
|
+
- Example: `klio --search-notes "house"` - Finds all house-related notes
|
|
202
|
+
- Example: `klio --search-notes "saturn"` - Finds all Saturn-related notes
|
|
203
|
+
|
|
204
|
+
### House Notation
|
|
205
|
+
|
|
206
|
+
- **House-specific notes**: `klio h[n] --note "house observation"`
|
|
207
|
+
- Example: `klio h[4] --note "4th house observation - family matters"`
|
|
208
|
+
- Example: `klio h[10] --note "10th house - career focus"`
|
|
209
|
+
|
|
210
|
+
- **Planet in house notes**: `klio h[n] [planet] --note "planet in house observation"`
|
|
211
|
+
- Example: `klio h[4] moon --note "Moon in 4th house - emotional security"`
|
|
212
|
+
- Example: `klio h[7] venus --note "Venus in 7th house - relationship harmony"`
|
|
213
|
+
|
|
214
|
+
### Aspect Notation
|
|
215
|
+
|
|
216
|
+
- **Aspect-specific notes**: `klio [planet1] [planet2] --note "aspect observation" --aspect [type]`
|
|
217
|
+
- Example: `klio saturn moon --note "Saturn conjunction Moon - emotional challenges" --aspect c`
|
|
218
|
+
- Example: `klio venus mars --note "Venus square Mars - relationship tension" --aspect s`
|
|
219
|
+
- Example: `klio jupiter uranus --note "Jupiter trine Uranus - sudden opportunities" --aspect t`
|
|
220
|
+
|
|
221
|
+
### Aspect Type Shorthands
|
|
222
|
+
|
|
223
|
+
- `c` - Conjunction
|
|
224
|
+
- `s` - Square
|
|
225
|
+
- `t` - Trine
|
|
226
|
+
- `se` - Sextile
|
|
227
|
+
- `o` - Opposition
|
|
228
|
+
|
|
229
|
+
### Advanced Features
|
|
230
|
+
|
|
231
|
+
- **House system integration**: Notes include house information when using `--hs` option
|
|
232
|
+
- **Date-specific notes**: Use `--d` option to add notes for specific dates
|
|
233
|
+
- **JSON storage**: All notes are stored with full astrological context in `~/.config/astrocli/notes.json`
|
|
234
|
+
|
|
235
|
+
### Examples
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Basic planet note
|
|
239
|
+
klio mars --note "Mars in Aries - high energy period"
|
|
240
|
+
|
|
241
|
+
# House note
|
|
242
|
+
klio h[7] --note "7th house focus - relationships this month"
|
|
243
|
+
|
|
244
|
+
# Planet in house note
|
|
245
|
+
klio h[10] saturn --note "Saturn in 10th house - career challenges"
|
|
246
|
+
|
|
247
|
+
# Aspect note with shorthand
|
|
248
|
+
klio sun moon --note "Sun trine Moon - emotional harmony" --aspect t
|
|
249
|
+
|
|
250
|
+
# List all notes
|
|
251
|
+
klio --notes
|
|
252
|
+
|
|
253
|
+
# Search for specific notes
|
|
254
|
+
klio --search-notes "saturn"
|
|
255
|
+
```
|
|
256
|
+
|
|
190
257
|
## License
|
|
191
258
|
|
|
192
259
|
ISC
|
package/package.json
CHANGED
|
@@ -194,8 +194,13 @@ function getHouseSystemCode(houseSystemName) {
|
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// Function to calculate houses
|
|
197
|
-
function calculateHouses(julianDay, houseSystem = 'K', useBirthLocation = false) {
|
|
197
|
+
function calculateHouses(julianDay, houseSystem = 'K', useBirthLocation = false, locationOverride = null) {
|
|
198
198
|
return new Promise((resolve, reject) => {
|
|
199
|
+
if (!Number.isFinite(julianDay)) {
|
|
200
|
+
reject('Invalid Julian Day');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
199
204
|
// Load the configured location data
|
|
200
205
|
const configPath = path.join(__dirname, '../../astrocli-config.json');
|
|
201
206
|
let config;
|
|
@@ -210,7 +215,13 @@ function calculateHouses(julianDay, houseSystem = 'K', useBirthLocation = false)
|
|
|
210
215
|
|
|
211
216
|
let latitude, longitude, locationName;
|
|
212
217
|
|
|
213
|
-
if (
|
|
218
|
+
if (locationOverride && locationOverride.latitude != null && locationOverride.longitude != null) {
|
|
219
|
+
latitude = locationOverride.latitude;
|
|
220
|
+
longitude = locationOverride.longitude;
|
|
221
|
+
locationName = locationOverride.name
|
|
222
|
+
? `${locationOverride.name}${locationOverride.country ? `, ${locationOverride.country}` : ''}`
|
|
223
|
+
: 'Custom location';
|
|
224
|
+
} else if (useBirthLocation && config && config.birthData && config.birthData.location) {
|
|
214
225
|
// Use birth location for birth charts
|
|
215
226
|
latitude = config.birthData.location.latitude;
|
|
216
227
|
longitude = config.birthData.location.longitude;
|
|
@@ -222,6 +233,14 @@ function calculateHouses(julianDay, houseSystem = 'K', useBirthLocation = false)
|
|
|
222
233
|
locationName = config && config.currentLocation ? `${config.currentLocation.name}, ${config.currentLocation.country}` : 'Berlin, Germany';
|
|
223
234
|
}
|
|
224
235
|
|
|
236
|
+
latitude = parseFloat(latitude);
|
|
237
|
+
longitude = parseFloat(longitude);
|
|
238
|
+
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) {
|
|
239
|
+
latitude = defaultLatitude;
|
|
240
|
+
longitude = defaultLongitude;
|
|
241
|
+
locationName = 'Berlin, Germany';
|
|
242
|
+
}
|
|
243
|
+
|
|
225
244
|
swisseph.swe_houses(julianDay, latitude, longitude, houseSystem, function(result) {
|
|
226
245
|
if (result.error) {
|
|
227
246
|
if (result.error.includes('not found')) {
|
|
@@ -1328,7 +1347,7 @@ async function calculatePersonalTransits(transitDate = null, birthData = null) {
|
|
|
1328
1347
|
|
|
1329
1348
|
// Calculate houses based on birth data (ASC-based)
|
|
1330
1349
|
const birthJulianDay = calculateJulianDayUTC(birthData, getTimezoneOffset(birthData, birthData.location?.timezone || 'Europe/Zurich'));
|
|
1331
|
-
const houses = await calculateHouses(birthJulianDay, 'K', true); // Always Koch house system for transits
|
|
1350
|
+
const houses = await calculateHouses(birthJulianDay, 'K', true, birthData.location || null); // Always Koch house system for transits
|
|
1332
1351
|
|
|
1333
1352
|
// Calculate current planet positions (Transit)
|
|
1334
1353
|
const transitPlanets = {};
|
|
@@ -1467,7 +1486,11 @@ function showPersonalTransitAspects(transitDate = null, birthData = null, target
|
|
|
1467
1486
|
birthPlanetsData[name] = getAstrologicalData(name, birthData);
|
|
1468
1487
|
}
|
|
1469
1488
|
|
|
1470
|
-
aspectData.aspects.
|
|
1489
|
+
const sortedAspects = [...aspectData.aspects].sort((a, b) => {
|
|
1490
|
+
return parseFloat(a.orb) - parseFloat(b.orb);
|
|
1491
|
+
});
|
|
1492
|
+
|
|
1493
|
+
sortedAspects.forEach(aspect => {
|
|
1471
1494
|
const transitPlanetData = transitPlanetsData[aspect.transitPlanet];
|
|
1472
1495
|
const birthPlanetData = birthPlanetsData[aspect.birthPlanet];
|
|
1473
1496
|
|
|
@@ -1504,7 +1527,7 @@ async function showCombinedAnalysis(planetName, transitDate = null, birthData =
|
|
|
1504
1527
|
|
|
1505
1528
|
// Calculate houses based on birth data
|
|
1506
1529
|
const birthJulianDay = calculateJulianDayUTC(birthData, getTimezoneOffset(birthData, birthData.location?.timezone || 'Europe/Zurich'));
|
|
1507
|
-
const houses = await calculateHouses(birthJulianDay, getHouseSystemCode(houseSystem), true);
|
|
1530
|
+
const houses = await calculateHouses(birthJulianDay, getHouseSystemCode(houseSystem), true, birthData.location || null);
|
|
1508
1531
|
|
|
1509
1532
|
// Calculate current planet positions (Transit)
|
|
1510
1533
|
const transitPlanetData = getAstrologicalData(planetName, transitDate);
|
|
@@ -1836,16 +1859,28 @@ function parseFilterCriteria(filterString) {
|
|
|
1836
1859
|
const filterConditions = filterString.split(',').map(cond => cond.trim());
|
|
1837
1860
|
|
|
1838
1861
|
const criteria = [];
|
|
1862
|
+
const operatorRegex = /^(.+?)\s*(>=|<=|!=|=|>|<)\s*(.+)$/;
|
|
1839
1863
|
|
|
1840
1864
|
for (const condition of filterConditions) {
|
|
1865
|
+
const operatorMatch = condition.match(operatorRegex);
|
|
1866
|
+
if (operatorMatch) {
|
|
1867
|
+
criteria.push({
|
|
1868
|
+
column: operatorMatch[1].trim(),
|
|
1869
|
+
operator: operatorMatch[2],
|
|
1870
|
+
value: operatorMatch[3].trim()
|
|
1871
|
+
});
|
|
1872
|
+
continue;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1841
1875
|
const parts = condition.split(':');
|
|
1842
1876
|
if (parts.length !== 2) {
|
|
1843
|
-
console.warn(`Invalid filter format. Expected "column:value" but got "${condition}"`);
|
|
1877
|
+
console.warn(`Invalid filter format. Expected "column:value" or "column > value" but got "${condition}"`);
|
|
1844
1878
|
return null;
|
|
1845
1879
|
}
|
|
1846
|
-
|
|
1880
|
+
|
|
1847
1881
|
criteria.push({
|
|
1848
1882
|
column: parts[0].trim(),
|
|
1883
|
+
operator: '=',
|
|
1849
1884
|
value: parts[1].trim()
|
|
1850
1885
|
});
|
|
1851
1886
|
}
|
|
@@ -1863,19 +1898,45 @@ function applyFilter(records, filterCriteria) {
|
|
|
1863
1898
|
|
|
1864
1899
|
if (!parsedCriteria) return records;
|
|
1865
1900
|
|
|
1901
|
+
const matchesCriterion = (recordValue, criterion) => {
|
|
1902
|
+
if (recordValue === undefined || recordValue === null) return false;
|
|
1903
|
+
const recordValueStr = recordValue.toString().trim();
|
|
1904
|
+
const valueStr = criterion.value.toString().trim();
|
|
1905
|
+
const recordNum = parseFloat(recordValueStr);
|
|
1906
|
+
const valueNum = parseFloat(valueStr);
|
|
1907
|
+
const bothNumeric = Number.isFinite(recordNum) && Number.isFinite(valueNum);
|
|
1908
|
+
|
|
1909
|
+
switch (criterion.operator || '=') {
|
|
1910
|
+
case '=':
|
|
1911
|
+
return recordValueStr === valueStr;
|
|
1912
|
+
case '!=':
|
|
1913
|
+
return recordValueStr !== valueStr;
|
|
1914
|
+
case '>':
|
|
1915
|
+
return bothNumeric ? recordNum > valueNum : false;
|
|
1916
|
+
case '>=':
|
|
1917
|
+
return bothNumeric ? recordNum >= valueNum : false;
|
|
1918
|
+
case '<':
|
|
1919
|
+
return bothNumeric ? recordNum < valueNum : false;
|
|
1920
|
+
case '<=':
|
|
1921
|
+
return bothNumeric ? recordNum <= valueNum : false;
|
|
1922
|
+
default:
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
};
|
|
1926
|
+
|
|
1866
1927
|
// Handle both single criteria and multiple criteria
|
|
1867
1928
|
if (Array.isArray(parsedCriteria)) {
|
|
1868
1929
|
return records.filter(record => {
|
|
1869
1930
|
return parsedCriteria.every(criterion => {
|
|
1870
1931
|
const recordValue = record[criterion.column];
|
|
1871
|
-
return recordValue
|
|
1932
|
+
return matchesCriterion(recordValue, criterion);
|
|
1872
1933
|
});
|
|
1873
1934
|
});
|
|
1874
1935
|
} else {
|
|
1875
1936
|
// Single criteria (backward compatibility)
|
|
1876
1937
|
return records.filter(record => {
|
|
1877
1938
|
const recordValue = record[parsedCriteria.column];
|
|
1878
|
-
return recordValue
|
|
1939
|
+
return matchesCriterion(recordValue, parsedCriteria);
|
|
1879
1940
|
});
|
|
1880
1941
|
}
|
|
1881
1942
|
}
|
|
@@ -1959,44 +2020,66 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
1959
2020
|
// First pass: look for YYYY-MM-DD format (most specific)
|
|
1960
2021
|
const yyyyMmDdColumns = Object.keys(data).filter(key => {
|
|
1961
2022
|
const value = data[key];
|
|
1962
|
-
|
|
2023
|
+
const trimmed = value != null ? value.toString().trim() : '';
|
|
2024
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
|
|
2025
|
+
});
|
|
2026
|
+
|
|
2027
|
+
// Second pass: look for DD/MM/YYYY format (optional time)
|
|
2028
|
+
const ddMmYyyyColumns = Object.keys(data).filter(key => {
|
|
2029
|
+
const value = data[key];
|
|
2030
|
+
const trimmed = value != null ? value.toString().trim() : '';
|
|
2031
|
+
return /^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
1963
2032
|
});
|
|
1964
2033
|
|
|
1965
|
-
//
|
|
2034
|
+
// Third pass: look for Unix timestamps (10 or 13 digits)
|
|
1966
2035
|
const unixTimestampColumns = Object.keys(data).filter(key => {
|
|
1967
2036
|
const value = data[key];
|
|
1968
|
-
|
|
2037
|
+
const trimmed = value != null ? value.toString().trim() : '';
|
|
2038
|
+
return /^\d{10,13}$/.test(trimmed);
|
|
1969
2039
|
});
|
|
1970
2040
|
|
|
1971
|
-
//
|
|
2041
|
+
// Fourth pass: look for ISO-8601 dates (least specific, as it can match many formats)
|
|
1972
2042
|
const isoDateColumns = Object.keys(data).filter(key => {
|
|
1973
2043
|
const value = data[key];
|
|
2044
|
+
const trimmed = value != null ? value.toString().trim() : '';
|
|
1974
2045
|
// Only consider it an ISO date if it's not already matched by more specific patterns
|
|
1975
|
-
if (yyyyMmDdColumns.includes(key) || unixTimestampColumns.includes(key)) {
|
|
2046
|
+
if (yyyyMmDdColumns.includes(key) || ddMmYyyyColumns.includes(key) || unixTimestampColumns.includes(key)) {
|
|
1976
2047
|
return false;
|
|
1977
2048
|
}
|
|
1978
|
-
return moment(
|
|
2049
|
+
return moment(trimmed, moment.ISO_8601, true).isValid();
|
|
1979
2050
|
});
|
|
1980
2051
|
|
|
1981
|
-
// Prioritize columns: YYYY-MM-DD
|
|
1982
|
-
datetimeColumns.push(...yyyyMmDdColumns, ...unixTimestampColumns, ...isoDateColumns);
|
|
2052
|
+
// Prioritize columns: YYYY-MM-DD, DD/MM/YYYY, Unix timestamps, then ISO dates
|
|
2053
|
+
datetimeColumns.push(...yyyyMmDdColumns, ...ddMmYyyyColumns, ...unixTimestampColumns, ...isoDateColumns);
|
|
1983
2054
|
|
|
1984
2055
|
if (datetimeColumns.length > 0) {
|
|
1985
2056
|
const datetimeValue = data[datetimeColumns[0]];
|
|
2057
|
+
const datetimeValueTrimmed = datetimeValue != null ? datetimeValue.toString().trim() : '';
|
|
1986
2058
|
let datetime;
|
|
1987
2059
|
|
|
1988
2060
|
// Check if it's a Unix-Timestamp
|
|
1989
|
-
if (/^\d{10,13}$/.test(
|
|
2061
|
+
if (/^\d{10,13}$/.test(datetimeValueTrimmed)) {
|
|
1990
2062
|
// Convert Unix-Timestamp to milliseconds (if 10 digits, multiply by 1000)
|
|
1991
|
-
const timestamp =
|
|
2063
|
+
const timestamp = datetimeValueTrimmed.length === 10 ? parseInt(datetimeValueTrimmed) * 1000 : parseInt(datetimeValueTrimmed);
|
|
1992
2064
|
datetime = moment(timestamp);
|
|
1993
|
-
} else if (/^\d{4}-\d{2}-\d{2}$/.test(
|
|
2065
|
+
} else if (/^\d{4}-\d{2}-\d{2}$/.test(datetimeValueTrimmed)) {
|
|
1994
2066
|
// Handle as YYYY-MM-DD date format
|
|
1995
2067
|
// Parse as date only, set time to 12:00 (noon) as default
|
|
1996
|
-
datetime = moment(
|
|
2068
|
+
datetime = moment(datetimeValueTrimmed + 'T12:00:00');
|
|
2069
|
+
} else if (/^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(datetimeValueTrimmed)) {
|
|
2070
|
+
// Handle as DD/MM/YYYY (optional time)
|
|
2071
|
+
const hasTime = /\d{1,2}:\d{2}/.test(datetimeValueTrimmed);
|
|
2072
|
+
datetime = moment(datetimeValueTrimmed, ['DD/MM/YYYY HH:mm:ss', 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY'], true);
|
|
2073
|
+
if (datetime.isValid() && !hasTime) {
|
|
2074
|
+
datetime = datetime.hour(12).minute(0);
|
|
2075
|
+
}
|
|
1997
2076
|
} else {
|
|
1998
2077
|
// Handle as ISO-8601 date
|
|
1999
|
-
datetime = moment(
|
|
2078
|
+
datetime = moment(datetimeValueTrimmed);
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
if (!datetime.isValid()) {
|
|
2082
|
+
continue;
|
|
2000
2083
|
}
|
|
2001
2084
|
|
|
2002
2085
|
// Convert the date to the format needed for astrological calculations
|
|
@@ -2017,11 +2100,39 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2017
2100
|
continue;
|
|
2018
2101
|
}
|
|
2019
2102
|
|
|
2103
|
+
if (!Number.isFinite(astroData.longitude)) {
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2020
2107
|
// Increase the counter for pending operations
|
|
2021
2108
|
pendingOperations++;
|
|
2022
2109
|
|
|
2023
2110
|
// Calculate the houses
|
|
2024
2111
|
const julianDay = calculateJulianDayUTC(dateComponents, -datetime.utcOffset());
|
|
2112
|
+
if (!Number.isFinite(julianDay)) {
|
|
2113
|
+
let aspects = [];
|
|
2114
|
+
if (analyzeAspects) {
|
|
2115
|
+
try {
|
|
2116
|
+
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2117
|
+
|
|
2118
|
+
if (partnerPlanet) {
|
|
2119
|
+
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2120
|
+
}
|
|
2121
|
+
} catch (error) {}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
results.push({
|
|
2125
|
+
datetime: datetimeValue,
|
|
2126
|
+
planet: planetName,
|
|
2127
|
+
sign: astroData.sign,
|
|
2128
|
+
degreeInSign: astroData.degreeInSign,
|
|
2129
|
+
house: 'N/A',
|
|
2130
|
+
...(analyzeAspects ? { aspects } : {})
|
|
2131
|
+
});
|
|
2132
|
+
pendingOperations--;
|
|
2133
|
+
continue;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2025
2136
|
calculateHouses(julianDay, getHouseSystemCode(houseSystem), false)
|
|
2026
2137
|
.then(houses => {
|
|
2027
2138
|
const planetLongitude = parseFloat(astroData.degreeInSign) + (signs.indexOf(astroData.sign) * 30);
|
|
@@ -2134,63 +2245,54 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2134
2245
|
delimiter: delimiter
|
|
2135
2246
|
});
|
|
2136
2247
|
|
|
2248
|
+
const filteredRecords = filterCriteria ? applyFilter(records, filterCriteria) : records;
|
|
2249
|
+
|
|
2137
2250
|
// Process each record
|
|
2138
|
-
for (const data of
|
|
2139
|
-
// Apply filter if specified
|
|
2140
|
-
if (filterCriteria) {
|
|
2141
|
-
const parsedCriteria = typeof filterCriteria === 'string'
|
|
2142
|
-
? parseFilterCriteria(filterCriteria)
|
|
2143
|
-
: filterCriteria;
|
|
2144
|
-
|
|
2145
|
-
if (parsedCriteria) {
|
|
2146
|
-
let shouldSkip = false;
|
|
2147
|
-
|
|
2148
|
-
if (Array.isArray(parsedCriteria)) {
|
|
2149
|
-
// Multiple criteria - all must match
|
|
2150
|
-
shouldSkip = !parsedCriteria.every(criterion => {
|
|
2151
|
-
const recordValue = data[criterion.column];
|
|
2152
|
-
return recordValue && recordValue.toString() === criterion.value;
|
|
2153
|
-
});
|
|
2154
|
-
} else {
|
|
2155
|
-
// Single criteria
|
|
2156
|
-
const recordValue = data[parsedCriteria.column];
|
|
2157
|
-
shouldSkip = !recordValue || recordValue.toString() !== parsedCriteria.value;
|
|
2158
|
-
}
|
|
2159
|
-
|
|
2160
|
-
if (shouldSkip) {
|
|
2161
|
-
continue; // Skip this record
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
}
|
|
2251
|
+
for (const data of filteredRecords) {
|
|
2165
2252
|
|
|
2166
|
-
// Look for a column with ISO-Datetime values, YYYY-MM-DD dates, or Unix-Timestamps
|
|
2253
|
+
// Look for a column with ISO-Datetime values, YYYY-MM-DD dates, DD/MM/YYYY dates (optional time), or Unix-Timestamps
|
|
2167
2254
|
const datetimeColumns = Object.keys(data).filter(key => {
|
|
2168
2255
|
const value = data[key];
|
|
2256
|
+
const trimmed = value != null ? value.toString().trim() : '';
|
|
2169
2257
|
// Check for ISO-8601 date
|
|
2170
|
-
const isISO = moment(
|
|
2258
|
+
const isISO = moment(trimmed, moment.ISO_8601, true).isValid();
|
|
2171
2259
|
// Check for YYYY-MM-DD date format
|
|
2172
|
-
const isYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/.test(
|
|
2260
|
+
const isYYYYMMDD = /^\d{4}-\d{2}-\d{2}$/.test(trimmed);
|
|
2261
|
+
// Check for DD/MM/YYYY date format
|
|
2262
|
+
const isDDMMYYYY = /^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(trimmed);
|
|
2173
2263
|
// Check for Unix-Timestamp (number with 10 or 13 digits)
|
|
2174
|
-
const isUnixTimestamp = /^\d{10,13}$/.test(
|
|
2175
|
-
return isISO || isYYYYMMDD || isUnixTimestamp;
|
|
2264
|
+
const isUnixTimestamp = /^\d{10,13}$/.test(trimmed);
|
|
2265
|
+
return isISO || isYYYYMMDD || isDDMMYYYY || isUnixTimestamp;
|
|
2176
2266
|
});
|
|
2177
2267
|
|
|
2178
2268
|
if (datetimeColumns.length > 0) {
|
|
2179
2269
|
const datetimeValue = data[datetimeColumns[0]];
|
|
2270
|
+
const datetimeValueTrimmed = datetimeValue != null ? datetimeValue.toString().trim() : '';
|
|
2180
2271
|
let datetime;
|
|
2181
2272
|
|
|
2182
2273
|
// Check if it's a Unix-Timestamp
|
|
2183
|
-
if (/^\d{10,13}$/.test(
|
|
2274
|
+
if (/^\d{10,13}$/.test(datetimeValueTrimmed)) {
|
|
2184
2275
|
// Convert Unix-Timestamp to milliseconds (if 10 digits, multiply by 1000)
|
|
2185
|
-
const timestamp =
|
|
2276
|
+
const timestamp = datetimeValueTrimmed.length === 10 ? parseInt(datetimeValueTrimmed) * 1000 : parseInt(datetimeValueTrimmed);
|
|
2186
2277
|
datetime = moment(timestamp);
|
|
2187
|
-
} else if (/^\d{4}-\d{2}-\d{2}$/.test(
|
|
2278
|
+
} else if (/^\d{4}-\d{2}-\d{2}$/.test(datetimeValueTrimmed)) {
|
|
2188
2279
|
// Handle as YYYY-MM-DD date format
|
|
2189
2280
|
// Parse as date only, set time to 12:00 (noon) as default
|
|
2190
|
-
datetime = moment(
|
|
2281
|
+
datetime = moment(datetimeValueTrimmed + 'T12:00:00');
|
|
2282
|
+
} else if (/^\d{2}\/\d{2}\/\d{4}(?:\s+\d{1,2}:\d{2}(?::\d{2})?)?$/.test(datetimeValueTrimmed)) {
|
|
2283
|
+
// Handle as DD/MM/YYYY (optional time)
|
|
2284
|
+
const hasTime = /\d{1,2}:\d{2}/.test(datetimeValueTrimmed);
|
|
2285
|
+
datetime = moment(datetimeValueTrimmed, ['DD/MM/YYYY HH:mm:ss', 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY'], true);
|
|
2286
|
+
if (datetime.isValid() && !hasTime) {
|
|
2287
|
+
datetime = datetime.hour(12).minute(0);
|
|
2288
|
+
}
|
|
2191
2289
|
} else {
|
|
2192
2290
|
// Handle as ISO-8601 date
|
|
2193
|
-
datetime = moment(
|
|
2291
|
+
datetime = moment(datetimeValueTrimmed);
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
if (!datetime.isValid()) {
|
|
2295
|
+
continue;
|
|
2194
2296
|
}
|
|
2195
2297
|
|
|
2196
2298
|
// Convert the date to the format needed for astrological calculations
|
|
@@ -2211,11 +2313,39 @@ function analyzeCSVWithDatetime(filePath, planetName = 'moon', houseSystem = 'ko
|
|
|
2211
2313
|
continue;
|
|
2212
2314
|
}
|
|
2213
2315
|
|
|
2316
|
+
if (!Number.isFinite(astroData.longitude)) {
|
|
2317
|
+
continue;
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2214
2320
|
// Increase the counter for pending operations
|
|
2215
2321
|
pendingOperations++;
|
|
2216
2322
|
|
|
2217
2323
|
// Calculate the houses
|
|
2218
2324
|
const julianDay = calculateJulianDayUTC(dateComponents, -datetime.utcOffset());
|
|
2325
|
+
if (!Number.isFinite(julianDay)) {
|
|
2326
|
+
let aspects = [];
|
|
2327
|
+
if (analyzeAspects) {
|
|
2328
|
+
try {
|
|
2329
|
+
aspects = calculatePlanetAspects(planetName, dateComponents, true);
|
|
2330
|
+
|
|
2331
|
+
if (partnerPlanet) {
|
|
2332
|
+
aspects = aspects.filter(a => a.planet.toLowerCase() === partnerPlanet.toLowerCase());
|
|
2333
|
+
}
|
|
2334
|
+
} catch (error) {}
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
results.push({
|
|
2338
|
+
datetime: datetimeValue,
|
|
2339
|
+
planet: planetName,
|
|
2340
|
+
sign: astroData.sign,
|
|
2341
|
+
degreeInSign: astroData.degreeInSign,
|
|
2342
|
+
house: 'N/A',
|
|
2343
|
+
...(analyzeAspects ? { aspects } : {})
|
|
2344
|
+
});
|
|
2345
|
+
pendingOperations--;
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2219
2349
|
calculateHouses(julianDay, getHouseSystemCode(houseSystem), false)
|
|
2220
2350
|
.then(houses => {
|
|
2221
2351
|
const planetLongitude = parseFloat(astroData.degreeInSign) + (signs.indexOf(astroData.sign) * 30);
|
package/src/cli/cli.js
CHANGED
|
@@ -2,7 +2,7 @@ const { Command } = require('commander');
|
|
|
2
2
|
const { planets, signs } = require('../astrology/astrologyConstants');
|
|
3
3
|
const { showRetrogradePlanets } = require('../astrology/retrogradeService');
|
|
4
4
|
const { getCurrentTimeInTimezone, showAspectFigures, analyzeElementDistribution, getTimezoneOffset, calculateJulianDayUTC, calculateHouses, getAstrologicalData, getPlanetHouse, showPlanetAspects, calculatePlanetAspects, getAllActiveAspects, showAllActiveAspects, getBirthDataFromConfig, getPersonDataFromConfig, detectAspectFigures, calculatePersonalTransits, showPersonalTransitAspects, showCombinedAnalysis, calculatePersonalTransitAspects, determineAspectPhase, getAspectAngle, getFutureAspects, getPastAspects, analyzeCSVWithDatetime, analyzeHouseDistributionSignificance, analyzeAspectDistributionSignificance, analyzeSignDistributionSignificance, calculateAspectStatistics, calculatePlanetComboAspects, showPlanetComboAspects, getCriticalPlanets, getHouseSystemCode, calculateNextPlanetIngress, calculateAstrologicalAngles, longitudeToSignDegree, calculateTransitFrequency } = require('../astrology/astrologyService');
|
|
5
|
-
const { performSetup, showConfigStatus, loadConfig, setAIModel, askAIModel, setPerson1, setPerson2, setPerson, getPersonData, listPeople, deletePerson } = require('../config/configService');
|
|
5
|
+
const { performSetup, showConfigStatus, loadConfig, setAIModel, askAIModel, setPerson1, setPerson2, setPerson, getPersonData, listPeople, deletePerson, saveNote, showNotes, searchNotesBySituation } = require('../config/configService');
|
|
6
6
|
const { parseAppleHealthXML } = require('../health/healthService');
|
|
7
7
|
const { analyzeStepsByPlanetSign, analyzeStressByPlanetAspects, analyzePlanetAspectsForSleep, analyzeLateNightAspects, analyzeAllNighterAspects } = require('../health/healthAnalysis');
|
|
8
8
|
const { getFileCreationDate, parseDateToComponents } = require('../utils/fileUtils');
|
|
@@ -159,6 +159,136 @@ const shouldUseBirthData = (options) => {
|
|
|
159
159
|
return options.i;
|
|
160
160
|
};
|
|
161
161
|
|
|
162
|
+
// Helper function to parse aspect type shorthands
|
|
163
|
+
const parseAspectType = (aspectCode) => {
|
|
164
|
+
const aspectMap = {
|
|
165
|
+
'c': 'Conjunction',
|
|
166
|
+
'con': 'Conjunction',
|
|
167
|
+
'conjunction': 'Conjunction',
|
|
168
|
+
'o': 'Opposition',
|
|
169
|
+
'opp': 'Opposition',
|
|
170
|
+
'opposition': 'Opposition',
|
|
171
|
+
's': 'Square',
|
|
172
|
+
'sq': 'Square',
|
|
173
|
+
'square': 'Square',
|
|
174
|
+
't': 'Trine',
|
|
175
|
+
'tri': 'Trine',
|
|
176
|
+
'trine': 'Trine',
|
|
177
|
+
'se': 'Sextile',
|
|
178
|
+
'sex': 'Sextile',
|
|
179
|
+
'sextile': 'Sextile'
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
return aspectMap[aspectCode.toLowerCase()] || aspectCode;
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// Helper function to parse house notation h[n]
|
|
186
|
+
const parseHouseNotation = (arg) => {
|
|
187
|
+
const houseMatch = arg.match(/^h\[(\d+)\]$/);
|
|
188
|
+
if (houseMatch) {
|
|
189
|
+
const houseNumber = parseInt(houseMatch[1]);
|
|
190
|
+
if (houseNumber >= 1 && houseNumber <= 12) {
|
|
191
|
+
return { isHouse: true, houseNumber: houseNumber };
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return { isHouse: false };
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Helper function to create a note for the current astrological situation
|
|
198
|
+
const createAstrologicalNote = async (planetArg, planet2Arg, options, customDate = null, aspectType = null) => {
|
|
199
|
+
try {
|
|
200
|
+
// Get current date if not provided
|
|
201
|
+
const currentDate = customDate || getCurrentTimeInTimezone();
|
|
202
|
+
|
|
203
|
+
// Parse planet arguments for special notations
|
|
204
|
+
const planet1Parse = parseHouseNotation(planetArg);
|
|
205
|
+
const planet2Parse = planet2Arg ? parseHouseNotation(planet2Arg) : { isHouse: false };
|
|
206
|
+
|
|
207
|
+
// Create the note data structure
|
|
208
|
+
const noteData = {
|
|
209
|
+
content: options.note,
|
|
210
|
+
timestamp: new Date().toISOString()
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Handle different astrological notations
|
|
214
|
+
if (planet1Parse.isHouse) {
|
|
215
|
+
// House notation: h[n]
|
|
216
|
+
noteData.house = planet1Parse.houseNumber;
|
|
217
|
+
noteData.astrologicalSituation = `House ${planet1Parse.houseNumber}`;
|
|
218
|
+
|
|
219
|
+
if (planet2Arg && !planet2Parse.isHouse) {
|
|
220
|
+
// House with planet: h[n] planet
|
|
221
|
+
const planetData = getAstrologicalData(planet2Arg, currentDate);
|
|
222
|
+
noteData.planet = planet2Arg;
|
|
223
|
+
noteData.astrologicalSituation = `${planet2Arg} in House ${planet1Parse.houseNumber} (${planetData.sign} ${planetData.degreeInSign}°)`;
|
|
224
|
+
}
|
|
225
|
+
} else if (aspectType) {
|
|
226
|
+
// Explicit aspect notation: planet1 aspectType planet2 (e.g., saturn c moon)
|
|
227
|
+
const planet1Data = getAstrologicalData(planetArg, currentDate);
|
|
228
|
+
let planet2Data = null;
|
|
229
|
+
|
|
230
|
+
if (planet2Arg) {
|
|
231
|
+
planet2Data = getAstrologicalData(planet2Arg, currentDate);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
noteData.planet1 = planetArg;
|
|
235
|
+
noteData.aspectType = aspectType;
|
|
236
|
+
noteData.astrologicalSituation = `${planetArg} ${aspectType}`;
|
|
237
|
+
|
|
238
|
+
if (planet2Arg) {
|
|
239
|
+
noteData.planet2 = planet2Arg;
|
|
240
|
+
noteData.astrologicalSituation += ` ${planet2Arg} (${planet1Data.sign} ${planet1Data.degreeInSign}° ${aspectType} ${planet2Data.sign} ${planet2Data.degreeInSign}°)`;
|
|
241
|
+
} else {
|
|
242
|
+
noteData.astrologicalSituation += ` (${planet1Data.sign} ${planet1Data.degreeInSign}°)`;
|
|
243
|
+
}
|
|
244
|
+
} else if (planetArg && planet2Arg) {
|
|
245
|
+
// Two planets - check if there's an aspect between them
|
|
246
|
+
const planet1Data = getAstrologicalData(planetArg, currentDate);
|
|
247
|
+
const aspects = calculatePlanetAspects(planetArg, currentDate);
|
|
248
|
+
const aspectWithPlanet2 = aspects.find(a => a.planet === planet2Arg);
|
|
249
|
+
|
|
250
|
+
if (aspectWithPlanet2) {
|
|
251
|
+
noteData.planet1 = planetArg;
|
|
252
|
+
noteData.planet2 = planet2Arg;
|
|
253
|
+
noteData.aspectType = aspectWithPlanet2.type;
|
|
254
|
+
noteData.astrologicalSituation = `${planetArg} ${aspectWithPlanet2.type} ${planet2Arg} (${planet1Data.sign} ${planet1Data.degreeInSign}°)`;
|
|
255
|
+
} else {
|
|
256
|
+
noteData.planet1 = planetArg;
|
|
257
|
+
noteData.planet2 = planet2Arg;
|
|
258
|
+
noteData.astrologicalSituation = `${planetArg} and ${planet2Arg} (no current aspect)`;
|
|
259
|
+
}
|
|
260
|
+
} else if (planetArg) {
|
|
261
|
+
// Single planet
|
|
262
|
+
const planetData = getAstrologicalData(planetArg, currentDate);
|
|
263
|
+
noteData.planet = planetArg;
|
|
264
|
+
noteData.astrologicalSituation = `${planetArg} in ${planetData.sign} (${planetData.degreeInSign}°)`;
|
|
265
|
+
|
|
266
|
+
// Add house information if available
|
|
267
|
+
if (options.hs) {
|
|
268
|
+
const houseSystem = options.hs.toLowerCase();
|
|
269
|
+
const julianDay = getJulianDay(currentDate, shouldUseBirthData(options));
|
|
270
|
+
const houses = await calculateHouses(julianDay, getHouseSystemCode(houseSystem), shouldUseBirthData(options));
|
|
271
|
+
const planetLongitude = parseFloat(planetData.degreeInSign) + (signs.indexOf(planetData.sign) * 30);
|
|
272
|
+
const house = getPlanetHouse(planetLongitude, houses.house);
|
|
273
|
+
noteData.house = house;
|
|
274
|
+
noteData.astrologicalSituation += ` in house ${house}`;
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
// General situation
|
|
278
|
+
noteData.astrologicalSituation = 'General astrological situation';
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Add date information
|
|
282
|
+
noteData.date = currentDate;
|
|
283
|
+
|
|
284
|
+
// Save the note
|
|
285
|
+
saveNote(noteData);
|
|
286
|
+
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Error creating note:', error.message);
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
162
292
|
// Function to calculate the Julian Day in UTC
|
|
163
293
|
// This function is defined before use to avoid initialization issues
|
|
164
294
|
const getJulianDay = (customDate = null, useBirthData = false) => {
|
|
@@ -251,6 +381,10 @@ program
|
|
|
251
381
|
.option('--delete-person <id>', 'Deletes a person')
|
|
252
382
|
.option('--t <days>', 'Time limit (e.g. 7d, 30d, 90d, 14d)')
|
|
253
383
|
.option('--p <prompt>', 'Asks a question to the AI model (e.g. "Which careers suit this position?")')
|
|
384
|
+
.option('--note <content>', 'Adds a note to the current astrological situation')
|
|
385
|
+
.option('--notes', 'Shows all saved notes')
|
|
386
|
+
.option('--search-notes <situation>', 'Searches notes by astrological situation')
|
|
387
|
+
.option('--aspect <type>', 'Specifies aspect type for note (c, s, t, se, o)')
|
|
254
388
|
.option('--el', 'Shows the element distribution of planets in a horizontal chart')
|
|
255
389
|
.option('--af', 'Shows active aspect figures like T-squares, Grand Trines, etc.')
|
|
256
390
|
.option('--tra', 'Shows personal transits based on birth data')
|
|
@@ -987,6 +1121,32 @@ program
|
|
|
987
1121
|
return;
|
|
988
1122
|
}
|
|
989
1123
|
|
|
1124
|
+
// Show all notes if --notes option is specified (no planet required)
|
|
1125
|
+
if (options.notes) {
|
|
1126
|
+
showNotes();
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Search notes if --search-notes option is specified (no planet required)
|
|
1131
|
+
if (options.searchNotes) {
|
|
1132
|
+
const results = searchNotesBySituation(options.searchNotes);
|
|
1133
|
+
if (results.length === 0) {
|
|
1134
|
+
console.log(`No notes found for situation: "${options.searchNotes}"`);
|
|
1135
|
+
} else {
|
|
1136
|
+
console.log(`Found ${results.length} notes for situation: "${options.searchNotes}"`);
|
|
1137
|
+
console.log('================================================================================');
|
|
1138
|
+
results.forEach((note, index) => {
|
|
1139
|
+
console.log(`Note ${index + 1}:`);
|
|
1140
|
+
console.log(`Date: ${new Date(note.timestamp).toLocaleString()}`);
|
|
1141
|
+
if (note.content) {
|
|
1142
|
+
console.log(`Content: ${note.content}`);
|
|
1143
|
+
}
|
|
1144
|
+
console.log('');
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
990
1150
|
// Create/update person 1 if --person1 option is specified (no planet required)
|
|
991
1151
|
if (options.person1) {
|
|
992
1152
|
await setPerson1(options.person1);
|
|
@@ -1269,6 +1429,56 @@ program
|
|
|
1269
1429
|
process.exit(1);
|
|
1270
1430
|
}
|
|
1271
1431
|
|
|
1432
|
+
// Handle note creation if --note option is specified
|
|
1433
|
+
if (options.note) {
|
|
1434
|
+
// Determine the custom date if specified
|
|
1435
|
+
let customDate = null;
|
|
1436
|
+
if (options.d) {
|
|
1437
|
+
// Try to parse the date as DD.MM.YYYY or DD.MM.YYYY HH:MM
|
|
1438
|
+
const dateRegex = /^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?$/;
|
|
1439
|
+
const match = options.d.match(dateRegex);
|
|
1440
|
+
|
|
1441
|
+
if (match) {
|
|
1442
|
+
const day = parseInt(match[1], 10);
|
|
1443
|
+
const month = parseInt(match[2], 10);
|
|
1444
|
+
const year = parseInt(match[3], 10);
|
|
1445
|
+
const hour = match[4] ? parseInt(match[4], 10) : 12; // Default: 12 o'clock
|
|
1446
|
+
const minute = match[5] ? parseInt(match[5], 10) : 0; // Default: 0 minutes
|
|
1447
|
+
|
|
1448
|
+
// Check if the date is valid
|
|
1449
|
+
const date = new Date(year, month - 1, day, hour, minute);
|
|
1450
|
+
if (date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day) {
|
|
1451
|
+
customDate = {
|
|
1452
|
+
day: day,
|
|
1453
|
+
month: month,
|
|
1454
|
+
year: year,
|
|
1455
|
+
hour: hour,
|
|
1456
|
+
minute: minute
|
|
1457
|
+
};
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
// Check if we have an explicit aspect option
|
|
1463
|
+
let planet1 = planetArg;
|
|
1464
|
+
let aspectType = null;
|
|
1465
|
+
let planet2 = planet2Arg;
|
|
1466
|
+
|
|
1467
|
+
// Handle explicit aspect notation via --aspect option
|
|
1468
|
+
if (options.aspect) {
|
|
1469
|
+
aspectType = parseAspectType(options.aspect);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
// Create the note with the parsed arguments
|
|
1473
|
+
await createAstrologicalNote(planet1, planet2, options, customDate, aspectType);
|
|
1474
|
+
|
|
1475
|
+
// If only --note is specified, don't continue with other processing
|
|
1476
|
+
if (!options.a && !options.s && !options.hs && !options.k && !options.c && !options.rx && !options.el && !options.af && !options.tra && !options.tr && !options.in && !options.o && !options.p) {
|
|
1477
|
+
console.log('✓ Note saved successfully!');
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1272
1482
|
// Show table view of all planet positions if --s option is specified
|
|
1273
1483
|
if (options.s) {
|
|
1274
1484
|
// Use person data for house calculation if --p1, --p2 or --i option is specified
|
package/src/cli/cliService.js
CHANGED
|
@@ -889,7 +889,7 @@ class CLIService {
|
|
|
889
889
|
const julianDayForHouses = getJulianDayUTCForHouses();
|
|
890
890
|
const useBirthLocation = birthData !== null;
|
|
891
891
|
|
|
892
|
-
houses = await calculateHouses(julianDayForHouses, getHouseSystemCode(houseSystem), useBirthLocation);
|
|
892
|
+
houses = await calculateHouses(julianDayForHouses, getHouseSystemCode(houseSystem), useBirthLocation, birthData ? birthData.location : null);
|
|
893
893
|
} catch (error) {
|
|
894
894
|
console.error('Error calculating houses:', error);
|
|
895
895
|
}
|
|
@@ -264,6 +264,134 @@ function deletePerson(personId, userId = null) {
|
|
|
264
264
|
return saveConfig(config, userId);
|
|
265
265
|
}
|
|
266
266
|
|
|
267
|
+
// Function to get the notes file path
|
|
268
|
+
function getNotesPath(userId = null) {
|
|
269
|
+
if (process.env.KLIO_NOTES_PATH) {
|
|
270
|
+
return process.env.KLIO_NOTES_PATH;
|
|
271
|
+
}
|
|
272
|
+
let configDir;
|
|
273
|
+
|
|
274
|
+
// Determine configuration directory based on operating system
|
|
275
|
+
if (process.platform === 'win32') {
|
|
276
|
+
// Windows: Use %APPDATA%\astrocli
|
|
277
|
+
configDir = path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'astrocli');
|
|
278
|
+
} else if (process.platform === 'darwin') {
|
|
279
|
+
// macOS: Use ~/Library/Application Support/astrocli
|
|
280
|
+
configDir = path.join(os.homedir(), 'Library', 'Application Support', 'astrocli');
|
|
281
|
+
} else {
|
|
282
|
+
// Linux and other Unix systems: Use ~/.config/astrocli
|
|
283
|
+
configDir = path.join(os.homedir(), '.config', 'astrocli');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Create directory if it doesn't exist
|
|
287
|
+
if (!fs.existsSync(configDir)) {
|
|
288
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// For user-specific notes, create user directory
|
|
292
|
+
if (userId) {
|
|
293
|
+
const userDir = path.join(configDir, 'users', userId);
|
|
294
|
+
if (!fs.existsSync(userDir)) {
|
|
295
|
+
fs.mkdirSync(userDir, { recursive: true });
|
|
296
|
+
}
|
|
297
|
+
return path.join(userDir, 'notes.json');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return path.join(configDir, 'notes.json');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Function to save a note
|
|
304
|
+
function saveNote(noteData, userId = null) {
|
|
305
|
+
try {
|
|
306
|
+
const notesPath = getNotesPath(userId);
|
|
307
|
+
let notes = [];
|
|
308
|
+
|
|
309
|
+
// Load existing notes if file exists
|
|
310
|
+
if (fs.existsSync(notesPath)) {
|
|
311
|
+
const notesData = fs.readFileSync(notesPath, 'utf8');
|
|
312
|
+
notes = JSON.parse(notesData);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Add timestamp if not provided
|
|
316
|
+
if (!noteData.timestamp) {
|
|
317
|
+
noteData.timestamp = new Date().toISOString();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Add the new note
|
|
321
|
+
notes.push(noteData);
|
|
322
|
+
|
|
323
|
+
// Sort notes by timestamp (newest first)
|
|
324
|
+
notes.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
|
325
|
+
|
|
326
|
+
// Save the notes
|
|
327
|
+
fs.writeFileSync(notesPath, JSON.stringify(notes, null, 2));
|
|
328
|
+
console.log('✓ Note successfully saved!');
|
|
329
|
+
console.log(`📁 Location: ${notesPath}`);
|
|
330
|
+
return true;
|
|
331
|
+
} catch (error) {
|
|
332
|
+
console.error('Error saving note:', error.message);
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Function to load all notes
|
|
338
|
+
function loadNotes(userId = null) {
|
|
339
|
+
try {
|
|
340
|
+
const notesPath = getNotesPath(userId);
|
|
341
|
+
if (fs.existsSync(notesPath)) {
|
|
342
|
+
const notesData = fs.readFileSync(notesPath, 'utf8');
|
|
343
|
+
return JSON.parse(notesData);
|
|
344
|
+
}
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error('Error loading notes:', error.message);
|
|
347
|
+
}
|
|
348
|
+
return [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Function to show all notes
|
|
352
|
+
function showNotes(userId = null) {
|
|
353
|
+
const notes = loadNotes(userId);
|
|
354
|
+
|
|
355
|
+
if (notes.length === 0) {
|
|
356
|
+
console.log('No notes found.');
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log('Your astrological notes:');
|
|
361
|
+
console.log('========================');
|
|
362
|
+
|
|
363
|
+
notes.forEach((note, index) => {
|
|
364
|
+
console.log(`Note ${index + 1}:`);
|
|
365
|
+
console.log(`Date: ${new Date(note.timestamp).toLocaleString()}`);
|
|
366
|
+
|
|
367
|
+
if (note.astrologicalSituation) {
|
|
368
|
+
console.log(`Astrological Situation: ${note.astrologicalSituation}`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (note.planet1 && note.aspectType && note.planet2) {
|
|
372
|
+
console.log(`Aspect: ${note.planet1} ${note.aspectType} ${note.planet2}`);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (note.moonHouse) {
|
|
376
|
+
console.log(`Moon House: ${note.moonHouse}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (note.content) {
|
|
380
|
+
console.log(`Content: ${note.content}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log('');
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Function to search notes by astrological situation
|
|
388
|
+
function searchNotesBySituation(situation, userId = null) {
|
|
389
|
+
const notes = loadNotes(userId);
|
|
390
|
+
return notes.filter(note =>
|
|
391
|
+
note.astrologicalSituation && note.astrologicalSituation.toLowerCase().includes(situation.toLowerCase())
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
267
395
|
// Function to create/set person 1 (for backward compatibility)
|
|
268
396
|
async function setPerson1(personString, userId = null) {
|
|
269
397
|
return await setPerson('p1', personString, userId);
|
|
@@ -769,6 +897,11 @@ module.exports = {
|
|
|
769
897
|
getPersonData,
|
|
770
898
|
listPeople,
|
|
771
899
|
deletePerson,
|
|
900
|
+
// Note functions
|
|
901
|
+
saveNote,
|
|
902
|
+
loadNotes,
|
|
903
|
+
showNotes,
|
|
904
|
+
searchNotesBySituation,
|
|
772
905
|
// Add helper functions for user-specific operations
|
|
773
906
|
getConfigPath
|
|
774
907
|
};
|