Communitygithub.com

demo-video

Cria um pequeno vídeo de demonstração do produto, mostrando um aplicativo (warden ou o app de exemplo) em um simulador real, com um terminal digitando comandos ao lado do celular. Use quando for solicitado um vídeo demo, um clipe de recurso para documentação ou X, uma regravação após uma mudança na interface, ou para adicionar/editar uma definição `*.demo.ts`.

O que é demo-video?

Esta skill gera vídeos de demonstração realistas e honestos, combinando a gravação de um simulador iOS real com um terminal que digita e mostra a saída de comandos `warden` (ou de desenvolvimento) em tempo real. É útil para criar clipes de funcionalidades visuais (biometria, rede, localização, etc.) para documentação, postagens em redes sociais ou regravações rápidas após alterações na interface do usuário. O processo é inteiramente definido e controlado por código: você escreve uma definição no formato TypeScript (`.demo.ts`), executa `bun run demo` para gravar e renderizar, e a skill verifica automaticamente a veracidade do que foi mostrado, garantindo que nenhum comando seja falsificado e que a saída corresponda exatamente ao que o aplicativo fez. Suporta múltiplos pipelines, masterizando um vídeo final 1920x1080 com layout lado a lado (título, terminal e telefone), geração de pôster (JPEG) e um processo de revisão em loop (gravar, verificar uma folha de contato, ajustar a definição e renderizar novamente) para garantir a qualidade e a honestidade da demonstração.

Funciona com~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/delacournz/warden/tree/HEAD/.claude/skills/demo-video

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

O que demo-video faz?

bun run demo <funcionalidade>/<clipe> grava uma tomada em um simulador iOS alugado e renderiza um vídeo 1920x1080: título à esquerda, um terminal que digita cada comando warden real e imprime sua saída quando a etapa termina, o telefone à direita (arredondado, com borda de 3px). Kit: scripts/demo/ (demo.ts CLI, record.ts, render.py, timeline.ts, demo.types.ts). Definições: apps/example/demos/<funcionalidade>/<clipe>.demo.ts (name deve ser igual a <funcionalidade>/<clipe>); ativos: apps/docs/public/demos/<funcionalidade>/<clipe>.mp4|jpg. Uma pasta por funcionalidade (biometrics, network, location, clock), minúsculas, hífens. Interno: sem changeset.

1. Quando criar um

  • Uma funcionalidade cujo efeito seja visível na tela e aconteça em segundos (biometria, alertas, gestos). Não usar para logs, números de desempenho ou qualquer coisa que uma tabela mostre melhor.
  • Após a interface do aplicativo ou o comportamento de um comando ter mudado: regravar, nunca editar o mp4.
  • Uma história por clipe, 10-25 s, 3-6 etapas legendadas.

Requer ffmpeg, ffprobe, uv (brew install ffmpeg uv), Xcode e as fontes em node_modules/@expo-google-fonts (bun install).

2. Escrever uma definição

Exportar demo: DemoDefinition (scripts/demo/demo.types.ts). Copiar apps/example/demos/biometrics/faceid.demo.ts.

CampoSignificado
title / subtitle / terminalTitle / footertexto estático; título é a afirmação principal, subtítulo uma linha
phoneCaptionlinha abaixo do telefone, padrão real simulator · not an animation; "" a oculta
claim{ biometrics?, profile? } -> warden claim ios --json --label demo-<funcionalidade>-<clipe> --ttl 30m [--biometrics]
appbundleId, kind (simulator padrão), project (cwd relativo ao repositório), deepLink aberto com --settle, appearance (light/dark, definido via simctl ui no udid alugado, restaurado depois)
setupwarden argv executado fora da câmera após a abertura do aplicativo, antes do deep link; ["ui","network","set","online","--relaunch"] dá o lançamento do dylib iOS que as etapas de rede/relógio precisam. Uma entrada é um argv ou { argv, optional: true }: uma etapa opcional que sai com código não zero é registrada (optional setup warden … -> <código>) em vez de falhar a tomada (um prompt de permissão que pode não aparecer)
readyseletores warden ui wait que devem passar após o deep link e antes do vídeo começar (ex.: id=bio-screen); uma etapa nunca pressiona uma tela que não montou
leadInMs / tailHoldMstempo ocioso antes da etapa 1 / último quadro mantido, preenchido pelo renderizador (a cauda da tomada tem 1 s)
steps[]argv (após warden; o gravador anexa --udid), output (ok desenha uma marca de verificação vetorial, plain é mudo, lines imprime várias linhas mudas), pauseAfterMs, shown?, hidden?, quote?
outrolinha # comment digitada após a última saída

