sapiens-mcp 1.41.0 → 1.43.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/index.js +19 -2
- package/dist/registry.js +29 -42
- package/dist/remote.js +22 -6
- package/dist/skills.js +437 -0
- package/dist/tools/gallery.js +144 -4
- package/dist/tools/image.js +7 -2
- package/dist/tools/meta.js +1 -1
- package/dist/tools/skill.js +51 -0
- package/dist/tools/video.js +4 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
-
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
10
|
import { MCP_VERSION } from "./version.js";
|
|
11
11
|
import { buildToolList, callTool, SAPIENS_INSTRUCTIONS, TOOLS, } from "./registry.js";
|
|
12
12
|
import { getCachedTier, onTierVisibilityChange, probeTierInBackground, } from "./tier.js";
|
|
13
13
|
import { getPrompt, listPrompts } from "./prompts.js";
|
|
14
|
+
import { listSkillResources, readSkillResource } from "./skills.js";
|
|
14
15
|
import { convexMutation, getSessionToken } from "./convexClient.js";
|
|
15
16
|
// Telemetria stdio (fire-and-forget, NUNCA derruba a chamada). O stdio sempre
|
|
16
17
|
// foi caixa-preta (cada cliente roda a própria cópia via npx), então o
|
|
@@ -44,12 +45,28 @@ function logStdioUsage(fields) {
|
|
|
44
45
|
// login local em disco (cache de processo + probe no boot) e muda por
|
|
45
46
|
// notifications/tools/list_changed.
|
|
46
47
|
const server = new Server({ name: "mcp-sapiens", version: MCP_VERSION }, {
|
|
47
|
-
capabilities: {
|
|
48
|
+
capabilities: {
|
|
49
|
+
tools: { listChanged: true },
|
|
50
|
+
prompts: {},
|
|
51
|
+
resources: {},
|
|
52
|
+
},
|
|
48
53
|
instructions: SAPIENS_INSTRUCTIONS,
|
|
49
54
|
});
|
|
50
55
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
51
56
|
tools: buildToolList(getCachedTier()),
|
|
52
57
|
}));
|
|
58
|
+
// As skills da casa também como RESOURCE (skill://sapiens/<slug>/SKILL.md).
|
|
59
|
+
// Conteúdo estático do pacote: não exige login, não custa, não toca o backend.
|
|
60
|
+
// Cliente que não lê resources pega o mesmo texto pela tool sapiens_skill.
|
|
61
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
62
|
+
resources: listSkillResources(),
|
|
63
|
+
}));
|
|
64
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
65
|
+
const found = readSkillResource(req.params.uri);
|
|
66
|
+
if (!found)
|
|
67
|
+
throw new Error(`Resource desconhecido: ${req.params.uri}`);
|
|
68
|
+
return found;
|
|
69
|
+
});
|
|
53
70
|
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
54
71
|
prompts: listPrompts(),
|
|
55
72
|
}));
|
package/dist/registry.js
CHANGED
|
@@ -29,7 +29,9 @@ import { atlas, atlasSchema } from "./tools/atlas.js";
|
|
|
29
29
|
import { reference, referenceSchema } from "./tools/reference.js";
|
|
30
30
|
import { trilhas, trilhasSchema } from "./tools/trilhas.js";
|
|
31
31
|
import { shareDrop, shareDropSchema } from "./tools/shareDrop.js";
|
|
32
|
+
import { skill, skillSchema } from "./tools/skill.js";
|
|
32
33
|
import { describeConvexError } from "./convexClient.js";
|
|
34
|
+
import { skillMenuLine } from "./skills.js";
|
|
33
35
|
/**
|
|
34
36
|
* REGISTRY compartilhado do sapiens-mcp: o catálogo de tools (descriptions +
|
|
35
37
|
* schemas + handlers), as instructions da casa e o dispatch. Fonte ÚNICA usada
|
|
@@ -44,7 +46,7 @@ export const TOOLS = {
|
|
|
44
46
|
handler: pipeline,
|
|
45
47
|
},
|
|
46
48
|
sapiens_image: {
|
|
47
|
-
description: "Operações de imagem via Sapiens (Gemini, Azure gpt-image-2, Grok, Veo). Sub-actions: 'generate' (gera imagem completa imediato — prompt+model+aspectRatio+size; suporta mode=edit/variation e MULTI-REFERÊNCIA: combine até 4 imagens como referência numa geração só, igual ao modal 'Selecionar Referência' do web — via referenceImageUrls (sua galeria + Acervo + personagens públicos de sapiens_character) e/ou sourceImageIds (ids da sua galeria); refs valem pros modelos robustos nano-banana-2/gpt-image-2-*/grok-2-image
|
|
49
|
+
description: "Operações de imagem via Sapiens (Gemini, Azure gpt-image-2, Grok, Seedream/ByteDance, Veo). Sub-actions: 'generate' (gera imagem completa imediato — prompt+model+aspectRatio+size; suporta mode=edit/variation e MULTI-REFERÊNCIA: combine até 4 imagens como referência numa geração só, igual ao modal 'Selecionar Referência' do web — via referenceImageUrls (sua galeria + Acervo + personagens públicos de sapiens_character) e/ou sourceImageIds (ids da sua galeria); refs valem pros modelos robustos nano-banana-2/gpt-image-2-*/grok-2-image*/seedream-*), 'request_generation' (cria APENAS row pendente em generatedImages + debita créditos — pra modelos sapiens-video-* ANTES de sapiens_shorts/sapiens_video; whitelist, rate limit 3/min), 'compose' (combina persona+screen via Gemini pra app-demo Shorts; 25 sinapses, rate limit 10/min). generate=image one-shot, request_generation=criar row video, compose=montar start frame app-demo. TEMPLATE: passe templateSlug numa generate pra usar um super-prompt travado da casa — o `prompt` vira só a CENA (quem + pose + objeto-conceito) e o template embrulha estilo+fundo+enquadramento+ref de traço. 'retrato-sapiens-v1' = retrato editorial cartoon de um personagem no grid verde Sapiens (mesma 'mão' dos artigos); sem ref própria, injeta a Helen como âncora de traço (passar referenceImageUrls troca quem aparece). Mutuamente exclusivo com brandSlug. Sub-action 'models' (sem custo, sem login): lista o catálogo vivo (modelos ativos + preço atual com override admin + maxResolution + se aceita referência) pra descobrir modelo/preço em vez de chutar. NOTA: generate é SÍNCRONA e cobra ao concluir; modelo pesado (Pro, gpt-image-2-high, Grok quality, 2K/4K) cai na REGRA DO TIMEOUT (cheque sapiens_gallery action=list antes de repetir, evita cobrança dupla).",
|
|
48
50
|
schema: imageSchema,
|
|
49
51
|
handler: image,
|
|
50
52
|
},
|
|
@@ -53,13 +55,18 @@ export const TOOLS = {
|
|
|
53
55
|
schema: metaSchema,
|
|
54
56
|
handler: meta,
|
|
55
57
|
},
|
|
58
|
+
sapiens_skill: {
|
|
59
|
+
description: "As SKILLS da casa: o passo a passo travado de cada fluxo do Sapiens, servido pelo próprio connector (sem login, sem custo, sem rede). LEIA A SKILL ANTES DE OPERAR O FLUXO — é mais barato que errar uma geração que cobra Sinapses. Sub-actions: 'list' (índice: slug + quando usar cada uma), 'get' (o SKILL.md inteiro, passe name=<slug>). Slugs: primeiros-passos (porta de entrada e as 4 armadilhas: timeout que já cobrou, disjuntor anti-loop, saldo, sessão), voz-da-casa (DNA editorial, leia antes de redigir qualquer texto publicável), musica (Musicator em 4 passos + efeito sonoro), video (qual modelo, iterar barato no Mini, storyboard por referência, sonorize), imagem (prompt full-bleed na régua da casa, multi-referência, templates), studio (gerar na identidade da pessoa vs base, e a Emancipação), tirinha (2 fases, a imagem sai SEM texto), forum-comunidade (mídia estruturada, menção, tese), companhia (o Sintético veste você), trilhas (missão e prova). O mesmo conteúdo também sai como resource MCP em skill://sapiens/<slug>/SKILL.md pra quem lê resources.",
|
|
60
|
+
schema: skillSchema,
|
|
61
|
+
handler: skill,
|
|
62
|
+
},
|
|
56
63
|
sapiens_repertorio: {
|
|
57
64
|
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.",
|
|
58
65
|
schema: repertorioSchema,
|
|
59
66
|
handler: repertorio,
|
|
60
67
|
},
|
|
61
68
|
sapiens_gallery: {
|
|
62
|
-
description: "Browse e
|
|
69
|
+
description: "Browse, publicação e upload das imagens do user. Sub-actions: list (últimas N imagens, com prompt/model/url + isPublic), 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), unpublish (volta a privada), upload (traz pra galeria uma peça gerada FORA da casa, por sourceUrl https, filePath local ou base64; ADMIN por ora). 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.",
|
|
63
70
|
schema: gallerySchema,
|
|
64
71
|
handler: gallery,
|
|
65
72
|
},
|
|
@@ -196,52 +203,26 @@ export const TOOLS = {
|
|
|
196
203
|
// disjuntor anti-loop). Mantém curto de propósito: viaja em todo handshake.
|
|
197
204
|
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.
|
|
198
205
|
|
|
206
|
+
AS SKILLS DA CASA (leia ANTES de operar, não improvise o fluxo):
|
|
207
|
+
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.
|
|
208
|
+
- sapiens_skill action=list -> o índice (slug + quando usar cada uma).
|
|
209
|
+
- sapiens_skill action=get name=<slug> -> a skill inteira.
|
|
210
|
+
Slugs: ${skillMenuLine()}
|
|
211
|
+
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.
|
|
212
|
+
Cliente que lê resources MCP acha o MESMO conteúdo em skill://sapiens/<slug>/SKILL.md.
|
|
213
|
+
|
|
199
214
|
REGRA DE OURO:
|
|
200
215
|
- 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.
|
|
201
216
|
- 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.
|
|
202
217
|
- 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.
|
|
203
|
-
- 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.
|
|
204
|
-
- 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; pipeline: /dashboard/admin/content).
|
|
218
|
+
- 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.
|
|
219
|
+
- 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).
|
|
205
220
|
- "sessionToken expirado" = refaça login: sapiens_meta action=login com o código de sapiensinteticos.com/conectar-claude.
|
|
206
|
-
- sapiens_meta action=formats devolve os schemas por formato; action=whoami diz tier (user/admin) + saldo.
|
|
207
|
-
|
|
208
|
-
FLUXOS QUE NÃO PODEM ERRAR:
|
|
209
|
-
|
|
210
|
-
Música (sapiens_musicator) é fluxo de 4 passos, EM ORDEM:
|
|
211
|
-
1. create exige title (≥3) + context (≥20 chars, o tema/ângulo) + direction (gênero/mood). Custo 0, devolve trackId.
|
|
212
|
-
2. lyrics passe trackId + context pra GRAVAR a letra na track (300 Sinapses). NUNCA chame lyrics sem title+context.
|
|
213
|
-
3. render passe o trackId pronto pra sintetizar o áudio (3000 Sinapses, assíncrono, 3/min).
|
|
214
|
-
4. get passe o trackId e fique polando o status até 'ready' (ou 'failed').
|
|
215
|
-
Pular pro lyrics/render sem create, ou sem os campos, sempre falha.
|
|
216
|
-
|
|
217
|
-
Efeito sonoro (sapiens_stock_audio): primeiro procure pronto (action=list kind=sfx, grátis). Não achou, action=generate: prompt + durationSeconds (1-15, default 5) + provider ('mirelo' padrão | 'elevenlabs' premium, custo por segundo) + promptInfluence opcional (0..1, só elevenlabs: fidelidade ao texto). Assíncrono: devolve generationId, acompanhe com action=generation-status até 'ready' (audioUrl) ou 'failed' (reembolsa sozinho). Efeito é CURTO; música inteira é no sapiens_musicator, não aqui.
|
|
218
|
-
|
|
219
|
-
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.
|
|
220
|
-
|
|
221
|
-
Vídeo (sapiens_video) action=create EXIGE 'model': sapiens-video-seedance | seedance-2-fast | seedance-2-mini | kling | wan | kling-motion | lite | fast | quality | omni. Sem model, falha de cara. Os três Seedance 2.0 têm o MESMO repertório (referência, frame inicial/final, vídeo de movimento, áudio nativo): o 'sapiens-video-seedance-2-mini' custa METADE do padrão e o '-2-fast' 20% menos, ambos com teto 720p. Iterar no Mini e fechar no padrão economiza Sinapses do usuário sem trocar de fluxo. 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: ela cai na SUA timeline de vídeos (quem extrai vira dono, igual ao recorte de fundo na imagem) e vira ficha 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.
|
|
222
|
-
|
|
223
|
-
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.
|
|
224
|
-
|
|
225
|
-
SEU STUDIO vs geração BASE (não confunda, é o ponto): o usuário tem UM "Meu Studio", único, que o servidor resolve pela SESSÃO. Você NUNCA passa id de studio: é sempre o studio dele. É a identidade configurada dele: marca + personagem-operador + por ferramenta os presets e a "vibe" (o estilo afinado).
|
|
226
|
-
- Criar SEGUINDO o studio (na cara dele): sapiens_image com useStudio=true. O servidor acha o studio e aplica marca + personagem + vibe + presets sozinho (o que você passar explícito vence). O retorno traz studioApplied=true. Use quando ele diz "no meu studio", "na minha marca", "do meu jeito", "como sempre".
|
|
227
|
-
- Geração AVULSA (Sapiens base, sem a identidade dele): sapiens_image SEM useStudio (studioApplied=false). Pra teste solto ou pedido fora da identidade. Não misture: ou é studio, ou é base.
|
|
228
|
-
- Antes de criar no studio, cheque com sapiens_studios action=mine (nível, marca, operador, ferramentas, a vibe da imagem). Se você pediu useStudio e voltar studioApplied=false, o user não tem studio montado: avise e aponte /dashboard/studio. Gerar no studio faz ele evoluir de nível.
|
|
229
|
-
- EMANCIPAÇÃO (Nível 3, o passo grande): quando o membro quer a casa PRÓPRIA dele (site/produto próprio, FORA do Sapiens), use sapiens_studios action=emancipar. Ele devolve o blueprint pra VOCÊ construir na infra DO MEMBRO (Vercel + Convex + domínio dele), começando pela Fundação e seguindo um módulo por vez (action=module module=<slug>). Confirme cada passo, nunca hospede no Sapiens, siga o gosto dele. É complexo: conduza com calma.
|
|
230
|
-
|
|
231
|
-
Tirinha / Comic (sapiens_pipeline format=tirinha + sapiens_image): fluxo de 2 FASES, decida o MODO antes de gerar pixel.
|
|
232
|
-
FASE 1 (roteiro): monte 3-6 painéis, cada um com a CENA visual e a FALA/legenda SEPARADAS. Salve em sapiens_pipeline create_production format=tirinha. O texto dos balões mora no payload (campos dialogue/caption), NUNCA dentro da imagem.
|
|
233
|
-
FASE 2 (imagem), escolha UM dos dois modos (pergunte ao user, ou decida pelo caso):
|
|
234
|
-
MODO A, tira inteira numa imagem só: UMA chamada sapiens_image com prompt em grade (descreva "Painel 1 (cima-esq): ...; Painel 2 (cima-dir): ...", peça grade NxM com calhas brancas finas, MESMO personagem/estilo/luz em todos os quadros). Mais barato, estilo mais travado, menos controle quadro a quadro.
|
|
235
|
-
MODO B, painel a painel (N imagens): UMA sapiens_image por painel, com character-lock passando o painel anterior + a ref da Helen em referenceImageUrls. Mais controle e consistência de enquadramento, custa N gerações.
|
|
236
|
-
REGRA DE OURO da tirinha: a imagem sai SEM texto (o estilo da casa é no-text e modelo de imagem erra letra). A fala vai SEPARADA como legenda: manda a imagem com a fala embaixo (ex no Telegram "Painel 1 · Helen: '...'"); os balões editáveis e a diagramação final ficam no comic-builder do dashboard (/experimentos/comic-builder). NÃO tente assar a fala dentro do prompt.
|
|
237
|
-
|
|
238
|
-
Mídia no Fórum (PADRÃO, faça sempre assim): a peça vai EMBEDADA num card estruturado, nunca como "vai ouvir/ver noutro lugar". Música publicada (sapiens_musicator action=publish) já cria a tese no Fórum com a faixa tocável, não escreva "tá no Acervo, ouça lá". Pra anexar mídia a uma tese sua: sapiens_forum action=post com mediaTrackId (faixa pronta sua → card de música) OU mediaUrl + mediaKind (video|image; arquivo da casa, ou link YouTube/Vimeo pra vídeo). NÃO cole a URL da mídia no corpo da tese, use o campo: o corpo é só texto.
|
|
239
|
-
|
|
240
|
-
Trilhas e Desafios (sapiens_trilhas): 'list'/'get' pra navegar as trilhas; 'list_challenges' mostra os Desafios ativos + seu status; 'claim_mission' (missionId + proofText) envia a prova pra revisão do dono — o crédito em Sinapses sai na aprovação dele, não na hora. Não prometa Sinapse na hora do claim.
|
|
221
|
+
- 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.
|
|
241
222
|
|
|
242
|
-
MODO COMPANHIA
|
|
223
|
+
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.
|
|
243
224
|
|
|
244
|
-
Voz da casa: 1ª pessoa, direto, anti-corporate, sem travessão. Pra bom entendedor, meia palavra basta.`;
|
|
225
|
+
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.`;
|
|
245
226
|
// Annotations MCP: título humano + dica read-only. São HINTS (não-confiáveis por
|
|
246
227
|
// spec): quem gateia de verdade continua o servidor (saldo, gate de admin,
|
|
247
228
|
// posse). Servem pro client AUTO-APROVAR leitura pura e PEDIR confirmação em
|
|
@@ -254,11 +235,17 @@ const READ_ONLY_TOOLS = new Set([
|
|
|
254
235
|
"sapiens_stock_video",
|
|
255
236
|
"sapiens_atlas",
|
|
256
237
|
"sapiens_reference",
|
|
238
|
+
"sapiens_skill",
|
|
257
239
|
]);
|
|
240
|
+
// A única tool que NÃO fala com o backend: as skills são conteúdo estático do
|
|
241
|
+
// pacote. openWorldHint=false é honesto e ajuda o client a auto-aprovar sem
|
|
242
|
+
// nem pensar (é leitura de documentação, não uma ação na conta de ninguém).
|
|
243
|
+
const CLOSED_WORLD_TOOLS = new Set(["sapiens_skill"]);
|
|
258
244
|
const TOOL_TITLES = {
|
|
259
245
|
sapiens_pipeline: "Pipeline de Conteúdo",
|
|
260
246
|
sapiens_image: "Gerar Imagem",
|
|
261
247
|
sapiens_meta: "Conta & Utilitários",
|
|
248
|
+
sapiens_skill: "Skills da Casa",
|
|
262
249
|
sapiens_repertorio: "Repertório",
|
|
263
250
|
sapiens_gallery: "Galeria de Imagens",
|
|
264
251
|
sapiens_community: "Chat da Comunidade",
|
|
@@ -317,7 +304,7 @@ export function buildToolList(tier) {
|
|
|
317
304
|
annotations: {
|
|
318
305
|
title: TOOL_TITLES[name] ?? name,
|
|
319
306
|
readOnlyHint: READ_ONLY_TOOLS.has(name),
|
|
320
|
-
openWorldHint:
|
|
307
|
+
openWorldHint: !CLOSED_WORLD_TOOLS.has(name),
|
|
321
308
|
},
|
|
322
309
|
}));
|
|
323
310
|
}
|
package/dist/remote.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createMcpHandler, withMcpAuth } from "mcp-handler";
|
|
2
|
-
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
2
|
+
import { CallToolRequestSchema, GetPromptRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
3
3
|
import { buildToolList, callTool, SAPIENS_INSTRUCTIONS, } from "./registry.js";
|
|
4
4
|
import { getPrompt, listPrompts } from "./prompts.js";
|
|
5
|
+
import { listSkillResources, readSkillResource } from "./skills.js";
|
|
5
6
|
import { convexMutation, convexQuery, getSessionToken, runWithSessionToken, } from "./convexClient.js";
|
|
6
7
|
import { MCP_VERSION } from "./version.js";
|
|
7
8
|
/**
|
|
@@ -19,7 +20,8 @@ import { MCP_VERSION } from "./version.js";
|
|
|
19
20
|
* cache por token: inválida = erro claro, sem catálogo e sem dispatch
|
|
20
21
|
* (fail-closed). Transiente (backend fora) = erro pedindo retry.
|
|
21
22
|
* 3. Saldo: conta sem Sinapses não executa tool nenhuma exceto sapiens_meta
|
|
22
|
-
* (diagnóstico: whoami/credits/subscription pra pessoa entender o porquê)
|
|
23
|
+
* (diagnóstico: whoami/credits/subscription pra pessoa entender o porquê)
|
|
24
|
+
* e sapiens_skill (as skills da casa, conteúdo estático do pacote).
|
|
23
25
|
* Admin (dono) passa sempre.
|
|
24
26
|
*
|
|
25
27
|
* Identidade: SEMPRE o bearer do header Authorization (o sessionToken de 30
|
|
@@ -172,6 +174,18 @@ function initServer(mcp) {
|
|
|
172
174
|
s.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
173
175
|
prompts: listPrompts(),
|
|
174
176
|
}));
|
|
177
|
+
// Skills da casa como resource, em paridade com o stdio. Conteúdo estático do
|
|
178
|
+
// pacote: sem backend, sem custo, e de propósito FORA do gate de saldo (quem
|
|
179
|
+
// ficou sem Sinapse ainda consegue ler como a casa funciona).
|
|
180
|
+
s.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
181
|
+
resources: listSkillResources(),
|
|
182
|
+
}));
|
|
183
|
+
s.setRequestHandler(ReadResourceRequestSchema, async (req) => {
|
|
184
|
+
const found = readSkillResource(req.params.uri);
|
|
185
|
+
if (!found)
|
|
186
|
+
throw new Error(`Resource desconhecido: ${req.params.uri}`);
|
|
187
|
+
return found;
|
|
188
|
+
});
|
|
175
189
|
s.setRequestHandler(GetPromptRequestSchema, async (req) => getPrompt(req.params.name, (req.params.arguments ?? {})));
|
|
176
190
|
s.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
177
191
|
const name = req.params.name;
|
|
@@ -184,8 +198,9 @@ function initServer(mcp) {
|
|
|
184
198
|
return remoteSessionError(String(args.action));
|
|
185
199
|
}
|
|
186
200
|
// O gate do dono: login válido + Sinapses na conta pra executar qualquer
|
|
187
|
-
// coisa.
|
|
188
|
-
// DESCOBRE que está sem saldo)
|
|
201
|
+
// coisa. Duas tools ficam de fora do gate de saldo: sapiens_meta (é como a
|
|
202
|
+
// pessoa DESCOBRE que está sem saldo) e sapiens_skill (documentação
|
|
203
|
+
// estática, não toca o backend nem gera nada). Admin passa sempre.
|
|
189
204
|
const session = await resolveRemoteSession(token);
|
|
190
205
|
if (session.status === "invalid") {
|
|
191
206
|
return isErrorResult(MSG_INVALID(session.reason));
|
|
@@ -195,7 +210,8 @@ function initServer(mcp) {
|
|
|
195
210
|
}
|
|
196
211
|
if (session.tier !== "admin" &&
|
|
197
212
|
session.balance <= 0 &&
|
|
198
|
-
name !== "sapiens_meta"
|
|
213
|
+
name !== "sapiens_meta" &&
|
|
214
|
+
name !== "sapiens_skill") {
|
|
199
215
|
return isErrorResult(MSG_NO_BALANCE);
|
|
200
216
|
}
|
|
201
217
|
const t0 = Date.now();
|
|
@@ -228,7 +244,7 @@ export function createSapiensRemoteHandler(opts) {
|
|
|
228
244
|
const tokenLimiter = new WindowLimiter(limits.perTokenPerMin, 60_000);
|
|
229
245
|
const base = createMcpHandler(initServer, {
|
|
230
246
|
serverInfo: { name: "mcp-sapiens", version: MCP_VERSION },
|
|
231
|
-
capabilities: { tools: {}, prompts: {} },
|
|
247
|
+
capabilities: { tools: {}, prompts: {}, resources: {} },
|
|
232
248
|
instructions: SAPIENS_INSTRUCTIONS,
|
|
233
249
|
}, {
|
|
234
250
|
basePath: opts.basePath,
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SKILLS DA CASA servidas pelo próprio connector.
|
|
3
|
+
*
|
|
4
|
+
* O problema que isto resolve: as skills do repo (`skills/`, 40+) só existem
|
|
5
|
+
* pra quem instala o PLUGIN no Claude Code. No claude.ai, na Helen, no Cursor,
|
|
6
|
+
* no Gemini CLI, o connector chegava pelado, e todo o saber de fluxo tinha que
|
|
7
|
+
* viajar dentro do SAPIENS_INSTRUCTIONS, injetado em TODO handshake de TODA
|
|
8
|
+
* conversa, mesmo quando a pessoa só quer ver saldo.
|
|
9
|
+
*
|
|
10
|
+
* Aqui o saber vira conteúdo SOB DEMANDA, servido de dois jeitos ao mesmo
|
|
11
|
+
* tempo (o cliente usa o que souber):
|
|
12
|
+
* 1. resources MCP -> skill://sapiens/<name>/SKILL.md
|
|
13
|
+
* 2. tool sapiens_skill (action=list|get), pra client que não lê resources
|
|
14
|
+
*
|
|
15
|
+
* A instruction do handshake fica magra e guarda só o que evita DANO (custo,
|
|
16
|
+
* timeout, disjuntor, login). O que aprofunda mora aqui e é puxado quando a
|
|
17
|
+
* conversa pede. Handshake curto, saber fundo: um não paga pelo outro.
|
|
18
|
+
*
|
|
19
|
+
* O corpo é markdown no formato aberto de skill (frontmatter + instruções),
|
|
20
|
+
* então dá pra copiar direto pra uma pasta `skills/` de qualquer agente.
|
|
21
|
+
*
|
|
22
|
+
* REGRA AO EDITAR: skill é sobre USAR a plataforma (o membro é o público). Cerne
|
|
23
|
+
* interno da casa (Convex, deploy, release do MCP, auditoria) NÃO entra aqui:
|
|
24
|
+
* isso é do plugin local do dono, não do connector público.
|
|
25
|
+
*/
|
|
26
|
+
export const SKILL_URI_PREFIX = "skill://sapiens/";
|
|
27
|
+
/** URI canônica do resource de uma skill. */
|
|
28
|
+
export function skillUri(name) {
|
|
29
|
+
return `${SKILL_URI_PREFIX}${name}/SKILL.md`;
|
|
30
|
+
}
|
|
31
|
+
/** Extrai o slug de uma URI de skill; null se não for uma. */
|
|
32
|
+
export function skillNameFromUri(uri) {
|
|
33
|
+
if (!uri.startsWith(SKILL_URI_PREFIX))
|
|
34
|
+
return null;
|
|
35
|
+
const rest = uri.slice(SKILL_URI_PREFIX.length);
|
|
36
|
+
const name = rest.replace(/\/SKILL\.md$/, "").replace(/\/$/, "");
|
|
37
|
+
return name.length > 0 && SKILLS[name] ? name : null;
|
|
38
|
+
}
|
|
39
|
+
const SKILL_LIST = [
|
|
40
|
+
{
|
|
41
|
+
name: "primeiros-passos",
|
|
42
|
+
title: "Primeiros passos e armadilhas",
|
|
43
|
+
description: "A porta de entrada e as quatro armadilhas que fazem o operador queimar Sinapses do usuário à toa (timeout, disjuntor, saldo, sessão). Puxe no primeiro contato ou quando algo der errado sem explicação.",
|
|
44
|
+
body: `## A porta de entrada
|
|
45
|
+
|
|
46
|
+
Primeiro contato, ou "o que você faz?" / "como começo?": chame \`sapiens_meta action=start\` e mostre o resultado NA SUA VOZ. Sem login ele ensina a conectar; logado, traz saldo e os primeiros poderes com exemplo pronto.
|
|
47
|
+
|
|
48
|
+
Logo após um login que deu certo, chame \`start\` na sequência. O recém-chegado não sabe o que pedir: guie a primeira jogada sem ele precisar perguntar.
|
|
49
|
+
|
|
50
|
+
Não despeje a lista inteira de tools. O start guia.
|
|
51
|
+
|
|
52
|
+
## As quatro armadilhas
|
|
53
|
+
|
|
54
|
+
**1. Timeout que já cobrou.** Toda geração SÍNCRONA (imagem pesada, artigo, mega-gráfico, carrossel) pode estourar o teto de ~120s do cliente e voltar 'Timeout' MESMO tendo gerado e debitado. Nunca repita às cegas. Confira antes onde o resultado teria caído:
|
|
55
|
+
|
|
56
|
+
| O que gerou | Onde conferir |
|
|
57
|
+
|---|---|
|
|
58
|
+
| imagem | \`sapiens_gallery action=list\` |
|
|
59
|
+
| artigo do perfil | \`sapiens_write action=list\` |
|
|
60
|
+
| carrossel | \`sapiens_pipeline action=list_carousels\` |
|
|
61
|
+
| pipeline | /dashboard/admin/content |
|
|
62
|
+
|
|
63
|
+
**2. Disjuntor anti-loop.** Três falhas seguidas no MESMO tool fazem o cliente marcar o servidor como "unreachable" por ~56s. Parece que "o MCP caiu", mas foi argumento faltando. Quando um tool volta erro de validação ("exige X", "falta Y"), LEIA o erro e refaça a chamada COM o que falta. Nunca repita a chamada idêntica que falhou.
|
|
64
|
+
|
|
65
|
+
**3. Saldo.** Antes de gerar algo caro (imagem, música, vídeo), cheque com \`sapiens_meta action=credits\` ou \`action=subscription\`. Saldo baixo, avise ANTES de gastar. Vídeo é o mais caro da casa: confirme com a pessoa antes de disparar.
|
|
66
|
+
|
|
67
|
+
**4. Sessão.** "sessionToken expirado" significa refazer login: \`sapiens_meta action=login\` com o código de sapiensinteticos.com/conectar-claude.
|
|
68
|
+
|
|
69
|
+
## Utilitários que evitam chute
|
|
70
|
+
|
|
71
|
+
- \`sapiens_meta action=whoami\`: tier (user/admin) e saldo.
|
|
72
|
+
- \`sapiens_meta action=formats\`: os schemas por formato.
|
|
73
|
+
- \`sapiens_meta action=version\`: qual versão está rodando de verdade e se é a última. Não exige login.
|
|
74
|
+
- \`sapiens_image action=models\` e \`sapiens_video action=models\`: catálogo vivo com o preço ATUAL. Consulte em vez de chutar custo.`,
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "voz-da-casa",
|
|
78
|
+
title: "A voz da casa",
|
|
79
|
+
description: "O DNA editorial Sapiens: como escrever qualquer texto que sai com a marca (post, tese, artigo, legenda, microcopy). Puxe ANTES de redigir qualquer coisa que vai ser publicada.",
|
|
80
|
+
body: `## A régua
|
|
81
|
+
|
|
82
|
+
Primeira pessoa. Opinião explícita. Conversa, não palestra. Analogia concreta antes de abstração.
|
|
83
|
+
|
|
84
|
+
**Sem travessão.** O em-dash é proibido no DNA Sapiens. Troque por vírgula, ponto, dois-pontos, parênteses, ou reescreva a frase.
|
|
85
|
+
|
|
86
|
+
**Para bom entendedor, meia palavra basta.** Respeita a inteligência de quem lê: não overlegenda, não explica o óbvio, não repete o que o contexto já diz, não fecha toda ideia com frase-resumo. Corta a frase a mais. É editorial, não TV aberta.
|
|
87
|
+
|
|
88
|
+
## O que evitar
|
|
89
|
+
|
|
90
|
+
- Linguagem corporativa ("soluções", "alavancar", "jornada", "ecossistema" solto).
|
|
91
|
+
- Tom jornalístico neutro. A casa tem opinião, assume.
|
|
92
|
+
- "Neste artigo vamos abordar", "vamos explorar", "sem mais delongas".
|
|
93
|
+
- Tom influencer, promessa de atalho mágico, receita pronta.
|
|
94
|
+
- Fechar tudo com CTA. O texto termina quando a ideia termina.
|
|
95
|
+
|
|
96
|
+
## O que manter
|
|
97
|
+
|
|
98
|
+
- Léxico próprio: borderless, degen, farmei, "ombros dos gigantes", "fala ai".
|
|
99
|
+
- Jargão cripto e tech, sem pedir desculpa e sem glossário defensivo.
|
|
100
|
+
- Framework nomeado mais analogia concreta. Antes de inventar nome novo, reusa o vocabulário canônico da casa.
|
|
101
|
+
|
|
102
|
+
## Estrutura padrão de peça didática
|
|
103
|
+
|
|
104
|
+
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.`,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "musica",
|
|
108
|
+
title: "Música e efeito sonoro",
|
|
109
|
+
description: "O fluxo de 4 passos do Musicator (create, lyrics, render, get) e a diferença entre música e efeito sonoro. Puxe ANTES de qualquer sapiens_musicator ou sapiens_stock_audio, pular passo sempre falha.",
|
|
110
|
+
body: `## Musicator: 4 passos, nesta ordem
|
|
111
|
+
|
|
112
|
+
1. **create**: exige \`title\` (>=3 chars) e \`context\` (>=20 chars, o tema/ângulo) e \`direction\` (gênero/mood). Custo 0. Devolve \`trackId\`.
|
|
113
|
+
2. **lyrics**: passe \`trackId\` e \`context\` pra gravar a letra na track (300 Sinapses). Nunca chame lyrics sem title e context.
|
|
114
|
+
3. **render**: passe o \`trackId\` pronto pra sintetizar o áudio (3000 Sinapses, assíncrono, teto de 3/min).
|
|
115
|
+
4. **get**: passe o \`trackId\` e vá polando o status até 'ready' (ou 'failed').
|
|
116
|
+
|
|
117
|
+
Pular pro lyrics ou render sem create, ou sem os campos, falha sempre. É o erro número um deste fluxo.
|
|
118
|
+
|
|
119
|
+
Depois de pronta: \`action=publish\` põe a faixa no Acervo da Comunidade (só admin/dono, custo 0, idempotente). A publicação já nasce com player no Fórum, veja a skill \`forum\`.
|
|
120
|
+
|
|
121
|
+
## Efeito sonoro é outra coisa
|
|
122
|
+
|
|
123
|
+
Efeito é CURTO. Música inteira é no Musicator, não aqui.
|
|
124
|
+
|
|
125
|
+
1. Procure pronto primeiro: \`sapiens_stock_audio action=list kind=sfx\`. Grátis.
|
|
126
|
+
2. Não achou: \`action=generate\` com \`prompt\` e \`durationSeconds\` (1 a 15, default 5) e \`provider\` ('mirelo' padrão 30 Sinapses/s mín 60, 'elevenlabs' premium 60/s mín 120). \`promptInfluence\` (0..1) só vale no elevenlabs.
|
|
127
|
+
3. É assíncrono: devolve \`generationId\`, acompanhe com \`action=generation-status\` até 'ready' (traz audioUrl) ou 'failed' (reembolsa sozinho).
|
|
128
|
+
|
|
129
|
+
## Sonorizar um clipe
|
|
130
|
+
|
|
131
|
+
Dar som a um vídeo SEU já pronto é \`sapiens_video action=sonorize\`, não é aqui. Veja a skill \`video\`.`,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: "video",
|
|
135
|
+
title: "Vídeo: escolher modelo sem queimar Sinapses",
|
|
136
|
+
description: "Qual modelo de vídeo usar, como iterar barato, referências e storyboard, sonorizar, e o que são os Vídeos Programáticos. Puxe ANTES de sapiens_video, vídeo é a operação mais cara da casa.",
|
|
137
|
+
body: `## Antes de tudo
|
|
138
|
+
|
|
139
|
+
\`action=create\` EXIGE \`model\`. Sem model, falha de cara. Vídeo é o mais caro: confirme com a pessoa antes de disparar.
|
|
140
|
+
|
|
141
|
+
\`action=models\` (sem custo, sem login) lista os modelos ativos com preço-piso, durações, resoluções e disponibilidade. Consulte em vez de chutar.
|
|
142
|
+
|
|
143
|
+
## Iterar barato, fechar caro
|
|
144
|
+
|
|
145
|
+
Os três Seedance 2.0 têm o MESMO repertório (referência, frame inicial e final, vídeo de movimento, áudio nativo):
|
|
146
|
+
|
|
147
|
+
| Modelo | Custo | Teto |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| \`sapiens-video-seedance-2-mini\` | metade do padrão | 720p |
|
|
150
|
+
| \`sapiens-video-seedance-2-fast\` | 20% menos | 720p |
|
|
151
|
+
| \`sapiens-video-seedance\` | padrão | 1080p |
|
|
152
|
+
|
|
153
|
+
Itere enquadramento e prompt no Mini, feche no padrão quando o take estiver certo. Economiza Sinapses da pessoa sem trocar de fluxo. Pedir 1080p nos dois de cima entrega e cobra 720p.
|
|
154
|
+
|
|
155
|
+
## Os outros modelos
|
|
156
|
+
|
|
157
|
+
- \`sapiens-video-kling\`: Kling 3.0 Pro, anima imagem, 3 a 15s, som opcional.
|
|
158
|
+
- \`sapiens-video-wan\`: WAN 2.5, imagem que fala ou canta, com lip-sync, 5 ou 10s.
|
|
159
|
+
- \`sapiens-video-kling-motion\`: passa o movimento de um vídeo pra uma imagem. Precisa de pessoa com tronco visível na imagem E no vídeo.
|
|
160
|
+
- \`sapiens-video-shot-mimic\`: recria o plano, a câmera e os cortes de um vídeo de referência como cena nova.
|
|
161
|
+
- \`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.
|
|
162
|
+
- \`sapiens-video-lite\` / \`-fast\` / \`-quality\`: Veo 3.1.
|
|
163
|
+
|
|
164
|
+
## Fluxo storyboard (o que dá o melhor resultado)
|
|
165
|
+
|
|
166
|
+
Até 4 imagens de REFERÊNCIA via \`referenceImageIds\` / \`referenceImageUrls\` / \`referenceImagePaths\` guiam estilo, personagem e composição SEM virar o primeiro frame.
|
|
167
|
+
|
|
168
|
+
1. Gere a folha de key poses com \`sapiens_image templateSlug='storyboard-sapiens-v1'\`.
|
|
169
|
+
2. Passe folha e personagem como refs num text-to-video.
|
|
170
|
+
3. Descreva o take contínuo no prompt, citando as refs por descrição e mandando ignorar o traço do sketch.
|
|
171
|
+
|
|
172
|
+
Dá pra somar 1 vídeo de movimento via \`referenceVideoUrls\` (até 15s, host da casa): a coreografia e a câmera do clipe guiam o take.
|
|
173
|
+
|
|
174
|
+
Frame inicial e final: \`startImageId\`/\`endImageId\` (galeria) ou \`startImageUrl\`/\`endImageUrl\`. Arquivo local (\`startImagePath\`) só funciona no MCP instalado via stdio, nunca no remoto. Suporte a frame final varia por modelo.
|
|
175
|
+
|
|
176
|
+
## É assíncrono
|
|
177
|
+
|
|
178
|
+
\`create\` cria o row, debita e volta NA HORA com \`{imageId, status:'rendering'}\`. Acompanhe com \`action=status imageId=<id>\` até 'completed' (traz a url) ou 'error'/'blocked'.
|
|
179
|
+
|
|
180
|
+
NÃO chame create de novo enquanto renderiza: cria outro vídeo e cobra de novo. Falha de provider refunda sozinha.
|
|
181
|
+
|
|
182
|
+
## Sonorizar
|
|
183
|
+
|
|
184
|
+
\`action=sonorize\` com \`imageId\` de um vídeo SEU já completed e \`prompt\` descrevendo o som da cena (ambiente, materiais, impactos). Sai uma VARIANTE nova com trilha sincronizada ao movimento (MMAudio, 20 Sinapses/s do clipe, mín 100), e o original fica intacto.
|
|
185
|
+
|
|
186
|
+
Sonorize sempre o ORIGINAL, nunca uma variante. Acompanhe com \`action=status\` no imageId NOVO que o sonorize devolve.
|
|
187
|
+
|
|
188
|
+
## Extrair a sombra (ADMIN)
|
|
189
|
+
|
|
190
|
+
\`action=shadows\` com \`videoUrl\` (URL pública) e \`title\` extrai o mapa de profundidade de um vídeo: 200 Sinapses/segundo, com refund na falha. Passe \`durationSec\` quando souber, pra cobrar proporcional; sem ela vai no flat ~2000.
|
|
191
|
+
|
|
192
|
+
A sombra cai na SUA timeline de vídeos (quem extrai vira dono) e vira ficha no Acervo como driving reutilizável pro Shot Mimic e o Kling Motion. \`action=shadows-list\` lista as prontas.
|
|
193
|
+
|
|
194
|
+
## Vídeos Programáticos são outra coisa
|
|
195
|
+
|
|
196
|
+
A mesa de filmes-de-código da casa (/experimentos/films) tem cinco tipos: demo (UI clonada mais câmera), aula-tour (telas reais mais narração), essay (fita-ensaio abstrata), tipografia-musical (a música dirige o corte) e dataviz (números no tempo).
|
|
197
|
+
|
|
198
|
+
O RENDER não sai deste connector: um agente local no repo da casa produz o mp4. Mas o CICLO na plataforma fecha por aqui, sem custo: \`film-list\`, \`film-get\`, \`film-upsert\`, \`film-status\`, \`film-publish\`. Membro pedindo "fita-ensaio" ou "demo film": aponte pra tela e pro agente local.`,
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
name: "imagem",
|
|
202
|
+
title: "Imagem na régua da casa",
|
|
203
|
+
description: "Como escrever prompt de imagem que sai com a cara do Sapiens (full-bleed, sujeito oversized), templates travados, e multi-referência. Puxe ANTES de sapiens_image action=generate.",
|
|
204
|
+
body: `## A regra do prompt
|
|
205
|
+
|
|
206
|
+
**Full-bleed. Sujeito oversized, ocupando 70% ou mais do frame.** Sem moldura, sem margem, sem cena pequena em paisagem vasta.
|
|
207
|
+
|
|
208
|
+
Proibidos no prompt: "tarot card illustration", "intimate scale", "card-style portrait", "watercolor portrait of", "small subject in vast landscape". São os padrões que sabotam o resultado e entregam imagem genérica.
|
|
209
|
+
|
|
210
|
+
## O básico
|
|
211
|
+
|
|
212
|
+
\`action=generate\` com \`prompt\`, \`model\` e \`aspectRatio\`. \`action=models\` (sem custo, sem login) lista o catálogo vivo com preço atual, resolução máxima e se o modelo aceita referência.
|
|
213
|
+
|
|
214
|
+
## Multi-referência
|
|
215
|
+
|
|
216
|
+
Combine até 4 imagens como referência numa geração só:
|
|
217
|
+
|
|
218
|
+
- \`referenceImageUrls\`: sua galeria, o Acervo, personagens públicos (descubra em \`sapiens_character\`).
|
|
219
|
+
- \`sourceImageIds\`: ids da sua galeria.
|
|
220
|
+
|
|
221
|
+
Refs valem pros modelos robustos (nano-banana-2, gpt-image-2-*, grok-2-image*). Use \`sapiens_reference\` pra achar a URL certa em vez de adivinhar.
|
|
222
|
+
|
|
223
|
+
## Templates travados
|
|
224
|
+
|
|
225
|
+
\`templateSlug\` aplica um super-prompt da casa: o seu \`prompt\` vira só a CENA (quem, que pose, que objeto-conceito) e o template embrulha estilo, fundo, enquadramento e referência de traço.
|
|
226
|
+
|
|
227
|
+
- \`retrato-sapiens-v1\`: retrato editorial cartoon no grid verde Sapiens, a mesma "mão" dos artigos. Sem ref própria, injeta a Helen como âncora de traço; passar \`referenceImageUrls\` troca quem aparece.
|
|
228
|
+
- \`storyboard-sapiens-v1\`: folha de key poses, a entrada do fluxo de vídeo.
|
|
229
|
+
|
|
230
|
+
\`templateSlug\` e \`brandSlug\` são mutuamente exclusivos.
|
|
231
|
+
|
|
232
|
+
## Cuidados
|
|
233
|
+
|
|
234
|
+
- \`generate\` é SÍNCRONA e cobra ao concluir. Modelo pesado (Pro, gpt-image-2-high, Grok quality, 2K/4K) cai na regra do timeout: cheque \`sapiens_gallery action=list\` antes de repetir, senão cobra duas vezes.
|
|
235
|
+
- \`request_generation\` NÃO gera imagem: só cria a row pendente e debita, pra modelos \`sapiens-video-*\` antes de chamar vídeo ou shorts.
|
|
236
|
+
- Uma imagem só vira pública (e ganha página indexável) com \`sapiens_gallery action=publish\`.
|
|
237
|
+
|
|
238
|
+
## Quando a pessoa quer "do jeito dela"
|
|
239
|
+
|
|
240
|
+
Aí não é geração base, é o Studio dela. Veja a skill \`studio\`.`,
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
name: "studio",
|
|
244
|
+
title: "Meu Studio e a Emancipação",
|
|
245
|
+
description: "A diferença entre gerar na identidade da pessoa (useStudio) e gerar na base Sapiens, e o que é a Emancipação de Nível 3. Puxe quando a pessoa disser 'do meu jeito', 'na minha marca', ou pedir casa própria.",
|
|
246
|
+
body: `## Studio vs base: não misture
|
|
247
|
+
|
|
248
|
+
A pessoa tem UM "Meu Studio", único, que o servidor resolve pela SESSÃO. Você NUNCA passa id de studio: é sempre o dela. É a identidade configurada: marca, personagem-operador, e por ferramenta os presets e a "vibe" (o estilo afinado).
|
|
249
|
+
|
|
250
|
+
**Seguindo o studio:** \`sapiens_image\` com \`useStudio=true\`. O servidor acha o studio e aplica marca, personagem, vibe e presets sozinho (o que você passar explícito vence). O retorno traz \`studioApplied=true\`.
|
|
251
|
+
|
|
252
|
+
Use quando ela disser "no meu studio", "na minha marca", "do meu jeito", "como sempre".
|
|
253
|
+
|
|
254
|
+
**Geração avulsa:** \`sapiens_image\` SEM \`useStudio\` (\`studioApplied=false\`). Pra teste solto ou pedido fora da identidade.
|
|
255
|
+
|
|
256
|
+
Ou é studio, ou é base. Não misture na mesma peça.
|
|
257
|
+
|
|
258
|
+
## Antes de criar no studio
|
|
259
|
+
|
|
260
|
+
\`sapiens_studios action=mine\` mostra nível, marca, operador, ferramentas e a vibe da imagem.
|
|
261
|
+
|
|
262
|
+
Se você pediu \`useStudio=true\` e voltou \`studioApplied=false\`, a pessoa não tem studio montado: avise e aponte /dashboard/studio. Gerar no studio faz ele evoluir de nível.
|
|
263
|
+
|
|
264
|
+
## Emancipação (Nível 3)
|
|
265
|
+
|
|
266
|
+
O passo grande: quando o membro quer a casa PRÓPRIA dele, site ou produto próprio, FORA do Sapiens.
|
|
267
|
+
|
|
268
|
+
\`sapiens_studios action=emancipar\` devolve o blueprint pra VOCÊ construir na infra DELE (Vercel, Convex e domínio dele), começando pela Fundação e seguindo um módulo por vez (\`action=module module=<slug>\`: fundacao, sapiens-connect, telegram, email, auth).
|
|
269
|
+
|
|
270
|
+
Confirme cada passo. Nunca hospede no Sapiens. Siga o gosto dele. É complexo: conduza com calma.`,
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
name: "tirinha",
|
|
274
|
+
title: "Tirinha e quadrinho",
|
|
275
|
+
description: "O fluxo de 2 fases da tirinha e a regra de ouro (a imagem sai SEM texto, a fala vai separada). Puxe ANTES de gerar qualquer quadrinho, assar a fala no prompt é o erro clássico.",
|
|
276
|
+
body: `## Duas fases. Decida o modo antes de gerar pixel.
|
|
277
|
+
|
|
278
|
+
### Fase 1: roteiro
|
|
279
|
+
|
|
280
|
+
Monte 3 a 6 painéis, cada um com a CENA visual e a FALA/legenda SEPARADAS. Salve em \`sapiens_pipeline action=create_production format=tirinha\`.
|
|
281
|
+
|
|
282
|
+
O texto dos balões mora no payload (campos \`dialogue\` e \`caption\`), NUNCA dentro da imagem.
|
|
283
|
+
|
|
284
|
+
### Fase 2: imagem, escolha um dos modos
|
|
285
|
+
|
|
286
|
+
**Modo A, tira inteira numa imagem só.** UMA chamada \`sapiens_image\` com prompt em grade: descreva "Painel 1 (cima-esquerda): ...; Painel 2 (cima-direita): ...", peça grade NxM com calhas brancas finas, e o MESMO personagem, estilo e luz em todos os quadros. Mais barato, estilo mais travado, menos controle quadro a quadro.
|
|
287
|
+
|
|
288
|
+
**Modo B, painel a painel.** UMA \`sapiens_image\` por painel, com character-lock: passe o painel anterior e a ref da Helen em \`referenceImageUrls\`. Mais controle e consistência de enquadramento, custa N gerações.
|
|
289
|
+
|
|
290
|
+
Pergunte à pessoa, ou decida pelo caso: tira curta de humor pede A, narrativa com continuidade pede B.
|
|
291
|
+
|
|
292
|
+
## A regra de ouro
|
|
293
|
+
|
|
294
|
+
**A imagem sai SEM texto.** O estilo da casa é no-text e modelo de imagem erra letra.
|
|
295
|
+
|
|
296
|
+
A fala vai SEPARADA, como legenda junto da imagem (por exemplo no Telegram: "Painel 1 · Helen: '...'"). Os balões editáveis e a diagramação final ficam no comic-builder do dashboard (/experimentos/comic-builder).
|
|
297
|
+
|
|
298
|
+
Não tente assar a fala dentro do prompt. Nunca funciona.`,
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
name: "forum-comunidade",
|
|
302
|
+
title: "Fórum, chat e mídia estruturada",
|
|
303
|
+
description: "Como postar tese, mencionar gente e anexar música ou vídeo do jeito certo (campo estruturado, nunca URL solta no corpo). Puxe antes de postar no Fórum ou no chat da Comunidade.",
|
|
304
|
+
body: `## Mídia é ESTRUTURADA. Padrão, não negociável.
|
|
305
|
+
|
|
306
|
+
A peça vai EMBEDADA num card, nunca como "vai ouvir noutro lugar". Nunca cole a URL da mídia no corpo da tese: o corpo é só texto.
|
|
307
|
+
|
|
308
|
+
\`sapiens_forum action=post\` com:
|
|
309
|
+
|
|
310
|
+
- \`mediaTrackId\`: uma faixa pronta sua, vira card de música tocável. O servidor confere a posse.
|
|
311
|
+
- \`mediaUrl\` mais \`mediaKind\` (\`video\` ou \`image\`): arquivo da casa, ou link YouTube/Vimeo pra vídeo. Sanitizado no servidor.
|
|
312
|
+
|
|
313
|
+
Música publicada por \`sapiens_musicator action=publish\` JÁ cria a tese no Fórum com a faixa tocável. Não escreva "tá no Acervo, ouça lá".
|
|
314
|
+
|
|
315
|
+
## Chat da Comunidade
|
|
316
|
+
|
|
317
|
+
\`sapiens_community action=send\`: você posta como intercessor da pessoa, e o servidor acrescenta o sufixo "· via Claude".
|
|
318
|
+
|
|
319
|
+
**Menção:** escreva \`@username\` no \`content\` e o servidor NOTIFICA quem foi citado (sino e Telegram). Descubra o username certo antes com \`action=participants\` (quem está na sala, já traz o \`mention\` pronto) ou \`action=search_users\` (autocomplete por parte do nome).
|
|
320
|
+
|
|
321
|
+
**Anexo do próprio acervo:** \`mediaAssetKind\` (track, video, film, comic) mais \`mediaAssetId\` monta um card da peça. Posse conferida no servidor.
|
|
322
|
+
|
|
323
|
+
**Tese:** \`asTese=true\` posta a fala como card de marca, pingável pro Fórum.
|
|
324
|
+
|
|
325
|
+
Reação é toggle de emoji, com allowlist: 👍 🔥 ❤️ 🚀 🤯
|
|
326
|
+
|
|
327
|
+
## Voz
|
|
328
|
+
|
|
329
|
+
Tudo que sai no Fórum e no chat segue o DNA editorial da casa. Puxe a skill \`voz-da-casa\` antes de redigir.`,
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "companhia",
|
|
333
|
+
title: "Modo Companhia (o Sintético veste você)",
|
|
334
|
+
description: "Como incorporar o Sintético do membro quando o companion está ativo, e como gravar diretriz no caderno do par. Puxe quando sapiens_meta trouxer um bloco companion ou companionInvite.",
|
|
335
|
+
body: `## Quando o companion está ativo
|
|
336
|
+
|
|
337
|
+
\`sapiens_meta action=start\` e \`action=whoami\` podem trazer um bloco \`companion\`. Se vier \`companion.active=true\`, INCORPORE aquele Sintético:
|
|
338
|
+
|
|
339
|
+
- Assuma a voz descrita em \`companion.voiceDirective\` em TODA fala.
|
|
340
|
+
- Abra a conversa com o oi dele.
|
|
341
|
+
- Se o cliente renderiza imagem, mostre \`companion.avatarUrl\` como card inline. No terminal cru, cite o link.
|
|
342
|
+
|
|
343
|
+
**O limite que importa:** você continua operando as ferramentas na conta do USUÁRIO. As Sinapses e a identidade são DELE. Você não vira a conta do Sintético, só empresta a voz.
|
|
344
|
+
|
|
345
|
+
## Quando vem companionInvite
|
|
346
|
+
|
|
347
|
+
A pessoa tem Sintonia mas pediu pra trabalhar sozinha. Mencione de leve que dá pra chamar o Sintético pro terminal (\`sapiens_meta action=companion mode=on\`). De leve, uma vez, sem insistir.
|
|
348
|
+
|
|
349
|
+
## Gravar diretriz
|
|
350
|
+
|
|
351
|
+
Quando a pessoa FIXAR uma diretriz na conversa ("sempre faça X", "grava isso", "de agora em diante Y"):
|
|
352
|
+
|
|
353
|
+
\`sapiens_sintetico action=remember text="<a diretriz>"\`
|
|
354
|
+
|
|
355
|
+
Grava no caderno do par e vira lei que o Sintético segue no site E no terminal. Confirme na voz dele.
|
|
356
|
+
|
|
357
|
+
## Sair de cena
|
|
358
|
+
|
|
359
|
+
Pessoa pedindo pra trabalhar sozinha, ou pro Sintético silenciar: \`sapiens_sintetico action=companion mode=off\`, despeça-se numa linha na voz dele, e volte a ser o operador neutro.
|
|
360
|
+
|
|
361
|
+
Sem bloco companion, opere na voz neutra da casa.`,
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
name: "trilhas",
|
|
365
|
+
title: "Trilhas e Desafios",
|
|
366
|
+
description: "Como navegar as trilhas e enviar prova de missão sem prometer crédito na hora. Puxe quando a pessoa falar de desafio, missão ou trilha.",
|
|
367
|
+
body: `## Navegar
|
|
368
|
+
|
|
369
|
+
- \`sapiens_trilhas action=list\` e \`action=get\`: as trilhas.
|
|
370
|
+
- \`action=list_challenges\`: os Desafios ativos e o status da pessoa em cada um.
|
|
371
|
+
|
|
372
|
+
## Enviar prova
|
|
373
|
+
|
|
374
|
+
\`action=claim_mission\` com \`missionId\` e \`proofText\` envia a prova pra revisão do dono.
|
|
375
|
+
|
|
376
|
+
**O crédito em Sinapses sai na APROVAÇÃO dele, não na hora.** Não prometa Sinapse no momento do claim. Diga que a prova foi enviada e que o crédito vem quando for aprovada.`,
|
|
377
|
+
},
|
|
378
|
+
];
|
|
379
|
+
export const SKILLS = Object.fromEntries(SKILL_LIST.map((s) => [s.name, s]));
|
|
380
|
+
/** Monta o SKILL.md completo (frontmatter do formato aberto + corpo). */
|
|
381
|
+
export function renderSkill(skill) {
|
|
382
|
+
return `---
|
|
383
|
+
name: ${skill.name}
|
|
384
|
+
description: ${skill.description}
|
|
385
|
+
source: sapiens-mcp (sapiensinteticos.com)
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
# ${skill.title}
|
|
389
|
+
|
|
390
|
+
${skill.body}
|
|
391
|
+
`;
|
|
392
|
+
}
|
|
393
|
+
/** SKILL.md pronto de uma skill pelo slug; null se o slug não existe. */
|
|
394
|
+
export function getSkillMd(name) {
|
|
395
|
+
const s = SKILLS[name];
|
|
396
|
+
return s ? renderSkill(s) : null;
|
|
397
|
+
}
|
|
398
|
+
/** Índice enxuto pro tools/list e pro resources/list: slug, título, quando usar. */
|
|
399
|
+
export function listSkills() {
|
|
400
|
+
return SKILL_LIST.map((s) => ({
|
|
401
|
+
name: s.name,
|
|
402
|
+
title: s.title,
|
|
403
|
+
description: s.description,
|
|
404
|
+
uri: skillUri(s.name),
|
|
405
|
+
}));
|
|
406
|
+
}
|
|
407
|
+
/** Uma linha por skill, pra caber no SAPIENS_INSTRUCTIONS sem inchar. */
|
|
408
|
+
export function skillMenuLine() {
|
|
409
|
+
return SKILL_LIST.map((s) => s.name).join(" | ");
|
|
410
|
+
}
|
|
411
|
+
// ---------- resources MCP (a outra porta pro mesmo conteúdo) ----------
|
|
412
|
+
/**
|
|
413
|
+
* Payload do resources/list. Compartilhado pelos dois transportes (stdio e
|
|
414
|
+
* remoto), igual ao buildToolList: um lugar só pra mexer.
|
|
415
|
+
*/
|
|
416
|
+
export function listSkillResources() {
|
|
417
|
+
return SKILL_LIST.map((s) => ({
|
|
418
|
+
uri: skillUri(s.name),
|
|
419
|
+
name: s.name,
|
|
420
|
+
title: s.title,
|
|
421
|
+
description: s.description,
|
|
422
|
+
mimeType: "text/markdown",
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Payload do resources/read. Devolve null quando a URI não é uma skill da casa
|
|
427
|
+
* (o caller decide se erra ou delega).
|
|
428
|
+
*/
|
|
429
|
+
export function readSkillResource(uri) {
|
|
430
|
+
const name = skillNameFromUri(uri);
|
|
431
|
+
if (!name)
|
|
432
|
+
return null;
|
|
433
|
+
const text = getSkillMd(name);
|
|
434
|
+
if (!text)
|
|
435
|
+
return null;
|
|
436
|
+
return { contents: [{ uri, mimeType: "text/markdown", text }] };
|
|
437
|
+
}
|
package/dist/tools/gallery.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { convexAction, getSessionToken, isRemoteContext } from "../convexClient.js";
|
|
3
4
|
/**
|
|
4
|
-
*
|
|
5
|
-
* Wrapper sobre `desktopMcp.galleryList` / `
|
|
5
|
+
* Galeria do usuário: browse, publicação e UPLOAD de peça de fora.
|
|
6
|
+
* Wrapper sobre `desktopMcp.galleryList` / `galleryGet` / `gallerySetPublic` e
|
|
7
|
+
* `galleryImport.mcpUploadImage`.
|
|
6
8
|
*
|
|
7
9
|
* Usado pra:
|
|
8
10
|
* - Listar imagens recentes do user (pra reusar como referenceImage)
|
|
9
11
|
* - Pegar bytes de uma imagem pra mostrar inline no Claude
|
|
10
12
|
* - Descobrir imageId pra passar como sourceImageId em sapiens_image edit/variation
|
|
11
13
|
* - Publicar/despublicar a própria imagem (galeria pública + feed Pinterest)
|
|
14
|
+
* - Subir peça gerada FORA da casa (action=upload), que entra privada e serve
|
|
15
|
+
* de referência pras gerações daqui
|
|
16
|
+
*
|
|
17
|
+
* O upload resolve URL/arquivo pra base64 AQUI, no cliente, e manda os bytes
|
|
18
|
+
* prontos pro Convex. O servidor não busca URL nenhuma (sem SSRF novo lá).
|
|
12
19
|
*/
|
|
13
20
|
export const gallerySchema = z.object({
|
|
14
|
-
action: z.enum(["list", "get", "publish", "unpublish"]),
|
|
21
|
+
action: z.enum(["list", "get", "publish", "unpublish", "upload"]),
|
|
15
22
|
limit: z
|
|
16
23
|
.number()
|
|
17
24
|
.int()
|
|
@@ -31,7 +38,123 @@ export const gallerySchema = z.object({
|
|
|
31
38
|
.boolean()
|
|
32
39
|
.optional()
|
|
33
40
|
.describe("Default false. Quando true, action=get devolve os bytes em base64 (pesado, evite em listagens)."),
|
|
41
|
+
// --- action=upload (peça de fora) ---
|
|
42
|
+
sourceUrl: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("action=upload: URL https direta da imagem gerada em outro lugar (ex: o link do render da Magnific). Alternativa a filePath/base64."),
|
|
46
|
+
filePath: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("action=upload: caminho ABSOLUTO de um arquivo de imagem local. SÓ no MCP instalado (stdio); no remoto use sourceUrl."),
|
|
50
|
+
base64: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("action=upload: os bytes já em base64, se você mesmo os tem em mãos."),
|
|
54
|
+
mimeType: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("action=upload: tipo do arquivo (image/png, image/webp...). Detectado sozinho a partir da URL/extensão/resposta HTTP quando omitido."),
|
|
58
|
+
note: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("action=upload: o que é a peça (prompt original, contexto). Vira a descrição na galeria e a busca acha por ela."),
|
|
62
|
+
uploadedSource: z
|
|
63
|
+
.string()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("action=upload: de onde veio ('magnific', 'suno', 'camera'). Fica registrado na peça e no aviso do Discord."),
|
|
34
66
|
});
|
|
67
|
+
// Teto igual ao do servidor (convex/galleryImport.ts): falha aqui é mais barata
|
|
68
|
+
// que falhar depois de trafegar o base64 inteiro.
|
|
69
|
+
const MAX_UPLOAD_BYTES = 12 * 1024 * 1024;
|
|
70
|
+
const FETCH_TIMEOUT_MS = 30_000;
|
|
71
|
+
const EXT_MIME = {
|
|
72
|
+
png: "image/png",
|
|
73
|
+
jpg: "image/jpeg",
|
|
74
|
+
jpeg: "image/jpeg",
|
|
75
|
+
webp: "image/webp",
|
|
76
|
+
gif: "image/gif",
|
|
77
|
+
avif: "image/avif",
|
|
78
|
+
};
|
|
79
|
+
function mimeFromPath(p) {
|
|
80
|
+
const ext = p.split("?")[0].split("#")[0].split(".").pop()?.toLowerCase();
|
|
81
|
+
return ext ? EXT_MIME[ext] : undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Recusa host que não é internet pública. O `sourceUrl` vem de um agente, e no
|
|
85
|
+
* transporte remoto o fetch roda no NOSSO servidor: sem isto, "sobe essa
|
|
86
|
+
* imagem" viraria uma porta pra ler serviço interno / metadata da cloud.
|
|
87
|
+
*/
|
|
88
|
+
function assertPublicHttpsUrl(raw) {
|
|
89
|
+
let u;
|
|
90
|
+
try {
|
|
91
|
+
u = new URL(raw);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new Error(`sourceUrl não é uma URL válida: ${raw}`);
|
|
95
|
+
}
|
|
96
|
+
if (u.protocol !== "https:") {
|
|
97
|
+
throw new Error("sourceUrl precisa ser https.");
|
|
98
|
+
}
|
|
99
|
+
const h = u.hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
100
|
+
const isPrivate = h === "localhost" ||
|
|
101
|
+
h.endsWith(".local") ||
|
|
102
|
+
h.endsWith(".internal") ||
|
|
103
|
+
h === "::1" ||
|
|
104
|
+
/^127\./.test(h) ||
|
|
105
|
+
/^0\./.test(h) ||
|
|
106
|
+
/^10\./.test(h) ||
|
|
107
|
+
/^192\.168\./.test(h) ||
|
|
108
|
+
/^169\.254\./.test(h) ||
|
|
109
|
+
/^172\.(1[6-9]|2\d|3[01])\./.test(h) ||
|
|
110
|
+
/^f[cd][0-9a-f]{2}:/.test(h);
|
|
111
|
+
if (isPrivate) {
|
|
112
|
+
throw new Error(`Host não permitido no upload: ${u.hostname}`);
|
|
113
|
+
}
|
|
114
|
+
return u;
|
|
115
|
+
}
|
|
116
|
+
/** Resolve sourceUrl | filePath | base64 nos bytes prontos pro Convex. */
|
|
117
|
+
async function resolveUploadBytes(args) {
|
|
118
|
+
if (args.base64?.trim()) {
|
|
119
|
+
const mimeType = args.mimeType ||
|
|
120
|
+
args.base64.match(/^data:([^;]+);base64,/)?.[1] ||
|
|
121
|
+
"image/png";
|
|
122
|
+
return { base64: args.base64.trim(), mimeType };
|
|
123
|
+
}
|
|
124
|
+
if (args.filePath) {
|
|
125
|
+
if (isRemoteContext()) {
|
|
126
|
+
throw new Error("filePath só funciona no MCP instalado (stdio). No remoto, mande sourceUrl.");
|
|
127
|
+
}
|
|
128
|
+
const buf = await readFile(args.filePath);
|
|
129
|
+
if (buf.byteLength > MAX_UPLOAD_BYTES) {
|
|
130
|
+
throw new Error(`Arquivo grande demais: ${(buf.byteLength / 1024 / 1024).toFixed(1)} MB (teto 12 MB).`);
|
|
131
|
+
}
|
|
132
|
+
const mimeType = args.mimeType || mimeFromPath(args.filePath) || "image/png";
|
|
133
|
+
return { base64: buf.toString("base64"), mimeType };
|
|
134
|
+
}
|
|
135
|
+
if (args.sourceUrl) {
|
|
136
|
+
const u = assertPublicHttpsUrl(args.sourceUrl);
|
|
137
|
+
const res = await fetch(u, {
|
|
138
|
+
redirect: "follow",
|
|
139
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
140
|
+
});
|
|
141
|
+
if (!res.ok) {
|
|
142
|
+
throw new Error(`Baixar a imagem falhou: HTTP ${res.status} ${res.statusText}`);
|
|
143
|
+
}
|
|
144
|
+
// Revalida o destino final: um 3xx pra host privado seria o buraco que o
|
|
145
|
+
// assertPublicHttpsUrl da URL inicial não vê.
|
|
146
|
+
if (res.url)
|
|
147
|
+
assertPublicHttpsUrl(res.url);
|
|
148
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
149
|
+
if (buf.byteLength > MAX_UPLOAD_BYTES) {
|
|
150
|
+
throw new Error(`Imagem grande demais: ${(buf.byteLength / 1024 / 1024).toFixed(1)} MB (teto 12 MB).`);
|
|
151
|
+
}
|
|
152
|
+
const headerMime = res.headers.get("content-type")?.split(";")[0]?.trim();
|
|
153
|
+
const mimeType = args.mimeType || mimeFromPath(u.pathname) || headerMime || "image/png";
|
|
154
|
+
return { base64: buf.toString("base64"), mimeType };
|
|
155
|
+
}
|
|
156
|
+
throw new Error("action=upload exige a peça: sourceUrl (https), filePath (local, stdio) ou base64.");
|
|
157
|
+
}
|
|
35
158
|
export async function gallery(args) {
|
|
36
159
|
const sessionToken = getSessionToken();
|
|
37
160
|
if (args.action === "list") {
|
|
@@ -67,4 +190,21 @@ export async function gallery(args) {
|
|
|
67
190
|
isPublic: args.action === "publish",
|
|
68
191
|
});
|
|
69
192
|
}
|
|
193
|
+
// upload: peça gerada FORA entra na galeria do dono. Nasce privada e assim
|
|
194
|
+
// fica: o servidor recusa publicar e compartilhar um upload. Serve de
|
|
195
|
+
// referência pras gerações da casa, e o derivado publica normal.
|
|
196
|
+
if (args.action === "upload") {
|
|
197
|
+
const { base64, mimeType } = await resolveUploadBytes(args);
|
|
198
|
+
const result = await convexAction("galleryImport:mcpUploadImage", {
|
|
199
|
+
sessionToken,
|
|
200
|
+
base64,
|
|
201
|
+
mimeType,
|
|
202
|
+
note: args.note,
|
|
203
|
+
uploadedSource: args.uploadedSource,
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
...result,
|
|
207
|
+
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
|
+
};
|
|
209
|
+
}
|
|
70
210
|
}
|
package/dist/tools/image.js
CHANGED
|
@@ -7,9 +7,14 @@ import { convexAction, convexQuery, getSessionToken } from "../convexClient.js";
|
|
|
7
7
|
const MODELS = [
|
|
8
8
|
"nano-banana-max", // gemini-3-pro-image-preview · 450 + adder
|
|
9
9
|
"nano-banana-2", // gemini-3.1-flash-image-preview (V2, Flash 3.1) · 450 + adder · COM refs · DEFAULT
|
|
10
|
-
"nova-canvas", // Amazon Nova Canvas (AWS Bedrock). Corp/censurado, txt2img · 250
|
|
11
10
|
"gpt-image-2-low", // Azure gpt-image-2 quality=low · 250
|
|
12
11
|
"gpt-image-2-high", // Azure gpt-image-2 quality=high · 800
|
|
12
|
+
// ByteDance via ModelArk direto. Corp/censurado, 2K nativo, COM refs (até 4).
|
|
13
|
+
// (O "nova-canvas" ficava aqui e saiu: a AWS matou o modelo. Pedido antigo
|
|
14
|
+
// ainda funciona, o backend faz o alias pro gpt-image-2-low.)
|
|
15
|
+
"seedream-4-5", // Seedream 4.5 · 400 · cinematográfico
|
|
16
|
+
"seedream-5-0", // Seedream 5.0, a geração nova · 400 · lê prompt complexo melhor
|
|
17
|
+
"seedream-5-0-pro", // Seedream 5.0 Pro, topo da linha · 450 (roda no pacote pago)
|
|
13
18
|
// xAI Grok Imagine. Moderação frouxa (+18), aceita refs (img2img) + aspect.
|
|
14
19
|
"grok-2-image", // grok-imagine-image · 450 + adder 2K · COM refs
|
|
15
20
|
"grok-2-image-quality", // grok-imagine-image-quality, mais fiel pra character lock · 900 + adder 2K · COM refs
|
|
@@ -38,7 +43,7 @@ export const imageSchema = z.object({
|
|
|
38
43
|
model: z
|
|
39
44
|
.enum(MODELS)
|
|
40
45
|
.optional()
|
|
41
|
-
.describe("Default 'nano-banana-2' (Flash 3.1 com refs). 'nano-banana-max' (Pro 3) = qualidade alta. 'gpt-image-2-low/high' = Azure. 'grok-2-image'/'grok-2-image-quality' = xAI Grok Imagine (moderação frouxa +18, aceita refs e aspect; quality é mais fiel pra character lock). DEGEN (uncensored, gate +18): 'wavespeed-chroma' (fotorrealista rápido), 'wavespeed-flux2' (Flux.2 Klein), 'wavespeed-flux-nsfw' (flux+LoRA NSFW, Ousadia regulável via loraIntensity), 'wavespeed-klein-anime' (Flux.2 Klein + LoRA anime, inteligente+controlável), 'wavespeed-klein-anime-plus' (Klein anime +18, Ousadia regulável) = WaveSpeed rápido; 'civitai-wai-illustrious'/'civitai-nova-anime-xl' (anime), 'civitai-pony-v6' (Pony V6 XL, base nº1) = Civitai sdcpp rápido. 'fal-krea2-realism'/'fal-krea2-realism-v2' (Krea-2 Turbo 12B + LoRA de realismo, fal.ai, ~4s, fotorrealismo forte) = SÓ txt2img (não aceita referência)."),
|
|
46
|
+
.describe("Default 'nano-banana-2' (Flash 3.1 com refs). 'nano-banana-max' (Pro 3) = qualidade alta. 'gpt-image-2-low/high' = Azure. 'seedream-4-5'/'seedream-5-0'/'seedream-5-0-pro' = ByteDance 2K nativo, cinematográfico, aceita até 4 referências (o 5.0 é a geração nova, entende prompt complexo melhor; o 5.0-pro é o topo da linha). 'grok-2-image'/'grok-2-image-quality' = xAI Grok Imagine (moderação frouxa +18, aceita refs e aspect; quality é mais fiel pra character lock). DEGEN (uncensored, gate +18): 'wavespeed-chroma' (fotorrealista rápido), 'wavespeed-flux2' (Flux.2 Klein), 'wavespeed-flux-nsfw' (flux+LoRA NSFW, Ousadia regulável via loraIntensity), 'wavespeed-klein-anime' (Flux.2 Klein + LoRA anime, inteligente+controlável), 'wavespeed-klein-anime-plus' (Klein anime +18, Ousadia regulável) = WaveSpeed rápido; 'civitai-wai-illustrious'/'civitai-nova-anime-xl' (anime), 'civitai-pony-v6' (Pony V6 XL, base nº1) = Civitai sdcpp rápido. 'fal-krea2-realism'/'fal-krea2-realism-v2' (Krea-2 Turbo 12B + LoRA de realismo, fal.ai, ~4s, fotorrealismo forte) = SÓ txt2img (não aceita referência)."),
|
|
42
47
|
aspectRatio: z
|
|
43
48
|
.enum(["1:1", "16:9", "9:16", "4:3", "3:4", "3:2", "2:3"])
|
|
44
49
|
.optional()
|
package/dist/tools/meta.js
CHANGED
|
@@ -75,7 +75,7 @@ const FORMAT_GUIDE = {
|
|
|
75
75
|
hashtags: ["string"],
|
|
76
76
|
keyword: "string MAIÚSCULA (gatilho do comment-to-DM, ex: QUERO)",
|
|
77
77
|
},
|
|
78
|
-
theme: { palette: "editorial-ink | editorial-dark | editorial-cream | editorial-bone" },
|
|
78
|
+
theme: { palette: "editorial-ink | editorial-dark | editorial-cream | editorial-bone | feed-preto | feed-floresta | feed-sage (as feed-* são da família de templates *-feed-c)" },
|
|
79
79
|
slides: [
|
|
80
80
|
{
|
|
81
81
|
id: "slide-1",
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { getSkillMd, listSkills } from "../skills.js";
|
|
3
|
+
/**
|
|
4
|
+
* sapiens_skill: as skills da casa servidas pelo próprio connector.
|
|
5
|
+
*
|
|
6
|
+
* É a tool mais barata do catálogo e a única que NÃO fala com o backend: o
|
|
7
|
+
* conteúdo é estático, mora no pacote, não exige login, não custa Sinapse e
|
|
8
|
+
* não pode falhar por rede. Existe pra dois motivos:
|
|
9
|
+
*
|
|
10
|
+
* 1. O saber de fluxo saiu do SAPIENS_INSTRUCTIONS (que viaja em TODO
|
|
11
|
+
* handshake) e virou conteúdo sob demanda. Handshake magro, saber fundo.
|
|
12
|
+
* 2. Nem todo cliente MCP lê `resources`. Quem lê pega por
|
|
13
|
+
* skill://sapiens/<name>/SKILL.md; quem não lê, pega por aqui. Mesmo
|
|
14
|
+
* conteúdo, duas portas.
|
|
15
|
+
*/
|
|
16
|
+
export const skillSchema = z.object({
|
|
17
|
+
action: z
|
|
18
|
+
.enum(["list", "get"])
|
|
19
|
+
.default("list")
|
|
20
|
+
.describe("'list' devolve o índice (slug + quando usar) das skills da casa. 'get' devolve o SKILL.md inteiro de uma."),
|
|
21
|
+
name: z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Slug da skill no action=get. Ex: 'musica', 'video', 'voz-da-casa'. Rode action=list se não souber."),
|
|
25
|
+
});
|
|
26
|
+
export async function skill(args) {
|
|
27
|
+
const action = args.action ?? "list";
|
|
28
|
+
if (action === "list") {
|
|
29
|
+
const skills = listSkills();
|
|
30
|
+
return {
|
|
31
|
+
count: skills.length,
|
|
32
|
+
skills,
|
|
33
|
+
note: "Chame action=get name=<slug> pra ler a skill inteira antes de operar o fluxo.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const name = args.name?.trim();
|
|
37
|
+
if (!name) {
|
|
38
|
+
return {
|
|
39
|
+
error: "action=get exige 'name'. Rode action=list pra ver os slugs.",
|
|
40
|
+
available: listSkills().map((s) => s.name),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const md = getSkillMd(name);
|
|
44
|
+
if (!md) {
|
|
45
|
+
return {
|
|
46
|
+
error: `Skill '${name}' não existe.`,
|
|
47
|
+
available: listSkills().map((s) => s.name),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
return { name, content: md };
|
|
51
|
+
}
|
package/dist/tools/video.js
CHANGED
|
@@ -37,6 +37,9 @@ import { httpUrl } from "../schema.js";
|
|
|
37
37
|
* - film-delete: apaga um spec (limpar rascunho/duplicata).
|
|
38
38
|
*
|
|
39
39
|
* Modelos (action=create):
|
|
40
|
+
* - sapiens-video-seedance-15 Seedance 1.5 Pro — o meio-termo forte, 4-12s, 480/720/1080p (t2v/i2v).
|
|
41
|
+
* Mais fiel que o 1.0 e bem mais barato que o 2.0. Sem áudio, sem
|
|
42
|
+
* referência e sem frame final (o repertório completo é o 2.0).
|
|
40
43
|
* - sapiens-video-seedance Seedance 2.0 — cena com áudio nativo, 4-15s, 480/720/1080p (t2v/i2v).
|
|
41
44
|
* Aceita até 4 imagens de REFERÊNCIA (referenceImage*): guiam estilo/
|
|
42
45
|
* personagem/composição sem virar o 1º frame. É o fluxo storyboard:
|
|
@@ -71,6 +74,7 @@ import { httpUrl } from "../schema.js";
|
|
|
71
74
|
* Retorna `{ success, url, imageId, cost }` ou `{ success: false, error }`.
|
|
72
75
|
*/
|
|
73
76
|
const VIDEO_MODELS = [
|
|
77
|
+
"sapiens-video-seedance-15",
|
|
74
78
|
"sapiens-video-seedance",
|
|
75
79
|
"sapiens-video-seedance-2-fast",
|
|
76
80
|
"sapiens-video-seedance-2-mini",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapiens-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.43.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": {
|