@openephemeris/mcp-server 3.13.9 → 3.13.10
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 +2 -2
- package/config/dev-allowlist.json +28 -4
- package/dist/index.js +45 -0
- package/dist/oauth/dcr.d.ts +11 -0
- package/dist/oauth/dcr.js +49 -0
- package/dist/oauth/discovery.d.ts +11 -0
- package/dist/oauth/discovery.js +40 -0
- package/dist/oauth/pkce.d.ts +18 -0
- package/dist/oauth/pkce.js +34 -0
- package/dist/oauth/rate-limit.d.ts +20 -0
- package/dist/oauth/rate-limit.js +65 -0
- package/dist/oauth/store.d.ts +68 -0
- package/dist/oauth/store.js +247 -0
- package/dist/oauth/supabase-jwt.d.ts +39 -0
- package/dist/oauth/supabase-jwt.js +194 -0
- package/dist/oauth/token.d.ts +21 -0
- package/dist/oauth/token.js +213 -0
- package/dist/prompts.js +171 -159
- package/dist/server-sse.js +134 -36
- package/dist/tools/apps/bazi-app.d.ts +23 -0
- package/dist/tools/apps/bazi-app.js +224 -0
- package/dist/tools/apps/bi-wheel-app.d.ts +14 -7
- package/dist/tools/apps/bi-wheel-app.js +293 -108
- package/dist/tools/apps/bodygraph-app.js +38 -3
- package/dist/tools/apps/moon-phase-app.d.ts +19 -0
- package/dist/tools/apps/moon-phase-app.js +190 -0
- package/dist/tools/apps/transit-timeline-app.d.ts +20 -0
- package/dist/tools/apps/transit-timeline-app.js +243 -0
- package/dist/tools/apps/vedic-chart-app.d.ts +17 -0
- package/dist/tools/apps/vedic-chart-app.js +226 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/specialized/acg.js +2 -2
- package/dist/tools/specialized/bazi.js +367 -44
- package/dist/tools/specialized/bi_wheel.js +1 -1
- package/dist/tools/specialized/chart_wheel.js +1 -1
- package/dist/tools/specialized/comparative.js +4 -4
- package/dist/tools/specialized/eclipse.js +1 -1
- package/dist/tools/specialized/electional.js +4 -4
- package/dist/tools/specialized/ephemeris_core.js +2 -2
- package/dist/tools/specialized/ephemeris_extended.js +8 -8
- package/dist/tools/specialized/hd_bodygraph.js +1 -1
- package/dist/tools/specialized/hd_cycles.js +2 -2
- package/dist/tools/specialized/hd_group.js +2 -2
- package/dist/tools/specialized/human_design.js +1 -1
- package/dist/tools/specialized/moon.js +2 -2
- package/dist/tools/specialized/natal.js +1 -1
- package/dist/tools/specialized/progressed.js +1 -1
- package/dist/tools/specialized/relocation.js +1 -1
- package/dist/tools/specialized/returns.js +3 -3
- package/dist/tools/specialized/synastry.js +1 -1
- package/dist/tools/specialized/transits.js +1 -1
- package/dist/tools/specialized/vedic.js +1 -1
- package/dist/tools/specialized/venus_star_points.js +6 -6
- package/dist/ui/bazi.html +213 -0
- package/dist/ui/bi-wheel.html +762 -489
- package/dist/ui/bodygraph.html +1788 -1649
- package/dist/ui/chart-wheel.html +1772 -1593
- package/dist/ui/moon-phase.html +6758 -0
- package/dist/ui/transit-timeline.html +6835 -0
- package/dist/ui/vedic-chart.html +210 -0
- package/package.json +2 -2
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* bi-wheel-app.ts — MCP App tool registration for the Bi-Wheel Explorer.
|
|
3
3
|
*
|
|
4
|
-
* Registers
|
|
5
|
-
* • explore_bi_wheel — primary entry point
|
|
6
|
-
*
|
|
4
|
+
* Registers 6 tools:
|
|
5
|
+
* • explore_bi_wheel — primary entry point [model + app]
|
|
6
|
+
* • bi_wheel_recalculate — re-fetch with new params [app-only]
|
|
7
7
|
* • bi_wheel_on_cross_aspect_click — cross-aspect click handler [app-only]
|
|
8
|
-
* • bi_wheel_on_planet_click — planet click handler
|
|
8
|
+
* • bi_wheel_on_planet_click — planet click handler [app-only]
|
|
9
|
+
* • bi_wheel_on_house_click — house cusp click handler [app-only]
|
|
10
|
+
* • bi_wheel_synopsis — full reading overview [model + app]
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
* synastry
|
|
12
|
-
* transit
|
|
12
|
+
* Supported modes (BiWheelMode):
|
|
13
|
+
* synastry — two natal charts (person1 inner, person2 outer)
|
|
14
|
+
* transit — natal inner + transiting planets outer
|
|
15
|
+
* progressed — natal inner + secondary progressions outer (POST /ephemeris/progressed)
|
|
16
|
+
* solar_return — natal inner + solar return chart outer (POST /predictive/returns/solar)
|
|
17
|
+
* lunar_return — natal inner + lunar return chart outer (POST /predictive/returns/lunar)
|
|
18
|
+
* solar_arc — natal inner + solar arc directions outer (client-side Naibod approximation)
|
|
13
19
|
*
|
|
14
20
|
* Cross-aspects are computed client-side (UI) AND server-side (for summary/fallback).
|
|
15
21
|
* The payload cross_aspects array is capped at 30 tightest-orb aspects.
|
|
@@ -42,6 +48,53 @@ export function getBiWheelBundle() {
|
|
|
42
48
|
export function clearBiWheelBundleCache() {
|
|
43
49
|
cachedBiWheelBundle = null;
|
|
44
50
|
}
|
|
51
|
+
const ALL_MODES = [
|
|
52
|
+
"synastry", "transit", "progressed", "solar_return", "lunar_return", "solar_arc",
|
|
53
|
+
];
|
|
54
|
+
const MODE_META = {
|
|
55
|
+
synastry: {
|
|
56
|
+
outerLabel: (_, name) => name ?? "Person 2",
|
|
57
|
+
innerRole: "Person 1 (inner)",
|
|
58
|
+
outerRole: "Person 2 (outer)",
|
|
59
|
+
credits: 1,
|
|
60
|
+
modeLabel: "Synastry",
|
|
61
|
+
},
|
|
62
|
+
transit: {
|
|
63
|
+
outerLabel: (dt) => `Transit · ${dt.slice(0, 10)}`,
|
|
64
|
+
innerRole: "Natal (inner)",
|
|
65
|
+
outerRole: "Transits (outer)",
|
|
66
|
+
credits: 1,
|
|
67
|
+
modeLabel: "Transit",
|
|
68
|
+
},
|
|
69
|
+
progressed: {
|
|
70
|
+
outerLabel: (dt) => `Progressions · ${dt.slice(0, 10)}`,
|
|
71
|
+
innerRole: "Natal (inner)",
|
|
72
|
+
outerRole: "Progressed (outer)",
|
|
73
|
+
credits: 1,
|
|
74
|
+
modeLabel: "Secondary Progressions",
|
|
75
|
+
},
|
|
76
|
+
solar_return: {
|
|
77
|
+
outerLabel: (dt) => `Solar Return · ${dt.slice(0, 4)}`,
|
|
78
|
+
innerRole: "Natal (inner)",
|
|
79
|
+
outerRole: "Solar Return (outer)",
|
|
80
|
+
credits: 5,
|
|
81
|
+
modeLabel: "Solar Return",
|
|
82
|
+
},
|
|
83
|
+
lunar_return: {
|
|
84
|
+
outerLabel: (dt) => `Lunar Return · ${dt.slice(0, 10)}`,
|
|
85
|
+
innerRole: "Natal (inner)",
|
|
86
|
+
outerRole: "Lunar Return (outer)",
|
|
87
|
+
credits: 5,
|
|
88
|
+
modeLabel: "Lunar Return",
|
|
89
|
+
},
|
|
90
|
+
solar_arc: {
|
|
91
|
+
outerLabel: (dt) => `Solar Arc · ${dt.slice(0, 10)}`,
|
|
92
|
+
innerRole: "Natal (inner)",
|
|
93
|
+
outerRole: "Solar Arc Directions (outer)",
|
|
94
|
+
credits: 1,
|
|
95
|
+
modeLabel: "Solar Arc Directions",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
45
98
|
// ── House system helpers (shared with chart-wheel) ────────────────────────────
|
|
46
99
|
const HOUSE_SYSTEM_MAP = {
|
|
47
100
|
placidus: "P", whole_sign: "W", equal: "E", koch: "K",
|
|
@@ -81,6 +134,108 @@ function buildTransitBody(transitDate, natalDatetime, lat, lon, timezone) {
|
|
|
81
134
|
configuration: { house_system: HOUSE_SYSTEM_MAP["placidus"] },
|
|
82
135
|
};
|
|
83
136
|
}
|
|
137
|
+
/** POST /ephemeris/progressed — secondary progressions */
|
|
138
|
+
function buildProgressedBody(natalDatetime, lat, lon, timezone, targetDatetime) {
|
|
139
|
+
const body = {
|
|
140
|
+
subject: {
|
|
141
|
+
name: "MCP Request",
|
|
142
|
+
birth_datetime: { iso: natalDatetime },
|
|
143
|
+
birth_location: {
|
|
144
|
+
latitude: { decimal: lat ?? 0 },
|
|
145
|
+
longitude: { decimal: lon ?? 0 },
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
configuration: {
|
|
149
|
+
target_datetime: { iso: targetDatetime },
|
|
150
|
+
method: "secondary",
|
|
151
|
+
house_system: HOUSE_SYSTEM_MAP["placidus"],
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
if (timezone) {
|
|
155
|
+
body.subject.birth_location.timezone = { iana_name: timezone };
|
|
156
|
+
}
|
|
157
|
+
return body;
|
|
158
|
+
}
|
|
159
|
+
/** POST /predictive/returns/solar or /predictive/returns/lunar */
|
|
160
|
+
function buildReturnBody(natalDatetime, natalLat, natalLon, timezone, targetDatetime, returnLat, returnLon) {
|
|
161
|
+
const body = {
|
|
162
|
+
subject: {
|
|
163
|
+
name: "MCP Request",
|
|
164
|
+
birth_datetime: { iso: natalDatetime },
|
|
165
|
+
birth_location: {
|
|
166
|
+
latitude: { decimal: natalLat ?? 0 },
|
|
167
|
+
longitude: { decimal: natalLon ?? 0 },
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
return_location: {
|
|
171
|
+
latitude: { decimal: returnLat ?? natalLat ?? 0 },
|
|
172
|
+
longitude: { decimal: returnLon ?? natalLon ?? 0 },
|
|
173
|
+
},
|
|
174
|
+
target_datetime: { iso: targetDatetime },
|
|
175
|
+
configuration: { house_system: HOUSE_SYSTEM_MAP["placidus"] },
|
|
176
|
+
};
|
|
177
|
+
if (timezone) {
|
|
178
|
+
body.subject.birth_location.timezone = { iana_name: timezone };
|
|
179
|
+
}
|
|
180
|
+
return body;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Client-side solar arc: advance every natal planet longitude by elapsed years in degrees.
|
|
184
|
+
* Uses the Naibod rate (1 tropical year ≈ 0.9856°). The natal data object is cloned and
|
|
185
|
+
* the planets array/map is offset — houses are left at natal positions (standard practice).
|
|
186
|
+
*/
|
|
187
|
+
function applySolarArcOffset(natalData, natalDatetime, targetDatetime) {
|
|
188
|
+
const natalYear = new Date(natalDatetime).getFullYear() +
|
|
189
|
+
new Date(natalDatetime).getMonth() / 12;
|
|
190
|
+
const targetYear = new Date(targetDatetime).getFullYear() +
|
|
191
|
+
new Date(targetDatetime).getMonth() / 12;
|
|
192
|
+
const arcDegrees = (targetYear - natalYear) * 0.9856; // Naibod rate
|
|
193
|
+
const offsetPlanets = (raw) => {
|
|
194
|
+
if (Array.isArray(raw)) {
|
|
195
|
+
return raw.map((p) => ({
|
|
196
|
+
...p,
|
|
197
|
+
longitude: ((p.longitude ?? 0) + arcDegrees + 360) % 360,
|
|
198
|
+
}));
|
|
199
|
+
}
|
|
200
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
201
|
+
const out = {};
|
|
202
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
203
|
+
if (v && typeof v === "object" && "longitude" in v) {
|
|
204
|
+
out[k] = { ...v, longitude: ((v.longitude ?? 0) + arcDegrees + 360) % 360 };
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
out[k] = v;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
return raw;
|
|
213
|
+
};
|
|
214
|
+
return {
|
|
215
|
+
...natalData,
|
|
216
|
+
planets: offsetPlanets(natalData.planets),
|
|
217
|
+
_solar_arc_degrees: Math.round(arcDegrees * 1000) / 1000,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** Route outer-wheel fetch to the correct API endpoint based on mode. */
|
|
221
|
+
async function fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2) {
|
|
222
|
+
switch (mode) {
|
|
223
|
+
case "synastry":
|
|
224
|
+
return client.post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2, lon2, tz2));
|
|
225
|
+
case "transit":
|
|
226
|
+
return client.post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2 ?? lat1, lon2 ?? lon1, tz2 ?? tz1));
|
|
227
|
+
case "progressed":
|
|
228
|
+
return client.post("/ephemeris/progressed", buildProgressedBody(dt1, lat1, lon1, tz1, dt2));
|
|
229
|
+
case "solar_return":
|
|
230
|
+
return client.post("/predictive/returns/solar", buildReturnBody(dt1, lat1, lon1, tz1, dt2, lat2 ?? lat1, lon2 ?? lon1));
|
|
231
|
+
case "lunar_return":
|
|
232
|
+
return client.post("/predictive/returns/lunar", buildReturnBody(dt1, lat1, lon1, tz1, dt2, lat2 ?? lat1, lon2 ?? lon1));
|
|
233
|
+
case "solar_arc": {
|
|
234
|
+
const natal = await client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1));
|
|
235
|
+
return applySolarArcOffset(natal, dt1, dt2);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
84
239
|
const CROSS_ASPECT_DEFS = [
|
|
85
240
|
{ type: "conjunction", angle: 0, orb: 8 },
|
|
86
241
|
{ type: "opposition", angle: 180, orb: 8 },
|
|
@@ -196,10 +351,10 @@ function buildBiWheelSummary(innerPlanets, outerPlanets, crossAspects, mode, loc
|
|
|
196
351
|
square: "□", sextile: "⚹",
|
|
197
352
|
};
|
|
198
353
|
const top5 = crossAspects.slice(0, 5).map((ca) => `• ${capitalize(ca.planet1)} ${ASPECT_SYMBOL[ca.type] ?? ca.type} ${capitalize(ca.planet2)} (${ca.orb.toFixed(1)}° orb${ca.applying ? ", applying" : ""})`);
|
|
199
|
-
const
|
|
200
|
-
const label1 =
|
|
201
|
-
const label2 =
|
|
202
|
-
return (`**${modeLabel} Bi-Wheel**\n\n` +
|
|
354
|
+
const meta = MODE_META[mode];
|
|
355
|
+
const label1 = meta.innerRole;
|
|
356
|
+
const label2 = meta.outerRole;
|
|
357
|
+
return (`**${meta.modeLabel} Bi-Wheel**\n\n` +
|
|
203
358
|
`${label1}: ${location1} — ${innerBasics || "chart loaded"}\n` +
|
|
204
359
|
`${label2}: ${location2}\n\n` +
|
|
205
360
|
`**Top Cross-Aspects:**\n${top5.join("\n") || "No major cross-aspects within orb."}\n\n` +
|
|
@@ -208,12 +363,16 @@ function buildBiWheelSummary(innerPlanets, outerPlanets, crossAspects, mode, loc
|
|
|
208
363
|
// ── Tool: explore_bi_wheel ─────────────────────────────────────────────────────
|
|
209
364
|
registerTool({
|
|
210
365
|
name: "explore_bi_wheel",
|
|
211
|
-
description: "Generate an interactive bi-wheel chart
|
|
212
|
-
"
|
|
213
|
-
"
|
|
214
|
-
"
|
|
366
|
+
description: "Generate an interactive bi-wheel chart comparing two astrological chart positions. " +
|
|
367
|
+
"The inner wheel is always Person 1's natal chart. The outer ring depends on mode:\n" +
|
|
368
|
+
"• synastry — Person 2's natal chart (two-person compatibility). 1 credit.\n" +
|
|
369
|
+
"• transit — Transiting planets for a given date. 1 credit.\n" +
|
|
370
|
+
"• progressed — Secondary progressed positions (person2_datetime = target date). 1 credit.\n" +
|
|
371
|
+
"• solar_return — Nearest solar return chart (person2_datetime = year to return for). 5 credits.\n" +
|
|
372
|
+
"• lunar_return — Nearest lunar return chart (person2_datetime = target month). 5 credits.\n" +
|
|
373
|
+
"• solar_arc — Solar arc directed positions (person2_datetime = target date). 1 credit.\n" +
|
|
215
374
|
"Cross-aspects between both wheels are computed and displayed as coloured dashed lines. " +
|
|
216
|
-
"Click any planet
|
|
375
|
+
"Click any planet, aspect line, or house cusp for interpretation.",
|
|
217
376
|
inputSchema: {
|
|
218
377
|
type: "object",
|
|
219
378
|
properties: {
|
|
@@ -230,14 +389,16 @@ registerTool({
|
|
|
230
389
|
},
|
|
231
390
|
person2_datetime: {
|
|
232
391
|
type: "string",
|
|
233
|
-
description: "ISO 8601 datetime for
|
|
392
|
+
description: "ISO 8601 datetime for the outer wheel. Meaning depends on mode: " +
|
|
393
|
+
"synastry = Person 2 birth datetime; transit/progressed/solar_arc = target date; " +
|
|
394
|
+
"solar_return/lunar_return = any date within the target year/month.",
|
|
234
395
|
},
|
|
235
|
-
person2_latitude: { type: "number", description: "Birth latitude for Person 2 /
|
|
236
|
-
person2_longitude: { type: "number", description: "Birth longitude for Person 2 /
|
|
237
|
-
person2_timezone: { type: "string", description: "IANA timezone for Person 2 /
|
|
396
|
+
person2_latitude: { type: "number", description: "Birth latitude for Person 2 (synastry) or return/transit location. Defaults to Person 1 coords for single-person modes." },
|
|
397
|
+
person2_longitude: { type: "number", description: "Birth longitude for Person 2 (synastry) or return/transit location. Defaults to Person 1 coords for single-person modes." },
|
|
398
|
+
person2_timezone: { type: "string", description: "IANA timezone for Person 2 / outer chart (e.g. 'America/Chicago')." },
|
|
238
399
|
person2_name: {
|
|
239
400
|
type: "string",
|
|
240
|
-
description: "Display name for Person 2
|
|
401
|
+
description: "Display name for Person 2 (synastry mode). Used in the badge and summary.",
|
|
241
402
|
},
|
|
242
403
|
location: {
|
|
243
404
|
type: "string",
|
|
@@ -245,13 +406,12 @@ registerTool({
|
|
|
245
406
|
},
|
|
246
407
|
mode: {
|
|
247
408
|
type: "string",
|
|
248
|
-
enum: ["synastry", "transit"],
|
|
249
|
-
description: "Chart mode
|
|
409
|
+
enum: ["synastry", "transit", "progressed", "solar_return", "lunar_return", "solar_arc"],
|
|
410
|
+
description: "Chart comparison mode. Defaults to 'synastry'.",
|
|
250
411
|
},
|
|
251
412
|
},
|
|
252
413
|
required: ["person1_datetime", "person2_datetime",
|
|
253
|
-
"person1_latitude", "person1_longitude",
|
|
254
|
-
"person2_latitude", "person2_longitude"],
|
|
414
|
+
"person1_latitude", "person1_longitude"],
|
|
255
415
|
},
|
|
256
416
|
annotations: {
|
|
257
417
|
title: "Interactive Bi-Wheel Explorer",
|
|
@@ -278,25 +438,12 @@ registerTool({
|
|
|
278
438
|
const lon2 = args.person2_longitude;
|
|
279
439
|
const tz2 = args.person2_timezone;
|
|
280
440
|
const loc1 = String(args.person1_name ?? args.location ?? `${lat1 ?? "?"},${lon1 ?? "?"}`);
|
|
281
|
-
const loc2 = mode
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
if (mode === "synastry") {
|
|
288
|
-
[innerData, outerData] = await Promise.all([
|
|
289
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
290
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2, lon2, tz2)),
|
|
291
|
-
]);
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
// Transit mode: inner = natal (dt1), outer = transits for dt2 at natal location
|
|
295
|
-
[innerData, outerData] = await Promise.all([
|
|
296
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
297
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2 ?? lat1, lon2 ?? lon1, tz2 ?? tz1)),
|
|
298
|
-
]);
|
|
299
|
-
}
|
|
441
|
+
const loc2 = MODE_META[mode].outerLabel(dt2, args.person2_name);
|
|
442
|
+
// Fetch inner (natal) and outer (mode-dependent) charts in parallel where possible
|
|
443
|
+
const [innerData, outerData] = await Promise.all([
|
|
444
|
+
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
445
|
+
fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2),
|
|
446
|
+
]);
|
|
300
447
|
const innerPlanets = normalizePlanets(innerData.planets);
|
|
301
448
|
const outerPlanets = normalizePlanets(outerData.planets);
|
|
302
449
|
const crossAspects = computeCrossAspects(innerPlanets, outerPlanets);
|
|
@@ -324,11 +471,11 @@ registerTool({
|
|
|
324
471
|
// ── Tool: bi_wheel_recalculate ──────────────────────────────────────────────
|
|
325
472
|
registerTool({
|
|
326
473
|
name: "bi_wheel_recalculate",
|
|
327
|
-
description: "Recalculates a bi-wheel chart
|
|
474
|
+
description: "Recalculates a bi-wheel chart with new parameters. Supports all modes: synastry, transit, progressed, solar_return, lunar_return, solar_arc.",
|
|
328
475
|
inputSchema: {
|
|
329
476
|
type: "object",
|
|
330
477
|
properties: {
|
|
331
|
-
mode: { type: "string", enum:
|
|
478
|
+
mode: { type: "string", enum: ALL_MODES },
|
|
332
479
|
person1_datetime: { type: "string" },
|
|
333
480
|
person1_latitude: { type: "number" },
|
|
334
481
|
person1_longitude: { type: "number" },
|
|
@@ -339,7 +486,7 @@ registerTool({
|
|
|
339
486
|
person2_longitude: { type: "number" },
|
|
340
487
|
person2_timezone: { type: "string" },
|
|
341
488
|
person2_name: { type: "string" },
|
|
342
|
-
location: { type: "string" },
|
|
489
|
+
location: { type: "string" },
|
|
343
490
|
},
|
|
344
491
|
required: ["mode", "person1_datetime", "person1_latitude", "person1_longitude", "person2_datetime"],
|
|
345
492
|
},
|
|
@@ -357,30 +504,11 @@ registerTool({
|
|
|
357
504
|
const lon2 = args.person2_longitude;
|
|
358
505
|
const tz2 = args.person2_timezone;
|
|
359
506
|
const loc1 = String(args.person1_name ?? args.location ?? `${lat1 ?? "?"},${lon1 ?? "?"}`);
|
|
360
|
-
const loc2 = mode
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
366
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2, lon2, tz2)),
|
|
367
|
-
]);
|
|
368
|
-
}
|
|
369
|
-
else if (mode === "progressed") {
|
|
370
|
-
[innerData, outerData] = await Promise.all([
|
|
371
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
372
|
-
client.post("/predictive/progressed", {
|
|
373
|
-
subject: { birth_datetime: { iso: dt1 }, birth_location: { latitude: { decimal: lat1 ?? 0 }, longitude: { decimal: lon1 ?? 0 }, timezone: tz1 ? { iana_name: tz1 } : undefined } },
|
|
374
|
-
configuration: { target_datetime: { iso: dt2 }, method: "secondary" }
|
|
375
|
-
}),
|
|
376
|
-
]);
|
|
377
|
-
}
|
|
378
|
-
else {
|
|
379
|
-
[innerData, outerData] = await Promise.all([
|
|
380
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
381
|
-
client.post("/ephemeris/natal-chart", buildNatalBody(dt2, lat2 ?? lat1, lon2 ?? lon1, tz2 ?? tz1)),
|
|
382
|
-
]);
|
|
383
|
-
}
|
|
507
|
+
const loc2 = MODE_META[mode].outerLabel(dt2, args.person2_name);
|
|
508
|
+
const [innerData, outerData] = await Promise.all([
|
|
509
|
+
client.post("/ephemeris/natal-chart", buildNatalBody(dt1, lat1, lon1, tz1)),
|
|
510
|
+
fetchOuterChart(client, mode, dt1, lat1, lon1, tz1, dt2, lat2, lon2, tz2),
|
|
511
|
+
]);
|
|
384
512
|
const params1 = { datetime: dt1, timezone: tz1 ?? null, latitude: lat1 ?? null, longitude: lon1 ?? null, location: loc1 };
|
|
385
513
|
const params2 = { datetime: dt2, timezone: tz2 ?? null, latitude: lat2 ?? null, longitude: lon2 ?? null, location: loc2 };
|
|
386
514
|
const payload = buildBiWheelPayload(innerData, outerData, mode, params1, params2);
|
|
@@ -393,19 +521,19 @@ registerTool({
|
|
|
393
521
|
registerTool({
|
|
394
522
|
name: "bi_wheel_on_cross_aspect_click",
|
|
395
523
|
description: "Event handler called when the user clicks a cross-aspect line in the bi-wheel. " +
|
|
396
|
-
"Returns
|
|
524
|
+
"Returns an interpretation of that aspect contextualised by chart mode (synastry, transit, progressed, etc.). " +
|
|
397
525
|
"This tool is called automatically by the bi-wheel UI; you do not need to call it directly.",
|
|
398
526
|
inputSchema: {
|
|
399
527
|
type: "object",
|
|
400
528
|
properties: {
|
|
401
529
|
planet1: { type: "string", description: "Planet name from the inner (natal) wheel" },
|
|
402
530
|
wheel1: { type: "string", enum: ["inner", "outer"], description: "Which wheel planet1 belongs to" },
|
|
403
|
-
planet2: { type: "string", description: "Planet name from the outer
|
|
531
|
+
planet2: { type: "string", description: "Planet name from the outer wheel" },
|
|
404
532
|
wheel2: { type: "string", enum: ["inner", "outer"], description: "Which wheel planet2 belongs to" },
|
|
405
533
|
aspect_type: { type: "string", description: "Aspect type: conjunction, opposition, trine, square, sextile" },
|
|
406
534
|
orb: { type: "number", description: "Orb in degrees" },
|
|
407
535
|
applying: { type: "boolean", description: "True if the aspect is applying (tightening)" },
|
|
408
|
-
mode: { type: "string", enum:
|
|
536
|
+
mode: { type: "string", enum: ALL_MODES, description: "Chart mode. Determines interpretation style." },
|
|
409
537
|
},
|
|
410
538
|
required: ["planet1", "planet2", "aspect_type"],
|
|
411
539
|
},
|
|
@@ -438,12 +566,12 @@ registerTool({
|
|
|
438
566
|
type: "object",
|
|
439
567
|
properties: {
|
|
440
568
|
planet: { type: "string", description: "Planet name (e.g. 'saturn')" },
|
|
441
|
-
wheel: { type: "string", enum: ["inner", "outer"], description: "Which wheel (inner=natal/person1, outer=
|
|
569
|
+
wheel: { type: "string", enum: ["inner", "outer"], description: "Which wheel (inner=natal/person1, outer=mode-dependent)" },
|
|
442
570
|
longitude: { type: "number", description: "Ecliptic longitude in degrees" },
|
|
443
571
|
sign: { type: "string", description: "Zodiac sign" },
|
|
444
572
|
house: { type: "number", description: "House number (1–12) if available" },
|
|
445
573
|
retrograde: { type: "boolean", description: "Whether the planet is retrograde" },
|
|
446
|
-
mode: { type: "string", enum:
|
|
574
|
+
mode: { type: "string", enum: ALL_MODES, description: "Chart mode" },
|
|
447
575
|
},
|
|
448
576
|
required: ["planet", "wheel", "longitude"],
|
|
449
577
|
},
|
|
@@ -452,16 +580,17 @@ registerTool({
|
|
|
452
580
|
handler: async (args) => {
|
|
453
581
|
const planet = String(args.planet);
|
|
454
582
|
const wheel = args.wheel ?? "inner";
|
|
583
|
+
const mode = args.mode ?? "synastry";
|
|
455
584
|
const sign = args.sign ? String(args.sign) : zodSignFromLon(Number(args.longitude));
|
|
456
585
|
const houseNum = args.house != null ? Number(args.house) : null;
|
|
457
586
|
const house = houseNum != null && houseNum > 0 ? ` in the ${ordinal(houseNum)} house` : "";
|
|
458
|
-
const wheelLabel = wheel === "inner" ?
|
|
587
|
+
const wheelLabel = wheel === "inner" ? MODE_META[mode].innerRole : MODE_META[mode].outerRole;
|
|
459
588
|
const deg = `${Math.floor(Number(args.longitude) % 30)}° ${sign}`;
|
|
460
589
|
return {
|
|
461
590
|
content: [{
|
|
462
591
|
type: "text",
|
|
463
592
|
text: `**${capitalize(planet)} at ${deg}${house}** *(${wheelLabel})*\n\n` +
|
|
464
|
-
interpretBiWheelPlanet(planet, sign, houseNum ?? 0, wheel, Boolean(args.retrograde)),
|
|
593
|
+
interpretBiWheelPlanet(planet, sign, houseNum ?? 0, wheel, Boolean(args.retrograde), mode),
|
|
465
594
|
}],
|
|
466
595
|
};
|
|
467
596
|
},
|
|
@@ -555,7 +684,24 @@ function interpretBiWheelPlanet(planet, sign, house, wheel, retrograde = false,
|
|
|
555
684
|
return (`**Transiting Planet:** Transiting ${capitalize(planet)} represents ${theme}. Moving through ${capitalize(sign)}, it carries the quality of ${signQuality}.${houseCtx}${retroCtx}\n\n` +
|
|
556
685
|
`As a **transit**, this planet is temporarily activating the natal chart. Its influence is current and time-limited — watch for events, shifts in awareness, or opportunities related to its themes in the houses and planets it touches.`);
|
|
557
686
|
}
|
|
687
|
+
else if (mode === "progressed") {
|
|
688
|
+
return (`**Progressed Planet:** Progressed ${capitalize(planet)} in ${capitalize(sign)} shows how the natal ${capitalize(planet)} archetype has evolved through lived experience.${houseCtx}${retroCtx}\n\n` +
|
|
689
|
+
`Unlike transits, progressed planets move slowly — a sign ingress can take years to unfold. This position describes a chapter of inner development: the themes of ${capitalize(planet)} are now being expressed with the colouring of ${signQuality}. Aspects from this progressed position to natal points mark turning points in that developmental arc.`);
|
|
690
|
+
}
|
|
691
|
+
else if (mode === "solar_arc") {
|
|
692
|
+
return (`**Solar Arc ${capitalize(planet)}:** This directed position represents natal ${capitalize(planet)} advanced by approximately 1° per year of life.${houseCtx}${retroCtx}\n\n` +
|
|
693
|
+
`Solar arc directions symbolise the symbolic maturation of each natal archetype. Where the directed ${capitalize(planet)} now falls — and especially which natal planets or angles it contacts — marks a significant chapter in the long development of ${theme}. Conjunctions, squares, and oppositions to natal points are the most potent activation signals.`);
|
|
694
|
+
}
|
|
695
|
+
else if (mode === "solar_return") {
|
|
696
|
+
return (`**Solar Return ${capitalize(planet)}:** In this solar return chart, ${capitalize(planet)} falls at ${Math.floor((Number(sign) || 0) % 30)}° ${capitalize(sign)}.${houseCtx}${retroCtx}\n\n` +
|
|
697
|
+
`Solar return planets describe themes that will be especially prominent during this **solar year** — from this birthday to the next. ${capitalize(planet)} in the return chart highlights where the energy of ${theme} will be most active, especially in the house it occupies and through contacts to natal planets.`);
|
|
698
|
+
}
|
|
699
|
+
else if (mode === "lunar_return") {
|
|
700
|
+
return (`**Lunar Return ${capitalize(planet)}:** In this lunar return chart, ${capitalize(planet)} sets the tone for the coming **lunar month**.${houseCtx}${retroCtx}\n\n` +
|
|
701
|
+
`Lunar returns reset monthly as the Moon returns to its natal degree. ${capitalize(planet)}'s position here — through ${signQuality} — shapes the emotional and circumstantial flavour of the next four weeks. Contacts to natal planets or angles sharpen its influence considerably.`);
|
|
702
|
+
}
|
|
558
703
|
else {
|
|
704
|
+
// synastry fallback
|
|
559
705
|
return (`**Synastry Planet:** ${capitalize(planet)} represents ${theme}. In ${capitalize(sign)}, this manifests through ${signQuality}.${houseCtx}${retroCtx}\n\n` +
|
|
560
706
|
`This is the **outer person's natal ${capitalize(planet)}** — when this planet makes contact with the inner chart, it activates the natal themes of both parties simultaneously, creating a dynamic of mutual recognition and activation.`);
|
|
561
707
|
}
|
|
@@ -584,8 +730,11 @@ const CROSS_ASPECT_NATURE = {
|
|
|
584
730
|
};
|
|
585
731
|
function interpretCrossAspect(p1, p2, type, applying, mode) {
|
|
586
732
|
const natures = CROSS_ASPECT_NATURE[type.toLowerCase()];
|
|
733
|
+
// Single-person time-based modes share the transit nature text; synastry/solar arc use synastry
|
|
734
|
+
const isTimeBased = mode === "transit" || mode === "progressed" || mode === "solar_arc" ||
|
|
735
|
+
mode === "solar_return" || mode === "lunar_return";
|
|
587
736
|
const nature = natures
|
|
588
|
-
? (
|
|
737
|
+
? (isTimeBased ? natures.transit : natures.synastry)
|
|
589
738
|
: "creates a notable connection across the two charts.";
|
|
590
739
|
let timing;
|
|
591
740
|
if (mode === "transit") {
|
|
@@ -593,6 +742,17 @@ function interpretCrossAspect(p1, p2, type, applying, mode) {
|
|
|
593
742
|
? "This aspect is **applying** — its influence is building and moving toward maximum expression. The themes involved are intensifying now."
|
|
594
743
|
: "This aspect is **separating** — its peak has passed; the energy is waning and the lessons of this transit are moving into integration.";
|
|
595
744
|
}
|
|
745
|
+
else if (mode === "progressed" || mode === "solar_arc") {
|
|
746
|
+
timing = applying
|
|
747
|
+
? "This aspect is **applying** — progressed and directed aspects move slowly, often unfolding over months or years before reaching exactitude. The themes it represents are gradually intensifying."
|
|
748
|
+
: "This aspect is **separating** — it has passed its peak of exactitude and is now in a phase of integration. The defining moment of this developmental aspect lies in the recent past.";
|
|
749
|
+
}
|
|
750
|
+
else if (mode === "solar_return") {
|
|
751
|
+
timing = "As a **solar return aspect**, this connection is active for the entire solar year from this birthday to the next. It describes a consistent theme or dynamic that will colour the whole year rather than arriving as a dated event.";
|
|
752
|
+
}
|
|
753
|
+
else if (mode === "lunar_return") {
|
|
754
|
+
timing = "As a **lunar return aspect**, this connection is active for the coming lunar month. It shapes the emotional atmosphere and events of the next four weeks before the next lunar return resets the cycle.";
|
|
755
|
+
}
|
|
596
756
|
else {
|
|
597
757
|
timing = "As a **synastry aspect**, this connection exists as a permanent feature of this relationship dynamic — it neither builds nor fades, but remains as a constant undercurrent in how these two people interact.";
|
|
598
758
|
}
|
|
@@ -611,7 +771,7 @@ registerTool({
|
|
|
611
771
|
house_number: { type: "number", description: "House number (1–12)" },
|
|
612
772
|
sign: { type: "string", description: "Sign on the house cusp (inner natal wheel)" },
|
|
613
773
|
cusp_longitude: { type: "number", description: "Cusp longitude in degrees" },
|
|
614
|
-
mode: { type: "string", enum:
|
|
774
|
+
mode: { type: "string", enum: ALL_MODES, description: "Chart mode" },
|
|
615
775
|
},
|
|
616
776
|
required: ["house_number"],
|
|
617
777
|
},
|
|
@@ -625,14 +785,19 @@ registerTool({
|
|
|
625
785
|
const signFlavor = sign
|
|
626
786
|
? `\n\n${capitalize(sign)} on this cusp emphasizes ${SIGN_HOUSE_FLAVOR[sign.toLowerCase()] ?? "its natural qualities"} in the matters of this house.`
|
|
627
787
|
: "";
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
: "\n\
|
|
788
|
+
const HOUSE_MODE_NOTES = {
|
|
789
|
+
synastry: "\n\nIn synastry, planets from the outer natal chart falling in this house create experiences related to these themes in the relationship — often powerfully felt by the person whose house is being activated.",
|
|
790
|
+
transit: "\n\nWatch for transiting planets moving through this house — they will activate and bring events related to these life areas during their passage.",
|
|
791
|
+
progressed: "\n\nAs progressed planets move through this house, they gradually shift the inner developmental focus toward these life themes. Natal house boundaries mark important life-chapter transitions in secondary progression work.",
|
|
792
|
+
solar_arc: "\n\nSolar arc directed planets entering this house signal a new chapter of life experience in these domains. The ingress year often corresponds to a major shift in the themes of this house.",
|
|
793
|
+
solar_return: "\n\nPlanets in this house in the solar return chart will be emphasised life areas for the coming year. A concentration of solar return planets here intensifies activity and attention in this domain.",
|
|
794
|
+
lunar_return: "\n\nPlanets in this house in the lunar return chart highlight the month's dominant emotional themes and practical focus areas. The lunar return ascendant and its house emphasis set the tone for the entire lunar cycle.",
|
|
795
|
+
};
|
|
631
796
|
return {
|
|
632
797
|
content: [{
|
|
633
798
|
type: "text",
|
|
634
799
|
text: `**House ${num}${sign ? ` — ${capitalize(sign)} on the cusp` : ""}** *(Inner Natal Wheel)*\n\n` +
|
|
635
|
-
meaning + signFlavor +
|
|
800
|
+
meaning + signFlavor + HOUSE_MODE_NOTES[mode],
|
|
636
801
|
}],
|
|
637
802
|
};
|
|
638
803
|
},
|
|
@@ -648,9 +813,9 @@ registerTool({
|
|
|
648
813
|
inputSchema: {
|
|
649
814
|
type: "object",
|
|
650
815
|
properties: {
|
|
651
|
-
mode: { type: "string", enum:
|
|
652
|
-
person1_name: { type: "string", description: "Name or label for the inner wheel (natal/person 1)" },
|
|
653
|
-
person2_name: { type: "string", description: "Name or label for the outer wheel (
|
|
816
|
+
mode: { type: "string", enum: ALL_MODES, description: "Chart mode" },
|
|
817
|
+
person1_name: { type: "string", description: "Name or label for the inner wheel (natal / person 1)" },
|
|
818
|
+
person2_name: { type: "string", description: "Name or label for the outer wheel (person 2, transit date, progression target, etc.)" },
|
|
654
819
|
cross_aspects: {
|
|
655
820
|
type: "array",
|
|
656
821
|
description: "Array of cross-aspects from the bi-wheel payload (up to 30, sorted by orb)",
|
|
@@ -673,8 +838,9 @@ registerTool({
|
|
|
673
838
|
_meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app", "model"] } },
|
|
674
839
|
handler: async (args) => {
|
|
675
840
|
const mode = args.mode ?? "synastry";
|
|
676
|
-
const
|
|
677
|
-
const
|
|
841
|
+
const meta = MODE_META[mode];
|
|
842
|
+
const label1 = args.person1_name ? String(args.person1_name) : meta.innerRole;
|
|
843
|
+
const label2 = args.person2_name ? String(args.person2_name) : meta.outerRole;
|
|
678
844
|
const topN = args.top_n ? Number(args.top_n) : 8;
|
|
679
845
|
const rawAspects = args.cross_aspects ?? [];
|
|
680
846
|
const aspects = rawAspects.slice(0, topN);
|
|
@@ -684,36 +850,55 @@ registerTool({
|
|
|
684
850
|
const harmonious = aspects.filter((a) => ["trine", "sextile"].includes((a.aspect_type ?? a.type ?? "").toLowerCase()));
|
|
685
851
|
const dynamic = aspects.filter((a) => ["square", "opposition"].includes((a.aspect_type ?? a.type ?? "").toLowerCase()));
|
|
686
852
|
const fusing = aspects.filter((a) => (a.aspect_type ?? a.type ?? "").toLowerCase() === "conjunction");
|
|
853
|
+
// For time-based modes, show applying/separating; for relational modes show tightness
|
|
854
|
+
const isTimeBased = mode !== "synastry";
|
|
687
855
|
const lines = aspects.map((ca) => {
|
|
688
856
|
const type = ca.aspect_type ?? ca.type ?? "aspect";
|
|
689
857
|
const sym = ASPECT_SYM[type.toLowerCase()] ?? type;
|
|
690
|
-
const dir =
|
|
858
|
+
const dir = isTimeBased
|
|
691
859
|
? (ca.applying ? "↗ applying" : "↘ separating")
|
|
692
860
|
: (ca.orb <= 1 ? "exact" : ca.orb <= 3 ? "tight" : "wide");
|
|
693
861
|
return `• ${capitalize(ca.planet1)} ${sym} ${capitalize(ca.planet2)} (${ca.orb.toFixed(1)}° — ${dir})`;
|
|
694
862
|
});
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
: `**
|
|
863
|
+
// Theme summary copy varies by mode
|
|
864
|
+
const synastryTheme = [
|
|
865
|
+
fusing.length > 0 ? `**${fusing.length} conjunction${fusing.length > 1 ? "s" : ""}** — areas of deep merging and fusion between the two charts.` : "",
|
|
866
|
+
harmonious.length > 0 ? `**${harmonious.length} flowing aspect${harmonious.length > 1 ? "s" : ""}** (trines/sextiles) — natural ease, support, and compatibility.` : "",
|
|
867
|
+
dynamic.length > 0 ? `**${dynamic.length} dynamic aspect${dynamic.length > 1 ? "s" : ""}** (squares/oppositions) — areas of growth, friction, and mutual challenge.` : "",
|
|
868
|
+
].filter(Boolean).join("\n");
|
|
869
|
+
const timeBasedTheme = [
|
|
870
|
+
fusing.length > 0 ? `**${fusing.length} conjunction${fusing.length > 1 ? "s" : ""}** — direct activation of natal points; peak influence for this ${meta.modeLabel.toLowerCase()}.` : "",
|
|
871
|
+
harmonious.length > 0 ? `**${harmonious.length} flowing aspect${harmonious.length > 1 ? "s" : ""}** (trines/sextiles) — periods of ease, opportunity, and natural support.` : "",
|
|
872
|
+
dynamic.length > 0 ? `**${dynamic.length} dynamic aspect${dynamic.length > 1 ? "s" : ""}** (squares/oppositions) — active pressure points requiring conscious engagement.` : "",
|
|
873
|
+
].filter(Boolean).join("\n");
|
|
874
|
+
const themeSummary = mode === "synastry" ? synastryTheme : timeBasedTheme;
|
|
875
|
+
const SYNOPSIS_INTRO = {
|
|
876
|
+
synastry: `**Synastry Overview: ${label1} × ${label2}**\n\n` +
|
|
877
|
+
`This synastry bi-wheel compares two natal charts to reveal the dynamics of interaction and connection between these two individuals. The cross-aspects show how their planetary energies align, challenge, and transform each other.`,
|
|
878
|
+
transit: `**Transit Overview: ${label1} × ${label2}**\n\n` +
|
|
879
|
+
`This transit bi-wheel overlays current planetary positions onto the natal chart, revealing which natal points are being activated right now and the nature of those influences.`,
|
|
880
|
+
progressed: `**Secondary Progressions Overview: ${label1} × ${label2}**\n\n` +
|
|
881
|
+
`This progression bi-wheel places the natal chart inside and secondary progressed positions in the outer ring. Progressed planets reflect the gradual inner maturation of natal archetypal themes using the day-for-a-year method. Cross-aspects from progressed to natal points mark the defining chapters of inner development.`,
|
|
882
|
+
solar_arc: `**Solar Arc Directions Overview: ${label1} × ${label2}**\n\n` +
|
|
883
|
+
`This solar arc bi-wheel shows natal positions inside and solar arc directed positions in the outer ring. Solar arc direction advances every natal planet by approximately 1° per year of life (Naibod rate). Contacts from directed positions to natal points — especially conjunctions — mark pivotal life moments.`,
|
|
884
|
+
solar_return: `**Solar Return Overview: ${label1} × ${label2}**\n\n` +
|
|
885
|
+
`This solar return bi-wheel compares the natal chart against the solar return for the target year. The outer ring shows where planets fall at the exact moment the Sun returns to its natal degree, describing the dominant themes and developmental focus of the coming solar year.`,
|
|
886
|
+
lunar_return: `**Lunar Return Overview: ${label1} × ${label2}**\n\n` +
|
|
887
|
+
`This lunar return bi-wheel compares the natal chart against the lunar return. The outer ring shows planetary positions when the Moon returned to its natal degree, setting the emotional tone and circumstantial focus of the coming lunar month.`,
|
|
888
|
+
};
|
|
889
|
+
const SYNOPSIS_CLOSING = {
|
|
890
|
+
synastry: "\n\nClick any planet or aspect line in the bi-wheel for a deeper interpretation of individual connections.",
|
|
891
|
+
transit: "\n\nClick any planet or cross-aspect line for a detailed transit interpretation. Navigate the date forward or backward using the step controls to track how transits evolve.",
|
|
892
|
+
progressed: "\n\nClick any progressed planet or cross-aspect line for a detailed interpretation. The most significant turning points occur when a progressed planet reaches exactitude with a natal planet or angle.",
|
|
893
|
+
solar_arc: "\n\nClick any directed planet or cross-aspect line for interpretation. Solar arc contacts to natal angles (ASC, MC, IC, DSC) are among the most powerful triggers in predictive astrology.",
|
|
894
|
+
solar_return: "\n\nClick any solar return planet or cross-aspect line for interpretation. The solar return ascendant and its ruler are the master theme-setters for the year.",
|
|
895
|
+
lunar_return: "\n\nClick any lunar return planet or cross-aspect line for interpretation. The lunar return Moon's house placement describes this month's emotional centre of gravity.",
|
|
896
|
+
};
|
|
709
897
|
const body = lines.length > 0
|
|
710
898
|
? `\n\n**Top Cross-Aspects by Orb:**\n${lines.join("\n")}\n\n**Thematic Summary:**\n${themeSummary || "No major aspects within standard orbs."}`
|
|
711
899
|
: "\n\nNo major cross-aspects are within orb at this time.";
|
|
712
|
-
const closing = mode === "synastry"
|
|
713
|
-
? "\n\nClick any planet or aspect line in the bi-wheel for a deeper interpretation of individual connections."
|
|
714
|
-
: "\n\nClick any planet or cross-aspect line for a detailed transit interpretation. Navigate the date forward or backward using the step controls to track how transits evolve.";
|
|
715
900
|
return {
|
|
716
|
-
content: [{ type: "text", text:
|
|
901
|
+
content: [{ type: "text", text: SYNOPSIS_INTRO[mode] + body + SYNOPSIS_CLOSING[mode] }],
|
|
717
902
|
};
|
|
718
903
|
},
|
|
719
904
|
});
|