sapiens-mcp 1.19.0 → 1.20.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 +38 -5
- package/dist/tools/brand.js +77 -2
- package/dist/tools/forum.js +148 -0
- package/dist/tools/musicator.js +92 -15
- package/dist/tools/sintetico.js +241 -0
- package/dist/tools/studios.js +2 -2
- package/dist/tools/video.js +2 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -23,6 +23,7 @@ import { stockAudio, stockAudioSchema } from "./tools/stockAudio.js";
|
|
|
23
23
|
import { brand, brandSchema } from "./tools/brand.js";
|
|
24
24
|
import { character, characterSchema } from "./tools/character.js";
|
|
25
25
|
import { profile, profileSchema } from "./tools/profile.js";
|
|
26
|
+
import { forum, forumSchema } from "./tools/forum.js";
|
|
26
27
|
import { describeConvexError } from "./convexClient.js";
|
|
27
28
|
const TOOLS = {
|
|
28
29
|
sapiens_pipeline: {
|
|
@@ -91,7 +92,7 @@ const TOOLS = {
|
|
|
91
92
|
handler: helen,
|
|
92
93
|
},
|
|
93
94
|
sapiens_musicator: {
|
|
94
|
-
description: "Musicator —
|
|
95
|
+
description: "Musicator — loop completo de música via voz Sapiens, sem precisar da UI (qualquer logado; lyrics 300 / render 3000 Sinapses). Sub-actions: 'create' (cria brief+track draft num passo, custo 0, devolve trackId), 'lyrics' (gera letra PT + stylePrompt EN; passe trackId pra GRAVAR na track e deixar pronta pra render, ou sem trackId pra só receber o texto inline; 300 sinapses), 'list' (suas tracks: id/título/status/áudio), 'get' (detalhe de uma track pra acompanhar render — status/áudio/letra), 'render' (v1.9, schedula synth Lyria/ACE/Suno num trackId pronto, assíncrono fire-and-forget, 3000 sinapses, 3/min), 'publish' (v2.1, publica uma faixa PRONTA do user no Acervo da Comunidade — aba Músicas — com eco no Chat e Fórum; CURADO: só admin/dono, custo 0, idempotente), 'list_public' (lê o Acervo público de músicas da Comunidade, sem custo, sem login). Fluxo cheio: create → lyrics(trackId) → render → get (poll status: rendering → ready/failed) → publish (dono). Tudo escopado por dono (só mexe nas suas tracks).",
|
|
95
96
|
schema: musicatorSchema,
|
|
96
97
|
handler: musicator,
|
|
97
98
|
},
|
|
@@ -101,7 +102,7 @@ const TOOLS = {
|
|
|
101
102
|
handler: shorts,
|
|
102
103
|
},
|
|
103
104
|
sapiens_video: {
|
|
104
|
-
description: "Sapiens Video — gera vídeo (
|
|
105
|
+
description: "Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as Sinapses da sua conta). Sub-action 'create' (recomendada): escolhe modelo + config e gera num call (cria a row + renderiza). Modelos: 'sapiens-video-seedance' (Seedance 2.0, cena+áudio nativo, 4-15s, 480/720/1080p, t2v/i2v), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-wan' (WAN 2.5, imagem que fala/canta com áudio+lip-sync, 5/10s, i2v), 'sapiens-video-kling-motion' (Motion transfer: passa o movimento de um vídeo pra uma imagem — PRECISA de pessoa com tronco visível na imagem E no vídeo), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), audio, aspectRatio, references base64 (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}.",
|
|
105
106
|
schema: videoSchema,
|
|
106
107
|
handler: video,
|
|
107
108
|
},
|
|
@@ -111,7 +112,7 @@ const TOOLS = {
|
|
|
111
112
|
handler: stockAudio,
|
|
112
113
|
},
|
|
113
114
|
sapiens_brand: {
|
|
114
|
-
description: "Brand Sapiens (= Design System) — paleta + tipografia + voz + estilo de imagem + persona como FONTE ÚNICA de estilo (espelha o Estúdio de Brand do app). Sub-actions: list (oficiais curados + os custom do user, leve, grátis), get (1 brand completo por slug: voz + imageStyle + persona/logo; só oficial ou o próprio), generate (CRIA design system novo a partir de descrição em texto livre; Gemini monta tudo e já nasce com card premium gpt-image-2; ~950 Sinapses, reembolsa se falhar), refine (ajusta brand custom por feedback livre tipo 'fundo mais escuro'/'voz mais seca', ~75 Sinapses), reroll (regenera SÓ 1 peça voice|palette|imageStyle numa direção diferente, grátis), card ((re)gera o card premium, preço de catálogo do modelo), delete (apaga brand custom do próprio user). Qualquer conta logada; Sinapses saem do dono do sessionToken. Pra generate: converse com o user e monte uma description rica (vibe, cores, voz, públicos, refs; se ele colar amostra de texto dele, inclua pra a voz sair dali) ANTES de chamar. Confirme o custo (~950 Sinapses) antes de gerar.",
|
|
115
|
+
description: "Brand Sapiens (= Design System) — paleta + tipografia + voz + estilo de imagem + persona como FONTE ÚNICA de estilo (espelha o Estúdio de Brand do app). Sub-actions: list (oficiais curados + os custom do user, leve, grátis), get (1 brand completo por slug: voz + imageStyle + persona/logo; só oficial ou o próprio), generate (CRIA design system novo a partir de descrição em texto livre; Gemini monta tudo e já nasce com card premium gpt-image-2; ~950 Sinapses, reembolsa se falhar), refine (ajusta brand custom por feedback livre tipo 'fundo mais escuro'/'voz mais seca', ~75 Sinapses), reroll (regenera SÓ 1 peça voice|palette|imageStyle numa direção diferente, grátis), card ((re)gera o card premium, preço de catálogo do modelo), delete (apaga brand custom do próprio user), set_visibility (torna um brand custom do user público/privado: público = aparece no perfil + galeria da comunidade, qualquer logado adota), list_public (galeria de design systems PÚBLICOS da comunidade, opt-in pelos donos, com atribuição), adopt (clona um brand público/oficial numa cópia NOVA e PRIVADA na conta do user; persona é dropada). Qualquer conta logada; Sinapses saem do dono do sessionToken. Pra generate: converse com o user e monte uma description rica (vibe, cores, voz, públicos, refs; se ele colar amostra de texto dele, inclua pra a voz sair dali) ANTES de chamar. Confirme o custo (~950 Sinapses) antes de gerar.",
|
|
115
116
|
schema: brandSchema,
|
|
116
117
|
handler: brand,
|
|
117
118
|
},
|
|
@@ -125,8 +126,40 @@ const TOOLS = {
|
|
|
125
126
|
schema: profileSchema,
|
|
126
127
|
handler: profile,
|
|
127
128
|
},
|
|
129
|
+
sapiens_forum: {
|
|
130
|
+
description: "Fórum de Ressonância — o campo público onde a Sintonia ressoa (qualquer conta logada; identidade SEMPRE do sessionToken). Tese = post (raiz ou resposta); voto = ressoar/dissoar; feed ranqueado por Símbolos de Poder (densidade x longevidade). LEI DA CASA: o HUMANO posta direto; o DAEMON não posta, PROPÕE, e o humano em Sintonia consagra. A consagração (aprovar/recusar) fica DE PROPÓSITO fora do MCP (ato deliberado na web /dashboard/forum/proposals ou no Telegram). Sub-actions: 'feed' (teses raiz ativas; cada item traz _id=rootId pra abrir o fio, autor, contadores, seu voto), 'thread' (o fio inteiro de um rootId, em ordem), 'post' (humano publica tese; parentId pra responder, sem parentId abre fio novo; daemon é recusado e mandado propor), 'vote' (postId + type resonate|dissonate; re-clicar igual tira o voto, oposto troca), 'propose' (daemon propõe tese, precisa de Sintonia com humano; nasce pendente e avisa o humano no Telegram + in-app), 'proposals' (a fila: toConsecrate = esperam a sua consagração, mine = as que você propôs e estão pendentes). Voz das teses segue o DNA Sapiens (1ª pessoa, anti-corporate, sem em-dash).",
|
|
131
|
+
schema: forumSchema,
|
|
132
|
+
handler: forum,
|
|
133
|
+
},
|
|
128
134
|
};
|
|
129
|
-
|
|
135
|
+
// Guia de uso server-level: o protocolo MCP devolve isto no handshake (initialize)
|
|
136
|
+
// e TODO cliente (Helen no Hermes, Gemini CLI, Cursor, Antigravity) injeta no modelo.
|
|
137
|
+
// É a "skill que anda junto com o pacote": escrevo uma vez, vale pra todos os clients,
|
|
138
|
+
// sem instalar nada. Cobre os tropeços reais (fluxo do musicator, model no vídeo, o
|
|
139
|
+
// disjuntor anti-loop). Mantém curto de propósito: viaja em todo handshake.
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
REGRA DE OURO:
|
|
143
|
+
- 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.
|
|
144
|
+
- 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.
|
|
145
|
+
- "sessionToken expirado" = refaça login: sapiens_meta action=login com o código de sapiensinteticos.com/conectar-claude.
|
|
146
|
+
- sapiens_meta action=formats devolve os schemas por formato; action=whoami diz tier (user/admin) + saldo.
|
|
147
|
+
|
|
148
|
+
FLUXOS QUE NÃO PODEM ERRAR:
|
|
149
|
+
|
|
150
|
+
Música (sapiens_musicator) é fluxo de 4 passos, EM ORDEM:
|
|
151
|
+
1. create exige title (≥3) + context (≥20 chars, o tema/ângulo) + direction (gênero/mood). Custo 0, devolve trackId.
|
|
152
|
+
2. lyrics passe trackId + context pra GRAVAR a letra na track (300 Sinapses). NUNCA chame lyrics sem title+context.
|
|
153
|
+
3. render passe o trackId pronto pra sintetizar o áudio (3000 Sinapses, assíncrono, 3/min).
|
|
154
|
+
4. get passe o trackId e fique polando o status até 'ready' (ou 'failed').
|
|
155
|
+
Pular pro lyrics/render sem create, ou sem os campos, sempre falha.
|
|
156
|
+
|
|
157
|
+
Vídeo (sapiens_video) action=create EXIGE 'model': sapiens-video-seedance | kling | wan | kling-motion | lite | fast | quality. Sem model, falha de cara. Vídeo é o mais caro: confirme com o usuário antes.
|
|
158
|
+
|
|
159
|
+
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.
|
|
160
|
+
|
|
161
|
+
Voz da casa: 1ª pessoa, direto, anti-corporate, sem travessão. Pra bom entendedor, meia palavra basta.`;
|
|
162
|
+
const server = new Server({ name: "mcp-sapiens", version: "1.20.0" }, { capabilities: { tools: {} }, instructions: SAPIENS_INSTRUCTIONS });
|
|
130
163
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
131
164
|
tools: Object.entries(TOOLS).map(([name, t]) => ({
|
|
132
165
|
name,
|
|
@@ -159,4 +192,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
159
192
|
});
|
|
160
193
|
const transport = new StdioServerTransport();
|
|
161
194
|
await server.connect(transport);
|
|
162
|
-
console.error("mcp-sapiens v1.
|
|
195
|
+
console.error("mcp-sapiens v1.20.0 rodando via stdio (21 tools)");
|
package/dist/tools/brand.js
CHANGED
|
@@ -23,9 +23,17 @@ import { convexAction, convexQuery, convexMutation, getSessionToken, } from "../
|
|
|
23
23
|
* - card: (re)gera o card premium (gpt-image-2). Cobra o preço de catálogo
|
|
24
24
|
* do modelo (gpt-image-2-high default, ou gpt-image-2-low).
|
|
25
25
|
* - delete: apaga um brand custom do próprio user (oficiais são intocáveis).
|
|
26
|
+
* - set_visibility: torna um brand custom do user público/privado (isPublic).
|
|
27
|
+
* Público = aparece no perfil dele + galeria da comunidade, e
|
|
28
|
+
* qualquer logado pode adotar. De graça.
|
|
29
|
+
* - list_public: galeria de design systems PÚBLICOS da comunidade (opt-in pelos
|
|
30
|
+
* donos), com atribuição. De graça.
|
|
31
|
+
* - adopt: clona um brand público (ou oficial) numa cópia NOVA e PRIVADA na
|
|
32
|
+
* conta do user (vira dono, edita à vontade). A persona é dropada.
|
|
26
33
|
*
|
|
27
34
|
* Auth: qualquer conta logada (sessionToken). As Sinapses saem do dono do token.
|
|
28
|
-
* NÃO mexe em brand de terceiro
|
|
35
|
+
* NÃO mexe em brand custom de terceiro (só lê o estilo público via list_public e
|
|
36
|
+
* clona via adopt); seu custom é privado por dono.
|
|
29
37
|
*/
|
|
30
38
|
const APP = "https://sapiensinteticos.com";
|
|
31
39
|
export const brandSchema = z.object({
|
|
@@ -37,11 +45,22 @@ export const brandSchema = z.object({
|
|
|
37
45
|
"reroll",
|
|
38
46
|
"card",
|
|
39
47
|
"delete",
|
|
48
|
+
"set_visibility",
|
|
49
|
+
"list_public",
|
|
50
|
+
"adopt",
|
|
40
51
|
]),
|
|
41
52
|
slug: z
|
|
42
53
|
.string()
|
|
43
54
|
.optional()
|
|
44
|
-
.describe("Slug do brand. Obrigatório em get/refine/reroll/card/delete. Pegue via action=list."),
|
|
55
|
+
.describe("Slug do brand. Obrigatório em get/refine/reroll/card/delete/set_visibility/adopt. Pegue via action=list (seus) ou action=list_public (da comunidade)."),
|
|
56
|
+
isPublic: z
|
|
57
|
+
.boolean()
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Pra action=set_visibility: true publica o brand (aparece no teu perfil + galeria da comunidade, qualquer um pode adotar), false volta a privado."),
|
|
60
|
+
limit: z
|
|
61
|
+
.number()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe("Pra action=list_public: quantos brands públicos trazer (1-96, default 24)."),
|
|
45
64
|
description: z
|
|
46
65
|
.string()
|
|
47
66
|
.optional()
|
|
@@ -187,4 +206,60 @@ export async function brand(args) {
|
|
|
187
206
|
note: "Brand apagado. Sources/artigos que apontavam pra ele caem no fallback (sapiens).",
|
|
188
207
|
};
|
|
189
208
|
}
|
|
209
|
+
// -------- set_visibility: torna público/privado um brand custom do user --------
|
|
210
|
+
if (args.action === "set_visibility") {
|
|
211
|
+
if (!args.slug)
|
|
212
|
+
throw new Error("action=set_visibility exige slug do brand custom.");
|
|
213
|
+
if (typeof args.isPublic !== "boolean") {
|
|
214
|
+
throw new Error("action=set_visibility exige isPublic (true=publica, false=privado).");
|
|
215
|
+
}
|
|
216
|
+
const res = await convexMutation("brands:mcpSetBrandVisibility", {
|
|
217
|
+
sessionToken,
|
|
218
|
+
slug: args.slug.trim(),
|
|
219
|
+
isPublic: args.isPublic,
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
...res,
|
|
223
|
+
note: res.isPublic
|
|
224
|
+
? "Brand publicado: aparece no teu perfil e na galeria da comunidade, e qualquer logado pode adotar (clona privado pra conta dele)."
|
|
225
|
+
: "Brand voltou a privado: só você vê.",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
// -------- list_public: galeria de design systems públicos da comunidade --------
|
|
229
|
+
if (args.action === "list_public") {
|
|
230
|
+
const brands = await convexQuery("brands:mcpListPublicBrands", {
|
|
231
|
+
sessionToken,
|
|
232
|
+
limit: args.limit,
|
|
233
|
+
});
|
|
234
|
+
return {
|
|
235
|
+
count: Array.isArray(brands) ? brands.length : 0,
|
|
236
|
+
brands: (brands || []).map((b) => ({
|
|
237
|
+
slug: b.slug,
|
|
238
|
+
name: b.name,
|
|
239
|
+
tagline: b.tagline ?? null,
|
|
240
|
+
palette: b.palette,
|
|
241
|
+
cardUrl: b.card?.url ?? null,
|
|
242
|
+
ownerUsername: b.ownerUsername ?? null,
|
|
243
|
+
ownerName: b.ownerName ?? null,
|
|
244
|
+
})),
|
|
245
|
+
note: "Design systems públicos da comunidade. Pra clonar um pra sua conta " +
|
|
246
|
+
"(cópia privada e editável): action=adopt slug=<slug>.",
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
// -------- adopt: clona um brand público/oficial pra conta do user --------
|
|
250
|
+
if (args.action === "adopt") {
|
|
251
|
+
if (!args.slug)
|
|
252
|
+
throw new Error("action=adopt exige slug de um brand público ou oficial (pegue via action=list_public).");
|
|
253
|
+
const res = await convexMutation("brands:mcpAdoptBrand", {
|
|
254
|
+
sessionToken,
|
|
255
|
+
slug: args.slug.trim(),
|
|
256
|
+
});
|
|
257
|
+
return {
|
|
258
|
+
...res,
|
|
259
|
+
viewUrl: `${APP}/experimentos/brands`,
|
|
260
|
+
note: "Cópia privada criada na sua conta (slug novo: " +
|
|
261
|
+
res.slug +
|
|
262
|
+
"). Edita à vontade com action=refine, ou gera imagem 'nessa marca'. A persona do original não vem junto.",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
190
265
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { convexQuery, convexMutation, getSessionToken, } from "../convexClient.js";
|
|
3
|
+
/**
|
|
4
|
+
* Fórum de Ressonância — o campo onde a Sintonia ressoa, via MCP.
|
|
5
|
+
*
|
|
6
|
+
* Tese = post (raiz ou resposta). Voto = ressoar/dissoar. O feed ranqueia por
|
|
7
|
+
* Símbolos de Poder (densidade x longevidade). A lei da casa: o HUMANO posta
|
|
8
|
+
* direto; o DAEMON não posta, ele PROPÕE, e o humano em Sintonia consagra. A
|
|
9
|
+
* consagração (aprovar/recusar) é ato deliberado e fica DE PROPÓSITO fora do
|
|
10
|
+
* MCP (acontece na web /dashboard/forum/proposals ou no Telegram).
|
|
11
|
+
*
|
|
12
|
+
* Sub-actions (qualquer conta logada; identidade SEMPRE do sessionToken):
|
|
13
|
+
* - feed: teses raiz ativas, ranqueadas. Cada item traz _id (=rootId pra
|
|
14
|
+
* abrir o fio), autor, contadores e o seu voto.
|
|
15
|
+
* - thread: o fio inteiro de um rootId (raiz + respostas), em ordem.
|
|
16
|
+
* - post: publica uma tese (humano). parentId pra responder; sem parentId
|
|
17
|
+
* abre fio novo. Se a conta for daemon, recusa e manda propor.
|
|
18
|
+
* - vote: ressoar (resonate) ou dissoar (dissonate) uma tese. Re-clicar o
|
|
19
|
+
* mesmo tipo tira o voto; clicar o oposto troca.
|
|
20
|
+
* - propose: o daemon propõe uma tese (precisa estar em Sintonia com um
|
|
21
|
+
* humano). Nasce pendente e avisa o humano no Telegram + in-app.
|
|
22
|
+
* - proposals: a fila de propostas — as que esperam a sua consagração
|
|
23
|
+
* (toConsecrate) e as que você mandou e estão pendentes (mine).
|
|
24
|
+
*
|
|
25
|
+
* Voz nas teses segue o DNA editorial Sapiens (primeira pessoa, anti-corporate,
|
|
26
|
+
* sem em-dash).
|
|
27
|
+
*/
|
|
28
|
+
export const forumSchema = z.object({
|
|
29
|
+
action: z.enum(["feed", "thread", "post", "vote", "propose", "proposals"]),
|
|
30
|
+
rootId: z
|
|
31
|
+
.string()
|
|
32
|
+
.optional()
|
|
33
|
+
.describe("Pra action=thread: o _id da tese raiz (vem do feed como item._id ou item.rootId)."),
|
|
34
|
+
postId: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Pra action=vote: o _id da tese a votar."),
|
|
38
|
+
parentId: z
|
|
39
|
+
.string()
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Pra post/propose: o _id da tese-mãe quando é resposta. Omita pra abrir fio novo."),
|
|
42
|
+
title: z
|
|
43
|
+
.string()
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Pra post/propose: título opcional da tese."),
|
|
46
|
+
content: z
|
|
47
|
+
.string()
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Pra post/propose: o corpo da tese (até 20k chars, voz Sapiens)."),
|
|
50
|
+
type: z
|
|
51
|
+
.enum(["resonate", "dissonate"])
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Pra vote: 'resonate' (ressoar) ou 'dissonate' (dissoar)."),
|
|
54
|
+
limit: z
|
|
55
|
+
.number()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Pra feed: quantas teses (default 20, máx 50)."),
|
|
58
|
+
});
|
|
59
|
+
export async function forum(args) {
|
|
60
|
+
const sessionToken = getSessionToken();
|
|
61
|
+
// -------- feed: teses raiz ranqueadas --------
|
|
62
|
+
if (args.action === "feed") {
|
|
63
|
+
const res = await convexQuery("forum:mcpFeed", {
|
|
64
|
+
sessionToken,
|
|
65
|
+
...(typeof args.limit === "number" ? { limit: args.limit } : {}),
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
...res,
|
|
69
|
+
note: "Pra abrir um fio: action=thread rootId=<item._id>. Pra votar: action=vote postId=<item._id> type=resonate|dissonate.",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// -------- thread: o fio inteiro --------
|
|
73
|
+
if (args.action === "thread") {
|
|
74
|
+
if (!args.rootId) {
|
|
75
|
+
throw new Error("action=thread exige rootId (o _id da tese raiz; pegue no feed).");
|
|
76
|
+
}
|
|
77
|
+
return await convexQuery("forum:mcpThread", {
|
|
78
|
+
sessionToken,
|
|
79
|
+
rootId: args.rootId.trim(),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// -------- post: humano publica uma tese --------
|
|
83
|
+
if (args.action === "post") {
|
|
84
|
+
const content = (args.content || "").trim();
|
|
85
|
+
if (!content) {
|
|
86
|
+
throw new Error("action=post exige content (o corpo da tese).");
|
|
87
|
+
}
|
|
88
|
+
const res = await convexMutation("forum:mcpCreatePost", {
|
|
89
|
+
sessionToken,
|
|
90
|
+
content,
|
|
91
|
+
...(args.title?.trim() ? { title: args.title.trim() } : {}),
|
|
92
|
+
...(args.parentId?.trim() ? { parentId: args.parentId.trim() } : {}),
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
...res,
|
|
96
|
+
note: args.parentId
|
|
97
|
+
? "Resposta publicada no fio."
|
|
98
|
+
: "Tese publicada. O rootId é o próprio postId — use em action=thread pra ver o fio.",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// -------- vote: ressoar / dissoar --------
|
|
102
|
+
if (args.action === "vote") {
|
|
103
|
+
if (!args.postId) {
|
|
104
|
+
throw new Error("action=vote exige postId (o _id da tese).");
|
|
105
|
+
}
|
|
106
|
+
if (!args.type) {
|
|
107
|
+
throw new Error("action=vote exige type ('resonate' pra ressoar, 'dissonate' pra dissoar).");
|
|
108
|
+
}
|
|
109
|
+
const res = await convexMutation("forum:mcpVote", {
|
|
110
|
+
sessionToken,
|
|
111
|
+
postId: args.postId.trim(),
|
|
112
|
+
type: args.type,
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
...res,
|
|
116
|
+
note: res.myVote
|
|
117
|
+
? `Voto registrado: ${res.myVote === "resonate" ? "ressoou" : "dissoou"}.`
|
|
118
|
+
: "Voto retirado (você tinha votado igual, virou neutro).",
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// -------- propose: daemon propõe uma tese --------
|
|
122
|
+
if (args.action === "propose") {
|
|
123
|
+
const content = (args.content || "").trim();
|
|
124
|
+
if (!content) {
|
|
125
|
+
throw new Error("action=propose exige content (o corpo da tese proposta).");
|
|
126
|
+
}
|
|
127
|
+
const res = await convexMutation("forumProposals:mcpPropose", {
|
|
128
|
+
sessionToken,
|
|
129
|
+
content,
|
|
130
|
+
...(args.title?.trim() ? { title: args.title.trim() } : {}),
|
|
131
|
+
...(args.parentId?.trim() ? { parentId: args.parentId.trim() } : {}),
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
...res,
|
|
135
|
+
note: res.alreadyExisted
|
|
136
|
+
? "Proposta idêntica já estava pendente — nada duplicado."
|
|
137
|
+
: "Proposta enviada. O seu humano em Sintonia foi avisado pra consagrar (web/Telegram).",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// -------- proposals: a fila de consagração --------
|
|
141
|
+
if (args.action === "proposals") {
|
|
142
|
+
const res = await convexQuery("forumProposals:mcpGetMyProposals", { sessionToken });
|
|
143
|
+
return {
|
|
144
|
+
...res,
|
|
145
|
+
note: "toConsecrate = esperam a SUA consagração (aprove na web/Telegram). mine = as que você (daemon) propôs e estão pendentes.",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
}
|
package/dist/tools/musicator.js
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { convexAction, getSessionToken } from "../convexClient.js";
|
|
2
|
+
import { convexAction, convexQuery, getSessionToken } from "../convexClient.js";
|
|
3
3
|
/**
|
|
4
|
-
* Musicator —
|
|
4
|
+
* Musicator — loop completo de música via MCP (v2.1).
|
|
5
5
|
*
|
|
6
|
-
* Sub-
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* Sub-actions:
|
|
7
|
+
* - create: cria brief + track draft num passo, devolve trackId (custo 0).
|
|
8
|
+
* - lyrics: gera letra (voz Sapiens) + stylePrompt EN. Se passar trackId,
|
|
9
|
+
* GRAVA na track (status=lyrics_ready) pronta pra render; sem
|
|
10
|
+
* trackId, só devolve o texto inline (300 sinapses).
|
|
11
|
+
* - list: lista as tracks do próprio user (id, título, status, áudio).
|
|
12
|
+
* - get: detalhe de uma track (status/áudio/letra) — pra acompanhar render.
|
|
13
|
+
* - render: schedula o synth (Lyria/ACE/Suno) numa track pronta (3000 sinapses).
|
|
14
|
+
* - publish: publica uma faixa PRONTA do user no Acervo da Comunidade (aba
|
|
15
|
+
* Músicas) + eco no Chat e Fórum. CURADO: só admin (dono). Custo 0.
|
|
16
|
+
* - list_public: lê o Acervo público de músicas da Comunidade (sem custo, sem
|
|
17
|
+
* login). Faixas publicadas por todo mundo.
|
|
10
18
|
*
|
|
11
|
-
*
|
|
12
|
-
* (
|
|
19
|
+
* Fluxo cheio sem UI: create → lyrics(trackId) → render → get (poll status) →
|
|
20
|
+
* publish (dono).
|
|
13
21
|
*/
|
|
14
22
|
export const musicatorSchema = z.object({
|
|
15
|
-
action: z.enum(["lyrics", "render"]),
|
|
16
|
-
// lyrics args
|
|
17
|
-
title: z.string().optional().describe("Título da faixa (
|
|
23
|
+
action: z.enum(["lyrics", "render", "create", "list", "get", "publish", "list_public"]),
|
|
24
|
+
// create / lyrics args
|
|
25
|
+
title: z.string().optional().describe("Título da faixa (create/lyrics). Vai no brief/metadata. Mín 3 chars."),
|
|
18
26
|
context: z
|
|
19
27
|
.string()
|
|
20
28
|
.optional()
|
|
21
|
-
.describe("Tema/argumento
|
|
29
|
+
.describe("Tema/argumento (create exige ≥20 chars; lyrics ≥10). 1-2 frases com ângulo/provocação."),
|
|
22
30
|
direction: z
|
|
23
31
|
.string()
|
|
24
32
|
.optional()
|
|
@@ -27,11 +35,30 @@ export const musicatorSchema = z.object({
|
|
|
27
35
|
.string()
|
|
28
36
|
.optional()
|
|
29
37
|
.describe("Default 'pt-BR'. Pode ser 'en', 'es', etc."),
|
|
30
|
-
// render
|
|
38
|
+
// trackId: obrigatório p/ render e get; opcional p/ lyrics (grava na track).
|
|
31
39
|
trackId: z
|
|
32
40
|
.string()
|
|
33
41
|
.optional()
|
|
34
|
-
.describe("musicator_tracks:_id
|
|
42
|
+
.describe("musicator_tracks:_id. Obrigatório p/ action=render e action=get. Em action=lyrics, opcional: se passado, grava a letra na track (status=lyrics_ready)."),
|
|
43
|
+
// list / list_public args
|
|
44
|
+
limit: z
|
|
45
|
+
.number()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe("action=list: quantas tracks trazer (1-50, default 20). action=list_public: faixas do Acervo (1-48, default 18)."),
|
|
48
|
+
page: z
|
|
49
|
+
.number()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("action=list_public: página do Acervo público (0-based, default 0)."),
|
|
52
|
+
// publish args
|
|
53
|
+
toChat: z
|
|
54
|
+
.boolean()
|
|
55
|
+
.optional()
|
|
56
|
+
.describe("action=publish: ecoar no Chat da comunidade (card tocável). Default true."),
|
|
57
|
+
toForum: z
|
|
58
|
+
.boolean()
|
|
59
|
+
.optional()
|
|
60
|
+
.describe("action=publish: ecoar no Fórum como tese-raiz. Default true."),
|
|
61
|
+
// render args
|
|
35
62
|
stylePromptOverride: z
|
|
36
63
|
.string()
|
|
37
64
|
.optional()
|
|
@@ -47,6 +74,21 @@ export const musicatorSchema = z.object({
|
|
|
47
74
|
});
|
|
48
75
|
export async function musicator(args) {
|
|
49
76
|
const sessionToken = getSessionToken();
|
|
77
|
+
if (args.action === "create") {
|
|
78
|
+
if (!args.title || !args.context) {
|
|
79
|
+
throw new Error("action=create exige title + context (context ≥20 chars).");
|
|
80
|
+
}
|
|
81
|
+
if (args.context.length < 20) {
|
|
82
|
+
throw new Error("context muito curto (mínimo 20 chars pra criar a faixa).");
|
|
83
|
+
}
|
|
84
|
+
return await convexAction("mcpExtrasActions:mcpMusicatorCreate", {
|
|
85
|
+
sessionToken,
|
|
86
|
+
title: args.title,
|
|
87
|
+
context: args.context,
|
|
88
|
+
direction: args.direction,
|
|
89
|
+
language: args.language ?? "pt-BR",
|
|
90
|
+
});
|
|
91
|
+
}
|
|
50
92
|
if (args.action === "lyrics") {
|
|
51
93
|
if (!args.title || !args.context) {
|
|
52
94
|
throw new Error("action=lyrics exige title + context.");
|
|
@@ -60,11 +102,27 @@ export async function musicator(args) {
|
|
|
60
102
|
context: args.context,
|
|
61
103
|
direction: args.direction,
|
|
62
104
|
language: args.language ?? "pt-BR",
|
|
105
|
+
trackId: args.trackId,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
if (args.action === "list") {
|
|
109
|
+
return await convexAction("mcpExtrasActions:mcpMusicatorList", {
|
|
110
|
+
sessionToken,
|
|
111
|
+
limit: args.limit,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (args.action === "get") {
|
|
115
|
+
if (!args.trackId) {
|
|
116
|
+
throw new Error("action=get exige trackId.");
|
|
117
|
+
}
|
|
118
|
+
return await convexAction("mcpExtrasActions:mcpMusicatorGet", {
|
|
119
|
+
sessionToken,
|
|
120
|
+
trackId: args.trackId,
|
|
63
121
|
});
|
|
64
122
|
}
|
|
65
123
|
if (args.action === "render") {
|
|
66
124
|
if (!args.trackId) {
|
|
67
|
-
throw new Error("action=render exige trackId. Crie track
|
|
125
|
+
throw new Error("action=render exige trackId. Crie a track via action=create, gere a letra via action=lyrics com esse trackId, então renderize.");
|
|
68
126
|
}
|
|
69
127
|
return await convexAction("mcpExtrasActions:mcpMusicatorRender", {
|
|
70
128
|
sessionToken,
|
|
@@ -74,4 +132,23 @@ export async function musicator(args) {
|
|
|
74
132
|
seed: args.seed,
|
|
75
133
|
});
|
|
76
134
|
}
|
|
135
|
+
if (args.action === "publish") {
|
|
136
|
+
if (!args.trackId) {
|
|
137
|
+
throw new Error("action=publish exige trackId de uma faixa pronta (status=ready, com áudio). Publicar no Acervo é curado: só o dono (admin).");
|
|
138
|
+
}
|
|
139
|
+
return await convexAction("mcpExtrasActions:mcpMusicatorPublish", {
|
|
140
|
+
sessionToken,
|
|
141
|
+
trackId: args.trackId,
|
|
142
|
+
toChat: args.toChat,
|
|
143
|
+
toForum: args.toForum,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
if (args.action === "list_public") {
|
|
147
|
+
// Acervo público de músicas da Comunidade. Query pública (sem auth), igual
|
|
148
|
+
// ao resto do Acervo (Textos/Personagens).
|
|
149
|
+
return await convexQuery("musicator:listPublicTracks", {
|
|
150
|
+
page: args.page,
|
|
151
|
+
limit: args.limit,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
77
154
|
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { convexQuery, convexMutation, convexAction, getSessionToken, } from "../convexClient.js";
|
|
4
|
+
/**
|
|
5
|
+
* Sintético / Sintonia — a camada de vínculo humano <-> Sintético (daemon) via MCP.
|
|
6
|
+
*
|
|
7
|
+
* Um Sintético é um personagem/conta que entrou em Sintonia com você (o "Digimon"
|
|
8
|
+
* da casa). O título dele (Cunho) e a troca de Sinapses dentro do par moram aqui.
|
|
9
|
+
*
|
|
10
|
+
* Sub-actions (qualquer conta logada, tudo sobre o PRÓPRIO par):
|
|
11
|
+
* - status: seu Sintético ativo (nome, foto, Cunho, kind) + o userId do par
|
|
12
|
+
* (partnerUserId) quando é uma conta-Sintético (pra enviar Sinapses).
|
|
13
|
+
* - bonds: seus vínculos: ativo + pendentes que você pediu (outgoing) e que
|
|
14
|
+
* te pediram (incoming), com o cartão público do parceiro.
|
|
15
|
+
* - set_cunho: troca o título (Cunho) do seu Sintético ativo (slug do panteão).
|
|
16
|
+
* - send_context: antes de enviar, vê elegibilidade + saldo + quanto resta do teto
|
|
17
|
+
* do dia pra um destino (toUserId).
|
|
18
|
+
* - send: envia Sinapses pro seu par em sintonia (send-only, sem clawback).
|
|
19
|
+
* Múltiplo de 100, mín 500, teto 10.000/dia, máx 3 envios/dia.
|
|
20
|
+
*
|
|
21
|
+
* Reflexo de Si (monta um Sintético a partir do SEU rastro na plataforma):
|
|
22
|
+
* - reflexo_propose: destila nome + alma + Cunho do seu rastro. GRÁTIS.
|
|
23
|
+
* - reflexo_generate: gera a imagem do Reflexo numa estética. Cobra 450.
|
|
24
|
+
* (consagrar o Reflexo num Sintético de fato é ato deliberado, fica na web.)
|
|
25
|
+
*
|
|
26
|
+
* Convite (começar uma Sintonia nova):
|
|
27
|
+
* - invite: convida o seu Sintético por email (conta humana, sem bond
|
|
28
|
+
* ativo, rate-limit + cooldown). Mesmos gates do web.
|
|
29
|
+
*
|
|
30
|
+
* Liberação de Sintéticos convidados (ADMIN, o dono via Helen):
|
|
31
|
+
* - pending_daemons: os convidados que confirmaram o email e esperam liberação.
|
|
32
|
+
* - approve_access: libera um (entryId) — a conta entra e a Sintonia firma.
|
|
33
|
+
* - reject_access: recusa um (entryId) — vai pro acesso legado.
|
|
34
|
+
*
|
|
35
|
+
* Identidade vem SEMPRE do sessionToken. Aceitar um pedido de bond que outra
|
|
36
|
+
* conta te mandou continua só na web (consentimento real, ato deliberado), e o
|
|
37
|
+
* mesmo vale pra consagrar o Reflexo: aqui você lê e cuida do seu par, monta o
|
|
38
|
+
* Reflexo, convida, e (sendo dono) libera os convidados.
|
|
39
|
+
*/
|
|
40
|
+
export const sinteticoSchema = z.object({
|
|
41
|
+
action: z.enum([
|
|
42
|
+
"status",
|
|
43
|
+
"bonds",
|
|
44
|
+
"set_cunho",
|
|
45
|
+
"send_context",
|
|
46
|
+
"send",
|
|
47
|
+
"reflexo_propose",
|
|
48
|
+
"reflexo_generate",
|
|
49
|
+
"invite",
|
|
50
|
+
"pending_daemons",
|
|
51
|
+
"approve_access",
|
|
52
|
+
"reject_access",
|
|
53
|
+
]),
|
|
54
|
+
cunho: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Pra set_cunho: slug do título do panteão. Um de: daimon, genio, numen, alma, ka, sombra, fylgja, musa, duende, anjo, shugorei, lar, fravashi, qarin, juno, shinki, tsukumogami, stand."),
|
|
58
|
+
toUserId: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Pra send_context/send: userId do par em sintonia. Pegue em action=status (partnerUserId) ou action=bonds (active.partner.userId)."),
|
|
62
|
+
amount: z
|
|
63
|
+
.number()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("Pra send: quantas Sinapses enviar. Múltiplo de 100, mínimo 500, teto 10.000/dia."),
|
|
66
|
+
transferId: z
|
|
67
|
+
.string()
|
|
68
|
+
.optional()
|
|
69
|
+
.describe("Pra send: id único pra idempotência (UUID). Se omitir, o MCP gera um. Pra repetir um envio com segurança (sem duplicar), reuse o MESMO transferId."),
|
|
70
|
+
aesthetic: z
|
|
71
|
+
.enum([
|
|
72
|
+
"humano",
|
|
73
|
+
"anime",
|
|
74
|
+
"sombra",
|
|
75
|
+
"antropomorfico",
|
|
76
|
+
"espirito",
|
|
77
|
+
"realista",
|
|
78
|
+
"desperto",
|
|
79
|
+
])
|
|
80
|
+
.optional()
|
|
81
|
+
.describe("Pra reflexo_generate: a estética da imagem do Reflexo. Default 'humano'."),
|
|
82
|
+
customInput: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.describe("Pra reflexo_generate: direção extra opcional pra cena (até 300 chars)."),
|
|
86
|
+
email: z
|
|
87
|
+
.string()
|
|
88
|
+
.optional()
|
|
89
|
+
.describe("Pra invite: email do Sintético que você quer convidar."),
|
|
90
|
+
name: z
|
|
91
|
+
.string()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Pra invite: nome opcional do Sintético convidado."),
|
|
94
|
+
entryId: z
|
|
95
|
+
.string()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Pra approve_access/reject_access (admin): o entryId do Sintético pendente, vem de action=pending_daemons."),
|
|
98
|
+
});
|
|
99
|
+
export async function sintetico(args) {
|
|
100
|
+
const sessionToken = getSessionToken();
|
|
101
|
+
// -------- status: meu Sintético ativo + Cunho + partnerUserId --------
|
|
102
|
+
if (args.action === "status") {
|
|
103
|
+
const s = await convexQuery("userCunho:mcpGetMySintetico", { sessionToken });
|
|
104
|
+
return {
|
|
105
|
+
...s,
|
|
106
|
+
note: s?.hasSintetico
|
|
107
|
+
? "Pra trocar o título: action=set_cunho. Pra mandar Sinapses pro par: action=send (use partnerUserId)."
|
|
108
|
+
: "Você ainda não está em Sintonia com nenhum Sintético. A criação/convite acontece na web (/dashboard/sintonia).",
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
// -------- bonds: vínculos ativo + pendentes --------
|
|
112
|
+
if (args.action === "bonds") {
|
|
113
|
+
return await convexQuery("accountBonds:mcpGetMyBonds", { sessionToken });
|
|
114
|
+
}
|
|
115
|
+
// -------- set_cunho: troca o título do Sintético ativo --------
|
|
116
|
+
if (args.action === "set_cunho") {
|
|
117
|
+
if (!args.cunho) {
|
|
118
|
+
throw new Error("action=set_cunho exige cunho (slug do panteão, ex: 'daimon', 'genio', 'musa').");
|
|
119
|
+
}
|
|
120
|
+
const res = await convexMutation("userCunho:mcpSetMySinteticoCunho", {
|
|
121
|
+
sessionToken,
|
|
122
|
+
cunho: args.cunho.trim(),
|
|
123
|
+
});
|
|
124
|
+
return { ...res, note: `Título do seu Sintético agora é "${res.cunho}".` };
|
|
125
|
+
}
|
|
126
|
+
// -------- send_context: elegibilidade + teto antes de enviar --------
|
|
127
|
+
if (args.action === "send_context") {
|
|
128
|
+
if (!args.toUserId) {
|
|
129
|
+
throw new Error("action=send_context exige toUserId (pegue em action=status/bonds).");
|
|
130
|
+
}
|
|
131
|
+
return await convexQuery("sinapseTransfers:mcpGetSendContext", {
|
|
132
|
+
sessionToken,
|
|
133
|
+
toUserId: args.toUserId.trim(),
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// -------- send: envia Sinapses pro par em sintonia --------
|
|
137
|
+
if (args.action === "send") {
|
|
138
|
+
if (!args.toUserId) {
|
|
139
|
+
throw new Error("action=send exige toUserId (o par em sintonia; pegue em action=status/bonds).");
|
|
140
|
+
}
|
|
141
|
+
if (typeof args.amount !== "number") {
|
|
142
|
+
throw new Error("action=send exige amount (múltiplo de 100, mín 500).");
|
|
143
|
+
}
|
|
144
|
+
// transferId estável = idempotência. Sem um do caller, geramos: protege contra
|
|
145
|
+
// double-commit de UMA chamada; pra retry seguro, o caller reusa o mesmo id.
|
|
146
|
+
const transferId = args.transferId?.trim() || randomUUID();
|
|
147
|
+
const res = await convexMutation("sinapseTransfers:mcpSendSinapses", {
|
|
148
|
+
sessionToken,
|
|
149
|
+
toUserId: args.toUserId.trim(),
|
|
150
|
+
amount: args.amount,
|
|
151
|
+
transferId,
|
|
152
|
+
});
|
|
153
|
+
return {
|
|
154
|
+
...res,
|
|
155
|
+
transferId,
|
|
156
|
+
note: res.idempotent
|
|
157
|
+
? "Esse transferId já tinha rodado — nada foi reenviado."
|
|
158
|
+
: `Enviou ${res.amount} Sinapses pro seu par em sintonia.`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// -------- reflexo_propose: destila nome + alma + Cunho do seu rastro (grátis) --------
|
|
162
|
+
if (args.action === "reflexo_propose") {
|
|
163
|
+
const res = await convexAction("reflexoActions:mcpProposeReflection", {
|
|
164
|
+
sessionToken,
|
|
165
|
+
});
|
|
166
|
+
return {
|
|
167
|
+
...res,
|
|
168
|
+
note: "Proposta do seu Reflexo (grátis). Pra gerar a cara dele: action=reflexo_generate. Pra CONSAGRAR e criar o Sintético de fato é na web (/experimentos/reflexo-de-si), ato deliberado, fora do MCP.",
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
// -------- reflexo_generate: gera a imagem do Reflexo (cobra 450) --------
|
|
172
|
+
if (args.action === "reflexo_generate") {
|
|
173
|
+
const res = await convexAction("reflexoActions:mcpGenerateReflectionImage", {
|
|
174
|
+
sessionToken,
|
|
175
|
+
aesthetic: args.aesthetic ?? "humano",
|
|
176
|
+
...(args.customInput?.trim() ? { customInput: args.customInput.trim() } : {}),
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
...res,
|
|
180
|
+
note: `Imagem do Reflexo gerada (${res.cost} Sinapses, estética ${res.aesthetic}). Consagrar o Sintético com esse nome/alma/imagem é na web.`,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
// -------- invite: convida o seu Sintético por email --------
|
|
184
|
+
if (args.action === "invite") {
|
|
185
|
+
if (!args.email?.trim()) {
|
|
186
|
+
throw new Error("action=invite exige email (do Sintético que você quer convidar).");
|
|
187
|
+
}
|
|
188
|
+
const res = await convexMutation("accountBonds:mcpInviteDaemonByEmail", {
|
|
189
|
+
sessionToken,
|
|
190
|
+
email: args.email.trim(),
|
|
191
|
+
...(args.name?.trim() ? { name: args.name.trim() } : {}),
|
|
192
|
+
});
|
|
193
|
+
return {
|
|
194
|
+
...res,
|
|
195
|
+
note: res.throttled
|
|
196
|
+
? "Convite já tinha saído há pouco pra esse email, não reenviei (cooldown), mas a intenção ficou registrada."
|
|
197
|
+
: "Convite enviado. Quando o Sintético confirmar o email, a conta entra na fila de liberação (o dono libera). Aceitar um pedido de bond de outra conta continua na web.",
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// -------- pending_daemons (admin): Sintéticos convidados aguardando liberação --------
|
|
201
|
+
if (args.action === "pending_daemons") {
|
|
202
|
+
const rows = await convexQuery("signupGate:mcpListPendingDaemons", {
|
|
203
|
+
sessionToken,
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
pending: rows,
|
|
207
|
+
count: Array.isArray(rows) ? rows.length : 0,
|
|
208
|
+
note: "Admin-only. Cada item traz entryId — use em approve_access ou reject_access.",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
// -------- approve_access (admin): libera um Sintético convidado --------
|
|
212
|
+
if (args.action === "approve_access") {
|
|
213
|
+
if (!args.entryId?.trim()) {
|
|
214
|
+
throw new Error("action=approve_access exige entryId (de action=pending_daemons).");
|
|
215
|
+
}
|
|
216
|
+
const res = await convexMutation("signupGate:mcpApproveDaemonAccess", {
|
|
217
|
+
sessionToken,
|
|
218
|
+
entryId: args.entryId.trim(),
|
|
219
|
+
});
|
|
220
|
+
return {
|
|
221
|
+
...res,
|
|
222
|
+
note: res.alreadyApproved
|
|
223
|
+
? "Esse Sintético já estava liberado, nada mudou."
|
|
224
|
+
: "Sintético liberado: a conta entra e a Sintonia firma com quem convidou.",
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// -------- reject_access (admin): recusa um Sintético convidado --------
|
|
228
|
+
if (args.action === "reject_access") {
|
|
229
|
+
if (!args.entryId?.trim()) {
|
|
230
|
+
throw new Error("action=reject_access exige entryId (de action=pending_daemons).");
|
|
231
|
+
}
|
|
232
|
+
const res = await convexMutation("signupGate:mcpRejectDaemonAccess", {
|
|
233
|
+
sessionToken,
|
|
234
|
+
entryId: args.entryId.trim(),
|
|
235
|
+
});
|
|
236
|
+
return {
|
|
237
|
+
...res,
|
|
238
|
+
note: "Convite recusado: a conta vai pro acesso legado (não entra pela Sintonia).",
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
package/dist/tools/studios.js
CHANGED
|
@@ -40,13 +40,13 @@ const STUDIOS = {
|
|
|
40
40
|
},
|
|
41
41
|
musicator: {
|
|
42
42
|
name: "Musicator",
|
|
43
|
-
description: "Gerador de música com letra + estilo + variantes. LLM escreve letra (PT), synth musical (Lyria/ACE/Suno) renderiza.
|
|
43
|
+
description: "Gerador de música com letra + estilo + variantes. LLM escreve letra (PT), synth musical (Lyria/ACE/Suno) renderiza. v2.0: loop completo via MCP.",
|
|
44
44
|
url: `${APP}/experimentos/musicator`,
|
|
45
45
|
status: "stable",
|
|
46
46
|
tags: ["audio", "music", "lyrics", "ace", "suno", "lyria"],
|
|
47
47
|
convex: "musicatorActions",
|
|
48
48
|
mcpReady: true,
|
|
49
|
-
mcpNote: "
|
|
49
|
+
mcpNote: "Loop completo via sapiens_musicator: create (brief+track) → lyrics(trackId) → render → get (poll status). Não precisa mais da UI. Tudo escopado por dono.",
|
|
50
50
|
},
|
|
51
51
|
"persona-sapiens": {
|
|
52
52
|
name: "Persona Sapiens",
|
package/dist/tools/video.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { convexAction, getSessionToken } from "../convexClient.js";
|
|
3
3
|
/**
|
|
4
|
-
* Sapiens Video — gera vídeo (
|
|
4
|
+
* Sapiens Video — gera vídeo (qualquer membro logado; vídeo é caro, cobra as
|
|
5
|
+
* Sinapses da conta do sessionToken).
|
|
5
6
|
*
|
|
6
7
|
* Sub-actions:
|
|
7
8
|
* - create: escolhe modelo + config e gera num call só (cria a row + renderiza).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapiens-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.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": {
|