Vidéo de démonstration
Transforme une fonctionnalité en une courte vidéo : des interactions réelles dans un vrai navigateur, une voix off qui explique ce qui est affiché à l'écran, et des sous-titres. Tout s'exécute en local : agent-browser enregistre, Kokoro parle, ffmpeg et Pillow assemblent.
L'idée unique qui fait fonctionner le tout : la narration est l'horloge. Générer d'abord la voix off, mesurer chaque scène, et déclencher chaque action du navigateur au moment du mot qui en parle. Ne jamais caler la narration sur un enregistrement ; le rythme d'un enregistrement dérive, celui d'un WAV non. L'enregistrement lui-même n'attend pas la narration : les actions s'exécutent en continu, et l'assemblage fige l'image là où la narration a besoin de plus de temps.
Outils
Les scripts de ce skill se trouvent dans le dossier scripts/ sous son répertoire de base, celui indiqué comme « Répertoire de base pour ce skill » lors de son chargement ; le scénario d'exemple se trouve dans references/ à cet endroit. Chaque commande shell démarre dans un environnement vierge, donc une variable exportée dans l'une est perdue dans la suivante : commencez chaque commande qui en a besoin par export SCRIPTS=<base directory>/scripts DEMO=<demo directory>; (ci-dessous), comme ici pour la vérification préalable :
export SCRIPTS=<base directory>/scripts; bash $SCRIPTS/preflight.sh
Elle vérifie la présence d'agent-browser et d'un Chrome compatible, ffmpeg/ffprobe avec libx264, ainsi que l'environnement Python et le modèle de Kokoro : soit préinstallés (DEMO_VIDEO_VENV est défini), soit uv (qui exécute Kokoro et Pillow sur Python 3.12 quel que soit le Python système) plus l'accès à Hugging Face. Si elle se termine avec un code non nul, arrêtez-vous : dites à l'utilisateur ce qui manque et comment l'installer, et ne commencez pas le script, les données ou l'enregistrement. Ne remplacez pas le moteur TTS, l'enregistreur ou la vidéo silencieuse sans demander — le résultat ne correspondrait pas à ce qui a été demandé. Ses avertissements (absence d'espeak-ng, de police système) ne bloquent pas ; mentionnez-les.
$SCRIPTS/python exécute les scripts Python ci-dessous dans l'environnement que le uv.lock du skill verrouille ; les scénarios lisent aussi $SCRIPTS. Conservez tous les fichiers de travail dans un seul répertoire de démonstration (DEMO, un chemin absolu) dans le scratchpad, en dehors du dépôt. Si vous ne pouvez écrire qu'à l'intérieur du dépôt, ou si la vidéo doit être attachée à une pull request depuis celui-ci, utilisez plutôt un dossier du dépôt que git ignore : tmp/demo-video/<nom> quand git check-ignore -q tmp/demo-video réussit, sinon un autre dossier dont il confirme qu'il est ignoré, afin que rien ne puisse être commité. Ne modifiez pas .gitignore pour cela sans demander.
Flux de travail
1. Comprendre la fonctionnalité et choisir l'histoire
Lisez le diff de la branche, le plan ou la description de la PR, et le texte visible par l'utilisateur de la fonctionnalité (fichiers i18n, infobulles, texte d'aide). Les infobulles définissent généralement les colonnes et les options mieux que tout autre chose — la narration doit s'accorder avec elles mot pour mot sur la signification d'un nombre.
Choisissez un protagoniste (une entreprise fictive qui utiliserait vraiment la fonctionnalité) et une question à laquelle la fonctionnalité répond pour elle. Une démo qui parcourt un scénario concret, de la question à la réponse, est plus efficace qu'une visite guidée de tous les contrôles.
Ne demandez à l'utilisateur que ce qui modifie la vidéo : le public (clients ou équipe), la durée cible, la voix, et s'il souhaite relire le script avant tout enregistrement. Valeurs par défaut : clients, ~3 minutes, af_heart. Ignorez les questions auxquelles l'utilisateur a déjà répondu ou qui sont devenues sans objet — quelqu'un qui a demandé le processus complet de bout en bout sans vérification, ou qui n'est clairement pas disponible pour répondre, reçoit les valeurs par défaut et aucune relecture du script.
2. Créer des données de démo qui racontent l'histoire
Des données vides ou aléatoires donnent l'impression que la fonctionnalité est cassée. Construisez des données dont les chiffres rendent chaque point évident à l'écran : des cas limites visibles dès le premier écran, des totaux qui changent visiblement lorsqu'une option est modifiée.
- Isolez-les. Initialisez une base de données séparée et exécutez l'application sur son propre port ; ne réutilisez jamais les données de développement de l'utilisateur. Cherchez d'abord les conventions existantes du projet (CLAUDE.md, guides sur les worktrees et les serveurs de développement, répertoires de seeds). Si le projet dispose d'un framework de seeds, écrivez-y une graine de démo réutilisable et spécifique au secteur (par exemple
db/seeds/demos/bike_shop.rb) plutôt qu'un script unique — la prochaine démo ne coûtera alors rien. Demandez avant de le commiter. - Datez tout par rapport à aujourd'hui, afin que la démo ne devienne jamais obsolète.
- L'historique a besoin de voyage dans le temps. Les applications refusent de créer des commandes ou des réservations dans le passé ; créez chaque enregistrement au moment où il aurait vraiment été créé (par exemple
travel_tode Rails). Méfiez-vous des états qui ignorent l'horloge décalée : les valeurs mémorisées sur une instance chargée avant le voyage, et les colonnes de statut calculées à la création. Rechargez et rafraîchissez-les à l'intérieur du bloc de voyage. - Instantané du schéma vide (par exemple
createdb -T) afin que la réinitialisation après une correction soit un drop-copy, pas une migration. - Ensuite, ouvrez la page et vérifiez chaque chiffre que vous prévoyez de mentionner.
3. Écrire le script
Écrivez DEMO/scenes.json : [{"id": "01_intro", "text": "..."}, ...]. Une scène par idée, 10 à 15 scènes. Les identifiants se trient dans l'ordre de lecture.
- Longueur : Kokoro parle à environ 150 mots par minute, et chaque scène ajoute environ 1 à 3 secondes d'actions et de pauses. Pour 3 minutes, écrivez environ 450 mots ; 420 mots ont donné 2:40.
- Dites ce que le spectateur voit, dans l'ordre où il le voit. Nommez les contrôles par leur libellé à l'écran.
- Écrivez pour l'oreille : phrases courtes, nombres épelés là où ils se lisent étrangement (« vingt-cinq pour cent »).
- Suivez les règles de vocabulaire du projet (glossaires, mots réservés) dans la narration comme dans le code.
- Nommez ce qui est à l'écran, quand il est à l'écran. Ne nommez pas une valeur de filtre, une option de menu ou un enregistrement avant que le spectateur puisse le voir (« tout ce que Sam a vendu » alors que la liste du personnel est encore fermée). Placez les mots d'ouverture du menu avant le nom, afin que le scénario puisse d'abord ouvrir le menu et positionner le pointeur sur l'option au moment où elle est prononcée.
- Commencez par la question propre à la fonctionnalité. La première phrase doit être une question à laquelle seule cette fonctionnalité répond. « Combien avons-nous vendu ce mois-ci ? » décrit aussi un rapport voisin ; « Quel magasin a vendu le plus de kits de démarrage ? » non. Vérifiez les fonctionnalités sœurs de l'application (autres rapports, autres pages de paramètres) avant de vous arrêter sur cette question.
- Écrivez pour le public indiqué. Une démo client montre ce qu'ils peuvent faire ; une démo interne pour le marketing ou la gestion de compte couvre aussi comment cela décide, comment activer la fonctionnalité, ses limites, et quoi dire à un client qui demande.
Si l'utilisateur a demandé à relire le script, arrêtez-vous ici et montrez-le avant de générer l'audio : d'abord un bref plan (une ligne par scène : ce qui est montré et l'affirmation unique qu'elle fait), plus toutes les affirmations dont vous n'êtes pas sûr, avec le texte complet ci-dessous pour ceux qui le souhaitent. Modifier une phrase plus tard signifie re-générer la scène, re-synchroniser le scénario et ré-enregistrer la prise, c'est donc le moment le moins coûteux pour changer l'histoire. Sinon, continuez ; mentionnez dans le rapport final que le script se trouve dans DEMO/scenes.json.
4. Générer la voix off
export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/narrate.py $DEMO
Écrit tts/<id>.wav et tts/<id>.words.json avec les horodatages par mot, et affiche la durée de chaque scène. Re-générez une seule scène avec --only <id> .... Voix : af_heart (la plus naturelle US), am_michael, bf_emma (britannique). La première lettre de la voix détermine la langue.
5. Simuler chaque interaction
Avant l'enregistrement, exécutez chaque étape une fois avec agent-browser et prenez une capture d'écran. Cela permet de détecter ce qui cassera devant la caméra : mauvaises routes, éléments cachés par des superpositions, menus qui se soumettent à la fermeture, mise en page qui se décale lorsqu'un badge apparaît. Les références d'instantanés (@e12) changent à chaque chargement de page, utilisez donc des sélecteurs CSS stables (ids, dom_ids, name=) dans le scénario. Préférez-les à demo.click_role : les aides glissent le pointeur vers l'élément d'un sélecteur avant d'agir, mais agent-browser ne donne pas de position pour un rôle, donc un clic sur un rôle fait sauter le pointeur.
Masquez les superpositions réservées au développement (profileurs, barres de débogage) et effacez les notifications uniques telles que « Connecté en tant que… » en chargeant la page deux fois avant le début de la prise (demo.prime(url)).
6. Écrire le scénario et enregistrer
Un scénario est un court script Python dans DEMO utilisant SCRIPTS/recorder.py. Commencez à partir de references/example_scenario.py. Synchronisez les actions avec les mots en utilisant s.at("mot"). La ponctuation est un jeton séparé, donc synchronisez sur "Octobre" et non "Octobre,", et utilisez nth= lorsqu'un mot apparaît deux fois.
Utilisez uniquement les aides pour les déplacements. Les commandes brutes de agent-browser pour le pointeur téléportent celui-ci et une molette de souris fait sauter la page, ce qui donne l'impression d'une coupe à l'écran. demo.move, demo.hover et demo.click font glisser le pointeur et reviennent une fois arrivés ; demo.scroll anime le défilement et revient une fois la page stable. Ainsi, chaque mouvement est enregistré intégralement, avant que la pose suivante ne fige une image.
Laissez le spectateur lire chaque menu. Une liste qui s'ouvre et se ferme en une seconde ne montre rien. Utilisez les aides, qui s'attardent sur la liste ouverte et posent le pointeur sur l'option avant de la choisir :
demo.pick(open_trigger, option_selector)pour les menus HTML personnalisés.- Passez
scene=s, cue="<mot>"à l'une ou l'autre aide pour poser le pointeur sur l'option exactement au moment où le narrateur la nomme ; le menu s'ouvre environ deux secondes avant. Cela nécessite deux secondes de narration avant le mot de repère dans la même scène, sans action lente entre les deux — l'assemblage affichewarning: … runs …s latequand ce n'est pas possible, et la correction est dans le script (« Le troisième choix de ce menu le divise… » plutôt que « Ou le diviser… ») ou en déplaçant l'action lente vers la scène précédente. demo.pick_native(select_selector, label)pour un<select>natif. Un enregistrement sans tête ne dessine jamais la popup d'un select natif, donc cette fonction dessine une liste de substitution à partir des vraies options du select, pointe sur le choix, puis le sélectionne.
Une seule zone d'affichage par prise. Une prise dont la zone d'affichage change en cours d'enregistrement donne un résultat déformé. Enregistrez une séquence mobile comme sa propre prise (demo.viewport(390, 844)) ; l'assemblage l'adapte à un canevas. Une courte prise de conclusion après cela revient au bureau.
Enregistrez chaque prise : export SCRIPTS=… DEMO=…; python3 -I $DEMO/scenario.py desktop (et phone, outro, …). L'enregistrement n'est pas chronométré : s.at() et s.until() ne mettent pas en pause mais enregistrent un point de pose, donc la prise ne dure que le temps de ses actions (une vidéo de 2 minutes s'enregistre en moins d'une minute) et un sélecteur cassé ou un mot de repère absent de la narration échoue en quelques secondes. Cela rend une simulation séparée inutile ; itérez directement sur les prises. Chaque prise écrit <take>.mp4 et <take>.holds.json ; l'assemblage fige l'image à chaque point de pose jusqu'à ce que la narration atteigne son mot de repère. Si une étape échoue en cours de prise, corrigez le scénario et ré-exécutez toute la prise ; les poses doivent provenir d'une exécution propre.
Chaque action doit laisser la page stable avant le prochain repère, car c'est cette dernière image qui est figée : conservez le time.sleep après un rechargement ou un menu qui se soumet, sinon une image figée montre une page à moitié chargée. Tout ce qui s'exécute avec sa propre horloge (un toast qui disparaît, un spinner, un compte à rebours) n'avance que pendant que le scénario agit, donc une pose le fige à mi-chemin. Pour le garder hors caméra, attendez qu'il soit terminé avant le repère suivant, comme le fait prime pour les notifications de connexion. Pour en montrer un, comme un toast « Enregistré » qui apparaît et disparaît, placez un time.sleep après l'action qui couvre tout l'effet, afin qu'il se joue avant la pose suivante. Pour une page qui s'anime en continu au repos, enregistrez cette prise avec DEMO_TIMED=1, qui attend chaque mot de repère tout en enregistrant et écrit <take>.timeline.json à la place.
7. Assembler
Fermez d'abord le navigateur (agent-browser close) : Chrome et l'encodeur ffmpeg ensemble peuvent dépasser la mémoire d'une petite machine ou d'un conteneur.
export SCRIPTS=… DEMO=…; bash $SCRIPTS/python $SCRIPTS/assemble.py $DEMO --takes desktop phone outro --out <nom>
Produit DEMO/build/<nom>.mp4 avec sous-titres incrustés, une piste de sous-titres logicielle et la narration, plus <nom>.srt.
8. Vérifier avant de remettre
Vous pouvez voir la vidéo mais pas l'entendre. Vérifiez ce que vous pouvez :
export DEMO=…; ffmpeg -v error -y -i $DEMO/build/<nom>.mp4 -vf "fps=1/6,scale=480:-1,tile=4x8" -frames:v 1 $DEMO/build/grid.png # lire
export DEMO=…; ffmpeg -i $DEMO/build/<nom>.mp4 -map 0:a -af volumedetect -f null - 2>&1 | grep volume # non silencieuse
Dans la grille, confirmez que chaque scène montre ce que sa narration décrit, que les sous-titres ne cachent jamais l'élément pointé, et qu'aucune image ne capture une page à moitié chargée ou une erreur. Capturez des images individuelles aux moments des repères (-ss <t>) pour les instants importants.
Ensuite, arrêtez le serveur de développement et remettez la vidéo : sur la machine de l'utilisateur, copiez-la à un endroit où il peut la trouver et open celle-ci ; ailleurs, donnez son chemin, ou attachez-la à la pull request si l'un de vos outils le fait. Lorsque vous faites votre rapport, dites ce que vous n'avez pas pu vérifier (audio à l'oreille) et listez les raccourcis pris, comme un menu de substitution.
Pièges déjà rencontrés
agent-browser clickéchoue avec « covered by » lorsqu'une page n'est pas encore stable.recorder.abréessaie ; un échec qui survit aux tentatives est réel, corrigez donc le sélecteur.- Les menus qui se soumettent à la fermeture, comme les listes de cases à cocher qui se soumettent sur Échap, prennent quelques secondes pour se recharger. Attendez avant le clic suivant, sinon celui-ci atterrit sur un bouton déplacé.
- Overmind refuse de démarrer sur un
.overmind.sockobsolète. Supprimez le socket seulement après avoir confirmé que rien n'y répond (overmind status). - Les longs chemins du scratchpad dépassent les limites des sockets Unix ; n'y pointez pas les sockets.
- Kokoro divise les longs textes en morceaux ;
narrate.pydécale les horodatages de chaque morceau, donc synchronisez par mot, jamais par secondes estimées.