2.9 KiB
AGENTS.md
Purpose
Groundwork for a Home Assistant integration for West Wood Club (an Irish gym
chain). The West Wood app is a white-label build of PerfectGym Go, so the
integration targets PerfectGym's hosted backend at https://goapi2.perfectgym.com.
The integration itself isn't written yet — the repo currently holds a reverse-engineering capture, the API docs derived from it, and a login helper.
Layout
api.md— documented PerfectGym endpoints (login, club list, live occupancy), secrets redacted. The source of truth for API behaviour; extend it as more endpoints are mapped.get-token.py— logs in and prints a bearer token to stdout (stdlib only).android-flows.mitm— mitmproxy capture of the app's traffic. Gitignored and untracked: it contains real credentials and a bearer token in cleartext. Never commit it or copy its secrets into tracked files.token.txt— a captured/extracted bearer token. Also gitignored.flake.nix/.envrc— Nix dev shell (providesmitmproxy).
Dev environment
Nix flake + nix-direnv. direnv allow (or nix develop) enters a shell with
mitmproxy. There is no python3 on PATH outside the shell — run Python via
nix develop --command python ....
Gotchas:
- Nix flakes only see git-tracked files.
git addnew files beforenix develop/nix flake lock, or Nix errors with "not tracked by Git". nix develop --commandmay change cwd — use absolute paths when a script opensandroid-flows.mitm.
Working with the capture
Read flows with the mitmproxy Python API:
from mitmproxy.io import FlowReader
from mitmproxy.http import HTTPFlow
with open("/abs/path/android-flows.mitm", "rb") as f:
for flow in FlowReader(f).stream():
if isinstance(flow, HTTPFlow) and "perfectgym.com" in flow.request.host:
... # flow.request / flow.response
When dumping flows, redact Authorization / Cookie headers and the login body
(email + password) before writing anything to a tracked file.
API essentials
Full detail in api.md. Quick reference:
- Responses are wrapped
{ "data": ..., "errors": ... };errorsisnullon success. - Auth:
POST /v1/Authorize/LogInWithEmail(white-label ID goes in the body) → reuse the returnedbearer <token>as theAuthorizationheader. Token expiry is unconfirmed (expireTimewasnull). - Authenticated endpoints need only the
Authorizationheader — theX-Go-*headers and appUser-Agentthe app sends are not required (verified against the clubs endpoint). - Live occupancy:
GET /v1/Clubs/WhoIsInCount→countperclubId(the main sensor signal).clubIdmaps toidfromGET /v1/Clubs/Clubs.
Security
The capture and token.txt hold live credentials/tokens. Keep them gitignored,
and prefer the WESTWOOD_EMAIL / WESTWOOD_PASSWORD env vars (read by
get-token.py) over hardcoding credentials anywhere.