output: { kind: "lines", text: [...] } imprime uma linha muda por entrada (mantenha cerca de 6; cada uma após a primeira custa 0,4 de um pitch de entrada no layout) para código ou texto mostrado de um arquivo. Combine com quote: { file } (caminho relativo a app.project): após a etapa, o gravador verifica se cada linha impressa, aparada, ocorre nesse arquivo e lança quoted line not in <arquivo>: <linha> caso contrário, garantindo que uma linha citada nunca seja inventada. As linhas ainda passam por maskSecrets.

Os tempos nunca são escritos à mão: o gravador mede o início e o fim de cada etapa em relação ao relógio do vídeo e a linha de saída aparece no FIM da etapa.

3. O loop

bun run demo <funcionalidade>/<clipe> --record-only      # claim, barra de status 9:41, lançar, gravar, liberar por id
bun run demo <funcionalidade>/<clipe> --render-only --sheet   # construir mp4 + jpg, escrever <work>/sheet.jpg
bun run demo <funcionalidade>/<clipe>          # ambos

Diretório de trabalho: $TMPDIR/warden-demo/<funcionalidade>/<clipe> (--out dir): take.mp4, steps.json, plan.json, sheet.jpg; em uma tomada com falha failure-snapshot.txt (erro + ui snapshot -i). Nada disso é commitado.

  1. Gravar.
  2. Ler sheet.jpg (blocos 4x4, 1 por segundo, pts queimados). Verificar contra o log: cada linha de saída aparece quando o telefone reage visivelmente; nenhum quadro mostra um alerta não relacionado, teclado, red box ou barra de status errada; o último quadro mostra o estado final.
  3. Para uma inspeção mais detalhada: ffmpeg -ss 7 -i take.mp4 -frames:v 1 f.png e Read.
  4. Corrigir na definição (pauseAfterMs, descartar/ocultar uma etapa, shown) e --render-only; regravar apenas quando a própria tomada estava errada. Correções em lote: uma gravação por rodada.
  5. Commitar o mp4 + jpg em apps/docs/public/demos/<funcionalidade>/.

4. Regras de honestidade

  • Todo comando impresso foi executado, contra o aplicativo real. Nenhum shown que difira em significado de argv (ele apenas corta flags como --timeout).
  • O texto de saída é o que o telefone fez, não o que o comando imprimiu. Se um toque não tem efeito visível (descartado durante a animação de entrada de uma folha, nada mudou), não legende: hidden: true, ou corrija o timing com um pauseAfterMs anterior mais longo.
  • Alertas de permissão de primeira execução aparecem apenas em uma instalação nova; o claim reutiliza um build aquecido, portanto não legende um alerta a menos que a tomada o mostre.
  • Nenhum bypass no lado do aplicativo ou prompt simulado: a afirmação no título deve ser verdadeira na tomada.
  • Segredos: maskSecrets oculta key=value, tokens estilo ghp_/sk- e hex/base64 longos nas linhas impressas. Ainda verifique o telefone em busca de tokens, e-mails, nomes de dispositivos.
  • Simulador real, interface iOS real. Nunca componha quadros falsos.

5. Pegadinhas (codificadas no kit)

  • O vídeo simctl é VFR e descarta a cauda ociosa: o renderizador normaliza o fps e preenche o último quadro (tpad) até a duração do plano.
  • O tempo do vídeo corresponde ao log de comandos dentro de ~0,5 s; o gravador inicia o relógio logo após video start.
  • Martian Mono não tem glifo de verificação: render.py o desenha como vetores.
  • O quadro é a paleta escura do Device Hub da documentação (Mona Sans + Martian Mono); defina app.appearance: "dark" para que o telefone corresponda. O gravador restaura a aparência do simulador depois.
  • Estouro do terminal: computeTerminalLayout reduz o pitch e a fonte para 17 px e rola as entradas mais antigas para fora.
  • Limite de vídeo do dispositivo 3 min; mantenha as tomadas abaixo de 40 s de qualquer forma.
  • Barra de status: xcrun simctl status_bar <udid> override é permitido em um udid alugado; o gravador a limpa depois.

6. Exportação para X

1920x1080, H.264 High, yuv420p, 30 fps, +faststart, sem áudio, <= 2:20, arquivo pequeno (kit visa <= 2 MB através de uma escada CRF 24..36). Verificar:

