Add a Checks section (`py_compile` + `nix build`), note the `brand/` assets and their source, broaden the backtick-quoting rule, refine the token-expiry guidance, and explain using decompiled APK output if provided. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.3 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 repo holds the reverse-engineering capture, the API docs derived from it, a token helper, and the Home Assistant custom component itself (see Integration).
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).custom_components/west_wood_club/— the Home Assistant integration. Itsbrand/dir holds the integration icon/logo, extracted from the capture'swestwoodclub-ie.pngasset.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 enters a shell with mitmproxy/python.
To run Python/mitmproxy from a non-interactive shell (where direnv does not
auto-activate and there's no python on PATH), use direnv exec .:
direnv exec . python script.py
This reuses nix-direnv's cached dev shell (~0.06s). Prefer it over
nix develop --command ..., which re-evaluates the flake every call (slow, and
noisy with "Git tree is dirty" warnings).
Gotchas:
- Nix flakes only see git-tracked files.
git addnew files beforenix develop/nix flake lock, or Nix errors with "not tracked by Git". - Use absolute paths when a script opens
android-flows.mitm—nix develop --commandresets cwd to the repo root (direnv exec .keeps the current dir).
Code style
Python uses single-quoted strings ('...'). Reformat with
ruff format --config "format.quote-style='single'" <paths> (ruff is available via
nix run nixpkgs#ruff). Docstrings stay triple-double-quoted (""").
In prose (commit messages, docs, comments), backtick-quote anything code-like — paths, filenames, identifiers, commands, endpoints, HTTP headers, field/JSON keys, UUIDs/IDs, env vars — rather than plain or double-quoted text. If in doubt and it's a literal token from code or an API, backtick it.
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.
Some questions the capture can't answer (token lifecycle, the white-label ID,
error codes) need the app itself. If you're pointed at decompiled APK output
(e.g. apktool smali/), grep it there — but it's R8-obfuscated: class names are
mangled and library types (e.g. OkHttp) may be shrunk/repackaged, so a missing
grep hit is not proof of absence. No such decompilation lives in this repo.
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. The token most likely doesn't expire (no refresh token in the response, app appears to store no credentials); on401/403get a fresh one. Seeapi.md. - 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.
Integration
custom_components/west_wood_club/ is a UI-configured (config-flow) integration.
- Auth model: the user pastes a long-lived bearer token (from
get-token.py); no credentials are stored. A rejected token (WestWoodAuthError→ coordinator raisesConfigEntryAuthFailed) triggers HA's reauth flow to paste a fresh one. - One device, N sensors: a single
DataUpdateCoordinatorpollsWhoIsInCountonce per interval for all clubs; oneSensorEntityper selected club reads itsclub_idout ofcoordinator.data. All sensors share one device (identifiers={(DOMAIN, entry.entry_id)}) named "West Wood Club". - Config flow steps:
userandreauthare HA-fixed names (dispatched by the flow source);clubsandreauth_confirmare reached because a form was shown with thatstep_id(HA callsasync_step_<step_id>on submit). Every step name must also have a matching key underconfig.stepinstrings.json. - Nix:
flake.nixexposespackages.west_wood_club(built withbuildHomeAssistantComponent) andoverlays.default, which adds it topkgs.home-assistant-custom-components. On a NixOS host, apply the overlay and list it inservices.home-assistant.customComponents; it must be built against the same Python as the host'shome-assistant. Bumpversionin bothmanifest.jsonand the flake on code changes. The integration has no externalrequirements, so no extra Nix packaging is needed.
Checks
There is no test suite. Verify changes with:
direnv exec . python -m py_compile custom_components/west_wood_club/*.py— fast syntax check of the component.nix build .#west_wood_club— builds the component and runs nixpkgs' manifest and import checks (the closest thing to CI here). Remember togit addnew files first, or the flake won't see them.
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.