sapiens-mcp 1.33.0 → 1.35.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
@@ -38,7 +38,7 @@ import { describeConvexError } from "./convexClient.js";
38
38
  */
39
39
  export const TOOLS = {
40
40
  sapiens_pipeline: {
41
- description: "CRUD do content pipeline Sapiens (sources/productions/publishables). Sub-actions: list_sources, list_articles (use includeDrafts pra incluir drafts; onlyAvailable pra esconder os já virados em source), get_source, get_production, list_versions, add_article_as_source, create_draft_article_and_source (seed), create_production (sourceId+format → productionId draft), update_production (substitui payload, opcionalmente muda status), finalize_production (cria publishable v1, v2... com snapshot), remove_production, remove_source, set_source_done, update_source_notes, restore_version (volta payload duma versão antiga), set_publishable_title (renomeia um publishable), backfill_via (rotula em lote o campo 'via' das productions antigas; dryRun=true só lista), propose_mega_grafico_plan (granular: só gera plano via Gemini, devolve fullPrompt+spec), run_mega_grafico_full (ONE-SHOT, recomendado: cria production+propõe plano+gera imagem+aplica selo Sapiens+finaliza publishable numa chamada só), generate_carousel (gera um carrossel editorial standalone a partir de brief OU articleId — 7-9 slides na voz Sapiens + imagens do banco; devolve id + url do editor pro humano abrir, ajustar e exportar; admin-only, cobra Sinapses, reembolsa se falhar). Pra mega_grafico, SEMPRE prefira run_mega_grafico_full em vez de sequenciar manualmente (menos drift). Idempotente só na production (passa productionId pra reusar a MESMA row), mas cada run RE-GERA a imagem e cobra de novo (~900 Sinapses): não é grátis re-rodar. OBRIGATÓRIO perguntar ao user antes se withHelen=true (cartoon Helen interage com tema, ~15-25% do poster) ou false (poster 100% diagramático). Custo ~900-1000 sinapses por geração. run_mega_grafico_full e generate_carousel são SÍNCRONAS e pesadas: vale a REGRA DO TIMEOUT (podem cobrar mesmo voltando 'Timeout'; cheque /dashboard/admin/content antes de repetir). Use skipFinalize=true se quiser deixar production em 'ready' pro admin revisar antes de publishable. Payload livre por formato — chame sapiens_meta action=formats pra ver schemas sugeridos.",
41
+ description: "CRUD do content pipeline Sapiens (sources/productions/publishables). Sub-actions: list_sources, list_articles (use includeDrafts pra incluir drafts; onlyAvailable pra esconder os já virados em source), get_source, get_production, list_versions, add_article_as_source, create_draft_article_and_source (seed), create_production (sourceId+format → productionId draft), update_production (substitui payload, opcionalmente muda status), finalize_production (cria publishable v1, v2... com snapshot), remove_production, remove_source, set_source_done, update_source_notes, restore_version (volta payload duma versão antiga), set_publishable_title (renomeia um publishable), backfill_via (rotula em lote o campo 'via' das productions antigas; dryRun=true só lista), propose_mega_grafico_plan (granular: só gera plano via Gemini, devolve fullPrompt+spec), run_mega_grafico_full (ONE-SHOT, recomendado: cria production+propõe plano+gera imagem+aplica selo Sapiens+finaliza publishable numa chamada só), generate_carousel (gera um carrossel editorial standalone a partir de brief OU articleId — 7-9 slides na voz Sapiens + imagens do banco; devolve id + url do editor pro humano abrir, ajustar e exportar; admin-only, cobra Sinapses, reembolsa se falhar). CARROSSEL FINO (edição sem tela, admin-only): list_carousels (seus carrosséis standalone), get_carousel (payload completo + catálogo de templates com slots/limites + paletas — tudo pra VOCÊ escrever os slides), update_carousel (payload inteiro de volta, sanitizado no servidor; preserve os campos image dos slides que não mexeu), carousel_auto_images (IA escolhe imagens do banco pros slides de foto, 60 Sinapses; makeVisual=true converte slides de texto pra layouts de foto antes — ritmo visual; generateMissing=true gera imagem nova pros que o banco não cobrir, ~450 Sinapses cada, até 4, avise o custo antes), carousel_generate_image (imagem nova pra UM slide, ~450 Sinapses, customPrompt opcional). Fluxo confortável: generate_carousel → get_carousel → update_carousel (afia os textos) → carousel_auto_images → humano abre a url pra exportar. Pra mega_grafico, SEMPRE prefira run_mega_grafico_full em vez de sequenciar manualmente (menos drift). Idempotente só na production (passa productionId pra reusar a MESMA row), mas cada run RE-GERA a imagem e cobra de novo (~900 Sinapses): não é grátis re-rodar. OBRIGATÓRIO perguntar ao user antes se withHelen=true (cartoon Helen interage com tema, ~15-25% do poster) ou false (poster 100% diagramático). Custo ~900-1000 sinapses por geração. run_mega_grafico_full, generate_carousel, carousel_auto_images (com generateMissing) e carousel_generate_image são SÍNCRONAS e pesadas: vale a REGRA DO TIMEOUT (podem cobrar mesmo voltando 'Timeout'; cheque get_carousel/dashboard antes de repetir). Use skipFinalize=true se quiser deixar production em 'ready' pro admin revisar antes de publishable. Payload livre por formato — chame sapiens_meta action=formats pra ver schemas sugeridos.",
42
42
  schema: pipelineSchema,
43
43
  handler: pipeline,
44
44
  },
@@ -113,7 +113,7 @@ export const TOOLS = {
113
113
  handler: shorts,
114
114
  },
115
115
  sapiens_video: {
116
- 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), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), '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. 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. 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 a sombra/depth-map de um vídeo pro Acervo como driving reutilizável; 'shadows-list' lista as sombras prontas. 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).",
116
+ 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), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), '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. 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): a mesa 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 a sombra/depth-map de um vídeo pro Acervo como driving reutilizável; 'shadows-list' lista as sombras prontas. 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).",
117
117
  schema: videoSchema,
