Product video (e os ativos ao redor dele), do início ao fim
Você está produzindo um ativo de produto pronto para ser enviado — um vídeo polido, um conjunto de stills com tamanhos exatos, ou ambos — a partir de uma página web ativa, com a CLI vos.
Tudo são dados: você escreve um script de fluxo, a CLI grava e planeja, você ajusta o JSON, você re-renderiza. Nunca regrave para corrigir ritmo ou zooms — edite doc.json e renderize novamente. Padrão de qualidade: references/taste.md — siga seu ciclo de qualidade e julgue os stills multimodalmente.
Configuração
npm i -D @vosjs/cli
Um comando, um pacote: @vosjs/cli é o binário vos de código aberto (MIT) com os verbos do motor, o pipeline de takes usado aqui (record / plan / frames / render em gravações de tela) e os verbos da plataforma vos.so. (Até a versão 0.9, o pipeline de takes era enviado separadamente como @vosso/vos-plugin; um projeto que ainda o liste pode descartá-lo.) Requisitos:
- Um Chromium: o Chrome do sistema é encontrado automaticamente; caso contrário, execute
npx playwright install chromiumou aponteVOS_BROWSER_PATHpara um. Código de saída 3 significa que nenhum navegador foi encontrado. - Rede no momento da renderização: a página de renderização carrega three/mediabunny do esm.sh. A gravação também precisa alcançar a URL alvo. Sandboxes totalmente offline não podem renderizar — diga isso em vez de não enviar nada.
Convenções: logs → stderr, resultados → stdout; --json emite NDJSON terminando com {"event":"done",…}; códigos de saída 0 ok / 1 erro / 2 uso ou falha estrita / 3 sem navegador / 4 o gravador encontrou um login em vez da página (uma SESSION ausente ou expirada, nunca um bug de script: references/sessions.md).
vos <verb> --help imprime as flags desse verbo (@vosjs/cli 0.41.1 e posteriores).
Passo 0 — escolha o destino (ele decide tudo)
| Destino | Viewport | Saída | Extras |
|---|---|---|---|
| Clipe de página inicial (hero/embed de seção) | 2560×1440 (2K nativo da filmagem) | webm → re-encode VP9 + poster | loop silencioso; veja references/destinations.md |
| Vídeo de lançamento (PH, social, promo de loja) | 2560×1440 ou 1280×720 | mp4 (--format mp4, precisa do Chrome do sistema) | faixa de música via doc.audio |
| Stills do kit de lançamento (capturas de loja, tiles, OG) | dimensionado para o ativo | PNGs frames --frame <t> --size WxH do mesmo take | um take → cada ativo |
| Demo rápida (issue, PR, chat) | 1280×720 | webm, padrões | velocidade acima do polimento; rascunhos aceitáveis SOMENTE aqui |
Dimensões e orçamentos de bytes por canal: references/destinations.md.
O loop principal (todo destino)
-
Explore a página alvo com suas próprias ferramentas (buscar HTML / Playwright). Identifique os 3–6 momentos que contam UMA história. Colete SELETORES estáveis (
a[href='…'], ids, papéis — não cadeias nth-child). Prepare o conteúdo como um cenário: o script deve deixar o produto no estado que uma captura de tela orgulhosa mostraria — rótulos digitados, dados com aparência real, a funcionalidade no meio da história. Uma tela vazia grava rápido e não demonstra nada, e nenhuma composição downstream a resgata.Atrás de um login? Resolva a sessão antes do script. Um gravador sem sessão grava a parede (a página de login, ou a página pública que o site envia para um estranho) e o único sintoma é um seletor ignorado. Suba a escada em
references/sessions.mdde cima para baixo e pare no primeiro degrau que segurar: nenhuma parede (um modo demo, um servidor local com autenticação desligada) → CUNHE uma sessão a partir da autenticação de teste que o projeto já tem (playwright/.auth, umauth.setup.ts, um script de seed: procure antes de perguntar a qualquer pessoa) → faça login fora da câmera comsetupemactions.json(a senha de{ "env": "NAME" }, nunca um literal) → o humano faz login uma vez (vos session open <url> --name <app>, então--session <app>no record) → o humano grava com a extensão a partir da lista de shots quevos actions script actions.jsonimprime, e você corta. Cada degrau termina emsetup, um arquivo de estado para--storage-state, ou uma sessão nomeada para--session. Nunca digite ou aceite uma senha de produção, mantenha o arquivo de estado fora do diretório do take e fora do git, e grave de uma conta demo: o que a conta mostra é enviado no vídeo.Já verificou a funcionalidade com agent-browser? Mantenha esse passeio e pule o segundo script. O resultado
--jsondo agent-browser não diz o que foi executado (scrollresponde{scrolled:true}), então envolva cada chamada para que o comando acompanhe seu resultado e depois converta: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 # a página mudou: refs renumerados ab fill @e2 query; ab press Enter vos actions from-agent-browser steps.jsonl --out actions.json(um caminho inteiro executado como um
agent-browser batch … --json > steps.jsonljá tem essa forma). O log ésteps.jsonlno diretório onde você está, então caminhe a partir de um diretório. As refs são resolvidas através do últimosnapshot -iantes delas, e elas RENUMERAM após uma navegação e novamente dentro de um diálogo: tire um novo snapshot depois de qualquer coisa que mude a página e leia as refs antes de nomear uma (-udá aos links seu href). Clique no controle, não pressione seu atalho: ⌘K abre o diálogo para um humano, mas uma tecla é o único passo que o gravador não pode repetir. Tudo o que ele não pode seguir (uma tecla de atalho, um arrasto, um segundoopen) é NOMEADO na saída, nunca descartado: leia as notas, escreva esses passos à mão e grave. -
Escreva
actions.json:{ "url": "https://target.example", "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 } ] }Verbos:
waithoverclicktypescrollmovedrag(type={do:'type', selector, text, delayMs?, ms?, focus?}: ele clica no campo e depois digitatext;focus:falsedigita no que já está focado, para um Enter de envio) (drag = edições reais:{do:'drag', selector|x,y, tx, ty, ms}— deslize um input de faixa, arraste um elemento canvas, mova um clipe da linha do tempo). O RITMO É o plano do zoom: abrawait ≥700ms; passe o mouse sobre o que importa 700–900ms (pausas se tornam zooms); omsde um clique é o tempo de LEITURA, contado a partir da última alteração da página (@vosjs/cli0.47 e posteriores; o gravador paga o tempo de estabilização por conta própria), então dimensione-o como uma batida: cerca de 1000ms após uma navegação, 600ms após um controle, nunca preenchido para uma página lenta; termine estabilizado. Direcione o cursor para longe de menus acionados por hover (taste.md, regras de fluxo). Verifique comvos validate actions.json.Em seguida, ENSAIE-O (
@vosjs/cli0.39 e posteriores):vos record --actions actions.json --out take --dry-run. Cada passo é executado na página real, em ordem, porque um seletor posterior geralmente existe apenas após um clique anterior, mas nada é capturado e nada é escrito: um seletor perdido é nomeado em segundos (saída 2) em vez de após um take em tempo real e sua codificação, e um take já em--outmantém sua filmagem e seu corte. Ele imprime o rect de cada passo em px de captura, que é o que uma camada fixada lê. Ensaie novamente após cada edição de script, e grave apenas um script que passe. Um produto com login ensaia da mesma forma:--storage-statee--browser-arg=também se aplicam a ele, e o ensaio termina listando o que o quadro EXPÕE (um endereço, uma chave, um cartão). Esconda-os commaskantes de gravar:references/sessions.md. -
Grave:
vos record --actions actions.json --out take --strict --json--strictsempre: seletor ignorado / timeout networkidle → saída 2 comskipped[]no evento done. Uma ignorância significa que o fluxo está quebrado — corrija-o, nunca o entregue contornando-o. O take é auto-codificado e auto-planejado automaticamente. (vos create --actions actions.json out.webm --stricté o verbo gravar+renderizar de uma só vez — bom para uma primeira passagem rápida, mas o loop DESTA skill revisa os quadros antes de renderizar, então prefira os verbos separados aqui.) -
Ajuste
doc.json(o JSON Schema é enviado no pacote npm@vosjs/cli:schema/doc.schema.json):zoom:[{in, out, level, cx, cy, source}], segundos SOURCE; níveis 1.4–2.8;cx/cyNORMALIZADOS [0..1] (0.5,0.5 = centro) — NÃO pixels; defina"source": "manual"nos intervalos que você tocar (sobrevive ao re-planejamento).segments(cortes) ·speed(rate0.1–16) ·frame.*·cursor.tilt:[{in, out, rx, ry, source}], segundos SOURCE — o cartão 3D inclina-se para a pose enquanto ativo, retorna ao repouso entre elas. GRAUS (±5..18 lê premium): +rx = borda superior mais próxima, +ry = borda esquerda mais próxima (inclinar em direção a um foco no lado direito =rynegativo). Intervalos ≥ 0.8s; combine com momentos de zoom (mesmo in/out encadeia os movimentos), uma mudança de pose a cada ~5s batida."source": "manual"nos intervalos que você tocar;tiltStyle: "subtle"|"medium"|"strong"registra a varinha automática.frame.backgroundMedia: um loop de vídeo / imagem atrás do cartão —{"kind":"video","key":"/bg.webm","duration":10,"dim":0.2}.key= um arquivo colocado no diretório do take ("/bg.webm") ou uma URL de mídia; vídeo precisa deduration(modulo loop ancorado na SAÍDA);dim0..1 tela. Ambiente, não um assunto — escureça-o atrás de uma IU densa.audio: clipes ancorados na SAÍDA;keypode ser um arquivo colocado no diretório do take ("/music.mp3"); ganho/fades/loop. Misturado em renderizações completas (Opus/AAC);--rangepermanece silencioso; força voo único.- export:
{"resolution": "720p|1080p|2k|4k", "fps": 30}— nunca acima da filmagem (validate avisa). Entãovos validate take --json— as verificações devem passar.
-
Olhe antes de renderizar (o ciclo de qualidade taste.md):
vos frames take --at-zooms --times 0,25%,50%,75%,100% --json→ julgue cada still contra taste.md, os ápices de zoom mais fortemente.- Itere: edite doc.json →
vos render take check.webm --range a..b --draft(segundos, meia res — nunca envie rascunhos) → re-quadre a região alterada. - Tentando uma apresentação? Use uma flag, não um script de rascunho.
render/framesaceitam substituições do doc —--set <path>=<value>(repetível; JSON-ou-string),--frame <macos|windows|minimal|none>(render),--background <url>— que aplicam patch no doc em memória (doc.json intacto) e são protegidos por lint. Entãovos frames take --frame 2.0 --set frame.browserBar.kind=mac-light --set tilt[0].rx=8pré-visualiza um cartão emoldurado e inclinado sem tocar no arquivo.
-
Renderização final:
vos render take out.webm --json(ou--format mp4). Re-quadre o final (frames --at-zooms) contra taste.md antes de declarar concluído. As renderizações são determinísticas — apenas suas edições alteram a saída.
6b. Rodada de revisão humana (quando a solicitação envolve uma): vos open take
serve o take no estúdio — suas edições no doc.json chegam intactas e