sapiens-mcp 1.73.0 → 1.75.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/prompts.js +1 -1
- package/dist/registry.js +2 -2
- package/dist/skills.js +4 -2
- package/dist/tools/distribution.js +133 -2
- package/dist/tools/gallery.js +41 -6
- package/dist/tools/repertorio.js +2 -2
- package/package.json +1 -1
package/dist/prompts.js
CHANGED
|
@@ -19,7 +19,7 @@ export const SAPIENS_PROMPTS = [
|
|
|
19
19
|
{
|
|
20
20
|
name: "capturar-repertorio",
|
|
21
21
|
title: "Capturar obra no Repertório",
|
|
22
|
-
description: "Grava um filme/série/anime/jogo/livro/música no seu acervo pessoal (grátis).",
|
|
22
|
+
description: "Grava um filme/série/anime/jogo/jogo de tabuleiro/livro/música no seu acervo pessoal (grátis).",
|
|
23
23
|
arguments: [
|
|
24
24
|
{
|
|
25
25
|
name: "obra",
|
package/dist/registry.js
CHANGED
|
@@ -71,7 +71,7 @@ export const TOOLS = {
|
|
|
71
71
|
handler: skill,
|
|
72
72
|
},
|
|
73
73
|
sapiens_repertorio: {
|
|
74
|
-
description: "Acervo pessoal de filme/série/anime/jogo/livro/música/pessoa (Repertório, o segundo cérebro do user). Reads: list (filtros mediaType/status), search (texto em title/genres/tags), get (detalhe), popArticles, resolve (busca capa/ano/id nos providers server-side: OMDb/IGDB-Twitch/AniList/Google Books/iTunes/Wikidata). Mutations (qualquer logado, mexem no PRÓPRIO acervo): add_item, update_item (status/rating/tags/note/isPublic), remove_item. CAPTURA ONE-SHOT travada na lista de providers: quando o user fala natural ('acabei de ver Duna 2, nota 9', 'tô jogando Hollow Knight', 'li tal livro'), (1) infira mediaType e status (assisti/zerei/li=completed, quero=backlog, tô jogando/vendo=active, dropei=dropped) e rating se citado; (2) chame action=resolve {mediaType, query}, escolha o candidato certo e faça add_item passando SÓ o source + externalId DELE + os campos pessoais (status/rating/tags/note). O servidor re-resolve no provider e grava título/capa/ano canônicos — você NÃO manda título/capa nem inventa externalId. (3) Se o resolve não achar (lista vazia/providerKeyMissing), NÃO dá pra adicionar: diga ao user que não encontrou nos providers (não fabrique entry manual). Upsert/dedup por (userId, source, externalId). Só pergunte se ambíguo entre candidatos. FERRAMENTAS DE IA (Repertório de Ferramentas): fluxo separado (vêm do catálogo aitag, não dos providers de mídia). action=search_tools {query} acha a ferramenta no catálogo e devolve o toolId; action=add_tool {toolId, favorite?, rating?, note?} grava como mediaType 'tool' (estar no acervo já é 'usei'; favorite=true liga a estrela). Use quando o user fala 'adiciona o Midjourney/Cursor no meu repertório de ferramentas' ou 'uso tal ferramenta de IA'. Cada ferramenta aponta pra página dela no aitag. SAPIENS (pessoas, ombros de gigantes; a aba no site chama Sapiens): mediaType 'person' cataloga gente que inspira (cineasta, mangaká, cientista, escritor) via Wikidata. Mesmo fluxo de mídia: resolve {mediaType:'person', query:'Osamu Tezuka'} → add_item com source 'wikidata' + o QID do candidato. O servidor grava foto, ocupações, bio e o link da Wikipedia; entra como completed (estar no acervo = me inspira). Use quando o user fala 'adiciona o Miyazaki nas minhas inspirações/nos meus sapiens/no meu repertório'.",
|
|
74
|
+
description: "Acervo pessoal de filme/série/anime/jogo/jogo de tabuleiro/livro/música/pessoa (Repertório, o segundo cérebro do user). Reads: list (filtros mediaType/status), search (texto em title/genres/tags), get (detalhe), popArticles, resolve (busca capa/ano/id nos providers server-side: OMDb/IGDB-Twitch/AniList/Google Books/iTunes/Wikidata). Mutations (qualquer logado, mexem no PRÓPRIO acervo): add_item, update_item (status/rating/tags/note/isPublic), remove_item. CAPTURA ONE-SHOT travada na lista de providers: quando o user fala natural ('acabei de ver Duna 2, nota 9', 'tô jogando Hollow Knight', 'li tal livro'), (1) infira mediaType e status (assisti/zerei/li=completed, quero=backlog, tô jogando/vendo=active, dropei=dropped) e rating se citado; (2) chame action=resolve {mediaType, query}, escolha o candidato certo e faça add_item passando SÓ o source + externalId DELE + os campos pessoais (status/rating/tags/note). O servidor re-resolve no provider e grava título/capa/ano canônicos — você NÃO manda título/capa nem inventa externalId. (3) Se o resolve não achar (lista vazia/providerKeyMissing), NÃO dá pra adicionar: diga ao user que não encontrou nos providers (não fabrique entry manual). Upsert/dedup por (userId, source, externalId). Só pergunte se ambíguo entre candidatos. FERRAMENTAS DE IA (Repertório de Ferramentas): fluxo separado (vêm do catálogo aitag, não dos providers de mídia). action=search_tools {query} acha a ferramenta no catálogo e devolve o toolId; action=add_tool {toolId, favorite?, rating?, note?} grava como mediaType 'tool' (estar no acervo já é 'usei'; favorite=true liga a estrela). Use quando o user fala 'adiciona o Midjourney/Cursor no meu repertório de ferramentas' ou 'uso tal ferramenta de IA'. Cada ferramenta aponta pra página dela no aitag. SAPIENS (pessoas, ombros de gigantes; a aba no site chama Sapiens): mediaType 'person' cataloga gente que inspira (cineasta, mangaká, cientista, escritor) via Wikidata. Mesmo fluxo de mídia: resolve {mediaType:'person', query:'Osamu Tezuka'} → add_item com source 'wikidata' + o QID do candidato. O servidor grava foto, ocupações, bio e o link da Wikipedia; entra como completed (estar no acervo = me inspira). Use quando o user fala 'adiciona o Miyazaki nas minhas inspirações/nos meus sapiens/no meu repertório'. JOGO DE TABULEIRO (e de cartas; a aba no site chama Tabuleiro): mediaType 'boardgame', porque 'game' é videogame e o IGDB não conhece tabuleiro. Também via Wikidata: resolve {mediaType:'boardgame', query:'Galactic Cruise'} → add_item com source 'wikidata' + o QID do candidato. Busque pelo título em inglês, que é como a Wikidata cadastra; o designer vem no campo authors do candidato e separa homônimos. O servidor grava designers, editoras e o link da página do jogo no BGG. Lançamento muito recente pode ainda não estar na Wikidata: resolve vazio = diga isso à pessoa, não fabrique. Use quando o user fala 'joguei Catan', 'quero jogar Rebirth', 'adiciona esse jogo de tabuleiro'.",
|
|
75
75
|
schema: repertorioSchema,
|
|
76
76
|
handler: repertorio,
|
|
77
77
|
},
|
|
@@ -206,7 +206,7 @@ export const TOOLS = {
|
|
|
206
206
|
handler: trilhas,
|
|
207
207
|
},
|
|
208
208
|
sapiens_distribution: {
|
|
209
|
-
description: "Despachar peças (Distribution Workflow) — a peça pronta entra na FILA com canal, copy e a aba de quem ela é. ADMIN-ONLY: despacho depende de rede conectada e hoje só a casa tem. NADA aqui posta em rede nenhuma; quem despacha é a tela em /experimentos/distribution-workflow, que é onde a conta conectada mora. A ABA (`voice`) responde de quem é a peça: sapiens (a casa), helen-ailith, borderless, ou o slug de um personagem seu — slug livre, então personagem novo ganha aba sozinho na primeira peça dele, sem deploy. Em queue, aba ausente = o servidor deriva do personagem da peça e cai em sapiens quando não há. Sub-actions: queue (põe na fila: title e channel obrigatórios, mais copy/hashtags/assetUrl/notes). Em queue, DOIS campos que parecem opcionais e não são: `lang` diz em que língua a copy já está, e sem ele canal de rede internacional reescreve a sua copy sozinho, dois segundos depois, na voz da casa; `assetPageAssetId` é o imageId da obra da casa, e sem ele a peça só encontra a obra se a assetUrl bater letra a letra, então versão web de um master nasce órfã de ficha, list (o que está na fila, filtrável por status e por aba), voices (as abas que existem hoje com a contagem de cada uma), status (move a peça de lane: fila, agendado, postado, descartado; postado aceita postUrl e fecha o rastro, agendado aceita scheduledFor em ms), bio (lê ou troca a BIO de uma conta da casa no Bluesky, channel bluesky ou bluesky-2 a bluesky-5: sem texto só lê; bioLine acrescenta uma linha no fim sem mexer no resto e não repete linha que já está lá; bio reescreve a inteira; teto de 256; ESTA sub-action muda o perfil público na hora, e bio não aceita hiperlink, o endereço aparece escrito). Sem custo em Sinapses: isto organiza, não gera. A copy segue a voz de QUEM ASSINA a peça, que não é sempre a voz da casa: peça da Helen fala como a Helen. E a peça sai pela CONTA de quem assina ela: conta extra da mesma rede é um canal próprio (bluesky-2 é outra conta no Bluesky, instagram-pro outra no Instagram), então case o canal com a aba antes de despachar.",
|
|
209
|
+
description: "Despachar peças (Distribution Workflow) — a peça pronta entra na FILA com canal, copy e a aba de quem ela é. ADMIN-ONLY: despacho depende de rede conectada e hoje só a casa tem. NADA aqui posta em rede nenhuma; quem despacha é a tela em /experimentos/distribution-workflow, que é onde a conta conectada mora. A ABA (`voice`) responde de quem é a peça: sapiens (a casa), helen-ailith, borderless, ou o slug de um personagem seu — slug livre, então personagem novo ganha aba sozinho na primeira peça dele, sem deploy. Em queue, aba ausente = o servidor deriva do personagem da peça e cai em sapiens quando não há. Sub-actions: queue (põe na fila: title e channel obrigatórios, mais copy/hashtags/assetUrl/notes). Em queue, DOIS campos que parecem opcionais e não são: `lang` diz em que língua a copy já está, e sem ele canal de rede internacional reescreve a sua copy sozinho, dois segundos depois, na voz da casa; `assetPageAssetId` é o imageId da obra da casa, e sem ele a peça só encontra a obra se a assetUrl bater letra a letra, então versão web de um master nasce órfã de ficha, list (o que está na fila, filtrável por status e por aba), voices (as abas que existem hoje com a contagem de cada uma), status (move a peça de lane: fila, agendado, postado, descartado; postado aceita postUrl e fecha o rastro, agendado aceita scheduledFor em ms), bio (lê ou troca a BIO de uma conta da casa no Bluesky, channel bluesky ou bluesky-2 a bluesky-5: sem texto só lê; bioLine acrescenta uma linha no fim sem mexer no resto e não repete linha que já está lá; bio reescreve a inteira; teto de 256; ESTA sub-action muda o perfil público na hora, e bio não aceita hiperlink, o endereço aparece escrito), lineages (o CADASTRO das linhagens, o mesmo da aba Teia: de cada personagem, produto e da casa, a mãe no Bluesky onde a peça nasce, as contas próprias, as galerias que a mãe alimenta sozinha, as fichas e o post do dia, mais as regras que a teia desdobra e as vagas de conta ainda sem dona), lineage-new (nasce uma linhagem: nome obrigatório, grupo, artigo, lang; nasce sem mãe, então nada viaja sozinho até o lineage-save dar a mãe), lineage-save (slug e só o que muda: mae, contas rede->canal, galerias, fichaIds, rotina, contaNaCasa; o servidor recusa conta que já é de outra linhagem e canal de outra rede, e a vaga assumida ganha o nome da linhagem), lineage-archive (slug + arquivada). Sem custo em Sinapses: isto organiza, não gera. A copy segue a voz de QUEM ASSINA a peça, que não é sempre a voz da casa: peça da Helen fala como a Helen. E a peça sai pela CONTA de quem assina ela: conta extra da mesma rede é um canal próprio (bluesky-2 é outra conta no Bluesky, instagram-pro outra no Instagram), então case o canal com a aba antes de despachar.",
|
|
210
210
|
schema: distributionSchema,
|
|
211
211
|
handler: distribution,
|
|
212
212
|
},
|
package/dist/skills.js
CHANGED
|
@@ -137,7 +137,7 @@ Condensa a informação num framework com nome, ancora numa analogia concreta, e
|
|
|
137
137
|
{
|
|
138
138
|
name: "repertorio",
|
|
139
139
|
title: "Repertório: guardar obra e ferramenta no acervo",
|
|
140
|
-
description: "Guardar filme, série, anime, jogo, livro, música, pessoa ou ferramenta de IA no Repertório. Puxe ANTES de qualquer sapiens_repertorio: a gravação é travada num fluxo de dois passos e tentar guardar direto falha sempre.",
|
|
140
|
+
description: "Guardar filme, série, anime, jogo, jogo de tabuleiro, livro, música, pessoa ou ferramenta de IA no Repertório. Puxe ANTES de qualquer sapiens_repertorio: a gravação é travada num fluxo de dois passos e tentar guardar direto falha sempre.",
|
|
141
141
|
body: `## A regra que faz este fluxo falhar
|
|
142
142
|
|
|
143
143
|
Você NÃO grava uma obra passando o título. O servidor não aceita metadado vindo de você (título, capa, ano) de propósito: é anti-fabricação. Você identifica a obra num provider, e o servidor re-resolve por id e grava o canônico.
|
|
@@ -150,9 +150,11 @@ São sempre DOIS passos. Pular o primeiro falha sempre, e é o erro número um d
|
|
|
150
150
|
2. Escolha o candidato certo. Em dúvida entre dois, PERGUNTE em vez de chutar: obra errada no acervo é a pessoa quem vai ter que apagar.
|
|
151
151
|
3. **\`action=add_item\`** com o \`source\` e o \`externalId\` **exatos** daquele candidato, mais o que é pessoal: \`status\`, \`rating\` (0-10), \`tags\`, \`note\`, \`containsSpoilers\`, \`isPublic\`.
|
|
152
152
|
|
|
153
|
-
\`mediaType\`: movie, series, anime, game, book, music, tool, person.
|
|
153
|
+
\`mediaType\`: movie, series, anime, game, book, music, tool, person, boardgame.
|
|
154
154
|
\`status\`: backlog (quer ver), active (vendo agora), completed (viu), paused, dropped.
|
|
155
155
|
|
|
156
|
+
Jogo de tabuleiro (e de cartas) é \`boardgame\`: \`game\` é videogame, e o provider dele não conhece tabuleiro. A busca do tabuleiro é na Wikidata e acha pelo título em inglês; o designer vem em \`authors\` no candidato e separa homônimos. Lançamento muito recente pode ainda não estar cadastrado: resolve vazio quer dizer que o jogo não está na base, então diga isso à pessoa.
|
|
157
|
+
|
|
156
158
|
Nunca invente um \`externalId\` nem reaproveite um de outra busca: id que não resolve no provider é rejeitado. A dedup é por (você, source, externalId), então repetir o mesmo par não duplica.
|
|
157
159
|
|
|
158
160
|
## Guardar uma ferramenta de IA
|
|
@@ -22,6 +22,11 @@ import { convexQuery, convexMutation, convexAction, getSessionToken } from "../c
|
|
|
22
22
|
* - list: o que está na fila, filtrável por status e por aba.
|
|
23
23
|
* - voices: as abas que existem hoje, com a contagem de cada uma.
|
|
24
24
|
* - status: move a peça de lane (fila, agendado, postado, descartado).
|
|
25
|
+
* - lineages / lineage-new / lineage-save / lineage-archive: o CADASTRO das
|
|
26
|
+
* linhagens (personagens, produtos, a casa), o mesmo que a aba Teia edita:
|
|
27
|
+
* a mãe no Bluesky, as contas próprias, as galerias que a mãe alimenta, as
|
|
28
|
+
* fichas e o post do dia. É ele que decide por qual conta cada peça sai e
|
|
29
|
+
* o que viaja sozinho depois.
|
|
25
30
|
*
|
|
26
31
|
* Sem custo em Sinapses: isto organiza, não gera.
|
|
27
32
|
*/
|
|
@@ -119,7 +124,17 @@ const CHANNELS = [
|
|
|
119
124
|
"youtube-5",
|
|
120
125
|
];
|
|
121
126
|
export const distributionSchema = z.object({
|
|
122
|
-
action: z.enum([
|
|
127
|
+
action: z.enum([
|
|
128
|
+
"queue",
|
|
129
|
+
"list",
|
|
130
|
+
"voices",
|
|
131
|
+
"status",
|
|
132
|
+
"bio",
|
|
133
|
+
"lineages",
|
|
134
|
+
"lineage-new",
|
|
135
|
+
"lineage-save",
|
|
136
|
+
"lineage-archive",
|
|
137
|
+
]),
|
|
123
138
|
title: z
|
|
124
139
|
.string()
|
|
125
140
|
.optional()
|
|
@@ -160,7 +175,7 @@ export const distributionSchema = z.object({
|
|
|
160
175
|
lang: z
|
|
161
176
|
.enum(["pt", "en"])
|
|
162
177
|
.optional()
|
|
163
|
-
.describe("queue: em que LÍNGUA a copy JÁ ESTÁ. Declare sempre que escrever a copy você mesmo. Sem este campo, canal de rede internacional (bluesky, x, linkedin, farcaster, civitai, pinterest, reddit, medium, tumblr, deviantart) presume que a copy chegou em português e agenda uma vertida automática que SOBRESCREVE o que você mandou, dois segundos depois de enfileirar. Copy que já estava em inglês volta reescrita, e na voz da CASA em vez da voz de quem assina a peça, o que estraga copy de personagem. Declarar o idioma desliga a vertida e o texto fica exatamente como você mandou."),
|
|
178
|
+
.describe("lineage-new/lineage-save: a língua em que a linhagem fala. queue: em que LÍNGUA a copy JÁ ESTÁ. Declare sempre que escrever a copy você mesmo. Sem este campo, canal de rede internacional (bluesky, x, linkedin, farcaster, civitai, pinterest, reddit, medium, tumblr, deviantart) presume que a copy chegou em português e agenda uma vertida automática que SOBRESCREVE o que você mandou, dois segundos depois de enfileirar. Copy que já estava em inglês volta reescrita, e na voz da CASA em vez da voz de quem assina a peça, o que estraga copy de personagem. Declarar o idioma desliga a vertida e o texto fica exatamente como você mandou."),
|
|
164
179
|
voice: z
|
|
165
180
|
.string()
|
|
166
181
|
.optional()
|
|
@@ -200,6 +215,60 @@ export const distributionSchema = z.object({
|
|
|
200
215
|
.string()
|
|
201
216
|
.optional()
|
|
202
217
|
.describe("bio: UMA linha acrescentada no fim da bio atual, sem mexer no resto (ex: o CTA com t.me/<handle> do Telegram da personagem). Linha que já está na bio não entra de novo. Bio NÃO aceita hiperlink: o endereço aparece escrito e o app pinta de azul."),
|
|
218
|
+
slug: z
|
|
219
|
+
.string()
|
|
220
|
+
.optional()
|
|
221
|
+
.describe("lineage-save/lineage-archive: a linhagem (o slug que lineages devolve: sapiens, borderless, helen-inbt, aria, arak, giants...). lineage-new: opcional, sai do nome quando ausente."),
|
|
222
|
+
nome: z.string().optional().describe("lineage-new: o nome da linhagem (obrigatório). lineage-save: renomeia."),
|
|
223
|
+
grupo: z
|
|
224
|
+
.enum(["casa", "personagens", "produtos"])
|
|
225
|
+
.optional()
|
|
226
|
+
.describe("lineage-new/lineage-save: a gaveta. casa é só da casa e do dono. Default produtos."),
|
|
227
|
+
artigo: z.enum(["a", "o"]).optional().describe("lineage-new/lineage-save: o artigo do nome (a Aria, o Arak)."),
|
|
228
|
+
ordem: z.number().optional().describe("lineage-save: a posição na lista."),
|
|
229
|
+
quem: z
|
|
230
|
+
.string()
|
|
231
|
+
.nullable()
|
|
232
|
+
.optional()
|
|
233
|
+
.describe("lineage-save: como a tela diz de quem é (\"da casa\", \"do dono\"). null limpa."),
|
|
234
|
+
mae: z
|
|
235
|
+
.string()
|
|
236
|
+
.nullable()
|
|
237
|
+
.optional()
|
|
238
|
+
.describe("lineage-save: a MÃE da linhagem, a conta do Bluesky onde a peça nasce e de onde a teia desdobra pro resto (bluesky-2..9). Vaga livre sai de lineages (vagasLivres.bluesky). null tira a mãe. A mãe da casa e a do dono não trocam."),
|
|
239
|
+
contas: z
|
|
240
|
+
.record(z.string(), z.string())
|
|
241
|
+
.optional()
|
|
242
|
+
.describe("lineage-save: as contas PRÓPRIAS da linhagem, rede -> canal, ex: {\"telegram\":\"telegram-6\",\"instagram\":\"instagram-6\",\"x\":\"x-buffer-3\"}. Substitui o mapa inteiro. O servidor recusa canal de outra rede e canal que já é de outra linhagem."),
|
|
243
|
+
galerias: z
|
|
244
|
+
.object({
|
|
245
|
+
telegram: z.boolean(),
|
|
246
|
+
arena: z.boolean(),
|
|
247
|
+
tumblr: z.boolean(),
|
|
248
|
+
deviantart: z.boolean(),
|
|
249
|
+
civitai: z.boolean(),
|
|
250
|
+
proprias: z.array(z.string()),
|
|
251
|
+
})
|
|
252
|
+
.optional()
|
|
253
|
+
.describe("lineage-save: o que a mãe alimenta sozinha quando a peça dela vai ao ar. telegram exige conta de telegram em contas; proprias lista as redes de contas (instagram, x...) que também recebem. Manda o objeto inteiro."),
|
|
254
|
+
fichaIds: z
|
|
255
|
+
.array(z.string())
|
|
256
|
+
.optional()
|
|
257
|
+
.describe("lineage-save: as fichas de personagem da linhagem (ids de sapiens_character), a principal primeiro, até 6. É o que trava a peça dela fora das contas dela e o que dá a ficha ao post de texto na conta dela."),
|
|
258
|
+
contaNaCasa: z
|
|
259
|
+
.string()
|
|
260
|
+
.nullable()
|
|
261
|
+
.optional()
|
|
262
|
+
.describe("lineage-save: o username da conta dela aqui na casa (o perfil público). null limpa."),
|
|
263
|
+
rotina: z
|
|
264
|
+
.object({ ligada: z.boolean(), canais: z.array(z.string()) })
|
|
265
|
+
.nullable()
|
|
266
|
+
.optional()
|
|
267
|
+
.describe("lineage-save: o post do dia da linhagem, ligado ou não, e por quais canais sai. null volta ao padrão."),
|
|
268
|
+
arquivada: z
|
|
269
|
+
.boolean()
|
|
270
|
+
.optional()
|
|
271
|
+
.describe("lineage-archive: true arquiva, false devolve. A casa e o dono não se arquivam."),
|
|
203
272
|
});
|
|
204
273
|
export async function distribution(args) {
|
|
205
274
|
const sessionToken = getSessionToken();
|
|
@@ -280,5 +349,67 @@ export async function distribution(args) {
|
|
|
280
349
|
: "Nada mudou: a linha já estava na bio (ou o texto é igual ao atual).",
|
|
281
350
|
};
|
|
282
351
|
}
|
|
352
|
+
if (args.action === "lineages") {
|
|
353
|
+
const r = await convexQuery("distributionMcp:mcpLinhagens", { sessionToken });
|
|
354
|
+
return {
|
|
355
|
+
...r,
|
|
356
|
+
instruction: "Cada linhagem: mae (a conta do Bluesky onde a peça nasce), contas (as próprias, rede -> canal), galerias (o que a mãe alimenta sozinha), fichaIds, rotina (post do dia). regras é o que a teia desdobra desse cadastro. vagasLivres são as contas que existem no código e ainda não têm dona. semeada=false quer dizer que a tabela ainda roda a fábrica do código; a primeira edição grava.",
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (args.action === "lineage-new") {
|
|
360
|
+
if (!args.nome)
|
|
361
|
+
throw new Error("action=lineage-new exige nome.");
|
|
362
|
+
const r = await convexMutation("distributionMcp:mcpCriarLinhagem", {
|
|
363
|
+
sessionToken,
|
|
364
|
+
nome: args.nome,
|
|
365
|
+
slug: args.slug,
|
|
366
|
+
grupo: args.grupo,
|
|
367
|
+
artigo: args.artigo,
|
|
368
|
+
lang: args.lang,
|
|
369
|
+
});
|
|
370
|
+
return {
|
|
371
|
+
...r,
|
|
372
|
+
instruction: r.voltou
|
|
373
|
+
? "A linhagem estava arquivada e voltou com o cadastro que tinha."
|
|
374
|
+
: "A linhagem nasceu sem mãe e sem regra: nada viaja sozinho a partir dela até lineage-save dar a mãe (vaga livre em lineages).",
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
if (args.action === "lineage-save") {
|
|
378
|
+
if (!args.slug)
|
|
379
|
+
throw new Error("action=lineage-save exige slug.");
|
|
380
|
+
await convexMutation("distributionMcp:mcpSalvarLinhagem", {
|
|
381
|
+
sessionToken,
|
|
382
|
+
slug: args.slug,
|
|
383
|
+
nome: args.nome,
|
|
384
|
+
grupo: args.grupo,
|
|
385
|
+
artigo: args.artigo,
|
|
386
|
+
lang: args.lang,
|
|
387
|
+
ordem: args.ordem,
|
|
388
|
+
quem: args.quem,
|
|
389
|
+
mae: args.mae,
|
|
390
|
+
contas: args.contas,
|
|
391
|
+
galerias: args.galerias,
|
|
392
|
+
fichaIds: args.fichaIds,
|
|
393
|
+
contaNaCasa: args.contaNaCasa,
|
|
394
|
+
rotina: args.rotina,
|
|
395
|
+
});
|
|
396
|
+
return {
|
|
397
|
+
ok: true,
|
|
398
|
+
slug: args.slug,
|
|
399
|
+
instruction: "Cadastro salvo. A teia já lê o novo (a próxima peça da mãe desdobra pelas regras novas). Vaga de reserva que a linhagem assumiu ganhou o nome dela; a credencial da conta (env do Convex) continua sendo do dono.",
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (args.action === "lineage-archive") {
|
|
403
|
+
if (!args.slug)
|
|
404
|
+
throw new Error("action=lineage-archive exige slug.");
|
|
405
|
+
if (args.arquivada === undefined)
|
|
406
|
+
throw new Error("action=lineage-archive exige arquivada (true ou false).");
|
|
407
|
+
await convexMutation("distributionMcp:mcpArquivarLinhagem", {
|
|
408
|
+
sessionToken,
|
|
409
|
+
slug: args.slug,
|
|
410
|
+
arquivada: args.arquivada,
|
|
411
|
+
});
|
|
412
|
+
return { ok: true, slug: args.slug, arquivada: args.arquivada };
|
|
413
|
+
}
|
|
283
414
|
throw new Error(`action desconhecida: ${args.action}`);
|
|
284
415
|
}
|
package/dist/tools/gallery.js
CHANGED
|
@@ -154,6 +154,11 @@ export const gallerySchema = z.object({
|
|
|
154
154
|
.boolean()
|
|
155
155
|
.optional()
|
|
156
156
|
.describe("action=refs: false (default) SOMA às referências que já existem; true troca a lista inteira. Pra limpar tudo, mande a lista vazia com replace=true."),
|
|
157
|
+
// Mesma tolerância a lista serializada como texto do referenceImageIds.
|
|
158
|
+
skills: z
|
|
159
|
+
.union([z.array(z.string()), z.string()])
|
|
160
|
+
.optional()
|
|
161
|
+
.describe("action=ingest: COMO A PEÇA FOI FEITA, os slugs das skills da casa que dirigiram o take (ex: ['clipe'], ['rasante','storyboard']). Aparece como 'Feito com /clipe' na ficha e na página pública da peça. No Claude Code com o plugin Sapiens o hook carimba sozinho; em outro cliente, passe sempre que o take saiu de uma skill da casa. Re-ingerir SOMA às que já estavam, nunca apaga. Metadado puro: não muda custo nem alcance."),
|
|
157
162
|
uploadedSource: z
|
|
158
163
|
.string()
|
|
159
164
|
.optional()
|
|
@@ -343,7 +348,7 @@ async function ingestPelaPonte(sessionToken, args, ficha) {
|
|
|
343
348
|
throw new Error(`Subir a peça pro storage falhou: HTTP ${subida.status}. Tente de novo; se repetir, peça outra uploadUrl (ela vale 1h).`);
|
|
344
349
|
}
|
|
345
350
|
const { storageId } = (await subida.json());
|
|
346
|
-
return await convexAction("externalIngestNode:mcpIngestFromStorage", {
|
|
351
|
+
return await semSkillsSeRecusar(ficha, (f) => convexAction("externalIngestNode:mcpIngestFromStorage", {
|
|
347
352
|
sessionToken,
|
|
348
353
|
storageId,
|
|
349
354
|
mimeType,
|
|
@@ -352,8 +357,24 @@ async function ingestPelaPonte(sessionToken, args, ficha) {
|
|
|
352
357
|
// mover ou apagar qualquer byte.
|
|
353
358
|
contentSha256: sha256,
|
|
354
359
|
unfiltered: args.unfiltered,
|
|
355
|
-
...
|
|
356
|
-
});
|
|
360
|
+
...f,
|
|
361
|
+
}));
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Servidor de antes do carimbo de skill (deploy atrasado em relação ao pacote)
|
|
365
|
+
* recusa o arg que não conhece, e recusa ANTES do handler, sem tocar em nada.
|
|
366
|
+
* A peça vale mais que o carimbo: tenta de novo sem ele.
|
|
367
|
+
*/
|
|
368
|
+
async function semSkillsSeRecusar(ficha, chamar) {
|
|
369
|
+
try {
|
|
370
|
+
return await chamar(ficha);
|
|
371
|
+
}
|
|
372
|
+
catch (e) {
|
|
373
|
+
if (!ficha.skills || !/extra field `?skills`?/i.test(describeConvexError(e)))
|
|
374
|
+
throw e;
|
|
375
|
+
const { skills: _semCarimbo, ...resto } = ficha;
|
|
376
|
+
return await chamar(resto);
|
|
377
|
+
}
|
|
357
378
|
}
|
|
358
379
|
/**
|
|
359
380
|
* Normaliza `referenceImageIds` pro formato que o Convex espera (lista).
|
|
@@ -400,6 +421,19 @@ function normalizePartIds(raw) {
|
|
|
400
421
|
.filter(Boolean)
|
|
401
422
|
.slice(0, 16);
|
|
402
423
|
}
|
|
424
|
+
/**
|
|
425
|
+
* E pras skills: lista ou texto com vírgula vira lista, vazio vira undefined
|
|
426
|
+
* (campo que o servidor nem recebe). Quem limpa o slug é o servidor
|
|
427
|
+
* (convex/shared/skillSlugs.ts), aqui só o formato.
|
|
428
|
+
*/
|
|
429
|
+
function normalizeSkills(raw) {
|
|
430
|
+
if (!raw)
|
|
431
|
+
return undefined;
|
|
432
|
+
const lista = (Array.isArray(raw) ? raw : raw.split(","))
|
|
433
|
+
.map((s) => s.trim().replace(/^["'[]+|["'\]]+$/g, ""))
|
|
434
|
+
.filter(Boolean);
|
|
435
|
+
return lista.length ? lista.slice(0, 8) : undefined;
|
|
436
|
+
}
|
|
403
437
|
export async function gallery(args) {
|
|
404
438
|
const sessionToken = getSessionToken();
|
|
405
439
|
if (args.action === "list") {
|
|
@@ -492,14 +526,15 @@ export async function gallery(args) {
|
|
|
492
526
|
characterIds: normalizeCastIds(args.characterIds).length
|
|
493
527
|
? normalizeCastIds(args.characterIds)
|
|
494
528
|
: undefined,
|
|
529
|
+
skills: normalizeSkills(args.skills),
|
|
495
530
|
};
|
|
496
531
|
if (args.sourceUrl && !temBytesLocais) {
|
|
497
532
|
try {
|
|
498
|
-
const result = await convexAction("externalIngest:mcpIngestExternal", {
|
|
533
|
+
const result = await semSkillsSeRecusar(ficha, (f) => convexAction("externalIngest:mcpIngestExternal", {
|
|
499
534
|
sessionToken,
|
|
500
535
|
sourceUrl: args.sourceUrl,
|
|
501
|
-
...
|
|
502
|
-
});
|
|
536
|
+
...f,
|
|
537
|
+
}));
|
|
503
538
|
return { ...result, aviso: AVISO_INGEST };
|
|
504
539
|
}
|
|
505
540
|
catch (e) {
|
package/dist/tools/repertorio.js
CHANGED
|
@@ -24,7 +24,7 @@ import { need } from "../schema.js";
|
|
|
24
24
|
*
|
|
25
25
|
* Action-based design (memoria: consolidar tools via args).
|
|
26
26
|
*/
|
|
27
|
-
const mediaTypeEnum = z.enum(["movie", "series", "anime", "game", "book", "music", "tool", "person"]);
|
|
27
|
+
const mediaTypeEnum = z.enum(["movie", "series", "anime", "game", "book", "music", "tool", "person", "boardgame"]);
|
|
28
28
|
const statusEnum = z.enum([
|
|
29
29
|
"backlog",
|
|
30
30
|
"active",
|
|
@@ -62,7 +62,7 @@ export const repertorioSchema = z.object({
|
|
|
62
62
|
// add_item — só identifica o item; o servidor re-resolve e grava o metadado.
|
|
63
63
|
source: sourceEnum
|
|
64
64
|
.optional()
|
|
65
|
-
.describe("Pra add_item: o source EXATO de um candidato do action=resolve (manual=filme/série via OMDb, anilist, twitch=jogo, googlebooks, itunes, wikidata=pessoa). Não invente."),
|
|
65
|
+
.describe("Pra add_item: o source EXATO de um candidato do action=resolve (manual=filme/série via OMDb, anilist, twitch=jogo, googlebooks, itunes, wikidata=pessoa ou jogo de tabuleiro). Não invente."),
|
|
66
66
|
externalId: z
|
|
67
67
|
.string()
|
|
68
68
|
.optional()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapiens-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.75.0",
|
|
4
4
|
"mcpName": "com.sapiensinteticos/sapiens",
|
|
5
5
|
"description": "MCP server pra operar o Sapiens Sintéticos (sapiensinteticos.com) pelo Claude Code: gerar imagem, escrever artigo, voz, música e mais, na sua conta. Login pelo código de sapiensinteticos.com/conectar-claude.",
|
|
6
6
|
"type": "module",
|