À la fin de chaque tour, propose 3 prochaines étapes cliquables au-dessus du prompt

Ici, on range les skills de l'équipe. Une skill, c'est une notice que Claude lit tout seul pour mieux faire un travail précis.
Tu installes une seule fois le paquet skills-equipe. Ensuite, chaque nouvelle skill arrive toute seule chez toi. Tu n'as plus rien à télécharger.
Dans Claude, tape ces deux lignes :
/plugin marketplace add JRAYES000/marketplace-equipe
/plugin install skills-equipe@marketplace-equipe
Ou avec la souris : Réglages → Capacités → Plugins → Ajouter une marketplace, puis colle JRAYES000/marketplace-equipe.

Pour vérifier que ça a marché : tu dois voir les douze skills dans tes capacités, chacune précédée du nom du paquet — skills-equipe:phrase-magique, skills-equipe:revue-qualite, skills-equipe:fonce, skills-equipe:parallelisation-et-routage, skills-equipe:delegation-deepseek-openrouter, skills-equipe:linkedin-carrousel, skills-equipe:linkedin-commentaires, skills-equipe:linkedin-veille-virale, skills-equipe:linkedin-mise-en-forme, skills-equipe:analyse-reseau-client, skills-equipe:france-travail-extraction, skills-equipe:analyse-lundi-linkedin.
Une fois skills-equipe installé, les skills arrivent avec le paquet. Tu n'as rien à télécharger.
Paquet publié : skills-equipe 1.39.0. Si le numéro affiché dans Réglages → Plugins est inférieur à 1.39.0, tu n'as pas la dernière version.
Aucune skill ne s'active toute seule. Les douze attendent que tu les appelles — en tapant leur nom, ou avec une des formulations données dans la colonne Ce qu'elle fait. Une fois lancée, une skill tient jusqu'à ce que tu dises mode normal. C'est volontaire : rien ne change ta façon de travailler sans que tu l'aies décidé. Si une skill se déclenche alors que tu ne l'as pas demandée, c'est un bug — ouvre une issue.
Dans le tableau, Version est le numéro propre à chaque skill : il avance quand cette skill change, indépendamment des autres et du numéro du paquet. Dernière mise à jour est la date de ce changement-là.
Comment on les nomme, une fois pour toutes. Trois niveaux, un nom par niveau, et jamais deux graphies pour le même objet :
| Niveau | Nom | Où il sert |
|---|---|---|
| Le dépôt | marketplace-equipe | uniquement dans la ligne d'installation |
| Le paquet | skills-equipe | uniquement pour parler du paquet et de sa version |
| Une skill | skills-equipe:phrase-magique | partout ailleurs — c'est la forme complète, celle qui ne se confond avec rien |
La colonne Skill ci-dessous abrège au nom court pour tenir dans le tableau ; en dehors d'elle, on écrit la forme complète. Pour déclencher une skill, le langage naturel marche aussi (« phrase magique », « parallélise ça ») — c'est un déclencheur, pas un nom.
| Skill | Version | Dernière mise à jour | Ce qu'elle fait |
|---|---|---|---|
fonce | 1.4.2 | 22/09/2026 | Le mode « je pars, tu gères ». Claude écrit d'abord noir sur blanc à quoi ressemblera « c'est fini » (un test qui passe, une page qui répond), puis il y va tout seul : aucune question, il tranche les doutes lui-même et te dit à la fin ce qu'il a supposé — tu corriges d'un mot si besoin. Il vérifie pour de vrai avant d'annoncer terminé. Il tient un fichier de bord (.fonce.md, hors git) avec sa condition de fin, ses hypothèses et les pistes déjà essayées : si la conversation se vide ou se reprend plus tard, il repart de là au lieu de tout refaire. Tout ce qui sort du projet sans que tu l'aies demandé (mettre en ligne, envoyer un message) est préparé mais pas exécuté : ça t'attend dans le bloc « Reste » du récap, à lancer d'un mot. Tape /fonce suivi de ta demande. Dans Claude Code, colle la ligne /goal … qu'il affiche en tête pour qu'il enchaîne les tours sans toi ; sans elle, il s'arrête à la fin du premier tour et repart d'un « continue ». Le niveau d'effort se règle avant de lancer, pas pendant : medium suffit sur le travail courant, monte d'un cran sur du code exigeant. |
phrase-magique | 1.10.0 | 22/09/2026 | Le mode « travail soigné ». Avant de produire quoi que ce soit, Claude te signale les limites ou les risques qu'il voit, te pose 4 questions en une seule fois (même si ta demande te paraît claire), remplies au maximum — cases à cocher multiples, aperçus à comparer — et jamais de deuxième salve. Et, quand c'est une petite tâche, il te donne déjà un premier jet dans le même message. Ensuite il écrit noir sur blanc à quoi ressemblera un travail réussi, te dit en une phrase ce qu'il va faire avant de commencer, vérifie vraiment — il ouvre ce qu'il a produit au lieu de supposer que c'est bon — et te dit s'il a raté quelque chose, sans s'excuser trois fois pour une virgule. À la fin il reprend chaque point un par un : FAIT avec la preuve sous les yeux, ou NON CONFORME avec ce qui manque. Sur un gros chantier il écrit le plan dans un fichier, pour que tu puisses vider la conversation sans rien perdre, il s'arrête au lieu de tourner en rond quand ça bloque deux fois de suite, et il te propose de ranger dans le CLAUDE.md ou dans une skill ce que tu viens de lui réexpliquer. Une page de règles, pas un manuel : les 17 formulations à réutiliser dans tes propres demandes, et les preuves qui justifient chaque règle, sont rangées à côté — Claude les ouvre seulement quand tu les demandes. Tape /skills-equipe:phrase-magique pour l'activer, ou dis simplement « phrase magique ». mode normal pour l'arrêter. |
revue-qualite | 1.1.0 | 03/10/2026 | Le contrôle « avant de rendre ». Tu as fini ta tâche : tape /skills-equipe:revue-qualite (ou « revue qualité ») avant de l'annoncer à Julien. Claude relit la demande de départ, ouvre pour de vrai ce que tu as produit (la page en ligne, le post programmé, le fichier) et passe une grille : tout ce qui était demandé est fait, rien n'est inventé, tout est vérifié en ligne, le compte rendu est prêt. Pour un article ou une page web, il ajoute les contrôles SEO ; pour un post LinkedIn, il lance le script de linkedin-mise-en-forme. Il te donne les 3 corrections les plus importantes, chacune avec ce qu'il faut faire tout de suite et ce qu'il faut retenir pour la prochaine fois. Le message tient en 400 mots ; tape « détail » pour voir tous les contrôles et toutes les pistes. Puis il te pose 4 à 8 questions sur ton travail (pourquoi ce choix, quelle preuve, qu'est-ce qui a été laissé de côté). Tu réponds, il juge chaque réponse, et il rend un verdict : PRÊT ou À REPRENDRE, avec la liste de ce qui reste. Ton direct, sans compliments pour la forme. Pressé ? Dis « juste le verdict » : il saute les questions. |
parallelisation-et-routage | 1.4.3 | 22/09/2026 | Le mode « économie ». Claude arrête de tout faire lui-même : il regarde ce que tu demandes, décide quel assistant suffit (Haiku pour le mécanique, Sonnet pour le courant, Opus pour ce qui demande vraiment de réfléchir) et lui passe le travail. Pour un gros lot (50 documents à résumer, 60 lignes à classer, 12 pages à auditer), il découpe et lance plusieurs assistants rapides en même temps. Moins de tokens consommés, et plus rapide. À l'inverse de ce qu'on croit, elle sert surtout à freiner : Claude a tendance à lancer des assistants pour des broutilles, et elle lui dit quand ça n'en vaut pas la peine. Dis « parallélise ça », « active le routeur » ou « mode économie de tokens » ; mode normal pour l'arrêter. |
delegation-deepseek-openrouter | 1.4.1 | 13/09/2026 | Fait faire les grosses tâches (résumés, traductions, gros volumes) par une IA moins chère. Tu économises tes messages Claude. À réserver au vrai volume : sur une tâche moyenne, baisser l'effort de réflexion de Claude coûte moins cher que l'aller-retour. Nouveau : Claude choisit lui-même entre la petite IA, la grande, et lui-même, en se posant trois questions dans l'ordre. La grande coûte une quinzaine de fois la petite pour presque rien de mieux, donc elle ne sert que sur un cas précis — beaucoup de texte à produire, sans enjeu, mais avec du raisonnement dedans. |
linkedin-mise-en-forme | 1.6.1 | 03/10/2026 | Comptage des emojis corrigé le 03/10/2026 : un emoji suivi de son sélecteur invisible (❤️) comptait pour deux, et le sablier ⏳ ou le réveil ⏰ ne comptaient pas du tout — vu sur un post réel à 4 emojis mesuré à 3. Un test ajouté (76 au total). Contrôle de variété étendu aux 5 derniers posts du compte le 30/09/2026 : scripts/verif-post.mjs verif <brouillon> ne compare plus seulement le post précédent. Il lit jusqu'à 5 .final.txt du même compte (les 5 derniers strictement avant le brouillon ; à défaut de dossier local, les 5 derniers posts publiés via Zernio) et ajoute le 16e critère « variété sur les 5 derniers posts », qui refuse et nomme le post en cause (n°1 = le plus récent) si le brouillon reprend : le même schéma (accroche, nombre de blocs, fin) qu'un de ces 5 posts ; les 3 premiers mots de l'accroche ; la même dernière ligne mot pour mot (un lien ou une ligne « Source » ne compte pas) ; une même phrase en gras (2 mots minimum). Un même type d'accroche ou de fin sur plusieurs posts reste permis (il n'y a que trois types, et finir sur une vraie question est une consigne permanente). Le critère « schéma différent du post précédent » reste. precedent.mjs : nouvelles precedentsLocaux et derniersPostsZernio (limit=5), resoudrePrecedent renvoie aussi textes et sources. 10 tests ajoutés (75 au total). Post précédent branché automatiquement le 30/09/2026 (consigne de Julien du 29/09) : scripts/verif-post.mjs verif <brouillon> retrouve seul le post précédent du même compte (a<N>.final.txt = julien-agency, p<N>.final.txt = julien-partners) — le .final.txt le plus récent du compte dans livrables-Claude-Agency/linkedin/ (chemin configurable : --dossier ou LIVRABLES_LINKEDIN_DIR), strictement avant le brouillon s'il s'y trouve ; sinon le dernier post publié via l'API Zernio (GET /v1/posts, lecture seule, clé jamais affichée) ; sinon un avertissement, sans échec. Options --precedent, --compte, --dossier, --sans-precedent. Nouveau scripts/precedent.mjs, 13 tests ajoutés (65 au total). Règle de publication mise à jour dans CLAUDE.md : feu vert permanent pour les posts julien-agency et julien-partners, le reste garde la règle du feu vert. Premier constat réel : a6 a le même schéma que a5. Consignes de Julien du 29/09/2026 (mail de 22h08), appliquées le 30/09/2026 : l'accroche est libre (question, prise de position, chiffre ou situation concrète ; les 2 premières lignes doivent donner envie de cliquer sur « … plus », plafond de 140 caractères conservé) ; 1 à 4 blocs, avec ou sans titre, plus d'émojis 👉 ⚡ ✅ imposés (6 au maximum, en tête de ligne) ; fin libre, « Et vous ? » n'est plus obligatoire ; gras : 1 ou 2 passages par post au maximum (remplace « au moins huit ») ; trois tournures interdites de plus (« Notre lecture : », « Testons-la. », « Suivons son regard. »), refusées aussi quand elles sont déjà en gras Unicode ; nouvelle règle de variété : jamais deux posts de suite sur un compte avec la même accroche, le même nombre de blocs et la même fin. scripts/verif-post.mjs verif <brouillon> [<post-précédent>] mesure désormais 14 critères (15 avec le post précédent, qu'il ne devine pas). Le contrôle des accents et des anglicismes ne change pas. 52 tests (npm test) ; deux fixtures et deux regex de linkedin-veille-virale adaptées pour rester au vert (45 tests). Garde-fou d'accents réparé le 29/09/2026 (la 1re ligne de A4, « Confier l'IA a un seul salarie de l'equipe est-il un risque sous-estime ? », tout en gras sans accent, avait obtenu 14/14). Trois trous cumulés : « l'equipe » restait un seul mot (l'élision n'était pas découpée), « salarie »/« estime »/« sous-estime » existent aussi sans accent (homographes) donc le dictionnaire les jugeait valides, et « a » (une lettre) était ignoré d'office ; le gras Unicode coupé à chaque « ? », « - » ou virgule faisait en plus perdre tout contexte. Le contrôle lit désormais la ligne entière, découpe l'élision et ne juge que les mots en gras : « a » pour « à » (devant un infinitif, en début de phrase, après un infinitif sans sujet), mots en e- (« equipe », « etat »), composés (« peut-etre », « touche-a-tout ») et participes en -e qui existent en -é (après être/avoir, ou composé à préfixe en fin de propos, « sous-estime »). Pas de faux positifs sur « a-t-il », « il a », « l'a », « A4 », « projet », « risque ». Limite : un homographe sans indice de contexte (« un seul salarie ») ne se voit pas sans fréquence d'usage. Deux fixtures de test contenaient elles-mêmes la faute (« a noter » ; « Ce qui a change » dans linkedin-veille-virale) : corrigées. 9 tests ajoutés (44 au total). Contrôle des anglicismes ajouté le 29/09/2026 (le post A1 du 28/09 était parti avec « process ») : 15e critère bloquant « aucun anglicisme » dans scripts/verif-post.mjs — le post est refusé, avec le mot fautif et son équivalent français ("process" (dites plutôt "processus")). Le contrôle réutilise la liste de linkedin-carrousel/lib/valider-anglicismes.js par import direct, sans copie : un mot ajouté là-bas vaut pour les deux skills. 5 tests ajoutés (« process », « deadline », « feedback », un mot en gras Unicode, un post propre, plus un test qui rejoue toute la liste partagée). Garde-fou dictionnaire ajouté le 28/09/2026 (demande de Julien, après 3 posts publiés avec un accent retiré à tort dans le gras — « dependance », « coute », « defaillance », « declarent »— que rien ne détectait) : nouveau 14e critère « aucun accent manquant dans le gras », basé sur un dictionnaire français réel (an-array-of-french-words, ~336k mots) plutôt qu'une liste maison — un mot en gras dont la forme sans accent n'existe pas mais dont la forme accentuée existe est refusé, le mot fautif nommé ; un anglicisme ou nom propre sans équivalent accentué n'est jamais signalé à tort. 6 tests ajoutés rejouant les 4 cas exacts. Règles de forme obligatoires pour tout post LinkedIn de Julien, sur les deux comptes, depuis le 29/09/2026 : accroche libre qui donne envie de cliquer sur « … plus », 1 à 4 blocs avec ou sans titre, 1 ou 2 passages en gras au maximum (tous sans accent — le gras Unicode de LinkedIn n'a pas de lettre accentuée), 6 emojis au plus et en tête de ligne, phrases de 20 mots maximum, corps entre 1 300 et 1 900 caractères, jamais deux posts de suite sur le même schéma. scripts/verif-post.mjs verif <brouillon> mesure désormais quinze critères (contre neuf au départ) et sort en code 1 si l'un échoue ; scripts/verif-post.mjs gras "<segment>" fabrique le gras et refuse un segment accentué. Test adversarial du 22/09/2026 (même méthode que le 18/09 sur les autres skills LinkedIn) : ajoute un critère « aucun chiffre sans source » (repris de linkedin-carrousel, absent jusque-là — un chiffre inventé passait sans encombre) et corrige un contournement réel de la banque de formulations interdites (« C O M M E N T E Z O U I » espacée lettre par lettre, ou séparée par un tiret/de la ponctuation, passait à travers). Documente aussi deux limites sciemment non couvertes (une troisième police Unicode « gras » silencieusement ignorée ; le contrôle de langue de l'accroche, volontairement absent). Logique extraite dans scripts/lib.mjs, 35 tests (npm test). Se combine avec contenu-linkedin (une skill de projet, dans le dépôt visibilite-ops — pas une skill de ce paquet, donc pas toujours chargeable selon le dossier de travail), qui dit quoi écrire. |
| linkedin-carrousel | 1.10.5 | 03/10/2026 | Installation documentée le 03/10/2026 : la première fois sur un poste, lance npm ci puis npx playwright install chromium dans le dossier de la skill. Sans ces deux commandes, la fabrication du PDF échoue (62 tests sur 196 en échec sur un poste neuf, 196 sur 196 après). Exemple julien-agency-2026-09-14-v2 débarrassé de « process » le 29/09/2026 (validé par Julien) : diapos 2 et 4, commentary.txt et .brouillon.txt corrigés (« votre recrutement perd… », « une démarche non pilotée », « votre recrutement hésite », « ce recrutement »), contrôle des anglicismes 0 refus sur les 12 fichiers, 196 tests verts, PDF régénéré en local sans rien publier. Le post publié le 15/09 reste en ligne avec l'ancien texte (correction à la main par Julien). Garde-fou dictionnaire ajouté le 28/09/2026 (demande de Julien, même incident que linkedin-mise-en-forme — la liste fermée MOTS_SANS_ACCENT_VERS_CORRECT ne couvrait pas « dependance », « defaillance », « declarent ») : verifierMotsAccentuesEnGras (lib/valider-post.js) est désormais doublé d'un vrai dictionnaire français (an-array-of-french-words, ~336k mots) en plus de la liste fermée existante, qui reste active pour le texte hors gras. 5 tests ajoutés rejouant les 4 cas exacts. Deux finitions de la checklist recrutement le 26/09/2026 (demande de Julien) : ligne avant la signature reformulée (« Accusés de réception, relances à 48 h : l’IA peut les envoyer pour vous. C’est le métier de Claude Agency. ») ; ligne de source reformulée (« Source du délai de deux semaines (point 2) : Yaggo/Ifop, baromètre expérience candidat, janvier 2026. »). Vérifié par rendu réel (page unique, rien de coupé). Quatre retouches de la checklist recrutement le 26/09/2026 (demande de Julien) : point 2 ne répétait plus que le titre, remplacé par une action (« Annoncez au candidat la date de votre réponse dès le premier échange »), la ligne de source en bas de page précise désormais qu'elle justifie le délai de deux semaines du point 2 ; point 4 recentré sur une action datée (« Notez la date de chaque échange. Sans nouvelle de vous depuis 48 h, relancez. ») ; une ligne ajoutée avant la signature sur le métier de Claude Agency (accusés de réception et relances automatiques), sans chiffre promis ; claudeagency.fr ajouté au pied de page. Vérifié par rendu réel (page unique, rien de coupé, texte foncé ≥14px). Checklist PDF A4 ajoutée le 26/09/2026 (demande de Julien) : generer-checklist-pdf.js produit un lead magnet une page (10 points actionnables avec exemple concret) à partir d'un nouveau gabarit templates/checklist-a4.html (mêmes polices/couleurs/logo que les carrousels, canevas 794×1123px = A4 à 96dpi, mesure réelle de la hauteur avant export pour refuser tout débordement). Premier usage : la checklist recrutement promise dans le carrousel julien-agency programmé le 26/09/2026 08:30 Paris (a-publier/julien-agency-2026-09-26-checklist-recrutement.pdf), en continuité du carrousel source (même chiffre Yaggo/Ifop, même délai de relance 48h, même réseau de recommandations). « Fais-moi un carrousel » : compose un carrousel LinkedIn (PDF de 6 à 10 pages) pour Claude Agency ou Claude Partners à partir d'un sujet. Refonte complète du dessin le 24/09/2026 (demande de Julien) : 6 modèles de page (accroche, gros chiffre, comparaison, checklist, citation, appel à l'action) au lieu d'un seul ; logo réel, photo de l'auteur et couleurs de marque visibles sur CHAQUE page (avant : masqués sur la page 1) ; polices Bricolage Grotesque + Schibsted Grotesk (mesurées réellement sur les deux sites de marque, plus Fraunces/Inter jugées « IA ») ; emplacement image optionnel signalé explicitement tant que fal.ai n'est pas accessible. Quatre corrections le même jour après relecture du premier carrousel de test : limite de mots relevée à 60 (avant 25) et comptée sur TOUS les champs visibles d'un modèle (titre, texte, chiffre, items, colonnes, citation, auteur, bouton — pas seulement titre+texte comme avant, un vrai trou de couverture) ; nombre de diapos ramené à 6-10 (avant 8-12, conforme au brief initial qui l'avait déjà spécifié) ; numéro de page reformulé en « N/Total » (ex. « 3/10 ») à la place de « 01 », cité par Julien parmi les défauts du dessin ; règle ajoutée interdisant toute citation ou témoignage inventé (incident réel : une citation attribuée à « un dirigeant de PME » sur le carrousel de test, jamais publiée, corrigée avant tout envoi). Publication reelle basculée sur Zernio le 24/09/2026 (mail de Julien : Composio ne sait publier aucun document/PDF, ce qui partait sous ce canal jusque-là était en réalité une grille d'images, pas un vrai carrousel feuilletable) : publier-zernio.js <compte> <pdf> <documentTitle> [texte-post.txt] [--publier], en trois appels (POST /v1/media/presign, upload sur l'URL signée, POST /v1/posts avec un média document et platformSpecificData.documentTitle), avec vérification réelle (GET /v1/accounts) que l'accountId Zernio utilisé correspond bien au compte demandé avant tout envoi — jamais d'appel /v1/posts sans le drapeau --publier explicite. node etat.js donne un point de situation avant de commencer. Le 25/09/2026, premier vrai carrousel publié pour julien-agency (sujet sourcé Bpifrance/Insee) : révèle un bug jamais vu jusque-là, l'emplacement image des modèles checklist/comparaison n'avait aucune règle CSS (introduit le 24/09/2026, jamais utilisé avec une vraie image avant) — une image sans hauteur contrainte faisait déborder le bloc et chevaucher le bandeau logo, corrigé dans les deux templates (hauteur fixe 480px, object-fit: cover). Publication automatique désormais actée pour julien-agency et julien-partners uniquement (mail de Julien du 24/09/2026), voir SKILL.md. Corrigé le 25/09/2026 (même jour, premier carrousel julien-partners) : le correctif ci-dessus ne s'appliquait en réalité JAMAIS — un second bloc CSS .image-slot plus ancien, laissé après la refonte du 24/09, écrasait la hauteur fixe par la cascade (dernière règle du fichier gagne), retirant height: 480px et repassant img { height: auto }. Trouvé par rendu réel (l'image débordait encore malgré le « correctif »), corrigé en supprimant le bloc dupliqué dans les deux templates, confirmé par les 132 tests (node --test) et un nouveau rendu visuel propre. Premier carrousel réel publié pour julien-partners le 25/09/2026, sujet « le temps gagné par l'IA sert-il vraiment à quelque chose, ou isole-t-il davantage ? » (sources Opinium pour OpenAI et Collective.work/Shine), 1 image fal.ai réelle (flux/schnell) sur la diapo checklist — un second essai d'image sur une diapo comparaison/gros-chiffre a débordé la diapo (texte trop dense pour 480px d'image) et a été retiré plutôt que publié en l'état. Six corrections le même jour (25/09/2026), après relecture par Julien du carrousel julien-agency publié plus haut** (urn:li:ugcPost:7509117168333287425) : photo d'ordinateur à l'écran vide retirée d'une diapo ; titre qui touchait le trait décoratif du haut, corrigé dans les deux gabarits (.image-slot réduite de 480px à 380px — un premier correctif avait déplacé le débordement du haut vers le bas plutôt que de le résorber, corrigé après un second rendu réel) ; texte de soutien des pages « gros chiffre » agrandi et assombri (40px gris → 46px encre) dans les deux gabarits ; garde-fou d'accents élargi après une faille réelle (un mot toujours accentué, écrit sans son accent À L'INTÉRIEUR d'un passage en gras, échappait à tout contrôle — convertirGras vérifie désormais aussi ces mots, et documentTitle, jamais vérifié jusque-là, passe maintenant par le même contrôle avant tout envoi Zernio) ; règle ajoutée — le texte d'un post de carrousel fait 5 lignes maximum (accroche/promesse/appel à l'action), la fourchette 1300-1900 caractères de linkedin-mise-en-forme ne s'applique plus ici ; une page utile ajoutée (« Comment choisir votre premier process »). Bug d'environnement trouvé au passage, sans rapport : un .env à fins de ligne Windows (CRLF) faisait échouer silencieusement la lecture de ZERNIO_API_KEY, corrigé. Carrousel corrigé republié pour julien-agency, ancien post vo
hooks/register.tsx 163 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4const steps = atom({ plugin: 'next-steps', key: 'steps' } as const, [])
5const isEnabled = atom({ plugin: 'next-steps', key: 'isEnabled' } as const, true)
6const isLoading = atom({ plugin: 'next-steps', key: 'isLoading' } as const, false)
7
8const ACCENT = '#D97757'
9
10// Lucide's corner-down-right, stroked in the accent: reads as "what comes next".
11const ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="${ACCENT}" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 10 20 15 15 20"/><path d="M4 4v7a4 4 0 0 0 4 4h12"/></svg>`
12
13const ASK = `Propose exactement 3 prochaines étapes concrètes pour faire avancer ce travail, \
14formulées comme des consignes que l'utilisateur t'enverrait (en français, 80 caractères maximum chacune). \
15Réponds uniquement par un tableau JSON de 3 chaînes, sans aucun autre texte.`
16
17const parse = (text: string): string[] => {
18 const match = text.match(/\[[\s\S]*\]/)
19 if (match === null) return []
20 try {
21 const list = JSON.parse(match[0]) as unknown
22
23 return Array.isArray(list) ? list.filter((one): one is string => typeof one === 'string').slice(0, 3) : []
24 } catch {
25 return []
26 }
27}
28
29const PICK = /^\s*[1-9](\s*(,|;|\+|&|et)\s*[1-9])*\s*\.?\s*$/i
30
31const pick = (text: string, list: string[]): string[] => {
32 if (list.length === 0 || !PICK.test(text)) return []
33 const numbers = [...new Set(text.match(/[1-9]/g) ?? [])].map(Number)
34 if (numbers.some(n => n > list.length)) return []
35
36 return numbers.map(n => list[n - 1]!)
37}
38
39// An answer that ends on a question waits for the person's reply, not for suggestions.
40const endsOnQuestion = (answer: string) => {
41 const lastLine = answer.trim().split('\n').at(-1) ?? ''
42
43 return /\?$/.test(lastLine.replace(/[\s*_`)»"']+$/, ''))
44}
45
46// When a slash command or a compaction last ran: a turn that covers it gets no suggestions.
47let quietAt = 0
48
49export const register: Register = on => {
50 on('session.start', async ($, e, next) => {
51 await $.command.register({
52 name: 'next-steps',
53 description: 'Active ou coupe les prochaines étapes proposées (on | off)',
54 })
55 const stored = await $.store.get('isEnabled')
56 if (typeof stored === 'boolean') await update($, isEnabled, () => stored)
57
58 return next(e)
59 })
60
61 on('command.run', { command: 'next-steps' }, async ($, e) => {
62 const isOn = !/off/i.test(e.args ?? '')
63 await update($, isEnabled, () => isOn)
64 await $.store.set('isEnabled', isOn)
65 if (!isOn) await update($, steps, () => [])
66
67 return { text: isOn ? 'Prochaines étapes activées.' : 'Prochaines étapes coupées.' }
68 })
69
70 // "1", "1 et 3", "2, 3" or "1+2" typed while suggestions show sends those suggestions.
71 on('prompt.submit', async ($, e, next) => {
72 const list = await read($, steps)
73 await update($, steps, () => [])
74 const picked = pick(e.text, list)
75 if (picked.length === 0) return next(e)
76
77 const text = picked.length === 1
78 ? picked[0]!
79 : `Fais ces étapes dans l'ordre :\n${picked.map((step, index) => `${index + 1}. ${step}`).join('\n')}`
80
81 return next({ ...e, text })
82 })
83
84 on('command.run', async ($, e, next) => {
85 quietAt = await $.clock.now()
86
87 return next(e)
88 })
89
90 on('session.compact', async ($, e, next) => {
91 quietAt = await $.clock.now()
92
93 return next(e)
94 })
95
96 on('turn.complete', async ($, e, next) => {
97 const done = await next(e)
98 const isMainAnswer = e.agentId === undefined && !e.isAborted && e.reason === 'answer'
99 if (!isMainAnswer || !(await read($, isEnabled))) return done
100 const isAfterCommand = quietAt >= (await $.clock.now()) - e.durationMs - 1000
101 if (isAfterCommand || endsOnQuestion(e.answer)) return done
102
103 // Outside the dispatch, so the turn ends at once.
104 $.clock.after(1, async () => {
105 await update($, isLoading, () => true)
106 try {
107 const reply = await $.model.fork({ prompt: ASK })
108 if (reply.isAnswered) await update($, steps, () => parse(reply.text))
109 } finally {
110 await update($, isLoading, () => false)
111 }
112 })
113
114 return done
115 })
116
117 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
118 const list = await read($, steps)
119 const isThinking = await read($, isLoading)
120 if (e.props.hasSurvey || e.props.isWorking || (list.length === 0 && !isThinking)) return next(e)
121
122 const elements = $.ui.resolve(e)
123 const { Box, Button, Text } = elements
124 const Svg = 'Svg' in elements ? elements.Svg : undefined
125 const below = await next(e)
126
127 const header = (
128 <Box key="header" flexDirection="row" alignItems="center" gap={1}>
129 {Svg !== undefined && <Svg source={ICON} alt="Suite" width={14} height={14} />}
130 <Text bold color={ACCENT}>
131 Prochaines étapes
132 </Text>
133 <Text dimColor>{isThinking ? 'en préparation…' : 'clic, touche 1 à 3, ou tape « 1 et 2 »'}</Text>
134 <Box flexGrow={1} />
135 {!isThinking && (
136 <Button key="dismiss" label="Ignorer" role="dismiss" dimColor onPress={() => update($, steps, () => [])} />
137 )}
138 </Box>
139 )
140
141 return (
142 <Box flexDirection="column" gap={1}>
143 <Box flexDirection="column">
144 {header}
145 {list.map((step, index) => (
146 <Button
147 key={`step-${index}`}
148 label={step}
149 hotkey={String(index + 1)}
150 plain
151 onPress={async () => {
152 await update($, steps, () => [])
153 void $.prompt.submit({ text: step })
154 }}
155 />
156 ))}
157 </Box>
158 {below}
159 </Box>
160 )
161 })
162}
163types/index.d.ts 8 lines1export type NextStep = string
2
3declare module 'claude-code' {
4 interface PluginState {
5 'next-steps': { steps: NextStep[]; isEnabled: boolean; isLoading: boolean }
6 }
7}
8