From dd7058b6bd156a81592909c079cae127c66358aa Mon Sep 17 00:00:00 2001 From: Per Magnus Petersen Date: Thu, 17 Sep 2026 15:02:42 +0200 Subject: [PATCH] Add agent project guidance --- AGENTS.md | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..74401dd --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,87 @@ +# AGENTS.md + +Bitcoin price tracker UI — Python FastAPI web application. Serves the HTML/Tailwind UI and +proxies live price data from btcdata over WebSocket. + +## Build & Development Commands + +- `make run` — Start dev server on port 4000; creates `.venv` and installs `requirements.txt` + on first run (the `venv` target, keyed off `requirements.txt`) +- `cp .env.example .env` — optional; `.env` is `-include`d, so a missing one is not fatal +- `make run-local` — Same, but with `BTCDATA_URL=$BTCDATA_LOCAL_URL` +- `make tailwind` — Watch Tailwind CSS for changes +- `make test` — Installs test-only dependencies from `requirements-dev.txt` and runs `pytest tests/` +- `make venv` — Bootstrap the virtualenv without starting the server + +## Docker + +Docker runs alongside local dev on different ports: + +| Service | Local dev | Docker | +|----------|----------------|--------------------------------------| +| btcprice | localhost:4000 | localhost:4041 (container port 4040) | +| btcdata | localhost:4100 | localhost:4102 | + +`docker compose up -d` here starts **btcprice only**. Both projects attach to the external +`btc-network` (`docker network create btc-network` once), and btcdata must be started from +`../btcdata`. Inside the network btcprice reaches it at `http://btcdata:${DOCKER_BTCDATA_PORT:-4102}`. + +The image does not set `BTCDATA_URL`; deployments must provide it. `BUILD_SHA` is embedded as +`APP_VERSION` and exposed at `/api/version`. The port in `CMD` is hardcoded to 4040. + +## CI/CD and production + +`.gitea/workflows/build.yml` tests pushes to `main` and `v*` tags on the `anvil` Gitea Actions +runner, then publishes immutable linux/amd64 images to +`harbor.pmagnus.cloud/btcprice/btcprice`. It embeds the short Git SHA as `APP_VERSION`. + +Flux image automation updates the manifests in the separate `magnus/deploy` repository and Flux +reconciles k3s. CI has no Kubernetes credentials. Production runs two replicas—one on hostinger and +one on citadel—and is served at https://btcprice.k8s.pmagnus.cloud through ingress-nginx and +hostinger Traefik TLS. Deployment changes belong in `../deploy/apps/btcprice`; do not deploy with +Compose or manual `kubectl apply`. + +## Legacy deployment + +`./deploy-citadel.sh` — build locally for the remote's arch, `docker save | ssh docker load`, +`docker compose up -d --no-build` in `/home/pmp/apps/btcprice` on citadel (192.168.50.195). +Mirrors `../elprice/deploy-citadel.sh`; no registry. + +- `docker-compose.citadel.yml` → remote `docker-compose.yml`; `.env.citadel` → remote `.env`. +- btcdata also runs on citadel, so both attach to the external `btc-network` there and + `BTCDATA_URL` is `http://btcdata:4102` (service name, not a LAN address). The deploy script + creates the network on the remote if it is missing. +- Temporary migration fallback at http://192.168.50.195:4041; it is not the production endpoint. + +## Architecture + +- **Framework:** FastAPI +- **Server:** Uvicorn +- **Templates:** Inline HTML strings + HTMX (WebSocket extension) +- **Styling:** Tailwind CSS v4 +- **Data backend:** btcdata (separate service at `BTCDATA_URL`, default `http://localhost:4100`) +- **No database** — all data comes from btcdata +- **Probes:** `/api/health` is process-only liveness; `/api/ready` checks btcdata with a 3-second + timeout; `/api/version` exposes the build identifier + +### btcdata connection + +The browser connects only to btcprice: `ws-connect="/ws/price"` and `ws-connect="/ws/strike"`. +Each endpoint runs `_ws_proxy`, which opens an upstream WebSocket to btcdata's `/api/price/ws` +and `/api/strike/ws`, converts each JSON message to an HTML fragment (`price_json_to_html`, +`strike_json_to_html`) and forwards it. `BTCDATA_URL` is the single point of contact; +`_btcdata_ws_url` rewrites its scheme to `ws`/`wss`. + +## Source Layout + +btcprice acts as a WebSocket proxy between the browser and btcdata: +1. Browser connects via HTMX WebSocket to `/ws/price` or `/ws/strike` +2. btcprice proxies to btcdata's `/api/price/ws` or `/api/strike/ws` +3. JSON from btcdata is transformed into HTML fragments (in `price_json_to_html` / `strike_json_to_html`) +4. HTMX swaps the HTML into the DOM by matching element IDs (`price-display`, `strike-display`) + +### Key Conventions + +- Numbers use Danish locale formatting (dots for thousands, commas for decimals) via `da_fmt()` +- Price changes are color-coded: green (up), red (down), gray (unchanged) +- All HTML is built as string concatenation in Python — no template engine