@openephemeris/mcp-server 3.13.6 → 3.13.8

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.
Files changed (37) hide show
  1. package/README.md +2 -2
  2. package/dist/backend/client.js +27 -9
  3. package/dist/index.js +33 -32
  4. package/dist/prompts.d.ts +26 -0
  5. package/dist/prompts.js +942 -0
  6. package/dist/tools/apps/bi-wheel-app.js +286 -32
  7. package/dist/tools/apps/bodygraph-app.js +361 -12
  8. package/dist/tools/apps/chart-wheel-app.js +64 -30
  9. package/dist/tools/apps/location-tools.d.ts +1 -0
  10. package/dist/tools/apps/location-tools.js +52 -0
  11. package/dist/tools/index.js +1 -0
  12. package/dist/tools/specialized/acg.js +2 -0
  13. package/dist/tools/specialized/bazi.js +1 -0
  14. package/dist/tools/specialized/bi_wheel.js +1 -0
  15. package/dist/tools/specialized/chart_wheel.js +1 -0
  16. package/dist/tools/specialized/comparative.js +4 -0
  17. package/dist/tools/specialized/eclipse.js +1 -0
  18. package/dist/tools/specialized/electional.js +4 -0
  19. package/dist/tools/specialized/ephemeris_core.js +2 -0
  20. package/dist/tools/specialized/ephemeris_extended.js +8 -0
  21. package/dist/tools/specialized/hd_bodygraph.js +1 -0
  22. package/dist/tools/specialized/hd_cycles.js +2 -0
  23. package/dist/tools/specialized/hd_group.js +2 -0
  24. package/dist/tools/specialized/human_design.js +1 -0
  25. package/dist/tools/specialized/moon.js +2 -0
  26. package/dist/tools/specialized/natal.js +1 -0
  27. package/dist/tools/specialized/progressed.js +1 -0
  28. package/dist/tools/specialized/relocation.js +1 -0
  29. package/dist/tools/specialized/returns.js +3 -0
  30. package/dist/tools/specialized/synastry.js +1 -0
  31. package/dist/tools/specialized/transits.js +1 -0
  32. package/dist/tools/specialized/vedic.js +1 -0
  33. package/dist/tools/specialized/venus_star_points.js +6 -0
  34. package/dist/ui/bi-wheel.html +7124 -60
  35. package/dist/ui/bodygraph.html +7057 -508
  36. package/dist/ui/chart-wheel.html +7355 -565
  37. package/package.json +1 -1
@@ -137,7 +137,7 @@ function normalizePlanets(raw) {
137
137
  speed: (p.longitude_speed ?? p.speed),
138
138
  retrograde: Boolean(p.is_retrograde ?? p.retrograde),
139
139
  sign: (p.sign_name ?? p.sign),
140
- house: p.house,
140
+ house: (p.house_number ?? p.house),
141
141
  }));
142
142
  }
143
143
  return [];