ffprobe -v error -show_entries stream=codec_name,profile,width,height,r_frame_rate -show_entries format=duration,size apps/docs/public/demos/<funcionalidade>/<clipe>.mp4

7. Ativos e incorporação na documentação

Saída: apps/docs/public/demos/<funcionalidade>/<clipe>.mp4 + <clipe>.jpg (pôster = último quadro), servidos em /demos/<funcionalidade>/<clipe>.mp4 (Vite public/). Embutir com o componente MDX DemoClip (apps/docs/src/components/demo-clip.tsx): reproduz automaticamente sem som e em loop, para no pôster em movimento reduzido. src não tem extensão; label diz o que o clipe mostra (corresponder à legenda). Um <video> bruto falha em apps/docs/src/lib/source.test.ts.

<DemoClip src="/demos/<funcionalidade>/<clipe>" label="warden ui … fazendo … em um simulador alugado" />

Adicione uma frase de legenda nomeando os comandos. O carretel em lote apps/docs/public/demos/e2e/batch.* (embutido com <DemoReel>) é um pipeline diferente (bun run demo:compose).

8. O carretel warden dev (dev/hero)

Um terceiro pipeline: o painel de desenvolvimento REAL do warden (esquerda, em um cartão de terminal) ao lado do simulador que warden dev reivindica a si mesmo (direita). Nada é digitado ou falsificado: dev-take.ts inicia warden dev ios --label demo-dev-<clipe> --headless --no-shutdown em um PTY (Bun.spawn({ terminal })) espelhado em @xterm/headless (leituras de quadro para as esperas) e escreve cada bloco do PTY em tui.cast; a origem do relógio é o início. Ele consulta warden ls --json pela concessão de dispositivo rotulada, então define escuro + 9:41, desinstala o aplicativo e inicia ui screen video (o telefone fica preto até então). O filho dev recebe seu próprio WARDEN_SESSION_ID: warden dev adota uma concessão de dispositivo mantida pela sessão do chamador, e cada agente de equipe compartilha uma, então sem isso a tomada poderia dirigir o simulador de outra pessoa.

bun run demo:dev dev/hero --record-only        # claim, desinstalar app, 9:41 + escuro, gravar, liberar por id
bun run demo:dev dev/hero --render-only --sheet # agg + ffmpeg -> apps/docs/public/demos/dev/hero.{mp4,webm,jpg}, <work>/sheet.jpg
bun run demo:dev dev/hero                      # ambos
  • Kit: dev-demo.ts (CLI), dev-take.ts (gravador, runBeats), dev-render.ts (agg + compor, DEV_LAYOUT), dev-frame.py (cartão, anel, máscara), dev-timeline.ts (texto do cast, segmentos de rampa, legendas). Tipos: DevDemoDefinition / DevBeat em demo.types.ts. Definição: apps/example/demos/dev/<clipe>.demo.ts; diretório de trabalho $TMPDIR/warden-demo/dev/<clipe> (take.mp4, tui.cast, dev.json, sheet.jpg, failure-frame.txt).
  • Um beat = waitScreen (regex sobre o quadro TUI) + waitPhone (seletores ui wait) -> ação (keys na TUI, e/ou um edit real de um arquivo do repositório) -> seletores afterPhone (o pouso do hot reload) -> pauseAfterMs. dev.json contém o início / pronto / fim de cada beat no relógio do vídeo; as legendas vão do início da ação de um beat até o próximo.
  • A edição é uma edição real do código fonte (string do corpo de welcome.tsx), revertida byte a byte em finally; verifique se git diff apps/example/src está vazio após uma tomada.
  • uninstallFirst remove o aplicativo do sim alugado para que warden dev instale o build de desenvolvimento do cache na câmera. A PRIMEIRA tomada em uma impressão digital é um build local (minutos, a linha dev build compila): grave uma vez para aquecer o cache, depois regrave. warden app ensure --kind dev não aquece o cache que warden dev lê.
  • Rampas de velocidade: DevBeat.ramp: N acelera a espera desse beat por N (mantendo 1,2 s de cabeça e 1 s de cauda em velocidade real) e o carretel desenha uma etiqueta × N speed. Uma tomada aquecida que é lenta na espera do cliente dev a acelera. As teclas e o hot reload são sempre em tempo real.
  • Honestidade: os fatos na legenda <DemoReel> vêm das linhas TUI da tomada (dev build 3.5s, Metro 3.9s, dev client 161ms) e dev.json (edit -> ui wait vê o novo texto: 0,76 s). Releia-os após cada regravação; eles mudam.
  • Limites: o painel não tem linha de claim separada (está dentro de preparing the ios dev build); --headless para que nenhuma janela do Device Hub seja aberta; PTY 100x26 (100 colunas é o mínimo do HUD). Um beat com stopVideoFirst para o vídeo antes de sua ação (q encerra a concessão e warden ui então se recusa). Após q a tomada verifica se warden ls não possui nenhuma concessão da sessão (falha caso contrário), e o cast é cortado onde o painel deixa a tela alternativa para que o carretel termine em seu último quadro. A tela de carregamento branca do launcher dev e o banner azul Refreshing… são o aplicativo real; um recarregamento do Metro também pode vir de outro agente editando apps/example/src.
  • Rampas: ramp só se aplica quando a espera, menos sua cabeça de 1,2 s e cauda de 1 s, tem pelo menos 6 s (RAMP_MIN_S). O herói acelera sua espera do cliente dev em 3x com uma etiqueta.
  • devEnv adiciona env ao filho warden dev; o herói define EXPO_NO_DEPENDENCY_VALIDATION=1 (@expo/cli checkDependenciesOnStart) para remover o aviso "update available" do Expo do painel Metro. Use-o apenas para ruído, nunca para comportamento.
  • Embutir com <DemoReel src="/demos/dev/hero" command="warden dev ios" facts="…" label="…" /> (herói da página inicial, sims/dev.mdx, getting-started/quickstart.mdx). Pôster = o quadro da ação posterBeat + 1 s (HUD ligado, todas as linhas concluídas, aplicativo em execução).