118
118
  handler: video,
119
119
  },
@@ -212,7 +212,7 @@ Efeito sonoro (sapiens_stock_audio): primeiro procure pronto (action=list kind=s
212
212
 
213
213
  Sonorizar clipe (sapiens_video action=sonorize): dá som a um vídeo SEU já gerado (status completed). Passe imageId + 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), o original fica intacto. Acompanhe com action=status no imageId NOVO que o sonorize devolve. Não re-sonorize uma variante: sonorize sempre o original.
214
214
 
215
- Vídeo (sapiens_video) action=create EXIGE 'model': sapiens-video-seedance | kling | wan | kling-motion | lite | fast | quality | omni. Sem model, falha de cara. O 'omni' (Gemini Omni) gera clipe de 10s 720p com áudio nativo a partir de TEXTO (não aceita imagem/vídeo do user, ignora duração/resolução) e EDITA os próprios vídeos: passe editOfImageId com o imageId de um vídeo Omni já gerado e o prompt vira instrução de edição sobre a MESMA cena (troca item/personagem, preserva câmera e ambiente; cada edição debita como geração nova). É o caminho pra variações com continuidade: gera a base uma vez, edita N vezes. Vídeo é o mais caro: confirme com o usuário antes. action=shadows (ADMIN, 200 Sinapses/segundo, refund na falha): passa videoUrl (URL pública) + title (+ durationSec se souber, pra cobrar proporcional; sem ela, flat ~2000) e o servidor extrai a SOMBRA (mapa de profundidade) do vídeo e guarda no Acervo (Corpo) como deepshadow reutilizável (driving pro Shot Mimic/Kling Motion). FILMES DA CASA (Estúdio de Vídeo, tela /experimentos/films): são OUTRA coisa, três kinds na tabela videoSpecs: demo (UI clonada + câmera), aula-tour (telas reais + narração) e essay (fita-ensaio abstrata: partícula/tinta vira símbolo, com a logo explodindo no fim; narração da casa OPCIONAL, sai em 16:9 ou 9:16 vertical, e o filme final é limpo, sem rótulos de produção). Eles NÃO são gerados por este MCP: o user monta o spec na tela e um agente local (Claude Code, skill /film) produz o mp4 no repo da casa. Se o user pedir 'fita-ensaio' ou 'demo film' aqui, aponte pra tela e pro agente local.
215
+ Vídeo (sapiens_video) action=create EXIGE 'model': sapiens-video-seedance | kling | wan | kling-motion | lite | fast | quality | omni. Sem model, falha de cara. O 'omni' (Gemini Omni) gera clipe de 10s 720p com áudio nativo a partir de TEXTO (não aceita imagem/vídeo do user, ignora duração/resolução) e EDITA os próprios vídeos: passe editOfImageId com o imageId de um vídeo Omni já gerado e o prompt vira instrução de edição sobre a MESMA cena (troca item/personagem, preserva câmera e ambiente; cada edição debita como geração nova). É o caminho pra variações com continuidade: gera a base uma vez, edita N vezes. Vídeo é o mais caro: confirme com o usuário antes. action=shadows (ADMIN, 200 Sinapses/segundo, refund na falha): passa videoUrl (URL pública) + title (+ durationSec se souber, pra cobrar proporcional; sem ela, flat ~2000) e o servidor extrai a SOMBRA (mapa de profundidade) do vídeo e guarda no Acervo (Corpo) como deepshadow reutilizável (driving pro Shot Mimic/Kling Motion). VÍDEOS PROGRAMÁTICOS (Estúdio de Vídeo, tela /experimentos/films): são OUTRA coisa, a mesa de filmes-de-código da casa, CINCO kinds na tabela videoSpecs: demo (UI clonada + câmera), aula-tour (telas reais + narração), essay (fita-ensaio abstrata: partícula/tinta vira símbolo; narração opcional, 16:9 ou 9:16), tipografia-musical (clipe tipográfico, a música dirige o corte) e dataviz (barras/números no tempo). O RENDER não sai deste MCP: um agente local (Claude Code, skill /film) produz o mp4 no repo da casa. Mas o CICLO na plataforma fecha por aqui (ADMIN, sem custo): film-list/film-get puxam os specs, film-upsert registra/atualiza, film-status cola a URL do render e marca pronto, film-publish põe no Acervo. User comum pedindo 'fita-ensaio'/'demo film': aponte pra tela e pro agente local.
216
216
 
