Vidéo produit (et les assets qui l'entourent), de bout en bout
Vous produisez un asset produit expédiable — une vidéo soignée, un ensemble
d'images fixes aux dimensions exactes, ou les deux — à partir d'une page web
en direct, avec l'interface en ligne de commande vos.
Tout est une donnée : vous écrivez un script de flux, le CLI enregistre et planifie, vous
ajustez du JSON, vous refaites le rendu. Ne ré-enregistrez jamais pour corriger le rythme ou les zooms — modifiez
doc.json et refaites le rendu. Barre de qualité : references/taste.md — suivez
sa boucle de qualité et jugez les images fixes de manière multimodale.
Configuration
npm i -D @vosjs/cli
Une commande, un paquet : @vosjs/cli est le binaire open source (MIT) vos
avec les verbes du moteur, le pipeline de prise utilisé ici (record / plan /
frames / render sur les enregistrements d'écran) et les verbes de la plateforme vos.so. (Jusqu'à la
version 0.9, le pipeline de prise était distribué séparément sous le nom @vosso/vos-plugin ; un projet
qui le liste encore peut le supprimer.) Prérequis :
- Un Chromium : le Chrome système est trouvé automatiquement ; sinon
npx playwright install chromiumou pointezVOS_BROWSER_PATHvers un. Le code de sortie 3 signifie qu'aucun navigateur n'a été trouvé. - Réseau au moment du rendu : la page de rendu charge three/mediabunny depuis esm.sh. L'enregistrement a également besoin d'atteindre l'URL cible. Les bacs à sable hors ligne ne peuvent pas faire de rendu — dites-le plutôt que de ne rien livrer.
Conventions : logs → stderr, résultats → stdout ; --json diffuse du NDJSON se terminant
par {"event":"done",…} ; codes de sortie 0 ok / 1 erreur / 2 utilisation ou échec
strict / 3 pas de navigateur / 4 l'enregistreur a rencontré une page de connexion au lieu de la page
(un SESSION manquant ou expiré, jamais un bug de script : references/sessions.md).
vos <verbe> --help affiche les flags de ce verbe (@vosjs/cli 0.41.1 et ultérieur).
Étape 0 — choisissez la destination (elle décide de tout)
| Destination | Viewport | Sortie | Extras |
|---|---|---|---|
| Clip pour page d'accueil (intégration hero/section) | 2560×1440 (footage natif 2K) | webm → re-encodage VP9 + affiche | boucle silencieuse ; voir references/destinations.md |
| Vidéo de lancement (PH, social, promo store) | 2560×1440 ou 1280×720 | mp4 (--format mp4, nécessite Chrome système) | fond musical via doc.audio |
| Images fixes du kit de lancement (captures d'écran store, tuiles, OG) | dimensionnées pour l'asset | PNGs frames --frame <t> --size LxH à partir de la même prise | une prise → chaque asset |
| Démo rapide (issue, PR, chat) | 1280×720 | webm, valeurs par défaut | rapidité avant le polissage ; les brouillons sont acceptables UNIQUEMENT ici |
Dimensions par canal et budgets d'octets : references/destinations.md.
La boucle principale (chaque destination)
-
Explorez la page cible avec vos propres outils (fetch HTML / Playwright). Identifiez les 3 à 6 moments qui racontent UNE histoire. Collectez des sélecteurs STABLES (
a[href='…'], ids, rôles — pas des chaînes nth-child). Mettez le contenu en scène comme un plateau : le script doit laisser le produit dans l'état qu'une fière capture d'écran montrerait — étiquettes tapées, données réalistes, la fonctionnalité en plein récit. Un canevas vide s'enregistre vite et ne fait pas de démo, et aucune composition en aval ne le sauve.Derrière une connexion ? Réglez la session avant le script. Un enregistreur sans session enregistre le mur (la page de connexion, ou la page publique que le site envoie à un inconnu) et le seul symptôme est un sélecteur sauté. Parcourez l'échelle dans
references/sessions.mdde haut en bas et arrêtez-vous au premier barreau qui tient : pas de mur (un mode démo, un serveur local avec auth désactivée) → CRÉEZ une session à partir de l'auth de test que le projet a déjà (playwright/.auth, unauth.setup.ts, un script d'amorçage : regardez avant de demander à qui que ce soit) → connectez-vous hors caméra avecsetupdansactions.json(le mot de passe depuis{ "env": "NOM" }, jamais un littéral) → l'humain se connecte une fois (vos session open <url> --name <app>, puis--session <app>sur record) → l'humain enregistre avec l'extension depuis la liste de plansvos actions script actions.jsonimprime, et vous le montez. Chaque barreau se termine parsetup, un fichier d'état pour--storage-state, ou une session nommée pour--session. Ne tapez jamais ni n'acceptez un mot de passe de production, gardez le fichier d'état en dehors du répertoire de prise et de git, et enregistrez à partir d'un compte de démo : ce que le compte montre est livré dans la vidéo.Vous avez déjà vérifié la fonctionnalité avec agent-browser ? Gardez cette navigation et sautez le second script. Le résultat
--jsond'agent-browser ne dit pas ce qui a été exécuté (scrollrépond{scrolled:true}), donc enveloppez chaque appel pour que la commande chevauche son résultat, puis convertissez :ab() { agent-browser "$@" --json | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{const r=JSON.parse(s);process.stdout.write(JSON.stringify({command:process.argv.slice(1),...r})+"\n")})' -- "$@" >> steps.jsonl; } ab open https://target.example; ab snapshot -i -u; ab click @e27 ab wait 800; ab snapshot -i # la page a changé : les refs sont renumérotées ab fill @e2 query; ab press Enter vos actions from-agent-browser steps.jsonl --out actions.json(un chemin entier exécuté comme un seul
agent-browser batch … --json > steps.jsonla déjà cette forme). Le journal eststeps.jsonldans le répertoire où vous vous tenez, donc naviguez depuis un seul répertoire. Les refs se résolvent via la dernièresnapshot -iavant elles, et elles sont RENUMÉROTÉES après une navigation et à nouveau dans une boîte de dialogue : refaites un snapshot après tout ce qui change la page et lisez les refs avant d'en nommer une (-udonne aux liens leur href). Cliquez sur le contrôle, n'utilisez pas son raccourci : ⌘K ouvre la boîte de dialogue pour un humain, mais une frappe est la seule étape que l'enregistreur ne peut pas rejouer. Tout ce qu'il ne peut pas suivre (une touche de raccourci, un glisser-déposer, un secondopen) est NOMMÉ dans la sortie, jamais supprimé : lisez les notes, écrivez ces étapes à la main, puis enregistrez. -
Écrivez
actions.json:{ "url": "https: "viewport": { "width": 1280, "height": 720 }, "steps": [ { "do": "wait", "ms": 800 }, { "do": "hover", "selector": "a[href='/pricing']", "ms": 700 }, { "do": "click", "selector": "#cta" }, { "do": "wait", "ms": 1500 }, { "do": "scroll", "dy": 400 }, { "do": "move", "x": 640, "y": 320 }, { "do": "wait", "ms": 900 } ] }Verbes :
waithoverclicktypescrollmovedrag(type={do:'type', selector, text, delayMs?, ms?, focus?}: il clique sur le champ, puis tapetext;focus:falsetape dans ce qui est déjà focus, pour un Entrée de soumission) (drag = modifications réelles :{do:'drag', selector|x,y, tx, ty, ms}— glissez un curseur de plage, faites glisser un élément canvas, déplacez un clip de timeline). Le RYTHME EST le plan de zoom : ouvrezwait ≥700ms; survolez ce qui compte 700–900ms (les temps de pause deviennent des zooms) ; lemsd'un clic est le temps de LECTURE, mesuré à partir du dernier changement de la page (@vosjs/cli0.47 et ultérieur ; l'enregistreur paie le stabilisateur lui-même), donc dimensionnez-le comme un temps : environ 1000ms après une navigation, 600ms après un contrôle, jamais prolongé pour une page lente ; terminez stable. Éloignez le curseur des menus déclenchés au survol (taste.md, règles de flux). Vérifiez avecvos validate actions.json.Ensuite, RÉPÉTEZ-LE (
@vosjs/cli0.39 et ultérieur) :vos record --actions actions.json --out take --dry-run. Chaque étape s'exécute sur la vraie page, dans l'ordre, car un sélecteur ultérieur n'existe généralement qu'après un clic précédent, mais rien n'est capturé et rien n'est écrit : un sélecteur manqué est nommé en secondes (exit 2) au lieu d'être après une prise en temps réel et son encodage, et une prise déjà dans--outconserve ses images et son montage. Il imprime le rect de chaque étape en px de capture, ce qui est ce qu'une couche épinglée lit. Répétez après chaque modification du script, et n'enregistrez qu'un script qui réussit. Un produit connecté se répète de la même manière :--storage-stateet--browser-arg=s'y appliquent aussi, et la répétition se termine en listant ce que le cadre EXPOSE (une adresse, une clé, une carte). Masquez-les avecmaskavant d'enregistrer :references/sessions.md. -
Enregistrez :
vos record --actions actions.json --out take --strict --json--stricttoujours : sélecteur sauté / timeout networkidle → exit 2 avecskipped[]dans l'événement done. Un saut signifie que le flux est cassé — corrigez-le, ne le contournez jamais. La prise s'auto-encode et s'auto-planifie. (vos create --actions actions.json out.webm --strictest le verbe enregistrer+rendu en un coup — bon pour un premier passage rapide, mais la boucle DE CE skill examine les images avant le rendu, donc préférez les verbes séparés ici.) -
Ajustez
doc.json(le schéma JSON est livré dans le paquet npm@vosjs/cli:schema/doc.schema.json) :zoom:[{in, out, level, cx, cy, source}], SOURCE en secondes ; niveaux 1.4–2.8 ;cx/cyNORMALISÉS [0..1] (0.5,0.5 = centre) — PAS des pixels ; définissez"source": "manual"sur les plages que vous touchez (survit à un re-plan).segments(recadrages) ·speed(rate0.1–16) ·frame.*·cursor.tilt:[{in, out, rx, ry, source}], SOURCE en secondes — la carte 3D s'incline dans la pose lorsqu'elle est active, revient au repos entre les deux. DEGRÉS (±5..18 se lit premium) : +rx = bord supérieur plus proche, +ry = bord gauche plus proche (s'incliner vers un focus à droite =rynégatif). Plages ≥ 0,8 s ; associez à des moments de zoom (les mêmes entrées/sorties enchaînent les mouvements), un changement de pose par ~5 s de temps."source": "manual"sur les plages que vous touchez ;tiltStyle: "subtle"|"medium"|"strong"enregistre la baguette automatique.frame.backgroundMedia: une boucle vidéo / image derrière la carte —{"kind":"video","key":"/bg.webm","duration":10,"dim":0.2}.key= un fichier déposé dans le répertoire de prise ("/bg.webm") ou une URL de média ; la vidéo a besoin deduration(ancré à la SORTIE modulo boucle) ;dim0..1 écran. Ambiance, pas un sujet — atténuez-le derrière une UI dense.audio: clips ancrés à la SORTIE ;keypeut être un fichier déposé dans le répertoire de prise ("/music.mp3") ; gain/fades/boucle. Mixé sur les rendus complets (Opus/AAC) ;--rangereste silencieux ; force le vol simple.- export :
{"resolution": "720p|1080p|2k|4k", "fps": 30}— jamais au-dessus du footage (validate avertit). Ensuitevos validate take --json— les vérifications lint doivent réussir.
-
Regardez avant de rendre (la boucle de qualité taste.md) :
vos frames take --at-zooms --times 0,25%,50%,75%,100% --json→ jugez chaque image fixe par rapport à taste.md, les apex de zoom le plus sévèrement.- Itérez : modifiez doc.json →
vos render take check.webm --range a..b --draft(secondes, demi-résolution — ne livrez jamais de brouillons) → recadrez la région modifiée. - Vous essayez une présentation ? Utilisez un flag, pas un script scratch.
render/framesprennent des overrides de doc —--set <chemin>=<valeur>(répétable ; JSON-ou-chaîne),--frame <macos|windows|minimal|none>(render),--background <url>— qui patchent le doc en mémoire (doc.json intact) et sont validés par lint. Doncvos frames take --frame 2.0 --set frame.browserBar.kind=mac-light --set tilt[0].rx=8prévisualise une carte encadrée et inclinée sans toucher au fichier.
-
Rendu final :
vos render take out.webm --json(ou--format mp4). Re-cadrez le résultat final (frames --at-zooms) par rapport à taste.md avant de déclarer terminé. Les rendus sont déterministes — seules vos modifications changent la sortie.
6b. Tour de révision humaine (lorsque la demande en implique une) : vos open take sert la prise dans le studio — vos modifications de doc.json arrivent intactes et