@@ -145,6 +145,12 @@ function normalizePlanets(raw) {
145
145
  function normalizeHouses(raw) {
146
146
  if (Array.isArray(raw))
147
147
  return raw;
148
+ if (raw && typeof raw === "object" && "cusps" in raw && Array.isArray(raw.cusps)) {
149
+ return raw.cusps.map((cusp, i) => ({
150
+ number: i + 1,
151
+ cusp_lon: cusp,
152
+ }));
153
+ }
148
154
  return [];
149
155
  }
150
156
  // ── Payload builder ────────────────────────────────────────────────────────────
@@ -304,16 +310,9 @@ registerTool({
304
310
  content: [
305
311
  { type: "text", text: summary },
306
312
  { type: "text", text: JSON.stringify({ ...payload, server_version: SERVER_VERSION }) },
307
- {
308
- type: "resource",
309
- resource: {
310
- uri: BI_WHEEL_RESOURCE_URI,
311
- mimeType: BI_WHEEL_MIME_TYPE,
312
- text: getBiWheelBundle(),
313
- },
314
- },
315
313
  ],
316
314
  _meta: {
315
+ "ui/resourceUri": BI_WHEEL_RESOURCE_URI,
317
316
  ui: { resourceUri: BI_WHEEL_RESOURCE_URI },
318
317
  },
319
318
  };
@@ -322,6 +321,74 @@ registerTool({
322
321
  return { content: [{ type: "text", text: summary }] };
323
322
  },
324
323
  });
324
+ // ── Tool: bi_wheel_recalculate ──────────────────────────────────────────────
325
+ registerTool({
326
+ name: "bi_wheel_recalculate",
327
+ description: "Recalculates a bi-wheel chart (synastry, transit, or progressed mode) with new parameters.",
328
+ inputSchema: {
329
+ type: "object",
330
+ properties: {
331
+ mode: { type: "string", enum: ["synastry", "transit", "progressed"] },
332
+ person1_datetime: { type: "string" },
333
+ person1_latitude: { type: "number" },
334
+ person1_longitude: { type: "number" },
335
+ person1_timezone: { type: "string" },
336
+ person1_name: { type: "string" },
337
+ person2_datetime: { type: "string" },
338
+ person2_latitude: { type: "number" },
339
+ person2_longitude: { type: "number" },
340
+ person2_timezone: { type: "string" },
341
+ person2_name: { type: "string" },
342
+ location: { type: "string" }, // fallback
343
+ },
344
+ required: ["mode", "person1_datetime", "person1_latitude", "person1_longitude", "person2_datetime"],
345
+ },
346
+ annotations: { title: "Recalculate Bi-Wheel", readOnlyHint: true, openWorldHint: false },
347
+ _meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app"] } },
348
+ handler: async (args) => {
349
+ const client = getActiveClient();
350
+ const mode = args.mode ?? "synastry";
351
+ const dt1 = String(args.person1_datetime);
352
+ const lat1 = args.person1_latitude;
353
+ const lon1 = args.person1_longitude;
354
+ const tz1 = args.person1_timezone;
355
+ const dt2 = String(args.person2_datetime);
356
+ const lat2 = args.person2_latitude;
357
+ const lon2 = args.person2_longitude;
358
+ const tz2 = args.person2_timezone;
359
+ const loc1 = String(args.person1_name ?? args.location ?? `${lat1 ?? "?"},${lon1 ?? "?"}`);
360
+ const loc2 = mode === "transit" ? `Transit ${dt2.slice(0, 10)}` : String(args.person2_name ?? `${lat2 ?? ""},${lon2 ?? ""}`);
361
+ let innerData;
362
+ let outerData;
363
+ if (mode === "synastry") {
364
+ [innerData, outerData] = await Promise.all([
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
+ }
384
+ const params1 = { datetime: dt1, timezone: tz1 ?? null, latitude: lat1 ?? null, longitude: lon1 ?? null, location: loc1 };
385
+ const params2 = { datetime: dt2, timezone: tz2 ?? null, latitude: lat2 ?? null, longitude: lon2 ?? null, location: loc2 };
386
+ const payload = buildBiWheelPayload(innerData, outerData, mode, params1, params2);
387
+ return {
388
+ content: [{ type: "text", text: JSON.stringify({ ...payload, server_version: SERVER_VERSION }) }],
389
+ };
390
+ },
391
+ });
325
392
  // ── Tool: bi_wheel_on_cross_aspect_click ──────────────────────────────────────
326
393
  registerTool({
327
394
  name: "bi_wheel_on_cross_aspect_click",
@@ -424,42 +491,229 @@ const PLANET_THEMES = {
424
491
  mc: "public vocation, highest ambition, and the world's face; the pinnacle of achievement this person strives toward",
425
492
  };
426
493
  const HOUSE_AREAS = {
427
- 1: "self and identity", 2: "values and resources",
428
- 3: "communication", 4: "home and roots",
429
- 5: "creativity and joy", 6: "work and health",
430
- 7: "partnerships", 8: "shared depth and transformation",
431
- 9: "beliefs and expansion", 10: "career and public life",
432
- 11: "community and hopes", 12: "solitude and the unconscious",
494
+ 1: "self, appearance, and first impressions",
495
+ 2: "money, material values, and personal resources",
496
+ 3: "communication, siblings, and local environment",
497
+ 4: "home, family, roots, and emotional foundations",
498
+ 5: "creativity, romance, children, and pleasure",
499
+ 6: "work, health, routines, and service",
500
+ 7: "partnerships, marriage, and close one-on-one relationships",
501
+ 8: "transformation, shared resources, sexuality, and the depths",
502
+ 9: "philosophy, higher education, long travel, and belief",
503
+ 10: "career, public reputation, and life calling",
504
+ 11: "friendships, groups, collective causes, and long-term hopes",
505
+ 12: "solitude, the unconscious, hidden matters, and spiritual retreat",
506
+ };
507
+ const HOUSE_MEANINGS = {
508
+ 1: "The 1st house governs identity, appearance, and how we project ourselves into the world. In synastry, planets here from the outer wheel can reshape how this person is perceived or how they see themselves.",
509
+ 2: "The 2nd house governs personal values, self-worth, and material security. Synastry or transit planets here trigger questions around money, possessions, and what one truly values.",
510
+ 3: "The 3rd house governs communication, learning, short journeys, and the immediate environment. Planets here stimulate the mind, increase dialogue, and sharpen or soften communication.",
511
+ 4: "The 4th house (IC) governs the home, family, ancestry, and emotional foundations. This is the most private sector of the chart — planets here reach deep into the inner life.",
512
+ 5: "The 5th house governs creativity, romance, play, and children. Synastry planets here light up pleasure-seeking, creative collaboration, and romantic sparks.",
513
+ 6: "The 6th house governs work, health, and daily rhythm. Planets here affect how one shows up day-to-day — discipline, habits, service, and the body.",
514
+ 7: "The 7th house (Descendant) governs committed partnerships. Planets here activate relationship patterns — the qualities we project onto partners or seek in them.",
515
+ 8: "The 8th house governs deep transformation, shared finances, intimacy, and taboo. Planets here create intense, soul-level connections or power dynamics that cannot be ignored.",
516
+ 9: "The 9th house governs worldview, higher learning, travel, and spiritual quest. Planets here expand perspective, challenge beliefs, and open new intellectual horizons.",
517
+ 10: "The 10th house (Midheaven) governs career, public life, and legacy. Planets here can profoundly affect one's sense of purpose and how the outer world recognizes them.",
518
+ 11: "The 11th house governs friends, networks, social causes, and future visions. Planets here energize collective activity, shared ideals, and the aspiration toward belonging.",
519
+ 12: "The 12th house governs the unconscious, hidden matters, solitude, and spiritual development. Planets here work in subtle and sometimes invisible ways — potent but beneath the surface.",
433
520
  };
434
- function interpretBiWheelPlanet(planet, sign, house, wheel, retrograde = false) {
521
+ const SIGN_QUALITIES = {
522
+ aries: "directness, initiative, and pioneering courage",
523
+ taurus: "steady determination, sensory pleasure, and practical patience",
524
+ gemini: "curiosity, adaptability, and intellectual versatility",
525
+ cancer: "emotional depth, nurturing instincts, and protective sensitivity",
526
+ leo: "bold self-expression, generosity, and regal confidence",
527
+ virgo: "precision, discernment, and devoted service to improvement",
528
+ libra: "harmony-seeking, relational grace, and aesthetic refinement",
529
+ scorpio: "intense focus, emotional depth, and transformative power",
530
+ sagittarius: "philosophical expansiveness, optimism, and adventurous seeking",
531
+ capricorn: "disciplined ambition, structural mastery, and long-term commitment",
532
+ aquarius: "innovative originality, humanitarian vision, and detached idealism",
533
+ pisces: "compassionate empathy, spiritual receptivity, and imaginative flow",
534
+ };
535
+ const SIGN_HOUSE_FLAVOR = {
536
+ aries: "initiative and directness", taurus: "patience and material stability",
537
+ gemini: "intellectual curiosity and communication", cancer: "emotional nurturing and memory",
538
+ leo: "creative expression and leadership", virgo: "analytical precision and service",
539
+ libra: "balance and relationship dynamics", scorpio: "depth, intensity, and transformation",
540
+ sagittarius: "expansion and philosophical seeking", capricorn: "discipline, structure, and achievement",
541
+ aquarius: "innovation, groups, and humanitarian ideals", pisces: "compassion, dreams, and dissolution of boundaries",
542
+ };
543
+ function interpretBiWheelPlanet(planet, sign, house, wheel, retrograde = false, mode = "synastry") {
435
544
  const theme = PLANET_THEMES[planet.toLowerCase()] ?? "a deep archetypal force";
436
- const houseCtx = house ? ` This energy operates in the sphere of ${HOUSE_AREAS[house] ?? "life experience"}.` : "";
545
+ const signQuality = SIGN_QUALITIES[sign.toLowerCase()] ?? "the qualities of this sign";
546
+ const houseCtx = house ? ` This energy is active in the **${ordinal(house)} house** — the sphere of ${HOUSE_AREAS[house] ?? "life experience"}.` : "";
437
547
  const retroCtx = retrograde
438
- ? ` Note: this planet is **retrograde** — its energy tends to be more internalized, reflective, or nonconforming in its expression.`
548
+ ? ` Note: this planet is **retrograde** — its energy turns inward; themes are more private, reflective, and may require more deliberate integration before they can be expressed outward.`
439
549
  : "";
440
- const wheelCtx = wheel === "inner"
441
- ? "This is a **natal planet** a core, stable part of this person's identity."
442
- : "This is an **outer planet** — a transiting or synastry energy currently activating the inner wheel.";
443
- return (`${capitalize(planet)} represents ${theme}. In ${capitalize(sign)}, this energy is expressed through the qualities of that sign.${houseCtx}${retroCtx}\n\n${wheelCtx}`);
550
+ if (wheel === "inner") {
551
+ return (`**Natal Planet:** ${capitalize(planet)} represents ${theme}. In ${capitalize(sign)}, this energy flows through ${signQuality}.${houseCtx}${retroCtx}\n\n` +
552
+ `This is a **core natal placement** — a stable, defining feature of this person's chart. It shows how ${capitalize(planet)}'s themes are wired into their fundamental nature.`);
553
+ }
554
+ else if (mode === "transit") {
555
+ return (`**Transiting Planet:** Transiting ${capitalize(planet)} represents ${theme}. Moving through ${capitalize(sign)}, it carries the quality of ${signQuality}.${houseCtx}${retroCtx}\n\n` +
556
+ `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
+ }
558
+ else {
559
+ return (`**Synastry Planet:** ${capitalize(planet)} represents ${theme}. In ${capitalize(sign)}, this manifests through ${signQuality}.${houseCtx}${retroCtx}\n\n` +
560
+ `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
+ }
444
562
  }
445
563
  const CROSS_ASPECT_NATURE = {
446
- conjunction: "creates a powerful merging of these two planetary energies across the chart boundary. What one person (or the natal chart) embodies, the other amplifies — this can feel fated, intense, and highly activating.",
447
- opposition: "creates a magnetic pull-and-push dynamic. The two energies are drawn to each other yet challenge each other to grow. In synastry, this often manifests as fascination and tension; in transit, as external pressure to integrate.",
448
- trine: "creates an easy, harmonious flow of energy between the two charts. This aspect supports mutual understanding, natural rapport, and a sense of ease gifts arrive without much effort.",
449
- square: "creates productive friction between the two charts. Growth and evolution are required. In synastry, this generates passion and drive; in transit, it signals action and adjustment.",
450
- sextile: "creates a gentle, supportive connection that rewards intentional effort. Opportunities arise where these two energies meet, but initiative is needed to activate them fully.",
564
+ conjunction: {
565
+ synastry: "creates a powerful merging of these two planetary energies across both charts. What one person embodies, the other amplifies this can feel fated, magnetic, and highly activating. The themes of both planets fuse and are difficult to separate.",
566
+ transit: "marks a moment of direct activation. The transiting planet sits exactly upon a natal planet, amplifying its themes with full force. This is often experienced as an event, a catalytic encounter, or a decisive shift in awareness.",
567
+ },
568
+ opposition: {
569
+ synastry: "creates a magnetic polarity across the chart boundary. The two energies are drawn to each other yet challenge integration — each person may embody qualities the other both desires and finds difficult. Fascination and tension coexist.",
570
+ transit: "brings an external pressure that demands integration. The transiting planet opposes a natal point, often projecting themes outward through encounters, relationships, or confrontations that mirror what needs to be balanced internally.",
571
+ },
572
+ trine: {
573
+ synastry: "creates an easy, harmonious flow between the two charts. Natural rapport, ease of connection, and effortless support arise where these themes meet. This feels comfortable — even familiar — often from the very first interaction.",
574
+ transit: "brings a period of natural ease and opportunity. The themes of the transiting planet flow harmoniously into the natal point — doors open, situations resolve with less effort, and the native feels supported by outer circumstances.",
575
+ },
576
+ square: {
577
+ synastry: "creates productive friction between the two charts. Life together requires growth and adjustment. This dynamic generates passion and drive, but also tension that must be consciously worked with rather than avoided. The reward is mutual evolution.",
578
+ transit: "signals a call to action. Current circumstances are pressing against a natal point, requiring adjustment, decision, or effort. Discomfort is the teacher here — the square challenges, but in doing so, builds capacity and strength.",
579
+ },
580
+ sextile: {
581
+ synastry: "creates a gentle, supportive connection that rewards intentional effort. The ease is more subtle than a trine, but the potential for growth is real. Collaboration, mutual curiosity, and opening to new shared possibilities arise here.",
582
+ transit: "brings a period of opportunity that rewards initiative. The transiting planet offers a supportive opening — but unlike a trine, it requires the native to consciously engage and take action rather than simply receiving.",
583
+ },
451
584
  };
452
585
  function interpretCrossAspect(p1, p2, type, applying, mode) {
453
- const nature = CROSS_ASPECT_NATURE[type.toLowerCase()] ?? "creates a notable connection across the two charts.";
586
+ const natures = CROSS_ASPECT_NATURE[type.toLowerCase()];
587
+ const nature = natures
588
+ ? (mode === "transit" ? natures.transit : natures.synastry)
589
+ : "creates a notable connection across the two charts.";
454
590
  let timing;
455
591
  if (mode === "transit") {
456
592
  timing = applying
457
- ? "This aspect is **applying** — its influence is building and moving toward maximum expression."
458
- : "This aspect is **separating** — its peak has passed; integration of its themes is underway.";
593
+ ? "This aspect is **applying** — its influence is building and moving toward maximum expression. The themes involved are intensifying now."
594
+ : "This aspect is **separating** — its peak has passed; the energy is waning and the lessons of this transit are moving into integration.";
459
595
  }
460
596
  else {
461
- // In synastry, both charts are frozenapplying/separating has no temporal meaning.
462
- timing = "As a synastry aspect, this connection exists as a permanent feature of this relationship dynamic — neither building nor fading, but always present as a foundational thread between these two charts.";
597
+ 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.";
463
598
  }
464
599
  return (`The ${type} between ${capitalize(p1)} and ${capitalize(p2)} ${nature}\n\n${timing}`);
465
600
  }
601
+ // ── Tool: bi_wheel_on_house_click ─────────────────────────────────────────────
602
+ registerTool({
603
+ name: "bi_wheel_on_house_click",
604
+ description: "Event handler called when the user clicks a house cusp in the inner wheel of the bi-wheel chart. " +
605
+ "Returns the meaning of that house in the context of synastry or transit — what life area it governs " +
606
+ "and how outer planets activating it tend to manifest. " +
607
+ "This tool is called automatically by the bi-wheel UI; you do not need to call it directly.",
608
+ inputSchema: {
609
+ type: "object",
610
+ properties: {
611
+ house_number: { type: "number", description: "House number (1–12)" },
612
+ sign: { type: "string", description: "Sign on the house cusp (inner natal wheel)" },
613
+ cusp_longitude: { type: "number", description: "Cusp longitude in degrees" },
614
+ mode: { type: "string", enum: ["synastry", "transit"], description: "Chart mode" },
615
+ },
616
+ required: ["house_number"],
617
+ },
618
+ annotations: { title: "Bi-Wheel House Interpretation", readOnlyHint: true, openWorldHint: false },
619
+ _meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app"] } },
620
+ handler: async (args) => {
621
+ const num = Number(args.house_number);
622
+ const sign = args.sign ? String(args.sign) : "";
623
+ const mode = args.mode ?? "synastry";
624
+ const meaning = HOUSE_MEANINGS[num] ?? `The ${ordinal(num)} house governs a distinct area of lived experience.`;
625
+ const signFlavor = sign
626
+ ? `\n\n${capitalize(sign)} on this cusp emphasizes ${SIGN_HOUSE_FLAVOR[sign.toLowerCase()] ?? "its natural qualities"} in the matters of this house.`
627
+ : "";
628
+ const modeNote = mode === "transit"
629
+ ? "\n\nWatch for transiting planets moving through this house — they will activate and bring events related to these life areas during their passage."
630
+ : "\n\nIn synastry, planets from the outer 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.";
631
+ return {
632
+ content: [{
633
+ type: "text",
634
+ text: `**House ${num}${sign ? ` — ${capitalize(sign)} on the cusp` : ""}** *(Inner Natal Wheel)*\n\n` +
635
+ meaning + signFlavor + modeNote,
636
+ }],
637
+ };
638
+ },
639
+ });
640
+ // ── Tool: bi_wheel_synopsis ────────────────────────────────────────────────────
641
+ registerTool({
642
+ name: "bi_wheel_synopsis",
643
+ description: "Generate a master-astrologer-grade overview of the entire bi-wheel configuration — " +
644
+ "summarising the mode (synastry or transit), key cross-aspects by quality, dominant themes, " +
645
+ "stationing or slow-moving outer planet influences, and an interpretive narrative. " +
646
+ "Use this when the user asks for a full reading, a relationship overview, or a transit overview " +
647
+ "rather than clicking a specific element. Accepts the cross-aspect data from the chart payload.",
648
+ inputSchema: {
649
+ type: "object",
650
+ properties: {
651
+ mode: { type: "string", enum: ["synastry", "transit"], description: "Chart mode" },
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 (synastry person 2 or transit date)" },
654
+ cross_aspects: {
655
+ type: "array",
656
+ description: "Array of cross-aspects from the bi-wheel payload (up to 30, sorted by orb)",
657
+ items: {
658
+ type: "object",
659
+ properties: {
660
+ planet1: { type: "string" },
661
+ planet2: { type: "string" },
662
+ aspect_type: { type: "string" },
663
+ orb: { type: "number" },
664
+ applying: { type: "boolean" },
665
+ },
666
+ },
667
+ },
668
+ top_n: { type: "number", description: "Number of top aspects to highlight (default 8)" },
669
+ },
670
+ required: ["mode"],
671
+ },
672
+ annotations: { title: "Bi-Wheel Synopsis", readOnlyHint: true, openWorldHint: false },
673
+ _meta: { ui: { resourceUri: BI_WHEEL_RESOURCE_URI, visibility: ["app", "model"] } },
674
+ handler: async (args) => {
675
+ const mode = args.mode ?? "synastry";
676
+ const label1 = args.person1_name ? String(args.person1_name) : (mode === "transit" ? "Natal Chart" : "Person 1");
677
+ const label2 = args.person2_name ? String(args.person2_name) : (mode === "transit" ? "Transits" : "Person 2");
678
+ const topN = args.top_n ? Number(args.top_n) : 8;
679
+ const rawAspects = args.cross_aspects ?? [];
680
+ const aspects = rawAspects.slice(0, topN);
681
+ const ASPECT_SYM = {
682
+ conjunction: "☌", opposition: "☍", trine: "△", square: "□", sextile: "⚹",
683
+ };
684
+ const harmonious = aspects.filter((a) => ["trine", "sextile"].includes((a.aspect_type ?? a.type ?? "").toLowerCase()));
685
+ const dynamic = aspects.filter((a) => ["square", "opposition"].includes((a.aspect_type ?? a.type ?? "").toLowerCase()));
686
+ const fusing = aspects.filter((a) => (a.aspect_type ?? a.type ?? "").toLowerCase() === "conjunction");
687
+ const lines = aspects.map((ca) => {
688
+ const type = ca.aspect_type ?? ca.type ?? "aspect";
689
+ const sym = ASPECT_SYM[type.toLowerCase()] ?? type;
690
+ const dir = mode === "transit"
691
+ ? (ca.applying ? "↗ applying" : "↘ separating")
692
+ : (ca.orb <= 1 ? "exact" : ca.orb <= 3 ? "tight" : "wide");
693
+ return `• ${capitalize(ca.planet1)} ${sym} ${capitalize(ca.planet2)} (${ca.orb.toFixed(1)}° — ${dir})`;
694
+ });
695
+ const themeSummary = mode === "synastry"
696
+ ? [
697
+ fusing.length > 0 ? `**${fusing.length} conjunction${fusing.length > 1 ? "s" : ""}** — areas of deep merging and fusion between the two charts.` : "",
698
+ harmonious.length > 0 ? `**${harmonious.length} flowing aspect${harmonious.length > 1 ? "s" : ""}** (trines/sextiles) — natural ease, support, and compatibility.` : "",
699
+ dynamic.length > 0 ? `**${dynamic.length} dynamic aspect${dynamic.length > 1 ? "s" : ""}** (squares/oppositions) — areas of growth, friction, and mutual challenge.` : "",
700
+ ].filter(Boolean).join("\n")
701
+ : [
702
+ fusing.length > 0 ? `**${fusing.length} conjunction${fusing.length > 1 ? "s" : ""}** — direct activation of natal points; peak transit influence.` : "",
703
+ harmonious.length > 0 ? `**${harmonious.length} flowing transit${harmonious.length > 1 ? "s" : ""}** — periods of ease, opportunity, and support.` : "",
704
+ dynamic.length > 0 ? `**${dynamic.length} challenging transit${dynamic.length > 1 ? "s" : ""}** — active pressure points requiring conscious engagement.` : "",
705
+ ].filter(Boolean).join("\n");
706
+ const intro = mode === "synastry"
707
+ ? `**Synastry Overview: ${label1} × ${label2}**\n\nThis 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.`
708
+ : `**Transit Overview: ${label1} × ${label2}**\n\nThis 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.`;
709
+ const body = lines.length > 0
710
+ ? `\n\n**Top Cross-Aspects by Orb:**\n${lines.join("\n")}\n\n**Thematic Summary:**\n${themeSummary || "No major aspects within standard orbs."}`
711
+ : "\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
+ return {
716
+ content: [{ type: "text", text: intro + body + closing }],
717
+ };
718
+ },
719
+ });