# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Contexte

Fork local de la démo Next.js de Better Auth (`better-auth/demo/nextjs`), réduit et reconfiguré :
Turso **local** (fichier libsql), emails via **Resend**, magic link + email/mot de passe + passkey, 2FA, organisations, Stripe, SCIM (démo, désactivée). Pas de providers OAuth sociaux, pas d'Electron, pas de MySQL.

Ce dossier **n'est pas un dépôt git** (le parent `~/Github` en est un) — décision utilisateur, ne pas `git init` sans demande.

## Commandes

Gestionnaire de paquets : **bun** (`bun.lock`). Nouvelle dépendance : `sfw bun add <pkg>`.

| Tâche | Commande |
|---|---|
| Dev (runtime + navigateur monitoré) | `d3k --no-agent --no-tui -t` → `http://better-auth-nextjs-demo.localhost:1355` (Portless). Réutiliser si `d3k status --json` dit `running: true`. Ne **jamais** lancer `bun run dev` à côté. |
| Dev sans d3k | `bun run dev` |
| Typecheck | `bun run typecheck` (`next typegen && tsc --noEmit`) |
| Migration DB | `bun run db:migrate` — **à relancer après tout ajout/retrait de plugin** dans `lib/auth.ts` |
| Shell SQL | `bun run db:shell` (`tursodb data/local.db`) ; en lecture pendant que l'app tourne : `tursodb --readonly data/local.db "<sql>"` |
| Code mort | `bun run knip` (config `knip.json`, ignore `.agents/`, `.claude/`) |
| Build | `bun run build` |
| Lint | `bun run lint` est cassé : `next lint` n'existe plus dans Next 16. Outil de remplacement (oxlint) non décidé. |
| Tests | Aucun runner installé. `lib/scim-demo-employee.test.ts` (vitest) vient de l'upstream et est exclu du `tsconfig`. |

Preuves pendant le dev : `d3k errors --context`, `d3k logs --type server`, `d3k agent-browser snapshot -i` (filtrer les `generic … clickable` du fond animé). Les emails sans `RESEND_API_KEY` sont loggés en texte brut côté serveur — c'est là qu'on récupère les liens magic link / reset / vérification.

Variables d'environnement : `env.example` (commenté) et `env-setup.txt` (guide complet). `BETTER_AUTH_URL` sans slash final.

## Architecture

**`lib/auth.ts` est la source de vérité.** Il choisit la base, déclare tous les plugins serveur et les hooks d'email. Trois fichiers doivent rester alignés :

- `lib/auth.ts` — plugins serveur. Sélection DB en tête de fichier : `DEMO_SQLITE_PATH` (node:sqlite, tests) sinon `TURSO_DATABASE_URL` via `@libsql/kysely-libsql` (`file:` local sans token, `libsql://` + `TURSO_AUTH_TOKEN` pour Turso Cloud). `baseURL` dérivé de `BETTER_AUTH_URL`.
- `lib/auth-client.ts` — miroir client (`createAuthClient` de `better-auth/react`) : chaque plugin serveur exposant des endpoints client a son `xxxClient()` ici. `customSessionClient<typeof auth>()` typé depuis le serveur.
- `lib/server-client.ts` — client serveur pour le resource server OAuth (`/api/mcp`).

Ajouter un plugin = les trois étapes : serveur, client, `bun run db:migrate`.

`app/api/auth/[...all]/route.ts` monte le handler (`toNextJsHandler`) avec un wrapper CORS limité aux endpoints OAuth2 en dev.

**Garde d'accès** : `proxy.ts` (Next 16 « proxy », ex-middleware) redirige `/dashboard/*` vers `/sign-in` si le cookie de session est absent — vérification de présence de cookie uniquement, pas de validation de session. La validation réelle se fait dans les pages via `auth.api.getSession`.

**Données côté client** : `data/<domaine>/` contient une fonction par requête/mutation TanStack Query (`user/`, `organization/`, `subscription/`), avec les clés de cache dans `keys.ts`. Les composants n'appellent pas `authClient` directement pour les mutations : ils passent par ces hooks. `data/query-client.ts` fournit le singleton (SSR-safe).

**Formulaires** : `components/forms/*` = react-hook-form + zod v4 (`z.email()`), UI shadcn (`components/ui/`, exclu du typecheck). `components/forms/magic-link-form.tsx` est le modèle minimal.

**Emails** : `lib/email/send.ts` (`sendEmail({ to, subject, react })`) → Resend, avec fallback console si pas de clé. Templates react-email dans `lib/email/*.tsx`, chacun exportant `reactXxxEmail(props)`. `lib/auth.ts` importe ces fichiers avec extension (`./email/x.tsx`, `allowImportingTsExtensions`) — style hérité de l'upstream, conservé pour la CLI `auth` qui charge `auth.ts` via jiti.

**Auth avancée** (héritée de la démo, fonctionnelle) :
- OAuth provider + MCP : `app/api/mcp/route.ts`, `app/.well-known/*`, pages de consentement/sélection sous `app/(auth)/oauth/*`. `resources` et claims dérivés de `baseURL`.
- Device flow : `app/(auth)/device/*`.
- 2FA (TOTP + OTP email) : `app/(auth)/two-factor/*`, formulaires `components/forms/two-factor-*`.
- SSO : le plugin `sso()` ne sert plus qu'au portail employé SCIM (`defaultSSO` vide si `SCIM_DEMO_ENABLED` ≠ `true`).

**SCIM demo** (`lib/scim-demo*.ts`, `app/api/scim-demo/*`, `app/scim-demo/*`, `app/dashboard/scim/*`) : module optionnel activé par `SCIM_DEMO_ENABLED=true` + `SCIM_DEMO_CREDENTIAL_PEPPER` (+ `SCIM_DEMO_OIDC_CLIENT_SECRET` pour le portail employé). Il ajoute le champ `user.scimDemoRole`. Conservé tel quel, non refactoré.

## Pièges

- `next.config.ts` a `typescript.ignoreBuildErrors: true` : le build passe même avec des erreurs de types — se fier à `bun run typecheck`.
- `serverExternalPackages: ["libsql"]` est requis pour le binaire natif libsql ; ne pas le retirer.
- Stripe : sans `STRIPE_KEY`, le plugin s'initialise avec `sk_test_` et des price IDs de démo ; `/pricing` s'affiche, le checkout échoue.
- `tursodb` peut lire `data/local.db` pendant que l'app tourne (`--readonly`) ; éviter les écritures concurrentes.
