rave-engine 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adam Blvck / Blvck Studios
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,33 @@
1
+ rave-engine
2
+ Copyright (c) 2026 Adam Blvck / Blvck Studios
3
+ Licensed under the MIT License (see LICENSE).
4
+
5
+ This product includes and/or depends on the following third-party software:
6
+
7
+ - astronomia (https://github.com/commenthol/astronomia) — MIT License.
8
+ Used as the default, pure-JavaScript ephemeris backend (VSOP87 planetary
9
+ theory, lunar theory, sidereal time, nutation). Bundles its own data; no
10
+ external ephemeris files are required.
11
+
12
+ - luxon (https://github.com/moment/luxon) — MIT License.
13
+ Date/time and IANA timezone handling.
14
+
15
+ - @vvo/tzdb (https://github.com/vvo/tzdb) — MIT License.
16
+ IANA timezone metadata and current offsets.
17
+
18
+ - city-timezones (https://github.com/kevinroberts/city-timezones) — MIT License.
19
+ City → timezone and representative coordinate lookups.
20
+
21
+ Optional, NOT bundled or required:
22
+
23
+ - swisseph (https://github.com/mivion/swisseph) — AGPL / commercial dual
24
+ license. rave-engine can use it as an opt-in high-precision backend
25
+ (EPHE_BACKEND=swisseph) only if a consumer installs it themselves. It is not
26
+ a dependency of this package and none of its code or Astrodienst data files
27
+ are distributed with rave-engine.
28
+
29
+ Reference data:
30
+
31
+ - The Human Design bodygraph reference data (gate→center map and the 36
32
+ channels) and the Gene Keys / I-Ching mandala mapping are based on the
33
+ standard, widely-published Human Design and Gene Keys systems.
package/README.md ADDED
@@ -0,0 +1,354 @@
1
+ # rave-engine
2
+
3
+ [![npm version](https://img.shields.io/npm/v/rave-engine.svg)](https://www.npmjs.com/package/rave-engine)
4
+ [![coverage](https://img.shields.io/badge/coverage-90%25-brightgreen.svg)](#tests)
5
+ [![tests](https://img.shields.io/badge/tests-216%20passing-brightgreen.svg)](#tests)
6
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+ [![node](https://img.shields.io/badge/node-%3E%3D16-brightgreen.svg)](package.json)
8
+ [![dependencies](https://img.shields.io/badge/native%20build-none-brightgreen.svg)](#accuracy--precision)
9
+
10
+ **A pure-JavaScript Human Design & Gene Keys engine — with extended astrology - from the maintainer of official Gene Keys Profiler and Event Horizon Election Engine, and Gene Key's Integral Human Design.**
11
+
12
+ From a birth date, time and timezone (and an optional location), `rave-engine`
13
+ computes:
14
+
15
+ - 🧬 **Gene Keys** — the gene key (hexagram) + line for every sphere of the
16
+ Hologenetic Profile (`lifeswork`, `purpose`, `pearl`, `iq`, `eq`, …).
17
+ - ⚡ **Human Design** — the full bodygraph: every planetary gate activation
18
+ (13 bodies × Personality + Design), **activated gates**, **defined channels**,
19
+ **defined centers**, **open centers**, and the derived **Type**, **Authority**
20
+ and **Profile**.
21
+ - 🪐 **Astrology** — **Ascendant**, **Descendant**, **Midheaven (MC)**,
22
+ **IC**, and house cusps (**Placidus**, **Whole Sign** or **Equal**).
23
+
24
+ It is **pure JavaScript** — no native build, no `node-gyp`, and **no ephemeris
25
+ data files**. Planetary positions come from the MIT-licensed
26
+ [`astronomia`](https://github.com/commenthol/astronomia) package (VSOP87 + lunar
27
+ theory), validated to the exact gate/line against Swiss Ephemeris.
28
+
29
+ <p align="center">
30
+ <img src="assets/rave-mandala.png" alt="The 64-gate Human Design / Gene Keys mandala wheel around the bodygraph" width="440">
31
+ </p>
32
+
33
+ ```bash
34
+ npm install rave-engine
35
+ ```
36
+
37
+ ---
38
+
39
+ ## Quickstart
40
+
41
+ ```js
42
+ const { computeChart } = require('rave-engine');
43
+
44
+ const chart = computeChart({
45
+ birthdate: '1972-08-02', // YYYY-MM-DD (or YYYY-M-D)
46
+ birthtime: '14:30', // HH:mm (or H:mm, or HH:mm:ss)
47
+ timezone: 'Asia/Bangkok', // any IANA timezone
48
+ // location: { lat: 13.75, lng: 100.5 }, // optional — see "Location" below
49
+ // houseSystem: 'placidus', // 'placidus' | 'whole' | 'equal'
50
+ });
51
+
52
+ console.log(chart.humanDesign.type); // 'Manifesting Generator'
53
+ console.log(chart.humanDesign.authority); // 'Sacral'
54
+ console.log(chart.humanDesign.profile); // '3/5'
55
+ console.log(chart.geneKeys.spheres.lifeswork); // { gk: 33, line: 3 }
56
+ console.log(chart.astrology.angles.ascendant.sign); // 'Sagittarius'
57
+ ```
58
+
59
+ `computeChart` returns three coherent sections:
60
+
61
+ ```jsonc
62
+ {
63
+ "input": {
64
+ "birthdate": "1972-08-02", "birthtime": "14:30", "timezone": "Asia/Bangkok",
65
+ "birth_utc": "1972-08-02T07:30:00.000Z",
66
+ "location": { "lat": 13.75, "lng": 100.51, "city": "Bangkok", "source": "timezone-city" }
67
+ },
68
+
69
+ "geneKeys": {
70
+ "spheres": {
71
+ "lifeswork": { "gk": 33, "line": 3 },
72
+ "evolution": { "gk": 19, "line": 3 },
73
+ "radiance": { "gk": 24, "line": 5 },
74
+ "purpose": { "gk": 44, "line": 5 },
75
+ "iq": { "gk": 12, "line": 6 }, "eq": { "gk": 4, "line": 4 },
76
+ "pearl": { "gk": 10, "line": 2 }, "relating": { "gk": 4, "line": 1 },
77
+ "attraction": { "gk": 11, "line": 3 }, "sq": { "gk": 12, "line": 3 },
78
+ "core": { "gk": 12, "line": 1 }, "culture": { "gk": 58, "line": 5 },
79
+ "stability": { "gk": 16, "line": 1 }, "creativity": { "gk": 57, "line": 1 }
80
+ }
81
+ },
82
+
83
+ "humanDesign": {
84
+ "type": "Manifesting Generator",
85
+ "authority": "Sacral",
86
+ "profile": "3/5",
87
+ "definitionCount": 6,
88
+ "activatedGates": [4, 10, 11, 12, 16, 19, 24, 33, 34, 44, 45, 48, 51, 56, 57, 58, 60, 61, 62],
89
+ "definedChannels": [{ "key": "34-57", "gates": [34, 57], "centers": ["sacral", "spleen"], "name": "Power" }, "…"],
90
+ "definedCenters": ["head", "ajna", "throat", "g", "sacral", "spleen"],
91
+ "openCenters": ["heart", "solarplexus", "root"],
92
+ "centers": { "head": true, "ajna": true, "…": "…" },
93
+ "p_": { "sun": { "gate": 33, "line": 3, "color": 4, "retrograde": false, "longitude": 130.09, "…": "…" }, "…": "…" },
94
+ "d_": { "…": "…" },
95
+ "activations": { "personality": [ "…13 bodies…" ], "design": [ "…13 bodies…" ] }
96
+ },
97
+
98
+ "astrology": {
99
+ "location": { "lat": 13.75, "lng": 100.51, "city": "Bangkok" },
100
+ "angles": {
101
+ "ascendant": { "longitude": 249.97, "sign": "Sagittarius", "degInSign": 9.97, "gateLine": { "gate": 9, "line": 5 } },
102
+ "descendant": { "sign": "Gemini", "…": "…" },
103
+ "mc": { "sign": "Virgo", "…": "…" },
104
+ "ic": { "sign": "Pisces", "…": "…" }
105
+ },
106
+ "houses": { "system": "placidus", "cusps": [ { "house": 1, "longitude": 249.97, "sign": "Sagittarius" }, "…" ] }
107
+ }
108
+ }
109
+ ```
110
+
111
+ If no location can be resolved, `astrology` is `null` (everything else still
112
+ computes — astrology is the only part that needs a place of birth).
113
+
114
+ ---
115
+
116
+ ## 🌐 Free API & live site
117
+
118
+ **[rave-engine.netlify.app](https://rave-engine.netlify.app)** — a free, local-only
119
+ API and an in-browser playground. Every endpoint is a plain `GET` with open CORS,
120
+ so you can call it from a browser, a notebook, or hand it straight to an AI.
121
+
122
+ <p align="center">
123
+ <img src="assets/site-demo.png" alt="The rave-engine landing page with the live in-browser API demo computing a chart" width="760">
124
+ </p>
125
+
126
+ ```bash
127
+ curl "https://rave-engine.netlify.app/api/chart?date=1972-08-02&time=14:30&tz=Asia/Bangkok"
128
+ ```
129
+
130
+ | Endpoint | Returns |
131
+ | --- | --- |
132
+ | `/api/chart` | Full chart — Gene Keys + Human Design + Astrology |
133
+ | `/api/genekeys` | Gene Keys spheres |
134
+ | `/api/humandesign` | Human Design bodygraph |
135
+ | `/api/astrology` | Ascendant / MC / houses |
136
+ | `/api/prompt` | A ready-to-paste **AI interpretation prompt** |
137
+ | `/api/timezones?q=bali` | IANA timezone search |
138
+
139
+ Params: `date=YYYY-MM-DD` · `time=HH:mm` · `tz=IANA` · `[lat lng house=placidus|whole|equal]`
140
+
141
+ Dark and light, fully responsive:
142
+
143
+ | Dark | Light |
144
+ | --- | --- |
145
+ | <img src="assets/site-hero.png" alt="rave-engine landing page, dark mode" width="380"> | <img src="assets/site-hero-light.png" alt="rave-engine landing page, light mode" width="380"> |
146
+
147
+ The site and API live in [`site/`](site) and [`netlify/functions/`](netlify/functions);
148
+ the deploy config is [`netlify.toml`](netlify.toml). Nothing about a birth ever
149
+ leaves the function — the ephemeris is computed in-process.
150
+
151
+ ---
152
+
153
+ ## The three layers
154
+
155
+ ### 🧬 Gene Keys (the spheres)
156
+
157
+ <p align="center">
158
+ <img src="assets/hologenetic-profile.png" alt="Gene Keys Hologenetic Profile — the spheres and their planetary activations" width="380">
159
+ </p>
160
+
161
+ Each sphere is keyed by its gene key (`gk`, the I-Ching hexagram 1–64) and line
162
+ (1–6), mapped from a planet at the Personality (birth) or Design (~88° of solar
163
+ arc before birth) moment:
164
+
165
+ | Sphere | Source | Sphere | Source |
166
+ | --- | --- | --- | --- |
167
+ | `lifeswork` | Personality Sun | `attraction` | Design Moon |
168
+ | `evolution` | Personality Earth | `sq` | Design Venus |
169
+ | `radiance` | Design Sun | `core` | Design Mars |
170
+ | `purpose` | Design Earth | `culture` | Design Jupiter |
171
+ | `iq` | Personality Venus | `stability` | Design Saturn |
172
+ | `eq` | Personality Mars | `creativity` | Design Uranus |
173
+ | `pearl` | Personality Jupiter | `relating` | Personality Mercury |
174
+
175
+ ### ⚡ Human Design (the bodygraph)
176
+
177
+ The bodygraph is derived from **26 activations** — 13 bodies (Sun, Earth, Moon,
178
+ North/South Node, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto)
179
+ at both the Personality and Design moments:
180
+
181
+ ```js
182
+ const { computeChart } = require('rave-engine');
183
+ const hd = computeChart({ birthdate: '1972-08-02', birthtime: '14:30', timezone: 'Asia/Bangkok' }).humanDesign;
184
+
185
+ hd.activatedGates; // [4, 10, 11, … ] — every gate lit by a planet
186
+ hd.definedChannels; // channels where BOTH gates are activated
187
+ hd.definedCenters; // centers touched by a defined channel
188
+ hd.openCenters; // the rest
189
+ hd.type; // Generator | Manifesting Generator | Projector | Manifestor | Reflector
190
+ hd.authority; // Emotional | Sacral | Splenic | Ego | Self-Projected | Mental | Lunar
191
+ hd.profile; // e.g. '3/5' (Personality Sun line / Design Sun line)
192
+ ```
193
+
194
+ Reference data (gate→center map and the 36 channels) is the standard Human
195
+ Design bodygraph and is exported for your own use:
196
+
197
+ ```js
198
+ const { GATE_CENTER, CHANNELS, CENTERS, computeBodygraph } = require('rave-engine');
199
+ ```
200
+
201
+ ### 🪐 Astrology (angles & houses)
202
+
203
+ ```js
204
+ const { computeAngles, computeHouses } = require('rave-engine');
205
+
206
+ const angles = computeAngles({ jdUT: 2441531.8125, lat: 13.75, lng: 100.5 });
207
+ // { ascendant, descendant, mc, ic } — each with longitude, sign, degInSign, gateLine
208
+
209
+ const houses = computeHouses({ jdUT: 2441531.8125, lat: 13.75, lng: 100.5, system: 'placidus' });
210
+ // { system, cusps: [{ house, longitude, sign, degInSign }, …], angles }
211
+ ```
212
+
213
+ Angles and houses need only sidereal time + obliquity (no ephemeris). Placidus
214
+ falls back to Whole Sign above the polar circle, where it is mathematically
215
+ undefined.
216
+
217
+ ---
218
+
219
+ ## Location
220
+
221
+ The astrology section needs a **place of birth**. You have two options:
222
+
223
+ 1. **Explicit coordinates** (most accurate):
224
+ ```js
225
+ computeChart({ /* … */ location: { lat: 13.7563, lng: 100.5018 } });
226
+ ```
227
+ 2. **Just the timezone** — `rave-engine` derives a representative location from
228
+ the most-populous city in that timezone, which is plenty for an Ascendant:
229
+ ```js
230
+ computeChart({ birthdate, birthtime, timezone: 'Asia/Bangkok' });
231
+ // → location { city: 'Bangkok', lat: 13.75, lng: 100.51, source: 'timezone-city' }
232
+ ```
233
+
234
+ Coordinates use **north-positive latitude** and **east-positive longitude**.
235
+
236
+ ### Historical timezones
237
+
238
+ Birth charts are historical, so the engine honours the **full IANA history** of
239
+ each zone — not just modern rules. Offsets resolve through
240
+ [Luxon](https://moment.github.io/luxon/) against the runtime's IANA/ICU tz
241
+ database, which correctly handles 20th-century edge cases such as US year-round
242
+ DST in 1974, British Double Summer Time (1944/1947 = UTC+2), Poland skipping DST
243
+ in 1970, Nepal's 1986 switch to UTC+5:45, and pre-standardisation Local Mean
244
+ Time. These are regression-locked in
245
+ [`test/timezone-history.test.js`](test/timezone-history.test.js).
246
+
247
+ > Accuracy of historical offsets depends on the tz database in your JS runtime.
248
+ > Use a current Node.js (full ICU is the default since Node 13) for complete
249
+ > 20th-century coverage. Nonexistent local times (spring-forward gaps) shift
250
+ > forward into DST; ambiguous times (fall-back overlaps) resolve to the earlier
251
+ > offset.
252
+
253
+ ### Timezone autocomplete
254
+
255
+ ```js
256
+ const { searchTimezones, locationForTimezone } = require('rave-engine');
257
+
258
+ searchTimezones('bali'); // → [{ ianaName: 'Asia/Makassar', mainCity: 'Makassar', … }]
259
+ searchTimezones('tokyo'); // → [{ ianaName: 'Asia/Tokyo', currentOffsetMinutes: 540, … }]
260
+ locationForTimezone('Europe/London'); // → { lat: 51.5, lng: -0.12, city: 'London', … }
261
+ ```
262
+
263
+ ---
264
+
265
+ ## CLI
266
+
267
+ ```bash
268
+ # Full chart (readable summary)
269
+ npx rave --chart --date 1972-08-02 --time 14:30 --tz Asia/Bangkok
270
+
271
+ # With explicit coordinates and a house system, as JSON
272
+ npx rave --chart --date 1972-08-02 --time 14:30 --tz Asia/Bangkok \
273
+ --lat 13.75 --lng 100.5 --house whole --json
274
+
275
+ # Classic profile (p_/d_ + spheres)
276
+ npx rave --date 1972-08-02 --time 14:30 --tz Asia/Bangkok
277
+
278
+ # Timezone search
279
+ npx rave --tz-search bali
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Accuracy & precision
285
+
286
+ Planetary positions come from `astronomia` (VSOP87 + lunar theory) and match
287
+ Swiss Ephemeris to well within a Gene Keys **line** (0.9375°): typically `<1″`
288
+ for the Sun and planets and `~10″` for the Moon. The gate/line/retrograde output
289
+ is regression-locked against Swiss Ephemeris golden vectors (see
290
+ [`test/profile.test.js`](test/profile.test.js)) **and validated against five
291
+ real charts from a third-party calculator** — 125 of 130 body activations match
292
+ to the exact gate.line, with the remainder within a single line (design-Moon
293
+ timing, true-node algorithm and sub-line ephemeris differences between
294
+ calculators). See [`test/reference-charts.test.js`](test/reference-charts.test.js).
295
+
296
+ Lunar nodes use the **true node** (oscillating), matching mainstream Human
297
+ Design calculators.
298
+
299
+ ### Optional high-precision backend
300
+
301
+ If you want Swiss Ephemeris precision (and accept its AGPL/commercial license),
302
+ install it yourself and opt in — it is **not** a dependency of this package:
303
+
304
+ ```bash
305
+ npm install swisseph # native build; provide ephemeris files via EPHE_PATH
306
+ EPHE_BACKEND=swisseph node your-app.js
307
+ ```
308
+
309
+ `rave-engine` auto-detects it and falls back to the pure-JS backend if it isn't
310
+ available.
311
+
312
+ ---
313
+
314
+ ## API
315
+
316
+ | Export | Returns |
317
+ | --- | --- |
318
+ | `computeChart(input)` | Full `{ input, geneKeys, humanDesign, astrology, _meta }` |
319
+ | `computeProfile(input)` | `{ input, engine: { p_, d_, spheres, _meta } }` (back-compat) |
320
+ | `computeBodygraph(activations)` | `{ type, authority, profile, activatedGates, definedChannels, definedCenters, openCenters, centers, … }` |
321
+ | `computeActivations({ birthUtc })` | The 26 raw activations (`{ personality, design }`) |
322
+ | `computeAngles({ jdUT, lat, lng })` | `{ ascendant, descendant, mc, ic }` |
323
+ | `computeHouses({ jdUT, lat, lng, system })` | `{ system, cusps, angles }` |
324
+ | `searchTimezones(query, { limit })` | Ranked IANA zones with offsets + city hints |
325
+ | `locationForTimezone(iana)` | Representative `{ lat, lng, city, country }` |
326
+ | `parseBirthToUtc({ birthdate, birthtime, timezone })` | UTC `Date` |
327
+ | `mapLongitudeDegrees(lon)` | `{ hexagram, line, color }` |
328
+
329
+ Also exported: `GATE_CENTER`, `CHANNELS`, `CENTERS`, `signOf`, `SIGNS`,
330
+ `resolveLocation`, `getBackend`.
331
+
332
+ ---
333
+
334
+ ## Tests
335
+
336
+ ```bash
337
+ npm test # run the suite
338
+ npm run coverage # run with a coverage report
339
+ npm run coverage:badge # refresh the coverage badge above from a fresh run
340
+ ```
341
+
342
+ 200+ tests covering: the mandala mapping, regression-locked profiles, the
343
+ bodygraph reference data + derivation, the astronomia backend, the astrology
344
+ angles/houses (verified against the horizon/meridian geometry), the location
345
+ lookup, the end-to-end chart, **five real reference charts** from a third-party
346
+ Swiss-Ephemeris calculator, and **historical IANA timezone offsets** across the
347
+ 20th century.
348
+
349
+ ---
350
+
351
+ ## License
352
+
353
+ MIT © Adam Blvck / Blvck Studios. See [LICENSE](LICENSE) and [NOTICE](NOTICE)
354
+ for third-party attributions.
package/bin/rave.js ADDED
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { computeProfile, computeChart, searchTimezones } = require('../src');
4
+
5
+ function parseArgs(argv) {
6
+ // Tiny argv parser. Supports `--key value` and `--key=value`. No deps.
7
+ const out = {};
8
+ const args = argv.slice(2);
9
+ for (let i = 0; i < args.length; i += 1) {
10
+ const a = args[i];
11
+ if (!a.startsWith('--')) continue;
12
+ const eq = a.indexOf('=');
13
+ let key;
14
+ let val;
15
+ if (eq >= 0) {
16
+ key = a.slice(2, eq);
17
+ val = a.slice(eq + 1);
18
+ } else {
19
+ key = a.slice(2);
20
+ const next = args[i + 1];
21
+ if (next !== undefined && !next.startsWith('--')) {
22
+ val = next;
23
+ i += 1;
24
+ } else {
25
+ val = true;
26
+ }
27
+ }
28
+ out[key] = val;
29
+ }
30
+ return out;
31
+ }
32
+
33
+ function printUsage() {
34
+ const lines = [
35
+ 'rave-engine CLI',
36
+ '',
37
+ 'Compute a profile (p_/d_ + spheres):',
38
+ ' rave --date 1972-08-02 --time 14:30 --tz Asia/Bangkok',
39
+ '',
40
+ 'Compute the full chart (Gene Keys + Human Design + Astrology):',
41
+ ' rave --chart --date 1972-08-02 --time 14:30 --tz Asia/Bangkok',
42
+ ' rave --chart --date 1972-08-02 --time 14:30 --tz Asia/Bangkok --lat 13.75 --lng 100.5',
43
+ '',
44
+ 'Aliases: --date|--birthdate, --time|--birthtime, --tz|--timezone',
45
+ '',
46
+ 'Search timezones:',
47
+ ' rave --tz-search bali',
48
+ ' rave --tz-search "los ang" --limit 5',
49
+ '',
50
+ 'Flags:',
51
+ ' --chart Full Gene Keys + Human Design + Astrology output.',
52
+ ' --lat --lng Birth coordinates (else derived from the timezone city).',
53
+ ' --house House system for --chart: placidus|whole|equal (default placidus).',
54
+ ' --json Print full JSON instead of the readable summary.',
55
+ ' --pretty Indent output (default: 2 spaces).',
56
+ ' --help Show this help.',
57
+ ];
58
+ // eslint-disable-next-line no-console
59
+ console.log(lines.join('\n'));
60
+ }
61
+
62
+ function formatOffsetMinutes(min) {
63
+ const sign = min >= 0 ? '+' : '-';
64
+ const abs = Math.abs(min);
65
+ const hh = String(Math.floor(abs / 60)).padStart(2, '0');
66
+ const mm = String(abs % 60).padStart(2, '0');
67
+ return `${sign}${hh}:${mm}`;
68
+ }
69
+
70
+ function runTzSearch(query, limit) {
71
+ const results = searchTimezones(query, { limit });
72
+ if (results.length === 0) {
73
+ // eslint-disable-next-line no-console
74
+ console.log(`(no matches for "${query}")`);
75
+ return 0;
76
+ }
77
+
78
+ const widthIana = Math.max(...results.map((r) => r.ianaName.length));
79
+ for (const r of results) {
80
+ const offset = formatOffsetMinutes(r.currentOffsetMinutes);
81
+ const city = r.mainCity || '';
82
+ const country = r.countryName || '';
83
+ // eslint-disable-next-line no-console
84
+ console.log(
85
+ `${r.ianaName.padEnd(widthIana)} ${offset} ${city}${country ? `, ${country}` : ''}`
86
+ );
87
+ }
88
+ return 0;
89
+ }
90
+
91
+ function printChartSummary(chart) {
92
+ const { geneKeys, humanDesign: hd, astrology } = chart;
93
+ const lines = [];
94
+ lines.push(`Birth (UTC): ${chart.input.birth_utc}`);
95
+ lines.push('');
96
+ lines.push('— Human Design —');
97
+ lines.push(` Type: ${hd.type}`);
98
+ lines.push(` Authority: ${hd.authority}`);
99
+ lines.push(` Profile: ${hd.profile}`);
100
+ lines.push(` Definition:${hd.definitionCount} channels`);
101
+ lines.push(` Defined centers: ${hd.definedCenters.join(', ') || '(none)'}`);
102
+ lines.push(` Open centers: ${hd.openCenters.join(', ') || '(none)'}`);
103
+ lines.push(` Channels: ${hd.definedChannels.map((c) => `${c.key}${c.name ? ` (${c.name})` : ''}`).join(', ') || '(none)'}`);
104
+ lines.push(` Activated gates: ${hd.activatedGates.join(', ')}`);
105
+ lines.push('');
106
+ lines.push('— Gene Keys (spheres) —');
107
+ for (const [sphere, v] of Object.entries(geneKeys.spheres)) {
108
+ lines.push(` ${sphere.padEnd(11)} ${v.gk}.${v.line}`);
109
+ }
110
+ if (astrology) {
111
+ lines.push('');
112
+ lines.push(`— Astrology (${astrology.location.city || 'lat/lng'}, ${astrology.houses.system} houses) —`);
113
+ const a = astrology.angles;
114
+ const fa = (x) => `${x.sign} ${x.degInSign.toFixed(2)}°`;
115
+ lines.push(` Ascendant: ${fa(a.ascendant)}`);
116
+ lines.push(` Descendant: ${fa(a.descendant)}`);
117
+ lines.push(` MC: ${fa(a.mc)}`);
118
+ lines.push(` IC: ${fa(a.ic)}`);
119
+ } else {
120
+ lines.push('');
121
+ lines.push('— Astrology — (no location resolved; pass --lat/--lng or a recognized --tz)');
122
+ }
123
+ return lines.join('\n');
124
+ }
125
+
126
+ function runChart(args) {
127
+ const birthdate = args.date || args.birthdate;
128
+ const birthtime = args.time || args.birthtime;
129
+ const timezone = args.tz || args.timezone;
130
+ if (!birthdate || !birthtime || !timezone) {
131
+ // eslint-disable-next-line no-console
132
+ console.error('error: --date, --time and --tz are all required\n');
133
+ printUsage();
134
+ return 2;
135
+ }
136
+ const lat = Number(args.lat);
137
+ const lng = Number(args.lng);
138
+ const location =
139
+ Number.isFinite(lat) && Number.isFinite(lng) ? { lat, lng } : undefined;
140
+ const chart = computeChart({
141
+ birthdate,
142
+ birthtime,
143
+ timezone,
144
+ location,
145
+ houseSystem: args.house || 'placidus',
146
+ });
147
+
148
+ if (args.json) {
149
+ const indent = args.pretty === false ? 0 : 2;
150
+ // eslint-disable-next-line no-console
151
+ console.log(JSON.stringify(chart, null, indent));
152
+ } else {
153
+ // eslint-disable-next-line no-console
154
+ console.log(printChartSummary(chart));
155
+ }
156
+ return 0;
157
+ }
158
+
159
+ function runProfile(args) {
160
+ const birthdate = args.date || args.birthdate;
161
+ const birthtime = args.time || args.birthtime;
162
+ const timezone = args.tz || args.timezone;
163
+
164
+ if (!birthdate || !birthtime || !timezone) {
165
+ // eslint-disable-next-line no-console
166
+ console.error('error: --date, --time and --tz are all required\n');
167
+ printUsage();
168
+ return 2;
169
+ }
170
+
171
+ const out = computeProfile({ birthdate, birthtime, timezone });
172
+ const indent = args.pretty === false ? 0 : 2;
173
+ // eslint-disable-next-line no-console
174
+ console.log(JSON.stringify(out, null, indent));
175
+ return 0;
176
+ }
177
+
178
+ function main(argv) {
179
+ const args = parseArgs(argv);
180
+
181
+ if (args.help || args.h) {
182
+ printUsage();
183
+ return 0;
184
+ }
185
+
186
+ if (args['tz-search'] !== undefined && args['tz-search'] !== true) {
187
+ const limit = Number.isFinite(Number(args.limit)) ? Number(args.limit) : 10;
188
+ return runTzSearch(String(args['tz-search']), limit);
189
+ }
190
+
191
+ if (args.chart) {
192
+ return runChart(args);
193
+ }
194
+
195
+ return runProfile(args);
196
+ }
197
+
198
+ try {
199
+ process.exitCode = main(process.argv) || 0;
200
+ } catch (e) {
201
+ // eslint-disable-next-line no-console
202
+ console.error(`error: ${e?.message || e}`);
203
+ process.exitCode = 1;
204
+ }
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "rave-engine",
3
+ "version": "1.0.0",
4
+ "description": "Pure-JS Human Design & Gene Keys engine: gates, lines, centers, channels and a full bodygraph (Type/Authority/Profile), plus extended astrology (Ascendant/MC/houses) and IANA timezone tools. No native build, no ephemeris data files.",
5
+ "main": "src/index.js",
6
+ "type": "commonjs",
7
+ "bin": {
8
+ "rave": "bin/rave.js"
9
+ },
10
+ "exports": {
11
+ ".": "./src/index.js",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "src",
16
+ "bin",
17
+ "README.md",
18
+ "LICENSE",
19
+ "NOTICE"
20
+ ],
21
+ "scripts": {
22
+ "test": "jest",
23
+ "coverage": "jest --coverage",
24
+ "coverage:badge": "jest --coverage && node scripts/make-coverage-badge.js",
25
+ "cli": "node bin/rave.js",
26
+ "prepublishOnly": "jest"
27
+ },
28
+ "keywords": [
29
+ "human-design",
30
+ "gene-keys",
31
+ "bodygraph",
32
+ "astrology",
33
+ "ephemeris",
34
+ "natal-chart",
35
+ "ascendant",
36
+ "midheaven",
37
+ "houses",
38
+ "placidus",
39
+ "i-ching",
40
+ "hexagram",
41
+ "gates",
42
+ "channels",
43
+ "centers",
44
+ "timezone"
45
+ ],
46
+ "author": "Adam Blvck <adam@blvckstudios.com>",
47
+ "license": "MIT",
48
+ "homepage": "https://rave-engine.netlify.app",
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/adamblvck/rave-engine.git"
52
+ },
53
+ "bugs": {
54
+ "url": "https://github.com/adamblvck/rave-engine/issues"
55
+ },
56
+ "engines": {
57
+ "node": ">=16"
58
+ },
59
+ "dependencies": {
60
+ "@vvo/tzdb": "^6.157.0",
61
+ "astronomia": "^4.2.0",
62
+ "city-timezones": "^1.3.0",
63
+ "luxon": "^3.5.0"
64
+ },
65
+ "devDependencies": {
66
+ "jest": "^30.3.0"
67
+ }
68
+ }