Ajoute CLAUDE.md (guide d'architecture pour Claude Code)
This commit is contained in:
130
CLAUDE.md
Normal file
130
CLAUDE.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Vue d'ensemble
|
||||
|
||||
Monitorink transforme une **Kobo Libra 2** (e-reader e-ink) en écran de monitoring domestique.
|
||||
Le backend agrège plusieurs sources (usage **Claude** + **Codex**, météo, NAS, ratios trackers
|
||||
torrent), rend un dashboard HTML, le capture en PNG niveaux de gris, et le sert à la Kobo qui
|
||||
l'affiche puis se rendort. La fonctionnalité phare est le **% de tokens restants** des
|
||||
abonnements Claude/Codex (les API ne renvoient qu'une *utilisation* en %, pas de compteur absolu).
|
||||
|
||||
Trois zones :
|
||||
- **`backend/`** — serveur FastAPI Python qui agrège les données et génère l'image.
|
||||
- **`kobo/`** — scripts shell client pour la Kobo (overlay sur `usetrmnl/trmnl-kobo`).
|
||||
- **`dev/`** — utilitaires de dev (`preview.py` rendu hors-ligne, `probe_usage.py` sonde API).
|
||||
|
||||
## Commandes
|
||||
|
||||
Le venv local est `backend/.venv` (Python 3.9 sur le Mac de dev ; le conteneur, lui, est en 3.x
|
||||
via l'image Playwright). Toujours utiliser le binaire du venv.
|
||||
|
||||
```sh
|
||||
# Dev backend (depuis backend/)
|
||||
cd backend
|
||||
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
|
||||
.venv/bin/python -m playwright install chromium # navigateur pour le rendu
|
||||
cp ../.env.example ../.env # puis compléter
|
||||
.venv/bin/uvicorn app:app --reload --port 8080
|
||||
# -> http://localhost:8080/image.png et /debug.html (HTML brut, itération design rapide)
|
||||
|
||||
# Aperçu design hors-ligne (données fictives, aucune dépendance réseau) — depuis la racine
|
||||
backend/.venv/bin/python dev/preview.py docs/preview.png
|
||||
|
||||
# Sonde l'endpoint /usage de Claude (debug auth/refresh)
|
||||
backend/.venv/bin/python dev/probe_usage.py
|
||||
|
||||
# Vérif syntaxe rapide (pas de suite de tests dans ce repo)
|
||||
backend/.venv/bin/python -m py_compile backend/render.py backend/integrations/*.py
|
||||
sh -n kobo/monitorinkloop.sh # lint shell des scripts Kobo
|
||||
|
||||
# Déploiement serveur
|
||||
docker compose up -d --build
|
||||
# Valider le rendu de la config compose sans .env réel :
|
||||
HOME=/tmp MONITORINK_DOMAIN=d.example.com MONITORINK_NETWORK=net docker compose config
|
||||
```
|
||||
|
||||
Il n'y a **pas de suite de tests automatisée** : la validation se fait via `py_compile`,
|
||||
`dev/preview.py` (rendu visuel), et le test réel sur l'appareil.
|
||||
|
||||
## Architecture du rendu
|
||||
|
||||
Le flux central (`backend/render.py`) :
|
||||
1. `build_context()` récupère **toutes les sources en parallèle** (`asyncio.gather`) et assemble
|
||||
le contexte du template Jinja2.
|
||||
2. Le template `templates/dashboard.html` est rendu en HTML, **canevas paysage 1680×1264**.
|
||||
3. Playwright (Chromium) capture le HTML en PNG via `page.set_content()` (pas de base URL →
|
||||
les polices woff2 sont embarquées en data-URI base64 par `fonts.py`).
|
||||
4. Le PNG est converti en niveaux de gris (`mode 'L'`) puis **pivoté de 90°** (`config.rotate`)
|
||||
pour le panneau e-ink physiquement en portrait (1264×1680).
|
||||
|
||||
`config.py` charge **toute** la configuration depuis l'environnement / `.env` (dataclass `Config`
|
||||
gelée, instance singleton `config`). Aucune valeur sensible n'est versionnée. Les trackers sont
|
||||
configurés dynamiquement via `MONITORINK_TRACKERS=clé1,clé2` + un bloc d'env par clé.
|
||||
|
||||
### Endpoints (`backend/app.py`)
|
||||
- `GET /image.png` — dashboard PNG complet, avec cache TTL (`cache_ttl_seconds`). La Kobo **pousse
|
||||
sa batterie** ici (`?bat=0-100&chg=0|1`) — voir `integrations/kobo.py`.
|
||||
- `GET /frame.meta` + `GET /frame.png` — **refresh partiel e-ink** (voir ci-dessous).
|
||||
- `GET /debug.html` — HTML brut sans screenshot, pour itérer sur le design.
|
||||
- `GET /health` — sonde.
|
||||
|
||||
### Refresh partiel e-ink (`backend/frame.py`)
|
||||
Les full refresh e-ink « flashent » (waveform complète qui efface le ghosting) — visuellement
|
||||
gênant. `frame.py` calcule **côté serveur** (PIL dispo, pas la Kobo) le diff entre le rendu courant
|
||||
et le précédent par client, détecte les **bandes horizontales modifiées disjointes**, et renvoie
|
||||
soit des crops partiels (sans flash), soit un full. Un full n'est forcé qu'au 1er lancement/reset
|
||||
et toutes les `full_refresh_interval_minutes`. La Kobo parse le bloc texte trivial de `/frame.meta`
|
||||
(`MODE SEQ NREGIONS` + lignes `i x y w h`) puis fetch chaque région via `/frame.png?region=i`.
|
||||
|
||||
## Intégrations (`backend/integrations/`)
|
||||
|
||||
Toutes suivent le même contrat : `async def fetch_*()` renvoie une dataclass avec un champ `ok` ;
|
||||
en erreur transitoire elles **replient sur la dernière valeur connue** plutôt que d'afficher une
|
||||
erreur à l'écran. Toutes sont **optionnelles sauf Claude** : env vide = section masquée.
|
||||
|
||||
- **`claude_usage.py`** — LA pièce délicate. `GET api.anthropic.com/api/oauth/usage` exige le scope
|
||||
OAuth `user:profile`, que `claude setup-token` n'a PAS (403). On utilise donc un **login Claude
|
||||
isolé dédié** (`CLAUDE_CONFIG_DIR` séparé, monté sur `/creds/.credentials.json`). Le backend
|
||||
lit/refresh **ce seul fichier**, jamais le `~/.claude` partagé. Points sensibles : le refresh
|
||||
token est **rotatif** (on ne re-soumet JAMAIS le même token → reuse-detection), refresh
|
||||
**proactif** bien avant l'expiration (~8 h de vie), **backoff exponentiel** sur échec (anti-429),
|
||||
et distinction stricte entre erreur **transitoire** (429/réseau → backoff + repli cache) et
|
||||
**fatale** (`invalid_grant`/401 → `_RefreshFatal`, alerte « login requis » à l'écran, jamais de
|
||||
re-soumission en boucle). Écriture des credentials **atomique** (tmp + `os.replace`, mode 0600).
|
||||
- **`codex.py`** — usage ChatGPT/Codex via `chatgpt.com/backend-api/wham/usage`. Token lu dans un
|
||||
`auth.json` maintenu frais par un **processus externe** (monté en lecture seule) ; Monitorink ne
|
||||
refresh pas. Extrait `chatgpt_account_id` du JWT (header requis).
|
||||
- **`trackers.py`** — ratio des trackers torrent privés. Architecture **multi-fetcher** : chaque
|
||||
`TrackerSpec.type` choisit une fonction dans `_FETCHERS` (`unit3d_nuxt`, `torr9`, `tr4ker`,
|
||||
`yggreborn`). Le ratio n'est PAS lisible au token API → **login session** (CSRF + cookies) propre
|
||||
à chaque techno. Cache long (`tracker_ttl_seconds`, défaut 1 h) **persisté sur disque** (`/data`)
|
||||
pour survivre aux redéploiements sans reloguer. Pour ajouter un tracker : écrire `_fetch_xxx(spec)`
|
||||
renvoyant un `TrackerStat` et l'enregistrer dans `_FETCHERS`.
|
||||
- **`weather.py`** — Open-Meteo (sans clé). **`nas.py`** — endpoint HTTP maison `/api/status`.
|
||||
- **`kobo.py`** — inverse des autres : la Kobo **pousse** sa batterie (elle seule la connaît), on
|
||||
stocke la dernière valeur horodatée en mémoire pour le prochain rendu.
|
||||
|
||||
## Client Kobo (`kobo/`)
|
||||
|
||||
`monitorinkloop.sh` est la boucle d'affichage, **overlay** réutilisant les binaires ARM (`fbink`,
|
||||
`busybox_kobo`) et helpers WiFi de [`usetrmnl/trmnl-kobo`](https://github.com/usetrmnl/trmnl-kobo)
|
||||
(à copier dans `bin/`, `scripts/` — non versionnés ici). Cycle : WiFi up → fetch `/frame.meta` →
|
||||
affiche (full/partial via `fbink`) → WiFi down → suspend `rtcwake -m mem` → réveil RTC. Détails
|
||||
critiques dans `kobo/README.md` :
|
||||
- **Piège EPDC/VEE** : juste après un refresh e-ink, le pilote EPDC refuse de suspendre (haute
|
||||
tension VEE pas redescendue) → `suspend_for()` attend ~12 s puis **retente** le suspend (sinon
|
||||
le CPU tournait 24/24 et vidait la batterie).
|
||||
- **Reboot** : 3 appuis rapides sur un bouton de page (`reboot_watcher.sh`, evdev `EV_KEY` 193/194).
|
||||
Sur batterie : un appui power d'abord (réveil) puis les 3 appuis.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **e-ink = noir & blanc purs, zéro gris** (le gris fantôme au refresh partiel). La hiérarchie passe
|
||||
par taille/graisse, le « consommé » par des hachures. Polices : Archivo (mots) + JetBrains Mono
|
||||
(nombres tabulaires), vendorisées en woff2 dans `backend/static/fonts/`.
|
||||
- **Tout le texte utilisateur et les commentaires sont en français** (accents inclus).
|
||||
- Modifier `dashboard.html` → vérifier le rendu avec `dev/preview.py` avant de commit.
|
||||
</content>
|
||||
</invoke>
|
||||
Reference in New Issue
Block a user