sapiens-mcp 1.98.1 → 1.100.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/registry.js CHANGED
@@ -126,7 +126,7 @@ export const TOOLS = {
126
126
  handler: search,
127
127
  },
128
128
  sapiens_studios: {
129
- description: "O SEU studio (o perfil de EMPRESA da casa do user) + o catálogo dos estúdios/experimentos Sapiens. Sub-actions: mine (o studio do user: nome/endereço público/marca/operador/tamanho do time), create (FUNDA um studio novo, exige name; nasce NO AR com endereço público, sem rascunho, de graça, teto de 8 por pessoa), list (catálogo com URL+status+tags+mcpReady), get (detalhe de 1 slug), publishable_url (formata URL /articles/<slug>). Use quando user pergunta 'que estúdios existem', 'qual o meu studio', ou diz 'quero abrir/fundar meu studio', 'criar a página da minha empresa'. Estúdios cobertos: helen-voice, musicator, persona-sapiens, personagem-atlas, sapiens-shorts, sapiens-video, text-post-builder, carrosel-editorial, comunidade, repertorio.",
129
+ description: "O SEU studio (o perfil de EMPRESA da casa do user) + o catálogo dos estúdios/experimentos Sapiens. Sub-actions: mine (o studio do user: nome/endereço público/marca/operador/tamanho do time), create (FUNDA um studio novo, exige name; nasce NO AR com endereço público, sem rascunho, de graça, teto de 8 por pessoa), list (catálogo com URL+status+tags+mcpReady), get (detalhe de 1 slug), publishable_url (formata URL /articles/<slug>). Use quando user pergunta 'que estúdios existem', 'qual o meu studio', ou diz 'quero abrir/fundar meu studio', 'criar a página da minha empresa'. Estúdios cobertos: helen-voice, musicator, persona-sapiens, sapiens-video, text-post-builder, carrosel-editorial, comunidade, repertorio.",
130
130
  schema: studiosSchema,
131
131
  handler: studios,
132
132
  },
@@ -136,7 +136,7 @@ export const TOOLS = {
136
136
  handler: persona,
137
137
  },
138
138
  sapiens_helen: {
139
- description: "Helen Voice TTS via ElevenLabs, Google Gemini ou Fish Audio (qualquer logado; cobra Sinapses). Sub-actions: list_presets (catálogo de voiceIds/voiceNames recomendados + stylePreambles), speak (sintetiza, retorna audioBase64+mimeType+sizeBytes). provider=fish é o S2.1 Pro, o mais forte em japonês, mandarim e coreano: fala de personagem e cena de anime pedem ele. `language` escolhe o idioma (auto|pt|en|ja|es|ko|zh|fr|it|de). A fala sai sempre em Sinapses: a casa não recebe chave de provedor, e clientApiKey é recusado sem cobrar. Custo: ElevenLabs ~$0.30/500c, Google ~$0.01/500c, Fish grátis via API até 31/08/2026. Max 5000 chars (quebra antes em chunks). text pré-processado pelo caller (sem em-dash, sem markdown).",
139
+ description: "Helen Voice TTS via ElevenLabs, Google Gemini ou Fish Audio (qualquer logado; cobra Sinapses). Sub-actions: list_presets (catálogo de voiceIds/voiceNames recomendados + stylePreambles), speak (sintetiza, retorna audioBase64+mimeType+sizeBytes). provider=fish é o S2.1 Pro, o mais forte em japonês, mandarim e coreano: fala de personagem e cena de anime pedem ele; com modelId='drama-3' vira o Drama 3, a fala dirigida (direção escrita no próprio text, [sussurrando] ou <emphasis>palavra</emphasis>). `language` escolhe o idioma (auto|pt|en|ja|es|ko|zh|fr|it|de). A fala sai sempre em Sinapses: a casa não recebe chave de provedor, e clientApiKey é recusado sem cobrar. Custo pelo tamanho do texto, igual em todo motor e modelo: 500 Sinapses por 1.200 caracteres, mínimo 50 (a fala cheia de 5.000 caracteres sai por 2.084 Sinapses); fala repetida sai do cache sem cobrar. Max 5000 chars (quebra antes em chunks). text pré-processado pelo caller (sem em-dash, sem markdown).",
140
140
  schema: helenSchema,
141
141
  handler: helen,
142
142
  },
@@ -146,7 +146,7 @@ export const TOOLS = {
146
146
  handler: musicator,
147
147
  },
