Screen video
O screencast do Chrome grava o próprio navegador, reconstruído como um .mp4 H.264 a 60 FPS. Legendas, cursor, zoom e sons são adicionados depois, então a gravação permanece suave independentemente do que a página esteja fazendo.
Os scripts resolvem o Playwright a partir do node_modules do repositório atual, portanto execute-os a partir da raiz do repositório. <skill-dir> é a pasta desta skill.
0. Pergunte primeiro
A menos que a solicitação já diga, pergunte em uma única mensagem:
- Qual história? Qual aplicativo e URL, quais páginas, os 3 a 6 momentos que o espectador deve ver. Quem assiste (colegas de equipe, um revisor, um cliente)?
- Voz? Apenas legendas ou narrado (narração por IA)? Vídeos para clientes são sempre narrados.
- Para onde vai? Apenas o arquivo
.mp4(padrão), um PR ou issue do GitHub, uma issue do Linear, Slack ou outro lugar. Pergunte pelo PR/issue/canal; nunca crie um ticket em nome de outra pessoa. - Quais dados? Qual banco de dados local ou conta será usada para a gravação. Nunca produção, nunca dados reais de clientes.
1. Gravar
-
Execute o aplicativo localmente e abra cada página uma vez primeiro: a primeira compilação de um servidor de desenvolvimento pode aparecer no vídeo.
-
Escreva um script de fluxo baseado em
scripts/record-lib.mjs(copieexamples/playwright-site/flow.mjs, uma gravação executável de um site público). Usestep(id, caption, action)(oucaption()simples) para colocar uma linha de legenda na tela para cada etapa: centralizado na parte inferior, elevado acima da barra de reprodução do player, translúcido (captionStyle: 'top'para um banner). Mantenha em cerca de 1 minuto. Clique comclick(locator)/hover(locator)do fluxo, não comlocator.click(): eles deslizam o cursor desenhado até o alvo primeiro. Umlocator.click()simples ainda funciona, mas o cursor salta.zoom(locator, { scale })aproxima suavemente de um alvo,unzoom()volta, eclick(locator, { zoom: true })aproxima antes de um clique. O zoom permanece até que você o mova; um clique fora da área ampliada primeiro move o panorama para lá.Orçamento de zoom: deve parecer que uma pessoa está se inclinando, não um reflexo.
- Aplique zoom apenas quando um texto pequeno é o ponto central: um diff, um valor que mudou, a mensagem de um diálogo. Nunca para navegação, abrir um menu ou pressionar um botão que qualquer um pode ver.
- Cerca de um zoom a cada 15–20 segundos. Dois em sequência: faça um panorama entre eles (chame
zoomno próximo alvo) em vez de sair e voltar. - Permaneça com zoom enquanto as próximas ações ocorrerem na mesma área; use
unzoom()quando a história se mover para outro lugar. - Escala 1,3–1,5. Vá para 2 apenas para algo muito pequeno, como um único ícone. Mantenha por pelo menos 2 segundos.
- Prefira uma pausa narrada a um zoom: se a linha for legível no tamanho normal, não aplique zoom.
Digite com
type(locator, text, { clear }): digita no ritmo de uma pessoa.clicketyperegistram cada clique e tecla pressionada em<video>.sfx.json, enarrate.mjscoloca os sons gravados sob a voz: amostras de clique de mouse e teclado mecânico desounds/.SFX_VOLUMEos escala (padrão 1),SFX=0para nenhum.page.keyboard.typeelocator.click()simples não produzem som. -
node flow.mjsimprime o caminho do.mp4. Precisa do ffmpeg ($FFMPEGou PATH; sem ffmpeg?npm i --prefix <scratch> ffmpeg-static).
Qualidade
record(flow, { scale }) define a página no size (padrão 1280×800 CSS px, para a UI parecer normal) e a renderiza em scale× densidade de pixels: 2 (padrão) é 2560×1600, 3 é 3840×2400. O texto fica mais nítido, não menor. Espere aproximadamente 15–40 MB por minuto em 2 e 2–3× isso em 3. Para um destino com limite de tamanho (plano gratuito do GitHub 10 MB, prévias do Slack), use scale: 1 ou CRF=24.
Narração (quando solicitado voz)
- Escreva
script.json:[{ "id": "intro", "say": "…" }, …], uma linha falada porstepid. Escreva para ser ouvido: frases curtas, números por extenso ("mil e quinhentos"). GOOGLE_API_KEY=… node <skill-dir>/scripts/tts.mjs script.json <audio-dir>escreve um WAV por linha edurations.json. O Gemini 3.8 Flash TTS (TTS_MODEL,TTS_VOICEpara sobrescrever) soou mais natural que o OpenAIgpt-4o-mini-tts. Obtenha uma chave em aistudio.google.com.- Grave com
{ narration: '<audio-dir>/durations.json' }. Cadastepespera até que sua linha termine, e os horários de início ficam em<video>.mp4.marks.json. node <skill-dir>/scripts/narrate.mjs <video.mp4> <audio-dir> <out.mp4>multiplexa os clipes e sons (recodifica emCRF, padrão 18). Entregue<out.mp4>.
Peculiaridades do TTS:
- Envie apenas a linha. Um prefixo "Diga calorosamente: …" é lido em voz alta, e
systemInstructionretorna 400. - O Gemini retorna um WAV completo mais um bloco de proveniência; encapsular isso em um segundo cabeçalho faz som estático em ambas as extremidades de cada linha. O
tts.mjsextrai o bloco de dados por esse motivo; mantenha isso. - Para verificar a sincronia, transcreva o áudio do mp4 com timestamps (OpenAI
whisper-1,response_format=verbose_json) e compare com as marcas.
Peculiaridades que custam uma regravação
- Limpe uma entrada pré-preenchida com
.fill('')antes depressSequentially, que apenas anexa. - Cada gravação altera dados (publica, aceita, salva). Redefina os dados entre as gravações.
- Aguarde as salvaguardas automáticas e preenchimentos de IA em segundo plano antes do próximo passo, ou a UI salta.
- A rolagem com roda pode recolher um cabeçalho colapsável e esconder o próximo botão. Prefira
el.scrollIntoView()através delocator.evaluate. - Nunca deixe a página entrar em tela cheia do navegador: os quadros redimensionam e o vídeo corta para o canto superior esquerdo. Execute
document.documentElement.requestFullscreen = undefinedantes de um botão de tela cheia.
Dados de demonstração
- Semeie exatamente o estado que a história precisa em um banco local, com pessoas verossímeis em
example.come números555. Nunca um cliente real, nunca um banco de pré-visualização ou produção. - Tabelas de histórico, auditoria e eventos são somente anexação: cada regravação adiciona linhas que fazem a próxima gravação parecer encenada. Use um registro novo por gravação ou mantenha a história em ações que não deixam rastros.
- Execute primeiro uma simulação: uma ramificação
DRY=1no fluxo clica em Cancelar em cada confirmação destrutiva. Capture quadros em algumas marcas de etapa, corrija localizadores e enquadramento, depois faça a única gravação real. - Após a gravação, verifique se o estado final do banco corresponde ao que as legendas ou narração afirmam.
- Alterar os dados significa executar novamente o
tts.mjspara as linhas que os descrevem. - O zsh não divide
$VARpor palavras. Passe os tempos dos quadros paraframes.mjsliteralmente ou faça um loop sob obash.
Vídeos tutoriais para clientes (onboarding, how-to)
- Sempre narrados. Mantenha as legendas em frases curtas; a voz carrega os detalhes.
- Uma etapa de email vai para o Gmail:
mountGmail(page, { emailHtml, subject, sender, me })descripts/gmail-scene.mjs. Alimente com o email real renderizado do aplicativo. Ele reescrevetarget="_blank"para_top, pois um popup deixa a gravação esperando na página errada. Responder é embutido:#reply-btn,#reply-text,#send-btn. Passemailbox: 'sam'para várias caixas de entrada (servidas em/same/sam/message). Uma resposta nunca chega ao aplicativo, então escreva seu efeito diretamente no banco local. - Espere por elementos, não por
waitForURL: cadeias de redirecionamento de autenticação abortam navegações (ERR_ABORTED) e algumas páginas nunca disparamload. Dê a cliques de navegação{ noWaitAfter: true }. - Após (re)iniciar um servidor de desenvolvimento, aqueça todas as páginas e rotas de API que a gravação acessa.
- Ressemeie antes de cada gravação. Tokens de link mágico são de uso único, portanto uma tentativa falha os queima.
2. Verificar
node <skill-dir>/scripts/frames.mjs <video.mp4> 10 30 50 escreve frame-<t>.jpg ao lado do vídeo. Olhe para eles antes de entregar. Para narração, peça ao usuário para ouvir uma vez: quadros não podem capturar defeitos de áudio. Verifique se há dados reais de clientes (nomes, emails, telefones): fazer upload os publica para todos que podem ver o destino.
3. Entregar
Apenas o arquivo (padrão)
Imprima o caminho absoluto do .mp4 final. Pronto.
PR, issue ou comentário do GitHub (reproduz inline)
O GitHub só reproduz um vídeo inline a partir de uma URL https://github.com/user-attachments/assets/<uuid> em sua própria linha, e essa URL vem apenas do upload web do GitHub: gh, a API REST e arquivos commitados fornecem um link de download. Os uploads são privados para membros do repositório e nunca são commitados.
node <skill-dir>/scripts/attach-video.mjs <URL do PR ou issue do GitHub> <video.mp4>
- Abre uma janela visível do Chromium. Se o login salvo expirou, mostra a página de login do GitHub: o usuário faz o login. Nunca peça a senha e nunca leia os cookies do navegador.
- Faz upload através da caixa de comentário da página; o comentário nunca é postado. Imprime
ATTACHMENT_URL https://github.com/user-attachments/assets/<uuid>. - Lembra o login em
~/.cache/screen-video/gh-profile.FORGET_SESSION=1o deleta após o upload. Um upload concorrente espera pelo perfil (WAITING_FOR_PROFILE).
Coloque a URL sozinha em sua própria linha no corpo ou comentário (gh pr edit <n> --body-file <file>, gh issue comment <n> --body-file <file>). Para confirmar, verifique o body_html do PR por uma tag <video src=…private-user-images…>; um curl -sI na URL do ativo pode retornar 404 mesmo quando reproduz. Limite de tamanho: 10 MB em planos gratuitos, 100 MB em pagos.
Issue do Linear
O MCP do Linear faz upload diretamente; nenhum navegador é necessário.
prepare_attachment_uploadcomissue,filename,contentType(video/mp4) esize(bytes exatos:stat -f%z <file>no macOS,stat -c%sno Linux).- Dentro de 60 s,
curl -X PUT --data-binary @<file> "<uploadRequest.url>"com todas as entradasuploadRequest.headersenviadas literalmente. Alterar a capitalização de um cabeçalho ou omitir um resulta em 403. Faça upload de um arquivo por vez. create_attachment_from_uploadcomissueeassetUrl.- Para que seja reproduzido no tópico, use
save_commentcommais uma legenda de uma linha. Abra a issue uma vez para confirmar que é renderizada como um player. - Substituir um vídeo: faça upload do novo, edite o mesmo comentário (
save_commentcomid), entãodelete_attachmentdo antigo.
Slack
Com o MCP do Slack, após o usuário confirmar o canal (o compartilhamento não pode ser desfeito):
slack_get_file_upload_urlcomfilenameecontent_length(bytes exatos).URL='<upload_url>' && curl -X POST "$URL" -H 'Content-Type: video/mp4' --data-binary @<file>. Mantenha a URL byte por byte; um 401 significa que foi corrompida ou expirou, então obtenha uma nova.slack_complete_file_uploadcomfile_id,channel_id(um idC…/D…, não um nome; procure comslack_search_channels), opcionalmentethread_tseinitial_comment.
Sem o MCP, forneça ao usuário o caminho do arquivo para arrastar.
Qualquer outro lugar
Entregue o caminho do arquivo e o tamanho; mencione scale: 1 / CRF=24 se o destino limitar o tamanho.