klio 1.4.4 → 1.4.5
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 +9 -0
- package/package.json +1 -1
- package/src/cli/cli.js +115 -0
- package/src/wikidata/wikidataService.js +228 -0
package/README.md
CHANGED
|
@@ -129,6 +129,15 @@ Then, instead using `--i` for the commands from above you can use `--wp <id>` i.
|
|
|
129
129
|
- Example: `klio saturn --in 2` - Shows next 2 Saturn ingresses (note: slow planets may have limited future data)
|
|
130
130
|
- Example: `klio mercury --in --d "15.03.2026"` - Shows next Mercury ingress from a specific date
|
|
131
131
|
|
|
132
|
+
### Wikidata Integration
|
|
133
|
+
|
|
134
|
+
- **Search for people with specific aspects**: `[planet1] [aspect-type] [planet2] --wiki <occupation> [limit]` - Searches Wikidata for people with specific astrological aspects
|
|
135
|
+
- **Available occupations**: authors, scientists, artists, musicians, politicians, actors, philosophers
|
|
136
|
+
- **Aspect types**: c (conjunction), o (opposition), s (square), t (trine), se (sextile), q (quincunx)
|
|
137
|
+
- **Example**: `klio moon c neptune --wiki authors 50` - Tries to find authors with Moon conjunct Neptune aspect with a limit of 50. Is faster but less common aspects are maybe not found with 50
|
|
138
|
+
- **Example**: `klio saturn s pluto --wiki scientists 100` - Finds scientists with Saturn square Pluto aspect
|
|
139
|
+
- **Example**: `klio sun t moon --wiki artists 200` - Finds artists with Sun trine Moon aspect
|
|
140
|
+
|
|
132
141
|
### AI Integration
|
|
133
142
|
|
|
134
143
|
- **AI model selection**: `--ai <model>` - Sets a specific AI model (e.g., "google/gemma-3n-e4b") for LM Studio ([lmstudio.ai](https://lmstudio.ai))
|
package/package.json
CHANGED
package/src/cli/cli.js
CHANGED
|
@@ -11,6 +11,15 @@ const swisseph = require('swisseph');
|
|
|
11
11
|
const path = require('path');
|
|
12
12
|
const fs = require('fs');
|
|
13
13
|
|
|
14
|
+
// Wikidata service import
|
|
15
|
+
let wikidataService = null;
|
|
16
|
+
try {
|
|
17
|
+
wikidataService = require('../wikidata/wikidataService');
|
|
18
|
+
} catch (error) {
|
|
19
|
+
// Wikidata service not available
|
|
20
|
+
console.debug('Wikidata service not available');
|
|
21
|
+
}
|
|
22
|
+
|
|
14
23
|
// GUI Server import
|
|
15
24
|
let guiServer = null;
|
|
16
25
|
try {
|
|
@@ -255,6 +264,7 @@ program
|
|
|
255
264
|
.option('--z <count>', 'Shows future aspects between two planets (Format: --z <count> planet1 aspectType planet2)')
|
|
256
265
|
.option('--csv <filepath>', 'Analyzes a CSV file with ISO-Datetime values or Unix timestamps')
|
|
257
266
|
.option('--in [count]', 'Shows next planet ingress (entering new sign). Optional count for multiple ingresses')
|
|
267
|
+
.option('--wiki <occupation>', 'Fetches people from Wikidata by occupation and checks for specific aspects (Format: planet1 aspectType planet2 --wiki <occupation> [limit])')
|
|
258
268
|
.option('--gui', 'Launches the web interface for command history (port 37421)')
|
|
259
269
|
.option('--gui-port <port>', 'Specify custom port for GUI server')
|
|
260
270
|
.description('Shows astrological data for a planet')
|
|
@@ -1112,6 +1122,111 @@ program
|
|
|
1112
1122
|
return;
|
|
1113
1123
|
}
|
|
1114
1124
|
|
|
1125
|
+
// Handle Wikidata people search if --wiki option is specified
|
|
1126
|
+
if (options.wiki) {
|
|
1127
|
+
// For the wiki command, the format is: planet1 aspectType planet2 --wiki <occupation> [limit]
|
|
1128
|
+
// But the argument parsing is: planetArg=planet1, planet2=aspectType, and options.wiki=planet2
|
|
1129
|
+
// We need to extract the occupation and planet2 from the remaining arguments
|
|
1130
|
+
|
|
1131
|
+
if (!planetArg || !planet2 || !options.wiki) {
|
|
1132
|
+
console.error('Error: Wikidata search requires format: planet1 aspectType planet2 --wiki <occupation> [limit]');
|
|
1133
|
+
console.error('Example: klio saturn conjunction neptune --wiki authors');
|
|
1134
|
+
console.error('Available occupations:', Object.keys(wikidataService.PREDEFINED_OCCUPATIONS || {}).join(', '));
|
|
1135
|
+
process.exit(1);
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
const planet1 = planetArg.toLowerCase();
|
|
1139
|
+
const aspectType = planet2.toLowerCase();
|
|
1140
|
+
|
|
1141
|
+
// The occupation is in options.wiki, but we need to find the actual planet2
|
|
1142
|
+
// Let's look at the original arguments to parse this correctly
|
|
1143
|
+
const args = process.argv.slice(2); // Skip 'node' and script name
|
|
1144
|
+
|
|
1145
|
+
// Find the position of --wiki
|
|
1146
|
+
const wikiIndex = args.findIndex(arg => arg === '--wiki');
|
|
1147
|
+
if (wikiIndex === -1 || wikiIndex + 1 >= args.length) {
|
|
1148
|
+
console.error('Error: Missing occupation after --wiki flag');
|
|
1149
|
+
process.exit(1);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const occupation = args[wikiIndex + 1].toLowerCase();
|
|
1153
|
+
|
|
1154
|
+
// The planet2 should be the argument before --wiki
|
|
1155
|
+
const planet2Name = args[wikiIndex - 1].toLowerCase();
|
|
1156
|
+
|
|
1157
|
+
// Validate planets
|
|
1158
|
+
if (planets[planet1] === undefined) {
|
|
1159
|
+
console.error(`Error: Invalid planet '${planet1}'. Available planets: ${Object.keys(planets).join(', ')}`);
|
|
1160
|
+
process.exit(1);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (planets[planet2Name] === undefined) {
|
|
1164
|
+
console.error(`Error: Invalid planet '${planet2Name}'. Available planets: ${Object.keys(planets).join(', ')}`);
|
|
1165
|
+
process.exit(1);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// Validate aspect type (including shorthands)
|
|
1169
|
+
const validAspects = ['conjunction', 'opposition', 'square', 'trine', 'sextile', 'quincunx', 'c', 'con', 'opp', 'sq', 'tri', 'sex', 'qui'];
|
|
1170
|
+
if (!validAspects.includes(aspectType)) {
|
|
1171
|
+
console.error(`Error: Invalid aspect type '${aspectType}'. Available aspects: ${['conjunction', 'opposition', 'square', 'trine', 'sextile', 'quincunx'].join(', ')}`);
|
|
1172
|
+
console.error('Shorthands: c/con, opp, sq, tri, sex, qui');
|
|
1173
|
+
process.exit(1);
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// Parse limit if provided (look for arguments after occupation)
|
|
1177
|
+
let limit = 500;
|
|
1178
|
+
if (wikiIndex + 2 < args.length) {
|
|
1179
|
+
const limitArg = args[wikiIndex + 2];
|
|
1180
|
+
if (!isNaN(limitArg) && parseInt(limitArg) > 0) {
|
|
1181
|
+
limit = parseInt(limitArg);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
console.log(`🔍 Searching Wikidata for ${occupation} with ${planet1} ${aspectType} ${planet2Name} aspect...`);
|
|
1186
|
+
console.log(`📊 Limit: ${limit} people`);
|
|
1187
|
+
|
|
1188
|
+
if (!wikidataService) {
|
|
1189
|
+
console.error('❌ Wikidata service is not available.');
|
|
1190
|
+
process.exit(1);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
try {
|
|
1194
|
+
const results = await wikidataService.findPeopleWithAspect(occupation, planet1, aspectType, planet2Name, limit);
|
|
1195
|
+
|
|
1196
|
+
if (results.length === 0) {
|
|
1197
|
+
console.log('No results found.');
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Display results in a table
|
|
1202
|
+
console.log('\n📋 Results (sorted by orb, closest aspects first):');
|
|
1203
|
+
console.log('================================================================================');
|
|
1204
|
+
console.log('| # | Name | Birth Date | Aspect Type | Orb | Wikidata Link |');
|
|
1205
|
+
console.log('================================================================================');
|
|
1206
|
+
|
|
1207
|
+
results.forEach((result, index) => {
|
|
1208
|
+
const name = result.name.padEnd(28, ' ').substring(0, 28);
|
|
1209
|
+
const birthDate = result.birthDate ? result.birthDate.substring(0, 10) : 'Unknown';
|
|
1210
|
+
const aspectTypeDisplay = result.aspect.type.padEnd(11, ' ');
|
|
1211
|
+
const orb = result.aspect.orb.padEnd(4, ' ');
|
|
1212
|
+
const linkUrl = result.linkUrl || 'N/A';
|
|
1213
|
+
|
|
1214
|
+
console.log(`| ${(index + 1).toString().padEnd(2)} | ${name} | ${birthDate} | ${aspectTypeDisplay} | ${orb}° | ${linkUrl} |`);
|
|
1215
|
+
});
|
|
1216
|
+
|
|
1217
|
+
console.log('================================================================================');
|
|
1218
|
+
console.log(`\n📊 Found ${results.length} ${occupation} with ${planet1} ${aspectType} ${planet2Name} aspect.`);
|
|
1219
|
+
console.log('🔗 Wikipedia search links are provided for each person.');
|
|
1220
|
+
console.log('🎯 Results are sorted by orb (closest aspects first).');
|
|
1221
|
+
|
|
1222
|
+
} catch (error) {
|
|
1223
|
+
console.error('❌ Error searching Wikidata:', error.message);
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1115
1230
|
// For other options, a planet is required (except for --a or --s without planet)
|
|
1116
1231
|
if (!planetArg && !options.a && !options.s) {
|
|
1117
1232
|
console.error('Error: Planet is required for this operation.');
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// Wikidata Service for fetching author data and checking astrological aspects
|
|
2
|
+
const axios = require('axios');
|
|
3
|
+
const { calculatePlanetComboAspects } = require('../astrology/astrologyService');
|
|
4
|
+
|
|
5
|
+
// Wikidata SPARQL endpoint
|
|
6
|
+
const WIKIDATA_SPARQL_ENDPOINT = 'https://query.wikidata.org/sparql';
|
|
7
|
+
|
|
8
|
+
// Predefined occupations with their Wikidata Q IDs
|
|
9
|
+
const PREDEFINED_OCCUPATIONS = {
|
|
10
|
+
'authors': 'wd:Q36180', // Writer
|
|
11
|
+
'scientists': 'wd:Q901', // Scientist
|
|
12
|
+
'artists': 'wd:Q1028181', // Visual artist
|
|
13
|
+
'musicians': 'wd:Q639669', // Musician
|
|
14
|
+
'politicians': 'wd:Q82955', // Politician
|
|
15
|
+
'actors': 'wd:Q33999', // Actor
|
|
16
|
+
'philosophers': 'wd:Q4964182' // Philosopher
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Function to fetch people by occupation from Wikidata
|
|
20
|
+
async function fetchPeopleByOccupation(occupation, limit = 500) {
|
|
21
|
+
try {
|
|
22
|
+
// Get the Wikidata Q ID for the occupation
|
|
23
|
+
const occupationQid = PREDEFINED_OCCUPATIONS[occupation.toLowerCase()];
|
|
24
|
+
|
|
25
|
+
if (!occupationQid) {
|
|
26
|
+
console.error(`Error: Unknown occupation '${occupation}'. Available occupations: ${Object.keys(PREDEFINED_OCCUPATIONS).join(', ')}`);
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const query = `
|
|
31
|
+
SELECT DISTINCT ?person ?personLabel ?birthDate ?wikipediaLink WHERE {
|
|
32
|
+
?person wdt:P31 wd:Q5; # Instance of human
|
|
33
|
+
wdt:P106 ${occupationQid}. # Specific occupation
|
|
34
|
+
|
|
35
|
+
# Make birth date optional
|
|
36
|
+
OPTIONAL { ?person wdt:P569 ?birthDate. }
|
|
37
|
+
|
|
38
|
+
# Get English Wikipedia article link
|
|
39
|
+
OPTIONAL { ?wikipediaArticle schema:about ?person ;
|
|
40
|
+
schema:isPartOf <https://en.wikipedia.org/> ;
|
|
41
|
+
schema:url ?wikipediaLink . }
|
|
42
|
+
|
|
43
|
+
# Get label
|
|
44
|
+
SERVICE wikibase:label { bd:serviceParam wikibase:language "en". }
|
|
45
|
+
}
|
|
46
|
+
LIMIT ${limit}
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
const response = await axios.get(WIKIDATA_SPARQL_ENDPOINT, {
|
|
50
|
+
params: {
|
|
51
|
+
query: query,
|
|
52
|
+
format: 'json'
|
|
53
|
+
},
|
|
54
|
+
headers: {
|
|
55
|
+
'Accept': 'application/sparql-results+json'
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (response.data && response.data.results && response.data.results.bindings) {
|
|
60
|
+
return response.data.results.bindings.map(binding => ({
|
|
61
|
+
name: binding.personLabel?.value || 'Unknown',
|
|
62
|
+
birthDate: binding.birthDate?.value || null,
|
|
63
|
+
wikidataId: binding.person.value.split('/').pop()
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return [];
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error('Error fetching people from Wikidata:', error.message);
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Backward compatibility function
|
|
75
|
+
async function fetchAuthorsFromWikidata(limit = 500) {
|
|
76
|
+
return fetchPeopleByOccupation('authors', limit);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Function to parse birth date and extract astrological data
|
|
80
|
+
function parseBirthDate(birthDateString) {
|
|
81
|
+
if (!birthDateString) return null;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
// Parse ISO date string (format: +YYYY-MM-DDThh:mm:ssZ or YYYY-MM-DDThh:mm:ssZ or +YYYY-MM-DD or YYYY-MM-DD or +YYYY-MM or YYYY-MM or +YYYY or YYYY)
|
|
85
|
+
const dateMatch = birthDateString.match(/^\+?(\d{4})(?:-(\d{1,2})(?:-(\d{1,2}))?)?/);
|
|
86
|
+
if (!dateMatch) return null;
|
|
87
|
+
|
|
88
|
+
const year = parseInt(dateMatch[1]);
|
|
89
|
+
const month = dateMatch[2] ? parseInt(dateMatch[2]) : 1; // Default to January if month not specified
|
|
90
|
+
const day = dateMatch[3] ? parseInt(dateMatch[3]) : 1; // Default to 1st if day not specified
|
|
91
|
+
|
|
92
|
+
// Handle invalid month/day values (like 00 for century-level dates)
|
|
93
|
+
const validMonth = month === 0 ? 1 : month;
|
|
94
|
+
const validDay = day === 0 ? 1 : day;
|
|
95
|
+
|
|
96
|
+
// Default time to noon if not specified
|
|
97
|
+
return {
|
|
98
|
+
year: year,
|
|
99
|
+
month: validMonth,
|
|
100
|
+
day: validDay,
|
|
101
|
+
hour: 12,
|
|
102
|
+
minute: 0
|
|
103
|
+
};
|
|
104
|
+
} catch (error) {
|
|
105
|
+
console.error('Error parsing birth date:', error.message);
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Aspect type mapping for shorthand support
|
|
111
|
+
const ASPECT_SHORTHAND = {
|
|
112
|
+
'c': 'conjunction',
|
|
113
|
+
'o': 'opposition',
|
|
114
|
+
's': 'square',
|
|
115
|
+
't': 'trine',
|
|
116
|
+
'se': 'sextile',
|
|
117
|
+
'q': 'quincunx'
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Function to create a Wikipedia search URL from a person's name
|
|
121
|
+
function createWikipediaSearchUrl(personName) {
|
|
122
|
+
// Create a Wikipedia search URL using the person's name
|
|
123
|
+
// This will take users to a search page where they can find the correct article
|
|
124
|
+
const encodedName = encodeURIComponent(personName);
|
|
125
|
+
return `https://en.wikipedia.org/wiki/Special:Search?search=${encodedName}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Function to normalize aspect type (support shorthands)
|
|
129
|
+
function normalizeAspectType(aspectType) {
|
|
130
|
+
const normalized = aspectType.toLowerCase();
|
|
131
|
+
return ASPECT_SHORTHAND[normalized] || normalized;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Function to check if an author has a specific aspect in their natal chart
|
|
135
|
+
function checkAuthorAspect(authorData, planet1, aspectType, planet2) {
|
|
136
|
+
if (!authorData.birthDateData) return null;
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Normalize aspect type to handle shorthands
|
|
140
|
+
const normalizedAspectType = normalizeAspectType(aspectType);
|
|
141
|
+
|
|
142
|
+
// Calculate aspects for this author's birth chart
|
|
143
|
+
const aspects = calculatePlanetComboAspects([planet1, planet2], authorData.birthDateData, true);
|
|
144
|
+
|
|
145
|
+
// Look for the specific aspect type
|
|
146
|
+
const matchingAspect = aspects.find(aspect =>
|
|
147
|
+
((aspect.planet1 === planet1 && aspect.planet2 === planet2) ||
|
|
148
|
+
(aspect.planet1 === planet2 && aspect.planet2 === planet1)) &&
|
|
149
|
+
aspect.type.toLowerCase() === normalizedAspectType.toLowerCase()
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return matchingAspect || null;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error('Error checking aspect for author:', authorData.name, error.message);
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Main function to find people with specific aspects by occupation
|
|
160
|
+
async function findPeopleWithAspect(occupation, planet1, aspectType, planet2, limit = 500) {
|
|
161
|
+
// Fetch people by occupation from Wikidata
|
|
162
|
+
const people = await fetchPeopleByOccupation(occupation, limit);
|
|
163
|
+
|
|
164
|
+
if (people.length === 0) {
|
|
165
|
+
console.log(`No ${occupation} found in Wikidata.`);
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log(`Fetched ${people.length} ${occupation} from Wikidata. Checking aspects...`);
|
|
170
|
+
|
|
171
|
+
// Process each person to check for the specified aspect
|
|
172
|
+
const results = [];
|
|
173
|
+
let processedCount = 0;
|
|
174
|
+
|
|
175
|
+
for (const person of people) {
|
|
176
|
+
// Parse birth date
|
|
177
|
+
const birthDateData = parseBirthDate(person.birthDate);
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
// Add birth date data to person object
|
|
181
|
+
person.birthDateData = birthDateData;
|
|
182
|
+
|
|
183
|
+
// Check for the specific aspect
|
|
184
|
+
const aspect = checkAuthorAspect(person, planet1, aspectType, planet2);
|
|
185
|
+
|
|
186
|
+
if (aspect) {
|
|
187
|
+
// Create Wikipedia search URL using the person's name
|
|
188
|
+
const linkUrl = createWikipediaSearchUrl(person.name);
|
|
189
|
+
|
|
190
|
+
results.push({
|
|
191
|
+
name: person.name,
|
|
192
|
+
birthDate: person.birthDate,
|
|
193
|
+
linkUrl: linkUrl,
|
|
194
|
+
aspect: aspect,
|
|
195
|
+
birthDateData: person.birthDateData
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
processedCount++;
|
|
200
|
+
if (processedCount % 50 === 0) {
|
|
201
|
+
console.log(`Processed ${processedCount}/${people.length} ${occupation}...`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
console.log(`Found ${results.length} ${occupation} with ${planet1} ${aspectType} ${planet2} aspect.`);
|
|
206
|
+
|
|
207
|
+
// Sort by orb (closest aspects first)
|
|
208
|
+
results.sort((a, b) => parseFloat(a.aspect.orb) - parseFloat(b.aspect.orb));
|
|
209
|
+
|
|
210
|
+
return results;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Backward compatibility function
|
|
214
|
+
async function findAuthorsWithAspect(planet1, aspectType, planet2, limit = 500) {
|
|
215
|
+
return findPeopleWithAspect('authors', planet1, aspectType, planet2, limit);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = {
|
|
219
|
+
fetchAuthorsFromWikidata,
|
|
220
|
+
findAuthorsWithAspect,
|
|
221
|
+
fetchPeopleByOccupation,
|
|
222
|
+
findPeopleWithAspect,
|
|
223
|
+
parseBirthDate,
|
|
224
|
+
checkAuthorAspect,
|
|
225
|
+
normalizeAspectType,
|
|
226
|
+
ASPECT_SHORTHAND,
|
|
227
|
+
PREDEFINED_OCCUPATIONS
|
|
228
|
+
};
|