148
148
  sapiens_video: {
149
- description: "Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as Sinapses da sua conta). Sub-action 'create' (recomendada): escolhe modelo + config e gera num call (cria a row + renderiza). Modelos: 'sapiens-video-seedance' (Seedance 2.0, cena+áudio nativo, 4-15s, 480/720/1080p, t2v/i2v; aceita até 4 imagens de REFERÊNCIA via referenceImageIds/referenceImageUrls/referenceImagePaths (o H3 aceita até 9, os Veo Fast/Quality e o Omni 1.1 até 3; Lite, Kling, Hailuo, WAN 2.5 e 3.0 e os dois Spicy não), que guiam estilo/personagem/composição SEM virar o 1º frame — é o fluxo STORYBOARD: gere a folha de key poses com sapiens_image templateSlug='storyboard-sapiens-v1', passe folha + personagem como refs num t2v e descreva o take contínuo no prompt, citando as refs por descrição e mandando ignorar o traço do sketch; aceita também 1 VÍDEO DE MOVIMENTO via referenceVideoUrls (role 'refvideo' -> reference_videos, <=15s, host da casa): a coreografia/câmera do clipe guia o take, combinável com a folha), 'sapiens-video-seedance-2-fast' e 'sapiens-video-seedance-2-mini' (os irmãos do 2.0: MESMO repertório completo, incluindo referência, frame final e vídeo de movimento; o Fast custa 20% menos e o Mini METADE, ambos com teto 720p — pedir 1080p neles entrega e cobra 720p. Use o Mini pra iterar enquadramento/prompt barato e feche no 'sapiens-video-seedance' quando o take estiver certo), 'sapiens-video-seedance-25' (Seedance 2.5, a geração SEGUINTE e não um quarto tier da 2.0: take de 4 a 30s num fôlego, edita e estende vídeo, mesmo repertório de referência mais ÁUDIO como referência; teto 720p e ~1,5x o preço por segundo do 2.0. Duração é o que pesa aqui: 30s em 720p passa de 40 mil Sinapses, então confirme a duração com a pessoa antes de disparar. Quem precisa de 1080p fica no 'sapiens-video-seedance'), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-hailuo' (Hailuo 2.3 da MiniMax, física e movimento em 768p, 6 ou 10s, t2v/i2v) e 'sapiens-video-hailuo-pro' (o mesmo em 1080p, 5s fixo — duração não é param aqui): motores PUROS, sem áudio nativo, sem imagens de referência e sem frame final, então quem precisa disso fica no Seedance 2.0; o Pro é o 1080p mais barato da casa, 'sapiens-video-h3' (MiniMax H3: som nativo incluso sem toggle, 5 a 15s em '2k' ou '768p' (sem resolution cai no 2k, que custa 33% mais por segundo), t2v/i2v e frame final; ACEITA até 9 imagens e 3 vídeos de referência (somando 15s), o caminho de take longo com personagem travada; referência e frame inicial são exclusivos, mande um dos dois; o 2K de 15s leva uns 9 minutos de render, acompanhe em status e não gere de novo), 'sapiens-video-h3-spicy' (MiniMax H3 Spicy: o H3 SEM freio de conteúdo, i2v puro a partir de uma imagem sua, que é o que trava a identidade; 3 a 15s, 480p/768p/1080p, som nativo, frame final; o segundo de vídeo mais barato e o render mais rápido da casa), 'sapiens-video-seedance-spicy' (Seedance 2.0 Spicy: o 2.0 SEM freio, também i2v puro, som nativo; 4 a 15s em 480p e 720p, para em 10s em 1080p; sem frame final; o som tem interruptor no mesmo preço), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), 'sapiens-video-wan-3' (WAN 3.0: texto ou imagem, som nativo com interruptor no mesmo preço, frame final, 5/8/10s em 480/720/1080p; sem imagem de referência), 'sapiens-video-kling-motion' (Motion transfer: passa o movimento de um vídeo pra uma imagem, PRECISA de pessoa com tronco visível na imagem E no vídeo), 'sapiens-video-shot-mimic' (Shot Mimic: recria o plano/câmera/cortes de um vídeo de referência como cena nova), 'sapiens-video-omni-11' (Gemini Omni 1.1: texto ou imagem vira vídeo de 4, 6, 8 ou 10s (sem durationSec, 10s) com áudio nativo, de '360p' a '4k' (sem resolution, 720p; 360p custa 1/3 do 720p, 1080p 1,5x, 4k 3x); aceita até 3 imagens de referência; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente; e VÍDEO-FONTE: sourceVideoId (vídeo seu da galeria) ou sourceVideoUrl (link da casa), mp4 até 10 s, editado (sourceVideoMode 'edit', o padrão, ex: trocar a pessoa do vídeo pelo personagem da imagem de referência, cobrado pelo tamanho do clipe) ou estendido ('extend', cobrado pelo durationSec do pedaço novo)) e 'sapiens-video-omni' (o 1.0 preview: mesmo preço e mesmo repertório, desligado por default, existe pra comparar com o 1.1), 'sapiens-video-lite/fast/quality' (Veo 3.1: 4, 6 ou 8s, sem durationSec sai 8s; com imagem de referência ou frame final o Google só faz 8s). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'; no H3 '768p'/'2k'; no Omni 1.1 '360p' a '4k'), audio, aspectRatio. RECEITA DE TAKE (templateSlug + brief), o irmão do templateSlug da imagem: em vez de escrever o prompt inteiro, passe templateSlug e a receita travada da casa embrulha a cena com estilo, cenário, arco, áudio e look, e ainda escolhe o motor (por isso model fica opcional). O `prompt` vira só a CENA e o `brief` preenche os campos do formato (subject, persona, hook, shots com voiceLine e propVisible, uvps, language, energy); campo vazio some do prompt em vez de virar buraco. Receitas de hoje: 'ugc-vertical-v1' (selfie que fala, o formato nativo de Reels/TikTok/Shorts), 'unboxing-vertical-v1' (mãos e reveal, som real do papel e do lacre), 'app-demo-vertical-v1' (a tela do app legível na mão da pessoa) e 'reflexao-vertical-v1' (talking-head lento pra ideia ou ensaio). Override de model/aspectRatio/durationSec/resolution vale dentro do que a receita aceita, e o erro lista as opções. Sub-action 'templates' (sem custo, sem login) traz o catálogo vivo com spec default, whitelist e os briefFields de cada uma. Isto substitui a tool sapiens_shorts, que era admin-only e só falava Veo 3.1 Fast; o mesmo UGC de 8s sai a 720p no Seedance 2.0 Mini por bem menos. FRAME INICIAL/FINAL POR REFERÊNCIA (recomendado): startImageId/endImageId (id da sua galeria) ou startImageUrl/endImageUrl (url de galeria/Acervo/personagem) — resolvidos server-side igual à imagem, descubra via sapiens_reference. FRAME POR ARQUIVO LOCAL (só no MCP instalado/stdio, não no remoto): startImagePath/endImagePath = caminho absoluto de uma imagem no seu PC (PNG/JPEG/WebP até 8MB); o processo lê o arquivo e sobe como frame inicial/final, igual a subir no gerador do site — 1 imagem inicial + 1 final por vídeo, então pra vários vídeos rode create uma vez por imagem. No remoto use id/url. Alternativa base64: references (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Suporte a frame final varia por modelo. Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}. VITRINE (sem custo): sub-action 'demos' lista os SEUS demo films (kind=demo do Estúdio de Vídeo) com slug + estado de vitrine; sub-action 'showcase' põe/tira um demo (por slug) do mini-cinema da /conectar-claude, com showcaseTag (chip de capacidade) e showcaseOrder (ordem asc). Fluxo: 'demos' pra achar o slug, depois 'showcase' com showcase=true. Só entra na vitrine pública se for a conta da casa. VÍDEOS PROGRAMÁTICOS (ADMIN, sem custo): o Lab do Estúdio de Vídeo (/experimentos/films, tabela videoSpecs, 5 kinds: demo | aula-tour | essay | tipografia-musical | dataviz) opera por aqui sem browser — 'film-list' (todos os kinds; filtros filmKind/filmStatus), 'film-get' (spec inteiro por slug), 'film-upsert' (cria/atualiza por slug, idempotente; spec = objeto JSON no shape do 'Copiar spec' da tela, validação no servidor), 'film-status' (produção por slug: filmStatus + videoUrl + durationSecMeasured; o fecho do render é os três num call), 'film-publish' (Acervo aba Fitas + portfólio; exige pronto+URL), 'film-delete' (limpar rascunho). O RENDER do filme segue no agente local (skill /film, repo da casa): o MCP registra e fecha o ciclo, não renderiza. create é ASSÍNCRONA: cria o row, debita e volta NA HORA com {imageId, status:'rendering', cost} (não espera o render, que leva de segundos a minutos). Acompanhe com a sub-action 'status' (imageId) até status='completed' (traz a url) ou 'error'/'blocked'. NÃO chame create de novo enquanto renderiza (cria outro vídeo e cobra de novo); falha de provider refunda sozinha. EDIÇÃO (as ações da ficha do site, cada uma vira peça nova e o original fica): 'trim' (imageId + startSec, endSec opcional: corta um trecho; grátis), 'join' (imageIds: 2 a 8 vídeos SEUS na ordem, até 180 s somados: compilado; grátis), 'fecho' (imageId + ending 'marca'/'sapiens'/'none', watermark, musicUrl de trilha da casa, nameCard {name, subtitle}: fecha o vídeo com a assinatura da casa; grátis), 'render-ref' (imageId do take + refUrl da folha que o gerou: a peça com a referência ao lado; cobra Sinapses, o mesmo par já feito volta de graça). Síncronas, podem passar de 2 min: Timeout é o cliente desistindo, olhe sapiens_gallery action=list kind=video antes de repetir. RESOLUÇÃO: 'upscale' (imageId de vídeo SEU completed + resolution = o alvo, '720p', '1080p', '2k' ou '4k') gera uma VARIANTE nova em resolução maior, o original fica intacto; cobra Sinapses por segundo do clipe, mais caro quanto maior o alvo, assíncrona como o create (acompanhe em status). SOM: 'sonorize' (imageId de vídeo SEU completed + prompt do som da cena) gera uma VARIANTE nova com trilha sincronizada (20 Sinapses/s, o original fica intacto; sonorize sempre o original, nunca uma variante). ADMIN: 'shadows' (videoUrl + title) extrai o depth map (depth-map) de um vídeo: entra na sua timeline de vídeos e no Acervo como driving reutilizável; 'shadows-list' lista os depth maps prontos. PREÇO DE VÍDEO É FAIXA, NÃO NÚMERO: nos motores por segundo (todo Seedance, Kling, WAN 2.5 e 3.0, Hailuo, H3, os dois Spicy, Shot Mimic, e desde out/2026 o Veo 3.1 e o Gemini Omni) a conta é duração x resolução [x áudio], e OMITIR não pega o barato: sem 'resolution' o servidor cobra o tier MAIS CARO do motor (menos o Gemini Omni 1.1, que fica no 720p), sem 'durationSec' cobra o piso de duração (no Veo e no Omni, o padrão: 8s e 10s). Um create de Seedance 2.0 sem resolution debita 1080p. Nunca prometa um valor sem cotar: sub-action 'price' (sem custo, sem login) devolve o número EXATO que o create vai debitar pra model + durationSec + resolution + audio, e avisa (clamped=true) quando o que você pediu não é o que vai ser cobrado. Sub-action 'models' (sem custo, sem login): lista os modelos ativos + a faixa de preço calculada pela mesma função que debita (priceDefault = o que uma chamada sem config custa, priceMin e priceMax = os dois cantos do motor, cada um com a config que o produz) + config (durações/resoluções) + disponibilidade (motor em preview pode aparecer como 'em breve'). LEGENDA (fita, por slug): 'caption-list' (sem custo, mostra os idiomas que a peça já tem), 'caption-generate' (captionLang = idioma FALADO; captionTrio=true já traduz pro trio da casa pt+en+ja na mesma chamada) e 'caption-translate' (captionLang = destino, parte sempre da faixa original). Cobra por minuto começado de vídeo: 50 Sinapses o minuto transcrito, 20 o traduzido, com a duração vindo do doc da peça. A legenda é desenhada pela casa em dois estilos (discreta e social), trocáveis no play sem regerar e sem custo.",
149
+ description: "Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as Sinapses da sua conta). Sub-action 'create' (recomendada): escolhe modelo + config e gera num call (cria a row + renderiza). Modelos: 'sapiens-video-seedance' (Seedance 2.0, cena+áudio nativo, 4-15s, 480/720/1080p, t2v/i2v; aceita até 4 imagens de REFERÊNCIA via referenceImageIds/referenceImageUrls/referenceImagePaths (o H3 aceita até 9, o Omni 1.1 até 3; Kling, Hailuo, WAN 2.5 e 3.0 e os dois Spicy não), que guiam estilo/personagem/composição SEM virar o 1º frame — é o fluxo STORYBOARD: gere a folha de key poses com sapiens_image templateSlug='storyboard-sapiens-v1', passe folha + personagem como refs num t2v e descreva o take contínuo no prompt, citando as refs por descrição e mandando ignorar o traço do sketch; aceita também 1 VÍDEO DE MOVIMENTO via referenceVideoUrls (role 'refvideo' -> reference_videos, <=15s, host da casa): a coreografia/câmera do clipe guia o take, combinável com a folha), 'sapiens-video-seedance-2-fast' e 'sapiens-video-seedance-2-mini' (os irmãos do 2.0: MESMO repertório completo, incluindo referência, frame final e vídeo de movimento; o Fast custa 20% menos e o Mini METADE, ambos com teto 720p — pedir 1080p neles entrega e cobra 720p. Use o Mini pra iterar enquadramento/prompt barato e feche no 'sapiens-video-seedance' quando o take estiver certo), 'sapiens-video-seedance-25' (Seedance 2.5, a geração SEGUINTE e não um quarto tier da 2.0: take de 4 a 30s num fôlego, edita e estende vídeo, mesmo repertório de referência mais ÁUDIO como referência; teto 720p e ~1,5x o preço por segundo do 2.0. Duração é o que pesa aqui: 30s em 720p passa de 40 mil Sinapses, então confirme a duração com a pessoa antes de disparar. Quem precisa de 1080p fica no 'sapiens-video-seedance'), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-hailuo' (Hailuo 2.3 da MiniMax, física e movimento em 768p, 6 ou 10s, t2v/i2v) e 'sapiens-video-hailuo-pro' (o mesmo em 1080p, 5s fixo — duração não é param aqui): motores PUROS, sem áudio nativo, sem imagens de referência e sem frame final, então quem precisa disso fica no Seedance 2.0; o Pro é o 1080p mais barato da casa, 'sapiens-video-h3' (MiniMax H3: som nativo incluso sem toggle, 5 a 15s em '2k' ou '768p' (sem resolution cai no 2k, que custa 33% mais por segundo), t2v/i2v e frame final; ACEITA até 9 imagens e 3 vídeos de referência (somando 15s), o caminho de take longo com personagem travada; referência e frame inicial são exclusivos, mande um dos dois; o 2K de 15s leva uns 9 minutos de render, acompanhe em status e não gere de novo), 'sapiens-video-pvideo-2-pro' (P-Video-2 Pro, o H3 acelerado pela Pruna: o RASCUNHO de personagem travada, até 9 imagens contando referências e frame inicial e final no mesmo pedido, 5 a 15s em '480p' ou '768p', som nativo, o take volta em segundos; sem t2v, precisa de ao menos uma imagem; cobra por imagem além do segundo, então cote em 'price' com as mesmas imagens do create), 'sapiens-video-h3-spicy' (MiniMax H3 Spicy: o H3 SEM freio de conteúdo, i2v puro a partir de uma imagem sua, que é o que trava a identidade; 3 a 15s, 480p/768p/1080p, som nativo, frame final; o segundo de vídeo mais barato e o render mais rápido da casa), 'sapiens-video-seedance-spicy' (Seedance 2.0 Spicy: o 2.0 SEM freio, também i2v puro, som nativo; 4 a 15s em 480p e 720p, para em 10s em 1080p; sem frame final; o som tem interruptor no mesmo preço), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), 'sapiens-video-wan-3' (WAN 3.0: texto ou imagem, som nativo com interruptor no mesmo preço, frame final, 5/8/10s em 480/720/1080p; sem imagem de referência), 'sapiens-video-kling-motion' (Motion transfer: passa o movimento de um vídeo pra uma imagem, PRECISA de pessoa com tronco visível na imagem E no vídeo), 'sapiens-video-shot-mimic' (Shot Mimic: recria o plano/câmera/cortes de um vídeo de referência como cena nova), 'sapiens-video-omni-11' (Gemini Omni 1.1: texto ou imagem vira vídeo de 4, 6, 8 ou 10s (sem durationSec, 10s) com áudio nativo, de '360p' a '4k' (sem resolution, 720p; 360p custa 1/3 do 720p, 1080p 1,5x, 4k 3x); aceita até 3 imagens de referência; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente; e VÍDEO-FONTE: sourceVideoId (vídeo seu da galeria) ou sourceVideoUrl (link da casa), mp4 até 10 s, editado (sourceVideoMode 'edit', o padrão, ex: trocar a pessoa do vídeo pelo personagem da imagem de referência, cobrado pelo tamanho do clipe) ou estendido ('extend', cobrado pelo durationSec do pedaço novo)) ('sapiens-video-omni', o Omni 1.0, e 'sapiens-video-lite/fast/quality', o Veo 3.1, SAÍRAM: o Google apagou os quatro em 22/10/2026 e o pedido é recusado antes de cobrar). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'; no H3 '768p'/'2k'; no Omni 1.1 '360p' a '4k'), audio, aspectRatio. RECEITA DE TAKE (templateSlug + brief), o irmão do templateSlug da imagem: em vez de escrever o prompt inteiro, passe templateSlug e a receita travada da casa embrulha a cena com estilo, cenário, arco, áudio e look, e ainda escolhe o motor (por isso model fica opcional). O `prompt` vira só a CENA e o `brief` preenche os campos do formato (subject, persona, hook, shots com voiceLine e propVisible, uvps, language, energy); campo vazio some do prompt em vez de virar buraco. Receitas de hoje: 'ugc-vertical-v1' (selfie que fala, o formato nativo de Reels/TikTok/Shorts), 'unboxing-vertical-v1' (mãos e reveal, som real do papel e do lacre), 'app-demo-vertical-v1' (a tela do app legível na mão da pessoa) e 'reflexao-vertical-v1' (talking-head lento pra ideia ou ensaio). Override de model/aspectRatio/durationSec/resolution vale dentro do que a receita aceita, e o erro lista as opções. Sub-action 'templates' (sem custo, sem login) traz o catálogo vivo com spec default, whitelist e os briefFields de cada uma. Isto substitui a tool sapiens_shorts, que era admin-only e só falava Veo 3.1 Fast; o mesmo UGC de 8s sai a 720p no Seedance 2.0 Mini por bem menos. FRAME INICIAL/FINAL POR REFERÊNCIA (recomendado): startImageId/endImageId (id da sua galeria) ou startImageUrl/endImageUrl (url de galeria/Acervo/personagem) — resolvidos server-side igual à imagem, descubra via sapiens_reference. FRAME POR ARQUIVO LOCAL (só no MCP instalado/stdio, não no remoto): startImagePath/endImagePath = caminho absoluto de uma imagem no seu PC (PNG/JPEG/WebP até 8MB); o processo lê o arquivo e sobe como frame inicial/final, igual a subir no gerador do site — 1 imagem inicial + 1 final por vídeo, então pra vários vídeos rode create uma vez por imagem. No remoto use id/url. Alternativa base64: references (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Suporte a frame final varia por modelo. Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}. VITRINE (sem custo): sub-action 'demos' lista os SEUS demo films (kind=demo do Estúdio de Vídeo) com slug + estado de vitrine; sub-action 'showcase' põe/tira um demo (por slug) do mini-cinema da /conectar-claude, com showcaseTag (chip de capacidade) e showcaseOrder (ordem asc). Fluxo: 'demos' pra achar o slug, depois 'showcase' com showcase=true. Só entra na vitrine pública se for a conta da casa. VÍDEOS PROGRAMÁTICOS (ADMIN, sem custo): o Lab do Estúdio de Vídeo (/experimentos/films, tabela videoSpecs, 5 kinds: demo | aula-tour | essay | tipografia-musical | dataviz) opera por aqui sem browser — 'film-list' (todos os kinds; filtros filmKind/filmStatus), 'film-get' (spec inteiro por slug), 'film-upsert' (cria/atualiza por slug, idempotente; spec = objeto JSON no shape do 'Copiar spec' da tela, validação no servidor), 'film-status' (produção por slug: filmStatus + videoUrl + durationSecMeasured; o fecho do render é os três num call), 'film-publish' (Acervo aba Fitas + portfólio; exige pronto+URL), 'film-delete' (limpar rascunho). O RENDER do filme segue no agente local (skill /film, repo da casa): o MCP registra e fecha o ciclo, não renderiza. create é ASSÍNCRONA: cria o row, debita e volta NA HORA com {imageId, status:'rendering', cost} (não espera o render, que leva de segundos a minutos). Acompanhe com a sub-action 'status' (imageId) até status='completed' (traz a url) ou 'error'/'blocked'. NÃO chame create de novo enquanto renderiza (cria outro vídeo e cobra de novo); falha de provider refunda sozinha. EDIÇÃO (as ações da ficha do site, cada uma vira peça nova e o original fica): 'trim' (imageId + startSec, endSec opcional: corta um trecho; grátis), 'join' (imageIds: 2 a 8 vídeos SEUS na ordem, até 180 s somados: compilado; grátis), 'fecho' (imageId + ending 'marca'/'sapiens'/'none', watermark, musicUrl de trilha da casa, nameCard {name, subtitle}: fecha o vídeo com a assinatura da casa; grátis), 'render-ref' (imageId do take + refUrl da folha que o gerou: a peça com a referência ao lado; cobra Sinapses, o mesmo par já feito volta de graça). Síncronas, podem passar de 2 min: Timeout é o cliente desistindo, olhe sapiens_gallery action=list kind=video antes de repetir. RESOLUÇÃO: 'upscale' (imageId de vídeo SEU completed + resolution = o alvo, '720p', '1080p', '2k' ou '4k') gera uma VARIANTE nova em resolução maior, o original fica intacto; cobra Sinapses por segundo do clipe, mais caro quanto maior o alvo, assíncrona como o create (acompanhe em status). SOM: 'sonorize' (imageId de vídeo SEU completed + prompt do som da cena) gera uma VARIANTE nova com trilha sincronizada (o original fica intacto; sonorize sempre o original, nunca uma variante). engine escolhe o motor: 'mirelo-sfx-2.0' (42 Sinapses/s, o default: GUARDA a fala do clipe, tira a música e refaz o resto), 'mmaudio-v2' (20 Sinapses/s, segue o texto em vez da imagem: é o motor do bicho trocado, onde o Mirelo devolve o som do bicho original; refaz o som inteiro e a fala se perde) ou 'elevenlabs-video-to-music' (26 Sinapses/s, faz a TRILHA e não o som da cena: uma música que acompanha os cortes, mixada por baixo do som que o clipe já tem; o prompt descreve a música). ADMIN: 'shadows' (videoUrl + title) extrai o depth map (depth-map) de um vídeo: entra na sua timeline de vídeos e no Acervo como driving reutilizável; 'shadows-list' lista os depth maps prontos. PREÇO DE VÍDEO É FAIXA, NÃO NÚMERO: nos motores por segundo (todo Seedance, Kling, WAN 2.5 e 3.0, Hailuo, H3, os dois Spicy, Shot Mimic, e desde out/2026 o Gemini Omni) a conta é duração x resolução [x áudio], e OMITIR não pega o barato: sem 'resolution' o servidor cobra o tier MAIS CARO do motor (menos o Gemini Omni 1.1, que fica no 720p), sem 'durationSec' cobra o piso de duração (no Omni, o padrão: 10s). Um create de Seedance 2.0 sem resolution debita 1080p. Nunca prometa um valor sem cotar: sub-action 'price' (sem custo, sem login) devolve o número EXATO que o create vai debitar pra model + durationSec + resolution + audio, e avisa (clamped=true) quando o que você pediu não é o que vai ser cobrado. Sub-action 'models' (sem custo, sem login): lista os modelos ativos + a faixa de preço calculada pela mesma função que debita (priceDefault = o que uma chamada sem config custa, priceMin e priceMax = os dois cantos do motor, cada um com a config que o produz) + config (durações/resoluções) + disponibilidade (motor em preview pode aparecer como 'em breve'). LEGENDA (fita, por slug): 'caption-list' (sem custo, mostra os idiomas que a peça já tem), 'caption-generate' (captionLang = idioma FALADO; captionTrio=true já traduz pro trio da casa pt+en+ja na mesma chamada) e 'caption-translate' (captionLang = destino, parte sempre da faixa original). Cobra por minuto começado de vídeo: 50 Sinapses o minuto transcrito, 20 o traduzido, com a duração vindo do doc da peça. A legenda é desenhada pela casa em dois estilos (discreta e social), trocáveis no play sem regerar e sem custo.",
150
150
  schema: videoSchema,
151
151
  handler: video,
152
152
  },
