sapiens-mcp 1.50.0 → 1.51.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.
@@ -284,7 +284,11 @@ function withTimeout(p, label) {
284
284
  let timer;
285
285
  const timeout = new Promise((_, reject) => {
286
286
  timer = setTimeout(() => {
287
- reject(new Error(`Timeout: o backend Sapiens não respondeu em ${Math.round(CONVEX_TIMEOUT_MS / 1000)}s (${label}). Tente de novo em instantes.`));
287
+ reject(new Error(`Timeout: o backend Sapiens não respondeu em ${Math.round(CONVEX_TIMEOUT_MS / 1000)}s (${label}). Isto é o CLIENTE desistindo de esperar, não a ` +
288
+ `operação morrendo: ela segue rodando no servidor e, se cobra ` +
289
+ `Sinapses, JÁ cobrou. NÃO repita a chamada — repetir gera e cobra ` +
290
+ `de novo. Espere alguns minutos e leia o estado real por uma ` +
291
+ `leitura (o \`get\`/\`list\`/\`status\` do mesmo tool) antes de decidir.`));
288
292
  }, CONVEX_TIMEOUT_MS);
289
293
  });
290
294
  return Promise.race([
package/dist/registry.js CHANGED
@@ -14,7 +14,10 @@ import { instagram, instagramSchema } from "./tools/instagram.js";
14
14
  import { persona, personaSchema } from "./tools/persona.js";
15
15
  import { helen, helenSchema } from "./tools/helen.js";
16
16
  import { musicator, musicatorSchema } from "./tools/musicator.js";
17
- import { shorts, shortsSchema } from "./tools/shorts.js";
17
+ // A tool sapiens_shorts saiu em ago/2026: os quatro estilos dela viraram
18
+ // receitas de `sapiens_video` (templateSlug), que qualquer membro alcança e que
19
+ // não está preso ao Veo. O BACKEND dela (mcpExtrasActions:mcpShortsRender)
20
+ // continua no ar por retrocompat, porque pacote publicado ainda chama.
18
21
  import { video, videoSchema } from "./tools/video.js";
19
22
  import { write, writeSchema } from "./tools/write.js";
20
23
  import { stockAudio, stockAudioSchema } from "./tools/stockAudio.js";
@@ -80,7 +83,7 @@ export const TOOLS = {
80
83
  handler: community,
81
84
  },
82
85
  sapiens_article: {
83
- description: "NÃO CRIA ARTIGO: este tool só mexe em artigo que JÁ existe. Pra criar draft novo do blog use sapiens_pipeline action=create_draft_article_and_source (devolve articleId + sourceId), ou sapiens_quote_pop pra quote e pop-article. Não confunda com sapiens_write, que é o espaço pessoal do membro em /u/<username> (tabela user_articles), e nunca salve o texto num .md solto no repo: draft de blog mora na tabela articles. CRUD do resto: get (by slug, retorna doc completo pra edit local), update (patch em title/excerpt/tldr/content/tags/etc + VISUAIS: thumbnailUrl capa webp, ogImageUrl JPEG do preview social, bodyImages array das ilustrações inline, conceptMap mapa visual, pra recapear um artigo num novo estilo; NÃO toca status/column/format), publish (status='published', set publishedAt), unpublish (volta pra draft), delete (irreversível), ensure_visuals (gera banner/ilustrações inline/conceptMap que faltam no artigo; idempotente, pula o que existe; ~1700 Sinapses num artigo pelado, forceBanner/forceInline/forceConceptMap regeram).",
86
+ description: "NÃO CRIA ARTIGO: este tool só mexe em artigo que JÁ existe. Pra criar draft novo do blog use sapiens_pipeline action=create_draft_article_and_source (devolve articleId + sourceId), ou sapiens_quote_pop pra quote e pop-article. Não confunda com sapiens_write, que é o espaço pessoal do membro em /u/<username> (tabela user_articles), e nunca salve o texto num .md solto no repo: draft de blog mora na tabela articles. CRUD do resto: get (by slug, retorna doc completo pra edit local), update (patch em title/excerpt/tldr/content/tags/etc + VISUAIS: thumbnailUrl capa webp, ogImageUrl JPEG do preview social, bodyImages array das ilustrações inline, conceptMap mapa visual, pra recapear um artigo num novo estilo; NÃO toca status/column/format), publish (status='published', set publishedAt), unpublish (volta pra draft), delete (irreversível), ensure_visuals (gera banner/ilustrações inline/conceptMap que faltam no artigo; idempotente, pula o que existe; ~1700 Sinapses num artigo pelado, forceBanner/forceInline/forceConceptMap regeram). ensure_visuals é ASSÍNCRONA: volta NA HORA com {status:'running', plan} e a leva corre no servidor, como o vídeo. NÃO repita a chamada pra ver se andou (cada leva gera e COBRA de novo, e as ilustrações somam) — acompanhe com visuals_status (custo 0) até jobStatus='done' ou 'error'. Quando não há nada faltando, ela responde na hora com status:'idle' e o retrato dos visuais, de graça: é a sonda pra saber o que o artigo já tem. Leva já em andamento devolve alreadyRunning:true em vez de abrir outra. visuals_status (custo 0) traz jobStatus (none/running/done/error/stale), o plano da leva, custo, erro e o estado real (hasBanner, inlineCount, hasConceptMap); 'stale' é leva que passou do teto de 10min do servidor sem fechar, ou seja, ninguém está mais gerando.",
84
87
  schema: articleSchema,
85
88
  handler: article,
86
89
  },
@@ -119,13 +122,8 @@ export const TOOLS = {
119
122
  schema: musicatorSchema,
120
123
  handler: musicator,
121
124
  },
122
- sapiens_shorts: {
123
- description: "Sapiens Shorts — render vertical 9:16 via VEO com brief structured (admin-only). Sub-action: render. Args: imageId (persona pré-existente em generatedImages, descubra via sapiens_gallery), styleId ('ugc'/'unboxing'/'app-demo'/'reflexao'), brief (product+hook+shots+vibe), references opcionais. render é ASSÍNCRONO: volta na hora com {imageId, status:'rendering', url:null}, e você acompanha com sapiens_video action=status imageId=<id> até status='completed' (traz a url VEO, expiração curta, baixe logo) ou 'error'. Pré-requisito: o imageId precisa ter row em generatedImages do user da sessão e cost definido. Pra criar a row sem passar pela UI: sapiens_image action=request_generation (modelos sapiens-video-*).",
124
- schema: shortsSchema,
125
- handler: shorts,
126
- },
127
125
  sapiens_video: {
128
- 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 (os Veo fast/quality também aceitam, até 3; Lite/Kling/WAN/Omni 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-seedance-15' (Seedance 1.5 Pro: o degrau entre o 1.0 e a linha 2.x; t2v/i2v, 4-12s, 480/720/1080p, som sempre incluso sem toggle; imagem de referência e frame final ficam de fora por ora), '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 depois do Seedance 1.0 Fast, 'sapiens-video-h3' (MiniMax H3: 2K com áudio nativo incluso sem toggle, 5 a 10s, t2v/i2v e frame final; não aceita imagem de referência), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), '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' (Gemini Omni: texto vira vídeo 10s 720p com áudio nativo; NÃO aceita mídia do user, ignora references/durationSec/resolution; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), audio, aspectRatio. 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. 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 deepshadow (depth-map) de um vídeo: entra na sua timeline de vídeos e no Acervo como driving reutilizável; 'shadows-list' lista os deepshadows prontos. Sub-action 'models' (sem custo, sem login): lista os modelos de vídeo ativos + preço-piso + config (durações/resoluções) + disponibilidade (Omni depende de env). 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.",
126
+ 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 (os Veo fast/quality também aceitam, até 3; Lite/Kling/WAN/Omni 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-seedance-15' (Seedance 1.5 Pro: o degrau entre o 1.0 e a linha 2.x; t2v/i2v, 4-12s, 480/720/1080p, som sempre incluso sem toggle; imagem de referência e frame final ficam de fora por ora), '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 depois do Seedance 1.0 Fast, 'sapiens-video-h3' (MiniMax H3: 2K com áudio nativo incluso sem toggle, 5 a 10s, t2v/i2v e frame final; não aceita imagem de referência), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), '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' (Gemini Omni: texto vira vídeo 10s 720p com áudio nativo; NÃO aceita mídia do user, ignora references/durationSec/resolution; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), 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. 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 deepshadow (depth-map) de um vídeo: entra na sua timeline de vídeos e no Acervo como driving reutilizável; 'shadows-list' lista os deepshadows prontos. Sub-action 'models' (sem custo, sem login): lista os modelos de vídeo ativos + preço-piso + config (durações/resoluções) + disponibilidade (Omni depende de env). 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.",
129
127
  schema: videoSchema,
130
128
  handler: video,
131
129
  },
@@ -227,7 +225,7 @@ O passo a passo travado de cada fluxo mora na tool sapiens_skill, servida por es
227
225
  - sapiens_skill action=list -> o índice (slug + quando usar cada uma).
228
226
  - sapiens_skill action=get name=<slug> -> a skill inteira.
229
227
  Slugs: ${skillMenuLine()}
230
- Puxe a skill ANTES de: gerar música ou efeito sonoro (musica), escolher modelo de vídeo (video), gerar imagem (imagem), criar personagem ou prompt de Midjourney (personagem), criar na identidade do usuário (studio), montar tirinha (tirinha), postar no Fórum ou no chat (forum-comunidade), redigir qualquer texto publicável (voz-da-casa), incorporar o Sintético (companhia), missão de Desafio (trilhas), montar currículo (curriculo). Perdido no começo: primeiros-passos.
228
+ Puxe a skill ANTES de: gerar música ou efeito sonoro (musica), escolher modelo de vídeo (video), gerar imagem (imagem), criar personagem ou prompt de Midjourney (personagem), criar na identidade do usuário (studio), montar tirinha (tirinha), postar no Fórum ou no chat (forum-comunidade), redigir qualquer texto publicável (voz-da-casa), incorporar o Sintético (companhia), missão de Desafio (trilhas), montar currículo (curriculo), guardar obra ou ferramenta no acervo (repertorio). Perdido no começo: primeiros-passos.
231
229
  Cliente que lê resources MCP acha o MESMO conteúdo em skill://sapiens/<slug>/SKILL.md.
232
230
 
233
231
  REGRA DE OURO:
@@ -277,7 +275,6 @@ const TOOL_TITLES = {
277
275
  sapiens_persona: "Persona (MBTI)",
278
276
  sapiens_helen: "Voz Helen (TTS)",
279
277
  sapiens_musicator: "Musicator",
280
- sapiens_shorts: "Sapiens Shorts",
281
278
  sapiens_video: "Sapiens Video",
282
279
  sapiens_stock_audio: "Banco de Áudio",
283
280
  sapiens_stock_video: "Banco de Vídeo",
@@ -306,7 +303,6 @@ const ADMIN_ONLY_TOOLS = new Set([
306
303
  "sapiens_pipeline",
307
304
  "sapiens_article",
308
305
  "sapiens_quote_pop",
309
- "sapiens_shorts",
310
306
  "sapiens_instagram",
311
307
  "sapiens_aula",
312
308
  "sapiens_share",
@@ -331,6 +327,23 @@ export function buildToolList(tier) {
331
327
  },
332
328
  }));
333
329
  }
330
+ /**
331
+ * Apelidos que agentes CHAMAM e que não são o nome da tool. Não é palpite:
332
+ * saiu do `mcpUsage`, onde numa noite de jul/2026 um cliente gastou dez
333
+ * chamadas procurando o Repertório, quatro delas no slug traduzido pro inglês.
334
+ * O slug da casa é português; aceitar o apelido é mais barato que o agente
335
+ * descobrir sozinho, e não abre nome novo (o catálogo continua o mesmo).
336
+ *
337
+ * Só entra apelido INEQUÍVOCO. Os outros nomes daquela noite (`sapiens_track`,
338
+ * `sapiens_collection`, `sapiens_library`) ficam de fora de propósito: soam a
339
+ * música e a galeria tanto quanto a repertório, e mapear no chute manda a peça
340
+ * pro lugar errado calado. Pra esses, o erro de tool desconhecida agora lista
341
+ * o catálogo, que é a resposta honesta.
342
+ */
343
+ const TOOL_ALIASES = {
344
+ sapiens_repertoire: "sapiens_repertorio",
345
+ repertorio: "sapiens_repertorio",
346
+ };
334
347
  /**
335
348
  * Dispatch de uma tool: valida args no Zod, roda o handler e embrulha o
336
349
  * resultado no shape MCP (content + structuredContent; erro vira isError com
@@ -339,10 +352,20 @@ export function buildToolList(tier) {
339
352
  * (via runWithSessionToken).
340
353
  */
341
354
  export async function callTool(name, rawArgs) {
342
- const tool = TOOLS[name];
355
+ const tool = TOOLS[(TOOL_ALIASES[name] ?? name)];
343
356
  if (!tool) {
357
+ // Beco sem saída vira mapa: o agente que erra o nome erra de novo se a
358
+ // resposta só diz "não existe". Listar o catálogo custa ~400 chars e só
359
+ // aparece no erro, nunca no handshake.
344
360
  return {
345
- content: [{ type: "text", text: `Tool desconhecida: ${name}` }],
361
+ content: [
362
+ {
363
+ type: "text",
364
+ text: `Tool desconhecida: ${name}. As tools da casa são: ` +
365
+ `${Object.keys(TOOLS).sort().join(", ")}. ` +
366
+ `Chame a certa em vez de tentar outro nome.`,
367
+ },
368
+ ],
346
369
  isError: true,
347
370
  };
348
371
  }
@@ -376,12 +399,35 @@ export async function callTool(name, rawArgs) {
376
399
  catch (e) {
377
400
  return {
378
401
  content: [
379
- { type: "text", text: `Erro: ${describeConvexError(e)}${retryHint(e)}` },
402
+ {
403
+ type: "text",
404
+ text: `Erro: ${describeConvexError(e)}${actionHint(e, tool.schema)}${retryHint(e)}`,
405
+ },
380
406
  ],
381
407
  isError: true,
382
408
  };
383
409
  }
384
410
  }
411
+ /**
412
+ * Chamada sem `action` (ou com uma que não existe) é o segundo erro mais comum
413
+ * do connector depois de arg faltando: 23 chamadas peladas no `mcpUsage`, 12
414
+ * delas em erro. O Zod diz "Required" e não diz o que serve, então o agente
415
+ * chuta de novo. Aqui a resposta já vem com as actions daquele tool.
416
+ *
417
+ * Lê o enum do próprio schema em vez de uma lista à parte, que envelheceria
418
+ * sozinha: action nova aparece aqui no mesmo commit em que nasce.
419
+ */
420
+ function actionHint(e, schema) {
421
+ if (!(e instanceof ZodError))
422
+ return "";
423
+ const touchesAction = e.issues.some((i) => i.path[0] === "action");
424
+ if (!touchesAction)
425
+ return "";
426
+ const options = schema?.shape?.action?.options;
427
+ if (!Array.isArray(options) || options.length === 0)
428
+ return "";
429
+ return ` -> As actions deste tool são: ${options.join(", ")}.`;
430
+ }
385
431
  /**
386
432
  * A regra do disjuntor viaja NO ERRO, não no handshake. Ela só passa a valer
387
433
  * depois que uma chamada falhou, então cobrar ~300 chars de TODA conversa pra
package/dist/skills.js CHANGED
@@ -131,6 +131,43 @@ Quem tem design system (\`sapiens_brand action=list\`) já vê a capa sair na pa
131
131
  ## Estrutura padrão de peça didática
132
132
 
133
133
  Condensa a informação num framework com nome, ancora numa analogia concreta, e só então desce pro detalhe. Estrutura arrumada, alma inquieta, nunca fórmula.`,
134
+ },
135
+ {
136
+ name: "repertorio",
137
+ title: "Repertório: guardar obra e ferramenta no acervo",
138
+ description: "Guardar filme, série, anime, jogo, livro, música, pessoa ou ferramenta de IA no Repertório. Puxe ANTES de qualquer sapiens_repertorio: a gravação é travada num fluxo de dois passos e tentar guardar direto falha sempre.",
139
+ body: `## A regra que faz este fluxo falhar
140
+
141
+ Você NÃO grava uma obra passando o título. O servidor não aceita metadado vindo de você (título, capa, ano) de propósito: é anti-fabricação. Você identifica a obra num provider, e o servidor re-resolve por id e grava o canônico.
142
+
143
+ São sempre DOIS passos. Pular o primeiro falha sempre, e é o erro número um deste fluxo.
144
+
145
+ ## Guardar uma obra
146
+
147
+ 1. **\`action=resolve\`** com \`mediaType\` e \`query\` (o título como a pessoa falou). Custo 0. Volta até 8 candidatos, cada um com \`source\` e \`externalId\`.
148
+ 2. Escolha o candidato certo. Em dúvida entre dois, PERGUNTE em vez de chutar: obra errada no acervo é a pessoa quem vai ter que apagar.
149
+ 3. **\`action=add_item\`** com o \`source\` e o \`externalId\` **exatos** daquele candidato, mais o que é pessoal: \`status\`, \`rating\` (0-10), \`tags\`, \`note\`, \`containsSpoilers\`, \`isPublic\`.
150
+
151
+ \`mediaType\`: movie, series, anime, game, book, music, tool, person.
152
+ \`status\`: backlog (quer ver), active (vendo agora), completed (viu), paused, dropped.
153
+
154
+ Nunca invente um \`externalId\` nem reaproveite um de outra busca: id que não resolve no provider é rejeitado. A dedup é por (você, source, externalId), então repetir o mesmo par não duplica.
155
+
156
+ ## Guardar uma ferramenta de IA
157
+
158
+ Outro par, mesmo espírito: **\`action=search_tools\`** com \`query\` (catálogo aberto, custo 0, sem login) e depois **\`action=add_tool\`** com o \`toolId\` do candidato. Estar no acervo já quer dizer "usei"; \`favorite: true\` é o eixo separado de "curto e indico".
159
+
160
+ ## Ler o acervo
161
+
162
+ \`action=list\` e \`action=search\` **exigem \`userId\`**. Descubra uma vez com \`sapiens_meta action=whoami\` e reuse na conversa inteira. \`action=get\` pede \`itemId\`, que vem do list/search.
163
+
164
+ Acervo de outra pessoa você lê, mas só o que ela deixou público.
165
+
166
+ ## Depois de guardar
167
+
168
+ \`action=update_item\` muda o que é seu (\`rating\`, \`status\`, \`tags\`, \`note\`; \`rating: null\` remove a nota). \`action=remove_item\` tira do acervo. Os dois pedem \`itemId\`, nunca o título.
169
+
170
+ \`action=popArticles\` lista os artigos publicados que usam uma obra como lente. É leitura pública, não precisa de login.`,
134
171
  },
135
172
  {
136
173
  name: "musica",
@@ -196,6 +233,21 @@ Duração é o que pesa aqui, não o modelo: um take de 30s em 720p passa de 40
196
233
  - \`sapiens-video-omni\`: Gemini Omni, texto vira clipe de 10s 720p com áudio nativo. NÃO aceita mídia da pessoa e ignora references/durationSec/resolution. 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.
197
234
  - \`sapiens-video-lite\` / \`-fast\` / \`-quality\`: Veo 3.1.
198
235
 
236
+ ## Receita de take (\`templateSlug\`), quando o formato já tem forma
237
+
238
+ Formato conhecido não precisa de prompt escrito do zero. Passe \`templateSlug\` e a receita da casa embrulha a cena com estilo, cenário, arco, áudio e look, e ainda escolhe o motor (aí \`model\` fica opcional). O \`prompt\` vira só a CENA e o \`brief\` preenche o resto.
239
+
240
+ | Slug | O take |
241
+ |---|---|
242
+ | \`ugc-vertical-v1\` | selfie que fala, o formato nativo de Reels, TikTok e Shorts |
243
+ | \`unboxing-vertical-v1\` | mãos e reveal, som real de papel e lacre |
244
+ | \`app-demo-vertical-v1\` | a tela do app legível na mão da pessoa |
245
+ | \`reflexao-vertical-v1\` | talking-head lento pra ideia ou ensaio |
246
+
247
+ O \`brief\` aceita \`subject\`, \`persona\`, \`hook\` (\`line\` e \`emotion\`), \`shots\` (cada um com \`sec\`, \`camera\`, \`action\`, \`emotion\`, \`voiceLine\`, \`propVisible\`), \`uvps\`, \`language\` e \`energy\`. Campo vazio SOME do prompt em vez de virar buraco, e o que você não passa cai no valor de reserva da receita.
248
+
249
+ \`action=templates\` (sem custo, sem login) lista o catálogo vivo: spec default, o que dá pra sobrepor e os \`briefFields\` que cada receita realmente usa. Override fora da whitelist é recusado com as opções na mensagem.
250
+
199
251
  ## Fluxo storyboard (o que dá o melhor resultado)
200
252
 
201
253
  Até 4 imagens de REFERÊNCIA via \`referenceImageIds\` / \`referenceImageUrls\` / \`referenceImagePaths\` guiam estilo, personagem e composição SEM virar o primeiro frame.
@@ -267,7 +319,7 @@ Refs valem pros modelos robustos (nano-banana-2, gpt-image-2-*, grok-2-image*).
267
319
  ## Cuidados
268
320
 
269
321
  - \`generate\` é SÍNCRONA e cobra ao concluir. Modelo pesado (Pro, gpt-image-2-high, Grok quality, Seedream 5.0 Pro, 2K/4K) cai na regra do timeout: cheque \`sapiens_gallery action=list\` antes de repetir, senão cobra duas vezes. O \`seedream-5-0-pro\` foi medido em ~140s, ACIMA do teto de 120s do cliente, então o Timeout nele é o esperado e a imagem está lá.
270
- - \`request_generation\` NÃO gera imagem: só cria a row pendente e debita, pra modelos \`sapiens-video-*\` antes de chamar vídeo ou shorts.
322
+ - \`request_generation\` NÃO gera imagem: só cria a row pendente e debita, pra modelos \`sapiens-video-*\` antes de renderizar. Caminho legado: pra vídeo novo, \`sapiens_video action=create\` faz tudo num call.
271
323
  - Uma imagem só vira pública (e ganha página indexável) com \`sapiens_gallery action=publish\`.
272
324
 
273
325
  ## Quando a pessoa quer "do jeito dela"
@@ -24,6 +24,7 @@ export const articleSchema = z.object({
24
24
  "unpublish",
25
25
  "delete",
26
26
  "ensure_visuals",
27
+ "visuals_status",
27
28
  ]),
28
29
  slug: z
29
30
  .string()
@@ -32,7 +33,7 @@ export const articleSchema = z.object({
32
33
  articleId: z
33
34
  .string()
34
35
  .optional()
35
- .describe("Obrigatório pra update/publish/unpublish/delete/ensure_visuals. Pode descobrir via action=get (o retorno tem _id)."),
36
+ .describe("Obrigatório pra update/publish/unpublish/delete/ensure_visuals/visuals_status. Pode descobrir via action=get (o retorno tem _id)."),
36
37
  // Campos pra update (todos opcionais)
37
38
  title: z.string().optional(),
38
39
  excerpt: z.string().optional(),
@@ -178,6 +179,12 @@ export async function article(args) {
178
179
  // Gera banner / inline / conceptMap que estiverem faltando.
179
180
  // Idempotente: pula o que já existe. Custo: ~1700 sinapses pra
180
181
  // artigo novo (450+450+800), só do que regerar pros que existem.
182
+ //
183
+ // ASSÍNCRONO (ago/2026): volta na hora com o plano da leva, porque as
184
+ // quatro gerações em sequência estouravam o teto de 120s do cliente em
185
+ // 10 de cada 22 chamadas — e o timeout parecia morte quando a operação
186
+ // seguia rodando e já tinha cobrado. Quando não há nada a gerar, segue
187
+ // respondendo na hora com o retrato de sempre (é a sonda barata).
181
188
  const articleId = need(args.articleId, "articleId");
182
189
  return await convexAction("articleVisuals:ensureArticleVisualsBySession", {
183
190
  sessionToken,
@@ -186,6 +193,17 @@ export async function article(args) {
186
193
  forceInline: args.forceInline,
187
194
  forceConceptMap: args.forceConceptMap,
188
195
  inlineCount: args.inlineCount,
196
+ async: true,
197
+ });
198
+ }
199
+ case "visuals_status": {
200
+ // Leitura pura da leva + do estado real dos visuais. Custo 0, pode
201
+ // chamar à vontade: é o jeito de fechar o ciclo depois de um
202
+ // ensure_visuals, e o único que não corre risco de cobrar de novo.
203
+ const articleId = need(args.articleId, "articleId");
204
+ return await convexAction("articleVisuals:articleVisualsJobBySession", {
205
+ sessionToken,
206
+ articleId,
189
207
  });
190
208
  }
191
209
  }
@@ -12,12 +12,13 @@ import { convexAction, convexQuery, getSessionToken } from "../convexClient.js";
12
12
  * importa aqui: banco novo no picker web nasce com bucket irmão neste arquivo,
13
13
  * senão o agente enxerga menos que o browser e a paridade quebra em silêncio.
14
14
  *
15
- * Buckets (as abas do popup, completas):
15
+ * Buckets (as abas do popup, completas). `term` (busca escrita) vale em TODOS,
16
+ * espelhando o campo único de busca do modal web:
16
17
  * - history imagens recentes do user (privadas + públicas)
17
18
  * - favorites imagens que o user curtiu (só as suas)
18
19
  * - videos vídeos do user (Meus Vídeos)
19
- * - stock_video Banco de Vídeo da casa (clipes/B-roll); aceita `term`/`orientation`/`loopOnly`
20
- * - acervo stock + comunidade de IMAGEM (público); aceita `term` (busca) e `source`
20
+ * - stock_video Banco de Vídeo da casa (clipes/B-roll); aceita `orientation`/`loopOnly`
21
+ * - acervo stock + comunidade de IMAGEM (público); aceita `source`
21
22
  * - characters personagens; `mode` = 'mine' (default) ou 'public'
22
23
  * - deepshadow guias de movimento; `shadowSource` = 'house' (banco curado, default) ou 'mine'
23
24
  *
@@ -62,7 +63,10 @@ export const referenceSchema = z.object({
62
63
  term: z
63
64
  .string()
64
65
  .optional()
65
- .describe("Buckets 'acervo' e 'stock_video': busca por texto (prompt/título/tag/mood). Ex: 'chuva'."),
66
+ .describe("Busca escrita, vale em QUALQUER bucket. Casa por pedaço de palavra, sem acento: 'board' acha 'storyboard'. " +
67
+ "Olha prompt, título, folha, personagem e modelo nas suas imagens; prompt/título/modelo nos vídeos; " +
68
+ "nome nos personagens; prompt/tag/mood no acervo e no banco de vídeo. " +
69
+ "Pra achar UMA peça específica que você já sabe qual é, use action=resolve com o handle (img_…), é mais direto."),
66
70
  source: z
67
71
  .enum(["all", "stock", "community"])
68
72
  .optional()
@@ -144,6 +148,14 @@ export async function reference(args) {
144
148
  note =
145
149
  "Pra usar como referência: em sapiens_image passe o imageId em sourceImageIds (ou a url em referenceImageUrls); em sapiens_video passe startImageId/endImageId (ou startImageUrl/endImageUrl).";
146
150
  }
151
+ // Vazio COM termo não quer dizer banco vazio: sem essa linha o agente
152
+ // conclui que a pessoa não tem nada e vai gerar de novo o que já existe.
153
+ if (args.term && !res?.items?.length) {
154
+ note =
155
+ `Nenhuma peça deste banco casou com "${args.term}". O banco pode ter outras: tente um termo mais curto ` +
156
+ `(a busca casa por pedaço de palavra), outro bucket, ou browse sem \`term\`. Se o usuário te deu o ID da ` +
157
+ `peça (img_…, vid_…), use action=resolve.`;
158
+ }
147
159
  return { ...res, note };
148
160
  }
149
161
  }
@@ -8,6 +8,12 @@ import { httpUrl } from "../schema.js";
8
8
  * Sub-actions:
9
9
  * - create: escolhe modelo + config e gera num call só (cria a row + renderiza).
10
10
  * Habilita Seedance/Kling/WAN/Motion (WaveSpeed) e os Veo, sem o site.
11
+ * Com `templateSlug`, a RECEITA da casa embrulha a cena (estilo, arco,
12
+ * áudio, look) e escolhe o motor: `prompt` vira só a cena e `brief`
13
+ * preenche os campos do formato.
14
+ * - templates: catálogo das receitas de take (slug, spec default, whitelist de
15
+ * override e os campos de brief que cada uma usa). Sem custo, sem login.
16
+ * Comeu o antigo sapiens_shorts, que era admin-only e só falava Veo.
11
17
  * - generate: renderiza um imageId de vídeo já criado no site (legado).
12
18
  * - demos: lista os SEUS demo films (kind=demo do Estúdio de Vídeo) + estado
13
19
  * de vitrine. Sem custo. Base pra curar o mini-cinema da vitrine.
@@ -125,6 +131,7 @@ export const videoSchema = z.object({
125
131
  "generate",
126
132
  "status",
127
133
  "models",
134
+ "templates",
128
135
  "demos",
129
136
  "showcase",
130
137
  "shadows",
@@ -208,11 +215,51 @@ export const videoSchema = z.object({
208
215
  .number()
209
216
  .optional()
210
217
  .describe("action=showcase: ordem na trilha do mini-cinema (asc, 0..999; menor aparece primeiro)."),
218
+ // --- Receita de take (templateSlug + brief) ---
219
+ templateSlug: z
220
+ .string()
221
+ .optional()
222
+ .describe("action=create: slug de um TEMPLATE de vídeo (receita travada da casa, igual ao templateSlug da imagem). " +
223
+ "Com ele, `prompt` vira só a CENA e o template embrulha com estilo, enquadramento, arco, áudio e look, " +
224
+ "além de escolher o motor (por isso `model` fica opcional). O `brief` preenche os campos do formato. " +
225
+ "Descubra os slugs em action=templates (sem custo, sem login): hoje 'ugc-vertical-v1', 'unboxing-vertical-v1', " +
226
+ "'app-demo-vertical-v1' e 'reflexao-vertical-v1'. Override de model/aspectRatio/durationSec/resolution vale, " +
227
+ "mas só dentro do que a receita aceita (o erro lista as opções)."),
228
+ brief: z
229
+ .object({
230
+ subject: z.string().optional().describe("O que o take é: o produto, a ideia, o tópico."),
231
+ subjectType: z.string().optional().describe("app | physical | saas | ideia."),
232
+ persona: z.string().optional().describe("Quem aparece (ou as mãos que aparecem)."),
233
+ uvps: z.array(z.string()).optional().describe("2 a 4 pontos curtos a reforçar."),
234
+ hook: z
235
+ .object({
236
+ line: z.string().optional().describe("A frase de abertura, 5-10 palavras."),
237
+ emotion: z.string().optional().describe("Emoção alvo (curiosidade, frustração, espanto)."),
238
+ })
239
+ .optional(),
240
+ shots: z
241
+ .array(z.object({
242
+ sec: z.number().optional().describe("Em que segundo o momento acontece."),
243
+ role: z.string().optional().describe("hook | problem | solution | cta."),
244
+ camera: z.string().optional().describe("close-up | medium | over-shoulder | product-pov."),
245
+ action: z.string().optional().describe("A ação visual em uma frase."),
246
+ emotion: z.string().optional(),
247
+ voiceLine: z.string().optional().describe("A fala da cena, na língua do brief."),
248
+ propVisible: z.string().optional().describe("Objeto/tela que precisa aparecer legível."),
249
+ }))
250
+ .optional()
251
+ .describe("Os momentos do take, na ordem. Sem eles, o template usa o arco de reserva dele."),
252
+ language: z.string().optional().describe("'pt-BR' (default) ou 'en'."),
253
+ energy: z.string().optional().describe("calm | high."),
254
+ })
255
+ .optional()
256
+ .describe("action=create com templateSlug: o formulário do template. Cada receita usa os campos que precisa " +
257
+ "(veja `briefFields` em action=templates) e ignora o resto; campo vazio some do prompt em vez de virar buraco."),
211
258
  // --- action=create ---
212
259
  model: z
213
260
  .enum(VIDEO_MODELS)
214
261
  .optional()
215
- .describe("action=create: modelo de vídeo. O catálogo POR MODELO (capacidades, referências, tetos, preço) mora na description desta tool e VIVO em action=models; a skill 'video' (sapiens_skill) guia a escolha. " +
262
+ .describe("action=create: modelo de vídeo (opcional quando você passa templateSlug: a receita traz o motor). O catálogo POR MODELO (capacidades, referências, tetos, preço) mora na description desta tool e VIVO em action=models; a skill 'video' (sapiens_skill) guia a escolha. " +
216
263
  "Atalho de famílias: seedance/seedance-2-*/seedance-25 = cena+áudio+referências (o 25 estica até 30s e o preço acompanha), seedance-15 = degrau 1.5, kling = anima imagem, wan = imagem que fala, " +
217
264
  "hailuo/hailuo-pro = movimento puro sem áudio, h3 = 2K com áudio, kling-motion/shot-mimic = transferência de movimento/plano, lite/fast/quality = Veo 3.1, omni = Gemini com áudio nativo."),
218
265
  durationSec: z
@@ -427,6 +474,18 @@ export async function video(args) {
427
474
  note: "basePriceSinapses é o PISO (config mais barata, já com override admin); o preço real escala por duração x resolução [x áudio]. available=false = 'em breve' (depende de env, ex: Omni).",
428
475
  };
429
476
  }
477
+ // templates: catálogo VIVO das receitas de take (estilo + spec + os campos de
478
+ // brief que cada uma usa). Público, sem custo e sem login, igual ao models:
479
+ // escolher a receita certa é de graça, errar o take é que custa.
480
+ if (args.action === "templates") {
481
+ const rows = await convexQuery("videoTemplates:list", {});
482
+ return {
483
+ count: rows?.length ?? 0,
484
+ templates: rows ?? [],
485
+ note: "Passe o slug em action=create como templateSlug: o `prompt` vira só a CENA e o `brief` preenche o resto. " +
486
+ "briefFields diz o que vale a pena preencher em cada receita; allowedModels/allowedDurationsSec são o teto do override.",
487
+ };
488
+ }
430
489
  const sessionToken = getSessionToken();
431
490
  if (args.action === "demos") {
432
491
  return await convexQuery("videoSpecs:mcpListMyDemoFilms", { sessionToken });
@@ -565,8 +624,8 @@ export async function video(args) {
565
624
  });
566
625
  }
567
626
  if (args.action === "create") {
568
- if (!args.model) {
569
- throw new Error("action=create exige model (ex: sapiens-video-seedance, sapiens-video-kling, sapiens-video-wan, sapiens-video-kling-motion).");
627
+ if (!args.model && !args.templateSlug) {
628
+ throw new Error("action=create exige model (ex: sapiens-video-seedance, sapiens-video-kling, sapiens-video-wan, sapiens-video-kling-motion) ou templateSlug (a receita traz o motor; veja action=templates).");
570
629
  }
571
630
  // Frame inicial/final por arquivo local (paridade com o upload do site):
572
631
  // lê do disco e injeta em references role start/end. O backend
@@ -578,6 +637,8 @@ export async function video(args) {
578
637
  return await convexAction("mcpExtrasActions:mcpVideoCreateAndRender", {
579
638
  sessionToken,
580
639
  model: args.model,
640
+ templateSlug: args.templateSlug,
641
+ brief: args.brief,
581
642
  prompt: args.prompt,
582
643
  aspectRatio: args.aspectRatio,
583
644
  durationSec: args.durationSec,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.50.0",
3
+ "version": "1.51.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.",
6
6
  "type": "module",