HUB DESATIVADO (02/10/2026, por ordem do usuário): NinjaBR Hub está SEM COTA e os perfis
hub/hubv2/hubtsforam removidos (backup em~/.agent-profiles/_removidos-hub-2026-10-02/). NÃO use Hub, NÃO executehub-livre.sh(sai com erro), NÃO espere perfil do Hub liberar. Imagem: ChatGPT — se a cota de upload de uma conta acabar, troque de CONTA:gpt-noelia-7-10*↔gpt-noelia.empt*. Vídeo: Google Flow (flow-video-playwright).
flow-video-playwright
Mecânica de geração de imagem+vídeo via Google Flow (labs.google/fx/pt/tools/flow). Alternativa ao hub-video-playwright quando o Hub está expirado (morreu 21/09/2026, veja [[feedback-ninjabr-hub-acesso-expirado]]).
Perfil
flow-enovalabs-pro (porta 9265), já logado no Google. Antes de gastar tempo, navegue e confirme o login:
agent-browser --session flow-enovalabs-pro navigate "https://labs.google/fx/pt/tools/flow"
Se cair na tela de login, pare e avise o usuário — as credenciais não estão em .credentials.env.
Modelo
Omni 1.1 Flash — confirmado em produção em 30/09/2026. Custa menos créditos que o Veo 3.1. É multimodal: gera imagem E vídeo a partir do prompt puro, sem upload.
Antes da geração, abra as configurações (botão tune, aria-label "Configurações") e garanta:
- Confirmar antes de gerar = Nunca (o padrão vem como "Sempre" — gasta crédito e bloqueia o envio se deixar em Sempre).
- Modelo padrão de geração de vídeo = Omni 1.1 Flash.
- Proporção de vídeo = crop_9_16 9:16 (radio role="radio", o SEGUNDO crop_9_16 da lista, índice 10 no mapeamento atual).
Salve.
Fluxo (passo a passo verificado em 01/10/2026)
- Criar projeto: dashboard → botão "Novo projeto" → URL vira
https://flow.google.com/project/<uuid>. - Composer:
[contenteditable=true]. Cole via clipboard, NÃO useinnerText— medido em 01/10:c.innerText = prompt; c.dispatchEvent(new InputEvent('input'))cola o texto, mas o botão "Iniciar geração" NÃO trava como disabled ao clicar (o handler de "text changed" do Material não dispara). 2 cliques não geram nada.- Caminho correto:
clipboard writenativo doagent-browser, depoisCtrl+A Ctrl+Vno composer:agent-browser --session flow-enovalabs-pro clipboard write "$PROMPT" agent-browser --session flow-enovalabs-pro click "[contenteditable=true]" agent-browser --session flow-enovalabs-pro press "Control+a" agent-browser --session flow-enovalabs-pro press "Control+v" - Confirme
txtLen === prompt.lengthantes de enviar.
- Enviar:
click button[aria-label='Iniciar geração']. O botão trava comdisabled=true= envio aceito. O texto "Na fila" aparece, depois "Conceptualizing the Video" / "play_circle" + percentual. - Aguardar: o status muda para "Conceptualizing the Video" (Flow) ou "Criando imagem" (ChatGPT). O botão "Parar" só aparece enquanto está gerando. ~50s para imagem apenas com prompt, mais para vídeo.
- Resultado:
<img src="blob:https://flow.google.com/...">comnaturalWidth>800quando é 9:16. Baixe via fetch+blob+<a download>(link DOM nativo).
Configurações obrigatórias (antes do primeiro envio)
Abra button[aria-label='Configurações'] (ícone tune):
- Confirmar antes de gerar = Nunca (radio group
mat-radio-group-2,value=2). O padrão vem como "Sempre" — gasta o crédito E bloqueia o envio automático. - Modelo padrão de geração de vídeo = Omni 1.1 Flash (combobox).
- Proporção de vídeo = crop_9_16 9:16 (button role="radio", SEGUNDO crop_9_16 da lista, índice 10).
Defina os radios via input[type=radio] diretamente (clicar no mat-radio-button não altera o input):
// Nunca
const inputs = Array.from(document.querySelectorAll('input[type=radio]'));
inputs.find(r => r.closest('mat-radio-button')?.innerText.startsWith('Nunca')).click();
// 9:16 vídeo (segundo crop_9_16)
const r916 = Array.from(document.querySelectorAll('button[role=radio]'))
.filter(b => b.innerText === 'crop_9_16\n9:16')[1];
r916.click();
// Salvar
Array.from(document.querySelectorAll('button')).find(b => b.innerText?.trim() === 'Salvar').click();
Upload de referência — bloqueado em 30/09/2026
O botão "Enviar mídia" do painel "Adicionar elementos" abre o seletor de arquivos nativo do SO (CDP Page.fileChooserOpened, não <input type=file> no DOM). agent-browser upload falha silenciosamente.
Workaround: gere apenas com prompt. O OmniFlash é multimodal e interpreta o prompt descritivo. Não é tão fiel à referência quanto o upload seria, mas evita o seletor de arquivos. Escalone o browser-harness com Page.setInterceptFileChooser se o prompt-only não for suficiente — não testado em produção.
Prompt-only — template base
A <tipo de plano> shot of <composição>. <sujeito> doing <ação única, lenta>.
<cenário>. <luz>. Shot in vertical 9:16 aspect ratio. Photorealistic,
shot on iPhone, no text, no watermark. <anti-morphing>.
Anti-morphing obrigatório (o Flow não tem campo NEGATIVE separado, coloque na mesma string):
- "Hand never clips through the fabric"
- "Garment stays flat / never floats unsupported"
- "Single continuous motion, no jumping"
Validado em 30/09/2026: cena 1 (mão feminina puxando tecido canelado rosa, fundo cama branco) gerada em ~50s no OmniFlash, saída 941x1672, visual iPhone UGC convincente.
Download (verificado em 01/10/2026)
O vídeo não renderiza <video> na grade — só após abrir o card no modal. Fluxo:
- Clique na thumbnail (
img[alt="Miniatura do vídeo gerada"]) — abre o modal com player + botões no cabeçalho. - Clique
button[aria-label="Baixar mídia"](ícone de download no cabeçalho). - Abre o menu de qualidade: 270p (GIF), 720p (Tamanho original), 1080p (Aprimorada), 4K (Aprimorada).
- Clique na opção certa via DOM direto:
Array.from(document.querySelectorAll('button')) .find(b => b.innerText.trim() === '720p\nTamanho original').click(); - O arquivo cai em
~/Downloads/com o nome do título do Flow + timestamp (Hands_lifting_brown_skirt_fabric_20261001180016.mp4). Mova para o staging do projeto e renomeie.
Para imagem (gerada diretamente, sem vídeo): <img src="blob:https://flow.google.com/..."> aparece na grade após gerar. Salve via fetch+blob+<a download> OU screenshot direto.
Gate de cor após o download
Quase toda mídia do Flow sai com color_range/color_space/color_transfer/color_primaries = unknown — confirmado em 01/10/2026 no Flow Omni 1.1 Flash. O gate feedback-metadado-cor-video-final reprova. Re-encode para bt709 ANTES de marcar o clipe como pronto:
ffmpeg -nostdin -i cena-1.mp4 \
-c:v libx264 -crf 18 -preset veryfast -pix_fmt yuv420p \
-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709:fullrange=off" \
-color_primaries bt709 -color_trc bt709 -colorspace bt709 -color_range tv \
-c:a copy cena-1-fixed.mp4
mv cena-1-fixed.mp4 cena-1.mp4
Confira com ffprobe -show_entries stream=color_* — os 4 campos devem estar como bt709/tv.
Pegar a lane — PRIMEIRA COISA, SEMPRE
Vários Clientes Claude usam este mesmo navegador. Sem lane, todos caem no mesmo projeto: o criar projeto de um sobrescreve o do outro, e as gerações saem trocadas.
~/.claude/bin/lane.sh take flow <lane> # ex.: lane.sh take flow pov-bella
S=flow-<lane> # use $S em TODO comando daqui pra frente
Perfil persistente ~/.agent-profiles/flow-enovalabs-pro, porta 9265, URL https://labs.google/fx/pt/tools/flow. Nunca use agent-browser open, press Escape (envia a aba para about:blank e quebra a lane). Ao terminar: ~/.claude/bin/lane.sh drop flow <lane>.
A área de download do Flow é FLOW, não HUB nem PLAYWRIGHT. Exporte uma vez no início:
export LANE_AREA=FLOW
O arquivo sai do Flow com o nome do título + timestamp (ex.: Hands_lifting_brown_skirt_fabric_20261001180016.mp4), cai em ~/Downloads/, e o lane.sh collect move para ~/Downloads/FLOW/$LANE/ → pasta do projeto. Nada fica solto em Downloads.
CHECK-LIST DE ENVIO — execute ANTES de cada clique em "Iniciar geração"
O Flow não limpa nada entre gerações (igual o Hub): o prompt do composer fica grudado, o modelo/proporção podem ser resetados pelo re-render após o envio. A ordem abaixo existe porque cada passo fora do lugar já custou crédito (cota cobrada mesmo em geração inválida).
1. CRIE um projeto novo (ou mude para um existente) — cada leva usa um projeto. Botão "Novo projeto" no dashboard. URL vira https://flow.google.com/project/<uuid>. Reutilizar um projeto velho acumula lixo e confunde a diff de src do download (veja adiante).
2. LIMPE o composer do projeto novo:
agent-browser --session $S click "[contenteditable=true]"
agent-browser --session $S press "Control+a"
agent-browser --session $S press "Delete"
Confirme:
echo '(()=>document.querySelector("[contenteditable=true]").textContent.length)()' \
| agent-browser --session $S eval --stdin # deve retornar 0
3. VERIFIQUE as configurações: "Confirmar antes de gerar" = Nunca, modelo = Omni 1.1 Flash, proporção = crop_9_16 9:16. A skill abre com padrões errados (Sempre + modelo Lite + 16:9) — veja o bloco Configurações obrigatórias.
4. ESCREVA o prompt via clipboard (não innerText) e CONFIRME — txtLen === prompt.length antes de enviar. Detalhes no bloco Fluxo abaixo.
5. ENVIE e confirme que o composer travou em disabled:
agent-browser --session $S click "button[aria-label='Iniciar geração']"
sleep 2
echo '(()=>{const c=document.querySelector("[contenteditable=true]");return c.contentEditable==="false"||c.getAttribute("aria-disabled")==="true"?"travado":"livre";})()' \
| agent-browser --session $S eval --stdin
ESPERA: três estados, três condutas
Depois de enviar, o status na página passa por três fases. Confundi-las faz você abortar cedo (perder crédito) ou esperar demais (achar que travou quando já saiu).
| Estado | Texto na tela | Conduta |
|---|---|---|
| Fila | "X foi agendado e está na fila devido à alta demanda. Sinta-se à vontade para verificar o status daqui a alguns minutos." | Espere. Pode levar de 1 a 30min. NÃO reenvie. |
| Gerando | "Conceptualizing the Video" + ícone play_circle + percentual | Espere. Botão "Parar" visível. |
| Pronto | Card com <img alt="Miniatura do vídeo gerada"> aparece na grade | Baixe. Veja o bloco de download. |
Limite máximo: 30 minutos na fila sem transição para "Gerando" = abortar. Medido em 02/10/2026: o Flow mostrou "alta demanda" por 45min em flow-enovalabs-pro, e quando moveu o status de fato o job já tinha consumido o crédito sem entregar nada. Sintoma: "alta demanda" >30min E o botão "Iniciar geração" da composição anterior continua disabled=true travado — não é fila real, é cota perdida.
Caminho de recuperação se travar:
# 1. Cancelar o job pendente (libera o composer para novo envio)
agent-browser --session $S click "button[aria-label='Parar']" 2>/dev/null
sleep 2
# 2. Se o composer não destravar, recarregue a página inteira (perde o projeto atual)
agent-browser --session $S navigate "https://labs.google/fx/pt/tools/flow"
# 3. Tente um clone paralelo
S=flow-<lane>-2 # ou -3, -4
Se o flow-enovalabs-pro-2/3/4 também estiver logado e na fila, use-o em paralelo (o Flow permite múltiplos perfis do mesmo login, diferente do Hub). Confirme o login: lane.sh take flow <lane> && agent-browser --session $S navigate "https://labs.google/fx/pt/tools/flow" — se cair no dashboard sem pedir login, está OK.
Polling curto, teto generoso:
T0=$(date +%s)
for i in $(seq 1 360); do # 30min × 6 = 360 × 5s
STATE=$(echo '(()=>{const txt=document.body.innerText;
if(/alta demanda/i.test(txt)) return "fila";
if(/Conceptualizing|Criando/i.test(txt)) return "gerando";
const imgs=document.querySelectorAll("img[alt=\\"Miniatura do vídeo gerada\\"]").length;
if(imgs>0) return "pronto";
return "?")()' \
| agent-browser --session $S eval --stdin | tr -d '"' | tail -1)
echo " [$(( $(date +%s)-T0 ))s] $STATE"
[ "$STATE" = "pronto" ] && break
[ "$STATE" = "?" ] && [ $i -gt 6 ] && { echo "abort: composer travado sem estado"; break; }
sleep 5
done
DOWNLOAD pelo CONTEÚDO — guarde antes, escolha o novo
vs[vs.length-1] não existe no Flow (não há <video> na grade). O Flow renderiza uma thumbnail (<img alt="Miniatura do vídeo gerada">) na grade; o vídeo real só aparece depois de abrir o card no modal e clicar em Baixar mídia. Logo, o caminho de download é diferente do Hub:
1. ANTES do envio, guarde a lista de thumbnails existentes:
BEFORE=$(echo '(()=>[...document.querySelectorAll("img[alt=\\"Miniatura do vídeo gerada\\"]")].map(i=>i.src).join("|"))()' \
| agent-browser --session $S eval --stdin | tr -d '"')
2. DEPOIS que pronto sair, encontre a thumbnail nova:
AFTER=$(echo '(()=>[...document.querySelectorAll("img[alt=\\"Miniatura do vídeo gerada\\"]")].map(i=>i.src).join("|"))()' \
| agent-browser --session $S eval --stdin | tr -d '"')
NEW=$(python3 -c "import sys;a=set('$AFTER'.split('|'));b=set('$BEFORE'.split('|'));print('|'.join(a-b))")
Se NEW vier vazio, o card ainda não apareceu — espere mais 10-30s e tente novamente.
3. Abra o modal do card novo e baixe 720p:
# clique no thumbnail novo
python3 -c "
import sys
srcs='$NEW'.split('|')
print('(()=>{const imgs=[...document.querySelectorAll(\"img[alt=\\\\