# CLAUDE.md

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

## Nature du dépôt

Dépôt de **données**, pas de code applicatif : des collections Bruno décrivant les API
Mangools. Pas de tests unitaires — l'unité de travail est un fichier YAML de requête. La
seule dépendance (`yaml`) sert au script de bundling, rien d'autre.

Six collections autonomes, une par produit Mangools, toutes bâties sur le même patron :

| Dossier | Endpoints |
| ------- | --------- |
| `KWfinder/` | 23 |
| `SERPWatcher/` | 28 |
| `AISearchWatcher/` | 14 |
| `LinkMiner/` | 10 |
| `SERPChecker/` | 7 |
| `SiteProfiler/` | 6 |

Les endpoints `/mangools/locations` sont partagés et dupliqués dans les collections qui les
référencent — c'est voulu, chaque collection Bruno devant être autosuffisante.

## Commandes

Le CLI n'est pas installé dans le dépôt — l'invoquer via `npx`.

```bash
# Exécuter toute la collection (depuis KWfinder/)
npx @usebruno/cli@4 run . -r --env Mangools --env-var apiKey=$MANGOOLS_API_KEY

# Une seule requête
npx @usebruno/cli@4 run keywords/get-related-keywords.yml --env Mangools --env-var apiKey=$MANGOOLS_API_KEY

# Valider sans toucher à l'API Mangools : mock local + override de baseUrl
npx @usebruno/cli@4 run . -r --env Mangools \
  --env-var baseUrl=http://127.0.0.1:45991/v3 --env-var apiKey=TEST --env-var listId=abc123
```

**Un run complet consomme de vrais crédits** (*Keyword lookups*, *SERP lookups*). Pour
vérifier une modification de la collection, préférer l'override `baseUrl` vers un serveur
local : ça valide le parsing YAML, la résolution des variables, l'héritage de l'auth, les
query strings et les corps JSON sans dépenser un crédit ni exiger de clé valide.
`account/get-limits.yml` est le seul endpoint réel qui ne consomme rien.

Bun (≥ 1.3) parse le YAML nativement via `Bun.YAML.parse` — pratique pour écrire un
validateur ad hoc sans dépendance. En revanche `Bun.YAML.stringify` sort du flow-style sur
une seule ligne : inutilisable pour produire un fichier destiné à être lu ou commité, d'où
la dépendance `yaml` dans `scripts/bundle.ts`.

```bash
bun run bundle   # régénère bundled/mangools-kwfinder.opencollection.yml
```

## Deux formes de collection, deux usages

`KWfinder/` est la **source de vérité** : forme arborescente, celle que Bruno écrit sur
disque et que consomme *Open Collection*.

`bundled/mangools-kwfinder.opencollection.yml` est un **artefact généré**, jamais édité à la
main. Il existe parce que *Import Collection* n'accepte qu'un fichier unique : la forme
arborescente y échoue avec « No valid collections found in the selected files », puisque
`opencollection.yml` n'a pas de clé `items` (les requêtes sont les fichiers voisins). Le
bundle, lui, inline `items` et embarque les environnements sous `config.environments`.

**Toute modification de `KWfinder/` doit être suivie de `bun run bundle`**, sinon les deux
formes divergent silencieusement.

## Vérifier une modification

Les bibliothèques de Bruno sont installables et utilisables directement — s'en servir plutôt
que de réimplémenter des contrôles :

- `@usebruno/filestore` → `parseRequest` / `parseFolder` / `parseCollection` /
  `parseEnvironment`, appelés avec `(contenu, { format: 'yml' })`. C'est le parseur exact de
  l'app ; s'il accepte un fichier, Bruno l'acceptera.
- `@usebruno/converters` → `openCollectionToBruno(obj)` est ce qu'exécute le bouton Import.
  L'appliquer au bundle vérifie l'import sans ouvrir l'app.

## Format : OpenCollection YAML