9. Clipes com muito terminal (VID-6)

Todos comandos reais; cada carretel mostra apenas o que foi executado. Um clipe de cada vez, liberar por etiqueta após cada.

ClipePipelineComando
e2e/parallelbatch-take.ts: warden e2e <suíte> --count N --record --headless em <work>/rec, estilizando cada sim alugado escuro + 9:41 enquanto inicializa, depois compose.ts --height 1080bun run demo:batch e2e/parallel --suite example --specs devtools,gestures,onboarding,sign-in,todo-detail --count 2 (--record-only / --compose-only; cabeçalho mostra contagem de specs; contador lê N/total shards done quando specs diferem de jobs)
dev/worktreesLanesDemoDefinition: um recordDev por faixa concorrentemente, uma faixa de shell (warden ls) abaixobun run demo:lanes dev/worktrees (lanes.ts, lanes-frame.py, lanes-demo.ts; faixas compartilham um relógio via DevTake.startedAtS)
golden/cloneDevDemoDefinition.script: um script shell é executado no PTY em vez de warden devbun run demo:dev golden/clone
debug/failurescript + rawVideobun run demo:dev debug/failure --sheet
  • Modo script: sem desinstalação, sem verificação de liberação; a tomada termina com release --mine --label L --shutdown e sims delete <udid> --yes. Um beat com waitBoot consulta simctl io screenshot até que o PNG exceda 100 KB (HOME_SCREEN_MIN_BYTES): um simulador headless recém-clonado mostra o logotipo da Apple por 10+ s após claim retornar. Acelere essa espera (3x, com etiqueta).
  • rawVideo: grava com simctl io recordVideo (SIGINT para parar) porque o auxiliar ui screen video do warden entra em conflito com warden e2e em um dispositivo.
  • --record só grava dispositivos presentes no início; participantes tardios elásticos não têm vídeo. O gate de capacidade do host define quantos cabem, então não peça mais sims do que os que foram inicializados no início (máx. 2 aqui).
  • A spec descartável para debug/failure é escrita em apps/example/e2e/debug-demo.e2e.ts e removida por um trap do shell; verifique git status depois (uma tomada morta pode deixá-la).
  • Embutir com <DemoReel src="/demos/<funcionalidade>/<clipe>" command="…" facts="…" label="…" />; os fatos vêm da tomada, releia após cada regravação.
  • demo:batch estiliza sims com um poller (styleWhenBooted: warden ls --json pela etiqueta, simctl list para Booted, depois ui appearance dark + status_bar override 9:41) porque warden e2e não tem hook por dispositivo; ele libera --mine --label demo-<funcionalidade>-<clipe> --shutdown em finally. Use specs que passam em um sim limpo (perf, settings, todos não são confiáveis); a flag de composição --height 1080 fornece o layout 16:9 (terminal...

Habilidades Relacionadas