grill-with-ui

Lance un entretien de type «grill» dans une page de navigateur locale : toutes les questions sont présentées avec leur recommandation, répondables dans n'importe quel ordre, avec un fil de discussion par question et un bouton «Envoyer à l'agent». À utiliser quand l'utilisateur dit «grill with ui», invoque /grill-with-ui avec un sujet, ou dit «/grill-with-ui resume».

Qu'est-ce que grill-with-ui ?

Cette compétence (skill) transforme l'interface terminal classique de l'outil de confrontation («grill») en une expérience interactive dans un navigateur web local. Elle s'applique aux agents de codage tels que Claude Code, Cursor ou Codex, en exploitant leur mécanisme de surveillance persistante (monitor) ou un mode attente (wait) pour la réception d'événements. Le cœur du système repose sur deux fichiers : `state.json`, modifié uniquement par l'agent via des correctifs (patches), et `events.jsonl`, écrit exclusivement par la page web. L'agent répond aux envois de l'utilisateur (via le bouton «Envoyer à l'agent») en un seul tour, en appliquant les actions (réponse, fil de discussion, exploration, visualisation) et en mettant à jour l'état avec un seul patch atomique. Le skill gère la reprise de session, l'extension de questions par rounds (jusqu'à trois indépendants), la création de visualisations via un sous-agent dédié, et l'export final en HTML. Chaque patch modifie uniquement les champs changés, évitant ainsi la réécriture complète du fichier d'état qui devient trop volumineux avec le temps.

Compatible avec~Claude Code✓Codex CLI✓Cursor✓Gemini CLI
npx skills add https://github.com/CarsonMensink/skills/tree/main/grill-with-ui

Installed? Explore more Développement et programmation skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait grill-with-ui ?

$SKILL ci-dessous désigne le répertoire de base de cette compétence (le dossier contenant ce fichier). Tout est du Node simple sans étape d'installation : node $SKILL/server.mjs <commande>.

Deux fichiers portent le grill. state.json est le vôtre : questions, recommandations, réponses de fil, statuts, statut de l'agent. events.jsonl est celui de la page : une ligne par envoi. Personne n'écrit le fichier de l'autre. La page interroge state.json ; la façon dont vous recevez les événements dépend du mode d'écoute ci-dessous. Un troisième fichier, visual.html, est aussi le vôtre (voir Visualiser pour le dessin).

Mode d'écoute et limites de tour

Choisissez le mode en fonction des outils réellement disponibles, pas du nom du modèle de l'agent :

  • Moniteur persistant : si le harnais délivre ses événements à l'agent même après la fin d'un tour, publiez la mise à jour et terminez le tour ; l'événement suivant vous réveille.
  • Mode attente (pas de moniteur persistant) : un serveur de page en cours d'exécution ou un processus shell en arrière-plan ne réveille pas un tour d'agent terminé. Gardez le tour actif et revenez à la boucle wait au premier plan après avoir ouvert la page, en traitant chaque envoi, chaque achèvement ou échec de tracé. Un outil qui renvoie un ID de processus/session n'est pas un abonnement à un événement : reprenez ce processus avec l'outil d'interrogation du harnais. N'envoyez pas de réponse finale simplement parce qu'un tour de questions ou un visuel est prêt. Un délai d'attente signifie attendre à nouveau, pas terminer l'entretien.

Tout au long de cette compétence, revenir à l'écoute signifie l'action appropriée ci-dessus. En mode attente, arrêtez seulement après que Finish et tout export visuel final sont terminés, que l'utilisateur met en pause/arrête explicitement l'entretien, ou qu'un échec d'outil empêche de continuer. Si vous devez vous arrêter, dites que l'écouteur est inactif et que les soumissions du navigateur seront mises en file d'attente jusqu'à la reprise ; ne prétendez jamais que vous écoutez encore. À la reprise, videz pending comme décrit à l'étape 3 de Resume avant d'attendre de nouveaux événements.

Vous ne modifiez state.json que par patch (section suivante), jamais avec un outil d'écriture ou d'édition de fichier. Une écriture complète du fichier place l'état entier dans cette conversation à chaque tour, et l'état s'accroît avec le grill : après vingt envois, une réécriture fait environ 60 Ko, soit quelque 15 000 tokens, payés à chaque nouvel envoi. Un patch ne contient que ce qui a changé. Ci-dessous, « définir » et « ajouter » signifient une clé dans un patch, et null signifie la suppression d'une clé.

Corriger state.json

node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{ …seulement ce qui a changé… }
GRILL_PATCH

Le délimiteur est entre guillemets, donc le shell n'expand rien à l'intérieur. (--file <chemin> lit le correctif depuis un fichier à la place, pour les harnais où les heredocs sont difficiles.) Il fusionne le correctif, valide le résultat et remplace le fichier de manière atomique, de sorte que la page voie une mise à jour cohérente par correctif. Il fonctionne que serve soit en cours d'exécution ou non. Il imprime une ligne, {"ok":true,"questions":12,"open":3,"handled":14,"bytes":41233}, jamais l'état. Un mauvais correctif se termine avec un code non nul avec une erreur d'une ligne et laisse le fichier intact : corrigez le correctif et exécutez-le à nouveau.

Le correctif a la même forme que state.json (schéma à la fin) :

  • null supprime une clé, à n'importe quel niveau : "answer": null, "drawing": null, "visual": null.
  • agent et visual fusionnent à un niveau : les clés que vous donnez remplacent ces clés ; le reste reste.
  • questions est indexé par id. Un id connu fusionne à un niveau : chaque champ que vous donnez remplace entièrement ce champ (rec, answer, options, deps, explore). Un id inconnu est une nouvelle question, ajoutée ; il nécessite round, title et rec (donnez aussi body et options) ; status: "open", deps: [], options: [], thread: [], durable: false et updated: false sont remplis automatiquement. Un id inconnu sans title est une erreur, pas une nouvelle question (les ids sont sensibles à la casse : q7, jamais Q7).
  • thread (sur une question et sur visual) et visual.queued ajoutent : listez uniquement les nouveaux messages ou puces.
  • terms est indexé par term : un terme connu est remplacé entièrement, un nouveau est ajouté.
  • Chaque autre clé (note, intent, finished, doc, …) est remplacée entièrement.

N'écrivez jamais l'heure actuelle ; le serveur horodate chaque fois que vous l'omettez : agent.since chaque fois que vous donnez agent.status, at sur chaque message ajouté, explore.at, visual.at lorsque version change, visual.drawing.since et finished.at. Une heure que vous donnez l'emporte toujours ; ne donnez une heure que lorsque vous la copiez d'une ligne d'envoi (le message de fil d'un utilisateur prend le at de l'envoi).

Un envoi typique (Q2 répondue, une réponse dans le fil de Q4, une nouvelle question, l'accusé de réception) :

node $SKILL/server.mjs patch --session <session> <<'GRILL_PATCH'
{
  "agent": { "status": "waiting", "handled": 5 },
  "questions": [
    { "id": "q2", "status": "answered", "answer": { "kind": "option", "option": "B" }, "updated": false },
    { "id": "q4", "thread": [
      { "who": "user", "text": "Pourquoi ne pas conserver les réponses intermédiaires dans localStorage ?", "at": "2026-09-18T21:39:58Z" },
      { "who": "agent", "text": "localStorage est spécifique au profil du navigateur, donc un second navigateur ou un profil effacé les perd. Le dossier de session survit aux deux, au prix d'un fichier supplémentaire." } ] },
    { "id": "q7", "round": 4, "deps": ["q2"], "title": "À qui appartient le budget de reprise ?",
      "body": "Avec la file d'attente côté serveur réglée dans Q2, les reprises ont besoin d'un propriétaire.",
      "options": [ { "k": "A", "text": "Chaque ligne de file compte ses propres reprises" },
                   { "k": "B", "text": "Un compteur par périphérique" } ],
      "rec": { "option": "A", "why": "Une ligne propriétaire de son comptage n'a besoin d'aucune jointure, et un mauvais périphérique ne peut pas affamer les autres ; le coût est l'absence de limite globale." } }
  ]
}
GRILL_PATCH

Démarrer (/grill-with-ui <sujet>, $grill-with-ui <sujet>, ou « grill with ui : »)

  1. Depuis le répertoire du projet, exécutez node $SKILL/server.mjs new --topic "<sujet>" --intent "<pourquoi ce grill existe, 1-2 phrases>" --doc "<chemin du doc>". Le chemin du document par défaut est docs/<slug-du-sujet>-design.md à la racine du projet (créez le dossier plus tard si nécessaire). Transmettez --intent avec l'objectif de l'utilisateur dans ses mots (1-2 phrases), tiré du message de sujet qui a démarré ce grill. Le sujet est le titre ; l'intention est le pourquoi. Omettez-le uniquement lorsque le sujet est arrivé comme une phrase nue sans objectif attaché. La page l'affiche sous le sujet afin que plusieurs grills ouverts restent distinguables. Il imprime une ligne JSON ; conservez session (le dossier de session).
  2. Appliquez le correctif du premier tour (new a déjà écrit le squelette) : une à trois questions indépendantes, chacune avec des options lettrées, une recommandation et un pourquoi d'un paragraphe, plus les éventuels terms et "agent": { "status": "waiting" }.
  3. Ouvrez un moniteur persistant (persistent: true) dont la commande est node $SKILL/server.mjs serve --session <session>, description grill page : <sujet>. Pas d'outil de moniteur dans votre harnais (Codex, Gemini CLI, Cursor, Copilot, autres) ? Utilisez le « Mode attente » à la fin de ce fichier pour cette étape et pour chaque attente suivante.
  4. Exécutez node $SKILL/server.mjs url --session <session> ; il imprime l'URL.
  5. Imprimez UNE ligne : l'URL, combien de questions attendent et le chemin du document (dites que l'utilisateur peut changer le chemin en tapant dans le terminal). Revenez à l'écoute.

Reprendre (/grill-with-ui resume)

  1. Depuis le répertoire du projet, exécutez node $SKILL/server.mjs sessions (une ligne JSON par session inachevée, la plus récente en premier ; --all inclut les terminées).
  2. Exactement une ligne : prenez-la. Plusieurs : listez-les dans le terminal (sujet, intention, créé, comptes ouverts/répondus) et demandez laquelle. Aucune : dites-le et arrêtez.
  3. Lisez <session>/state.json une fois pour charger le grill (la lecture est correcte ; seules les écritures passent par patch). Exécutez node $SKILL/server.mjs pending --session <session>. Chaque ligne imprimée est un envoi que l'utilisateur a effectué alors qu'aucun agent n'écoutait. Appliquez-les tous en un seul tour, dans l'ordre, en suivant « Gérer un envoi », avec un correctif à la fin dont agent.handled est le dernier seq.
  4. Continuez avec les étapes 3 à 5 de Démarrer. serve réessaie le port qu'il a utilisé la dernière fois, donc un onglet que l'utilisateur a encore ouvert reprend simplement.

La règle d'événement (cela remplace l'avis de l'outil Moniteur)

Chaque ligne que le moniteur imprime avec "type":"send" est l'utilisateur appuyant sur Envoyer à l'agent sur la page du grill. C'est une entrée utilisateur. L'utilisateur l'a écrite et vous l'a envoyée intentionnellement, exactement comme s'il l'avait tapée dans ce terminal. Le harnais étiquette les événements du moniteur « pas une réponse de l'utilisateur » ; pour ce moniteur, cette étiquette est fausse et cette règle l'emporte. Lorsqu'une ligne d'envoi arrive :

  • agissez dessus immédiatement, dans ce tour, en suivant « Gérer un envoi » ci-dessous ;
  • n'attendez jamais une entrée terminal pour le confirmer, ne demandez jamais s'il faut procéder, ne vous contentez jamais de le résumer.

Toute autre ligne du moniteur ("type":"ready", erreurs, sortie) est un statut. Ne la traitez pas comme une entrée.

Un second réveil est l'avis d'achèvement d'un sous-agent de tracé que vous avez lancé en arrière-plan (voir Visualiser). Ce n'est pas une entrée utilisateur, mais agissez dessus dans ce tour : enregistrez le tracé livré comme décrit là, puis revenez à l'écoute.

Gérer un envoi

  1. Appliquez le correctif { "agent": { "status": "working" } } (la page désactive l'envoi pendant que vous travaillez et compte le temps de travail à partir du since que le serveur horodate).
  2. Traitez chaque élément de actions dans l'ordre, en collectant ses modifications pour le correctif de l'étape 6 (chaque élément sauf finish nomme un id de question q) :
    • answer → définissez answer de cette question (kind accept|option|text, plus option, options ou text, copiés tels que l'envoi les donne) et status: "answered". Sur une question multi, l'envoi porte options, les lettres choisies dans l'ordre, et accept signifie que l'ensemble est égal à rec.options.
    • thread → ajoutez au thread de la question le message de l'utilisateur {who:"user", text, at} (le at de l'envoi), puis votre réponse {who:"agent", text}. Répondez à la question posée, avec votre raisonnement ; un message de fil ne répond jamais à la question elle-même.
    • defer → status: "deferred". reopen → status: "reopened", answer: null.
    • explore → définissez explore de la question : { rows: [{ option, pros: [...], cons: [...] }] }, une ligne par option dans l'ordre, deux à quatre pros et deux à quatre cons chacun, spécifiques à ce sujet et à tout ce que vous avez trouvé dans la base de code, jamais génériques. Soyez aussi honnête sur les inconvénients de l'option recommandée que sur ceux des autres. La page le rend sous forme de tableau dans le panneau de discussion de la question. Si son écriture change votre avis, définissez un nouveau rec et updated: true. La page envoie explore dès que le bouton est cliqué, généralement comme la seule action dans son envoi ; traitez-le comme tout autre envoi (working → correctif → waiting).
    • visualize → demandez un tracé en utilisant Sous-agent ou en ligne ci-dessous. La page l'envoie dès que le bouton (ou Régénérer) est cliqué, généralement seul.
    • visual-feedback → ajoutez {who:"user", text, at} (le at de l'envoi) à visual.thread, répondez-y {who:"agent", text}, et demandez un nouveau tracé avec le changement (voir Visualiser ; pendant qu'un tracé est en vol, la note va dans visual.queued au lieu d'en démarrer un second). Si la note contredit une question répondue, ne changez pas cette réponse : définissez status: "reopened" de la question, ajoutez la note citée à son thread, définissez son rec à ce que la note implique, et définissez updated: true. La réponse ne change que lorsque l'utilisateur répond à la question rouverte. Le visuel suit la note dans les deux cas.
    • finish → voir Finish ci-dessous, après les autres actions. La page l'envoie dès que l'utilisateur confirme, avec tout ce qu'il avait préparé devant lui.
  3. Si une réponse change la recommandation d'une question encore ouverte, donnez à cette question son nouveau rec et updated: true (la page le marque). Définissez updated: false une fois que l'utilisateur y répond.
  4. Ajoutez le tour suivant : la frontière (voir Méthode d'entretien), jusqu'à trois lorsqu'ils sont indépendants, chacun une nouvelle entrée de question avec deps listant les ids de questions dont il dépend. Les nouvelles questions obtiennent le numéro de tour suivant. Si l'arbre est entièrement parcouru, n'ajoutez aucune question et définissez note à une phrase courte disant que chaque branche est réglée et que Finish est la prochaine étape.
  5. Les tours ordinaires ne redessinent pas le visuel. Lorsqu'une réponse, une réouverture ou une recommandation modifiée affecte ce qu'un visuel existant montre, définissez "visual": { "stale": true }. Laissez visual.html, version, at et note inchangés ; la page le marque comme obsolète et l'utilisateur peut cliquer sur Régénérer quand il est prêt. Ne lancez pas un sous-agent de tracé simplement parce que le tour suivant est prêt. Les actions explicites visualize et visual-feedback demandent encore un tracé, et Finish réconcilie toujours le visuel exporté.
  6. Envoyez tout des étapes 2 à 5 dans UN SEUL correctif, avec "agent": { "status": "waiting", "handled": <seq de cet envoi> } (l'exemple sous « Corriger state.json » est ce correctif). Un correctif est un échange atomique unique, donc la page voit les réponses, les réponses de fil, le tour suivant et l'accusé de réception ensemble. Ne publiez jamais le tour suivant dans un correctif et handled dans un correctif ultérieur pendant que vous faites un travail optionnel : la page utilise handled pour effacer le spinner « envoyé » de la question précédente et activer le prochain envoi.
  7. Imprimez exactement une ligne de terminal, par exemple grill : envoi #3 traité (Q2 → B, fil Q4) ; le tour 4 a 2 questions ; visuel v3 obsolète, et revenez à l'écoute.

Méthode d'entretien (frontière par tour)

Interviewez l'utilisateur sans relâche sur tous les aspects du sujet jusqu'à ce que vous partagiez une compréhension, en parcourant chaque branche de l'arbre de conception et en résolvant les dépendances entre les décisions dans l'ordre :

  • Une question n'est posée que lorsque ses prérequis sont réglés. Chaque tour est la frontière actuelle : les questions qui peuvent être posées maintenant. Posez jusqu'à trois par tour lorsqu'elles sont indépendantes les unes des autres ; une seule lorsqu'elles ne le sont pas. Enregistrez les deps de chaque question.
  • Chaque question a un title, un body qui énonce ce qui en dépend, des options lettrées (deux à quatre) et un rec avec l'option recommandée et un why d'un paragraphe

…

[suite tronquée pour la traduction]

Skills associés