sapiens-mcp 1.71.1 → 1.72.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
@@ -76,7 +76,7 @@ export const TOOLS = {
76
76
  handler: repertorio,
77
77
  },
78
78
  sapiens_gallery: {
79
- description: "Browse, publicação e upload das imagens do user. Sub-actions: list (últimas N peças, com prompt/model/url + isPublic + acervoUrl; kind=image por default, kind=video traz os takes com posterUrl, a capa, e kind=all mistura. Depois de ingerir ou gerar vídeo, é o list kind=video que dá a capa pra mostrar miniatura + player em vez de link solto), get (1 imagem com metadados, opcionalmente base64), publish (torna a PRÓPRIA imagem pública: entra na galeria pública + feed Pinterest, e ganha página indexável /imagem/<id> se o modelo não for degen devolve publicPageUrl; publicado NÃO despublica, não existe unpublish), upload (traz pra galeria uma peça gerada FORA da casa, por sourceUrl https, filePath local ou base64; ADMIN por ora), ingest (A PONTE, qualquer conta logada: peça que a PESSOA dirigiu com a ficha do personagem daqui e um motor de fora renderizou, no crédito dela (Kie, fal, Magnific, Sogni, a própria placa): entra NATIVA na timeline e nasce PRIVADA (isPublic false), publicável DEPOIS por ela como qualquer geração da casa, nunca sozinha, com custo 0 em Sinapses e o custo real guardado em externalCost; carrega o prompt VERBATIM, as referências da casa (referenceImageIds), o personagem (characterId) e o motor (externalEngine 'provedor-motor'); a peça chega por filePath (local, só no MCP instalado), sourceUrl (o render, baixado pelo MCP na máquina da pessoa, nunca pelo servidor) ou base64; o carimbo +18 nasce na entrada pela CLASSE do provedor (peso aberto como Sogni, Replicate, HF, fal, WaveSpeed, Civitai e a própria placa entram carimbados, só a curadoria tira; serviço com moderação como Kie, Magnific, Freepik, Krea entra sem) ou por unfiltered=true dito pela pessoa (carimbo dela, ela tira depois); idempotente por sourceUrl ou pelo hash do arquivo, então re-ingerir corrige a ficha em vez de duplicar; tetos 12 MB imagem, 60 MB vídeo, 40 por dia. A chave do provedor NUNCA passa por aqui: quem gerou foi o harness da pessoa. Passo a passo na skill 'pontes'), refs (acopla peças da casa como REFERÊNCIA numa peça que já existe, de upload ou de ingest; ADMIN, e só na própria peça), processo (O COMO JUNTO DO QUE: anexa a uma peça as peças que mostram como ela foi dirigida, o mapa de rota desenhado sobre o still, a prancha de frames com o tempo de cada marco, o antes e depois de duas rodadas; elas viram a seção 'Como foi feito' na página pública da peça, na ordem que você mandar e com a sua legenda por peça; qualquer membro, só na própria peça, teto de 12. É IRMÃ do refs e a diferença importa: refs é o INSUMO que entrou na geração, processo é o REGISTRO de como ela foi dirigida, e uma lista não mexe na outra. Peça de processo privada não aparece pra quem está de fora nem em miniatura: pra aparecer, publique ela também), cast (QUEM ESTÁ EM CENA numa peça que já existe, quando é mais de uma criatura: characterIds em ordem, o protagonista primeiro, e a peça passa a contar na ficha de todos eles em vez de sumir da segunda; qualquer membro, na própria peça). Use list/get pra reusar imagem como referência (passe o imageId em sapiens_image mode=edit ou mode=variation) ou pra mostrar pro user o que ele já tem; publish quando o user quer divulgar a imagem dele. IMPORTANTE sobre upload: a peça entra PRIVADA e continua privada: o servidor recusa publicar e recusa compartilhar na comunidade, porque ela é do usuário e a responsabilidade é dele. O caminho pra ela virar coisa pública é usar como referência numa geração daqui e publicar o resultado. O refs escreve só proveniência (de que ficha/folha a peça nasceu): não promove nada, e um upload continua sem publicar depois de ganhar referência. COMO A PEÇA FOI FEITA: list aceita skill=<slug> e devolve só o que saiu daquela skill (o mesmo slug que sapiens_image gravou em skills), com a busca varrendo as últimas 200 peças antes de cortar no limit; cada item volta com o campo skills, e null ali quer dizer que ninguém marcou, não que foi feita sem skill.",
79
+ description: "Browse, publicação e upload das imagens do user. Sub-actions: list (últimas N peças, com prompt/model/url + isPublic + acervoUrl; kind=image por default, kind=video traz os takes com posterUrl, a capa, e kind=all mistura. Depois de ingerir ou gerar vídeo, é o list kind=video que dá a capa pra mostrar miniatura + player em vez de link solto), get (1 imagem com metadados, opcionalmente base64), publish (torna a PRÓPRIA imagem pública: entra na galeria pública + feed Pinterest, e ganha página indexável /imagem/<id> se o modelo não for degen devolve publicPageUrl; publicado NÃO despublica, não existe unpublish), upload (traz pra galeria uma peça gerada FORA da casa, por sourceUrl https, filePath local ou base64; ADMIN por ora), ingest (A PONTE, qualquer conta logada: peça que a PESSOA dirigiu com a ficha do personagem daqui e um motor de fora renderizou, no crédito dela (Kie, fal, Magnific, Sogni, a própria placa): entra NATIVA na timeline e nasce PRIVADA (isPublic false), publicável DEPOIS por ela como qualquer geração da casa, nunca sozinha, com custo 0 em Sinapses e o custo real guardado em externalCost; carrega o prompt VERBATIM, as referências da casa (referenceImageIds), o personagem (characterId) e o motor (externalEngine 'provedor-motor'); a peça chega por filePath (local, só no MCP instalado), sourceUrl (o render, baixado pelo MCP na máquina da pessoa, nunca pelo servidor) ou base64; o carimbo +18 nasce na entrada pela CLASSE do provedor (peso aberto como Sogni, Replicate, HF, fal, WaveSpeed, Civitai e a própria placa entram carimbados, só a curadoria tira; serviço com moderação como Kie, Magnific, Freepik, Krea entra sem) ou por unfiltered=true dito pela pessoa (carimbo dela, ela tira depois); idempotente por sourceUrl ou pelo hash do arquivo, então re-ingerir corrige a ficha em vez de duplicar; tetos 12 MB imagem, 60 MB vídeo, 40 por dia. A chave do provedor NUNCA passa por aqui: quem gerou foi o harness da pessoa. Passo a passo na skill 'pontes'), refs (acopla peças da casa como REFERÊNCIA numa peça que já existe, de upload ou de ingest; ADMIN, e só na própria peça), processo (O COMO JUNTO DO QUE: anexa a uma peça as peças que mostram como ela foi dirigida, o mapa de rota desenhado sobre o still, a prancha de frames com o tempo de cada marco, o antes e depois de duas rodadas; elas viram a seção 'Como foi feito' na página pública da peça, na ordem que você mandar e com a sua legenda por peça; qualquer membro, só na própria peça, teto de 12. É IRMÃ do refs e a diferença importa: refs é o INSUMO que entrou na geração, processo é o REGISTRO de como ela foi dirigida, e uma lista não mexe na outra. Peça de processo privada não aparece pra quem está de fora nem em miniatura: pra aparecer, publique ela também), cast (QUEM ESTÁ EM CENA numa peça que já existe, quando é mais de uma criatura: characterIds em ordem, o protagonista primeiro, e a peça passa a contar na ficha de todos eles em vez de sumir da segunda; qualquer membro, na própria peça), parts (O INTEIRO E OS PEDAÇOS de um vídeo: o clipe de 30s que foi montado a partir de dois takes de 15s, a fita longa feita de partes. COM imageId devolve a ficha de parentesco daquela peça, quais são as partes dela e de qual inteiro ela é parte, com a url do arquivo completo pra baixar. SEM imageId devolve o que AINDA NÃO FOI MONTADO: varre os takes soltos da conta, agrupa os que têm cara de par (mesmo personagem, mesmo período, prompt de 'Part one/Part two' ou arquivo terminando em -a/-b) e diz o PORQUE de cada agrupamento, pra você saber o que falta juntar antes de o autor perguntar. É heurística e não monta nada sozinha: adianta a descoberta, quem decide é quem lê. characterId filtra por personagem. Sem custo), link_parts (LIGA o inteiro aos pedaços: imageId é o vídeo montado e partIds são os takes NA ORDEM em que aparecem nele. O juntador da casa e o ingest que traz referências de vídeo já gravam esse laço sozinhos; esta porta é pra montagem feita FORA (ffmpeg na máquina, ingest do arquivo já pronto) e pra corrigir ligação errada. Só na própria peça, teto de 16 partes, e a lista TROCA o que estava gravado antes. Sem custo). Use list/get pra reusar imagem como referência (passe o imageId em sapiens_image mode=edit ou mode=variation) ou pra mostrar pro user o que ele já tem; publish quando o user quer divulgar a imagem dele. IMPORTANTE sobre upload: a peça entra PRIVADA e continua privada: o servidor recusa publicar e recusa compartilhar na comunidade, porque ela é do usuário e a responsabilidade é dele. O caminho pra ela virar coisa pública é usar como referência numa geração daqui e publicar o resultado. O refs escreve só proveniência (de que ficha/folha a peça nasceu): não promove nada, e um upload continua sem publicar depois de ganhar referência. COMO A PEÇA FOI FEITA: list aceita skill=<slug> e devolve só o que saiu daquela skill (o mesmo slug que sapiens_image gravou em skills), com a busca varrendo as últimas 200 peças antes de cortar no limit; cada item volta com o campo skills, e null ali quer dizer que ninguém marcou, não que foi feita sem skill.",
80
80
  schema: gallerySchema,
81
81
  handler: gallery,
82
82
  },
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { createHash } from "node:crypto";
4
- import { convexAction, convexMutation, describeConvexError, getSessionToken, isRemoteContext, } from "../convexClient.js";
4
+ import { convexAction, convexMutation, convexQuery, describeConvexError, getSessionToken, isRemoteContext, } from "../convexClient.js";
5
5
  /**
6
6
  * Galeria do usuário: browse, publicação e UPLOAD de peça de fora.
7
7
  * Wrapper sobre `desktopMcp.galleryList` / `galleryGet` / `gallerySetPublic` e
@@ -40,6 +40,8 @@ export const gallerySchema = z.object({
40
40
  "refs",
41
41
  "processo",
42
42
  "cast",
43
+ "parts",
44
+ "link_parts",
43
45
  ]),
44
46
  processo: z
45
47
  .array(z.object({
@@ -77,7 +79,7 @@ export const gallerySchema = z.object({
77
79
  imageId: z
78
80
  .string()
79
81
  .optional()
80
- .describe("generatedImages:_id (obrigatório pra action=get/publish/refs/cast)"),
82
+ .describe("generatedImages:_id (obrigatório pra action=get/publish/refs/cast/processo, e em link_parts é o vídeo INTEIRO). Em action=parts é OPCIONAL: com ele vem a ficha de parentesco daquela peça, sem ele vem o que ainda não foi montado na conta inteira."),
81
83
  includeBase64: z
82
84
  .boolean()
83
85
  .optional()
@@ -140,6 +142,10 @@ export const gallerySchema = z.object({
140
142
  .union([z.array(z.string()), z.string()])
141
143
  .optional()
142
144
  .describe("QUEM ESTÁ EM CENA, quando é mais de uma criatura (action=ingest e action=cast). Em ordem de cena: o PRIMEIRO é o protagonista (é ele que a ficha e o cartão do vídeo mostram) e o resto vira elenco, então a peça conta na ficha de TODOS eles em vez de sumir da segunda. Teto de 6. Use quando a fita foi dirigida com duas folhas de personagem: sem isto, ela aparece só na ficha de um. Aceita lista ou os ids separados por vírgula, personagem seu ou público. Pra peça de uma criatura só continue usando characterId."),
145
+ partIds: z
146
+ .union([z.array(z.string()), z.string()])
147
+ .optional()
148
+ .describe("action=link_parts: os PEDAÇOS que viraram o vídeo inteiro, NA ORDEM em que aparecem nele (take A, depois take B). generatedImages:_id de vídeo seu, teto de 16, aceita lista ou os ids separados por vírgula. A lista TROCA o que estava gravado antes, então mande sempre todas as partes, não só a que faltou."),
143
149
  unfiltered: z
144
150
  .boolean()
145
151
  .optional()
@@ -380,6 +386,20 @@ function normalizeCastIds(raw) {
380
386
  .filter(Boolean)
381
387
  .slice(0, 6);
382
388
  }
389
+ /**
390
+ * E o mesmo pras PARTES de um vídeo, com o teto de lá: dezesseis pedaços. O
391
+ * clipe da casa tem dois takes, mas a fita longa tem mais, e o teto é do
392
+ * servidor (MAX_PARTES em convex/videoParts.ts), não daqui.
393
+ */
394
+ function normalizePartIds(raw) {
395
+ if (!raw)
396
+ return [];
397
+ const lista = Array.isArray(raw) ? raw : raw.split(",");
398
+ return lista
399
+ .map((id) => id.trim().replace(/^["'[]+|["'\]]+$/g, ""))
400
+ .filter(Boolean)
401
+ .slice(0, 16);
402
+ }
383
403
  export async function gallery(args) {
384
404
  const sessionToken = getSessionToken();
385
405
  if (args.action === "list") {
@@ -573,4 +593,51 @@ export async function gallery(args) {
573
593
  replace: args.replace,
574
594
  });
575
595
  }
596
+ // parts: o parentesco entre o clipe INTEIRO e os takes que viraram ele.
597
+ //
598
+ // Duas perguntas na mesma action, porque quem faz uma quase sempre precisa
599
+ // da outra. COM imageId responde a ficha daquela peça: quais são as partes
600
+ // dela, ou de qual inteiro ela é parte. SEM imageId responde a pergunta que
601
+ // faz a tabela existir, o que ainda NÃO foi montado: varre os takes soltos,
602
+ // agrupa os que têm cara de par (mesmo personagem, mesmo período, prompt de
603
+ // "Part one/Part two" ou arquivo em -a/-b) e devolve o `porque` de cada
604
+ // agrupamento.
605
+ //
606
+ // O agrupamento é heurística e não monta nada sozinho: ele adianta o
607
+ // trabalho de descobrir, quem decide é quem lê, e a ligação se fecha com
608
+ // action=link_parts depois que o inteiro existe. characterId aqui FILTRA por
609
+ // personagem.
610
+ if (args.action === "parts") {
611
+ if (args.imageId) {
612
+ return await convexQuery("videoParts:mcpPartsOf", {
613
+ sessionToken,
614
+ imageId: args.imageId,
615
+ });
616
+ }
617
+ return await convexQuery("videoParts:mcpPendingWholes", {
618
+ sessionToken,
619
+ ...(args.characterId ? { influencerId: args.characterId } : {}),
620
+ });
621
+ }
622
+ // link_parts: liga na mão o inteiro aos pedaços.
623
+ //
624
+ // Existe porque a montagem quase nunca acontece aqui dentro: o clipe é
625
+ // juntado no ffmpeg da máquina de quem dirige e volta por action=ingest como
626
+ // uma peça nova, sem nenhum laço com os takes que a formaram. O juntador da
627
+ // casa (o "compilado") e o ingest com refs de vídeo já gravam o laço
628
+ // sozinhos; esta porta é pro resto, e pra corrigir ligação errada.
629
+ if (args.action === "link_parts") {
630
+ if (!args.imageId) {
631
+ throw new Error("action=link_parts exige imageId: o vídeo INTEIRO, o clipe montado. Use action=parts SEM imageId pra ver os pares de take que ainda não viraram um inteiro.");
632
+ }
633
+ const partIds = normalizePartIds(args.partIds);
634
+ if (!partIds.length) {
635
+ throw new Error("action=link_parts exige partIds: os takes que viraram esse inteiro, na ordem em que aparecem nele.");
636
+ }
637
+ return await convexMutation("videoParts:mcpLinkParts", {
638
+ sessionToken,
639
+ wholeId: args.imageId,
640
+ partIds,
641
+ });
642
+ }
576
643
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.71.1",
3
+ "version": "1.72.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",