217
217
  Imagem (sapiens_image) action=generate: prompt + model + aspectRatio. Referência via referenceImageUrls (galeria/Acervo/personagens) e/ou sourceImageIds. templateSlug aplica um super-prompt travado da casa. PROMPT na regra da casa: full-bleed, sujeito oversized (70%+ do frame), sem "tarot card"/"intimate scale"/moldura/margem. action=models (sem custo, sem login) lista os modelos ativos com o preço atual: consulte quando não tiver certeza do modelo ou do custo.
218
218
 
@@ -43,6 +43,11 @@ export const pipelineSchema = z.object({
43
43
  "propose_mega_grafico_plan",
44
44
  "run_mega_grafico_full",
45
45
  "generate_carousel",
46
+ "list_carousels",
47
+ "get_carousel",
48
+ "update_carousel",
49
+ "carousel_auto_images",
50
+ "carousel_generate_image",
46
51
  ]),
47
52
  // propose_mega_grafico_plan / run_mega_grafico_full
48
53
  withHelen: z
@@ -83,6 +88,31 @@ export const pipelineSchema = z.object({
83
88
  .boolean()
84
89
  .optional()
85
90
  .describe("generate_carousel: se TRUE, a voz do Sintético do dono dirige o copy (amplificar). Default false = estilo do dono."),
91
+ // carrossel standalone (get/update/imagens)
92
+ carouselId: z
93
+ .string()
94
+ .optional()
95
+ .describe("carousels_standalone:_id — get_carousel/update_carousel/carousel_auto_images/carousel_generate_image. Vem de generate_carousel ou list_carousels."),
96
+ slideId: z
97
+ .string()
98
+ .optional()
99
+ .describe("carousel_generate_image: id do slide (ex: 'slide-3'), do payload do get_carousel."),
100
+ force: z
101
+ .boolean()
102
+ .optional()
103
+ .describe("carousel_auto_images: TRUE substitui as imagens já aplicadas por escolhas novas. Default false = preenche só o que falta."),
104
+ makeVisual: z
105
+ .boolean()
106
+ .optional()
107
+ .describe("carousel_auto_images: TRUE converte slides de texto pra layouts de foto antes de escolher (ritmo visual pro carrossel texto-pesado). O texto fica, muda a moldura."),
108
+ generateMissing: z
109
+ .boolean()
110
+ .optional()
111
+ .describe("carousel_auto_images: TRUE gera imagem NOVA (nanoBanana, ~450 Sinapses cada, até 4) pros slides que o banco não cobriu. O fallback stock→gerador. Avise o usuário do custo antes."),
112
+ customPrompt: z
113
+ .string()
114
+ .optional()
115
+ .describe("carousel_generate_image: direção extra da cena (opcional; sem ela a IA usa o texto do slide)."),
86
116
  // list_articles
87
117
  includeDrafts: z.boolean().optional(),
88
118
  includeArchived: z.boolean().optional(),
@@ -353,5 +383,46 @@ export async function pipeline(args) {
353
383
  useSintetico: args.useSintetico,
354
384
  });
355
385
  }