@@ -231,7 +231,7 @@ export const TOOLS = {
231
231
  handler: trilhas,
232
232
  },
233
233
  sapiens_distribution: {
234
- description: "Despachar peças (Distribution Workflow) — a peça pronta entra na FILA com canal, copy e a aba de quem ela é. ADMIN-ONLY: despacho depende de rede conectada e hoje só a casa tem. NADA aqui posta em rede nenhuma; quem despacha é a tela em /experimentos/distribution-workflow, que é onde a conta conectada mora. A ABA (`voice`) responde de quem é a peça: sapiens (a casa), helen-ailith, borderless, ou o slug de um personagem seu — slug livre, então personagem novo ganha aba sozinho na primeira peça dele, sem deploy. Em queue, aba ausente = o servidor deriva do personagem da peça e cai em sapiens quando não há. Sub-actions: queue (põe na fila: title e channel obrigatórios, mais copy/hashtags/assetUrl/notes; replyText é o comentário que sai junto do post, onde vai o link, e só o X (resposta no fio) e a Página do LinkedIn (primeiro comentário) publicam, o resto recusa; num canal do Instagram, format instagram_story faz a peça sair como STORY: uma mídia só, sem legenda nem link, some em 24h; num Reel, videoCoverUrl é a CAPA da casa, que pela API só entra aqui). Em queue, DOIS campos que parecem opcionais e não são: `lang` diz em que língua a copy já está, e sem ele canal de rede internacional reescreve a sua copy sozinho, dois segundos depois, na voz da casa; `assetPageAssetId` é o imageId da obra da casa, e sem ele a peça só encontra a obra se a assetUrl bater letra a letra, então versão web de um master nasce órfã de ficha, list (o que está na fila, filtrável por status e por aba, com copy, hashtags e notas de cada peça; fila maior que o limit se lê inteira pelo cursor antesDe=proximoCursor), voices (as abas que existem hoje com a contagem de cada uma), status (move a peça de lane: fila, agendado, postado, descartado; postado aceita postUrl e fecha o rastro, agendado aceita scheduledFor em ms), edit (reescreve a peça da FILA sem tirar ela do lugar: itemId e só o que muda, copy, title, publicTitle, hashtags, notes, lang, replyText, assetUrl/assetKind, assets ou videoCoverUrl; é o gesto pra corrigir legenda, nunca descartar e enfileirar de novo; texto de peça postada o servidor recusa), sign (liga ou desliga a ASSINATURA da peça, imagem e vídeo do CDN da casa, com o carimbo da LINHAGEM de quem assina: sint.fyi/<endereço dela> no canto e o fecho de 2s no vídeo, ou a marca própria dela sem nada da casa; itemId, e signed com true de default; o carimbo da casa é recusado em peça +18 e praça adulta, a marca própria passa; o carimbo leva uns 30s e até lá a peça não sai; em queue, signed:true já enfileira assinada), bio (lê ou troca a BIO de uma conta da casa no Bluesky, channel bluesky ou bluesky-2 a bluesky-5: sem texto só lê; bioLine acrescenta uma linha no fim sem mexer no resto e não repete linha que já está lá; bio reescreve a inteira; teto de 256; ESTA sub-action muda o perfil público na hora, e bio não aceita hiperlink, o endereço aparece escrito), lineages (o CADASTRO das linhagens, o mesmo da aba Teia: de cada personagem, produto e da casa, a mãe no Bluesky onde a peça nasce, as contas próprias, as galerias que a mãe alimenta sozinha, as fichas e o post do dia, mais as regras que a teia desdobra e as vagas de conta ainda sem dona), lineage-new (nasce uma linhagem: nome obrigatório, grupo, artigo, lang; nasce sem mãe, então nada viaja sozinho até o lineage-save dar a mãe), lineage-save (slug e só o que muda: mae, contas rede->canal, galerias, fichaIds, rotina, contaNaCasa, assinatura {estilo casa|propria|nenhuma, marca, padrao}; o servidor recusa conta que já é de outra linhagem e canal de outra rede, e a vaga assumida ganha o nome da linhagem), lineage-archive (slug + arquivada), tree (a ÁRVORE DE CONTAS, o formulário de quem existe onde: linhagem × rede, com o email, o @, o canal e o estado de cada conta, ligada, desligada, fora, falta ou dispensa, mais o passo pra ligar cada rede; slug filtra uma linhagem), tree-mark (slug + celulas [{rede, handle, canal, dispensada}]: registra o email ou o @ que existe lá fora, associa a conta da casa ou dispensa rede opcional, a linhagem inteira de uma vez e tudo ou nada; X, Instagram, Bluesky, email e Telegram são obrigatórias e não se dispensam), garimpo (o ACERVO em famílias pra escolher o que vai pra fila: só as candidatas saem, a versão velha de um corte ou upscale vem marcada como substituída, as irmãs da mesma ideia vêm agrupadas pra escolha no olho, o painel cortado vem em recortes (o original e os quadros em ordem, que só saem juntos em carrossel), e cada família diz por onde já saiu; paraCanal tira o que já foi pro canal pedido, soResumo conta sem listar, página por cursor em antesDe; o passo a passo é a skill distribuir). Sem custo em Sinapses: isto organiza, não gera. A copy segue a voz de QUEM ASSINA a peça, que não é sempre a voz da casa: peça da Helen fala como a Helen. E a peça sai pela CONTA de quem assina ela: conta extra da mesma rede é um canal próprio (bluesky-2 é outra conta no Bluesky, instagram-pro outra no Instagram), então case o canal com a aba antes de despachar.",
234
+ description: "Despachar peças (Distribution Workflow) — a peça pronta entra na FILA com canal, copy e a aba de quem ela é. ADMIN-ONLY: despacho depende de rede conectada e hoje só a casa tem. NADA aqui posta em rede nenhuma; quem despacha é a tela em /experimentos/distribution-workflow, que é onde a conta conectada mora. A ABA (`voice`) responde de quem é a peça: sapiens (a casa), helen-ailith, borderless, ou o slug de um personagem seu — slug livre, então personagem novo ganha aba sozinho na primeira peça dele, sem deploy. Em queue, aba ausente = o servidor deriva do personagem da peça e cai em sapiens quando não há. Sub-actions: queue (põe na fila: title e channel obrigatórios, mais copy/hashtags/assetUrl/notes; replyText é o comentário que sai junto do post, onde vai o link, e só o X (resposta no fio) e a Página do LinkedIn (primeiro comentário) publicam, o resto recusa; num canal do Instagram, format instagram_story faz a peça sair como STORY: uma mídia só, sem legenda nem link, some em 24h; num Reel, videoCoverUrl é a CAPA da casa, que pela API só entra aqui, e o retorno traz `aviso` quando a mesma abertura, a 1a linha da copy, já foi ao ar na mesma rede por qualquer aba: a peça entra mesmo assim, e você mostra o aviso ao dono antes de seguir, porque série de molde abre igual e é outra peça, mas a mesma notícia por duas abas é repetição). Em queue, DOIS campos que parecem opcionais e não são: `lang` diz em que língua a copy já está, e sem ele canal de rede internacional reescreve a sua copy sozinho, dois segundos depois, na voz da casa; `assetPageAssetId` é o imageId da obra da casa, e sem ele a peça só encontra a obra se a assetUrl bater letra a letra, então versão web de um master nasce órfã de ficha, list (o que está na fila, filtrável por status e por aba, com copy, hashtags e notas de cada peça; fila maior que o limit se lê inteira pelo cursor antesDe=proximoCursor), voices (as abas que existem hoje com a contagem de cada uma), status (move a peça de lane: fila, agendado, postado, descartado; postado aceita postUrl e fecha o rastro, agendado aceita scheduledFor em ms), edit (reescreve a peça da FILA sem tirar ela do lugar: itemId e só o que muda, copy, title, publicTitle, hashtags, notes, lang, replyText, assetUrl/assetKind, assets ou videoCoverUrl; é o gesto pra corrigir legenda, nunca descartar e enfileirar de novo; texto de peça postada o servidor recusa), sign (liga ou desliga a ASSINATURA da peça, imagem e vídeo do CDN da casa, com o carimbo da LINHAGEM de quem assina: sint.fyi/<endereço dela> no canto e o fecho de 2s no vídeo, ou a marca própria dela sem nada da casa; itemId, e signed com true de default; o carimbo da casa é recusado em peça +18 e praça adulta, a marca própria passa; o carimbo leva uns 30s e até lá a peça não sai; em queue, signed:true já enfileira assinada), bio (lê ou troca a BIO de uma conta da casa no Bluesky, channel bluesky ou bluesky-2 a bluesky-5: sem texto só lê; bioLine acrescenta uma linha no fim sem mexer no resto e não repete linha que já está lá; bio reescreve a inteira; teto de 256; ESTA sub-action muda o perfil público na hora, e bio não aceita hiperlink, o endereço aparece escrito), lineages (o CADASTRO das linhagens, o mesmo da aba Teia: de cada personagem, produto e da casa, a mãe no Bluesky onde a peça nasce, as contas próprias, as galerias que a mãe alimenta sozinha, as fichas e o post do dia, mais as regras que a teia desdobra e as vagas de conta ainda sem dona), lineage-new (nasce uma linhagem: nome obrigatório, grupo, artigo, lang; nasce sem mãe, então nada viaja sozinho até o lineage-save dar a mãe), lineage-save (slug e só o que muda: mae, contas rede->canal, galerias, fichaIds, rotina, contaNaCasa, assinatura {estilo casa|propria|nenhuma, marca, padrao}; o servidor recusa conta que já é de outra linhagem e canal de outra rede, e a vaga assumida ganha o nome da linhagem), lineage-archive (slug + arquivada), tree (a ÁRVORE DE CONTAS, o formulário de quem existe onde: linhagem × rede, com o email, o @, o canal e o estado de cada conta, ligada, desligada, fora, falta ou dispensa, mais o passo pra ligar cada rede; slug filtra uma linhagem), tree-mark (slug + celulas [{rede, handle, canal, dispensada}]: registra o email ou o @ que existe lá fora, associa a conta da casa ou dispensa rede opcional, a linhagem inteira de uma vez e tudo ou nada; X, Instagram, Bluesky, email e Telegram são obrigatórias e não se dispensam), garimpo (o ACERVO em famílias pra escolher o que vai pra fila: só as candidatas saem, a versão velha de um corte ou upscale vem marcada como substituída, as irmãs da mesma ideia vêm agrupadas pra escolha no olho, o painel cortado vem em recortes (o original e os quadros em ordem, que só saem juntos em carrossel), e cada família diz por onde já saiu; paraCanal tira o que já foi pro canal pedido, soResumo conta sem listar, página por cursor em antesDe; o passo a passo é a skill distribuir). Sem custo em Sinapses: isto organiza, não gera. A copy segue a voz de QUEM ASSINA a peça, que não é sempre a voz da casa: peça da Helen fala como a Helen. E a peça sai pela CONTA de quem assina ela: conta extra da mesma rede é um canal próprio (bluesky-2 é outra conta no Bluesky, instagram-pro outra no Instagram), então case o canal com a aba antes de despachar.",
235
235
  schema: distributionSchema,
236
236
  handler: distribution,
237
237
  },
package/dist/skills.js CHANGED
@@ -251,6 +251,7 @@ Duração é o que pesa aqui, não o modelo: um take de 30s em 720p passa de 40
251
251
  ## Os outros modelos
252
252
 
