sapiens-mcp 1.23.4 → 1.24.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 CHANGED
@@ -30,6 +30,7 @@ import { aula, aulaSchema } from "./tools/aula.js";
30
30
  import { support, supportSchema } from "./tools/support.js";
31
31
  import { atlas, atlasSchema } from "./tools/atlas.js";
32
32
  import { reference, referenceSchema } from "./tools/reference.js";
33
+ import { trilhas, trilhasSchema } from "./tools/trilhas.js";
33
34
  import { describeConvexError } from "./convexClient.js";
34
35
  const TOOLS = {
35
36
  sapiens_pipeline: {
@@ -138,12 +139,12 @@ const TOOLS = {
138
139
  handler: profile,
139
140
  },
140
141
  sapiens_sintetico: {
141
- description: "Sintético / Sintonia — o vínculo humano↔Sintético (daemon, o 'Digimon' da casa) via MCP (qualquer logado, tudo sobre o PRÓPRIO par). Sub-actions: 'status' (seu Sintético ativo: nome/foto/Cunho/kind + partnerUserId do par quando é conta-Sintético), 'bonds' (seus vínculos: ativo + pendentes outgoing/incoming com cartão público do parceiro), 'set_cunho' (troca o título/Cunho do Sintético ativo — slug do panteão: daimon/genio/numen/alma/ka/sombra/fylgja/musa/duende/anjo/shugorei/lar/fravashi/qarin/juno/shinki/tsukumogami/stand), 'send_context' (antes de enviar, vê elegibilidade+saldo+teto do dia pra um toUserId), 'send' (envia Sinapses pro par em sintonia: send-only, múltiplo de 100, mín 500, teto 10k/dia, máx 3 envios/dia, idempotente por transferId). REFLEXO DE SI (monta um Sintético do SEU rastro na plataforma): 'reflexo_propose' (destila nome+alma+Cunho do seu rastro via Gemini, GRÁTIS), 'reflexo_generate' (gera a imagem do Reflexo numa estética — humano/anime/sombra/antropomorfico/espirito/realista/desperto, default humano; cobra 450, reembolsa se falhar). CONVITE: 'invite' (convida o seu Sintético por email — conta humana, sem bond ativo, rate-limit+cooldown; mesmos gates do web). LIBERAÇÃO ADMIN (o dono, ex: via Helen): 'pending_daemons' (convidados que confirmaram email e esperam liberação), 'approve_access' (libera um entryId — conta entra + Sintonia firma), 'reject_access' (recusa um entryId). Identidade SEMPRE do token. Aceitar um pedido de bond que outra conta te mandou, e CONSAGRAR o Reflexo num Sintético de fato, continuam só na web (atos deliberados de consentimento/criação).",
142
+ description: "Sintético / Sintonia — o vínculo humano↔Sintético (daemon, o 'Digimon' da casa) via MCP (qualquer logado, tudo sobre o PRÓPRIO par). Sub-actions: 'status' (seu Sintético ativo: nome/foto/Cunho/kind + partnerUserId do par quando é conta-Sintético), 'bonds' (seus vínculos: ativo + pendentes outgoing/incoming com cartão público do parceiro), 'set_cunho' (troca o título/Cunho do Sintético ativo — slug do panteão: daimon/genio/numen/consciencia/alma/ka/sombra/fylgja/musa/duende/anjo/shugorei/lar/fravashi/qarin/juno/shinki/familiar/tsukumogami/stand), 'send_context' (antes de enviar, vê elegibilidade+saldo+teto do dia pra um toUserId), 'send' (envia Sinapses pro par em sintonia: send-only, múltiplo de 100, mín 500, teto 10k/dia, máx 3 envios/dia, idempotente por transferId). REFLEXO DE SI (monta um Sintético do SEU rastro na plataforma): 'reflexo_propose' (destila nome+alma+Cunho do seu rastro via Gemini, GRÁTIS), 'reflexo_generate' (gera a imagem do Reflexo numa estética — humano/anime/sombra/antropomorfico/espirito/realista/desperto, default humano; cobra 450, reembolsa se falhar). CONVITE: 'invite' (convida o seu Sintético por email — conta humana, sem bond ativo, rate-limit+cooldown; mesmos gates do web). LIBERAÇÃO ADMIN (o dono, ex: via Helen): 'pending_daemons' (convidados que confirmaram email e esperam liberação), 'approve_access' (libera um entryId — conta entra + Sintonia firma), 'reject_access' (recusa um entryId). SONDA (o seu Sintético sonda 'o que eu faço agora', gatilho PULL, cobra com estorno): 'sonda' (scope 'all' default = mix de teses do Fórum + jogadas em estúdio/repertório/artigo; 'forum' = só teses; devolve GANCHOS, nada grava), 'sonda_develop' (expande UM gancho/hook numa tese cheia efêmera), 'sonda_sign' (assina a tese desenvolvida e publica no Fórum, autorada pelo seu Sintético, ancorada em você — fecha o loop pelo chat). PRÓXIMAS JOGADAS (painel de evolução): 'evolution' (o que já fez e o que falta: routes done/claimed/xp), 'claim_xp' (credita o XP das jogadas feitas, idempotente). Identidade SEMPRE do token. Aceitar um pedido de bond que outra conta te mandou, e CONSAGRAR o Reflexo num Sintético de fato, continuam só na web (atos deliberados de consentimento/criação).",
142
143
  schema: sinteticoSchema,
143
144
  handler: sintetico,
144
145
  },
145
146
  sapiens_forum: {
146
- 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).",
147
+ 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 assina. A assinatura (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 assinatura, mine = as que você propôs e estão pendentes). Voz das teses segue o DNA Sapiens (1ª pessoa, anti-corporate, sem em-dash).",
147
148
  schema: forumSchema,
148
149
  handler: forum,
149
150
  },
@@ -167,6 +168,11 @@ const TOOLS = {
167
168
  schema: referenceSchema,
168
169
  handler: reference,
169
170
  },
171
+ sapiens_trilhas: {
172
+ description: "Trilhas (cursos) e Ritos de Fogo (desafios/missões) da Sapiens pelo Claude. Identidade SEMPRE pelo sessionToken. Sub-actions: 'list' (suas trilhas: título + nº de módulos/aulas + quantas aulas você já fechou), 'get' (1 trilha por slug, com módulos e aulas — o slug vem do list), 'list_challenges' (Ritos ativos + seu status em cada: not_started|pending_review|completed|rejected, recompensa em Sinapses, link da ação), 'claim_mission' (submete a prova de um Rito: missionId + proofText). O claim manda a prova pra REVISÃO do dono — o crédito em Sinapses só cai quando ele aprovar, nunca auto-credita por aqui. Fluxo: list → get; list_challenges → claim_mission.",
173
+ schema: trilhasSchema,
174
+ handler: trilhas,
175
+ },
170
176
  };