386
+ case "list_carousels":
387
+ // Carrosséis standalone do dono (id + título + url do editor).
388
+ return await convexQuery("carouselMcp:mcpListCarousels", { sessionToken });
389
+ case "get_carousel":
390
+ // Devolve payload completo + catálogo de templates (id/slots/limites) +
391
+ // paletas válidas: tudo que você precisa pra ESCREVER os slides direto.
392
+ return await convexQuery("carouselMcp:mcpGetCarousel", {
393
+ sessionToken,
394
+ carouselId: need(args.carouselId, "carouselId"),
395
+ });
396
+ case "update_carousel":
397
+ // Payload INTEIRO (mesmo shape do get_carousel). Sanitizado no servidor.
398
+ // Preserve os campos image dos slides que você não mexeu — mandar slide
399
+ // sem image derruba a foto dele.
400
+ return await convexMutation("carouselMcp:mcpUpdateCarousel", {
401
+ sessionToken,
402
+ carouselId: need(args.carouselId, "carouselId"),
403
+ payload: coercePayloadArg(need(args.payload, "payload")),
404
+ ...(args.title ? { title: args.title } : {}),
405
+ });
406
+ case "carousel_auto_images":
407
+ // IA escolhe imagens do banco pros slides de foto sem imagem (60 Sinapses).
408
+ // makeVisual dá ritmo a carrossel texto-pesado; generateMissing gera as que
409
+ // o banco não cobriu (custo por imagem). SÍNCRONA e pesada com
410
+ // generateMissing: vale a REGRA DO TIMEOUT.
411
+ return await convexAction("carouselAutofill:mcpCarouselAutoImages", {
412
+ sessionToken,
413
+ carouselId: need(args.carouselId, "carouselId"),
414
+ force: args.force,
415
+ makeVisual: args.makeVisual,
416
+ generateMissing: args.generateMissing,
417
+ });
418
+ case "carousel_generate_image":
419
+ // Gera imagem NOVA pra UM slide (nanoBanana, ~450 Sinapses) e salva no
420
+ // carrossel. SÍNCRONA (~20-60s): vale a REGRA DO TIMEOUT.
421
+ return await convexAction("carouselAutofill:mcpCarouselGenerateImage", {
422
+ sessionToken,
423
+ carouselId: need(args.carouselId, "carouselId"),
424
+ slideId: need(args.slideId, "slideId"),
425
+ ...(args.customPrompt ? { customPrompt: args.customPrompt } : {}),
426
+ });
356
427
  }
357
428
  }
@@ -25,6 +25,17 @@ import { httpUrl } from "../schema.js";
25
25
  * som da cena -> VARIANTE nova com trilha sincronizada (o original
26
26
  * fica intacto). Cobra por segundo do clipe; poll via action=status.
27
27
  *
28
+ * Vídeos Programáticos (Estúdio de Vídeo, /experimentos/films) — ADMIN, sem custo.
29
+ * A mesa de specs da casa (tabela videoSpecs, 5 kinds: demo | aula-tour | essay |
30
+ * tipografia-musical | dataviz). A PRODUÇÃO do mp4 segue no agente local (skill
31
+ * /film, repo films/); estas actions fecham o CICLO na plataforma sem browser:
32
+ * - film-list: lista os specs (todos os kinds; filtros filmKind/filmStatus).
33
+ * - film-get: um spec inteiro por slug (payload pronto pra produção).
34
+ * - film-upsert: cria/atualiza spec por slug (idempotente; validação server).
35
+ * - film-status: status/URL do render/duração por slug (fecho: pronto+URL).
36
+ * - film-publish: publica/despublica no Acervo (aba Fitas) + portfólio.
37
+ * - film-delete: apaga um spec (limpar rascunho/duplicata).
38
+ *
28
39
  * Modelos (action=create):
