sapiens-mcp 1.44.0 → 1.45.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 +29 -22
- package/dist/tools/decupagem.js +172 -0
- package/dist/tools/gallery.js +58 -1
- package/dist/tools/repertorio.js +3 -3
- package/package.json +1 -1
package/dist/registry.js
CHANGED
|
@@ -26,6 +26,7 @@ import { forum, forumSchema } from "./tools/forum.js";
|
|
|
26
26
|
import { aula, aulaSchema } from "./tools/aula.js";
|
|
27
27
|
import { support, supportSchema } from "./tools/support.js";
|
|
28
28
|
import { atlas, atlasSchema } from "./tools/atlas.js";
|
|
29
|
+
import { decupagem, decupagemSchema } from "./tools/decupagem.js";
|
|
29
30
|
import { reference, referenceSchema } from "./tools/reference.js";
|
|
30
31
|
import { trilhas, trilhasSchema } from "./tools/trilhas.js";
|
|
31
32
|
import { semana, semanaSchema } from "./tools/semana.js";
|
|
@@ -62,7 +63,7 @@ export const TOOLS = {
|
|
|
62
63
|
handler: skill,
|
|
63
64
|
},
|
|
64
65
|
sapiens_repertorio: {
|
|
65
|
-
description: "Acervo pessoal de filme/série/anime/jogo/livro/música (Repertório, o segundo cérebro do user). Reads: list (filtros mediaType/status), search (texto em title/genres/tags), get (detalhe), popArticles, resolve (busca capa/ano/id nos providers server-side: OMDb/IGDB-Twitch/AniList/Google Books/iTunes). Mutations (qualquer logado, mexem no PRÓPRIO acervo): add_item, update_item (status/rating/tags/note/isPublic), remove_item. CAPTURA ONE-SHOT travada na lista de providers: quando o user fala natural ('acabei de ver Duna 2, nota 9', 'tô jogando Hollow Knight', 'li tal livro'), (1) infira mediaType e status (assisti/zerei/li=completed, quero=backlog, tô jogando/vendo=active, dropei=dropped) e rating se citado; (2) chame action=resolve {mediaType, query}, escolha o candidato certo e faça add_item passando SÓ o source + externalId DELE + os campos pessoais (status/rating/tags/note). O servidor re-resolve no provider e grava título/capa/ano canônicos — você NÃO manda título/capa nem inventa externalId. (3) Se o resolve não achar (lista vazia/providerKeyMissing), NÃO dá pra adicionar: diga ao user que não encontrou nos providers (não fabrique entry manual). Upsert/dedup por (userId, source, externalId). Só pergunte se ambíguo entre candidatos. FERRAMENTAS DE IA (Repertório de Ferramentas): fluxo separado (vêm do catálogo aitag, não dos providers de mídia). action=search_tools {query} acha a ferramenta no catálogo e devolve o toolId; action=add_tool {toolId, favorite?, rating?, note?} grava como mediaType 'tool' (estar no acervo já é 'usei'; favorite=true liga a estrela). Use quando o user fala 'adiciona o Midjourney/Cursor no meu repertório de ferramentas' ou 'uso tal ferramenta de IA'. Cada ferramenta aponta pra página dela no aitag.",
|
|
66
|
+
description: "Acervo pessoal de filme/série/anime/jogo/livro/música/pessoa (Repertório, o segundo cérebro do user). Reads: list (filtros mediaType/status), search (texto em title/genres/tags), get (detalhe), popArticles, resolve (busca capa/ano/id nos providers server-side: OMDb/IGDB-Twitch/AniList/Google Books/iTunes/Wikidata). Mutations (qualquer logado, mexem no PRÓPRIO acervo): add_item, update_item (status/rating/tags/note/isPublic), remove_item. CAPTURA ONE-SHOT travada na lista de providers: quando o user fala natural ('acabei de ver Duna 2, nota 9', 'tô jogando Hollow Knight', 'li tal livro'), (1) infira mediaType e status (assisti/zerei/li=completed, quero=backlog, tô jogando/vendo=active, dropei=dropped) e rating se citado; (2) chame action=resolve {mediaType, query}, escolha o candidato certo e faça add_item passando SÓ o source + externalId DELE + os campos pessoais (status/rating/tags/note). O servidor re-resolve no provider e grava título/capa/ano canônicos — você NÃO manda título/capa nem inventa externalId. (3) Se o resolve não achar (lista vazia/providerKeyMissing), NÃO dá pra adicionar: diga ao user que não encontrou nos providers (não fabrique entry manual). Upsert/dedup por (userId, source, externalId). Só pergunte se ambíguo entre candidatos. FERRAMENTAS DE IA (Repertório de Ferramentas): fluxo separado (vêm do catálogo aitag, não dos providers de mídia). action=search_tools {query} acha a ferramenta no catálogo e devolve o toolId; action=add_tool {toolId, favorite?, rating?, note?} grava como mediaType 'tool' (estar no acervo já é 'usei'; favorite=true liga a estrela). Use quando o user fala 'adiciona o Midjourney/Cursor no meu repertório de ferramentas' ou 'uso tal ferramenta de IA'. Cada ferramenta aponta pra página dela no aitag. SAPIENS (pessoas, ombros de gigantes; a aba no site chama Sapiens): mediaType 'person' cataloga gente que inspira (cineasta, mangaká, cientista, escritor) via Wikidata. Mesmo fluxo de mídia: resolve {mediaType:'person', query:'Osamu Tezuka'} → add_item com source 'wikidata' + o QID do candidato. O servidor grava foto, ocupações, bio e o link da Wikipedia; entra como completed (estar no acervo = me inspira). Use quando o user fala 'adiciona o Miyazaki nas minhas inspirações/nos meus sapiens/no meu repertório'.",
|
|
66
67
|
schema: repertorioSchema,
|
|
67
68
|
handler: repertorio,
|
|
68
69
|
},
|
|
@@ -181,6 +182,11 @@ export const TOOLS = {
|
|
|
181
182
|
schema: atlasSchema,
|
|
182
183
|
handler: atlas,
|
|
183
184
|
},
|
|
185
|
+
sapiens_decupagem: {
|
|
186
|
+
description: "Acervo de Decupagens — a régua de linguagem cinematográfica das referências da casa (admin-only, não cobra Sinapse). O que MEDE é o motor LOCAL `python tools/decupagem/decupa.py \"<arquivo ou link do YouTube>\"` (Python + OpenCV + ffmpeg; não existe OpenCV em serverless, igual à produção dos Vídeos Programáticos). Esta tool é a outra ponta: sobe a ficha que o motor gerou e consulta o acervo. Sub-actions: 'list' (a régua de cada referência já decupada: planos, plano mediano, escola de corte, movimento de câmera vs sujeito, registro de luz, BPM, se o corte obedece ao som), 'get' (a ficha INTEIRA de um slug: plano a plano com o que está no quadro, luz, som, sincronia), 'save' (sobe fichas/<slug>/ficha.json inteiro, sem resumir; mesmo slug ATUALIZA em vez de duplicar), 'note' (anota a leitura editorial na ficha), 'delete'. Fluxo que fecha o ciclo: roda o motor → lê ficha.json → save → aparece no Lab /experimentos/decupagem e entra no comparador. O acervo vale pelo CONJUNTO: uma ficha é morna, vinte lado a lado são a biblioteca de gramática da casa. Antes de decupar referência nova, rode list pra não medir a mesma coisa duas vezes.",
|
|
187
|
+
schema: decupagemSchema,
|
|
188
|
+
handler: decupagem,
|
|
189
|
+
},
|
|
184
190
|
sapiens_reference: {
|
|
185
191
|
description: "O 'popup global de referência' do Sapiens — espelha o modal 'Selecionar Referência' do gerador web: um lugar só pra navegar os bancos e pegar o que vira referência em imagem/vídeo. READ-ONLY. DUAS sub-actions: 'browse' (navega um banco) e 'resolve' (acha UM asset pelo ID citável). RESOLVE: passe handle=<tipo>_<id> (img_… imagem, vid_… vídeo, film_… vídeo programático, trk_… música, comic_… tirinha, char_… personagem — é o ID que o usuário copia no detalhe do item) e o servidor devolve {mine, isPublic, url, title, mediaKind} SE for seu ou público (senão recusa; nunca acessa asset privado de terceiro). Use a url retornada como referência (referenceImageUrls / startImageUrl / endImageUrl). BROWSE + bucket: 'history' (suas imagens recentes, privadas+públicas), 'favorites' (imagens que você curtiu, só as suas), 'videos' (seus vídeos / Meus Vídeos), 'stock_video' (banco de B-roll da casa, público), 'acervo' (stock + comunidade públicos; aceita term=busca e source=all|stock|community), 'characters' (personagens; mode=mine [default, inclui rascunhos] ou public [Explorar]). Paginado (page/limit, default 20, máx 50; use hasMore). Itens normalizados: imagem PRÓPRIA (history/favorites) traz imageId + url (use imageId em sapiens_image sourceImageIds ou sapiens_video startImageId/endImageId; ou a url em referenceImageUrls); acervo e characters são públicos/de terceiros, use a url (characters trazem mainImageUrl + imageUrls + characterId) em referenceImageUrls / startImageUrl / endImageUrl, NÃO em sourceImageIds. Personagens públicos também têm porta dedicada em sapiens_character action=list_public.",
|
|
186
192
|
schema: referenceSchema,
|
|
@@ -207,27 +213,27 @@ export const TOOLS = {
|
|
|
207
213
|
// É a "skill que anda junto com o pacote": escrevo uma vez, vale pra todos os clients,
|
|
208
214
|
// sem instalar nada. Cobre os tropeços reais (fluxo do musicator, model no vídeo, o
|
|
209
215
|
// disjuntor anti-loop). Mantém curto de propósito: viaja em todo handshake.
|
|
210
|
-
export const SAPIENS_INSTRUCTIONS = `Você opera o Sapiens Sintéticos (sapiensinteticos.com) NA CONTA de um usuário logado. Cada tool age de verdade na conta dele e muitas COBRAM Sinapses (o crédito da casa). Aja como operador, não no chute.
|
|
211
|
-
|
|
212
|
-
AS SKILLS DA CASA (leia ANTES de operar, não improvise o fluxo):
|
|
213
|
-
O passo a passo travado de cada fluxo mora na tool sapiens_skill, servida por este mesmo servidor: sem login, sem custo, sem rede. Ler a skill é mais barato que errar uma geração que cobra.
|
|
214
|
-
- sapiens_skill action=list -> o índice (slug + quando usar cada uma).
|
|
215
|
-
- sapiens_skill action=get name=<slug> -> a skill inteira.
|
|
216
|
-
Slugs: ${skillMenuLine()}
|
|
217
|
-
Puxe a skill ANTES de: gerar música ou efeito sonoro (musica), escolher modelo de vídeo (video), gerar imagem (imagem), 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). Perdido no começo: primeiros-passos.
|
|
218
|
-
Cliente que lê resources MCP acha o MESMO conteúdo em skill://sapiens/<slug>/SKILL.md.
|
|
219
|
-
|
|
220
|
-
REGRA DE OURO:
|
|
221
|
-
- PRIMEIRO CONTATO ou "o que você faz?"/"como começo?"/"o que dá pra fazer?": chame sapiens_meta action=start e MOSTRE o resultado na sua voz. Sem login, ele ensina a conectar; logado, traz saldo + primeiros poderes com exemplos. É a porta de entrada: não despeje a lista inteira de tools, deixe o start guiar.
|
|
222
|
-
- LOGO APÓS UM LOGIN BEM-SUCEDIDO (action=login retornou ok): chame action=start na sequência e mostre a porta de entrada. O recém-chegado não sabe o que pedir; não o deixe na tela em branco, guie a primeira jogada sem ele precisar perguntar.
|
|
223
|
-
- Se um tool voltar erro de validação ("exige X", "falta Y"), LEIA o erro e refaça a chamada COM o que falta. NUNCA repita igual a chamada que falhou: 3 falhas seguidas no mesmo tool fazem o cliente marcar o servidor como "unreachable" por ~56s (disjuntor anti-loop). Aí parece que "o MCP caiu", quando foi só argumento faltando.
|
|
224
|
-
- Antes de gerar algo caro (imagem/música/vídeo), cheque saldo: sapiens_meta action=credits (ou subscription). Saldo baixo, avise o usuário antes. Vídeo é o mais caro da casa: confirme com ele antes de disparar.
|
|
225
|
-
- REGRA DO TIMEOUT (vale pra toda geração SÍNCRONA: imagem pesada, artigo, mega-gráfico, carrossel): a chamada pode estourar o teto de ~120s do cliente e voltar 'Timeout' MESMO tendo gerado e COBRADO. Nunca repita às cegas: confira antes onde o resultado cairia (imagem: sapiens_gallery action=list; artigo: sapiens_write action=list; carrossel: sapiens_pipeline action=list_carousels; pipeline: /dashboard/admin/content).
|
|
226
|
-
- "sessionToken expirado" = refaça login: sapiens_meta action=login com o código de sapiensinteticos.com/conectar-claude.
|
|
227
|
-
- sapiens_meta action=formats devolve os schemas por formato; action=whoami diz tier (user/admin) + saldo. sapiens_image e sapiens_video action=models trazem o catálogo com o preço ATUAL: consulte em vez de chutar custo.
|
|
228
|
-
|
|
229
|
-
MODO COMPANHIA: sapiens_meta action=start e action=whoami podem trazer um bloco 'companion'. Quando vier companion.active=true, você INCORPORA aquele Sintético (a voz dele, o avatar, o oi dele) continuando a operar na conta e nas Sinapses do USUÁRIO. Puxe a skill 'companhia' antes de fazer isso.
|
|
230
|
-
|
|
216
|
+
export const SAPIENS_INSTRUCTIONS = `Você opera o Sapiens Sintéticos (sapiensinteticos.com) NA CONTA de um usuário logado. Cada tool age de verdade na conta dele e muitas COBRAM Sinapses (o crédito da casa). Aja como operador, não no chute.
|
|
217
|
+
|
|
218
|
+
AS SKILLS DA CASA (leia ANTES de operar, não improvise o fluxo):
|
|
219
|
+
O passo a passo travado de cada fluxo mora na tool sapiens_skill, servida por este mesmo servidor: sem login, sem custo, sem rede. Ler a skill é mais barato que errar uma geração que cobra.
|
|
220
|
+
- sapiens_skill action=list -> o índice (slug + quando usar cada uma).
|
|
221
|
+
- sapiens_skill action=get name=<slug> -> a skill inteira.
|
|
222
|
+
Slugs: ${skillMenuLine()}
|
|
223
|
+
Puxe a skill ANTES de: gerar música ou efeito sonoro (musica), escolher modelo de vídeo (video), gerar imagem (imagem), 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). Perdido no começo: primeiros-passos.
|
|
224
|
+
Cliente que lê resources MCP acha o MESMO conteúdo em skill://sapiens/<slug>/SKILL.md.
|
|
225
|
+
|
|
226
|
+
REGRA DE OURO:
|
|
227
|
+
- PRIMEIRO CONTATO ou "o que você faz?"/"como começo?"/"o que dá pra fazer?": chame sapiens_meta action=start e MOSTRE o resultado na sua voz. Sem login, ele ensina a conectar; logado, traz saldo + primeiros poderes com exemplos. É a porta de entrada: não despeje a lista inteira de tools, deixe o start guiar.
|
|
228
|
+
- LOGO APÓS UM LOGIN BEM-SUCEDIDO (action=login retornou ok): chame action=start na sequência e mostre a porta de entrada. O recém-chegado não sabe o que pedir; não o deixe na tela em branco, guie a primeira jogada sem ele precisar perguntar.
|
|
229
|
+
- Se um tool voltar erro de validação ("exige X", "falta Y"), LEIA o erro e refaça a chamada COM o que falta. NUNCA repita igual a chamada que falhou: 3 falhas seguidas no mesmo tool fazem o cliente marcar o servidor como "unreachable" por ~56s (disjuntor anti-loop). Aí parece que "o MCP caiu", quando foi só argumento faltando.
|
|
230
|
+
- Antes de gerar algo caro (imagem/música/vídeo), cheque saldo: sapiens_meta action=credits (ou subscription). Saldo baixo, avise o usuário antes. Vídeo é o mais caro da casa: confirme com ele antes de disparar.
|
|
231
|
+
- REGRA DO TIMEOUT (vale pra toda geração SÍNCRONA: imagem pesada, artigo, mega-gráfico, carrossel): a chamada pode estourar o teto de ~120s do cliente e voltar 'Timeout' MESMO tendo gerado e COBRADO. Nunca repita às cegas: confira antes onde o resultado cairia (imagem: sapiens_gallery action=list; artigo: sapiens_write action=list; carrossel: sapiens_pipeline action=list_carousels; pipeline: /dashboard/admin/content).
|
|
232
|
+
- "sessionToken expirado" = refaça login: sapiens_meta action=login com o código de sapiensinteticos.com/conectar-claude.
|
|
233
|
+
- sapiens_meta action=formats devolve os schemas por formato; action=whoami diz tier (user/admin) + saldo. sapiens_image e sapiens_video action=models trazem o catálogo com o preço ATUAL: consulte em vez de chutar custo.
|
|
234
|
+
|
|
235
|
+
MODO COMPANHIA: sapiens_meta action=start e action=whoami podem trazer um bloco 'companion'. Quando vier companion.active=true, você INCORPORA aquele Sintético (a voz dele, o avatar, o oi dele) continuando a operar na conta e nas Sinapses do USUÁRIO. Puxe a skill 'companhia' antes de fazer isso.
|
|
236
|
+
|
|
231
237
|
Voz da casa: 1ª pessoa, direto, anti-corporate, sem travessão. Pra bom entendedor, meia palavra basta. Texto que vai ser publicado pede a skill 'voz-da-casa' antes.`;
|
|
232
238
|
// Annotations MCP: título humano + dica read-only. São HINTS (não-confiáveis por
|
|
233
239
|
// spec): quem gateia de verdade continua o servidor (saldo, gate de admin,
|
|
@@ -295,6 +301,7 @@ const ADMIN_ONLY_TOOLS = new Set([
|
|
|
295
301
|
"sapiens_instagram",
|
|
296
302
|
"sapiens_aula",
|
|
297
303
|
"sapiens_share",
|
|
304
|
+
"sapiens_decupagem",
|
|
298
305
|
]);
|
|
299
306
|
/**
|
|
300
307
|
* Monta o payload do tools/list pro tier dado. Tier "user" esconde as
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { convexQuery, convexMutation, getSessionToken } from "../convexClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* sapiens_decupagem — o acervo de réguas de linguagem cinematográfica da casa.
|
|
5
|
+
*
|
|
6
|
+
* O que MEDE é o motor local (`tools/decupagem/decupa.py`, Python + OpenCV +
|
|
7
|
+
* ffmpeg): não existe OpenCV em serverless, então a medição roda na máquina,
|
|
8
|
+
* igual à produção dos Vídeos Programáticos. Esta tool é a outra ponta: sobe a
|
|
9
|
+
* ficha que o motor produziu e consulta o que já foi decupado.
|
|
10
|
+
*
|
|
11
|
+
* O fluxo que fecha o ciclo:
|
|
12
|
+
* 1. `python tools/decupagem/decupa.py "<arquivo ou link>"`
|
|
13
|
+
* 2. lê `fichas/<slug>/ficha.json`
|
|
14
|
+
* 3. `sapiens_decupagem action=save` com o conteúdo dela
|
|
15
|
+
* 4. a ficha aparece no Lab (/experimentos/decupagem) e entra no comparador
|
|
16
|
+
*
|
|
17
|
+
* O acervo vale pelo CONJUNTO: uma ficha é morna, vinte lado a lado são a
|
|
18
|
+
* biblioteca de gramática da casa (plano mediano, quem carrega o movimento,
|
|
19
|
+
* registro de luz, se o corte obedece ao som). Antes de decupar uma referência
|
|
20
|
+
* nova, `action=list` mostra o que já tem e evita medir a mesma coisa duas
|
|
21
|
+
* vezes.
|
|
22
|
+
*
|
|
23
|
+
* Admin-only: o acervo é material interno. Não cobra Sinapse.
|
|
24
|
+
*/
|
|
25
|
+
export const decupagemSchema = z.object({
|
|
26
|
+
action: z.enum(["list", "get", "save", "note", "delete"]),
|
|
27
|
+
slug: z
|
|
28
|
+
.string()
|
|
29
|
+
.optional()
|
|
30
|
+
.describe("Pra get/save/note/delete. É a chave do acervo: salvar de novo com o " +
|
|
31
|
+
"mesmo slug ATUALIZA a ficha em vez de duplicar (redecupar depois de " +
|
|
32
|
+
"afinar o motor é comum)."),
|
|
33
|
+
ficha: z
|
|
34
|
+
.any()
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Pra save: o conteúdo INTEIRO de fichas/<slug>/ficha.json, como o motor " +
|
|
37
|
+
"produziu. Não resuma nem reescreva: o Lab lê os campos de dentro."),
|
|
38
|
+
nota: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Pra save/note: a LEITURA editorial, escrita por quem olhou (o que dá " +
|
|
42
|
+
"pra roubar, o que não dá). O motor mede, isto interpreta, e os dois " +
|
|
43
|
+
"não se misturam na ficha."),
|
|
44
|
+
});
|
|
45
|
+
export async function decupagem(args) {
|
|
46
|
+
const sessionToken = getSessionToken();
|
|
47
|
+
if (args.action === "list") {
|
|
48
|
+
const fichas = await convexQuery("decupagens:mcpListar", { sessionToken });
|
|
49
|
+
const n = Array.isArray(fichas) ? fichas.length : 0;
|
|
50
|
+
return {
|
|
51
|
+
count: n,
|
|
52
|
+
fichas,
|
|
53
|
+
note: n === 0
|
|
54
|
+
? "Acervo vazio. Rode o motor local (python tools/decupagem/decupa.py) e suba com action=save."
|
|
55
|
+
: "Régua de cada referência. Pra ficha inteira (planos, luz, som): action=get slug=<slug>.",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (args.action === "get") {
|
|
59
|
+
if (!args.slug)
|
|
60
|
+
throw new Error("sapiens_decupagem get: exige slug.");
|
|
61
|
+
const ficha = await convexQuery("decupagens:mcpPegar", {
|
|
62
|
+
sessionToken,
|
|
63
|
+
slug: args.slug,
|
|
64
|
+
});
|
|
65
|
+
if (!ficha) {
|
|
66
|
+
return {
|
|
67
|
+
found: false,
|
|
68
|
+
note: `Nenhuma ficha com slug '${args.slug}'. Veja o acervo com action=list.`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { found: true, ficha };
|
|
72
|
+
}
|
|
73
|
+
if (args.action === "save") {
|
|
74
|
+
if (!args.ficha) {
|
|
75
|
+
throw new Error("sapiens_decupagem save: exige `ficha` (o ficha.json inteiro que o " +
|
|
76
|
+
"motor gerou em tools/decupagem/fichas/<slug>/).");
|
|
77
|
+
}
|
|
78
|
+
const f = args.ficha;
|
|
79
|
+
const resumo = extrairResumo(f, args.slug);
|
|
80
|
+
const r = await convexMutation("decupagens:mcpSalvar", {
|
|
81
|
+
sessionToken,
|
|
82
|
+
...resumo,
|
|
83
|
+
ficha: f,
|
|
84
|
+
nota: args.nota,
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
ok: true,
|
|
88
|
+
slug: resumo.slug,
|
|
89
|
+
criado: r?.criado ?? null,
|
|
90
|
+
note: `Ficha de '${resumo.titulo}' no acervo (${resumo.planos} planos, ` +
|
|
91
|
+
`plano mediano ${resumo.durPlanoMediana}s, ${resumo.escola}). ` +
|
|
92
|
+
`Aparece no Lab em /experimentos/decupagem.`,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (args.action === "note") {
|
|
96
|
+
if (!args.slug || !args.nota) {
|
|
97
|
+
throw new Error("sapiens_decupagem note: exige slug e nota.");
|
|
98
|
+
}
|
|
99
|
+
await convexMutation("decupagens:mcpSalvarNota", {
|
|
100
|
+
sessionToken,
|
|
101
|
+
slug: args.slug,
|
|
102
|
+
nota: args.nota,
|
|
103
|
+
});
|
|
104
|
+
return { ok: true, note: "Leitura anotada na ficha." };
|
|
105
|
+
}
|
|
106
|
+
if (args.action === "delete") {
|
|
107
|
+
if (!args.slug)
|
|
108
|
+
throw new Error("sapiens_decupagem delete: exige slug.");
|
|
109
|
+
const r = await convexMutation("decupagens:mcpApagar", {
|
|
110
|
+
sessionToken,
|
|
111
|
+
slug: args.slug,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
apagado: r?.apagado ?? false,
|
|
116
|
+
note: r?.apagado ? "Ficha removida do acervo." : "Não havia ficha com esse slug.",
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
throw new Error(`sapiens_decupagem: action desconhecida '${args.action}'.`);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Puxa as colunas que o Lab ordena e filtra de dentro da ficha do motor.
|
|
123
|
+
*
|
|
124
|
+
* O resto da medição vai inteiro pro campo `ficha`: o motor ganhou dolly,
|
|
125
|
+
* concentração de matiz, ilha de cor e leitura semântica em uma semana, e
|
|
126
|
+
* promover cada medida nova a coluna faria o schema virar gargalo.
|
|
127
|
+
*/
|
|
128
|
+
function extrairResumo(f, slugArg) {
|
|
129
|
+
const fonte = f?.fonte ?? {};
|
|
130
|
+
const cont = f?.container ?? {};
|
|
131
|
+
const ritmo = f?.ritmo ?? {};
|
|
132
|
+
const mov = f?.movimento ?? {};
|
|
133
|
+
const luz = f?.luz ?? {};
|
|
134
|
+
const som = f?.som ?? {};
|
|
135
|
+
const cad = f?.cadencia ?? {};
|
|
136
|
+
const titulo = String(fonte.titulo || fonte.arquivo || "sem titulo");
|
|
137
|
+
const slug = (slugArg || slugify(titulo)).slice(0, 80);
|
|
138
|
+
if (!slug)
|
|
139
|
+
throw new Error("sapiens_decupagem save: não consegui derivar o slug.");
|
|
140
|
+
const num = (x, def = 0) => (typeof x === "number" && isFinite(x) ? x : def);
|
|
141
|
+
// O veredito de sincronia é o do teste de IMPACTO quando existe: o teste
|
|
142
|
+
// geral satura em trilha densa e vira "sem relação" pra todo mundo.
|
|
143
|
+
const sinc = som?.sincronia_impacto ?? som?.sincronia_grade ?? som?.sincronia;
|
|
144
|
+
return {
|
|
145
|
+
slug,
|
|
146
|
+
titulo: titulo.slice(0, 200),
|
|
147
|
+
origem: String(fonte.origem || "local"),
|
|
148
|
+
entrada: fonte.entrada ? String(fonte.entrada).slice(0, 500) : undefined,
|
|
149
|
+
dur: num(cont.dur),
|
|
150
|
+
planos: num(ritmo.planos),
|
|
151
|
+
durPlanoMediana: num(ritmo.dur_mediana),
|
|
152
|
+
escola: String(ritmo.escola || "").slice(0, 120),
|
|
153
|
+
movTotalPct: num(mov.total_pct_s),
|
|
154
|
+
movCameraPct: num(mov.camera_pct_s),
|
|
155
|
+
movSujeitoPct: num(mov.sujeito_pct_s),
|
|
156
|
+
registro: String(luz.registro || "").slice(0, 160),
|
|
157
|
+
cadenciaReal: num(cad.cadencia_real),
|
|
158
|
+
bpm: typeof som.bpm === "number" ? som.bpm : undefined,
|
|
159
|
+
corteNoSom: sinc?.veredito ? String(sinc.veredito).slice(0, 160) : undefined,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
function slugify(s) {
|
|
163
|
+
return s
|
|
164
|
+
.normalize("NFD")
|
|
165
|
+
.replace(/[̀-ͯ]/g, "")
|
|
166
|
+
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
|
167
|
+
.trim()
|
|
168
|
+
.toLowerCase()
|
|
169
|
+
.replace(/[\s_-]+/g, "-")
|
|
170
|
+
.replace(/^-+|-+$/g, "")
|
|
171
|
+
.slice(0, 60);
|
|
172
|
+
}
|
package/dist/tools/gallery.js
CHANGED
|
@@ -13,12 +13,21 @@ import { convexAction, getSessionToken, isRemoteContext } from "../convexClient.
|
|
|
13
13
|
* - Publicar/despublicar a própria imagem (galeria pública + feed Pinterest)
|
|
14
14
|
* - Subir peça gerada FORA da casa (action=upload), que entra privada e serve
|
|
15
15
|
* de referência pras gerações daqui
|
|
16
|
+
* - Ingerir peça gerada em motor EXTERNO com receita da casa (action=ingest,
|
|
17
|
+
* ADMIN), que entra como peça nativa: timeline, publicação, comunidade
|
|
16
18
|
*
|
|
17
19
|
* O upload resolve URL/arquivo pra base64 AQUI, no cliente, e manda os bytes
|
|
18
20
|
* prontos pro Convex. O servidor não busca URL nenhuma (sem SSRF novo lá).
|
|
21
|
+
*
|
|
22
|
+
* O INGEST é o oposto e de propósito: quem baixa é o servidor, de uma allowlist
|
|
23
|
+
* fechada de hosts (convex/externalIngest.ts), porque a peça vai nascer nativa e
|
|
24
|
+
* a origem precisa ser verificável do lado de cá. A diferença entre as duas não
|
|
25
|
+
* é de onde vieram os bytes, é de quem foi a DIREÇÃO: peça achada pronta é
|
|
26
|
+
* upload; peça que a casa dirigiu (nossa folha, nosso personagem, nosso prompt)
|
|
27
|
+
* e um motor terceiro só renderizou é ingest.
|
|
19
28
|
*/
|
|
20
29
|
export const gallerySchema = z.object({
|
|
21
|
-
action: z.enum(["list", "get", "publish", "unpublish", "upload"]),
|
|
30
|
+
action: z.enum(["list", "get", "publish", "unpublish", "upload", "ingest"]),
|
|
22
31
|
limit: z
|
|
23
32
|
.number()
|
|
24
33
|
.int()
|
|
@@ -59,6 +68,32 @@ export const gallerySchema = z.object({
|
|
|
59
68
|
.string()
|
|
60
69
|
.optional()
|
|
61
70
|
.describe("action=upload: o que é a peça (prompt original, contexto). Vira a descrição na galeria e a busca acha por ela."),
|
|
71
|
+
// --- action=ingest (peça gerada em motor externo COM receita da casa) ---
|
|
72
|
+
externalEngine: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe("action=ingest (ADMIN): qual motor renderizou ('freepik-seedance-2.0', 'magnific'). Fica gravado como rastro na peça e no aviso do Discord."),
|
|
76
|
+
externalCost: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("action=ingest: quanto custou lá fora, em texto livre ('4200 créditos Freepik'). A peça entra com 0 Sinapses, então é isto que preserva o custo real no histórico."),
|
|
80
|
+
prompt: z
|
|
81
|
+
.string()
|
|
82
|
+
.optional()
|
|
83
|
+
.describe("action=ingest: o prompt que a CASA escreveu pra essa peça. Vira a descrição na galeria e a busca acha por ele."),
|
|
84
|
+
aspectRatio: z
|
|
85
|
+
.string()
|
|
86
|
+
.optional()
|
|
87
|
+
.describe("action=ingest: proporção da peça ('16:9', '9:16'). Default 16:9."),
|
|
88
|
+
size: z
|
|
89
|
+
.string()
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("action=ingest: resolução ('720p', '1080p'). Default 720p."),
|
|
92
|
+
referenceImageIds: z
|
|
93
|
+
.array(z.string())
|
|
94
|
+
.max(8)
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("action=ingest: generatedImages:_id das peças DA CASA que serviram de referência (folha de storyboard, ficha de personagem, frame inicial). Ficam acopladas à peça ingerida e aparecem na ficha dela: é o que prova que a receita saiu daqui. Só peça sua entra."),
|
|
62
97
|
uploadedSource: z
|
|
63
98
|
.string()
|
|
64
99
|
.optional()
|
|
@@ -207,4 +242,26 @@ export async function gallery(args) {
|
|
|
207
242
|
aviso: "Peça enviada de fora: fica privada, é sua e a responsabilidade é sua. Não publica nem vai pra comunidade. Use como referência (sourceImageIds em sapiens_image) e publique o que gerar a partir dela.",
|
|
208
243
|
};
|
|
209
244
|
}
|
|
245
|
+
if (args.action === "ingest") {
|
|
246
|
+
if (!args.sourceUrl) {
|
|
247
|
+
throw new Error("action=ingest exige sourceUrl: o link https da peça pronta no motor externo. Diferente do upload, aqui é o SERVIDOR que baixa, e só de host liberado.");
|
|
248
|
+
}
|
|
249
|
+
if (!args.externalEngine) {
|
|
250
|
+
throw new Error("action=ingest exige externalEngine: qual motor renderizou ('freepik-seedance-2.0'). É o rastro que explica depois por que a peça não tem custo em Sinapses.");
|
|
251
|
+
}
|
|
252
|
+
const result = await convexAction("externalIngest:mcpIngestExternal", {
|
|
253
|
+
sessionToken,
|
|
254
|
+
sourceUrl: args.sourceUrl,
|
|
255
|
+
externalEngine: args.externalEngine,
|
|
256
|
+
prompt: args.prompt ?? args.note,
|
|
257
|
+
externalCost: args.externalCost,
|
|
258
|
+
aspectRatio: args.aspectRatio,
|
|
259
|
+
size: args.size,
|
|
260
|
+
referenceImageIds: args.referenceImageIds,
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
...result,
|
|
264
|
+
aviso: "Peça NATIVA: entra na timeline, publica e vai pra comunidade como qualquer geração da casa. Custo em Sinapses é 0 e ela NÃO leva assinatura C2PA, porque o motor rodou fora. Use ingest só quando a receita (folha, personagem, prompt) foi da casa; peça achada pronta é action=upload.",
|
|
265
|
+
};
|
|
266
|
+
}
|
|
210
267
|
}
|
package/dist/tools/repertorio.js
CHANGED
|
@@ -24,7 +24,7 @@ import { need } from "../schema.js";
|
|
|
24
24
|
*
|
|
25
25
|
* Action-based design (memoria: consolidar tools via args).
|
|
26
26
|
*/
|
|
27
|
-
const mediaTypeEnum = z.enum(["movie", "series", "anime", "game", "book", "music", "tool"]);
|
|
27
|
+
const mediaTypeEnum = z.enum(["movie", "series", "anime", "game", "book", "music", "tool", "person"]);
|
|
28
28
|
const statusEnum = z.enum([
|
|
29
29
|
"backlog",
|
|
30
30
|
"active",
|
|
@@ -32,7 +32,7 @@ const statusEnum = z.enum([
|
|
|
32
32
|
"paused",
|
|
33
33
|
"dropped",
|
|
34
34
|
]);
|
|
35
|
-
const sourceEnum = z.enum(["tmdb", "anilist", "rawg", "twitch", "googlebooks", "itunes", "manual"]);
|
|
35
|
+
const sourceEnum = z.enum(["tmdb", "anilist", "rawg", "twitch", "googlebooks", "itunes", "manual", "wikidata"]);
|
|
36
36
|
export const repertorioSchema = z.object({
|
|
37
37
|
action: z.enum([
|
|
38
38
|
"list",
|
|
@@ -62,7 +62,7 @@ export const repertorioSchema = z.object({
|
|
|
62
62
|
// add_item — só identifica o item; o servidor re-resolve e grava o metadado.
|
|
63
63
|
source: sourceEnum
|
|
64
64
|
.optional()
|
|
65
|
-
.describe("Pra add_item: o source EXATO de um candidato do action=resolve (manual=filme/série via OMDb, anilist, twitch=jogo, googlebooks, itunes). Não invente."),
|
|
65
|
+
.describe("Pra add_item: o source EXATO de um candidato do action=resolve (manual=filme/série via OMDb, anilist, twitch=jogo, googlebooks, itunes, wikidata=pessoa). Não invente."),
|
|
66
66
|
externalId: z
|
|
67
67
|
.string()
|
|
68
68
|
.optional()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapiens-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.45.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": {
|