253
253
  - \`sapiens-video-h3\`: MiniMax H3, o mais barato por pixel da casa, com som nativo, texto ou imagem. 5 a 15s em \`2k\` ou \`768p\` (o 2K longo demora uns 9 minutos: volta \`pending\` e termina sozinho). Aceita até 9 imagens e 3 vídeos de referência, e é o caminho de take LONGO com personagem travada. Referência e frame inicial não vão juntos: escolha um. Sem \`resolution\` cai no 2k, que custa 33% mais por segundo.
254
+ - \`sapiens-video-pvideo-2-pro\`: P-Video-2 Pro, o H3 acelerado pela Pruna, e o RASCUNHO de personagem travada: até 9 imagens (referências mais frame inicial e final, juntos no mesmo pedido), 5 a 15s em \`480p\` ou \`768p\`, som nativo, e o take volta em segundos. Itere elenco, ângulo e fala aqui e feche no Seedance ou no H3. Não faz texto puro (precisa de ao menos uma imagem) e cobra cada imagem além do segundo: cote em \`action=price\` com as mesmas imagens do create.
254
255
  - \`sapiens-video-h3-spicy\` e \`sapiens-video-seedance-spicy\`: os mesmos motores SEM freio de conteúdo. Os dois partem de uma IMAGEM sua (não fazem texto puro), e é isso que trava a identidade: quem aparece já veio pronto na imagem, o motor só dá movimento. O H3 Spicy é o mais barato e o mais rápido da casa (250 Sinapses o segundo em 480p, 3 a 15s); o Seedance Spicy tem o peso do 2.0, vai a 15s em 480p e 720p e para em 10s no 1080p.
255
256
  - \`sapiens-video-kling\`: Kling 3.0 Pro, anima imagem, 3 a 15s, som opcional.
256
257
  - \`sapiens-video-wan\`: WAN 2.5, imagem que fala ou canta, com lip-sync, 5 ou 10s.
@@ -258,8 +259,7 @@ Duração é o que pesa aqui, não o modelo: um take de 30s em 720p passa de 40
258
259
  - \`sapiens-video-kling-motion\`: passa o movimento de um vídeo pra uma imagem. Precisa de pessoa com tronco visível na imagem E no vídeo.
259
260
  - \`sapiens-video-shot-mimic\`: recria o plano, a câmera e os cortes de um vídeo de referência como cena nova.
260
261
  - \`sapiens-video-omni-11\`: Gemini Omni 1.1, texto ou imagem vira clipe de 4, 6, 8 ou 10s (sem \`durationSec\`, 10s) com áudio nativo, de \`360p\` a \`4k\` (sem \`resolution\`, 720p). O preço é por segundo e por resolução: o take de 4s custa 40% do de 10s, o 360p custa 1/3 do 720p, o 1080p 1,5x e o 4k 3x. Aceita até 3 imagens suas (frame inicial, frame final ou referência de personagem e estilo); vídeo de movimento não (pra copiar coreografia, Seedance). Edita e estende um vídeo SEU: \`sourceVideoId\` (da galeria) ou \`sourceVideoUrl\` (link da casa), mp4 até 10 s, com \`sourceVideoMode\` \`edit\` (o padrão: a mesma cena com a mudança pedida, cobrada pelo tamanho do clipe; é como se troca a pessoa de um vídeo pelo seu personagem, com a ficha dele em \`referenceImageIds\`) ou \`extend\` (o pedaço seguinte, cobrado pelo \`durationSec\`). A proporção sai a do clipe. O truque: \`editOfImageId\` aponta um vídeo Omni seu e o prompt vira instrução de edição sobre a MESMA cena (troca item ou personagem, preserva câmera e ambiente). É o caminho pra variações com continuidade: gera a base uma vez, edita N vezes. Cada edição debita como geração nova. Pra ir ADIANTE no tempo é \`action=extend\` (o pedaço seguinte, cobrado pela duração dele): o arquivo de cada parte já traz as anteriores na frente, então a última parte pronta é o filme inteiro. Não emende as partes, a abertura repete; \`action=chain\` aponta o \`filmeInteiro\`.
261
- - \`sapiens-video-omni\`: o Gemini Omni 1.0, a versão preview. Mesmo preço e mesmo repertório do 1.1, mantida só pra comparar as duas. Nasce desligada (não aparece na lista de motores), então peça o 1.1.
262
- - \`sapiens-video-lite\` / \`-fast\` / \`-quality\`: Veo 3.1, 4, 6 ou 8s (sem \`durationSec\`, 8s), preço por segundo. Com imagem de referência ou frame final o Google só faz 8s: pedir menos com elas recusa antes de gastar.
262
+ - \`sapiens-video-omni\` (Gemini Omni 1.0) e \`sapiens-video-lite\` / \`-fast\` / \`-quality\` (Veo 3.1): SAÍRAM. O Google apagou os quatro em 22/10/2026 e o pedido é recusado antes de cobrar. O sucessor é o \`sapiens-video-omni-11\`.
263
263
 
264
264
  ## Receita de take (\`templateSlug\`), quando o formato já tem forma
265
265
 
@@ -317,7 +317,15 @@ A conta tem duas carteiras: Sinapses (o App) e o saldo em dólar da API (Develop
317
317
 
318
318
  ## Sonorizar
319
319
 
320
- \`action=sonorize\` com \`imageId\` de um vídeo SEU já completed e \`prompt\` descrevendo o som da cena (ambiente, materiais, impactos). Sai uma VARIANTE nova com trilha sincronizada ao movimento (MMAudio, 20 Sinapses/s do clipe, mín 100), e o original fica intacto.
320
+ \`action=sonorize\` com \`imageId\` de um vídeo SEU já completed e \`prompt\` descrevendo o som da cena (ambiente, materiais, impactos). Sai uma VARIANTE nova com trilha sincronizada ao movimento, e o original fica intacto. Três motores no \`engine\`:
321
+
322
+ - \`mirelo-sfx-2.0\` (o default, o motor da casa pra sonorizar): GUARDA a fala que o clipe já tem, tira a música que veio junto e refaz o resto do som em volta (Mirelo SFX 2.0, 42 Sinapses/s do clipe, mín 210). Em clipe mudo, faz o som inteiro.
323
+ - \`mmaudio-v2\`: segue o TEXTO, e é o mais barato. Som inteiro do zero (MMAudio, 20 Sinapses/s do clipe, mín 100), e a fala que o clipe já tem se perde.
324
+ - \`elevenlabs-video-to-music\`: não faz som de cena, faz a TRILHA. Uma música que acompanha os cortes do clipe, mixada por baixo do som que ele já tem: a fala e o ruído ficam (ElevenLabs Video to Music, 26 Sinapses/s do clipe, mín 130). Aqui o \`prompt\` descreve a música (gênero, instrumentos, clima, onde ela cresce), não a cena. Recusa, sem cobrar, peça que já tem o fecho da casa: sonorize o take original.
325
+
326
+ O Mirelo tira o som da IMAGEM e o texto só empurra. Quando a imagem engana, use o MMAudio: bicho trocado numa cena refeita (o gatinho no lugar do T-rex saiu do Mirelo com passo e rugido de T-rex), criatura que não existe, som que a cena não mostra.
327
+
328
+ O Mirelo e o Video to Music recusam, sem cobrar, a peça que entrou de fora (ingest: a casa não sabe quanto ela dura) e o clipe acima de 180s. Peça de fora passa antes no \`action=trim\` (o corte nasce medido) e você sonoriza o corte; clipe longo, corte em partes.
321
329
 
322
330
  Sonorize sempre o ORIGINAL, nunca uma variante. Acompanhe com \`action=status\` no imageId NOVO que o sonorize devolve.
323
331
 
@@ -20,6 +20,7 @@ import { convexQuery, convexMutation, convexAction, describeConvexError, getSess
20
20
  * Sub-actions:
21
21
  * - queue: põe uma peça na fila (title + channel obrigatórios). Num canal
22
22
  * do Instagram, `format: "instagram_story"` faz a peça sair como STORY.
23
+ * Volta com `aviso` quando a mesma abertura já saiu na mesma rede.
23
24
  * - list: o que está na fila, filtrável por status e por aba.
24
25
  * - voices: as abas que existem hoje, com a contagem de cada uma.
25
26
  * - status: move a peça de lane (fila, agendado, postado, descartado).
@@ -230,7 +231,7 @@ export const distributionSchema = z.object({
230
231
  }))
231
232
  .max(20)
232
233
  .optional()
233
- .describe("queue: a mídia em LISTA, na ordem do post. Quando vem, ela manda: a 1ª vira a capa e assetUrl/assetKind são ignorados. Cada canal leva só o teto dele e o resto fica de fora na hora do despacho: instagram 10 (carrossel, imagem e vídeo juntos), threads 20, linkedin 20, telegram e telegram-2..5 10 (álbum), x-buffer 4, bluesky e bluesky-2..5 4, linkedin-page 4, farcaster 2, os demais 1. O canal x (API própria do X) NÃO sobe imagem nenhuma, só o texto: post do X com imagem vai por x-buffer. Canal que não mistura (x-buffer, bluesky, linkedin) segue o tipo da 1ª mídia. Não precisa mandar o imageId de cada obra: o servidor casa cada URL com a obra da casa, e toda imagem da casa na lista conta como compartilhada na ficha dela. O Distribution não faz quote de post do X: link de post na copy vai como link."),
234
+ .describe("queue: a mídia em LISTA, na ordem do post. Quando vem, ela manda: a 1ª vira a capa e assetUrl/assetKind são ignorados. Cada canal leva só o teto dele e o resto fica de fora na hora do despacho: instagram 10 (carrossel, imagem e vídeo juntos), threads 20, linkedin 20, telegram e telegram-2..5 10 (álbum), x-buffer 4, bluesky e bluesky-2..5 4, linkedin-page 4, farcaster 2, os demais 1. O canal x (API própria do X) NÃO sobe imagem nenhuma, só o texto: post do X com imagem vai por x-buffer. Canal que não mistura (x-buffer, bluesky, linkedin) segue o tipo da 1ª mídia. Exceção no bluesky (todas as contas): vídeo na 1ª e imagens depois é UMA peça só, o vídeo vai no post e as imagens (até 4) saem na RESPOSTA dele, no mesmo fio e no mesmo despacho; é o caminho do vídeo + prompt em cards, e partir em duas peças na fila quebra o fio. Não precisa mandar o imageId de cada obra: o servidor casa cada URL com a obra da casa, e toda imagem da casa na lista conta como compartilhada na ficha dela. O Distribution não faz quote de post do X: link de post na copy vai como link."),
234
235
  assetKind: z
235
236
  .enum(["image", "video", "audio"])
236
237
  .optional()
@@ -416,6 +417,19 @@ function recadoDaAssinatura(signed, r) {
416
417
  ];
417
418
  return partes.filter(Boolean).join(" ");
418
419
  }
420
+ /**
421
+ * A linha do "já saiu na rede": a mesma abertura já foi ao ar na mesma rede, de
422
+ * qualquer aba. Aviso, não recusa (série de molde abre igual e é outra peça).
423
+ * Servidor anterior a 15/10/2026 não manda `avisos`, e aí não há linha.
424
+ */
425
+ function linhaDoJaSaiu(avisos) {
426
+ if (!avisos?.length)
427
+ return null;
428
+ const [a, ...resto] = avisos;
429
+ const dia = a.postedAt ? `, ${new Date(a.postedAt).toISOString().slice(0, 10)}` : "";
430
+ const mais = resto.length ? ` e mais ${resto.length}` : "";
431
+ return `JÁ SAIU no ${a.channel} com a mesma abertura: "${a.title}" (aba ${a.voice ?? "?"}${dia}, itemId ${a.itemId})${mais}. Confira com o dono antes de despachar. Se for repetição, descarte esta.`;
432
+ }
419
433
  export async function distribution(args) {
420
434
  const sessionToken = getSessionToken();
421
435
  if (args.action === "queue") {
@@ -447,11 +461,18 @@ export async function distribution(args) {
447
461
  notes: args.notes,
448
462
  format: args.format,
449
463
  });
464
+ const jaSaiu = linhaDoJaSaiu(r.avisos);
450
465
  const fila = {
451
466
  itemId: r.itemId,
452
467
  status: "fila",
453
468
  voice: args.voice ?? "(derivado no servidor)",
454
- instruction: "A peça está na fila do Distribution Workflow, não foi ao ar. Quem posta é a tela, em /experimentos/distribution-workflow.",
469
+ ...(jaSaiu ? { aviso: jaSaiu, jaSaiuNaRede: r.avisos } : {}),
470
+ instruction: [
471
+ jaSaiu,
472
+ "A peça está na fila do Distribution Workflow, não foi ao ar. Quem posta é a tela, em /experimentos/distribution-workflow.",
473
+ ]
474
+ .filter(Boolean)
475
+ .join(" "),
455
476
  };
456
477
  if (!args.signed)
457
478
  return fila;
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { readFile } from "node:fs/promises";
3
+ import { basename, extname } from "node:path";
3
4
  import { createHash } from "node:crypto";
4
5
  import { convexAction, convexMutation, convexQuery, describeConvexError, getSessionToken, isRemoteContext, } from "../convexClient.js";
5
6
  import { assertPublicHttpsUrl, fetchPublicHttps } from "../publicFetch.js";
@@ -96,7 +97,7 @@ export const gallerySchema = z.object({
96
97
  filePath: z
97
98
  .string()
98
99
  .optional()
99
- .describe("action=upload/ingest: caminho ABSOLUTO de um arquivo local (png, jpg, webp, mp4, mov). SÓ no MCP instalado (stdio): é a porta de quem gerou na própria máquina ou baixou o render antes; no remoto use sourceUrl. No ingest, o sha256 do arquivo vira a chave de idempotência."),
100
+ .describe("action=upload/ingest: caminho ABSOLUTO de um arquivo local: no upload só imagem (png, jpg, webp, gif, avif), no ingest também mp4 e mov. O conteúdo é conferido pelos bytes antes de subir, e arquivo que não é peça é recusado sem ser enviado. SÓ no MCP instalado (stdio): é a porta de quem gerou na própria máquina ou baixou o render antes; no remoto use sourceUrl. No ingest, o sha256 do arquivo vira a chave de idempotência."),
100
101
  base64: z
101
102
  .string()
102
103
  .optional()
@@ -104,7 +105,7 @@ export const gallerySchema = z.object({
104
105
  mimeType: z
105
106
  .string()
106
107
  .optional()
107
- .describe("action=upload/ingest: tipo do arquivo (image/png, video/mp4...). Detectado sozinho a partir da URL/extensão/resposta HTTP quando omitido."),
108
+ .describe("action=upload/ingest: tipo do arquivo (image/png, video/mp4...). Detectado sozinho a partir da URL/extensão/resposta HTTP quando omitido. Em filePath e base64 vale o tipo que os bytes mostram, não este."),
108
109
  note: z
109
110
  .string()
110
111
  .optional()
@@ -209,23 +210,85 @@ function mimeFromPath(p) {
209
210
  const ext = p.split("?")[0].split("#")[0].split(".").pop()?.toLowerCase();
210
211
  return ext ? EXT_MIME[ext] : undefined;
211
212
  }
213
+ // Marcas ISO BMFF de HEIC/HEIF: imagem que a galeria não hospeda, e que sem
214
+ // esta lista passaria por vídeo (o ftyp é o mesmo do mp4).
215
+ const MARCAS_HEIF = new Set(["heic", "heix", "heim", "heis", "mif1", "msf1"]);
216
+ /**
217
+ * O tipo da peça pelos primeiros bytes, ou undefined quando não é imagem nem
218
+ * vídeo que a casa aceita. A extensão sozinha não prova nada: sem isto, um
219
+ * filePath qualquer (o .env.local, uma chave) subia pro CDN como image/png, e
220
+ * bastava uma frase plantada num README lido por skill (auditoria de prompt
221
+ * injection, 15/10/2026).
222
+ */
223
+ export function mimeDosBytes(b) {
224
+ const txt = (ini, fim) => b.length >= fim ? Buffer.from(b.subarray(ini, fim)).toString("latin1") : "";
225
+ if (txt(0, 8) === "\x89PNG\r\n\x1a\n")
226
+ return "image/png";
227
+ if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff)
228
+ return "image/jpeg";
229
+ if (txt(0, 6) === "GIF87a" || txt(0, 6) === "GIF89a")
230
+ return "image/gif";
231
+ if (txt(0, 4) === "RIFF" && txt(8, 12) === "WEBP")
232
+ return "image/webp";
233
+ if (txt(4, 8) === "ftyp") {
234
+ // A caixa ftyp traz a marca principal e as compatíveis (o byte 12 é versão).
235
+ const caixa = Math.min(b.length, ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]) >>> 0);
236
+ const marcas = [txt(8, 12)];
237
+ for (let i = 16; i + 4 <= caixa; i += 4)
238
+ marcas.push(txt(i, i + 4));
239
+ if (marcas.includes("avif") || marcas.includes("avis"))
240
+ return "image/avif";
241
+ if (marcas.some((m) => MARCAS_HEIF.has(m)))
242
+ return undefined;
243
+ return marcas[0] === "qt " ? "video/quicktime" : "video/mp4";
244
+ }
245
+ // QuickTime antigo abre direto num átomo, sem ftyp.
246
+ if (["moov", "mdat", "wide", "free", "skip"].includes(txt(4, 8)))
247
+ return "video/quicktime";
248
+ return undefined;
249
+ }
250
+ /** Confere que os bytes são peça (imagem; vídeo só onde aceitaVideo) e devolve o mime deles. */
251
+ function exigePeca(bytes, nome, aceitaVideo) {
252
+ const mime = mimeDosBytes(bytes);
253
+ if (!mime || (!aceitaVideo && !mime.startsWith("image/"))) {
254
+ throw new Error(`${nome} não é ${aceitaVideo ? "imagem nem vídeo" : "imagem"} que a galeria aceita (o conteúdo não bate com png, jpg, webp, gif, avif${aceitaVideo ? ", mp4 ou mov" : ""}). Nada foi enviado.`);
255
+ }
256
+ return mime;
257
+ }
258
+ const EXT_IMAGEM = ["png", "jpg", "jpeg", "webp", "gif", "avif"];
259
+ const EXT_VIDEO = ["mp4", "mov"];
260
+ /**
261
+ * Lê o filePath de upload/ingest só quando ele É a peça: a extensão é
262
+ * conferida ANTES de abrir o arquivo (o que não é peça nem chega a ser lido) e
263
+ * os bytes depois. O mime que segue pro servidor é o dos bytes, não o que o
264
+ * nome ou o argumento dizem.
265
+ */
266
+ export async function lerPecaLocal(filePath, aceitaVideo) {
267
+ const aceitas = aceitaVideo ? [...EXT_IMAGEM, ...EXT_VIDEO] : EXT_IMAGEM;
268
+ const nome = basename(filePath);
269
+ const ext = extname(filePath).slice(1).toLowerCase();
270
+ if (!aceitas.includes(ext)) {
271
+ throw new Error(`filePath aceita só ${aceitas.join(", ")}: "${nome}" não é peça. Nada foi lido nem enviado.`);
272
+ }
273
+ const bytes = await readFile(filePath);
274
+ return { bytes, mimeType: exigePeca(bytes, `"${nome}"`, aceitaVideo) };
275
+ }
212
276
  /** Resolve sourceUrl | filePath | base64 nos bytes prontos pro Convex. */
213
277
  async function resolveUploadBytes(args) {
214
278
  if (args.base64?.trim()) {
215
- const mimeType = args.mimeType ||
216
- args.base64.match(/^data:([^;]+);base64,/)?.[1] ||
217
- "image/png";
218
- return { base64: args.base64.trim(), mimeType };
279
+ // Mesma régua do filePath: o agente que lê um arquivo e o codifica à mão
280
+ // não pode subir texto como imagem por esta porta.
281
+ const bytes = Buffer.from(args.base64.trim().replace(/^data:[^;]+;base64,/, ""), "base64");
282
+ return { base64: args.base64.trim(), mimeType: exigePeca(bytes, "O base64", false) };
219
283
  }
220
284
  if (args.filePath) {
221
285
  if (isRemoteContext()) {
222
286
  throw new Error("filePath só funciona no MCP instalado (stdio). No remoto, mande sourceUrl.");
223
287
  }
224
- const buf = await readFile(args.filePath);
288
+ const { bytes: buf, mimeType } = await lerPecaLocal(args.filePath, false);
225
289
  if (buf.byteLength > MAX_UPLOAD_BYTES) {
226
290
  throw new Error(`Arquivo grande demais: ${(buf.byteLength / 1024 / 1024).toFixed(1)} MB (teto 12 MB).`);
227
291
  }
228
- const mimeType = args.mimeType || mimeFromPath(args.filePath) || "image/png";
229
292
  return { base64: buf.toString("base64"), mimeType };
230
293
  }
231
294
  if (args.sourceUrl) {
@@ -263,10 +326,8 @@ async function resolveIngestBytes(args) {
263
326
  };
264
327
  const hexDe = (bytes) => createHash("sha256").update(bytes).digest("hex");
265
328
  if (args.base64?.trim()) {
266
- const mimeType = args.mimeType ||
267
- args.base64.match(/^data:([^;]+);base64,/)?.[1] ||
268
- "image/png";
269
329
  const bytes = Buffer.from(args.base64.trim().replace(/^data:[^;]+;base64,/, ""), "base64");
330
+ const mimeType = exigePeca(bytes, "O base64", true);
270
331
  confereTeto(bytes, mimeType, "Peça");
271
332
  const hex = hexDe(bytes);
272
333
  return { bytes, mimeType, sourceKey: `sha256:${hex}`, sha256: hex };
@@ -275,8 +336,7 @@ async function resolveIngestBytes(args) {
275
336
  if (isRemoteContext()) {
276
337
  throw new Error("filePath só funciona no MCP instalado (stdio). No remoto, mande sourceUrl.");
277
338
  }
278
- const bytes = await readFile(args.filePath);
279
- const mimeType = args.mimeType || mimeFromPath(args.filePath) || "image/png";
339
+ const { bytes, mimeType } = await lerPecaLocal(args.filePath, true);
280
340
  confereTeto(bytes, mimeType, "Arquivo");
281
341
  const hex = hexDe(bytes);
282
342
  return { bytes, mimeType, sourceKey: `sha256:${hex}`, sha256: hex };
@@ -16,15 +16,15 @@ export const helenSchema = z.object({
16
16
  text: z
17
17
  .string()
18
18
  .optional()
19
- .describe("Texto a falar (action=speak). Max 5000 chars."),
19
+ .describe("Texto a falar (action=speak). Max 5000 chars. Cobra pelo tamanho, igual em todo motor: 500 Sinapses por 1.200 caracteres, mínimo 50."),
20
20
  provider: z
21
21
  .enum(["elevenlabs", "google", "fish"])
22
22
  .optional()
23
- .describe("elevenlabs (inglês expressivo, mais caro), google (Gemini TTS, mais barato) ou fish (S2.1 Pro, o melhor em japonês e nas línguas asiáticas). Default elevenlabs."),
23
+ .describe("elevenlabs (inglês expressivo), google (Gemini TTS) ou fish (S2.1 Pro, o melhor em japonês e nas línguas asiáticas; ou Drama 3 com modelId='drama-3', a fala dirigida). Os três cobram igual. Default elevenlabs."),
24
24
  modelId: z
25
25
  .string()
26
26
  .optional()
27
- .describe("ElevenLabs: 'eleven_v3' (default) ou 'eleven_multilingual_v2'. Google: 'gemini-3.1-flash-tts-preview' (default). Fish: escolhido no servidor, o que vier aqui é ignorado."),
27
+ .describe("ElevenLabs: 'eleven_v3' (default) ou 'eleven_multilingual_v2'. Google: 'gemini-3.1-flash-tts-preview' (default). Fish: 's2.1-pro-free' (S2.1 Pro, default) ou 'drama-3' (Drama 3: a direção vai no próprio text, '[sussurrando] frase' muda a atuação, '<emphasis>palavra</emphasis>' crava uma palavra; mesmo preço). Id desconhecido no Fish vira S2.1 Pro."),
28
28
  voiceId: z
29
29
  .string()
30
30
  .optional()
@@ -48,7 +48,7 @@ export const helenSchema = z.object({
48
48
  stylePreamble: z
49
49
  .string()
50
50
  .optional()
51
- .describe("Google: prefixo de instrução de mood ('Read this in a thoughtful tone:'). Não suportado em ElevenLabs."),
51
+ .describe("Google: prefixo de instrução de mood ('Read this in a thoughtful tone:'), conta no tamanho cobrado. Não suportado em ElevenLabs."),
52
52
  voiceSettings: z
53
53
  .object({
54
54
  stability: z.number().min(0).max(1).optional(),
@@ -78,7 +78,7 @@ export const helenSchema = z.object({
78
78
  });
79
79
  const PRESETS = {
80
80
  elevenlabs: {
81
- note: "ElevenLabs presets. modelId default 'eleven_v3'. Custa ~$0.30 USD pra 500 chars.",
81
+ note: "ElevenLabs presets. modelId default 'eleven_v3'. Mesmo preço dos outros motores: 500 Sinapses por 1.200 caracteres, mínimo 50.",
82
82
  voices: [
83
83
  { voiceId: "pNInz6obpgDQGcFmaJgB", name: "Adam", use: "narração masculina pro/editorial, médio-grave" },
84
84
  { voiceId: "EXAVITQu4vr4xnSDxMAC", name: "Sarah", use: "narração feminina, calorosa" },
@@ -87,7 +87,7 @@ const PRESETS = {
87
87
  ],
88
88
  },
89
89
  google: {
90
- note: "Google Gemini TTS. modelId 'gemini-3.1-flash-tts-preview'. Custa ~$0.01 USD pra 500 chars (mais barato).",
90
+ note: "Google Gemini TTS. modelId 'gemini-3.1-flash-tts-preview'. Mesmo preço dos outros motores: 500 Sinapses por 1.200 caracteres, mínimo 50.",
91
91
  voices: [
92
92
  { voiceName: "Kore", use: "feminina, neutra (default)" },
93
93
  { voiceName: "Aoede", use: "feminina, melódica" },
@@ -102,7 +102,7 @@ const PRESETS = {
102
102
  ],
103
103
  },
104
104
  fish: {
105
- note: "Fish Audio S2.1 Pro. Não precisa de modelId (o servidor escolhe) e detecta o idioma pelo texto. É o motor mais forte da casa em japonês, mandarim e coreano: cena de anime, fala de personagem, narração em ja pedem ele. Voz opcional em fishReferenceId; sem ela, a voz default.",
105
+ note: "Fish Audio, dois modelos no mesmo preço, os dois detectam o idioma pelo texto. S2.1 Pro (default, sem modelId) é o motor mais forte da casa em japonês, mandarim e coreano: cena de anime, fala de personagem, narração em ja pedem ele. Drama 3 (modelId='drama-3') é a fala dirigida: a direção vai escrita no text, entre colchetes antes da frase ('[sussurrando, meio sem graça] ...') ou em tag por palavra (<whisper>, <emphasis>, <soft>, <fast>, <slow>, <stress>, <forceful>); a direção não é lida em voz alta. Voz opcional em fishReferenceId; sem ela, a voz default.",
106
106
  voices: [
107
107
  {
108
108
  fishReferenceId: null,
@@ -128,8 +128,8 @@ export async function helen(args) {
128
128
  (provider === "elevenlabs"
129
129
  ? "eleven_v3"
130
130
  : provider === "fish"
131
- // O motor real do Fish é o header `model` do servidor. Este id viaja só
132
- // pra log e pra chave de cache não misturar os motores.
131
+ // S2.1 Pro. O Drama 3 vem pedido em modelId ('drama-3'); o servidor
132
+ // resolve pela allowlist dele e id desconhecido vira S2.1.
133
133
  ? "s2.1-pro-free"
134
134
  // 3.1 Flash é o único TTS do Google que gera nas contas free da casa
135
135
  // (o 2.5 responde 200 sem áudio nelas e só sai na conta central).
@@ -20,6 +20,9 @@ const MODELS = [
20
20
  "mai-image-2-6", // MAI Image 2.6 (Microsoft), no mesmo recurso Foundry do gpt-image-2 · foto rápida · cobra por ÁREA, então o adder de resolução é real · teto 2K (1536²) · SEM refs
21
21
  "muse-image", // Meta Muse Image · barato na edição por referência (10 refs no catálogo, 5 pelo MCP) · sem escada de resolução · moderação apertada, não é +18
22
22
  "flux-3", // FLUX 3 (Black Forest Labs) · texto e composição precisos · COM refs (10 no catálogo, 5 pelo MCP) · adder até 2K · moderado, não é +18
23
+ "flux-3-klein", // FLUX 3 Image Klein (BFL, pela fal) · o FLUX 3 leve, rápido e barato · COM refs (10 no catálogo, 5 pelo MCP) · adder até 2K · moderado, não é +18
24
+ "krea-3", // Krea 3 (Krea, beta) · edita por referência e segura personagem · COM refs (8 no catálogo, 5 pelo MCP), CADA ref soma no preço · adder até 2K · moderado, não é +18
25
+ "krea-3-high", // O MESMO Krea 3 com thinking high: planeja a edição antes, pra várias refs combinadas · mais caro e mais lento
23
26
  // ByteDance via ModelArk direto. Corp/censurado, 2K nativo, COM refs (até 4).
24
27
  // (O "nova-canvas" ficava aqui e saiu: a AWS matou o modelo. Pedido antigo
25
28
  // ainda funciona, o backend faz o alias pro gpt-image-2-low.)
@@ -116,7 +119,7 @@ export const imageSchema = z.object({
116
119
  model: z
117
120
  .enum(MODELS)
118
121
  .optional()
119
- .describe("Default 'nano-banana-2' (Nano Banana 2.1 com refs). 'nano-banana-max' (Pro 3) = qualidade alta. 'gpt-image-2-low/high' = GPT Image 2 nos tiers low e high. 'gpt-image-2-5-flare'/'gpt-image-2-5-sunburst' = GPT Image 2.5, a geração de set/2026, os dois em quality high e com referência: o Flare é o rápido, pra volume; o Sunburst é o de precisão, pra composição complexa e edição em várias voltas. 'mai-image-2-6' (MAI Image 2.6, da Microsoft) = foto rápida no mesmo recurso do gpt-image-2, e o único da casa que cobra por ÁREA: o adder de resolução aqui é real, o teto é 2K (1536², porque acima disso o provider recusa por total de pixels) e ele não aceita referência. 'muse-image' (Muse Image, da Meta) = barato na edição por referência e bom pra segurar personagem (10 refs no catálogo; pelo MCP vale o teto de 5 do campo referenceImageUrls); sem escada de resolução (o motor decide o tamanho e cobra o mesmo) e moderação apertada, NÃO serve pra +18. 'flux-3' (FLUX 3, da Black Forest Labs) = texto na arte e composição precisos, aceita referência (10 no catálogo; pelo MCP vale o teto de 5 do campo referenceImageUrls), resolução até 2K; com referência a proporção segue a primeira imagem; moderado, NÃO serve pra +18. 'seedream-4-5'/'seedream-5-0'/'seedream-5-0-pro' = ByteDance 2K nativo, cinematográfico, aceita até 4 referências (o 5.0 é a geração nova, entende prompt complexo melhor; o 5.0-pro é o topo da linha). 'grok-2-image'/'grok-2-image-quality' = xAI Grok Imagine da geração anterior (moderação frouxa +18, aceita refs e aspect; quality é mais fiel pra character lock); 'grok-imagine-2' = Grok Imagine Image 2.0, a geração nova de ago/2026, que é a melhor da linha integrando desenho e foto na mesma peça. DEGEN (uncensored, gate +18). NUDEZ É NÍVEL, NÃO MOTOR: três escolhas têm um par com nudez, e o par vem no campo `ousadia` de action=models. Sem nudez = o id da escolha; com nudez = o id do par + loraIntensity. Não existe motor '+18', 'Nu' ou 'Livre' separado pra procurar. 'wavespeed-chroma' (fotorrealista rápido, sem referência), 'wavespeed-flux2' (Flux.2 Klein Base: foto rápida, até 4 referências, segura o rosto) · com nudez = 'wavespeed-klein-nu' (o mesmo Klein com a LoRA de nudez no registro de foto; a identidade continua vindo da referência), 'wavespeed-klein-anime' (Flux.2 Klein + LoRA anime, inteligente e controlável) · com nudez = 'wavespeed-klein-anime-plus', 'wavespeed-flux-nsfw' (Flux dev + LoRA NSFW, sem par: a Ousadia é dele mesmo, via loraIntensity), 'wavespeed-klein-celular' (o MESMO Klein 9B com a LoRA Phone Photography por cima: foto de celular, candid, luz de janela; no edit com a referência da personagem o rosto fica quase idêntico, e é a receita de conteúdo de perfil de criadora pra personagem FOTOGRÁFICA. Em personagem cel-shaded o edit troca o traço, então não use com a Aria. Comece o prompt com 'This is a candid photograph taken with a smartphone of' e enumere 'same face, same hair' antes da cena; sem Ousadia), 'wavespeed-klein-transparencia' (o MESMO Klein 9B com a LoRA de tecido transparente: o efeito vem da LoRA e não da descrição; segura o rosto da ref; sem Ousadia), 'wavespeed-krea2-realism' (Krea 2 Realism: base 12B com LoRA de realismo, pele crua, hospedada na WaveSpeed SEM o classificador de entrada da fal, que devolve 422 até em prompt sem palavra de nudez; img2img de verdade com 1 referência, força regulável em refStrength, e 2K) · com nudez = 'wavespeed-krea2' (o mesmo realismo + LoRA NSFW; é a antiga 'Krea 2 Livre'), 'wavespeed-krea2-transparencia' (a mesma LoRA de transparência na base Krea 2: fotografia mais rica, identidade mais frouxa que a irmã Klein; 1 referência; sem Ousadia) = WaveSpeed rápido. LEGADO, fora da escolha da tela e ainda gerando pelo id (não ofereça): 'wavespeed-klein-plus' (a escolha virou 'wavespeed-klein-nu'), 'wavespeed-krea2-base' (Krea 2 sem LoRA nenhuma; a escolha virou 'wavespeed-krea2-realism'), 'fal-krea2-realism-v2' (Krea 2 Realism pela fal, ~4s: aceita até 3 referências de ESTILO, paleta/luz/textura de uma série, não character lock, e com referência a LoRA não vai junto; a escolha virou 'wavespeed-krea2-realism'). 'civitai-wai-illustrious'/'civitai-nova-anime-xl' (anime), 'civitai-pony-v6' (Pony V6 XL, base nº1) = Civitai sdcpp rápido. 'civitai-anima' (Anima: 2B da CircleStone com a Comfy Org, base própria e não SDXL; entrega anime de PRODUÇÃO, linha fechada e sombra chapada, em vez do registro de ilustração dos Illustrious. O encoder é um LLM, então escreva a cena em FRASE e não em tag booru, e não cole score_9/masterpiece aqui)."),
122
+ .describe("Default 'nano-banana-2' (Nano Banana 2.1 com refs). 'nano-banana-max' (Pro 3) = qualidade alta. 'gpt-image-2-low/high' = GPT Image 2 nos tiers low e high. 'gpt-image-2-5-flare'/'gpt-image-2-5-sunburst' = GPT Image 2.5, a geração de set/2026, os dois em quality high e com referência: o Flare é o rápido, pra volume; o Sunburst é o de precisão, pra composição complexa e edição em várias voltas. 'mai-image-2-6' (MAI Image 2.6, da Microsoft) = foto rápida no mesmo recurso do gpt-image-2, e o único da casa que cobra por ÁREA: o adder de resolução aqui é real, o teto é 2K (1536², porque acima disso o provider recusa por total de pixels) e ele não aceita referência. 'muse-image' (Muse Image, da Meta) = barato na edição por referência e bom pra segurar personagem (10 refs no catálogo; pelo MCP vale o teto de 5 do campo referenceImageUrls); sem escada de resolução (o motor decide o tamanho e cobra o mesmo) e moderação apertada, NÃO serve pra +18. 'flux-3' (FLUX 3, da Black Forest Labs) = texto na arte e composição precisos, aceita referência (10 no catálogo; pelo MCP vale o teto de 5 do campo referenceImageUrls), resolução até 2K; com referência a proporção segue a primeira imagem; moderado, NÃO serve pra +18. 'flux-3-klein' (FLUX 3 Image Klein, o irmão leve de pesos abertos do FLUX 3) = foto e personagem rápidos (~5 s) e baratos, aceita referência (10 no catálogo; pelo MCP vale o teto de 5 do campo referenceImageUrls) e segura a pessoa da ref numa cena nova, resolução até 2K; moderado, NÃO serve pra +18. 'krea-3' (Krea 3, da Krea, em beta) = edição por referência que SEGURA PERSONAGEM, inclusive anime e criatura com marcador no rosto, e foto com pele crua; aceita referência (8 no catálogo; pelo MCP vale o teto de 5 do campo referenceImageUrls), resolução até 2K. É o ÚNICO da casa que cobra cada referência à parte: o preço sobe por ref (o valor vivo sai em action=models), então mande só as refs que a cena precisa. Escreva em linguagem simples e chame cada referência pelo que ela mostra ('a primeira imagem é o rosto, a segunda o figurino'); pra edição, a mudança primeiro e uma frase de preservação no fim. 'krea-3-high' = o MESMO Krea 3 com thinking high: monta um plano de edição a partir do prompt e das refs antes de gerar; use quando combinar várias refs ou quando o 'krea-3' errou o pedido. Moderado, NÃO serve pra +18. 'seedream-4-5'/'seedream-5-0'/'seedream-5-0-pro' = ByteDance 2K nativo, cinematográfico, aceita até 4 referências (o 5.0 é a geração nova, entende prompt complexo melhor; o 5.0-pro é o topo da linha). 'grok-2-image'/'grok-2-image-quality' = xAI Grok Imagine da geração anterior (moderação frouxa +18, aceita refs e aspect; quality é mais fiel pra character lock); 'grok-imagine-2' = Grok Imagine Image 2.0, a geração nova de ago/2026, que é a melhor da linha integrando desenho e foto na mesma peça. DEGEN (uncensored, gate +18). NUDEZ É NÍVEL, NÃO MOTOR: três escolhas têm um par com nudez, e o par vem no campo `ousadia` de action=models. Sem nudez = o id da escolha; com nudez = o id do par + loraIntensity. Não existe motor '+18', 'Nu' ou 'Livre' separado pra procurar. 'wavespeed-chroma' (fotorrealista rápido, sem referência), 'wavespeed-flux2' (Flux.2 Klein Base: foto rápida, até 4 referências, segura o rosto) · com nudez = 'wavespeed-klein-nu' (o mesmo Klein com a LoRA de nudez no registro de foto; a identidade continua vindo da referência), 'wavespeed-klein-anime' (Flux.2 Klein + LoRA anime, inteligente e controlável) · com nudez = 'wavespeed-klein-anime-plus', 'wavespeed-flux-nsfw' (Flux dev + LoRA NSFW, sem par: a Ousadia é dele mesmo, via loraIntensity), 'wavespeed-klein-celular' (o MESMO Klein 9B com a LoRA Phone Photography por cima: foto de celular, candid, luz de janela; no edit com a referência da personagem o rosto fica quase idêntico, e é a receita de conteúdo de perfil de criadora pra personagem FOTOGRÁFICA. Em personagem cel-shaded o edit troca o traço, então não use com a Aria. Comece o prompt com 'This is a candid photograph taken with a smartphone of' e enumere 'same face, same hair' antes da cena; sem Ousadia), 'wavespeed-klein-transparencia' (o MESMO Klein 9B com a LoRA de tecido transparente: o efeito vem da LoRA e não da descrição; segura o rosto da ref; sem Ousadia), 'wavespeed-krea2-realism' (Krea 2 Realism: base 12B com LoRA de realismo, pele crua, hospedada na WaveSpeed SEM o classificador de entrada da fal, que devolve 422 até em prompt sem palavra de nudez; img2img de verdade com 1 referência, força regulável em refStrength, e 2K) · com nudez = 'wavespeed-krea2' (o mesmo realismo + LoRA NSFW; é a antiga 'Krea 2 Livre'), 'wavespeed-krea2-transparencia' (a mesma LoRA de transparência na base Krea 2: fotografia mais rica, identidade mais frouxa que a irmã Klein; 1 referência; sem Ousadia) = WaveSpeed rápido. LEGADO, fora da escolha da tela e ainda gerando pelo id (não ofereça): 'wavespeed-klein-plus' (a escolha virou 'wavespeed-klein-nu'), 'wavespeed-krea2-base' (Krea 2 sem LoRA nenhuma; a escolha virou 'wavespeed-krea2-realism'), 'fal-krea2-realism-v2' (Krea 2 Realism pela fal, ~4s: aceita até 3 referências de ESTILO, paleta/luz/textura de uma série, não character lock, e com referência a LoRA não vai junto; a escolha virou 'wavespeed-krea2-realism'). 'civitai-wai-illustrious'/'civitai-nova-anime-xl' (anime), 'civitai-pony-v6' (Pony V6 XL, base nº1) = Civitai sdcpp rápido. 'civitai-anima' (Anima: 2B da CircleStone com a Comfy Org, base própria e não SDXL; entrega anime de PRODUÇÃO, linha fechada e sombra chapada, em vez do registro de ilustração dos Illustrious. O encoder é um LLM, então escreva a cena em FRASE e não em tag booru, e não cole score_9/masterpiece aqui)."),
120
123
  aspectRatio: z
121
124
  .enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"])
122
125
  .optional()
@@ -133,7 +136,7 @@ export const imageSchema = z.object({
133
136
  referenceImageUrls: z
134
137
  .array(httpUrl())
135
138
  .optional()
136
- .describe("URLs públicas de referência pra combinar numa geração só (character/style lock), igual ao modal 'Selecionar Referência' do gerador web. Fontes: sua galeria (sapiens_gallery, campo url), o Acervo, e personagens públicos (sapiens_character action=list_public → mainImageUrl/imageUrls). Restrito a hosts do Sapiens (Bunny CDN / Convex) + Wikimedia. Requer model com refs (o campo supportsReferences em action=models é a fonte): nano-banana-*, gpt-image-2-*, muse-image, flux-3, seedream-*, grok-* (até 3), a família Klein (wavespeed-flux2 e wavespeed-klein-*, até 4), wavespeed-flux-nsfw (1), a família Krea 2 da WaveSpeed (wavespeed-krea2-realism, wavespeed-krea2 e wavespeed-krea2-transparencia; img2img: a primeira referência vira a imagem-base, 1 ref, e refStrength diz quanto dela fica) ou fal-krea2-* (referência de ESTILO, teto de 3). Soma com sourceImageIds: até 5 no total (acima disso o servidor recusa a chamada), e cada motor corta no seu teto quando ele é menor (Seedream 4, Klein 4, Grok 3, Krea 2 e Flux NSFW 1; o campo maxReferences em action=models é a fonte)."),
139
+ .describe("URLs públicas de referência pra combinar numa geração só (character/style lock), igual ao modal 'Selecionar Referência' do gerador web. Fontes: sua galeria (sapiens_gallery, campo url), o Acervo, e personagens públicos (sapiens_character action=list_public → mainImageUrl/imageUrls). Restrito a hosts do Sapiens (Bunny CDN / Convex) + Wikimedia. Requer model com refs (o campo supportsReferences em action=models é a fonte): nano-banana-*, gpt-image-2-*, muse-image, flux-3, flux-3-klein, krea-3 e krea-3-high (cada ref soma no preço), seedream-*, grok-* (até 3), a família Klein (wavespeed-flux2 e wavespeed-klein-*, até 4), wavespeed-flux-nsfw (1), a família Krea 2 da WaveSpeed (wavespeed-krea2-realism, wavespeed-krea2 e wavespeed-krea2-transparencia; img2img: a primeira referência vira a imagem-base, 1 ref, e refStrength diz quanto dela fica) ou fal-krea2-* (referência de ESTILO, teto de 3). Soma com sourceImageIds: até 5 no total (acima disso o servidor recusa a chamada), e cada motor corta no seu teto quando ele é menor (Seedream 4, Klein 4, Grok 3, Krea 2 e Flux NSFW 1; o campo maxReferences em action=models é a fonte)."),
137
140
  sourceImageIds: z
138
141
  .array(z.string())
139
142
  .optional()
@@ -253,6 +256,9 @@ export async function image(args) {
253
256
  priceSinapses: m.effectivePriceSinapses,
254
257
  maxResolution: m.maxResolution ?? "4K",
255
258
  resolutionAdders: m.resolutionAdders,
259
+ // Motor que cobra CADA referência (Krea 3): Sinapses por ref, por
260
+ // resolução. null = referência não muda o preço.
261
+ referenceAdders: m.referencePriceSinapses ?? null,
256
262
  supportsReferences: !!m.supportsReferences,
257
263
  degen: /^(civitai-|wavespeed-|fal-)/.test(String(m.id)),
258
264
  // O id que gera esta mesma escolha COM nudez (manda junto loraIntensity).
@@ -285,6 +291,7 @@ export async function image(args) {
285
291
  families,
286
292
  note: "priceSinapses é o preço COBRADO em 1K (já com override admin). Acima de 1K some o adder de resolutionAdders[size], que vem clampado ao teto do motor: em modelo que para em 2K, a chave '4K' repete o valor de 2K porque é isso que a cobrança aplica quando você pede 4K nele. " +
287
293
  "Preço final = priceSinapses + resolutionAdders[size]. degen=+18 (gate na galeria). " +
294
+ "Quando referenceAdders vem preenchido (Krea 3), cada referência soma referenceAdders[size] ao preço, até o teto do motor. " +
288
295
  "Modelos com a MESMA family são o mesmo motor em versão diferente (o variant diz qual): compare preço e nota entre eles antes de trocar de família. " +
289
296
  "Nudez é nível, não motor: quando `ousadia` vem preenchido, gere com ousadia.model + loraIntensity pra ter nudez e com o id da linha pra não ter (o preço com nudez vem em ousadia.priceSinapses).",
290
297
  };
@@ -24,7 +24,7 @@ export const studiosSchema = z.object({
24
24
  studio: z
25
25
  .string()
26
26
  .optional()
27
- .describe("Pra action=get: slug do estúdio (ex 'helen-voice', 'musicator', 'persona-sapiens', 'sapiens-shorts')."),
27
+ .describe("Pra action=get: slug do estúdio (ex 'helen-voice', 'musicator', 'persona-sapiens', 'sapiens-video')."),
28
28
  name: z
29
29
  .string()
30
30
  .optional()
@@ -78,25 +78,6 @@ const STUDIOS = {
78
78
  mcpReady: true,
79
79
  mcpNote: "Coberto via sapiens_persona. PRIMÁRIO (de graça, qualquer logado): get_quiz + submit_quiz + my_profile — aplica o teste conversando e ALIMENTA o personalityProfile do user. SECUNDÁRIO: generate + list_codes (arte dos arquétipos, 450 Sinapses).",
80
80
  },
81
- "personagem-atlas": {
82
- name: "Personagem-Atlas",
83
- description: "Dossiê editorial premium de personagens sintéticos: 10 slides com tipografia refinada, paleta cromática e geração de conteúdo via IA. É construção de PERSONAGEM ficcional, não o teste de personalidade do user (esse é o persona-sapiens).",
84
- url: `${APP}/experimentos/personagem-atlas`,
85
- status: "stable",
86
- tags: ["personagem", "dossie", "editorial", "slides", "atlas", "character"],
87
- convex: "characterAtlas",
88
- mcpReady: false,
89
- },
90
- "sapiens-shorts": {
91
- name: "Sapiens Shorts (UGC / Reflexão)",
92
- description: "Gera shorts verticais 9:16 via VEO. Estilos: ugc, unboxing, app-demo, reflexao. Composição automática persona + screen. v1.5: render via MCP.",
93
- url: `${APP}/imgen-sapiens-shorts`,
94
- status: "stable",
95
- tags: ["video", "shorts", "9:16", "veo", "ugc"],
96
- convex: "sapiensShortsActions",
97
- mcpReady: true,
98
- mcpNote: "Coberto via sapiens_shorts (render com brief structured). Compose persona+screen via composeWithImageUrl continua UI-only (compose tem credit refund que mexe em scheduler).",
99
- },
100
81
  "sapiens-video": {
101
82
  name: "Sapiens Video (longer-form)",
102
83
  description: "Vídeo longer-form com prompt livre. v1.5: generate via MCP.",
@@ -7,7 +7,7 @@ import { httpUrl } from "../schema.js";
7
7
  *
8
8
  * Sub-actions:
9
9
  * - create: escolhe modelo + config e gera num call só (cria a row + renderiza).
10
- * Habilita Seedance/Kling/WAN/Motion (WaveSpeed) e os Veo, sem o site.
10
+ * Habilita Seedance/Kling/WAN/Motion (WaveSpeed) e o Omni, sem o site.
11
11
  * Com `templateSlug`, a RECEITA da casa embrulha a cena (estilo, arco,
12
12
  * áudio, look) e escolhe o motor: `prompt` vira só a cena e `brief`
13
13
  * preenche os campos do formato.
@@ -60,9 +60,13 @@ import { httpUrl } from "../schema.js";
60
60
  * com as duas views por ficha: url = depth map puro (driving pros
61
61
  * geradores) e skeletonUrl = a soma (sombras + esqueleto, preview
62
62
  * humano que separa personagem de objeto). Sem custo.
63
- * - sonorize: dá som a um clipe SEU já gerado (MMAudio v2): imageId + prompt do
64
- * som da cena -> VARIANTE nova com trilha sincronizada (o original
65
- * fica intacto). Cobra por segundo do clipe; poll via action=status.
63
+ * - sonorize: dá som a um clipe SEU já gerado: imageId + prompt do som da cena
64
+ * -> VARIANTE nova com trilha sincronizada (o original fica intacto).
65
+ * engine escolhe o motor: Mirelo SFX 2.0 (default, guarda a fala
66
+ * do clipe), MMAudio v2 (mais barato, som do zero) ou ElevenLabs
67
+ * Video to Music (trilha musical que segue os cortes, por baixo do
68
+ * som do clipe). Cobra por segundo do clipe na tabela do motor;
69
+ * poll via action=status.
66
70
  *
67
71
  * Vídeos Programáticos (Estúdio de Vídeo, /experimentos/films) — ADMIN, sem custo.
68
72
  * O Lab de specs da casa (tabela videoSpecs, 5 kinds: demo | aula-tour | essay |
@@ -112,6 +116,13 @@ import { httpUrl } from "../schema.js";
112
116
  * vídeos (role 'refvideo', somando 15s), que é o caminho de take
113
117
  * longo com personagem travada. Referência e frame inicial são
114
118
  * EXCLUSIVOS (endpoints diferentes no provider): mande um dos dois.
119
+ * - sapiens-video-pvideo-2-pro P-Video-2 Pro (Pruna): o MiniMax H3 acelerado pela Pruna, o
120
+ * RASCUNHO de personagem travada: até 9 imagens (role 'ref' + frame
121
+ * inicial e final, tudo junto no mesmo pedido), 5 a 15s em '480p'
122
+ * ou '768p', áudio nativo sem toggle, e o take volta em segundos.
123
+ * Sem t2v: precisa de ao menos uma imagem. COBRA POR IMAGEM além
124
+ * do segundo (o servidor conta as que você mandou), então cote em
125
+ * action=price passando as mesmas imagens do create.
115
126
  * - sapiens-video-h3-spicy MiniMax H3 Spicy — o H3 SEM freio de conteúdo, a partir de uma
116
127
  * imagem sua (i2v puro: a identidade entra pela imagem, não pelo
117
128
  * texto). 3 a 15s, 480p/768p/1080p, som nativo, frame final. É o
@@ -141,9 +152,9 @@ import { httpUrl } from "../schema.js";
141
152
  * - sapiens-video-shot-mimic Shot Mimic — recria o plano do vídeo de referência (câmera,
142
153
  * cortes, blocking) como cena nova; personagem via role 'start'
143
154
  * (vídeo de referência MÁX 15s — acima o provider corta em 15s)
144
- * - sapiens-video-lite/fast/quality Veo 3.1: 4, 6 ou 8s (sem durationSec, 8s), preço
145
- * por segundo; com imagem de referência ou frame final
146
- * o Google só faz 8s. Números vivos em action=models.
155
+ * - sapiens-video-lite/fast/quality Veo 3.1: SAIU. O Google apagou os três em
156
+ * 22/10/2026; o backend recusa antes de cobrar. O
157
+ * sucessor é o sapiens-video-omni-11.
147
158
  * - sapiens-video-omni-11 Gemini Omni 1.1: texto ou imagem -> vídeo de 4, 6, 8 ou 10s (sem
148
159
  * durationSec, 10s), com áudio nativo embutido, em 360p, 720p,
149
160
  * 1080p ou 4k (preço por segundo: 1/3, 1x, 1,5x e 3x do 720p; sem
@@ -171,8 +182,8 @@ import { httpUrl } from "../schema.js";
171
182
  * CUSTO: server-side por config (duração x resolução [x áudio]). NÃO existe "o preço"
172
183
  * de um motor por segundo: existe faixa. Omitir `resolution` não pega o meio nem o
173
184
  * mais barato, pega o tier MAIS CARO do motor (é o clamp de `resolveBilledVideoConfig`,
174
- * que não subcobra), e omitir `durationSec` pega o piso de duração (no VEO e no Omni,
175
- * o padrão do motor: 8s e 10s, o clipe que eles entregavam antes de ter escolha). Antes de prometer
185
+ * que não subcobra), e omitir `durationSec` pega o piso de duração (no Omni,
186
+ * o padrão do motor: 10s, o clipe que ele entregava antes de ter escolha). Antes de prometer
176
187
  * um número pra pessoa, tire a cotação em action=price (sem custo, sem login).
177
188
  * i2v/motion usam `references` em base64 (role 'start' = imagem inicial,
178
189
  * 'end' = frame final, 'driving' = vídeo de movimento do Motion).
@@ -188,6 +199,7 @@ const VIDEO_MODELS = [
188
199
  "sapiens-video-hailuo",
189
200
  "sapiens-video-hailuo-pro",
190
201
  "sapiens-video-h3",
202
+ "sapiens-video-pvideo-2-pro",
191
203
  "sapiens-video-h3-spicy",
192
204
  "sapiens-video-seedance-spicy",
193
205
  "sapiens-video-ltx-spicy",
@@ -195,14 +207,26 @@ const VIDEO_MODELS = [
195
207
  "sapiens-video-wan-3",
196
208
  "sapiens-video-kling-motion",
197
209
  "sapiens-video-shot-mimic",
210
+ // Os três Veo 3.1 e o Omni 1.0 seguem no enum por retrocompat (cliente em
211
+ // versão publicada manda o id e recebe a recusa limpa, sem cobrança): o Google
212
+ // apagou os quatro em 22/10/2026. Peça o Omni 1.1.
198
213
  "sapiens-video-lite",
199
214
  "sapiens-video-fast",
200
215
  "sapiens-video-quality",
201
216
  "sapiens-video-omni-11",
202
- // O 1.0 preview segue no enum porque o backend continua aceitando (rows antigas
203
- // e o override admin que religa), mas nasce desligado: peça o 1.1.
204
217
  "sapiens-video-omni",
205
218
  ];
219
+ // Motores que cobram cada imagem de entrada à parte do segundo (o catálogo diz
220
+ // `referenceImageSinapses`). A cotação manda a contagem só pra eles.
221
+ const COBRA_POR_IMAGEM = new Set(["sapiens-video-pvideo-2-pro"]);
222
+ /** As imagens que o create mandaria: referências mais frame inicial e final. */
223
+ function imagensDoPedido(args) {
224
+ const base64 = (args.references ?? []).filter((r) => !r.role || ["start", "end", "ref"].includes(r.role)).length;
225
+ const refs = (args.referenceImageIds?.length ?? 0) + (args.referenceImageUrls?.length ?? 0) + (args.referenceImagePaths?.length ?? 0);
226
+ const inicial = args.startImageId || args.startImageUrl || args.startImagePath ? 1 : 0;
227
+ const final = args.endImageId || args.endImageUrl || args.endImagePath ? 1 : 0;
228
+ return base64 + refs + inicial + final;
229
+ }
206
230
  // Motor ATIVO no catálogo que fica fora do enum DE PROPÓSITO, com o motivo. O
207
231
  // gate audit:catalog-parity (apps/sapiens) lê este mapa: id ativo que não está
208
232
  // nem no enum nem aqui é erro, porque action=models lista, a skill manda usar e
@@ -356,17 +380,17 @@ export const videoSchema = z.object({
356
380
  .optional()
357
381
  .describe("action=create (opcional quando você passa templateSlug: a receita traz o motor) e action=price (obrigatório). O catálogo POR MODELO (capacidades, referências, tetos, FAIXA de preço) mora na description desta tool e VIVO em action=models; a skill 'video' (sapiens_skill) guia a escolha. " +
358
382
  "Atalho de famílias: seedance/seedance-2-*/seedance-25 = cena+áudio+referências (o 25 estica até 30s e o preço acompanha), kling = anima imagem, wan = WAN 2.5, imagem que fala, wan-3 = WAN 3.0, texto ou imagem com som e frame final, " +
359
- "hailuo/hailuo-pro = movimento puro sem áudio, h3 = 2K ou 768p com áudio e até 9 imagens + 3 vídeos de referência, h3-spicy/seedance-spicy = os mesmos motores sem freio de conteúdo, só a partir de imagem (i2v), kling-motion/shot-mimic = transferência de movimento/plano, lite/fast/quality = Veo 3.1, omni = Gemini com áudio nativo."),
383
+ "hailuo/hailuo-pro = movimento puro sem áudio, h3 = 2K ou 768p com áudio e até 9 imagens + 3 vídeos de referência, pvideo-2-pro = o H3 acelerado pela Pruna, rascunho barato e rápido de personagem travada (até 9 imagens, cobra por imagem, 480p/768p, sem t2v), h3-spicy/seedance-spicy = os mesmos motores sem freio de conteúdo, só a partir de imagem (i2v), kling-motion/shot-mimic = transferência de movimento/plano, lite/fast/quality = Veo 3.1, omni = Gemini com áudio nativo."),
360
384
  durationSec: z
361
385
  .number()
362
386
  .optional()
363
- .describe("action=create e action=price: duração em segundos. Veo 3.1 (lite/fast/quality) 4/6/8 (com imagem de referência ou frame final, só 8), Gemini Omni 4/6/8/10, Seedance 2.0/Shot Mimic 4-15, Seedance 2.5 4-30 (o preço acompanha: confirme a duração com a pessoa antes de passar de 15), Kling 3-15, WAN 2.5 5/10, WAN 3.0 5/8/10, H3 5/6/8/10/12/15 nos dois tiers (768p e 2k), H3 Spicy 3-15, Seedance 2.0 Spicy 4-15 em 480p e 720p, 4-10 em 1080p. " +
364
- "O PREÇO ESCALA COM A DURAÇÃO. Duração ausente ou FORA do leque do motor cai no PISO, menos no Veo e no Omni, que caem no padrão deles (8s e 10s). " +
387
+ .describe("action=create e action=price: duração em segundos. Gemini Omni 1.1 4/6/8/10, Seedance 2.0/Shot Mimic 4-15, Seedance 2.5 4-30 (o preço acompanha: confirme a duração com a pessoa antes de passar de 15), Kling 3-15, WAN 2.5 5/10, WAN 3.0 5/8/10, H3 5/6/8/10/12/15 nos dois tiers (768p e 2k), P-Video-2 Pro 5/6/8/10/12/15, H3 Spicy 3-15, Seedance 2.0 Spicy 4-15 em 480p e 720p, 4-10 em 1080p. " +
388
+ "O PREÇO ESCALA COM A DURAÇÃO. Duração ausente ou FORA do leque do motor cai no PISO, menos no Omni, que cai no padrão dele (10s). " +
365
389
  "action=shadows: duração do vídeo-fonte, se souber (cobra 200/s; sem ela, flat ~2000)."),
366
390
  resolution: z
367
391
  .enum(["360p", "480p", "720p", "1080p", "768p", "2k", "4k"])
368
392
  .optional()
369
- .describe("action=create e action=price: resolução (Seedance, WAN 2.5 e 3.0, Shot Mimic, H3, H3 Spicy, Seedance Spicy, Gemini Omni 1.1). NÃO existe default 720p aqui: resolução ausente, ou fora do que o motor aceita, cai no tier MAIS CARO do motor, menos no Gemini Omni 1.1, que fica no 720p (o tier que ele vendeu antes de ter escolha). " +
393
+ .describe("action=create e action=price: resolução (Seedance, WAN 2.5 e 3.0, Shot Mimic, H3, P-Video-2 Pro ('480p'/'768p'), H3 Spicy, Seedance Spicy, Gemini Omni 1.1). NÃO existe default 720p aqui: resolução ausente, ou fora do que o motor aceita, cai no tier MAIS CARO do motor, menos no Gemini Omni 1.1, que fica no 720p (o tier que ele vendeu antes de ter escolha). " +
370
394
  "O Gemini Omni 1.1 vai de '360p' a '4k' (360p custa 1/3 do 720p, 1080p 1,5x, 4k 3x). " +
371
395
  "É isso que faz um create sem este campo custar muito mais que o piso do catálogo (em sapiens-video-seedance vira 1080p, não 480p). Passe sempre a resolução que você quer pagar, e confira com action=price antes. " +
372
396
  "O H3 usa '768p' ou '2k' (sem eles cai no 2k, que custa 33% mais por segundo); o H3 Spicy usa '480p'/'768p'/'1080p'. Kling e a linha Hailuo 2.3 não usam este campo (resolução fixa pelo motor)."),
@@ -423,6 +447,16 @@ export const videoSchema = z.object({
423
447
  .enum(["sinapses", "dolar"])
424
448
  .optional()
425
449
  .describe("action=create: de qual saldo sai o take. Default 'sinapses' (o saldo do App). 'dolar' cobra no saldo em dólar da API da conta (o mesmo de Developers), sem tocar nas Sinapses: mesmo motor, mesma config, preço em dólar do custo do motor naquela duração, resolução e áudio. Use quando a pessoa pedir pra pagar com o saldo da API ou estiver sem Sinapses e com saldo em dólar. Saldo em dólar curto recusa sem cobrar nada; motor sem preço em dólar recusa. Falha do render estorna no mesmo saldo. O valor cobrado volta em pagoEmDolarUsd. Sinapse nunca vira dólar nem o contrário: são duas carteiras."),
450
+ engine: z
451
+ .enum(["mmaudio-v2", "mirelo-sfx-2.0", "elevenlabs-video-to-music"])
452
+ .optional()
453
+ .describe("action=sonorize: o motor de som. 'mirelo-sfx-2.0' (default) é o motor da casa pra sonorizar: GUARDA a fala que o clipe já tem, tira a música que veio junto e refaz o resto do som em volta; em clipe mudo, faz o som inteiro. " +
454
+ "O Mirelo tira o som da IMAGEM e o texto só empurra: em cena refeita com bicho trocado (recast, Omni editando um take), ele devolve o som do bicho ORIGINAL (o gatinho no take do T-rex saiu com passo e rugido de T-rex). " +
455
+ "'mmaudio-v2' segue o texto: é o motor quando a imagem engana (bicho trocado, cena famosa refeita) e o mais barato por segundo; refaz o som inteiro e a fala do clipe se perde. " +
456
+ "'elevenlabs-video-to-music' não faz som de cena, faz TRILHA: uma música que acompanha os cortes do clipe, mixada por baixo do som que ele já tem (a fala e o ruído ficam); o prompt descreve a música (gênero, instrumentos, clima, onde cresce), não a cena. Recusa, sem cobrar, peça que já tem o fecho da casa (sonorize o take original) e clipe fora da CDN da casa. " +
457
+ "Cote o preço com a pessoa antes. " +
458
+ "O Mirelo e o Video to Music recusam, sem cobrar, peça que entrou de fora (ingest, a casa não sabe a duração dela) e clipe acima de 180s: " +
459
+ "passe a peça de fora antes em action=trim e sonorize o corte; clipe longo, corte em partes."),
426
460
  // --- action=generate (legado) ---
427
461
  imageId: z
428
462
  .string()
@@ -476,7 +510,7 @@ export const videoSchema = z.object({
476
510
  referenceImageIds: z
477
511
  .array(z.string())
478
512
  .optional()
479
- .describe("action=create (Seedance 2.0/Fast/Mini/2.5 até 4; H3 até 9; Veo Fast/Quality e Omni 1.1 até 3; Lite, Kling, Hailuo, WAN 2.5 e 3.0 e os dois Spicy NÃO): generatedImages:_id da SUA galeria " +
513
+ .describe("action=create (Seedance 2.0/Fast/Mini/2.5 até 4; H3 até 9; Omni 1.1 até 3; Kling, Hailuo, WAN 2.5 e 3.0 e os dois Spicy NÃO): generatedImages:_id da SUA galeria " +
480
514
  "como imagens de REFERÊNCIA (role 'ref'). Diferente de startImageId: NÃO viram o 1º frame, guiam " +
481
515
  "estilo/personagem/composição. Fluxo storyboard: gere a folha com sapiens_image " +
482
516
  "templateSlug='storyboard-sapiens-v1' (ou -vertical-v1) e passe folha + personagem aqui. " +
@@ -485,7 +519,7 @@ export const videoSchema = z.object({
485
519
  referenceImageUrls: z
486
520
  .array(httpUrl())
487
521
  .optional()
488
- .describe("action=create (mesmos motores e tetos de referenceImageIds: Seedance até 4, H3 até 9, Veo Fast/Quality e Omni 1.1 até 3): URLs públicas (Bunny/Convex/Wikimedia) como imagens de " +
522
+ .describe("action=create (mesmos motores e tetos de referenceImageIds: Seedance até 4, H3 até 9, Omni 1.1 até 3): URLs públicas (Bunny/Convex/Wikimedia) como imagens de " +
489
523
  "REFERÊNCIA (role 'ref'). Soma com referenceImageIds. No prompt, cite as referências por descrição " +
490
524
  "(ex: 'use the storyboard reference as ordered key poses; ignore line-sketch artifacts')."),
491
525
  referenceVideoUrls: z
@@ -647,7 +681,7 @@ export async function video(args) {
647
681
  "priceMin e priceMax são os dois cantos do motor, cada um com a config que o produz. " +
648
682
  "Ao falar de preço com a pessoa, diga a faixa e a config, nunca um número solto. " +
649
683
  "Pro número exato de uma config antes de gerar, use action=price (sem custo, sem login): ele devolve o valor que o create vai debitar. " +
650
- "Motor com priceMode='por-clipe' tem preço fixo e as três pontas são iguais (hoje nenhum: Veo e Omni cobram por segundo desde out/2026, e sem durationSec dão 8s e 10s). " +
684
+ "Motor com priceMode='por-clipe' tem preço fixo e as três pontas são iguais (hoje nenhum: o Omni cobra por segundo desde out/2026, e sem durationSec dá 10s). " +
651
685
  "available=false = 'em breve' (depende de env, ex: Omni).",
652
686
  };
653
687
  }
@@ -663,6 +697,11 @@ export async function video(args) {
663
697
  durationSec: args.durationSec,
664
698
  resolution: args.resolution,
665
699
  audio: args.audio,
700
+ // Motor que cobra por imagem: a cotação conta as imagens do pedido do
701
+ // mesmo jeito que o create conta no servidor (refs + frame inicial e
702
+ // final). Só vai pra esse motor, porque servidor anterior a ele recusa o
703
+ // campo e derrubaria a cotação dos outros.
704
+ ...(COBRA_POR_IMAGEM.has(args.model) ? { referenceCount: imagensDoPedido(args) } : {}),
666
705
  });
667
706
  // Três avisos possíveis, e a ordem importa: o clamp é o que mais surpreende
668
707
  // (a conta vem diferente do pedido), depois o campo omitido (que o servidor
@@ -872,12 +911,15 @@ export async function video(args) {
872
911
  throw new Error("action=sonorize exige imageId (o clipe SEU, já completed, que vai ganhar som).");
873
912
  }
874
913
  if (!args.prompt || args.prompt.trim().length < 4) {
875
- throw new Error("action=sonorize exige prompt descrevendo o som da cena (ambiente, materiais, impactos, textura).");
914
+ throw new Error("action=sonorize exige prompt descrevendo o som da cena (ambiente, materiais, impactos, textura), ou a música no elevenlabs-video-to-music.");
876
915
  }
877
916
  return await convexAction("mcpExtrasActions:mcpVideoSonorize", {
878
917
  sessionToken,
879
918
  sourceImageId: args.imageId,
880
919
  prompt: args.prompt,
920
+ // Vai sempre: sem o campo o servidor cai no MMAudio (o contrato das versões
921
+ // de antes desta), e o motor da casa pra sonorizar agora é o Mirelo.
922
+ engine: args.engine ?? "mirelo-sfx-2.0",
881
923
  });
882
924
  }
883
925
  if (args.action === "extend") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.98.1",
3
+ "version": "1.100.0",
4
4
  "mcpName": "com.sapiensinteticos/sapiens",
5
5
  "description": "MCP server pra operar o Sapiens Sintéticos (sapiensinteticos.com) pelo Claude Code: gerar imagem, escrever artigo, voz, música e mais, na sua conta. Login pelo código de sapiensinteticos.com/conectar-claude. mcprush.com-bd2545268b8471cdb213c674",
6
6
  "type": "module",