O que show-n-tell faz?
Você orquestra um pipeline completo que transforma qualquer site + um logotipo + um pouco de intenção em um vídeo de demonstração narrado e com marca personalizada de 5 minutos. Os scripts em scripts/ são canos burros; você é o maestro.
Documentos de referência nesta pasta: docs/SCHEMAS.md (gramática YAML + ação), docs/GOTCHAS.md (problemas conhecidos + soluções alternativas), docs/CONTEXT.md ("qual é a aparência de uma boa saída"), examples/halyard-spme/ (exemplo completo canônico). Leia-os conforme necessário — não os pré-carregue.
Quando usar este skill
- O usuário quer um vídeo de demonstração narrado de um site (ao vivo, desenvolvimento local ou estático).
- O usuário tem pelo menos uma orientação leve de intenção ("mostre X, Y, Z" / "demonstre o fluxo do painel").
- O usuário tem acesso a um logotipo ou marca nominativa.
Recuse-se a prosseguir se o usuário não fornecer nem notas de intenção nem um logotipo. Demonstrações genéricas com marca padrão são demonstrações ruins. Peça orientação em vez de produzir silenciosamente um vídeo fraco.
Detecção de Ambiente (leia antes de começar)
Antes de fazer qualquer coisa, determine em qual ambiente o Claude está sendo executado:
- Cowork:
mcp__cowork__list_artifactsemcp__computer-use__screenshotestão disponíveis na lista de ferramentas do Claude. - Claude Code CLI: essas ferramentas estão ausentes.
Isso é importante porque várias fases se comportam de forma diferente entre ambientes. Referência rápida:
| Fase | Claude Code CLI | Cowork |
|---|---|---|
| Diretório de trabalho (Fase 5) | ~/demo-videos/<slug>/ | request_cowork_directory gera o caminho |
| Exploração pública do site (Fase 2b) | Ferramentas MCP do Playwright | Ferramentas MCP do Chrome ou explore_page.py headless |
| Captura de autenticação OAuth (Fase 2a) | Execute capture_auth.py via Bash | O usuário executa capture_auth.py localmente |
| Comandos longos (Fases 7–9) | Executar de forma síncrona | nohup + poll (timeout de 45s do bash) |
| Entrega (Fase 11) | Relatar caminho do mp4 | Caminho do mp4 + link computer:// |
Fase 1 — Entrevista (coletar entradas)
Faça uma ou duas perguntas de cada vez, não todas de uma vez. Obrigatório:
- URL alvo — confirme que está acessível.
- Notas de intenção da demonstração — que história isso conta? Quais recursos são importantes?
- Público + tom — interno/externo/investidores; explicativo/vendas/técnico/casual.
- Duração alvo — padrão de 5 min.
- Logotipo — URL ou caminho. Se apenas texto estiver disponível, sinalize a limitação, mas aceite.
- Cores da marca — primária (tinta) + destaque. Se não especificado, infira a partir do logotipo com PIL.
- Autenticação? — o site requer login?
- Não → prossiga.
- Sim, baseado em formulário (o site tem seus próprios campos de e-mail/senha que você controla) → use
session.pre_sessionemdemo_config.yaml. Colete os nomes das variáveis de ambiente de credenciais (NÃO os valores reais) e a URL de login/seletores. Diga ao usuário para colocar as credenciais reais em<working_dir>/.envantes de executar, nunca no chat. Vejaexamples/login-flow/. - Sim, OAuth / SSO / magic-link / passkey (Google, Microsoft, Okta, etc.) → use
session.storage_state. O usuário NÃO executa nenhum comando. VOCÊ iniciahelpers/capture_auth.pypara eles na Fase 2a, e eles só fazem login interativamente na janela do navegador que é aberta. Oauth.jsoncapturado é então usado tanto para a exploração da Fase 2b quanto para a gravação da Fase 8. Vejaexamples/oauth-storage-state/.
Fase 2a — Captura de autenticação (somente OAuth)
Pule esta fase completamente se a pergunta 7 da Fase 1 resultou em "sem autenticação" ou "baseado em formulário". Para OAuth / SSO / magic-link / passkey, capture uma sessão autenticada AGORA, antes da exploração do site — a Fase 2b precisa dela para visualizar páginas autenticadas.
mkdir -p ~/demo-videos/<demo-slug>/
uv run helpers/capture_auth.py <login_ou_url_inicial> \
--out ~/demo-videos/<demo-slug>/auth.json \
--viewport <recording.viewport de demo_config.yaml; padrão 1440x900>
Execute isso via ferramenta Bash com timeout: 600000 (10 minutos — o máximo). O script abre uma janela do Chromium com cabeçalho. Diga ao usuário em palavras simples:
"Estou abrindo uma janela do navegador para você fazer login. Complete o login (Google, Microsoft, etc., lidere com 2FA), navegue até a página onde deseja que a gravação comece e feche a janela do navegador. Vou salvar a sessão e continuar automaticamente."
O script escreve auth.json com modo 0600 atomicamente. Se o usuário demorar mais de 10 minutos (raro, mas possível com 2FA baseado em telefone em outra sala), o timeout do Bash dispara — basta reexecutar o mesmo comando.
--viewport DEVE corresponder a recording.viewport em demo_config.yaml. Alguns sites coletam impressões digitais do tamanho da viewport; a incompatibilidade pode invalidar a sessão capturada no momento da gravação ou da exploração.
Se o Chromium do Playwright ainda não estiver instalado (clone recente do skill), o script gera um erro acionável. Execute uv run --with playwright playwright install chromium uma vez.
Se estiver executando no Claude Code CLI: execute capture_auth.py via Bash conforme descrito acima.
Se estiver executando no Cowork: o Claude lida com isso diretamente usando ferramentas de uso do computador e Chrome — o usuário só precisa fazer login quando o navegador abrir. Etapas:
-
Certifique-se de que um caminho de diretório de trabalho é conhecido. Se a Fase 5 ainda não foi executada, chame
mcp__cowork__request_cowork_directoryagora para obter o caminho das saídas e defina<working_dir>como<outputs_path>/<demo-slug>/. -
Abra o Chrome na área de trabalho do usuário:
mcp__computer-use__open_application → "Google Chrome" (ou "Chrome") -
Navegue até a URL de login. Prefira o MCP do Chrome se estiver conectado:
- Extensão do Chrome disponível:
mcp__Claude_in_Chrome__navigatepara<login_ou_url_inicial> - Extensão do Chrome não conectada: use ferramentas
mcp__computer-use__*para clicar na barra de endereço e digitar a URL
- Extensão do Chrome disponível:
-
Diga ao usuário para fazer login — diga em palavras simples:
"O Chrome está aberto na página de login. Faça o login (lide com qualquer 2FA, SSO, etc.), navegue até a página onde a gravação deve começar. Avise-me quando terminar."
-
Espere e observe. Faça polling com
mcp__computer-use__screenshota cada ~15 segundos. Continue assim que a URL não for mais uma página de login/auth e um painel ou página de aplicativo estiver visível. -
Extraia o estado da sessão via JavaScript. Execute este snippet com
mcp__Claude_in_Chrome__javascript_tool:(() => { const cookies = document.cookie.split(';').map(c => { const [name, ...rest] = c.trim().split('='); return { name: name.trim(), value: rest.join('='), domain: location.hostname, path: '/', expires: -1, httpOnly: false, secure: location.protocol === 'https:', sameSite: 'Lax' }; }).filter(c => c.name); const ls = {}; for (let i = 0; i < localStorage.length; i++) { const k = localStorage.key(i); ls[k] = localStorage.getItem(k); } const ss = {}; for (let i = 0; i < sessionStorage.length; i++) { const k = sessionStorage.key(i); ss[k] = sessionStorage.getItem(k); } return JSON.stringify({ cookies, origins: [{ origin: location.origin, localStorage: Object.entries(ls).map(([n,v]) => ({name:n,value:v})), sessionStorage: Object.entries(ss).map(([n,v]) => ({name:n,value:v})) }] }, null, 2); })() -
Escreva o resultado em
<working_dir>/auth.jsonusando a ferramenta Write. Este é um formatostorage_stateválido do Playwright —helpers/explore_page.pyerecord_demo.pyambos o consomem viabrowser.new_context(storage_state=...). -
Ressalva sobre cookies HttpOnly. JavaScript não pode ler cookies HttpOnly — eles são intencionalmente invisíveis para
document.cookie. Se o token de sessão do site for apenas HttpOnly (comum com cookies de sessão Rails/Django mais antigos), o estado extraído estará faltando. Diga ao usuário:"Nota: cookies de sessão marcados como HttpOnly não são acessíveis a partir do JavaScript, então posso não ter capturado todos os tokens de autenticação. A gravação tentará usar o que foi capturado — se encontrar uma parede de login durante a Fase 8, podemos recorrer ao uso de
capture_auth.pylocalmente." Em seguida, continue — muitos sites modernos (Google, Microsoft, Okta) mantêm estado suficiente em cookies não HttpOnly ou localStorage para o Playwright funcionar.
Fase 2b — Exploração do site
Se a Fase 2a foi executada (auth.json existe), use helpers/explore_page.py para visualizar páginas autenticadas — o MCP do Playwright não pode carregar storage_state e veria apenas paredes de login. Para demonstrações sem autenticação, o MCP do Playwright é suficiente e mais interativo; use-o como antes.
Para páginas autenticadas:
mkdir -p ~/demo-videos/<demo-slug>/_explore/
uv run helpers/explore_page.py https://target.example.com/dashboard \
--storage-state ~/demo-videos/<demo-slug>/auth.json \
--out-dir ~/demo-videos/<demo-slug>/_explore/
Isso escreve três arquivos por página em _explore/: <slug>.png (captura de tela), <slug>.dom.html (DOM para seleção de seletores), <slug>.meta.json ({url_requested, final_url, title, status}). Leia o PNG com a ferramenta Read para ver a página; pesquise no DOM por seletores. Se final_url em meta.json apontar para uma página de login, a sessão expirou — execute novamente a Fase 2a.
Para páginas públicas (página inicial, páginas de marketing em qualquer demonstração; tudo em uma demonstração sem autenticação) — use as ferramentas MCP do Playwright (mcp__plugin_playwright_playwright__*) como antes:
browser_navigatepara a URL alvo, depois para 2–5 páginas que a intenção do usuário menciona.browser_snapshotoubrowser_take_screenshotpara ver o layout de cada página.- Use
browser_evaluatepara ler o DOM em busca de seletores estáveis (prefira semântico em vez denth-of-typequando possível).
Se estiver executando no Claude Code CLI: use ferramentas mcp__plugin_playwright_playwright__* conforme descrito acima.
Se estiver executando no Cowork: Primeiro, certifique-se de que o Chromium está disponível:
uv run --with playwright playwright install chromium
Em seguida, escolha o melhor método disponível:
- Se
mcp__Claude_in_Chrome__navigateestiver disponível (extensão do Chrome conectada), usemcp__Claude_in_Chrome__navigate+mcp__Claude_in_Chrome__read_pagepara explorar o site interativamente. - Caso contrário, recorra à exploração headless:
(Nenhummkdir -p <working_dir>/_explore/ uv run helpers/explore_page.py <url> --out-dir <working_dir>/_explore/--storage-statenecessário para páginas públicas — o argumento agora é opcional.)
Anote para cada página significativa:
- Momentos dignos de demonstração (rolar para recurso, clicar para detalhes, dica de ferramenta ao passar o mouse).
- Adaptações necessárias do site: truque de cabeçalho fixo? Link direto para PDF? Bloqueadores de modal? Parede de autenticação?
- Seletores que parecem frágeis e podem precisar de defesas
wait_for_selector.
Não faça um rastreamento exaustivo. 2–5 ações por página são suficientes.
Fase 3 — Rascunho storyboard.yaml
Gere 15–30 cenas. Cada cena = uma ação de câmera + 1–3 frases de narração. Veja docs/SCHEMAS.md para a gramática de ação (subconjunto da Fase A: goto, goto_and_scroll, scroll_into_view, scroll_y, hover, click).
Alvo para um vídeo de 5 minutos com aceleração de 1,2x:
- Tempo total pós-aceleração: ~5:00
- Tempo de execução bruto total: ~6:00
- Média por cena: ~12s, dos quais ~9–10s são narração
Regras rígidas ao redigir a narração:
- Toda afirmação factual deve estar visível na tela neste quadro da cena. Sem números inventados ("o agente sinalizou 199 alterações"). Se você não vê na captura de tela, não diga.
- Iniciais lidas letra por letra devem ser escritas com hífen:
S-P-M-E,K-Y-B,A-P-I. O prompt TTS impõe essa convenção. - O comprimento corresponde ao conteúdo visual: 8–18s para cenas de rolagem, 6–12s para cenas de clique.
- O tom corresponde ao campo
voice.tonedebranding.yaml.
Agrupe mentalmente as cenas em segmentos (Visão Geral / Recurso A / Recurso B / Encerramento), mas escreva uma lista simples em YAML — sem marcadores de seção.
Fase 4 — Revisão em linguagem simples
O usuário não edita YAML diretamente. Você apresenta o rascunho como uma lista numerada e aceita feedback em linguagem natural. Use este formato exato:
Aqui está o fluxo de demonstração que eu rascunhei. Tempo estimado: 5:08 (após aceleração de 1,2x).
Diga-me o que você gostaria de alterar.
VISÃO GERAL (60s)
1. Aterrissar na página inicial (10s) — "Bem-vindo à Acme Cloud, uma plataforma serverless para pequenas equipes..."
2. Rolar para recursos (15s) — "Três capacidades principais impulsionam a plataforma..."
3. Rolar para saídas (8s) — "Estas dez áreas de política..."
RECURSO 1 — Painel (45s)
4. Navegar para o painel (8s) — "Clicando no painel..."
5. Passar o mouse no feed de atividade ao vivo (12s) — "Cada projeto mostra um feed de atividade ao vivo..."
...
ENCERRAMENTO (20s)
27. Voltar para estatísticas da página inicial (12s) — "Três razões pelas quais as equipes escolhem a Acme..."
28. Encerramento (8s)
Em seguida: "Algo para adicionar, remover ou reordenar? Algum ajuste no tom da narração? Algo para pular?"
Traduza o feedback em linguagem natural para edições YAML:
- "Pule a cena 7" → remova do YAML
- "Torne a cena 3 mais longa" → estenda a narração com mais detalhes na tela (divida em duas cenas se for muito mais longo)
- "Mencione preços em algum lugar" → identifique a cena certa e adicione uma frase (verifique se os preços estão realmente visíveis no quadro dessa cena)
- "Tom muito formal" → reescreva a narração com redação mais solta
- "Mostre a página da equipe em vez de sobre" → troque URL + ajuste a narração
Reapresente o fluxo atualizado. Itere até que o usuário aprove explicitamente.
Verificação de desvio de tempo antes do TTS: some len(beat.narration) / 16 (caracteres/s a 140 ppm) em todas as cenas. Se estimado * (1 / speed_multiplier) estiver a mais de 30s do alvo, sinalize: "Isso atualmente termina em ~6:20 pós-aceleração versus seu alvo de 5min. Quer que eu corte?"
Fase 5 — Configuração do diretório de trabalho
mkdir -p ~/demo-videos/<demo-slug>/_assets
cp <caminho_do_logotipo> ~/demo-videos/<demo-slug>/_assets/<logo>.png
# escreva storyboard.yaml, branding.yaml, demo_config.yaml a partir de seus rascunhos
Se estiver executando no Claude Code CLI: use ~/demo-videos/<demo-slug>/ conforme mostrado acima.
Se estiver executando no Cowork: chame mcp__cowork__request_cowork_directory para obter o caminho das saídas do usuário. Defina o diretório de trabalho como <caminho_das_saídas>/<demo-slug>/. Use este caminho para TODAS as gravações de arquivo subsequentes nas Fases 6–11 — armazene-o agora e mantenha-o consistente durante toda a sessão. Crie o subdiretório _assets/ sob este caminho, não sob ~/demo-videos/.
Se o usuário não tiver um arquivo de logotipo no disco e tiver apenas uma URL, baixe-o com curl e coloque-o em _assets/.
Fase 6 — Gerar ativos estáticos de marca
Execute helpers/apply_branding.py para gerar _assets/overlay-lower-third.png e _assets/overlay-watermark.png (usados na Fase 9):
cd ~/demo-videos/<demo-slug>/
uv run ../../../helpers/apply_branding.py .
Nota: o caminho ../../../helpers assume que você está dentro de ~/demo-videos/<demo-slug>/ e que o clone do skill está em ~/Code/show-n-tell/ ou um local semelhante — ajuste o caminho relativo conforme necessário.
Se estiver executando no Claude Code CLI: execute conforme mostrado acima.
Se estiver executando no Cowork: o diretório de trabalho está em <caminho_das_saídas>/<demo-slug>/. Use o caminho absoluto para aplicar_branding.
Fase 7 — Gerar narração TTS
Execute helpers/generate_tts.py para converter as narrações do storyboard.yaml em arquivos de áudio:
cd ~/demo-videos/<demo-slug>/
uv run ../../../helpers/generate_tts.py storyboard.yaml \
--speed-multiplier 1.2 \
--out-dir _audio
Isso gera um arquivo .mp3 por cena em _audio/. Flag --dry-run para depuração.
Problema conhecido: texto longo (>1000 caracteres) pode exceder o limite de caracteres da API TTS da OpenAI. O script divide automaticamente em partes, mas a pausa entre as partes pode soar não natural. Mantenha cada cena abaixo de 1000 caracteres para obter melhores resultados.
Resolução de problemas: Se o script falhar com um erro de quota/limite de taxa da API, o tratamento de retry interno deve lidar com isso — mas se não, verifique se sua chave da API OpenAI tem créditos e está definida em OPENAI_API_KEY.
Fase 8 — Gravação de tela
Execute helpers/record_demo.py para gravar o site de acordo com o storyboard:
cd ~/demo-videos/<demo-slug>/
uv run ../../../helpers/record_demo.py demo_config.yaml \
--storyboard storyboard.yaml \
--audio-dir _audio \
--out-dir _raw \
--speed-multiplier 1.2
Isso gera um vídeo .mp4 bruto por cena em _raw/. Cada cena é sincronizada com seu arquivo de áudio.
Pós-processamento: depois que o comando for concluído, inspecione a saída do console em busca de avisos — particularmente avisos de limite de tempo, onde um elemento não apareceu dentro do tempo limite do seletor. Se a gravação de uma cena falhar completamente (nenhum arquivo de vídeo gerado), marque a cena como "com problema" e decida se deve refazer manualmente com um seletor diferente ou pular (e reescrever as narrações com transições de cobertura adequadas).
Fase 9 — Pós-processamento de áudio/vídeo
Execute helpers/mux_final.py para combinar todos os clipes, sobrepor marca e ajustar a velocidade:
cd ~/demo-videos/<demo-slug>/
uv run ../../../helpers/mux_final.py \
--storyboard storyboard.yaml \
--audio-dir _audio \
--video-dir _raw \
--assets-dir _assets \
--output output.mp4 \
--speed-multiplier 1.2
Isso produz output.mp4 no diretório de trabalho.
Fase 10 — Verificação de qualidade
Reproduza o output.mp4 localmente ou use ffprobe para verificar:
ffprobe ~/demo-videos/<demo-slug>/output.mp4 2>&1 | grep -E "Duration|Stream"
Verificações básicas:
- Duração: dentro de 15% do alvo (5 min ± 45s)
- Áudio: pelo menos um stream de áudio está presente
- Vídeo: codec h264 ou similar, proporção de aspecto correta
Se qualquer verificação falhar (por exemplo, nenhum stream de áudio), verifique os logs em _audio/ e _raw/. Pode ser necessário repetir a Fase 7 ou 8.
Fase 11 — Entrega
Se estiver executando no Claude Code CLI: diga:
"Seu vídeo de demonstração está pronto em
~/demo-videos/<demo-slug>/output.mp4. Abra-o em seu reprodutor de mídia preferido."
Se estiver executando no Cowork: diga:
"Seu vídeo de demonstração está pronto em
<caminho_das_saídas>/<demo-slug>/output.mp4. Você também pode acessá-lo via:computer:///caminho/completo/para/output.mp4"
(Por exemplo, computer:///Users/seunome/demo-videos/acme-cloud/output.mp4)
Fase B — Recursos avançados (opcional)
Depois que o fluxo básico (Fases 1–11) estiver concluído, se o usuário solicitar intros, outros ou legendas, use estes scripts:
Introdução/Encerramento:
cd ~/demo-videos/<demo-slug>/
uv run ../../../helpers/generate_intro_outro.py \
--storyboard storyboard.yaml \
--branding branding.yaml \
--out-dir _intro_outro
Isso gera clipes de introdução (3–5s) e encerramento (2–3s) com o nome do produto e logotipo do usuário. Em seguida, remuxe:
uv run ../../../helpers/mux_final.py \
--storyboard storyboard.yaml \
--audio-dir _audio \
--video-dir _raw \
--assets-dir _assets \
--intro-clip _intro_outro/intro.mp4 \
--outro-clip _intro_outro/outro.mp4 \
--output output_with_intro_outro.mp4 \
--speed-multiplier 1.2
Legendas:
cd ~/demo-videos/<demo-slug>/
uv run ../../../helpers/generate_captions.py storyboard.yaml \
--audio-dir _audio \
--out-dir _captions
Isso produz um arquivo .srt por cena e um _captions/combined.srt combinado. Para queimá-los no vídeo:
ffmpeg -i output.mp4 -vf "subtitles=_captions/combined.srt" output_captioned.mp4
Resolução de problemas
A gravação trava/expira sem produzir um vídeo para uma cena:
Verifique o seletor — um seletor nth-of-type frágil provavelmente mudou. Reexplore a página com explore_page.py para obter um DOM atualizado e escolha um seletor mais estável (por exemplo, [data-testid="submit"] em vez de button:nth-of-type(3)).
A narração TTS soa robótica ou com pausas estranhas:
Frases muito longas (>1000 caracteres) podem ser divididas pela API. Encurte a narração ou divida a cena do storyboard em duas cenas mais curtas.
A sessão de autenticação expirou durante a gravação:
O tempo de vida do token de sessão varia muito. Se a gravação atingir uma parede de login, repita a Fase 2a para capturar uma sessão nova e, em seguida, repita a Fase 8 imediatamente enquanto o token está ativo. Para sites com vidas de sessão muito curtas (<15 min), considere capturar a autenticação imediatamente antes da Fase 8.
Os overlays de marca não aparecem ou ficam distorcidos:
Verifique se branding.yaml tem larguras/alturas de overlay razoáveis ou execute helpers/apply_branding.py com --force para regenerar. Verifique se a proporção de aspecto do vídeo corresponde às dimensões configuradas (por exemplo, 1920x1080 vs 1440x900 causa alongamento).
A exploração do Cowork não tem o MCP do Chrome disponível:
Às vezes, a extensão do Chrome não está instalada ou concedeu permissão. Peça ao usuário para instalar a extensão "Claude in Chrome" do Chrome Web Store e conceder acesso à URL. Enquanto isso, recorra ao método de exploração headless.
Notas de arquitetura
O pipeline inteiro é acionado a partir de um único prompt do usuário graças a:
- Modelo de estado sequencial: Sempre capture a autenticação antes de explorar e explore antes de storyboard.
- Separação de ativos: Logotipo, cores e voz de marca são capturados uma vez e passados como argumentos.
- Aceleração independente: O TTS e a mixagem aplicam
--speed-multiplierde forma independente sem distorção de tom. - Intros/outros como fases opcionais: O caminho principal não depende deles, mas a integração é trivial para usuários que os desejam.