Build Dreams & Stars
KINDAback to the verdict
A personal dream and chart journal whose hard problems are getting the astronomy from an ephemeris rather than from a model, handling birth data whose time is unknown and whose time zone is historical, and keeping a wellness-shaped app honest about what it is and what it will not answer.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the application.
Get it nodejs.org, or your version manager. open ↗
Verify
node --version prints v22 or higher - installfree under its open-source licence; a paid licence exists for closed distribution
Why Every planetary position in this build comes from here, and the library's licence constrains what you may do with the result.
Get it Install the bindings for your runtime, fetch the data files, and read the licence terms in full before you build on it.
Verify
Five known dates match a published source within your stated tolerance - API keypay per call; a reading is fractions of a penny
Why The interpretation is one call per reading and is the only generated part of the product.
Get it Create a key with the provider you prefer and set a spend limit in their console as well as the ceiling here.
Verify
One reading generates in the configured language with its model identifier stored - accountfree within the published policy
Why Birthplace lookup is the one thing here that hits somebody else's free service, and their policy sets the rate, the identification requirement and the caching obligation.
Get it Read the policy for the geocoder you choose, note the maximum rate and the identification requirement, and record the date you read it. open ↗
Verify
Your client sends an identifying user agent, respects the stated rate, and caches every result - decidefree
Why The safety check refuses to interpret certain entries and has to offer something real in their place, which is local and cannot be guessed by the build.
Get it Look up the current helpline numbers and text services where you live and write them into setup.
Verify
Setup refuses to complete with the field empty and the contacts render on a flagged entry
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- people(id, label, birth_date, birth_time, time_known, birth_place_id, created_at) · time_known is a real column because an unknown birth time changes what can be computed - places(id, query, display_name, lat, lon, tz_id, source, fetched_at) · the geocoder is called once per place and never again - charts(id, person_id, ephemeris_name, ephemeris_version, computed_at, positions_json, aspects_json, houses_json, houses_omitted_reason) - entries(id, kind, body, created_at, language, mood) · kind is dream or question - readings(id, entry_id, chart_id, body_md, model_id, prompt_version, language, layers_json, created_at, cost_minor) - flags(entry_id, reason, at) · set by the pre-call safety check, and a flagged entry has no reading row - settings(crisis_contacts_md, monthly_cost_ceiling_minor, default_language) - A chart with no birth time has no houses and no rising sign, and the row records why rather than filling them in from a guessed noon.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
DATABASE_PATH | required | ./data/journal.db | People, charts, entries, readings and flags. The diary. |
EPHEMERIS_PATH | required | ./data/ephe | Ephemeris data files. Its version is stored on every chart. |
MODEL_API_KEYsecret | required | sk-...redacted | The text model. Set a provider-side spend limit as well. |
GEOCODER_USER_AGENT | required | dream-journal/1.0 (you@example.com) | Identifies your client as the geocoder's policy requires. A default library agent is not acceptable. |
GEOCODER_MIN_INTERVAL_MS | required | 1000 | Minimum gap between geocoder calls. Check the current published policy before lowering it. |
DEFAULT_LANGUAGE | required | en | The whole reading, headings included, is produced in one language. |
MONTHLY_COST_CEILING | required | 3.00 | Readings stop here. Journal, charts and history keep working. |
The build, in order
Astronomy from an ephemeris, checked against published positions
Planetary positions and aspects are computed by a library and verified against known values.
The licence affects what you may do with the finished thing.
Files
README.mdsrc/astro/ephemeris.tsFive dates in the suite, with a stated tolerance.
Files
tests/positions.test.tsfixtures/positions.jsonThe prose is a view of the data, and it is regenerable.
Files
src/astro/chart.ts
done when · tick each as it passeswatch out- Asking a language model where Saturn was on a given date produces a plausible degree and sign that is wrong often enough to be useless and confident enough that nobody checks, and every later layer of the product inherits the error. Compute from the ephemeris, store the numbers, and have the model read the numbers rather than supply them.
Birth data is the hard input
Unknown times are handled honestly and places are geocoded once, inside the geocoder's published policy.
It omits houses and the rising sign and explains itself.
Files
src/people/birth.tstests/unknown-time.test.tsA 1978 birth is not converted with a 2026 offset.
Files
src/astro/time.tstests/tz-history.test.tsRead the policy, set an identifying user agent, rate limit to it.
Files
src/geo/lookup.tstests/geo-cache.test.ts
done when · tick each as it passeswatch out- Defaulting an unknown birth time to noon silently produces a rising sign and a full set of houses that are simply invented, and they are the first things the reading talks about. Record time_known, omit everything that depends on it, and say in the output which parts could not be computed and why. The same honesty applies to the offset: a birth in 1978 uses that year's rules for that zone, which a naive conversion from the current offset will get wrong by an hour or more.
The safety check runs before the model, not after
An entry describing crisis is saved, flagged and answered with your configured local contacts instead of an interpretation.
A spy in the suite proves no call happened.
Files
src/safety/screen.tstests/safety.test.tsLocal to the person, because a generic number helps nobody.
Files
src/setup/contacts.tsThe journal keeps the words; the app declines the reading.
Files
src/entries/create.ts
done when · tick each as it passeswatch out- Running the check on the model's output means the generation has already happened and the person has already been shown an interpretation of the worst night of their life by a dream app. Check the input before any call is made, and treat a false positive as an acceptable cost, because the failure in the other direction is not.
Readings that stay inside what they are
Generated text is layered, labelled, single-language, and checked against the things it must not say.
The chart data goes in; interpretation comes out.
Files
src/readings/generate.tssrc/readings/prompts.tsHeadings included, and check rather than trust.
Files
src/readings/language.tstests/language.test.tsOne regeneration, then a failure the person can see.
Files
src/readings/guard.tstests/guard.test.ts
done when · tick each as it passeswatch out- Translating an English reading into another language after generating it produces headings in one language and body text in another and idioms that land strangely, and people notice immediately. Pass the language into the prompt, require the entire response in it, and check the output before storing rather than trusting the instruction.
It is a diary, so treat it like oneproduct builder
Everything stays local, exports as plain files, deletes completely, and the model spend has a ceiling.
Watch the network for a full session and count them.
Files
src/app/layout.tsxtests/network.test.tsOne for reading years later, one for reimporting.
Files
src/export/journal.tstests/roundtrip.test.tsA diary you cannot fully delete is not private.
Files
src/people/delete.tstests/delete.test.ts
done when · tick each as it passeswatch out- Adding a convenience sync so the journal is available on a phone turns a private diary into an account on somebody's server, and the people most likely to write the most personal entries are the ones least likely to read the setup screen where you mentioned it. Keep it local, and make export the answer to portability.
That is the whole plan for Dreams & Stars. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Credits, payments and a paywall.
- Compatibility readings between two people.
- Any predictive claim about events, health or relationships.
- A version aimed at children.
- the three-layer interpretation methodology (psychological/Jungian, Islamic/Ibn Sirin traditional, actionable) tuned over real usage, not a first draft
- real natal-chart accuracy from ephemeris data instead of an LLM approximating planetary positions
- a spend-reservation system that caps provider cost before generation starts, so one bad request can't blow the budget
- native-quality bilingual Turkish/English copy, not machine-translated
- the credits/paywall, Stripe and RevenueCat payment plumbing
- A symbol index built from your own entries, which is more useful than any general dream dictionary
- Transit computation against a stored chart, once the natal positions are verified and trusted
- A yearly review that shows recurring themes by frequency, with the counts visible rather than summarised
Need the files? The project pack on the verdict page hands your agent the whole brief · more wellness.