@openephemeris/mcp-server 4.16.0 → 4.18.0
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/CHANGELOG.md +46 -0
- package/README.md +15 -15
- package/config/dev-allowlist.json +31 -19
- package/dist/backend/client.d.ts +57 -1
- package/dist/backend/client.js +125 -15
- package/dist/prompts.js +26 -24
- package/dist/server-sse.d.ts +18 -0
- package/dist/server-sse.js +41 -6
- package/dist/tools/apps/_render-token.d.ts +34 -0
- package/dist/tools/apps/_render-token.js +50 -0
- package/dist/tools/apps/bazi-app.js +36 -12
- package/dist/tools/apps/bi-wheel-app.d.ts +9 -2
- package/dist/tools/apps/bi-wheel-app.js +76 -117
- package/dist/tools/apps/bodygraph-app.js +88 -41
- package/dist/tools/apps/chart-wheel-app.js +56 -16
- package/dist/tools/apps/location-tools.js +10 -3
- package/dist/tools/apps/moon-phase-app.js +10 -2
- package/dist/tools/apps/transit-timeline-app.d.ts +3 -0
- package/dist/tools/apps/transit-timeline-app.js +77 -9
- package/dist/tools/apps/vedic-chart-app.js +10 -1
- package/dist/tools/datetime-historical.js +7 -2
- package/dist/tools/datetime.js +2 -1
- package/dist/tools/dev.js +7 -6
- package/dist/tools/specialized/electional.js +4 -3
- package/dist/tools/specialized/ephemeris_extended.js +19 -4
- package/dist/tools/specialized/hd_group.js +2 -2
- package/dist/tools/specialized/moon.d.ts +1 -1
- package/dist/tools/specialized/moon.js +51 -43
- package/dist/tools/specialized/progressed.js +2 -25
- package/dist/tools/specialized/transits.js +5 -5
- package/dist/ui/bazi.html +1523 -1522
- package/dist/ui/bi-wheel.html +406 -374
- package/dist/ui/bodygraph.html +83 -79
- package/dist/ui/chart-wheel.html +394 -361
- package/dist/ui/transit-timeline.html +3 -3
- package/dist/ui/vedic-chart.html +818 -818
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,52 @@ Version numbering follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [4.18.0] — 2026-09-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- **`explore_bi_wheel` progressed, solar return and lunar return modes work again.** They sent
|
|
14
|
+
requests the API rejected ("Validation failed" / "birth_datetime is required"); all six modes now
|
|
15
|
+
return a chart.
|
|
16
|
+
- **Solar arc directions are exact.** `explore_bi_wheel` mode `solar_arc` used a flat ~1°-per-year
|
|
17
|
+
estimate and left the houses at their birth positions. It now uses the Sun's real arc from the API:
|
|
18
|
+
every planet, angle and house cusp is directed by the same arc, with correct signs and houses.
|
|
19
|
+
`ephemeris_progressed_chart` with `method: "solar_arc"` directs the angles and house cusps too.
|
|
20
|
+
- **Every lunar node is named Mean or True.** The charts show "North Node (Mean)", "North Node
|
|
21
|
+
(True)", "South Node (Mean)" and "South Node (True)" as four separate points. The two South Nodes
|
|
22
|
+
used to merge into one, and the mean North Node was shown as a bare "North Node". The Mean/True
|
|
23
|
+
toggle now applies to the South Node as well.
|
|
24
|
+
- **Bi-wheel recalculation keeps the house system you choose** for the outer chart too (it was always
|
|
25
|
+
Placidus).
|
|
26
|
+
- **Black Moon Lilith appears on the chart wheel, named Mean, True or Interpolated.** Asking
|
|
27
|
+
`explore_natal_chart` for `lilith` or `lilith_true` returned the chart without it. The three Liliths
|
|
28
|
+
are now shown as "Lilith (Mean)", "Lilith (True)" and "Lilith (Interpolated)" (new slug
|
|
29
|
+
`lilith_interpolated`), never merged into one.
|
|
30
|
+
- **`explore_natal_chart` with `bodies: ["all"]` works.** It was rejected because it asked for Vertex and
|
|
31
|
+
Part of Fortune as extra bodies; "all" now covers every body the chart can add.
|
|
32
|
+
|
|
33
|
+
### Removed
|
|
34
|
+
- **`include_visual` on `ephemeris_progressed_chart`.** The API never draws a progressed chart
|
|
35
|
+
image, so the option returned no picture. It was never charged. To see progressions over the natal
|
|
36
|
+
chart, use `explore_bi_wheel` with mode `progressed`.
|
|
37
|
+
|
|
38
|
+
## [4.17.0] — 2026-09-24
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
- **Transit timelines show when each transit starts and ends.** `explore_transit_timeline` now asks
|
|
42
|
+
for orb windows (new `orb_deg` parameter, default 1°): every exact hit carries the in-orb window it
|
|
43
|
+
belongs to — the date the transit comes into orb and the date it leaves — and "pass 2 of 3" when a
|
|
44
|
+
retrograde brings the planet back over the same point without leaving the orb. Transits already in
|
|
45
|
+
orb at the start of the range are listed with whether they are applying or separating. The
|
|
46
|
+
interactive timeline shows the window on each card.
|
|
47
|
+
- **Two new API capabilities reachable through `dev_read_api`:** orb windows on
|
|
48
|
+
`POST /predictive/transits/search` (`orb_deg`, `aspect_orbs`, `natal_points`, `aspects`) and the new
|
|
49
|
+
`POST /human-design/transit-timeline` — every gate (and line) a transiting planet enters and leaves
|
|
50
|
+
over a date range, and when a transit completes one of your channels. 121 public endpoints.
|
|
51
|
+
|
|
52
|
+
### Fixed
|
|
53
|
+
- **`dev_read_api` now reaches three endpoints it was missing:** `/ephemeris/draconic`,
|
|
54
|
+
`/ephemeris/prenatal-lunation` and `/predictive/primary-directions` (124 allowlisted operations).
|
|
55
|
+
|
|
10
56
|
## [4.16.0] — 2026-09-21
|
|
11
57
|
|
|
12
58
|
### Changed
|
package/README.md
CHANGED
|
@@ -212,7 +212,7 @@ The server is hosted at `https://mcp.openephemeris.com/mcp` with full Streamable
|
|
|
212
212
|
|
|
213
213
|
- Missing/invalid credentials (`401`): tool call fails with a message that points users to sign up/sign in at `https://openephemeris.com/login?signup=true&redirect=%2Fdashboard%3Ftab%3Daccount`, then create/manage keys in `https://openephemeris.com/dashboard?tab=account`.
|
|
214
214
|
- Tier-gated endpoint (`403`): tool call returns an upgrade-required message with `https://openephemeris.com/pay` and dashboard billing/key management link.
|
|
215
|
-
-
|
|
215
|
+
- Out of credits (`402`): tool call returns a one-tap top-up link (Explorer's 150 free credits are one-time and do not reset; plan allowances renew each billing period) plus the dashboard usage link. Failed calls (any `4xx`/`5xx`) are refunded.
|
|
216
216
|
- Burst/rate limit (`429`): tool call returns retry guidance and links to dashboard usage monitoring.
|
|
217
217
|
|
|
218
218
|
## What You Can Ask
|
|
@@ -256,13 +256,13 @@ instead of the picture, so nothing breaks, you just don't get the wheel.
|
|
|
256
256
|
| Tool | What opens | What you can click | Credits |
|
|
257
257
|
|---|---|---|---|
|
|
258
258
|
| `explore_natal_chart` | Natal wheel — planets, houses, aspects, angles | Planets, houses, aspect lines; recalculate with new settings | 1 |
|
|
259
|
-
| `explore_bi_wheel` | Two charts on one wheel: transits, synastry, progressions | Either wheel's planets, houses, and the aspects between them | 2 |
|
|
260
|
-
| `explore_human_design` | Human Design bodygraph, with a mandala view toggle | Centers, gates, channels, planets, variables |
|
|
261
|
-
| `explore_human_design_transit` | Today's planets laid over a natal bodygraph | Transit-activated channels |
|
|
262
|
-
| `explore_human_design_connection` | Two bodygraphs combined, every shared channel classified | Connection channels by type |
|
|
259
|
+
| `explore_bi_wheel` | Two charts on one wheel: transits, synastry, progressions | Either wheel's planets, houses, and the aspects between them | 2 (6 for solar/lunar return) |
|
|
260
|
+
| `explore_human_design` | Human Design bodygraph, with a mandala view toggle | Centers, gates, channels, planets, variables | 4 |
|
|
261
|
+
| `explore_human_design_transit` | Today's planets laid over a natal bodygraph | Transit-activated channels | 5 |
|
|
262
|
+
| `explore_human_design_connection` | Two bodygraphs combined, every shared channel classified | Connection channels by type | 5 |
|
|
263
263
|
| `explore_vedic_chart` | South Indian Rashi grid — sidereal placements and Lagna | Each rashi, for its placements and nakshatras | 3 |
|
|
264
264
|
| `explore_bazi_chart` | Four Pillars (四柱命盘) — Year, Month, Day, Hour | Each pillar | 3 |
|
|
265
|
-
| `explore_transit_timeline` | Upcoming transit hits in date order | Individual hits | 6 |
|
|
265
|
+
| `explore_transit_timeline` | Upcoming transit hits in date order | Individual hits | 6 for up to 1 year (priced by span) |
|
|
266
266
|
| `explore_moon_phase` | Moon dial — illumination, phase, sign, void-of-course | Recalculate for another moment | 3 |
|
|
267
267
|
|
|
268
268
|
Ask for these the way you'd ask a person: *"show me my chart"*, *"put today's transits over my Human Design"*, *"what's the moon doing right now"*. The model picks the app.
|
|
@@ -300,10 +300,10 @@ Screenshots of each are on the way.
|
|
|
300
300
|
| Lunar return | `ephemeris_lunar_return` | Explorer |
|
|
301
301
|
| Planetary return | `ephemeris_planetary_return` | Explorer |
|
|
302
302
|
| Astrocartography lines | `acg_power_lines` | Developer |
|
|
303
|
-
| ACG hits at location | `acg_hits` |
|
|
303
|
+
| ACG hits at location | `acg_hits` | Developer |
|
|
304
304
|
| Venus Star Points | `venus_star_points` + 4 more | Explorer |
|
|
305
|
-
| Chart wheel image | `ephemeris_chart_wheel` |
|
|
306
|
-
| Bi-wheel image | `ephemeris_bi_wheel` |
|
|
305
|
+
| Chart wheel image | `ephemeris_chart_wheel` | Explorer |
|
|
306
|
+
| Bi-wheel image | `ephemeris_bi_wheel` | Explorer |
|
|
307
307
|
| Dignities / Midpoints / Fixed stars | `ephemeris_dignities`, `ephemeris_midpoints`, `ephemeris_fixed_stars` | Explorer |
|
|
308
308
|
|
|
309
309
|
## Tooling Model
|
|
@@ -335,7 +335,7 @@ Screenshots of each are on the way.
|
|
|
335
335
|
| `OPENEPHEMERIS_PROFILE` | No | `dev` by default |
|
|
336
336
|
| `OPENEPHEMERIS_TOOLS` | No | `core` (default) advertises a focused everyday tool set; `full` advertises every tool. See [Tool surface](#tool-surface) |
|
|
337
337
|
| `OPENEPHEMERIS_TELEMETRY` | No | Set to `0`/`false`/`off` to disable anonymous usage reporting. `DO_NOT_TRACK=1` also works. See [Telemetry](#telemetry) |
|
|
338
|
-
| `OPENEPHEMERIS_SERVICE_KEY` | No | Internal service auth |
|
|
338
|
+
| `OPENEPHEMERIS_SERVICE_KEY` | No | Internal service auth (stdio only; the hosted server refuses to start with one and always authenticates as the signed-in user) |
|
|
339
339
|
| `OPENEPHEMERIS_JWT` | No | Bearer token auth |
|
|
340
340
|
| `OPENEPHEMERIS_DEV_ALLOWLIST_PATH` | No | Override allowlist file path |
|
|
341
341
|
| `MCP_USER_ID` | No | Per-instance user identifier |
|
|
@@ -490,8 +490,8 @@ When you update the MCP server logic (handlers, bug fixes, hardening), you shoul
|
|
|
490
490
|
|
|
491
491
|
Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live tool registry.
|
|
492
492
|
|
|
493
|
-
- Allowlisted operations: **
|
|
494
|
-
- Methods: `GET=44`, `POST=
|
|
493
|
+
- Allowlisted operations: **124**
|
|
494
|
+
- Methods: `GET=44`, `POST=80`, `PUT=0`, `PATCH=0`, `DELETE=0`
|
|
495
495
|
- Registered tools (`OPENEPHEMERIS_PROFILE=dev`): **92**
|
|
496
496
|
- Typed tools: `account_usage`, `acg_hits`, `acg_power_lines`, `auth_login`, `auth_logout`, `auth_status`, `bazi_annual_pillar`, `bazi_chart`, `bazi_compatibility`, `bazi_element_balance`, `bazi_luck_pillars`, `bazi_recalculate`, `bazi_ten_gods`, `bi_wheel_on_cross_aspect_click`, `bi_wheel_on_house_click`, `bi_wheel_on_planet_click`, `bi_wheel_recalculate`, `bi_wheel_synopsis`, `bodygraph_recalculate`, `chart_wheel_on_aspect_click`, `chart_wheel_on_house_click`, `chart_wheel_on_planet_click`, `chart_wheel_recalculate`, `chinese_bazi`, `electional_angle_crossings`, `electional_aspect_search`, `electional_moment_analysis`, `electional_station_tracker`, `ephemeris_angles_points`, `ephemeris_aspect_check`, `ephemeris_bi_wheel`, `ephemeris_chart_wheel`, `ephemeris_composite`, `ephemeris_composite_midpoint`, `ephemeris_dignities`, `ephemeris_electional`, `ephemeris_fixed_stars`, `ephemeris_hermetic_lots`, `ephemeris_house_cusps`, `ephemeris_lunar_return`, `ephemeris_midpoints`, `ephemeris_moon_phase`, `ephemeris_natal_batch`, `ephemeris_natal_chart`, `ephemeris_natal_transits`, `ephemeris_next_eclipse`, `ephemeris_next_lunar_phase`, `ephemeris_overlay`, `ephemeris_planet_position`, `ephemeris_planetary_return`, `ephemeris_progressed_chart`, `ephemeris_relocation`, `ephemeris_retrograde_status`, `ephemeris_solar_return`, `ephemeris_synastry`, `ephemeris_transits`, `explore_bazi_chart`, `explore_bi_wheel`, `explore_human_design`, `explore_human_design_connection`, `explore_human_design_transit`, `explore_moon_phase`, `explore_natal_chart`, `explore_transit_timeline`, `explore_vedic_chart`, `hd_on_center_click`, `hd_on_channel_click`, `hd_on_connection_channel_click`, `hd_on_gate_click`, `hd_on_planet_click`, `hd_on_transit_channel_click`, `hd_on_variable_click`, `hd_opposition`, `hd_planetary_return`, `human_design_bodygraph`, `human_design_chart`, `human_design_composite`, `human_design_penta`, `location_search`, `moon_phase_recalculate`, `timezone_resolve`, `vedic_chart`, `vedic_chart_recalculate`, `venus_eight_year_star`, `venus_elongations`, `venus_phase`, `venus_star_points`, `venus_star_points_conjunctions`, `venus_stations`
|
|
497
497
|
- Generic tools: `dev_list_allowed`, `dev_read_api`, `dev_write_api`
|
|
@@ -507,11 +507,11 @@ Generated by `npm run sync:readme` from `config/dev-allowlist.json` and the live
|
|
|
507
507
|
| `comparative` | 5 | `POST /comparative/composite`, `POST /comparative/composite/midpoint` |
|
|
508
508
|
| `eclipse` | 6 | `GET /eclipse/besselian-elements`, `GET /eclipse/lunar/global` |
|
|
509
509
|
| `electional` | 6 | `GET /electional/angle-crossings`, `GET /electional/aspect-search` |
|
|
510
|
-
| `ephemeris` |
|
|
510
|
+
| `ephemeris` | 38 | `GET /ephemeris/agro/calendar`, `GET /ephemeris/agro/daily` |
|
|
511
511
|
| `health` | 2 | `GET /health`, `GET /health/detailed` |
|
|
512
|
-
| `human-design` |
|
|
512
|
+
| `human-design` | 9 | `POST /human-design/chart`, `POST /human-design/composite` |
|
|
513
513
|
| `location` | 2 | `GET /location/autocomplete`, `GET /location/reverse` |
|
|
514
|
-
| `predictive` |
|
|
514
|
+
| `predictive` | 11 | `POST /predictive/primary-directions`, `POST /predictive/returns` |
|
|
515
515
|
| `root` | 1 | `GET /` |
|
|
516
516
|
| `tidal` | 2 | `GET /tidal/forcing`, `GET /tidal/forcing/deep-time` |
|
|
517
517
|
| `time` | 6 | `GET /time/delta-t`, `GET /time/equation-of-time` |
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "astromcp-dev-allowlist-v1",
|
|
3
3
|
"description": "Allowlist derived from the Go sidecar openapi.json. Every entry in allow[] corresponds to a real Go endpoint. Run 'npm run check:dev-allowlist' to validate freshness.",
|
|
4
|
-
"generated_from": "C:\\src\\openephemeris\\.claude\\worktrees\\
|
|
4
|
+
"generated_from": "C:\\src\\openephemeris\\.claude\\worktrees\\adoring-rhodes-871fd7\\apps\\api\\go-sidecar\\openapi.json",
|
|
5
5
|
"deny": {
|
|
6
6
|
"path_prefixes": [
|
|
7
7
|
"/auth",
|
|
@@ -235,6 +235,10 @@
|
|
|
235
235
|
"method": "POST",
|
|
236
236
|
"path": "/ephemeris/dispositor-tree"
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
"method": "POST",
|
|
240
|
+
"path": "/ephemeris/draconic"
|
|
241
|
+
},
|
|
238
242
|
{
|
|
239
243
|
"method": "POST",
|
|
240
244
|
"path": "/ephemeris/fixed-stars"
|
|
@@ -291,6 +295,10 @@
|
|
|
291
295
|
"method": "POST",
|
|
292
296
|
"path": "/ephemeris/planet-position"
|
|
293
297
|
},
|
|
298
|
+
{
|
|
299
|
+
"method": "POST",
|
|
300
|
+
"path": "/ephemeris/prenatal-lunation"
|
|
301
|
+
},
|
|
294
302
|
{
|
|
295
303
|
"method": "POST",
|
|
296
304
|
"path": "/ephemeris/progressed"
|
|
@@ -383,6 +391,10 @@
|
|
|
383
391
|
"method": "POST",
|
|
384
392
|
"path": "/human-design/transit-chart"
|
|
385
393
|
},
|
|
394
|
+
{
|
|
395
|
+
"method": "POST",
|
|
396
|
+
"path": "/human-design/transit-timeline"
|
|
397
|
+
},
|
|
386
398
|
{
|
|
387
399
|
"method": "POST",
|
|
388
400
|
"path": "/human-design/transit"
|
|
@@ -395,6 +407,10 @@
|
|
|
395
407
|
"method": "GET",
|
|
396
408
|
"path": "/location/reverse"
|
|
397
409
|
},
|
|
410
|
+
{
|
|
411
|
+
"method": "POST",
|
|
412
|
+
"path": "/predictive/primary-directions"
|
|
413
|
+
},
|
|
398
414
|
{
|
|
399
415
|
"method": "POST",
|
|
400
416
|
"path": "/predictive/returns/lunar"
|
|
@@ -496,8 +512,8 @@
|
|
|
496
512
|
"path": "/visualization/chart-wheel"
|
|
497
513
|
}
|
|
498
514
|
],
|
|
499
|
-
"last_generated_at": "2026-
|
|
500
|
-
"openapi_sha256": "
|
|
515
|
+
"last_generated_at": "2026-09-24T18:29:22.473Z",
|
|
516
|
+
"openapi_sha256": "f5927cecddb0f8629f7c94d380ca878e6780bc491f85490bacb2508e1b721b3d",
|
|
501
517
|
"candidates_get": [
|
|
502
518
|
{
|
|
503
519
|
"method": "GET",
|
|
@@ -893,22 +909,6 @@
|
|
|
893
909
|
],
|
|
894
910
|
"operationId": "declination_lines_acg_declination_lines_post"
|
|
895
911
|
},
|
|
896
|
-
{
|
|
897
|
-
"method": "POST",
|
|
898
|
-
"path": "/acg/features",
|
|
899
|
-
"tags": [
|
|
900
|
-
"ACG"
|
|
901
|
-
],
|
|
902
|
-
"operationId": "features_acg_features_post"
|
|
903
|
-
},
|
|
904
|
-
{
|
|
905
|
-
"method": "POST",
|
|
906
|
-
"path": "/acg/heatmap/composite",
|
|
907
|
-
"tags": [
|
|
908
|
-
"Astrocartography"
|
|
909
|
-
],
|
|
910
|
-
"operationId": "AcgHeatmapCompositeAcgHeatmapCompositePost"
|
|
911
|
-
},
|
|
912
912
|
{
|
|
913
913
|
"method": "POST",
|
|
914
914
|
"path": "/acg/hermetic-lines",
|
|
@@ -1341,6 +1341,14 @@
|
|
|
1341
1341
|
],
|
|
1342
1342
|
"operationId": "human_design_transit_chart_human_design_transit_chart_post"
|
|
1343
1343
|
},
|
|
1344
|
+
{
|
|
1345
|
+
"method": "POST",
|
|
1346
|
+
"path": "/human-design/transit-timeline",
|
|
1347
|
+
"tags": [
|
|
1348
|
+
"Human Design"
|
|
1349
|
+
],
|
|
1350
|
+
"operationId": "human_design_transit_timeline_human_design_transit_timeline_post"
|
|
1351
|
+
},
|
|
1344
1352
|
{
|
|
1345
1353
|
"method": "POST",
|
|
1346
1354
|
"path": "/human-design/transit",
|
|
@@ -1538,6 +1546,7 @@
|
|
|
1538
1546
|
"Non-GET allowlisted: POST /ephemeris/declinations",
|
|
1539
1547
|
"Non-GET allowlisted: POST /ephemeris/dignities",
|
|
1540
1548
|
"Non-GET allowlisted: POST /ephemeris/dispositor-tree",
|
|
1549
|
+
"Non-GET allowlisted: POST /ephemeris/draconic",
|
|
1541
1550
|
"Non-GET allowlisted: POST /ephemeris/fixed-stars",
|
|
1542
1551
|
"Non-GET allowlisted: POST /ephemeris/harmonics",
|
|
1543
1552
|
"Non-GET allowlisted: POST /ephemeris/hermetic-lots",
|
|
@@ -1547,6 +1556,7 @@
|
|
|
1547
1556
|
"Non-GET allowlisted: POST /ephemeris/natal-chart",
|
|
1548
1557
|
"Non-GET allowlisted: POST /ephemeris/natal/batch",
|
|
1549
1558
|
"Non-GET allowlisted: POST /ephemeris/planet-position",
|
|
1559
|
+
"Non-GET allowlisted: POST /ephemeris/prenatal-lunation",
|
|
1550
1560
|
"Non-GET allowlisted: POST /ephemeris/progressed",
|
|
1551
1561
|
"Non-GET allowlisted: POST /ephemeris/relocation",
|
|
1552
1562
|
"Non-GET allowlisted: POST /ephemeris/retrograde-status",
|
|
@@ -1563,7 +1573,9 @@
|
|
|
1563
1573
|
"Non-GET allowlisted: POST /human-design/cycles/solar-return",
|
|
1564
1574
|
"Non-GET allowlisted: POST /human-design/penta",
|
|
1565
1575
|
"Non-GET allowlisted: POST /human-design/transit-chart",
|
|
1576
|
+
"Non-GET allowlisted: POST /human-design/transit-timeline",
|
|
1566
1577
|
"Non-GET allowlisted: POST /human-design/transit",
|
|
1578
|
+
"Non-GET allowlisted: POST /predictive/primary-directions",
|
|
1567
1579
|
"Non-GET allowlisted: POST /predictive/returns/lunar",
|
|
1568
1580
|
"Non-GET allowlisted: POST /predictive/returns/solar",
|
|
1569
1581
|
"Non-GET allowlisted: POST /predictive/returns",
|
package/dist/backend/client.d.ts
CHANGED
|
@@ -6,7 +6,29 @@ export interface BackendConfig {
|
|
|
6
6
|
apiKey?: string;
|
|
7
7
|
/** Back-compat: previous name for JWT token (Bearer). */
|
|
8
8
|
authToken?: string;
|
|
9
|
+
/**
|
|
10
|
+
* When false, the client uses ONLY the credentials passed in this config and
|
|
11
|
+
* never falls back to the process environment (OPENEPHEMERIS_SERVICE_KEY,
|
|
12
|
+
* OPENEPHEMERIS_API_KEY, OPENEPHEMERIS_JWT and their legacy aliases).
|
|
13
|
+
*
|
|
14
|
+
* The hosted HTTP server builds one client per user session and MUST pass
|
|
15
|
+
* false: the interceptor sends X-Service-Key ahead of the user's own key, so
|
|
16
|
+
* an env credential inherited by a per-session client would make every
|
|
17
|
+
* user's calls unmetered (service key) or billed to one account (API key /
|
|
18
|
+
* JWT). Defaults to true so the stdio/local server keeps reading its config
|
|
19
|
+
* from the environment exactly as before.
|
|
20
|
+
*/
|
|
21
|
+
inheritEnvCredentials?: boolean;
|
|
9
22
|
}
|
|
23
|
+
/** Env vars holding a service key — bypasses metering; never valid on the hosted server. */
|
|
24
|
+
export declare const SERVICE_KEY_ENV_VARS: readonly ["OPENEPHEMERIS_SERVICE_KEY", "ASTROMCP_SERVICE_KEY", "MERIDIAN_SERVICE_KEY"];
|
|
25
|
+
/** Env vars holding one user's credential — on the hosted server they would bill every session to one account. */
|
|
26
|
+
export declare const USER_CREDENTIAL_ENV_VARS: readonly ["OPENEPHEMERIS_API_KEY", "ASTROMCP_API_KEY", "MERIDIAN_API_KEY", "OPENEPHEMERIS_JWT", "ASTROMCP_JWT", "MERIDIAN_AUTH_TOKEN"];
|
|
27
|
+
/** Names of the credential env vars that are set (non-empty) in `env`. */
|
|
28
|
+
export declare function presentCredentialEnvVars(env?: NodeJS.ProcessEnv): {
|
|
29
|
+
serviceKeys: string[];
|
|
30
|
+
userCredentials: string[];
|
|
31
|
+
};
|
|
10
32
|
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
11
33
|
export interface BackendRequestOptions {
|
|
12
34
|
params?: Record<string, unknown>;
|
|
@@ -25,8 +47,33 @@ export declare const DASHBOARD_ACCOUNT_URL = "https://openephemeris.com/dashboar
|
|
|
25
47
|
export declare const LOGIN_SIGNUP_URL = "https://openephemeris.com/login?signup=true&redirect=%2Fdashboard%3Ftab%3Daccount";
|
|
26
48
|
export declare const UPGRADE_URL = "https://openephemeris.com/pricing";
|
|
27
49
|
/** One-tap $5 → 150-credit top-up (signs the user in if needed, then redirects to the prefilled Stripe Payment Link). */
|
|
28
|
-
export declare const TOPUP_URL = "https://openephemeris.com/topup?pack=payg_5";
|
|
50
|
+
export declare const TOPUP_URL = "https://openephemeris.com/topup?pack=payg_5&src=mcp_402";
|
|
51
|
+
/**
|
|
52
|
+
* The API's 402 carries `upgrade.topup_url`: the same /topup link, signed with
|
|
53
|
+
* the caller's account so it opens Stripe checkout without a sign-in step
|
|
54
|
+
* (most people hitting the wall are in ChatGPT/Claude and not signed in on the
|
|
55
|
+
* site). Use it when present, re-tagged src=mcp_402 so the funnel can tell MCP
|
|
56
|
+
* clicks from raw API ones; otherwise fall back to the unsigned link.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveTopupUrl(apiTopupUrl: unknown): string;
|
|
29
59
|
export declare const WALLET_TOPUP_URL = "https://openephemeris.com/wallet";
|
|
60
|
+
/**
|
|
61
|
+
* Whether an error body (typically a 422) is really the credit wall: the
|
|
62
|
+
* detail / type / field codes mention insufficient credits or an exceeded
|
|
63
|
+
* quota. Checked over the whole serialized body because the signal can sit in
|
|
64
|
+
* `detail`, `type`, or a nested `errors[].code`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function looksLikeCreditExhaustion(data: unknown, msg?: string): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* True for errors that mean "this account cannot be charged / is not allowed"
|
|
69
|
+
* — the credit wall (402), missing auth (401), or a tier gate (403). Tools
|
|
70
|
+
* that fan out or degrade gracefully must rethrow these instead of swallowing
|
|
71
|
+
* them: a placeholder result at zero credits hides the paywall (and its
|
|
72
|
+
* top-up link) from the assistant and reads as real data.
|
|
73
|
+
*/
|
|
74
|
+
export declare function isBillingOrAuthError(err: unknown): err is BackendError;
|
|
75
|
+
/** Rethrows the first billing/auth BackendError among settled results, if any. */
|
|
76
|
+
export declare function rethrowBillingOrAuth(results: PromiseSettledResult<unknown>[]): void;
|
|
30
77
|
export declare class BackendError extends Error {
|
|
31
78
|
readonly status: number;
|
|
32
79
|
readonly code: string;
|
|
@@ -59,6 +106,15 @@ export declare class BackendClient {
|
|
|
59
106
|
* (JWT is the lowest-priority credential in the interceptor).
|
|
60
107
|
*/
|
|
61
108
|
setJwt(jwt: string): void;
|
|
109
|
+
/**
|
|
110
|
+
* Drop every credential this client holds (service key, API key, JWT). The
|
|
111
|
+
* hosted HTTP server calls this on the module singleton at startup: the
|
|
112
|
+
* singleton is what getActiveClient() falls back to outside a session
|
|
113
|
+
* context, and it was built from the environment at import time.
|
|
114
|
+
*/
|
|
115
|
+
clearCredentials(): void;
|
|
116
|
+
/** Which configured credential the interceptor will send (diagnostics/tests). */
|
|
117
|
+
credentialKind(): "service_key" | "api_key" | "jwt" | null;
|
|
62
118
|
/**
|
|
63
119
|
* Start the device auth flow in the background if not already running.
|
|
64
120
|
* Called automatically when no credentials are found in the interceptor.
|
package/dist/backend/client.js
CHANGED
|
@@ -2,6 +2,29 @@ import { AsyncLocalStorage } from "node:async_hooks";
|
|
|
2
2
|
import axios, { AxiosError } from "axios";
|
|
3
3
|
import { CredentialManager } from "../auth/credentials.js";
|
|
4
4
|
import { DeviceAuthFlow } from "../auth/device-auth.js";
|
|
5
|
+
/** Env vars holding a service key — bypasses metering; never valid on the hosted server. */
|
|
6
|
+
export const SERVICE_KEY_ENV_VARS = [
|
|
7
|
+
"OPENEPHEMERIS_SERVICE_KEY",
|
|
8
|
+
"ASTROMCP_SERVICE_KEY",
|
|
9
|
+
"MERIDIAN_SERVICE_KEY",
|
|
10
|
+
];
|
|
11
|
+
/** Env vars holding one user's credential — on the hosted server they would bill every session to one account. */
|
|
12
|
+
export const USER_CREDENTIAL_ENV_VARS = [
|
|
13
|
+
"OPENEPHEMERIS_API_KEY",
|
|
14
|
+
"ASTROMCP_API_KEY",
|
|
15
|
+
"MERIDIAN_API_KEY",
|
|
16
|
+
"OPENEPHEMERIS_JWT",
|
|
17
|
+
"ASTROMCP_JWT",
|
|
18
|
+
"MERIDIAN_AUTH_TOKEN",
|
|
19
|
+
];
|
|
20
|
+
/** Names of the credential env vars that are set (non-empty) in `env`. */
|
|
21
|
+
export function presentCredentialEnvVars(env = process.env) {
|
|
22
|
+
const isSet = (name) => typeof env[name] === "string" && env[name].trim() !== "";
|
|
23
|
+
return {
|
|
24
|
+
serviceKeys: SERVICE_KEY_ENV_VARS.filter(isSet),
|
|
25
|
+
userCredentials: USER_CREDENTIAL_ENV_VARS.filter(isSet),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
5
28
|
const DEFAULT_TIMEOUT_MS = 45_000;
|
|
6
29
|
const RETRY_DELAYS_MS = [1_000, 2_000, 4_000]; // Three retries with backoff
|
|
7
30
|
// NOTE: 429 is intentionally NOT retryable. Every expensive compute endpoint is
|
|
@@ -23,8 +46,64 @@ export const DASHBOARD_ACCOUNT_URL = "https://openephemeris.com/dashboard?tab=ac
|
|
|
23
46
|
export const LOGIN_SIGNUP_URL = "https://openephemeris.com/login?signup=true&redirect=%2Fdashboard%3Ftab%3Daccount";
|
|
24
47
|
export const UPGRADE_URL = "https://openephemeris.com/pricing";
|
|
25
48
|
/** One-tap $5 → 150-credit top-up (signs the user in if needed, then redirects to the prefilled Stripe Payment Link). */
|
|
26
|
-
export const TOPUP_URL = "https://openephemeris.com/topup?pack=payg_5";
|
|
49
|
+
export const TOPUP_URL = "https://openephemeris.com/topup?pack=payg_5&src=mcp_402";
|
|
50
|
+
/**
|
|
51
|
+
* The API's 402 carries `upgrade.topup_url`: the same /topup link, signed with
|
|
52
|
+
* the caller's account so it opens Stripe checkout without a sign-in step
|
|
53
|
+
* (most people hitting the wall are in ChatGPT/Claude and not signed in on the
|
|
54
|
+
* site). Use it when present, re-tagged src=mcp_402 so the funnel can tell MCP
|
|
55
|
+
* clicks from raw API ones; otherwise fall back to the unsigned link.
|
|
56
|
+
*/
|
|
57
|
+
export function resolveTopupUrl(apiTopupUrl) {
|
|
58
|
+
if (typeof apiTopupUrl !== "string")
|
|
59
|
+
return TOPUP_URL;
|
|
60
|
+
try {
|
|
61
|
+
const url = new URL(apiTopupUrl);
|
|
62
|
+
if (url.protocol !== "https:" || url.hostname !== "openephemeris.com" || url.pathname !== "/topup") {
|
|
63
|
+
return TOPUP_URL;
|
|
64
|
+
}
|
|
65
|
+
url.searchParams.set("src", "mcp_402");
|
|
66
|
+
return url.toString();
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return TOPUP_URL;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
27
72
|
export const WALLET_TOPUP_URL = "https://openephemeris.com/wallet";
|
|
73
|
+
const CREDIT_EXHAUSTION_RE = /credits?_insufficient|insufficient[ _-]?credits?|not enough credits|out of credits|credits?[ _-]exhausted|quota[ _-]exceeded|usage_quota|requires \d+ credits/i;
|
|
74
|
+
/**
|
|
75
|
+
* Whether an error body (typically a 422) is really the credit wall: the
|
|
76
|
+
* detail / type / field codes mention insufficient credits or an exceeded
|
|
77
|
+
* quota. Checked over the whole serialized body because the signal can sit in
|
|
78
|
+
* `detail`, `type`, or a nested `errors[].code`.
|
|
79
|
+
*/
|
|
80
|
+
export function looksLikeCreditExhaustion(data, msg) {
|
|
81
|
+
let text = msg ?? "";
|
|
82
|
+
try {
|
|
83
|
+
text += " " + (typeof data === "string" ? data : JSON.stringify(data ?? ""));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
// unserializable body — fall back to the message alone
|
|
87
|
+
}
|
|
88
|
+
return CREDIT_EXHAUSTION_RE.test(text);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* True for errors that mean "this account cannot be charged / is not allowed"
|
|
92
|
+
* — the credit wall (402), missing auth (401), or a tier gate (403). Tools
|
|
93
|
+
* that fan out or degrade gracefully must rethrow these instead of swallowing
|
|
94
|
+
* them: a placeholder result at zero credits hides the paywall (and its
|
|
95
|
+
* top-up link) from the assistant and reads as real data.
|
|
96
|
+
*/
|
|
97
|
+
export function isBillingOrAuthError(err) {
|
|
98
|
+
return err instanceof BackendError && (err.status === 402 || err.status === 401 || err.status === 403);
|
|
99
|
+
}
|
|
100
|
+
/** Rethrows the first billing/auth BackendError among settled results, if any. */
|
|
101
|
+
export function rethrowBillingOrAuth(results) {
|
|
102
|
+
for (const r of results) {
|
|
103
|
+
if (r.status === "rejected" && isBillingOrAuthError(r.reason))
|
|
104
|
+
throw r.reason;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
28
107
|
function sleep(ms) {
|
|
29
108
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
30
109
|
}
|
|
@@ -53,22 +132,24 @@ export class BackendClient {
|
|
|
53
132
|
_authFlowResult = null;
|
|
54
133
|
constructor(config) {
|
|
55
134
|
this.userId = config.userId || "anonymous";
|
|
135
|
+
// See BackendConfig.inheritEnvCredentials — per-session hosted clients opt out.
|
|
136
|
+
const env = config.inheritEnvCredentials === false ? {} : process.env;
|
|
56
137
|
this.jwt =
|
|
57
138
|
config.jwt ||
|
|
58
139
|
config.authToken ||
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
140
|
+
env.OPENEPHEMERIS_JWT ||
|
|
141
|
+
env.ASTROMCP_JWT ||
|
|
142
|
+
env.MERIDIAN_AUTH_TOKEN;
|
|
62
143
|
this.serviceKey =
|
|
63
144
|
config.serviceKey ||
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
145
|
+
env.OPENEPHEMERIS_SERVICE_KEY ||
|
|
146
|
+
env.ASTROMCP_SERVICE_KEY ||
|
|
147
|
+
env.MERIDIAN_SERVICE_KEY;
|
|
67
148
|
this.apiKey =
|
|
68
149
|
config.apiKey ||
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
150
|
+
env.OPENEPHEMERIS_API_KEY ||
|
|
151
|
+
env.ASTROMCP_API_KEY ||
|
|
152
|
+
env.MERIDIAN_API_KEY;
|
|
72
153
|
this.credentialManager = new CredentialManager();
|
|
73
154
|
this.client = axios.create({
|
|
74
155
|
baseURL: config.baseURL ||
|
|
@@ -138,6 +219,27 @@ export class BackendClient {
|
|
|
138
219
|
setJwt(jwt) {
|
|
139
220
|
this.jwt = jwt;
|
|
140
221
|
}
|
|
222
|
+
/**
|
|
223
|
+
* Drop every credential this client holds (service key, API key, JWT). The
|
|
224
|
+
* hosted HTTP server calls this on the module singleton at startup: the
|
|
225
|
+
* singleton is what getActiveClient() falls back to outside a session
|
|
226
|
+
* context, and it was built from the environment at import time.
|
|
227
|
+
*/
|
|
228
|
+
clearCredentials() {
|
|
229
|
+
this.serviceKey = undefined;
|
|
230
|
+
this.apiKey = undefined;
|
|
231
|
+
this.jwt = undefined;
|
|
232
|
+
}
|
|
233
|
+
/** Which configured credential the interceptor will send (diagnostics/tests). */
|
|
234
|
+
credentialKind() {
|
|
235
|
+
if (this.serviceKey)
|
|
236
|
+
return "service_key";
|
|
237
|
+
if (this.apiKey)
|
|
238
|
+
return "api_key";
|
|
239
|
+
if (this.jwt)
|
|
240
|
+
return "jwt";
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
141
243
|
/**
|
|
142
244
|
* Start the device auth flow in the background if not already running.
|
|
143
245
|
* Called automatically when no credentials are found in the interceptor.
|
|
@@ -270,7 +372,12 @@ export class BackendClient {
|
|
|
270
372
|
`through Claude's connector settings, your session expired: in Claude's connector ` +
|
|
271
373
|
`settings, toggle OpenEphemeris off and on to reconnect — this only takes a moment.`, 401, "auth_required", false, LOGIN_SIGNUP_URL);
|
|
272
374
|
}
|
|
273
|
-
|
|
375
|
+
// A 422 whose body says the caller ran out of credits is the credit wall
|
|
376
|
+
// wearing a validation status (the Go visual add-on reservation returned
|
|
377
|
+
// 422 `credits_insufficient` before it moved to 402). Route it through
|
|
378
|
+
// the same 402 path so the assistant gets the one credit-wall message
|
|
379
|
+
// and top-up link, not "Validation error: ...".
|
|
380
|
+
if (status === 402 || (status === 422 && looksLikeCreditExhaustion(data, msg))) {
|
|
274
381
|
// PRICING SSOT: apps/web/config/billing-plans.ts — Explorer grant
|
|
275
382
|
// (EXPLORER.creditsNumeric = 150, one-time), Pro/`developer`
|
|
276
383
|
// (monthlyPrice = 29, credits = 75,000), and CREDIT_TOPUPS
|
|
@@ -289,18 +396,21 @@ export class BackendClient {
|
|
|
289
396
|
// One direct link, not a dashboard tab: /topup?pack=payg_5 signs the
|
|
290
397
|
// user in if needed and redirects straight to the prefilled Stripe
|
|
291
398
|
// Payment Link for the $5 → 150-credit pack.
|
|
399
|
+
const topupUrl = resolveTopupUrl(dataObj.upgrade?.topup_url);
|
|
292
400
|
let upsellMsg = "";
|
|
293
|
-
let actionUrl =
|
|
401
|
+
let actionUrl = topupUrl;
|
|
294
402
|
if (currentTier === 'explorer') {
|
|
295
403
|
upsellMsg =
|
|
296
|
-
`Let the user know warmly: Their 150 free credits are used up. ` +
|
|
297
|
-
`$5 gets 150 more — one tap: ${
|
|
404
|
+
`Let the user know warmly: Their 150 free credits are used up (they do not reset). ` +
|
|
405
|
+
`$5 gets 150 more — one tap, no sign-in, opens checkout directly: ${topupUrl} ` +
|
|
406
|
+
`(show this exact link to the user as a clickable link). ` +
|
|
298
407
|
`Or Pro ($29/mo, 75,000 credits): ${UPGRADE_URL}?plan=developer`;
|
|
299
408
|
}
|
|
300
409
|
else if (currentTier === 'payg') {
|
|
301
410
|
upsellMsg =
|
|
302
411
|
`Let the user know warmly: Their wallet balance is empty. ` +
|
|
303
|
-
`$5 gets 150 more credits — one tap: ${
|
|
412
|
+
`$5 gets 150 more credits — one tap, no sign-in, opens checkout directly: ${topupUrl} ` +
|
|
413
|
+
`(show this exact link to the user as a clickable link). ` +
|
|
304
414
|
`Or Pro ($29/mo, 75,000 credits): ${UPGRADE_URL}?plan=developer`;
|
|
305
415
|
}
|
|
306
416
|
else {
|
package/dist/prompts.js
CHANGED
|
@@ -36,8 +36,8 @@ export const PROMPTS = [
|
|
|
36
36
|
"| `solar_return_year_ahead` | Annual Solar Return chart — themes for the coming birthday year |\n" +
|
|
37
37
|
"| `developer_api_integration` | Help a dev integrate the Open Ephemeris API into their app |\n\n" +
|
|
38
38
|
"## Before Calling Any Tool — Three Rules\n" +
|
|
39
|
-
"1. **Coordinates first**:
|
|
40
|
-
"
|
|
39
|
+
"1. **Coordinates first**: Resolve place names with `location_search` — never recall " +
|
|
40
|
+
"coordinates from memory. Positive = North/East; negative = South/West.\n" +
|
|
41
41
|
"2. **Datetime format**: Pass birth times as local ISO 8601 + separate `timezone` IANA name " +
|
|
42
42
|
"(e.g. `America/Chicago`), OR as a UTC-offset datetime `1990-04-15T14:30:00-05:00`. " +
|
|
43
43
|
"Never append `Z` to a local birth time — `Z` means UTC.\n" +
|
|
@@ -870,7 +870,7 @@ export const PROMPTS = [
|
|
|
870
870
|
"| Feature | MCP Tool | REST Endpoint | Credits |\n" +
|
|
871
871
|
"|---------|----------|---------------|---------|\n" +
|
|
872
872
|
"| Natal chart | `ephemeris_natal_chart` | `POST /ephemeris/natal-chart` | 1 |\n" +
|
|
873
|
-
"| Natal batch (up to 100) | `ephemeris_natal_batch` | `POST /ephemeris/natal/batch` | 1/subject |\n" +
|
|
873
|
+
"| Natal batch (up to 100, Startup+) | `ephemeris_natal_batch` | `POST /ephemeris/natal/batch` | 1/subject |\n" +
|
|
874
874
|
"| Vedic / Jyotish chart | `vedic_chart` | `POST /vedic/chart` | 1 |\n" +
|
|
875
875
|
"| Human Design | `human_design_chart` | `POST /human-design/chart` | 2 |\n" +
|
|
876
876
|
"| BaZi Four Pillars | `chinese_bazi` | `POST /chinese/bazi` | 1 |\n" +
|
|
@@ -879,38 +879,39 @@ export const PROMPTS = [
|
|
|
879
879
|
"| Midpoint composite | `ephemeris_composite_midpoint` | `POST /comparative/composite/midpoint` | 3 |\n" +
|
|
880
880
|
"| House overlay | `ephemeris_overlay` | `POST /comparative/overlay` | 3 |\n" +
|
|
881
881
|
"| Natal transits (now) | `ephemeris_natal_transits` | `POST /comparative/natal-transits` | 3 |\n" +
|
|
882
|
-
"| Transit search (range) | `ephemeris_transits` | `POST /predictive/transits/search` |
|
|
882
|
+
"| Transit search (range) | `ephemeris_transits` | `POST /predictive/transits/search` | 5–70 by span (≤1y 5 … ≤40y 70); tool +1 for the natal chart |\n" +
|
|
883
883
|
"| Relocation chart | `ephemeris_relocation` | `POST /ephemeris/relocation` | 1 |\n" +
|
|
884
884
|
"| Solar return | `ephemeris_solar_return` | `POST /predictive/returns/solar` | 5 |\n" +
|
|
885
885
|
"| Lunar return | `ephemeris_lunar_return` | `POST /predictive/returns/lunar` | 5 |\n" +
|
|
886
886
|
"| Progressions | `ephemeris_progressed_chart` | `POST /ephemeris/progressed` | 1 |\n" +
|
|
887
|
-
"| ACG power lines | `acg_power_lines` | `POST /acg/power-lines` | 10 |\n" +
|
|
888
|
-
"| ACG city hits | `acg_hits` | `POST /acg/hits` |
|
|
887
|
+
"| ACG power lines (Pro+) | `acg_power_lines` | `POST /acg/power-lines` | 10 |\n" +
|
|
888
|
+
"| ACG city hits (Pro+) | `acg_hits` | `POST /acg/hits` | 10 |\n" +
|
|
889
889
|
"| Eclipse finder | `ephemeris_next_eclipse` | `GET /eclipse/next-visible` | 1 |\n" +
|
|
890
|
-
"| Moon phase (current) | `ephemeris_moon_phase` | `GET /ephemeris/moon/phase` |
|
|
891
|
-
"| Next lunar phase | `ephemeris_next_lunar_phase` | `GET /calendar/astrology/moon-phases` |
|
|
892
|
-
"| Electional windows | `ephemeris_electional` | `GET /electional/find-window` | 5 |\n" +
|
|
893
|
-
"| Electional score | `electional_moment_analysis` | `GET /electional/moment-analysis` |
|
|
890
|
+
"| Moon phase (current) | `ephemeris_moon_phase` | `GET /ephemeris/moon/phase` + `/void-of-course` | 2 |\n" +
|
|
891
|
+
"| Next lunar phase | `ephemeris_next_lunar_phase` | `GET /calendar/astrology/moon-phases` | 2 per occurrence |\n" +
|
|
892
|
+
"| Electional windows (Pro+) | `ephemeris_electional` | `GET /electional/find-window` | 5 / 8 / 12 for ≤30 / 60 / 120 days |\n" +
|
|
893
|
+
"| Electional score | `electional_moment_analysis` | `GET /electional/moment-analysis` | 5 |\n" +
|
|
894
894
|
"| Dignities | `ephemeris_dignities` | `POST /ephemeris/dignities` | 1 |\n" +
|
|
895
895
|
"| Hermetic lots | `ephemeris_hermetic_lots` | `POST /ephemeris/hermetic-lots` | 1 |\n" +
|
|
896
896
|
"| Midpoints | `ephemeris_midpoints` | `POST /ephemeris/midpoints` | 1 |\n\n" +
|
|
897
897
|
"## Step 3 — Critical Implementation Patterns\n\n" +
|
|
898
898
|
"### ⚠️ Datetime Handling — Most Common Source of Bugs\n" +
|
|
899
899
|
"```\n" +
|
|
900
|
-
"# CORRECT —
|
|
900
|
+
"# CORRECT — local wall-clock time + IANA zone (server applies historical DST)\n" +
|
|
901
901
|
"POST /ephemeris/natal-chart\n" +
|
|
902
|
-
"{ \"
|
|
903
|
-
"
|
|
904
|
-
"{ \"
|
|
905
|
-
"# CORRECT — UTC required for HD and Vedic\n" +
|
|
902
|
+
"{ \"subject\": { \"name\": \"A\", \"birth_datetime\": { \"iso\": \"1990-04-15T14:30:00\" },\n" +
|
|
903
|
+
" \"birth_location\": { \"latitude\": { \"decimal\": 41.88 }, \"longitude\": { \"decimal\": -87.63 },\n" +
|
|
904
|
+
" \"timezone\": { \"iana_name\": \"America/Chicago\" } } } }\n\n" +
|
|
906
905
|
"POST /human-design/chart\n" +
|
|
907
|
-
"{ \"
|
|
908
|
-
"
|
|
909
|
-
"
|
|
906
|
+
"{ \"birth_datetime_local\": \"1990-04-15T14:30:00\", \"timezone\": { \"iana_name\": \"America/Chicago\" },\n" +
|
|
907
|
+
" \"latitude\": 41.88, \"longitude\": -87.63 }\n\n" +
|
|
908
|
+
"# ALSO CORRECT — an explicit instant (offset or Z): trusted as-is\n" +
|
|
909
|
+
"\"1990-04-15T14:30:00-05:00\" or \"1990-04-15T19:30:00Z\"\n\n" +
|
|
910
|
+
"# WRONG — Z on a local time reads as UTC 2:30pm, not Central 2:30pm (no error raised)\n" +
|
|
911
|
+
"\"1990-04-15T14:30:00Z\"\n" +
|
|
910
912
|
"```\n\n" +
|
|
911
|
-
"**Rule of thumb
|
|
912
|
-
"
|
|
913
|
-
"- Human Design, Vedic: always UTC with Z\n\n" +
|
|
913
|
+
"**Rule of thumb**: on every endpoint, prefer local time + IANA zone. A zone-less clock time " +
|
|
914
|
+
"with no zone is a hard 400; a bare date resolves to 12:00 UTC.\n\n" +
|
|
914
915
|
"### Coordinates\n" +
|
|
915
916
|
"- Decimal degrees, at least 4 decimal places\n" +
|
|
916
917
|
"- Positive = North/East; Negative = South/West\n" +
|
|
@@ -938,9 +939,10 @@ export const PROMPTS = [
|
|
|
938
939
|
"### Credit Optimization\n" +
|
|
939
940
|
"- **Batch natal charts**: use `POST /ephemeris/natal/batch` for multi-user onboarding (1 credit/subject vs N calls)\n" +
|
|
940
941
|
"- **Current transits**: prefer `ephemeris_natal_transits` (3 credits) over " +
|
|
941
|
-
"`ephemeris_transits` (6 credits) when you only need right-now, not a date range\n" +
|
|
942
|
-
"- **
|
|
943
|
-
"
|
|
942
|
+
"`ephemeris_transits` (6+ credits, priced by span) when you only need right-now, not a date range\n" +
|
|
943
|
+
"- **Range searches**: priced by the span you ask for, before compute; a span over the plan cap " +
|
|
944
|
+
"(transit search: Explorer/PAYG 1y, Pro 5y, Startup 10y, Scale 40y) is a 400 `search_span_limit` — split it\n" +
|
|
945
|
+
"- **Failed calls are free**: every 4xx/5xx response is refunded\n" +
|
|
944
946
|
"- **Format matters**: `format: 'llm'` reduces token usage in your LLM calls significantly\n\n" +
|
|
945
947
|
"## Step 4 — Generate a Code Scaffold\n" +
|
|
946
948
|
"Ask: 'What language would you like the scaffold in? And what is the first feature you want to implement?'\n\n" +
|