Catalogue de projets développeur qui se rédige et se met à jour tout seul : tu donnes un dépôt à lire, une IA transforme son README.md en entrée de blog, et tout est commité directement sur GitHub — depuis ton navigateur pour un ajout ponctuel, ou tout seul via GitHub Actions pour une mise à jour automatique. Aucun serveur à toi ne tourne jamais.
Le site a trois sections : Accueil (présentation), Projets (le catalogue, en carnet de build chronologique) et Contribuer (lien vers ce dépôt) — accessibles via le menu ☰ dans l'en-tête.
- Fork ce dépôt.
- Sur vercel.com, importe ton fork. Framework preset : Other. Vercel détecte automatiquement
vercel.json(buildnpm run build, dossier de sortiepublic/— généré entièrement à chaque build, jamais commité). - Dans les réglages du projet Vercel (Settings → Environment Variables), coche "Enable access to System Environment Variables" — une seule case à cocher, rien à taper.
SITE_URLetSITE_REPOs'en déduisent automatiquement (URL de production et dépôt Git déjà connus de Vercel). Tu peux quand même forcerSITE_URL/SITE_REPOmanuellement si besoin (domaine personnalisé, etc.), mais ce n'est plus nécessaire par défaut. - Déploie. Le premier build génère
p/example-project.html,catalog.json,sitemap.xml,robots.txtetllms.txtà partir de l'exemple fourni dansprojects/. - Ouvre le site déployé, menu ☰ → Administration, renseigne :
- un token GitHub (classique ou fine-grained, au choix),
- le propriétaire et le nom de ton fork (là où le catalogue va écrire),
- un fournisseur IA (Groq ou OpenRouter) et sa clé API,
- clique Récupérer les modèles, puis choisis-en un dans la liste — c'est une étape à part entière, le formulaire de génération ne s'affiche que quand ce champ est rempli.
- Renseigne le dépôt à cataloguer (
proprietaire/depotou un lien GitHub complet, les deux marchent) et lance la génération. Supprime ensuiteprojects/example-project.json, qui ne sert qu'à amorcer le tout premier build.
Chaque écriture déclenche automatiquement un nouveau build Vercel, qui régénère la page SEO du projet, le catalogue et les fichiers de découvrabilité.
En plus du bouton manuel, .github/workflows/auto-catalog.yml tourne sur un cron et catalogue tout seul : il découvre tous tes dépôts publics (hors forks et hors ce dépôt), et ne (re)génère que ceux qui sont nouveaux ou poussés plus récemment que leur dernière entrée — pas besoin de maintenir une liste.
Pour l'activer, tout se passe depuis le panneau Administration : une fois le fournisseur/clé/modèle renseignés (étape 5 ci-dessus), la section "Automatisation" propose une fréquence (heure / jour / semaine / cron personnalisé), une limite de dépôts par passage, et un champ optionnel pour un token couvrant aussi tes dépôts privés. Le bouton Activer l'automatisation pousse tout ça directement vers GitHub :
- la clé IA part chiffrée (chiffrement scellé libsodium, calculé dans ce navigateur avec la vraie clé publique du dépôt — jamais en clair sur le réseau) comme secret
AI_API_KEY; - fournisseur/modèle/limite deviennent des variables de dépôt (
AI_PROVIDER,AI_MODEL,MAX_PER_RUN) ; - si renseigné, le token pour dépôts privés part chiffré comme secret
CATALOG_PAT; - la ligne
cron:de.github/workflows/auto-catalog.ymlest réécrite selon la fréquence choisie.
Aucune page GitHub à visiter. Un lancement manuel reste possible depuis l'onglet Actions → Auto-catalogue → Run workflow si tu ne veux pas attendre le prochain passage planifié.
En plus des projets (générés depuis un README), le panneau admin permet d'écrire des articles : tu donnes un titre et des notes/un plan en vrac, l'IA structure et développe — pas de dépôt source, pas de README à résumer. Volontairement en blog plat (comme les projets, triés par date) plutôt qu'en cours ordonné à suivre dans un ordre précis : c'est la façon dont les devs consultent la documentation technique (une recherche Google → un article précis), et aucun grand blog technique (Stripe, Vercel) ne structure ses articles en modules séquentiels.
Un champ optionnel série (nom + partie/total) existe pour les cas où plusieurs articles s'enchaînent naturellement — affiché comme repère ("Nom de la série · partie 2/4"), sans forcer un ordre de lecture. Contrairement aux projets, les articles ne sont pas générés automatiquement par le cron : ils partent des notes de l'admin, pas d'un dépôt à découvrir, donc il n'y a rien à automatiser côté planification.
Trois niveaux, du plus automatique au plus spécifique :
- Couverture — chaque page de projet affiche automatiquement l'image sociale GitHub du dépôt source (
opengraph.githubassets.com, générée par GitHub lui-même, sans rien à configurer). - Média détecté — avant d'appeler l'IA, le README est scanné pour des images/GIFs/vidéos YouTube pertinents (les badges de statut type shields.io sont exclus automatiquement). Si quelque chose de pertinent existe, l'IA choisit le meilleur et l'embarque dans la page (image, ou vidéo YouTube en iframe).
- Motif généré — si le README n'a vraiment aucun visuel exploitable, l'IA choisit un motif géométrique abstrait (pulsation de points, barres, grille) et jusqu'à 3 couleurs dans une liste fermée ; le balisage SVG lui-même est produit par du code déterministe (
js/svg-patterns.js), jamais écrit par le modèle — ça élimine le risque qu'un petit modèle échoue à échapper correctement un balisage complet à l'intérieur d'une chaîne JSON. Les entrées générées avant ce changement (avec un SVG brut fourni par le modèle) continuent de s'afficher normalement, nettoyées par le même filtre qu'avant.
Le corps (body) de chaque entrée est du Markdown (titres, blocs de code, listes, gras), rendu par marked des deux côtés — build et aperçu. Le skill blog-writing.md impose qu'un extrait de code du README apparaisse dans une section "Comment ça marche" quand la source en fournit un : l'objectif est une vraie profondeur technique, pas un résumé marketing.
Les modèles open source hébergés sur Groq n'ont pas tous une grande fenêtre de contexte, et un long README peut dépasser ce qu'un modèle donné peut réellement recevoir. Avant chaque génération (manuelle ou automatisée), la taille du README est comparée à la fenêtre de contexte réelle du modèle choisi (récupérée via l'API du fournisseur). Si ça ne tient pas, le README est découpé en sections et condensé séquentiellement (plusieurs appels, un par section, chacun ne gardant que les faits concrets) jusqu'à tenir dans la fenêtre — avec un garde-fou qui tronque plutôt que de boucler indéfiniment sur un cas extrême. Ça marche pareil côté navigateur et côté automatisation.
Une seule entrée malformée dans projects/ ou articles/ (champ inattendu, contenu qui casse le rendu) ne fait plus jamais échouer tout le build : elle est ignorée avec un message clair dans les logs Vercel, et le reste du site se régénère normalement. Avant ce correctif, une seule mauvaise entrée pouvait faire échouer le déploiement entier — et comme Vercel garde alors le déploiement précédent en ligne, le site continuait à "avoir l'air de marcher" tout en devenant de plus en plus périmé, sans qu'aucune alerte ne le signale. Les logs de build affichent maintenant aussi le SITE_URL résolu à chaque déploiement, pour repérer immédiatement un problème de configuration plutôt que de le découvrir des semaines plus tard dans Search Console.
Sitemap/indexation qui ne reflètent pas tes vrais projets — si ton dépôt a été créé avant ce correctif, il existe peut-être encore des copies figées de sitemap.xml, catalog.json, index.html, robots.txt, llms.txt committées à la racine du dépôt (index.template.html, la structure js//css/skills et le dossier public/ généré à chaque build sont les seuls qui doivent exister maintenant). Ces fichiers à la racine datent du tout premier commit et ne se mettent jamais à jour tout seuls — build.js régénère bien des versions fraîches, mais dans public/ (ignoré par git), donc les vieilles copies à la racine restent invisibles pour toi mais peuvent semer la confusion si tu les consultes sur GitHub. Supprime-les une bonne fois pour toutes directement sur github.com (ouvre le fichier → icône corbeille → commit) : sitemap.xml, catalog.json, index.html, robots.txt, llms.txt à la racine, et le dossier p/ s'il existe à la racine (pas public/p/, qui lui est généré et correct).
Le cron ne semble jamais se déclencher — comportement documenté de GitHub, pas un bug : après tout changement de planification, GitHub peut mettre 15 minutes à plus d'une heure à le "reconnaître", et le premier passage n'a lieu qu'au prochain horaire programmé après cette reconnaissance. Pour tester sans attendre : onglet Actions → Auto-catalogue → Run workflow (déclenchement manuel, indépendant du cron). Si ça ne produit rien non plus, c'est un vrai bug (secret/variable manquant) — vérifie les logs de ce run.
"Le modèle n'a pas renvoyé un JSON exploitable" — trois lignes de défense maintenant, dans l'ordre : le mode JSON natif du fournisseur (response_format) contraint la génération elle-même chez Groq et OpenRouter, avec repli automatique si un modèle particulier ne le supporte pas ; une vraie limite de tokens (max_tokens) évite qu'une réponse trop longue soit coupée en plein milieu ; et l'extraction JSON tolère du texte autour (prose avant/après) plutôt que d'exiger un format parfait, avec une tentative de réparation automatique si tout le reste échoue. Si l'erreur persiste malgré ça, le modèle choisi est vraiment peu fiable pour ce type de tâche — change-le depuis le panneau de configuration.
- Dans le navigateur du visiteur (
js/*.js, à la visite) : configuration, lecture/écriture GitHub, appel au fournisseur IA. Rien de tout ça ne tourne sur un serveur. - Au build Vercel (
build.js, à chaque push) : lecture deprojects/*.jsonetarticles/*.json, génération de tout ce qui est publiquement servi danspublic/(jamais à la racine du dépôt, pour ne pas exposernode_modules/,build.jsoupackage.json) —index.html(à partir deindex.template.html, avec le hero/catalogue/articles/contribuer déjà rendus dedans, sans quoi un visiteur ou robot qui n'exécute pas de JS ne verrait qu'un message de chargement), les pages statiquesp/*.htmleta/*.html,catalog.json,sitemap.xml,robots.txt,llms.txt, ainsi qu'une copie decss/,js/etskills/(nécessaires au navigateur à l'exécution).public/est entièrement régénéré à chaque build et n'est jamais commité (voir.gitignore). - Dans GitHub Actions (
.github/scripts/auto-catalog.mjs, sur cron) : découverte des dépôts, lecture des README, appel IA, écriture deprojects/*.json— puis commit/push géré par le workflow.
js/render-catalog.js (rendu du hero et du catalogue) et js/context-budget.js (gestion de la fenêtre de contexte) sont partagés tels quels entre le navigateur et le build/l'automatisation — une seule source de vérité, pas de logique dupliquée qui pourrait diverger.
Voir skills/orchestrator.md, skills/blog-writing.md et skills/seo-sitemaps.md pour les règles exactes suivies par ces trois étapes.
- Le token GitHub et la clé du fournisseur IA (chemin manuel) restent en
localStorage, envoyés uniquement àapi.github.comet à l'API du fournisseur choisi. Côté automatisation, les valeurs sensibles (AI_API_KEY,CATALOG_PAT) sont chiffrées dans ce navigateur avec la vraie clé publique du dépôt (chiffrement scellé libsodium, via TweetNaCl) avant tout envoi — GitHub est seul à pouvoir les déchiffrer, jamais exposées en clair sur le réseau ni dans les logs Actions. - Un token classique fonctionne, mais un token fine-grained limité à ce seul dépôt réduit les dégâts en cas de fuite — l'app affiche un avertissement non bloquant si elle détecte un token à accès large, sans jamais forcer le choix.
- Tout contenu injecté dans le DOM passe par un échappement HTML systématique, avec DOMPurify en filet de sécurité — un README malveillant ne peut pas faire exécuter de script dans le navigateur de l'admin.
build.jsetauto-catalog.mjsont été exécutés et testés localement (syntaxe validée, logique vérifiée contre un faux serveur GitHub/IA simulant plusieurs scénarios : dépôt neuf, dépôt existant, fork à exclure, projet déjà à jour, dépôt sans README).- Le chiffrement scellé utilisé pour pousser les secrets Actions a été vérifié de bout en bout : chiffré avec le code réel de
github.js(chargé comme de vraies balises<script>), puis déchiffré avec une bibliothèque indépendante (PyNaCl/libsodium) pour confirmer que le résultat est authentiquement déchiffrable — pas seulement "a l'air correct". - Les appels réels à
api.github.com, Groq et OpenRouter avec de vrais identifiants n'ont pas pu être testés en conditions réelles dans l'environnement où ce projet a été construit. La forme des requêtes suit la documentation de chaque fournisseur, mais teste le flux complet avec tes propres identifiants avant de t'y fier pour un vrai dépôt. - Groq déprécie parfois des modèles sans préavis long : si
Récupérer les modèlesrenvoie une erreur, vérifie d'abord que le modèle choisi est toujours actif. - Deux correctifs ont été trouvés en vérifiant le site réellement déployé (pas seulement en simulation locale) : une séquence d'échappement JS restée littérale dans le message
<noscript>(\u2019au lieu d'une vraie apostrophe — une erreur qui ne peut arriver que dans du HTML statique, jamais dans une chaîne JS), etoutputDirectoryqui pointait sur la racine du dépôt au lieu depublic/, exposantnode_modules/et le code source dans le déploiement. Les deux sont corrigés dans cette version ; si tu avais déjà déployé une version antérieure, un nouveau push depuis ce zip les résout.