klio 1.5.0 → 1.5.2

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/.dockerignore ADDED
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ npm-debug.log*
3
+ yarn-debug.log*
4
+ yarn-error.log*
5
+ .git
6
+ .gitignore
7
+ .idea
8
+ sessions
9
+ .DS_Store
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 20
package/Dockerfile ADDED
@@ -0,0 +1,18 @@
1
+ FROM node:20-bookworm-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Build deps for native modules like swisseph
6
+ RUN apt-get update \
7
+ && apt-get install -y --no-install-recommends python3 make g++ \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ COPY package.json package-lock.json ./
11
+ RUN npm ci
12
+
13
+ COPY . .
14
+
15
+ ENV NODE_ENV=production
16
+ EXPOSE 37421
17
+
18
+ CMD ["node", "src/gui/server.js"]
package/README.md CHANGED
@@ -56,7 +56,28 @@ This will start a web server on port 37421 (or a different port if specified) an
56
56
  klio --gui --gui-port 8080
57
57
  ```
58
58
 
59
- ### Configuration
59
+ ### Running with Docker
60
+
61
+ You can also run the GUI using Docker for easy deployment:
62
+
63
+ ```bash
64
+ # Build the Docker image
65
+ docker build -t astrocli-gui .
66
+
67
+ # Run the container
68
+ docker run -p 3006:37421 astrocli-gui
69
+ ```
70
+
71
+ This will start the GUI on `http://localhost:37421`.
72
+
73
+
74
+ ### Demo
75
+
76
+ Try a live demo of klio web here:
77
+
78
+ 🌐 [https://klio.up.railway.app](https://klio.up.railway.app)
79
+
80
+ ## Configuration
60
81
 
61
82
  - **Show status**: `--status` - Shows the stored configuration data
62
83
  - **Setup**: `--setup` - Setup for a default chart which can be accessed with `--i` for example `klio --a --i` for displaying the natal aspects. In this setup you can also set up a local AI model for LM-Studio.
package/compose.yaml ADDED
@@ -0,0 +1,6 @@
1
+ services:
2
+ astrocli-gui:
3
+ build: .
4
+ ports:
5
+ - "3006:37421"
6
+ restart: unless-stopped
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "klio",
3
- "version": "1.5.0",
3
+ "version": "1.5.2",
4
4
  "description": "A CLI for astrological calculations",
5
5
  "main": "src/main.js",
6
6
  "bin": {
7
7
  "klio": "./src/main.js"
8
8
  },
9
9
  "scripts": {
10
- "test": "echo \"Error: no test specified\" && exit 1"
10
+ "test": "echo \"Error: no test specified\" && exit 1",
11
+ "build:gui-css": "tailwindcss -i ./src/gui/public/tailwind.css -o ./src/gui/public/tailwind.generated.css --minify",
12
+ "watch:gui-css": "tailwindcss -i ./src/gui/public/tailwind.css -o ./src/gui/public/tailwind.generated.css --watch"
11
13
  },
12
14
  "keywords": [
13
15
  "astrology",
@@ -19,15 +21,18 @@
19
21
  "license": "ISC",
20
22
  "type": "commonjs",
21
23
  "dependencies": {
24
+ "@webcontainer/api": "^1.6.1",
22
25
  "axios": "^1.13.4",
26
+ "bcryptjs": "^3.0.3",
23
27
  "commander": "^14.0.3",
24
28
  "csv": "^6.4.1",
25
29
  "csv-parser": "^3.0.0",
26
30
  "express": "^4.19.2",
31
+ "express-session": "^1.19.0",
27
32
  "fast-xml-parser": "^5.3.4",
28
33
  "moment-timezone": "^0.6.0",
29
34
  "node-fetch": "^3.3.2",
30
- "sqlite3": "^5.1.7",
35
+ "session-file-store": "^1.5.0",
31
36
  "swisseph": "^0.5.17"
32
37
  }
33
38
  }
@@ -1,4 +1,4 @@
1
- const swisseph = require('swisseph');
1
+ const swisseph = require('./swissephAdapter');
2
2
  const fs = require('fs');
3
3
  const moment = require('moment-timezone');
4
4
  const csvParser = require('csv-parser');
@@ -50,11 +50,11 @@ function getCurrentTimeInTimezone() {
50
50
  }
51
51
 
52
52
  // Function to load birth data from configuration
53
- function getBirthDataFromConfig() {
53
+ function getBirthDataFromConfig(userId = null) {
54
54
  try {
55
55
  // First try to load the new configuration
56
56
  const { loadConfig } = require('../config/configService');
57
- const config = loadConfig();
57
+ const config = loadConfig(userId);
58
58
 
59
59
  if (config && config.birthData) {
60
60
  // Parse birth date (Format: DD.MM.YYYY)
@@ -78,32 +78,34 @@ function getBirthDataFromConfig() {
78
78
  };
79
79
  }
80
80
 
81
- // If the new configuration was not found, try the old file
82
- const configPath = path.join(__dirname, '../../astrocli-config.json');
83
- if (fs.existsSync(configPath)) {
84
- const configData = fs.readFileSync(configPath, 'utf8');
85
- const oldConfig = JSON.parse(configData);
86
-
87
- if (oldConfig && oldConfig.birthData) {
88
- // Parse birth date (Format: DD.MM.YYYY)
89
- const dateParts = oldConfig.birthData.date.split('.');
90
- const day = parseInt(dateParts[0]);
91
- const month = parseInt(dateParts[1]);
92
- const year = parseInt(dateParts[2]);
93
-
94
- // Parse birth time (Format: HH:MM)
95
- const timeParts = oldConfig.birthData.time.split(':');
96
- const hour = parseInt(timeParts[0]);
97
- const minute = parseInt(timeParts[1]);
81
+ // If the new configuration was not found, try the old file (only for global config)
82
+ if (!userId) {
83
+ const configPath = path.join(__dirname, '../../astrocli-config.json');
84
+ if (fs.existsSync(configPath)) {
85
+ const configData = fs.readFileSync(configPath, 'utf8');
86
+ const oldConfig = JSON.parse(configData);
98
87
 
99
- return {
100
- year: year,
101
- month: month,
102
- day: day,
103
- hour: hour,
104
- minute: minute,
105
- location: oldConfig.birthData.location
106
- };
88
+ if (oldConfig && oldConfig.birthData) {
89
+ // Parse birth date (Format: DD.MM.YYYY)
90
+ const dateParts = oldConfig.birthData.date.split('.');
91
+ const day = parseInt(dateParts[0]);
92
+ const month = parseInt(dateParts[1]);
93
+ const year = parseInt(dateParts[2]);
94
+
95
+ // Parse birth time (Format: HH:MM)
96
+ const timeParts = oldConfig.birthData.time.split(':');
97
+ const hour = parseInt(timeParts[0]);
98
+ const minute = parseInt(timeParts[1]);
99
+
100
+ return {
101
+ year: year,
102
+ month: month,
103
+ day: day,
104
+ hour: hour,
105
+ minute: minute,
106
+ location: oldConfig.birthData.location
107
+ };
108
+ }
107
109
  }
108
110
  }
109
111
  } catch (error) {
@@ -114,14 +116,14 @@ function getBirthDataFromConfig() {
114
116
  }
115
117
 
116
118
  // Function to load person data (including birth data)
117
- function getPersonDataFromConfig(personId = 'birth') {
119
+ function getPersonDataFromConfig(personId = 'birth', userId = null) {
118
120
  try {
119
121
  const { getPersonData } = require('../config/configService');
120
122
 
121
123
  if (personId === 'birth') {
122
- return getBirthDataFromConfig();
124
+ return getBirthDataFromConfig(userId);
123
125
  } else {
124
- return getPersonData(personId);
126
+ return getPersonData(personId, userId);
125
127
  }
126
128
  } catch (error) {
127
129
  console.error(`Error loading data for ${personId}:`, error.message);
@@ -684,7 +686,7 @@ function showPlanetAspects(planetName, dateComponents, useBirthData = false, use
684
686
 
685
687
  console.log(`Aspects for ${planetLabel}:`);
686
688
  console.log('================================================================================');
687
- console.log('| Aspect | Planet | Orb | Exact Date/Time |');
689
+ console.log('| Aspect | Planet | Orb |');
688
690
  console.log('================================================================================');
689
691
 
690
692
  if (aspects.length === 0) {
@@ -696,14 +698,8 @@ function showPlanetAspects(planetName, dateComponents, useBirthData = false, use
696
698
  const planetFormatted = planetNameFormatted.padEnd(8, ' ');
697
699
  const orb = aspect.orb.padEnd(4, ' ');
698
700
 
699
- // Calculate exact date and time for this aspect
700
- const exactDateTime = findExactAspectTime(planetName, aspect.planet, aspect.type, dateComponents);
701
- if (exactDateTime) {
702
- const dateTimeStr = `${String(exactDateTime.day).padStart(2, '0')}.${String(exactDateTime.month).padStart(2, '0')}.${exactDateTime.year} ${String(exactDateTime.hour).padStart(2, '0')}:${String(exactDateTime.minute).padStart(2, '0')}`;
703
- console.log(`| ${aspectName} | ${planetFormatted} | ${orb}° | ${dateTimeStr} |`);
704
- } else {
705
- console.log(`| ${aspectName} | ${planetFormatted} | ${orb}° | Calculation failed |`);
706
- }
701
+ // Exact time calculation removed
702
+ console.log(`| ${aspectName} | ${planetFormatted} | ${orb}° |`);
707
703
  });
708
704
  }
709
705
 
@@ -724,7 +720,7 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
724
720
 
725
721
  console.log(`Aspects between ${planetLabels}:`);
726
722
  console.log('================================================================================');
727
- console.log('| Planet 1 | Planet 2 | Aspect | Orb | Exact Date/Time |');
723
+ console.log('| Planet 1 | Planet 2 | Aspect | Orb |');
728
724
  console.log('================================================================================');
729
725
 
730
726
  if (aspects.length === 0) {
@@ -738,14 +734,8 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
738
734
  const aspectName = aspect.type.padEnd(11, ' ');
739
735
  const orb = aspect.orb.padEnd(4, ' ');
740
736
 
741
- // Calculate exact date and time for this aspect
742
- const exactDateTime = findExactAspectTime(aspect.planet1, aspect.planet2, aspect.type, dateComponents);
743
- if (exactDateTime) {
744
- const dateTimeStr = `${String(exactDateTime.day).padStart(2, '0')}.${String(exactDateTime.month).padStart(2, '0')}.${exactDateTime.year} ${String(exactDateTime.hour).padStart(2, '0')}:${String(exactDateTime.minute).padStart(2, '0')}`;
745
- console.log(`| ${planet1Padded} | ${planet2Padded} | ${aspectName} | ${orb}° | ${dateTimeStr} |`);
746
- } else {
747
- console.log(`| ${planet1Padded} | ${planet2Padded} | ${aspectName} | ${orb}° | Calculation failed |`);
748
- }
737
+ // Exact time calculation removed
738
+ console.log(`| ${planet1Padded} | ${planet2Padded} | ${aspectName} | ${orb}° |`);
749
739
  });
750
740
  }
751
741
 
@@ -758,107 +748,7 @@ function showPlanetComboAspects(planetNames, dateComponents, useBirthData = fals
758
748
  }
759
749
  }
760
750
 
761
- // Helper function to calculate exact date and time for an exact aspect
762
- function findExactAspectTime(planet1, planet2, aspectType, startDate) {
763
- const targetAngle = getAspectAngle(aspectType);
764
-
765
- if (targetAngle === null) {
766
- return null;
767
- }
768
-
769
- // We search in a reasonable time window (90 days in the future)
770
- // to find the next exact aspect
771
- // Especially important for slow planets like Saturn, Uranus, Neptune, Pluto
772
- let bestMatch = null;
773
- let bestDistance = Infinity;
774
-
775
- // Search only in the future (0-90 days after start date)
776
- const searchWindow = 90; // days
777
-
778
- // We search in smaller steps (every 2 hours) to get more precise results
779
- const stepsPerDay = 12; // 2-hour steps
780
-
781
- for (let daysOffset = 0; daysOffset <= searchWindow; daysOffset++) {
782
- for (let hourOffset = 0; hourOffset < 24; hourOffset++) { // Every hour
783
- for (let minuteOffset = 0; minuteOffset < 60; minuteOffset += 5) { // Every 5 minutes
784
- const testDate = addDays(startDate, daysOffset);
785
- testDate.hour = hourOffset;
786
- testDate.minute = minuteOffset;
787
-
788
- // Calculate positions of both planets
789
- const planet1Data = getAstrologicalData(planet1, testDate);
790
- const planet2Data = getAstrologicalData(planet2, testDate);
791
-
792
- // Calculate angle between planets
793
- const angleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
794
- const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
795
- const distanceToTarget = Math.abs(normalizedAngle - targetAngle);
796
-
797
- // Store the best result
798
- if (distanceToTarget < bestDistance) {
799
- bestDistance = distanceToTarget;
800
- bestMatch = {...testDate};
801
- }
802
- }
803
- }
804
- }
805
-
806
- // If we found an aspect close enough to the target, return it
807
- // Stricter tolerance of 0.5° for better accuracy
808
- if (bestMatch && bestDistance <= 0.5) { // Tolerance of 0.5°
809
- return bestMatch;
810
- }
811
-
812
- return null;
813
- }
814
751
 
815
- // Function to find the last exact aspect in the past (for separative phases)
816
- function findLastExactAspectTime(planet1, planet2, aspectType, startDate) {
817
- const targetAngle = getAspectAngle(aspectType);
818
-
819
- if (targetAngle === null) {
820
- return null;
821
- }
822
-
823
- // Search in the past (up to 180 days before start date)
824
- let bestMatch = null;
825
- let bestDistance = Infinity;
826
-
827
- // Search in a time window of 180 days before start date
828
- const searchWindow = 180; // days
829
-
830
- for (let daysOffset = -searchWindow; daysOffset <= 0; daysOffset++) {
831
- for (let hourOffset = 0; hourOffset < 24; hourOffset++) { // Every hour
832
- for (let minuteOffset = 0; minuteOffset < 60; minuteOffset += 5) { // Every 5 minutes
833
- const testDate = addDays(startDate, daysOffset);
834
- testDate.hour = hourOffset;
835
- testDate.minute = minuteOffset;
836
-
837
- // Calculate positions of both planets
838
- const planet1Data = getAstrologicalData(planet1, testDate);
839
- const planet2Data = getAstrologicalData(planet2, testDate);
840
-
841
- // Calculate angle between planets
842
- const angleDiff = Math.abs(planet1Data.longitude - planet2Data.longitude) % 360;
843
- const normalizedAngle = Math.min(angleDiff, 360 - angleDiff);
844
- const distanceToTarget = Math.abs(normalizedAngle - targetAngle);
845
-
846
- // Store the best result
847
- if (distanceToTarget < bestDistance) {
848
- bestDistance = distanceToTarget;
849
- bestMatch = {...testDate};
850
- }
851
- }
852
- }
853
- }
854
-
855
- // If we found an aspect close enough to the target, return it
856
- if (bestMatch && bestDistance <= 0.5) { // Tolerance of 0.5°
857
- return bestMatch;
858
- }
859
-
860
- return null;
861
- }
862
752
 
863
753
  // Function to calculate all active aspects between all planets
864
754
  function getAllActiveAspects(dateComponents) {
@@ -1775,7 +1665,7 @@ function showAllActiveAspects(dateComponents, useBirthData = false) {
1775
1665
 
1776
1666
  console.log('Active Aspects (all planets) - Classified by Precision:');
1777
1667
  console.log('═══════════════════════════════════════════════════════════════════════════');
1778
- console.log('║ Planet 1 │ Planet 2 │ Orb │ Status │ Exact Date/Time ║');
1668
+ console.log('║ Planet 1 │ Planet 2 │ Orb │ Status ║');
1779
1669
  console.log('═══════════════════════════════════════════════════════════════════════════');
1780
1670
 
1781
1671
  if (sortedAspects.length === 0) {
@@ -1826,14 +1716,10 @@ function showAllActiveAspects(dateComponents, useBirthData = false) {
1826
1716
  );
1827
1717
  const statusPadded = phase.padEnd(11, ' ');
1828
1718
 
1829
- // Calculate the exact date and time for this aspect
1830
- const exactDateTime = findExactAspectTime(aspect.planet1, aspect.planet2, aspect.type, dateComponents);
1831
- let dateTimeStr = '-';
1832
- if (exactDateTime) {
1833
- dateTimeStr = `${String(exactDateTime.day).padStart(2, '0')}.${String(exactDateTime.month).padStart(2, '0')}.${exactDateTime.year} ${String(exactDateTime.hour).padStart(2, '0')}:${String(exactDateTime.minute).padStart(2, '0')}`;
1834
- }
1719
+ // Exact time calculation removed
1720
+ const dateTimeStr = '-';
1835
1721
 
1836
- console.log(`║ ${planet1Padded} │ ${planet2Padded} │ ${orb}° │ ${statusPadded} │ ${dateTimeStr} ║`);
1722
+ console.log(`║ ${planet1Padded} │ ${planet2Padded} │ ${orb}° │ ${statusPadded} ║`);
1837
1723
  });
1838
1724
  }
1839
1725
  });
@@ -2953,8 +2839,6 @@ module.exports = {
2953
2839
  getPastAspects,
2954
2840
  getAspectAngle,
2955
2841
  getFutureAspects,
2956
- findExactAspectTime,
2957
- findLastExactAspectTime,
2958
2842
  analyzeCSVWithDatetime,
2959
2843
  detectAspectFigures,
2960
2844
  showAspectFigures,