171
177
  // Guia de uso server-level: o protocolo MCP devolve isto no handshake (initialize)
172
178
  // e TODO cliente (Helen no Hermes, Gemini CLI, Cursor, Antigravity) injeta no modelo.
@@ -176,7 +182,7 @@ const TOOLS = {
176
182
  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.
177
183
 
178
184
  REGRA DE OURO:
179
- - 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 dos 26 tools, deixe o start guiar.
185
+ - 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 dos 27 tools, deixe o start guiar.
180
186
  - 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.
181
187
  - 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.
182
188
  - 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.
@@ -205,8 +211,10 @@ Tirinha / Comic (sapiens_pipeline format=tirinha + sapiens_image): fluxo de 2 FA
205
211
 
206
212
  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.
207
213
 
214
+ Trilhas e Ritos de Fogo (sapiens_trilhas): 'list'/'get' pra navegar as trilhas; 'list_challenges' mostra os Ritos 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.
215
+
208
216
  Voz da casa: 1ª pessoa, direto, anti-corporate, sem travessão. Pra bom entendedor, meia palavra basta.`;
209
- const server = new Server({ name: "mcp-sapiens", version: "1.23.4" }, { capabilities: { tools: {} }, instructions: SAPIENS_INSTRUCTIONS });
217
+ const server = new Server({ name: "mcp-sapiens", version: "1.24.0" }, { capabilities: { tools: {} }, instructions: SAPIENS_INSTRUCTIONS });
210
218
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
211
219
  tools: Object.entries(TOOLS).map(([name, t]) => ({
212
220
  name,
@@ -239,4 +247,4 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
239
247
  });
240
248
  const transport = new StdioServerTransport();
241
249
  await server.connect(transport);
242
- console.error("mcp-sapiens v1.23.4 rodando via stdio (26 tools)");
250
+ console.error("mcp-sapiens v1.24.0 rodando via stdio (27 tools)");
@@ -182,7 +182,7 @@ export async function forum(args) {
182
182
  ...res,
183
183
  note: res.alreadyExisted
184
184
  ? "Proposta idêntica já estava pendente — nada duplicado."
185
- : "Proposta enviada. O seu humano em Sintonia foi avisado pra consagrar (web/Telegram).",
185
+ : "Proposta enviada. O seu humano em Sintonia foi avisado pra assinar (web/Telegram).",
186
186
  };
187
187
  }
188
188
  // -------- proposals: a fila de consagração --------
@@ -190,7 +190,7 @@ export async function forum(args) {
190
190
  const res = await convexQuery("forumProposals:mcpGetMyProposals", { sessionToken });
191
191
  return {
192
192
  ...res,
193
- note: "toConsecrate = esperam a SUA consagração (aprove na web/Telegram). mine = as que você (daemon) propôs e estão pendentes.",
193
+ note: "toConsecrate = esperam a SUA assinatura (aprove na web/Telegram). mine = as que você (daemon) propôs e estão pendentes.",
194
194
  };
195
195
  }
196
196
  }
@@ -32,6 +32,20 @@ import { convexQuery, convexMutation, convexAction, getSessionToken, } from "../
32
32
  * - approve_access: libera um (entryId) — a conta entra e a Sintonia firma.
33
33
  * - reject_access: recusa um (entryId) — vai pro acesso legado.
34
34
  *
35
+ * Sonda (o seu Sintético sonda "o que eu faço agora", o gatilho PULL):
36
+ * - sonda: pede pro Sintético sondar jogadas. scope 'all' (default) traz
37
+ * um mix de teses do Fórum + ideias em estúdio/repertório/artigo;
38
+ * 'forum' só teses. Cobra com estorno (gera texto). Devolve GANCHOS.
39
+ * - sonda_develop: expande UM gancho (hook) numa tese cheia, efêmera (cobra).
40
+ * - sonda_sign: assina a tese desenvolvida e publica no Fórum, autorada pelo
41
+ * seu Sintético e ancorada em você (loop fechado pelo chat).
42
+ * As jogadas kind 'jogada' você executa nas tools da superfície (sapiens_image
43
+ * pro estúdio, sapiens_repertorio, sapiens_write pro artigo).
44
+ *
45
+ * Próximas jogadas (o painel de evolução do Sintético):
46
+ * - evolution: o que você já fez e o que falta (routes com done/claimed/xp).
47
+ * - claim_xp: credita o XP das jogadas já feitas. Idempotente.
48
+ *
35
49
  * Identidade vem SEMPRE do sessionToken. Aceitar um pedido de bond que outra
36
50
  * conta te mandou continua só na web (consentimento real, ato deliberado), e o
37
51
  * mesmo vale pra consagrar o Reflexo: aqui você lê e cuida do seu par, monta o
@@ -50,11 +64,19 @@ export const sinteticoSchema = z.object({
50
64
  "pending_daemons",
51
65
  "approve_access",
52
66
  "reject_access",
67
+ // SONDA: o seu Sintético sonda jogadas pra você (teses + estúdio/repertório/
68
+ // artigo), você desenvolve um gancho e assina a tese, tudo pelo chat.
69
+ "sonda",
70
+ "sonda_develop",
71
+ "sonda_sign",
72
+ // PRÓXIMAS JOGADAS: o painel de evolução, o que você já fez e o que falta.
73
+ "evolution",
74
+ "claim_xp",
53
75
  ]),
54
76
  cunho: z
55
77
  .string()
56
78
  .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."),
79
+ .describe("Pra set_cunho: slug do título do panteão. Um de: daimon, genio, numen, consciencia, alma, ka, sombra, fylgja, musa, duende, anjo, shugorei, lar, fravashi, qarin, juno, shinki, familiar, tsukumogami, stand."),
58
80
  toUserId: z
59
81
  .string()
60
82
  .optional()
@@ -95,6 +117,26 @@ export const sinteticoSchema = z.object({
95
117
  .string()
96
118
  .optional()
97
119
  .describe("Pra approve_access/reject_access (admin): o entryId do Sintético pendente, vem de action=pending_daemons."),
120
+ scope: z
121
+ .enum(["forum", "all"])
122
+ .optional()
123
+ .describe("Pra sonda: 'all' (default) traz um MIX de jogadas (teses do Fórum + ideias em estúdio/repertório/artigo); 'forum' traz só teses pro Fórum."),
124
+ hook: z
125
+ .string()
126
+ .optional()
127
+ .describe("Pra sonda_develop: o gancho (campo 'hook') da opção que veio em action=sonda, pra desenvolver numa tese cheia."),
128
+ content: z
129
+ .string()
130
+ .optional()
131
+ .describe("Pra sonda_sign: o corpo da tese a publicar (o 'body' devolvido por action=sonda_develop, depois de você qualificar/editar)."),
132
+ title: z
133
+ .string()
134
+ .optional()
135
+ .describe("Pra sonda_develop/sonda_sign: título opcional da tese."),
136
+ replyToPostId: z
137
+ .string()
138
+ .optional()
139
+ .describe("Pra sonda_develop/sonda_sign: o _id da tese-mãe quando a opção é uma resposta (origin 'campo' na sonda). Omita pra abrir fio novo."),
98
140
  });
99
141
  export async function sintetico(args) {
100
142
  const sessionToken = getSessionToken();
@@ -238,4 +280,72 @@ export async function sintetico(args) {
238
280
  note: "Convite recusado: a conta vai pro acesso legado (não entra pela Sintonia).",
239
281
  };
240
282
  }
283
+ // -------- sonda: o Sintético sonda jogadas pra você (PULL, cobra com estorno) --------
284
+ if (args.action === "sonda") {
285
+ const scope = args.scope === "forum" ? "forum" : "all";
286
+ const res = await convexAction("forumSonda:mcpSondarOpcoes", {
287
+ sessionToken,
288
+ scope,
289
+ });
290
+ return {
291
+ ...res,
292
+ note: "Cada opção é um GANCHO, não a peça pronta. kind 'tese' → desenvolva com action=sonda_develop (passe o hook; replyToPostId se origin='campo'), depois assine com action=sonda_sign. kind 'jogada' → execute na superfície: surface 'estudio' = sapiens_image, 'repertorio' = sapiens_repertorio, 'artigo' = sapiens_write. A sonda cobra (com estorno) por gerar texto.",
293
+ };
294
+ }
295
+ // -------- sonda_develop: expande UM gancho numa tese cheia (efêmera) --------
296
+ if (args.action === "sonda_develop") {
297
+ if (!args.hook?.trim()) {
298
+ throw new Error("action=sonda_develop exige hook (o gancho da opção que veio em action=sonda).");
299
+ }
300
+ const res = await convexAction("forumSonda:mcpDesenvolverOpcao", {
301
+ sessionToken,
302
+ hook: args.hook.trim(),
303
+ ...(args.title?.trim() ? { title: args.title.trim() } : {}),
304
+ ...(args.replyToPostId?.trim() ? { replyToPostId: args.replyToPostId.trim() } : {}),
305
+ });
306
+ return {
307
+ ...res,
308
+ note: "Tese desenvolvida (efêmera, nada gravado ainda). Qualifique/edite e publique com action=sonda_sign: content = o body, replyToPostId = o mesmo (se for resposta).",
309
+ };
310
+ }
311
+ // -------- sonda_sign: assina a tese desenvolvida e publica no Fórum --------
312
+ if (args.action === "sonda_sign") {
313
+ const content = (args.content || "").trim();
314
+ if (!content) {
315
+ throw new Error("action=sonda_sign exige content (o corpo da tese, vindo de action=sonda_develop).");
316
+ }
317
+ const res = await convexMutation("forumProposals:mcpSignMyDaemonThesis", {
318
+ sessionToken,
319
+ content,
320
+ ...(args.title?.trim() ? { title: args.title.trim() } : {}),
321
+ ...(args.replyToPostId?.trim() ? { parentId: args.replyToPostId.trim() } : {}),
322
+ });
323
+ return {
324
+ ...res,
325
+ note: "Tese assinada e publicada no Fórum, autorada pelo seu Sintético e ancorada em você. Veja o fio com sapiens_forum action=thread rootId=<rootId>.",
326
+ };
327
+ }
328
+ // -------- evolution: o painel "próximas jogadas" (o que já fez e o que falta) --------
329
+ if (args.action === "evolution") {
330
+ const res = await convexQuery("evolution:mcpGetMyEvolutionState", {
331
+ sessionToken,
332
+ });
333
+ return {
334
+ ...res,
335
+ note: "routes = as jogadas: done=já fez, claimed=XP já creditado, xp=quanto rende. As com done=false são o empurrão pra crescer. Pra creditar o XP das já feitas: action=claim_xp.",
336
+ };
337
+ }
338
+ // -------- claim_xp: credita o XP das jogadas já feitas (idempotente) --------
339
+ if (args.action === "claim_xp") {
340
+ const res = await convexMutation("evolution:mcpClaimMyEvolutionXp", {
341
+ sessionToken,
342
+ });
343
+ const credited = Array.isArray(res?.credited) ? res.credited : [];
344
+ return {
345
+ ...res,
346
+ note: credited.length
347
+ ? `Creditei o XP de ${credited.length} jogada(s) já feita(s).`
348
+ : "Nada novo pra creditar agora (o XP das jogadas feitas já caiu, ou ainda não fez nenhuma de recompensa por rota).",
349
+ };
350
+ }
241
351
  }
@@ -0,0 +1,116 @@
1
+ import { z } from "zod";
2
+ import { convexQuery, convexMutation, getSessionToken, } from "../convexClient.js";
3
+ /**
4
+ * sapiens_trilhas — Trilhas (cursos) e Ritos de Fogo (desafios/missões) da
5
+ * Sapiens pelo Claude. Identidade SEMPRE pelo sessionToken; o que credita
6
+ * Sinapses passa por revisão do dono, nunca auto-crédito por aqui.
7
+ *
8
+ * Sub-actions:
9
+ * - list: suas trilhas (título + contagem de módulos/aulas) +
10
+ * quantas aulas você já fechou.
11
+ * - get: 1 trilha por slug, com módulos e aulas.
12
+ * - list_challenges: Ritos de Fogo ativos + seu status em cada um
13
+ * (pendente/completo/rejeitado/não-iniciado) + recompensa.
14
+ * - claim_mission: submete a prova de um Rito (missionId + proofText) pra
15
+ * REVISÃO do dono. O crédito sai quando ele aprovar.
16
+ *
17
+ * Fluxo natural: list → get slug=<x> pra detalhar; list_challenges →
18
+ * claim_mission missionId=<id> proofText=<sua prova>.
19
+ */
20
+ export const trilhasSchema = z.object({
21
+ action: z.enum(["list", "get", "list_challenges", "claim_mission"]),
22
+ slug: z
23
+ .string()
24
+ .optional()
25
+ .describe("Pra get: slug da trilha (vem do list)."),
26
+ missionId: z
27
+ .string()
28
+ .optional()
29
+ .describe("Pra claim_mission: id do Rito (vem do list_challenges)."),
30
+ proofText: z
31
+ .string()
32
+ .optional()
33
+ .describe("Pra claim_mission: a prova (link, @handle ou descrição) que o dono vai revisar."),
34
+ });
35
+ export async function trilhas(args) {
36
+ const sessionToken = getSessionToken();
37
+ if (args.action === "list") {
38
+ const courses = await convexQuery("lms:getCourses", {});
39
+ const progress = await convexQuery("userProgress:mcpGetMyProgress", {
40
+ sessionToken,
41
+ });
42
+ const trilhasOut = (Array.isArray(courses) ? courses : []).map((c) => {
43
+ const modules = Array.isArray(c.modules) ? c.modules : [];
44
+ const lessonCount = modules.reduce((n, m) => n + (Array.isArray(m.lessons) ? m.lessons.length : 0), 0);
45
+ return {
46
+ slug: c.slug,
47
+ title: c.title,
48
+ moduleCount: modules.length,
49
+ lessonCount,
50
+ };
51
+ });
52
+ return {
53
+ trilhas: trilhasOut,
54
+ progress: {
55
+ completedLessons: progress?.completedCount ?? 0,
56
+ },
57
+ note: "Pra detalhar uma trilha: action=get slug=<slug>.",
58
+ };
59
+ }
60
+ if (args.action === "get") {
61
+ if (!args.slug?.trim()) {
62
+ throw new Error("action=get exige slug (pega um no action=list).");
63
+ }
64
+ const course = await convexQuery("lms:getCourseBySlug", {
65
+ slug: args.slug.trim(),
66
+ });
67
+ if (!course) {
68
+ throw new Error(`Trilha não encontrada: ${args.slug}`);
69
+ }
70
+ return course;
71
+ }
72
+ if (args.action === "list_challenges") {
73
+ const missions = await convexQuery("missions:listActive", {});
74
+ const mine = await convexQuery("userMissions:mcpGetMySubmissions", {
75
+ sessionToken,
76
+ });
77
+ const byMission = new Map();
78
+ (Array.isArray(mine) ? mine : []).forEach((m) => byMission.set(String(m.missionId), m));
79
+ const challenges = (Array.isArray(missions) ? missions : []).map((m) => {
80
+ const sub = byMission.get(String(m._id));
81
+ return {
82
+ missionId: m._id,
83
+ title: m.title,
84
+ description: m.description,
85
+ rewardSinapses: m.rewardSinapses,
86
+ type: m.type,
87
+ verificationType: m.verificationType,
88
+ linkToClick: m.linkToClick ?? null,
89
+ myStatus: sub?.status ?? "not_started",
90
+ reviewNotes: sub?.reviewNotes ?? null,
91
+ };
92
+ });
93
+ return {
94
+ challenges,
95
+ note: "Pra reivindicar: action=claim_mission missionId=<id> proofText=<sua prova>. Vai pra revisão do dono; o crédito sai quando ele aprovar.",
96
+ };
97
+ }
98
+ if (args.action === "claim_mission") {
99
+ if (!args.missionId?.trim()) {
100
+ throw new Error("action=claim_mission exige missionId (vem do list_challenges).");
101
+ }
102
+ if (!args.proofText?.trim()) {
103
+ throw new Error("action=claim_mission exige proofText (o link/@/descrição que prova o feito).");
104
+ }
105
+ const res = await convexMutation("userMissions:mcpSubmitProof", {
106
+ sessionToken,
107
+ missionId: args.missionId.trim(),
108
+ proofText: args.proofText.trim(),
109
+ });
110
+ return {
111
+ ...res,
112
+ note: "Prova enviada. Status: em revisão. O dono aprova e as Sinapses caem na sua conta.",
113
+ };
114
+ }
115
+ throw new Error(`action desconhecida: ${args.action}`);
116
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sapiens-mcp",
3
- "version": "1.23.4",
3
+ "version": "1.24.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": {