hoshi-astro 1.7.0__tar.gz

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 (82) hide show
  1. hoshi_astro-1.7.0/.github/workflows/ci.yml +29 -0
  2. hoshi_astro-1.7.0/.github/workflows/release.yml +83 -0
  3. hoshi_astro-1.7.0/.gitignore +17 -0
  4. hoshi_astro-1.7.0/CHANGELOG.md +1 -0
  5. hoshi_astro-1.7.0/CLAUDE.md +213 -0
  6. hoshi_astro-1.7.0/LICENSE +21 -0
  7. hoshi_astro-1.7.0/PKG-INFO +204 -0
  8. hoshi_astro-1.7.0/README.md +161 -0
  9. hoshi_astro-1.7.0/docs/sdk.md +107 -0
  10. hoshi_astro-1.7.0/events/compute.json +21 -0
  11. hoshi_astro-1.7.0/hoshi/__init__.py +133 -0
  12. hoshi_astro-1.7.0/hoshi/adb.py +249 -0
  13. hoshi_astro-1.7.0/hoshi/aspects.py +164 -0
  14. hoshi_astro-1.7.0/hoshi/chart.py +381 -0
  15. hoshi_astro-1.7.0/hoshi/cli.py +671 -0
  16. hoshi_astro-1.7.0/hoshi/dignities.py +162 -0
  17. hoshi_astro-1.7.0/hoshi/ephemeris.py +369 -0
  18. hoshi_astro-1.7.0/hoshi/houses.py +258 -0
  19. hoshi_astro-1.7.0/hoshi/info.py +865 -0
  20. hoshi_astro-1.7.0/hoshi/output.py +1410 -0
  21. hoshi_astro-1.7.0/hoshi/points.py +156 -0
  22. hoshi_astro-1.7.0/hoshi/py.typed +0 -0
  23. hoshi_astro-1.7.0/hoshi/store.py +100 -0
  24. hoshi_astro-1.7.0/hoshi/utils.py +25 -0
  25. hoshi_astro-1.7.0/hoshi/zodiac.py +213 -0
  26. hoshi_astro-1.7.0/packages/hoshi-api/CHANGELOG.md +53 -0
  27. hoshi_astro-1.7.0/packages/hoshi-api/Dockerfile +55 -0
  28. hoshi_astro-1.7.0/packages/hoshi-api/README.md +81 -0
  29. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/__init__.py +0 -0
  30. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/app.py +84 -0
  31. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/lambda_handler.py +7 -0
  32. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/main.py +11 -0
  33. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/mcp_server.py +295 -0
  34. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/routes/__init__.py +0 -0
  35. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/routes/charts.py +232 -0
  36. hoshi_astro-1.7.0/packages/hoshi-api/hoshi_api/routes/info.py +63 -0
  37. hoshi_astro-1.7.0/packages/hoshi-api/pyproject.toml +53 -0
  38. hoshi_astro-1.7.0/packages/hoshi-api/tests/__init__.py +0 -0
  39. hoshi_astro-1.7.0/packages/hoshi-api/tests/conftest.py +97 -0
  40. hoshi_astro-1.7.0/packages/hoshi-api/tests/test_charts.py +102 -0
  41. hoshi_astro-1.7.0/packages/hoshi-api/tests/test_info.py +66 -0
  42. hoshi_astro-1.7.0/packages/hoshi-api/tests/test_mcp.py +124 -0
  43. hoshi_astro-1.7.0/packages/hoshi-ui/CHANGELOG.md +83 -0
  44. hoshi_astro-1.7.0/packages/hoshi-ui/README.md +31 -0
  45. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/__init__.py +0 -0
  46. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/main.py +95 -0
  47. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/__init__.py +0 -0
  48. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/chart_detail.py +231 -0
  49. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/chart_list.py +54 -0
  50. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/chart_picker.py +63 -0
  51. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/compare.py +159 -0
  52. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/info_modal.py +121 -0
  53. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/info_picker.py +71 -0
  54. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/screens/transits.py +132 -0
  55. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/widgets/__init__.py +0 -0
  56. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/widgets/body_display.py +267 -0
  57. hoshi_astro-1.7.0/packages/hoshi-ui/hoshi_ui/widgets/body_table.py +171 -0
  58. hoshi_astro-1.7.0/packages/hoshi-ui/pyproject.toml +51 -0
  59. hoshi_astro-1.7.0/packages/hoshi-ui/tests/__init__.py +0 -0
  60. hoshi_astro-1.7.0/packages/hoshi-ui/tests/conftest.py +39 -0
  61. hoshi_astro-1.7.0/packages/hoshi-ui/tests/test_app.py +37 -0
  62. hoshi_astro-1.7.0/packages/hoshi-ui/tests/test_chart_navigation.py +140 -0
  63. hoshi_astro-1.7.0/packages/hoshi-ui/tests/test_hover_tooltips.py +150 -0
  64. hoshi_astro-1.7.0/pyproject.toml +87 -0
  65. hoshi_astro-1.7.0/samconfig.toml +24 -0
  66. hoshi_astro-1.7.0/template.yaml +41 -0
  67. hoshi_astro-1.7.0/tests/__init__.py +0 -0
  68. hoshi_astro-1.7.0/tests/conftest.py +45 -0
  69. hoshi_astro-1.7.0/tests/test_adb.py +248 -0
  70. hoshi_astro-1.7.0/tests/test_api.py +25 -0
  71. hoshi_astro-1.7.0/tests/test_aspects.py +179 -0
  72. hoshi_astro-1.7.0/tests/test_chart.py +242 -0
  73. hoshi_astro-1.7.0/tests/test_cli.py +152 -0
  74. hoshi_astro-1.7.0/tests/test_dignities.py +86 -0
  75. hoshi_astro-1.7.0/tests/test_ephemeris.py +461 -0
  76. hoshi_astro-1.7.0/tests/test_houses.py +163 -0
  77. hoshi_astro-1.7.0/tests/test_info.py +229 -0
  78. hoshi_astro-1.7.0/tests/test_output.py +142 -0
  79. hoshi_astro-1.7.0/tests/test_points.py +99 -0
  80. hoshi_astro-1.7.0/tests/test_store.py +131 -0
  81. hoshi_astro-1.7.0/tests/test_zodiac.py +195 -0
  82. hoshi_astro-1.7.0/uv.lock +2649 -0