29
40
  * - sapiens-video-seedance Seedance 2.0 — cena com áudio nativo, 4-15s, 480/720/1080p (t2v/i2v)
30
41
  * - sapiens-video-kling Kling 3.0 Pro — dá vida a uma imagem, 3-15s, sound opcional (i2v/t2v)
@@ -56,12 +67,56 @@ const VIDEO_MODELS = [
56
67
  "sapiens-video-quality",
57
68
  "sapiens-video-omni",
58
69
  ];
70
+ const FILM_KINDS = ["demo", "aula-tour", "essay", "tipografia-musical", "dataviz"];
71
+ const FILM_STATUSES = ["rascunho", "na_fila", "renderizando", "pronto"];
59
72
  export const videoSchema = z.object({
60
- action: z.enum(["create", "generate", "status", "models", "demos", "showcase", "shadows", "shadows-list", "sonorize"]),
73
+ action: z.enum([
74
+ "create",
75
+ "generate",
76
+ "status",
77
+ "models",
78
+ "demos",
79
+ "showcase",
80
+ "shadows",
81
+ "shadows-list",
82
+ "sonorize",
83
+ "film-list",
84
+ "film-get",
85
+ "film-upsert",
86
+ "film-status",
87
+ "film-publish",
88
+ "film-delete",
89
+ ]),
90
+ // --- Vídeos Programáticos (film-*): mesa de specs da casa, ADMIN, sem custo ---
91
+ filmKind: z
92
+ .enum(FILM_KINDS)
93
+ .optional()
94
+ .describe("film-list: filtra por kind (demo | aula-tour | essay | tipografia-musical | dataviz)."),
95
+ filmStatus: z
96
+ .enum(FILM_STATUSES)
97
+ .optional()
98
+ .describe("film-status: novo status de produção (rascunho | na_fila | renderizando | pronto). " +
99
+ "film-list: filtra por status. O fecho do render é film-status com filmStatus='pronto' + videoUrl + durationSec."),
100
+ spec: z
101
+ .record(z.any())
102
+ .optional()
103
+ .describe("film-upsert: o spec inteiro como objeto JSON (validação fica no servidor, fonte única). " +
104
+ "Shape: { kind, musicMode ('default'|'file'|'track'), musicRef?, aulaSlug? (só aula-tour), " +
105
+ "e o payload do kind: demo | aulaTour | essay | tipoMusical | dataviz }. " +
106
+ "Mesmo shape do 'Copiar spec' da tela /experimentos/films; descubra um exemplo real com film-get."),
107
+ published: z
108
+ .boolean()
109
+ .optional()
110
+ .describe("film-publish: true publica no Acervo (aba Fitas) + portfólio (exige pronto + videoUrl), false despublica."),
111
+ durationSecMeasured: z
112
+ .number()
113
+ .optional()
114
+ .describe("film-status: duração MEDIDA do render em segundos (ffprobe), vira a duração do card."),
61
115
  // --- action=shadows (deepshadows: extrai o mapa de profundidade de um vídeo) ---
62
116
  videoUrl: httpUrl()
63
117
  .optional()
64
- .describe("action=shadows: URL pública (http/https) do vídeo-fonte. O servidor extrai a SOMBRA (depth) e guarda no Acervo (Corpo). ADMIN, 200 Sinapses/segundo (refund na falha)."),
118
+ .describe("action=shadows: URL pública (http/https) do vídeo-fonte. O servidor extrai a SOMBRA (depth) e guarda no Acervo (Corpo). ADMIN, 200 Sinapses/segundo (refund na falha). " +
119
+ "film-status: a URL https do render no CDN (Bunny), o que acende o player do card."),
65
120
  title: z
66
121
  .string()
67
122
  .optional()
@@ -74,7 +129,9 @@ export const videoSchema = z.object({
74
129
  slug: z
75
130
  .string()
76
131
  .optional()
77
- .describe("action=showcase: slug do demo film a curar (descubra via action=demos)."),
132
+ .describe("action=showcase: slug do demo film a curar (descubra via action=demos). " +
133
+ "film-get/film-status/film-publish/film-delete: slug do spec (descubra via film-list). " +
134
+ "film-upsert: slug fixo do filme no repo films/ (idempotente: existe = atualiza, não existe = cria com esse slug); omita pra criar com slug gerado."),
78
135
  showcase: z
79
136
  .boolean()
80
137
  .optional()
@@ -196,6 +253,69 @@ export async function video(args) {
196
253
  if (args.action === "demos") {
197
254
  return await convexQuery("videoSpecs:mcpListMyDemoFilms", { sessionToken });
198
255
  }
256
+ // --- Vídeos Programáticos (mesa de specs, ADMIN, sem custo) ---
257
+ if (args.action === "film-list") {
258
+ return await convexQuery("videoSpecs:mcpListMyVideoSpecs", {
259
+ sessionToken,
260
+ kind: args.filmKind,
261
+ status: args.filmStatus,
262
+ });
263
+ }
264
+ if (args.action === "film-get") {
265
+ if (!args.slug)
266
+ throw new Error("film-get exige slug (descubra via film-list).");
267
+ return await convexQuery("videoSpecs:mcpGetVideoSpec", { sessionToken, slug: args.slug });
268
+ }
269
+ if (args.action === "film-upsert") {
270
+ if (!args.spec || typeof args.spec !== "object") {
271
+ throw new Error("film-upsert exige spec (objeto JSON com kind + musicMode + payload do kind: demo | aulaTour | essay | tipoMusical | dataviz).");
272
+ }
273
+ const { kind, musicMode, musicRef, aulaSlug, demo, aulaTour, essay, tipoMusical, dataviz } = args.spec;
274
+ return await convexMutation("videoSpecs:mcpUpsertVideoSpec", {
275
+ sessionToken,
276
+ slug: args.slug,
277
+ kind,
278
+ musicMode: musicMode ?? "default",
279
+ musicRef,
280
+ aulaSlug,
281
+ demo,
282
+ aulaTour,
283
+ essay,
284
+ tipoMusical,
285
+ dataviz,
286
+ });
287
+ }
288
+ if (args.action === "film-status") {
289
+ if (!args.slug)
290
+ throw new Error("film-status exige slug (descubra via film-list).");
291
+ if (args.filmStatus === undefined && args.videoUrl === undefined && args.durationSecMeasured === undefined) {
292
+ throw new Error("film-status: passe filmStatus, videoUrl e/ou durationSecMeasured (o fecho do render é os três).");
293
+ }
294
+ return await convexMutation("videoSpecs:mcpSetVideoStatus", {
295
+ sessionToken,
296
+ slug: args.slug,
297
+ status: args.filmStatus,
298
+ videoUrl: args.videoUrl,
299
+ durationSec: args.durationSecMeasured,
300
+ });
301
+ }
302
+ if (args.action === "film-publish") {
303
+ if (!args.slug)
304
+ throw new Error("film-publish exige slug (descubra via film-list).");
305
+ if (typeof args.published !== "boolean") {
306
+ throw new Error("film-publish exige published (true publica no Acervo, false despublica).");
307
+ }
308
+ return await convexMutation("videoSpecs:mcpSetVideoPublished", {
309
+ sessionToken,
310
+ slug: args.slug,
311
+ published: args.published,
312
+ });
313
+ }
314
+ if (args.action === "film-delete") {
315
+ if (!args.slug)
316
+ throw new Error("film-delete exige slug (descubra via film-list).");
317
+ return await convexMutation("videoSpecs:mcpDeleteVideoSpec", { sessionToken, slug: args.slug });
318
+ }
199
319
  if (args.action === "showcase") {
200
320
  if (!args.slug) {
201
321
  throw new Error("action=showcase exige slug (descubra via action=demos).");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.33.0",
3
+ "version": "1.35.0",
4
4
  "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.",
5
5
  "type": "module",
6
6
  "bin": {