Blackmoon
A command-line astrology data manager — reads any supported target, writes any supported target, deduplicates everything in between.
What it does
One CLI verb (run with no subcommand). Inputs are file paths or a web account; the output is another file or another web account. Target type is detected from the file extension (.SFcht, .zdb, .xml) or specified explicitly with --from / --to / --target.
blackmoon input.zdb --output out.SFcht
blackmoon a.SFcht b.zdb export.xml --output merged.SFcht
blackmoon --from luna --session $COOKIE --output charts.SFcht
blackmoon --from astro --astro-user me@x.com --astro-pass ... --output charts.SFcht
blackmoon charts.SFcht --normalize
Targets currently wired up:
- File: Solar Fire
.SFchtbinary (cp1252), Zeus.zdbsemicolon-text, Astrodatabank.xml. - Web (authenticated): lunaastrology.com (
--from/--to luna), astro.com (--from/--to astro) — full CRUD including--delete <ids>for astro.com.
--normalize strips non-cp1252 characters and collapses whitespace; with no --output, it edits each input file in place. --output now.SFcht substitutes a UTC timestamp.
Why it exists
Astrologers can have their data spread out across many tools. Consolidating records is time consuming and error prone. Blackmoon automates the task entirely.
How it works
blackmoon is a thin CLI over astrogram. The pipeline for every conversion run is the same five steps:
- Read the sink first (if it already exists or is a web account) so the resulting set never gains duplicates. For LUNA® this is a cheap listing-only fetch keyed by
(name, full datetime); for astro.com it's a full chart fetch withnhorIDs; for files it's a normal parse. - Read each input in batch order, parsing through the per-format reader in
astrogram. Inputs already present in the sink listing are filtered out before merge so duplicates never even reach the deduper. - Merge and dedup with
consolidate::merge_reporting: same name + same date + time within ±2h + lat/lon within 0.1°. First-seen wins; the dropped names are reportable with--verbose. - Optionally normalize the merged set (cp1252 cleanup for any sink, mandatory before writing
.SFcht). - Write the merged set to the chosen sink. Web sinks (
--to luna,--to astro) carry an interactive y/N confirmation before any mutation, plus per-chart progress reporting.
Credentials come from flags or env vars (LUNA_ASTROLOGY_APP, ASTRO_COM_CID, ASTRO_COM_USER, ASTRO_COM_PASS); the help output hides their values. --delay rate-limits HTTP requests. --resume-from <prefix> resumes an interrupted LUNA® fetch.
In-place LUNA® consolidation
blackmoon can dedupe a LUNA® account in place by surfacing candidate
groups for human decision — no record is ever auto-dropped.
One-off delete by UUID
blackmoon --luna-delete <uuid1>,<uuid2> --luna-session "$LUNA_ASTROLOGY_APP"
Each UUID is deleted via POST /phenomena/delete/<uuid> (CakePHP DELETE
method tunnel). Per-record progress is printed; failures are tallied and
the run exits non-zero if any delete failed. --delay is honoured
between deletes.
Interactive consolidation
blackmoon --target luna --consolidate --luna-session "$LUNA_ASTROLOGY_APP"
The flow:
- Fetch every chart in the LUNA® account.
- Cluster duplicate candidates by spacetime tolerance: date equal, time within ±2h, latitude and longitude within 0.1° of each other. Names are not part of the trigger — they appear in the screen as a labelling cue, since the bug we are fixing is exactly the case where two charts for the same person have different names.
- Walk one group per screen. Press
a/b/c/… to mark the keeper — every other chart in that group becomes a Drop. Presssto skip the group (it will be re-prompted on the next run); pressqto quit immediately (decisions already logged are still applied). - Each keystroke is fsync'd to the decision log before the loop advances, so a crash never loses a prior choice.
- After the loop, a single confirmation gate (
y/N) precedes the apply phase, which callsdelete_phenomfor every Drop record.--delayis honoured between deletes.
Singleton groups (no other candidate within tolerance) are silently filtered out — there is nothing to consolidate.
Decision log
Default path: $XDG_CACHE_HOME/blackmoon/luna-decisions.jsonl (or
~/.cache/blackmoon/luna-decisions.jsonl when XDG_CACHE_HOME is unset).
Override with --decision-log <PATH>.
Each line is one JSON DecisionRecord with these fields:
group_id— opaque cluster identifier (first non-empty phenom UUID in the group).phenom_id— LUNA® phenomenon UUID this decision applies to.choice—"keep","drop", or"skip".chart_name— display name at decision time, informational.
Re-running --consolidate reads the log first and silently skips any
group whose group_id is already decided, so resuming is a no-op if
everything was finished. A partially-written trailing line (the
"crash mid-write" case) is silently skipped on read — the user is
re-prompted for that one record on the next run.