@@ -0,0 +1,29 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.11", "3.12", "3.13"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Install uv
20
+ uses: astral-sh/setup-uv@v6
21
+
22
+ - name: Set up Python ${{ matrix.python-version }}
23
+ run: uv python install ${{ matrix.python-version }}
24
+
25
+ - name: Install dependencies
26
+ run: uv sync --python ${{ matrix.python-version }}
27
+
28
+ - name: Run tests
29
+ run: uv run --python ${{ matrix.python-version }} pytest
@@ -0,0 +1,83 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_run:
5
+ workflows: ["CI"]
6
+ branches: [main]
7
+ types: [completed]
8
+
9
+ jobs:
10
+ release:
11
+ runs-on: ubuntu-latest
12
+ concurrency: release
13
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
14
+ permissions:
15
+ id-token: write
16
+ contents: write
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v6
25
+
26
+ - name: Set up Python
27
+ run: uv python install 3.13
28
+
29
+ - name: Install dependencies
30
+ run: uv sync
31
+
32
+ - name: Release hoshi
33
+ id: release-hoshi
34
+ uses: python-semantic-release/python-semantic-release@v10
35
+ with:
36
+ directory: .
37
+ github_token: ${{ secrets.GITHUB_TOKEN }}
38
+
39
+ - name: Release hoshi-api
40
+ id: release-hoshi-api
41
+ uses: python-semantic-release/python-semantic-release@v10
42
+ with:
43
+ directory: packages/hoshi-api
44
+ github_token: ${{ secrets.GITHUB_TOKEN }}
45
+
46
+ - name: Release hoshi-ui
47
+ id: release-hoshi-ui
48
+ uses: python-semantic-release/python-semantic-release@v10
49
+ with:
50
+ directory: packages/hoshi-ui
51
+ github_token: ${{ secrets.GITHUB_TOKEN }}
52
+
53
+ - name: Publish hoshi to GitHub Release
54
+ uses: python-semantic-release/publish-action@v10
55
+ if: steps.release-hoshi.outputs.released == 'true'
56
+ with:
57
+ directory: .
58
+ github_token: ${{ secrets.GITHUB_TOKEN }}
59
+ tag: ${{ steps.release-hoshi.outputs.tag }}
60
+
61
+ - name: Build hoshi for PyPI
62
+ run: uv build
63
+ if: steps.release-hoshi.outputs.released == 'true'
64
+
65
+ - name: Publish hoshi to PyPI
66
+ uses: pypa/gh-action-pypi-publish@release/v1
67
+ if: steps.release-hoshi.outputs.released == 'true'
68
+
69
+ - name: Publish hoshi-api to GitHub Release
70
+ uses: python-semantic-release/publish-action@v10
71
+ if: steps.release-hoshi-api.outputs.released == 'true'
72
+ with:
73
+ directory: packages/hoshi-api
74
+ github_token: ${{ secrets.GITHUB_TOKEN }}
75
+ tag: ${{ steps.release-hoshi-api.outputs.tag }}
76
+
77
+ - name: Publish hoshi-ui to GitHub Release
78
+ uses: python-semantic-release/publish-action@v10
79
+ if: steps.release-hoshi-ui.outputs.released == 'true'
80
+ with:
81
+ directory: packages/hoshi-ui
82
+ github_token: ${{ secrets.GITHUB_TOKEN }}
83
+ tag: ${{ steps.release-hoshi-ui.outputs.tag }}
@@ -0,0 +1,17 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ .history
13
+
14
+ # Runtime caches (Horizons responses, JPL ephemeris)
15
+ .chiron_cache.json
16
+ .lunar_cache.json
17
+ *.bsp
@@ -0,0 +1 @@
1
+ # CHANGELOG
@@ -0,0 +1,213 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Goal
6
+
7
+ Hoshi is a Python CLI for astrological charting, with a focus on real-sky astrology. Real-sky charts use IAU constellation boundaries (13 signs of unequal width, including Ophiuchus) rather than the traditional 12-sign tropical wheel — this makes charting significantly harder to do by hand, which is where Hoshi helps. Planetary positions come from [Skyfield](https://rhodesmill.org/skyfield/) (JPL ephemerides) for accuracy.
8
+
9
+ ## Repo contents
10
+
11
+ - `pyproject.toml` / `.python-version` — packaging and pinned interpreter. Requires Python 3.11+ (CI tests 3.11–3.13). The root `pyproject.toml` also configures a **uv workspace** (`[tool.uv.workspace]`) with members under `packages/`.
12
+ - `hoshi/` — core Python package. Exposed as the `hoshi` console script via `[project.scripts]`; build backend is `hatchling`. After dependency changes, run `uv sync` to reinstall.
13
+ - `packages/hoshi-api/` — FastAPI REST API package (workspace member). Depends on `hoshi` as a workspace dependency. Run with `uv run --package hoshi-api hoshi-api` (starts uvicorn on port 8000). Tests in `packages/hoshi-api/tests/`.
14
+ - `charts/` — user-saved charts (one JSON file per chart). Names are normalized to lowercase on save. Not a cache — treat as user data. Persists only inputs; charts are recomputed on `hoshi chart show`.
15
+ - `~/.cache/hoshi/chiron.json` — per-minute cache of Horizons OBSERVER responses for Chiron. Safe to delete.
16
+ - `~/.cache/hoshi/lunar.json` — per-minute cache of Horizons ELEMENTS responses for the Moon (true nodes and true Lilith). Safe to delete.
17
+
18
+ ## Package modules (`hoshi/`)
19
+
20
+ | Module | Purpose |
21
+ |--------|---------|
22
+ | `ephemeris.py` | Skyfield positions (`positions(when, include_chiron=...)`), Horizons HTTP fetch (`HorizonsError` on failure), shared `timescale()`/`cache_dir()`, JSON cache helpers, `ecliptic_precession()` |
23
+ | `zodiac.py` | IAU real-sky boundaries, tropical/sidereal placements (`Placement.for_mode(lon, mode, ...)`), `ZodiacMode` enum, `TROP_SIGNS`/`SIGN_ATTRS` sign tables |
24
+ | `houses.py` | Placidus, Porphyry, Equal, Arc-13 house cusps; angle computation (shared `_ramc_obliquity`/`_mc_from_ramc`) |
25
+ | `points.py` | True lunar nodes, Black Moon Lilith, Hermetic lots |
26
+ | `chart.py` | `Chart.build()` / `Chart.from_input()` — assembles all bodies; `Chart.bodies()`/`Chart.body(id)` uniform `BodyRef` iteration; `uncertain_signs()`; `Placed.for_longitude(...)` and `Placed.placement(mode)`; `location_known`/`time_known` flags; `house` is `None` when location unknown |
27
+ | `aspects.py` | Aspect definitions and orbs; `compute_aspects()`, `compute_inter_aspects()` |
28
+ | `dignities.py` | Planetary dignities table, element/modality tally |
29
+ | `output.py` | Pydantic output models for every command (`ChartOutput`, `TransitsOutput`, etc.) with `.build()` classmethods that assemble models from SDK types. Shared by CLI and API. |
30
+ | `store.py` | Save/load/list/delete named charts in `./charts/` |
31
+ | `utils.py` | Shared utilities (`fuzzy_match`) used by both CLI and API |
32
+ | `adb.py` | Astro-Databank import via MediaWiki API; `adb_to_chart_input()` fetches + parses `ASTRODATABANK_dma` template into `ChartInput`; coordinate/time/timezone converters; `ADBError` on failure |
33
+ | `cli.py` | Typer entry point — all `hoshi chart` subcommands |
34
+
35
+ ## Public SDK surface
36
+
37
+ Hoshi is usable as a library, not just a CLI. The top-level `hoshi` package
38
+ (`hoshi/__init__.py`) re-exports the stable domain types and functions with an
39
+ explicit `__all__`; **import from `hoshi`, not the submodules**, when consuming
40
+ Hoshi from another project. A `py.typed` marker ships so downstream
41
+ type-checkers see the annotations. See `docs/sdk.md` for a worked example.
42
+
43
+ Guidance when changing the package:
44
+ - New public constructs go in `__all__` (and ideally `docs/sdk.md`).
45
+ - Keep computation in the SDK layer (`chart.py`, `zodiac.py`, …); `cli.py`
46
+ should only handle argument parsing and display. `Chart.from_input()`,
47
+ `Chart.bodies()`, and `uncertain_signs()` exist so the CLI doesn't own domain
48
+ logic — prefer extending these over adding logic to `cli.py`.
49
+ - `Chart.from_input(ChartInput)` substitutes placeholder coordinates for an
50
+ unknown location and records `location_known` / `time_known` on the chart so
51
+ consumers can drive their own suppression.
52
+ - Zodiac mode is the `ZodiacMode` StrEnum (in `zodiac.py`); functions accept it
53
+ or its plain-string value interchangeably.
54
+
55
+ ## CLI commands
56
+
57
+ ```
58
+ hoshi chart add NAME DATE [TIME] [--lat] [--lon] [--tz] [--mode] [--houses] [--details] [--aspects] [--group-by] [--cusps] [--force]
59
+ hoshi chart show NAME|DATE [TIME] [--lat --lon] ... [--format table|json] [--compare-houses]
60
+ hoshi chart cusps NAME|DATE [TIME] [--lat --lon] ... [--mode] [--houses]
61
+ hoshi chart transits NAME [DATE [TIME]] [--tz] [--mode] [--houses] [--details] [--aspects] [--natal] [--group-by]
62
+ hoshi chart compare NAME1 NAME2 [--mode] [--houses] [--aspects] [--details] [--group-by]
63
+ hoshi chart import SOURCE [NAME] [--force] [--mode] [--houses] [--details] [--aspects] [--group-by] [--cusps] [--format]
64
+ hoshi chart list
65
+ hoshi chart delete NAME [--yes]
66
+ ```
67
+
68
+ ## Optional birth data and uncertainty
69
+
70
+ `ChartInput` stores `time`, `lat`, and `lon` as `str | None` and `float | None` respectively — all three are optional. Store whatever is known; omit the rest.
71
+
72
+ When displaying a chart with missing data:
73
+ - **Time unknown** — noon UTC is used for computation; all planet sign/degree/lon cells render in **yellow** as a warning; `⚠` note is printed above the table. Moon sign is particularly uncertain (moves ~13°/day).
74
+ - **Location unknown** — angles (ASC/MC/etc.), house numbers, and Hermetic lots are **suppressed entirely**; `⚠` note is printed.
75
+ - **Both unknown** — both sets of rules apply.
76
+
77
+ `ChartInput` properties `time_known` and `location_known` drive all suppression logic in `cli.py`. `--compare-houses` and `chart cusps` require both to be known and error if not.
78
+
79
+ ## Three zodiac modes
80
+
81
+ - `'realsky'` — IAU real-sky boundaries (13 signs incl. Ophiuchus, unequal widths). Default. See `IAU[]` in `hoshi/zodiac.py`.
82
+ - `'tropical'` — standard 12 equal 30° signs from the vernal equinox.
83
+ - `'vedic'` — sidereal, Lahiri ayanamsa offset applied.
84
+
85
+ ## Reference frame: of-date, not J2000
86
+
87
+ All ecliptic longitudes are in the **equinox of date** frame (measured from
88
+ the vernal equinox at the chart moment), matching standard astrological
89
+ convention. This requires explicit `epoch=t` in Skyfield calls —
90
+ **`ecliptic_latlon()` default returns J2000**, contrary to what the Skyfield
91
+ docs suggest. The two differ by precession (~50.3″/year).
92
+
93
+ The IAU constellation table is J2000-fixed. `Placement.realsky()` accepts a
94
+ `precession` argument (degrees since J2000 from `ecliptic_precession(when)`)
95
+ to shift the boundaries into the of-date frame before the sign lookup.
96
+ `Placed.for_longitude()` threads this through automatically when built via
97
+ `Chart.build()`.
98
+
99
+ ## Horizons-backed bodies
100
+
101
+ Skyfield/jplephem can't read JPL Horizons' small-body SPKs (SPK data type 21
102
+ is unsupported). Two bodies use Horizons HTTP APIs directly:
103
+
104
+ - **Chiron** (`_chiron_position` in `hoshi/ephemeris.py`) — Horizons
105
+ **OBSERVER** ephemeris at chart time + 1 minute for ecliptic lon/lat and
106
+ retrograde state.
107
+ - **True lunar nodes & Black Moon Lilith** (`lunar_elements` in
108
+ `hoshi/points.py`) — Horizons **ELEMENTS** API at chart time, parses
109
+ OM (ascending node) and W (argument of perigee) for the Moon. True Lilith
110
+ = OM + W + 180°.
111
+
112
+ Shared HTTP/SSL/cache helpers (`horizons_fetch`, `json_cache_get/put`) live
113
+ in `hoshi/ephemeris.py`. The SSL context uses `certifi` because macOS
114
+ stdlib `urllib` ships with an incomplete root bundle.
115
+
116
+ ## Aspects
117
+
118
+ Five major aspects (4° orb), four minor (2° orb), three micro (1° orb). See
119
+ `hoshi/aspects.py`. Single-chart aspects via `compute_aspects(chart, details)`;
120
+ synastry inter-aspects via `compute_inter_aspects(chart_a, chart_b, details)`.
121
+ Without `--details`, only planets + Asc are included. Axis pairs (Asc/Dsc,
122
+ MC/IC, etc.) are filtered from single-chart aspects.
123
+
124
+ `--group-by` controls grouping for both bodies and aspects: `category`
125
+ (default — bodies by kind, aspects by Major/Minor/Micro), `sign`, `house`,
126
+ or `planet` (aspects only).
127
+
128
+ ## Dignities
129
+
130
+ `hoshi/dignities.py` holds the domicile/exaltation/detriment/fall table for
131
+ the 13-sign real-sky scheme (Chiron domicile = Ophiuchus). Assignments follow
132
+ standard conventions adapted for 13 signs.
133
+ `element_modality_tally()` returns separate primary (planets) and total (all
134
+ bodies) counts.
135
+
136
+ ## REST API (`packages/hoshi-api/`)
137
+
138
+ A FastAPI application exposing the same chart functionality as the CLI via
139
+ JSON endpoints. It imports the `hoshi` SDK and uses the `.build()` classmethods
140
+ on the output models — the same models that back `--format json` in the CLI.
141
+
142
+ **Running:** `uv run --package hoshi-api hoshi-api` (starts on `0.0.0.0:8000`
143
+ with auto-reload). Interactive docs at `/docs`.
144
+
145
+ **Route overview:**
146
+
147
+ | Method | Path | Purpose |
148
+ |--------|------|---------|
149
+ | `POST` | `/charts` | Save a new chart |
150
+ | `GET` | `/charts` | List saved charts |
151
+ | `POST` | `/charts/compute` | Compute a one-off chart (no save) |
152
+ | `POST` | `/charts/import` | Import from Astro-Databank |
153
+ | `GET` | `/charts/{name}` | Show a saved chart |
154
+ | `DELETE` | `/charts/{name}` | Delete a saved chart |
155
+ | `GET` | `/charts/{name}/cusps` | House cusps |
156
+ | `GET` | `/charts/{name}/transits` | Transits against natal |
157
+ | `GET` | `/charts/{name}/compare/{other}` | Synastry |
158
+ | `GET` | `/info/{category}` | List reference info |
159
+ | `GET` | `/info/{category}/{name}` | Single item detail |
160
+
161
+ All route handlers are synchronous (`def`, not `async def`) so uvicorn
162
+ dispatches them to a thread pool — blocking Horizons/Skyfield calls work
163
+ correctly without async wrappers.
164
+
165
+ **Testing:** `uv run --package hoshi-api pytest packages/hoshi-api/tests/`
166
+
167
+ ## Conventions
168
+
169
+ - Use `uv` for dependency and run management (`uv add`, `uv run`).
170
+
171
+ ## Commit message format
172
+
173
+ This repo uses [Conventional Commits](https://www.conventionalcommits.org/) for
174
+ `python-semantic-release` to automate versioning. Every commit message must
175
+ follow this format:
176
+
177
+ ```
178
+ <type>[optional scope]: <description>
179
+
180
+ [optional body]
181
+
182
+ [optional footer(s)]
183
+ ```
184
+
185
+ **Types** (only these trigger releases):
186
+ - `fix:` — bug fix → **patch** bump (0.0.x)
187
+ - `feat:` — new feature → **minor** bump (0.x.0)
188
+ - Append `!` after the type/scope (e.g. `feat!:`) or add a `BREAKING CHANGE:` footer → **major** bump (x.0.0)
189
+
190
+ **Types** (no release, but still use them):
191
+ - `chore:` — maintenance, dependency updates
192
+ - `docs:` — documentation only
193
+ - `refactor:` — code restructuring with no behavior change
194
+ - `test:` — adding or updating tests
195
+ - `ci:` — CI/CD changes
196
+ - `style:` — formatting, whitespace
197
+
198
+ **Scope** is optional and names the area of change, e.g. `feat(cli):`, `fix(ephemeris):`.
199
+
200
+ Keep the first line under 72 characters. Use the body for additional context
201
+ when the "why" isn't obvious from the summary line alone.
202
+
203
+ **Always ask for confirmation before creating a commit.** Show the proposed
204
+ commit message and list of staged files, then wait for approval.
205
+
206
+ ## Code change checklist
207
+
208
+ Before considering any code change complete, verify all of the following:
209
+
210
+ 1. **Lint and format** — run `uv run ruff check --fix .` and `uv run ruff format .` to fix lint issues and enforce consistent formatting.
211
+ 2. **Tests are written** — new or changed functionality must have corresponding tests.
212
+ 3. **All tests pass** — run the full test suite (`uv run pytest`) and confirm zero failures.
213
+ 4. **Documentation is updated** — update this file (CLAUDE.md), CLI help text, and any other relevant docs to reflect the change.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Trey Gonsoulin
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.
@@ -0,0 +1,204 @@
1
+ Metadata-Version: 2.4
2
+ Name: hoshi-astro
3
+ Version: 1.7.0
4
+ Summary: Real-sky astrology birth chart CLI.
5
+ Project-URL: Homepage, https://github.com/trey-gonsoulin/hoshi
6
+ Project-URL: Repository, https://github.com/trey-gonsoulin/hoshi
7
+ Project-URL: Changelog, https://github.com/trey-gonsoulin/hoshi/blob/main/CHANGELOG.md
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Trey Gonsoulin
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Classifier: License :: OSI Approved :: MIT License
31
+ Classifier: Programming Language :: Python :: 3.11
32
+ Classifier: Programming Language :: Python :: 3.12
33
+ Classifier: Programming Language :: Python :: 3.13
34
+ Requires-Python: >=3.11
35
+ Requires-Dist: certifi>=2026.6.17
36
+ Requires-Dist: geopy>=2.0
37
+ Requires-Dist: pydantic>=2.8
38
+ Requires-Dist: pyyaml>=6.0.2
39
+ Requires-Dist: rich>=10.0.0
40
+ Requires-Dist: skyfield>=1.49
41
+ Requires-Dist: typer>=0.12.4
42
+ Description-Content-Type: text/markdown
43
+
44
+ # Hoshi
45
+
46
+ A CLI for astrological charting, with a focus on real-sky astrology. Inspired by [Nuastro](https://nuastro.com).
47
+
48
+ Traditional astrology divides the sky into 12 equal 30° signs. Real-sky astrology uses the actual IAU constellation boundaries — 13 unequal signs along the ecliptic, including Ophiuchus — which makes charting by hand significantly more difficult. Hoshi handles the calculation, using JPL-grade ephemerides ([Skyfield](https://rhodesmill.org/skyfield/) DE421 for planets, JPL Horizons for Chiron and the lunar nodes).
49
+
50
+ ## Setup
51
+
52
+ Requires Python 3.11+ and [uv](https://docs.astral.sh/uv/).
53
+
54
+ ```sh
55
+ uv sync
56
+ uv tool install --editable . # puts `hoshi` on your PATH (~/.local/bin/hoshi)
57
+ ```
58
+
59
+ `--editable` means source changes take effect immediately. If you change `pyproject.toml` dependencies, re-run with `--reinstall`. To remove: `uv tool uninstall hoshi`.
60
+
61
+ On first run, Skyfield downloads `de421.bsp` (~17 MB) into the current directory. Chiron and lunar (node/Lilith) lookups hit JPL Horizons and are cached in `~/.cache/hoshi/`.
62
+
63
+ ## Commands
64
+
65
+ ```sh
66
+ hoshi chart add NAME DATE [TIME] [--lat LAT] [--lon LON] [--tz TZ] [--mode MODE] [--houses SYS]
67
+ [--details] [--aspects] [--group-by category|sign|house]
68
+ [--cusps] [--force]
69
+ hoshi chart show NAME|DATE [TIME] [--lat LAT --lon LON] ...
70
+ [--format table|json] [--compare-houses]
71
+ hoshi chart cusps NAME|DATE [TIME] [--lat LAT --lon LON] ... [--mode MODE] [--houses SYS]
72
+ hoshi chart transits NAME [DATE [TIME]] [--tz TZ] [--mode MODE] [--houses SYS]
73
+ [--details] [--aspects] [--natal]
74
+ hoshi chart compare NAME1 NAME2 [--mode MODE] [--houses SYS] [--aspects] [--details]
75
+ hoshi chart list
76
+ hoshi chart delete NAME [--yes]
77
+ ```
78
+
79
+ `NAME|DATE` — pass a saved chart name, or a `YYYY-MM-DD` date with `--lat`/`--lon` for a one-off chart.
80
+
81
+ `--lat` and `--lon` are optional for `chart add` — omit either or both if the birth location is unknown. If time is also omitted, only the date is stored. Placements computed from unknown inputs are shown in yellow with a warning; angles and houses are omitted entirely when time or location is unknown.
82
+
83
+ ## Options
84
+
85
+ ### `--mode`
86
+
87
+ | Mode | Description |
88
+ |------|-------------|
89
+ | `realsky` (default) | IAU real-sky boundaries — 13 unequal signs including Ophiuchus |
90
+ | `tropical` | Standard 12 equal 30° signs from the vernal equinox |
91
+ | `vedic` | Sidereal: tropical longitudes minus the Lahiri ayanamsa |
92
+
93
+ ### `--houses`
94
+
95
+ | System | Description |
96
+ |--------|-------------|
97
+ | `porphyry` (default) | Trisects the quadrants |
98
+ | `equal` | Equal 30° houses from the Ascendant |
99
+ | `placidus` | Time-based Placidus division |
100
+ | `arc13` | 13-arc wheel with widths matching the IAU constellation boundaries |
101
+
102
+ ### `--details`
103
+
104
+ Expands the display to include all six angles (Asc, MC, IC, Dsc, Vertex, Antivertex), true lunar nodes, Black Moon Lilith, seven Hermetic lots, planetary dignities, and element/modality tallies.
105
+
106
+ ### `--aspects`
107
+
108
+ Prints aspect tables grouped by type (Major / Minor / Micro). Without `--details`, aspects are computed for planets + Ascendant only; with `--details`, all chart bodies are included.
109
+
110
+ ### `--group-by sign|house`
111
+
112
+ Alternative layout grouping bodies by zodiac sign or house instead of category. `--group-by house` labels each house with its cusp sign and includes empty houses.
113
+
114
+ ### `--format json`
115
+
116
+ Outputs a mode-specific JSON summary instead of the Rich table. Includes body placements, house assignments, and cusp longitudes. Useful for scripting or piping into other tools. When time or location is unknown, the JSON includes a `"warnings"` array and an `"approximate": true` flag on affected body entries.
117
+
118
+ ## Bodies
119
+
120
+ ### Planets
121
+ Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto, Chiron
122
+
123
+ ### Angles
124
+ Ascendant, Midheaven (MC), Imum Coeli (IC), Descendant, Vertex, Antivertex
125
+
126
+ ### Calculated points
127
+ - **N.Node / S.Node** — True (osculating) lunar nodes from JPL Horizons
128
+ - **Lilith** — True Black Moon Lilith: ascending node + argument of perigee + 180°
129
+
130
+ ### Hermetic lots
131
+ Fortune, Spirit, Eros, Necessity, Courage, Victory, Nemesis
132
+
133
+ ## Aspects
134
+
135
+ | Class | Aspects | Orb |
136
+ |-------|---------|-----|
137
+ | Major | Conjunction (0°), Opposition (180°), Trine (120°), Square (90°), Sextile (60°) | 4° |
138
+ | Minor | Inconjunct (150°), Semi-sextile (30°), Semi-square (45°), Sesquiquadrate (135°) | 2° |
139
+ | Micro | Quintile (72°), Bi-quintile (144°), Septile (360°/7) | 1° |
140
+
141
+ ## Dignities
142
+
143
+ The Planets table in `--details` shows a dignity indicator per planet:
144
+
145
+ | Symbol | Dignity |
146
+ |--------|---------|
147
+ | ⊕ | Domicile |
148
+ | △ | Exaltation |
149
+ | ▽ | Detriment |
150
+ | ✕ | Fall |
151
+
152
+ Assignments follow standard conventions adapted for the 13-sign real-sky scheme (Chiron domicile = Ophiuchus).
153
+
154
+ ## Transits
155
+
156
+ ```sh
157
+ hoshi chart transits person # current moment vs natal
158
+ hoshi chart transits person 2026-01-01 # specific date (noon UTC)
159
+ hoshi chart transits person 2026-01-01 14:30 --tz America/Chicago
160
+ hoshi chart transits person --natal # side-by-side natal vs transit table
161
+ hoshi chart transits person --aspects # inter-aspects (natal × transit)
162
+ ```
163
+
164
+ Computes current (or specified date) planetary positions and places them against the saved natal chart. The house column (`H`) always shows which **natal house** each transiting planet falls in, using the natal chart's cusps.
165
+
166
+ `--natal` adds a side-by-side table showing each body's natal sign/degree alongside its current transit sign/degree. `--details` and `--aspects` work the same as in other commands.
167
+
168
+ ## Synastry
169
+
170
+ ```sh
171
+ hoshi chart compare person1 person2 --aspects
172
+ hoshi chart compare person1 person2 --aspects --details
173
+ ```
174
+
175
+ Computes inter-aspects between two saved charts. Body selection respects `--details` the same way single-chart aspects do.
176
+
177
+ ## Accuracy notes
178
+
179
+ ### Reference frame
180
+
181
+ All ecliptic longitudes are in the **equinox of date** frame, matching standard astrological convention. Skyfield's `ecliptic_latlon()` defaults to J2000, so `epoch=t` is passed explicitly. The IAU constellation boundaries are J2000-fixed; Hoshi applies the precession offset at lookup time so sign assignments stay consistent with the of-date planet positions.
182
+
183
+ ### Per-body accuracy
184
+
185
+ - **Planets**: Skyfield + JPL DE421 — sub-arcsecond
186
+ - **Chiron**: Horizons OBSERVER ephemeris, cached per minute
187
+ - **Nodes / Lilith**: Horizons ELEMENTS API (Moon osculating elements), cached per minute
188
+ - **Angles / cusps**: standard formulas with a linear obliquity model — accurate to under 1′ for 20th–21st century dates
189
+ - **Lahiri ayanamsa**: linear approximation anchored at J2000 — adequate for chart display
190
+
191
+ ## Project layout
192
+
193
+ ```
194
+ hoshi/
195
+ ephemeris.py Skyfield positions, Horizons fetch, JSON cache helpers
196
+ zodiac.py IAU real-sky boundaries, tropical and sidereal placements
197
+ houses.py Placidus, Porphyry, Equal, Arc-13 cusps; angles
198
+ points.py True lunar nodes, Black Moon Lilith, Hermetic lots
199
+ chart.py Chart.build() — assembles all bodies
200
+ aspects.py Aspect detection (single-chart and inter-chart)
201
+ dignities.py Dignities table, element/modality tallies
202
+ store.py JSON persistence for named charts (./charts/)
203
+ cli.py Typer entry point
204
+ ```