[Spec v1.0.0](https://spec.opencollection.com/), format par défaut de Bruno depuis la v3.1.0.
Points qui ne se devinent pas :

- Extension **`.yml`**, jamais `.yaml`. Racine `opencollection.yml` (et non `bruno.json`,
  qui est le format `.bru` historique). Un `folder.yml` par dossier, un fichier par requête.
- Les clés `items:` de la spec ne servent **que** pour la forme « bundled » mono-fichier.
  Dans la forme arborescente utilisée ici, le contenu d'un dossier, ce sont ses fichiers
  voisins sur le disque.
- `additionalProperties: false` sur la quasi-totalité des objets du schéma : une clé
  inventée fait échouer la validation, elle n'est pas ignorée.
- **`auth: none` n'existe pas** malgré ce qu'affirme la prose de la doc Bruno. Le `oneOf`
  du schéma n'accepte que les 9 types d'auth plus la chaîne `"inherit"`. « Pas d'auth » =
  omettre la clé `auth`.
- `http.body.type` ∈ `json | text | xml | sparql | form-urlencoded | multipart-form | file`.
  **Pas de `graphql`** : GraphQL est un type d'item à part entière (`graphql:`), pas une
  variante de corps HTTP. La table de la doc Bruno se trompe sur ce point.
- Les valeurs de `params[]` et des variables typées sont des **chaînes**, y compris pour les
  nombres et booléens (`value: "10"`, ou `value: {type: number, data: "30"}`).

## Architecture de la collection

**L'authentification est déclarée une seule fois**, dans `KWfinder/opencollection.yml` sous
`request.auth` (type `apikey`, header `x-access-token`, valeur `{{apiKey}}`). Chaque requête
porte `auth: inherit`. Ne jamais redéclarer la clé au niveau d'une requête — et ne jamais
écrire de valeur de clé en dur dans un fichier.

`environments/Mangools.yml` définit `baseUrl`, `listId`, et `apiKey` en variable
`secret: true`. Une variable secrète **ne porte pas de champ `value`** : le schéma l'interdit,
et Bruno conserve la valeur dans le keychain de l'OS. C'est ce qui rend le dépôt commitable.

Les dossiers regroupent par domaine fonctionnel, pas par méthode HTTP : `keywords/`,
`competitors/`, `keyword-difficulty/`, `lists/`, `locations/`, `account/`. Le nom affiché
vient de `info.name` dans `folder.yml`, l'ordre de `info.seq`.

## Conventions à respecter en ajoutant une requête

1. `info` avec `name`, `type: http`, `seq` unique dans le dossier.
2. URL construite sur `{{baseUrl}}`, jamais un host en dur.
3. Paramètres **obligatoires** actifs et pré-remplis d'un exemple qui s'exécute tel quel ;
   paramètres **optionnels** présents avec `disabled: true`, une valeur d'exemple et leur
   défaut en `description`.
4. Cohérence URL ↔ `params` : tout param query actif doit figurer dans la query string de
   l'URL, tout param désactivé doit en être absent, tout param `type: path` doit apparaître
   comme `:nom` dans l'URL. Bruno ne recalcule pas l'URL à partir de `params`.
5. `auth: inherit`.
6. Une assertion sur le code HTTP attendu, pour que `bru run` ait une valeur.
7. Un bloc `docs` en Markdown : description, table des paramètres, limites/crédits, et les
   écarts de doc éventuels.

## Fidélité à la documentation source

La doc Mangools comporte des erreurs et des zones non spécifiées. La règle du dépôt est de
**documenter l'écart dans le bloc `docs` de la requête concernée** plutôt que de le corriger
silencieusement ou de l'inventer. Les cas connus sont récapitulés dans le tableau du
[README](README.md#écarts-relevés-dans-la-documentation-mangools) — notamment les deux paths
erronés dans la table d'index (`/serpchecker/serps`, `/siteprofiler/requests`) et les corps de
requête déduits par symétrie (`POST /kwfinder/competitor-keywords`,
`DELETE /kwfinder/lists/{id}/keyword`). Toute déduction non confirmée par la doc doit être
signalée comme telle au lecteur.

## Git

Voir les règles globales : `main` = production, jamais de commit direct ; travail sur
`feature/<nom>` ou `fix/<nom>` créées depuis `dev`, Conventional Commits obligatoires.
