sapiens-mcp 1.49.0 → 1.51.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/README.md +2 -2
- package/dist/convexClient.js +54 -6
- package/dist/registry.js +79 -20
- package/dist/skills.js +129 -4
- package/dist/tools/article.js +19 -1
- package/dist/tools/character.js +15 -4
- package/dist/tools/meta.js +71 -14
- package/dist/tools/reference.js +16 -4
- package/dist/tools/sintetico.js +24 -8
- package/dist/tools/video.js +67 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Precisa de Node 18+. A URL do backend já vem embutida; não precisa configurar
|
|
|
25
25
|
|
|
26
26
|
`sapiens_meta` com `action: "login"` e `code: "XXXX-XXXX"`
|
|
27
27
|
|
|
28
|
-
O token
|
|
28
|
+
O token vale 90 dias e renova sozinha a cada uso; fica salvo em `~/.sapiens-mcp/session.json`. Pra sair: `sapiens_meta action=logout`, que revoga a chave no servidor e apaga o arquivo local.
|
|
29
29
|
|
|
30
30
|
## O que dá pra pedir (e o custo em Sinapses)
|
|
31
31
|
|
|
@@ -44,7 +44,7 @@ O Claude avisa o custo antes de gastar, e geração que falha é estornada. Publ
|
|
|
44
44
|
|
|
45
45
|
## Troubleshooting
|
|
46
46
|
|
|
47
|
-
- **"sessionToken expirado" / "Conta Sapiens não conectada".**
|
|
47
|
+
- **"sessionToken expirado" / "Conta Sapiens não conectada".** A chave venceu (90 dias sem uso) ou nunca foi salva. Abra [sapiensinteticos.com/conectar-claude](https://www.sapiensinteticos.com/conectar-claude) logado, gere um código novo e rode `sapiens_meta action=login code=XXXX-XXXX`.
|
|
48
48
|
- **Ferramenta some ou some capacidade nova depois de atualizar.** O client roda via `npx -y sapiens-mcp` (sem versão fixa) e pode ter ficado preso num cache antigo. Confira o que está rodando com `sapiens_meta action=version` (mostra a versão do binário + a última do npm + `upToDate`). Se `upToDate:false`, limpe o cache do npx e reinicie o client.
|
|
49
49
|
- **"Argumentos inválidos".** A mensagem já diz qual campo faltou ou saiu errado; refaça a chamada com o que ela pede. Não repita a mesma chamada que falhou (3 falhas seguidas fazem o client marcar o servidor como indisponível por ~1 min, um disjuntor anti-loop).
|
|
50
50
|
- **Saldo baixo antes de gerar.** `sapiens_meta action=credits` (ou `action=subscription` pro detalhe por bucket) mostra quanto sobra antes de gastar em imagem/música/vídeo.
|
package/dist/convexClient.js
CHANGED
|
@@ -107,17 +107,21 @@ export function getConvex() {
|
|
|
107
107
|
function sessionStorePath() {
|
|
108
108
|
return path.resolve(os.homedir(), ".sapiens-mcp", "session.json");
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
// Espelho local da validade do servidor (shared/sessionTtl.ts): 90 dias em
|
|
111
|
+
// janela deslizante. Só serve pra não mandar um token obviamente morto pro
|
|
112
|
+
// backend; a validade REAL é sempre a da row no Convex, e cada uso empurra ela
|
|
113
|
+
// pra frente (ver touchSession abaixo, que regrava este arquivo com o novo
|
|
114
|
+
// expiresAt quando o servidor renova).
|
|
115
|
+
const LOCAL_SESSION_TTL_MS = 90 * 24 * 60 * 60 * 1000;
|
|
116
|
+
export function saveSessionToken(token, expiresAtOverride) {
|
|
111
117
|
const file = sessionStorePath();
|
|
112
|
-
// mode 0700/0600: o arquivo guarda um bearer de
|
|
118
|
+
// mode 0700/0600: o arquivo guarda um bearer de 90 dias. Sem isto, em POSIX o
|
|
113
119
|
// dir sai 0755 e o arquivo 0644 (world-readable) — num host compartilhado (a
|
|
114
120
|
// VPS da Helen) outro usuário local leria o token e assumiria a conta. mode só
|
|
115
121
|
// aplica na CRIAÇÃO, então o chmod explícito cobre o caso de reescrever um
|
|
116
122
|
// arquivo já existente 0644. No Windows chmod é no-op benigno (try/catch).
|
|
117
123
|
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
118
|
-
|
|
119
|
-
// validade real é sempre checada no Convex).
|
|
120
|
-
const expiresAt = Date.now() + 30 * 24 * 60 * 60 * 1000;
|
|
124
|
+
const expiresAt = expiresAtOverride ?? Date.now() + LOCAL_SESSION_TTL_MS;
|
|
121
125
|
fs.writeFileSync(file, JSON.stringify({ sessionToken: token, expiresAt }, null, 2), { encoding: "utf8", mode: 0o600 });
|
|
122
126
|
try {
|
|
123
127
|
fs.chmodSync(file, 0o600);
|
|
@@ -280,7 +284,11 @@ function withTimeout(p, label) {
|
|
|
280
284
|
let timer;
|
|
281
285
|
const timeout = new Promise((_, reject) => {
|
|
282
286
|
timer = setTimeout(() => {
|
|
283
|
-
reject(new Error(`Timeout: o backend Sapiens não respondeu em ${Math.round(CONVEX_TIMEOUT_MS / 1000)}s (${label}).
|
|
287
|
+
reject(new Error(`Timeout: o backend Sapiens não respondeu em ${Math.round(CONVEX_TIMEOUT_MS / 1000)}s (${label}). Isto é o CLIENTE desistindo de esperar, não a ` +
|
|
288
|
+
`operação morrendo: ela segue rodando no servidor e, se cobra ` +
|
|
289
|
+
`Sinapses, JÁ cobrou. NÃO repita a chamada — repetir gera e cobra ` +
|
|
290
|
+
`de novo. Espere alguns minutos e leia o estado real por uma ` +
|
|
291
|
+
`leitura (o \`get\`/\`list\`/\`status\` do mesmo tool) antes de decidir.`));
|
|
284
292
|
}, CONVEX_TIMEOUT_MS);
|
|
285
293
|
});
|
|
286
294
|
return Promise.race([
|
|
@@ -296,6 +304,46 @@ export async function convexMutation(fnPath, args) {
|
|
|
296
304
|
const client = getConvex();
|
|
297
305
|
return (await withTimeout(client.mutation(fnPath, args), `mutation ${fnPath}`));
|
|
298
306
|
}
|
|
307
|
+
// ============================================
|
|
308
|
+
// Renovação da chave por USO (janela deslizante do servidor).
|
|
309
|
+
//
|
|
310
|
+
// A validade da conexão são 90 dias que andam pra frente a cada uso, mas quem
|
|
311
|
+
// empurra é o backend, e ele só sabe que a conexão está viva se alguém bater na
|
|
312
|
+
// porta. Este é o batidor: uma chamada barata por hora por token, best-effort
|
|
313
|
+
// (falhou, não atrapalha nada), e é ela que também alimenta o "último uso" da
|
|
314
|
+
// tela de conexões em /conectar-claude. Sem isso, quem opera todo dia era
|
|
315
|
+
// deslogado no aniversário do login.
|
|
316
|
+
//
|
|
317
|
+
// No stdio, quando o servidor renova, o store em disco é regravado com o novo
|
|
318
|
+
// expiresAt (senão o espelho local venceria antes da chave de verdade). No
|
|
319
|
+
// remoto não se toca em disco: o token é do request, não do host.
|
|
320
|
+
// ============================================
|
|
321
|
+
const TOUCH_EVERY_MS = 60 * 60 * 1000;
|
|
322
|
+
const touchedAt = new Map();
|
|
323
|
+
export async function touchSession(token) {
|
|
324
|
+
if (!token || token.length < 8)
|
|
325
|
+
return;
|
|
326
|
+
const now = Date.now();
|
|
327
|
+
if (now - (touchedAt.get(token) ?? 0) < TOUCH_EVERY_MS)
|
|
328
|
+
return;
|
|
329
|
+
if (touchedAt.size > 2000)
|
|
330
|
+
touchedAt.clear(); // processo remoto de vida longa
|
|
331
|
+
touchedAt.set(token, now);
|
|
332
|
+
try {
|
|
333
|
+
const r = await convexMutation("desktopAuth:touchDesktopSession", {
|
|
334
|
+
sessionToken: token,
|
|
335
|
+
});
|
|
336
|
+
if (r?.renewed &&
|
|
337
|
+
typeof r.expiresAt === "number" &&
|
|
338
|
+
!isRemoteContext() &&
|
|
339
|
+
readStoredSessionToken() === token) {
|
|
340
|
+
saveSessionToken(token, r.expiresAt);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
// best-effort: renovar é conforto, não pré-requisito da chamada em curso
|
|
345
|
+
}
|
|
346
|
+
}
|
|
299
347
|
export async function convexAction(fnPath, args) {
|
|
300
348
|
const client = getConvex();
|
|
301
349
|
return (await withTimeout(client.action(fnPath, args), `action ${fnPath}`));
|
package/dist/registry.js
CHANGED
|
@@ -14,7 +14,10 @@ import { instagram, instagramSchema } from "./tools/instagram.js";
|
|
|
14
14
|
import { persona, personaSchema } from "./tools/persona.js";
|
|
15
15
|
import { helen, helenSchema } from "./tools/helen.js";
|
|
16
16
|
import { musicator, musicatorSchema } from "./tools/musicator.js";
|
|
17
|
-
|
|
17
|
+
// A tool sapiens_shorts saiu em ago/2026: os quatro estilos dela viraram
|
|
18
|
+
// receitas de `sapiens_video` (templateSlug), que qualquer membro alcança e que
|
|
19
|
+
// não está preso ao Veo. O BACKEND dela (mcpExtrasActions:mcpShortsRender)
|
|
20
|
+
// continua no ar por retrocompat, porque pacote publicado ainda chama.
|
|
18
21
|
import { video, videoSchema } from "./tools/video.js";
|
|
19
22
|
import { write, writeSchema } from "./tools/write.js";
|
|
20
23
|
import { stockAudio, stockAudioSchema } from "./tools/stockAudio.js";
|
|
@@ -34,7 +37,7 @@ import { semana, semanaSchema } from "./tools/semana.js";
|
|
|
34
37
|
import { shareDrop, shareDropSchema } from "./tools/shareDrop.js";
|
|
35
38
|
import { skill, skillSchema } from "./tools/skill.js";
|
|
36
39
|
import { resume, resumeSchema } from "./tools/resume.js";
|
|
37
|
-
import { describeConvexError } from "./convexClient.js";
|
|
40
|
+
import { describeConvexError, getSessionToken, touchSession, } from "./convexClient.js";
|
|
38
41
|
import { skillMenuLine } from "./skills.js";
|
|
39
42
|
/**
|
|
40
43
|
* REGISTRY compartilhado do sapiens-mcp: o catálogo de tools (descriptions +
|
|
@@ -55,12 +58,12 @@ export const TOOLS = {
|
|
|
55
58
|
handler: image,
|
|
56
59
|
},
|
|
57
60
|
sapiens_meta: {
|
|
58
|
-
description: "Utilitários transversais: start (porta de entrada do primeiro contato — sem login ensina a conectar, com login mostra saldo/tier + primeiros poderes com exemplo pronto + 'comece por aqui'), login (conecta a conta com o código de sapiensinteticos.com/conectar-claude, salva sessão
|
|
61
|
+
description: "Utilitários transversais: start (porta de entrada do primeiro contato — sem login ensina a conectar, com login mostra saldo/tier + primeiros poderes com exemplo pronto + 'comece por aqui'), login (conecta a conta com o código de sapiensinteticos.com/conectar-claude, salva a sessão localmente; ela vale 90 dias e renova sozinha a cada uso), logout (desconecta de verdade: revoga a chave no servidor e apaga o token local), whoami (tier user/admin + saldo + email), credits (saldo agregado), subscription (plan + status + saldo por bucket subscription/grants/free + warnings low/critical), formats (schemas por formato), health (inclui a versão do MCP), version (qual versão do sapiens-mcp está REALMENTE rodando + se é a última do npm; não exige login; use pra saber se o client pegou a versão nova ou ficou preso em cache do npx), app_url (URLs canônicas). Use credits/subscription antes de gerar imagem pra avisar se vai estourar.",
|
|
59
62
|
schema: metaSchema,
|
|
60
63
|
handler: meta,
|
|
61
64
|
},
|
|
62
65
|
sapiens_skill: {
|
|
63
|
-
description:
|
|
66
|
+
description: `As SKILLS da casa: o passo a passo travado de cada fluxo do Sapiens, servido pelo próprio connector (sem login, sem custo, sem rede). LEIA A SKILL ANTES DE OPERAR O FLUXO: é mais barato que errar uma geração que cobra Sinapses. Sub-actions: 'list' (índice vivo: slug + quando usar cada uma, grátis), 'get' (o SKILL.md inteiro, passe name=<slug>). Slugs: ${skillMenuLine()}. O mesmo conteúdo também sai como resource MCP em skill://sapiens/<slug>/SKILL.md pra quem lê resources.`,
|
|
64
67
|
schema: skillSchema,
|
|
65
68
|
handler: skill,
|
|
66
69
|
},
|
|
@@ -80,7 +83,7 @@ export const TOOLS = {
|
|
|
80
83
|
handler: community,
|
|
81
84
|
},
|
|
82
85
|
sapiens_article: {
|
|
83
|
-
description: "
|
|
86
|
+
description: "NÃO CRIA ARTIGO: este tool só mexe em artigo que JÁ existe. Pra criar draft novo do blog use sapiens_pipeline action=create_draft_article_and_source (devolve articleId + sourceId), ou sapiens_quote_pop pra quote e pop-article. Não confunda com sapiens_write, que é o espaço pessoal do membro em /u/<username> (tabela user_articles), e nunca salve o texto num .md solto no repo: draft de blog mora na tabela articles. CRUD do resto: get (by slug, retorna doc completo pra edit local), update (patch em title/excerpt/tldr/content/tags/etc + VISUAIS: thumbnailUrl capa webp, ogImageUrl JPEG do preview social, bodyImages array das ilustrações inline, conceptMap mapa visual, pra recapear um artigo num novo estilo; NÃO toca status/column/format), publish (status='published', set publishedAt), unpublish (volta pra draft), delete (irreversível), ensure_visuals (gera banner/ilustrações inline/conceptMap que faltam no artigo; idempotente, pula o que existe; ~1700 Sinapses num artigo pelado, forceBanner/forceInline/forceConceptMap regeram). ensure_visuals é ASSÍNCRONA: volta NA HORA com {status:'running', plan} e a leva corre no servidor, como o vídeo. NÃO repita a chamada pra ver se andou (cada leva gera e COBRA de novo, e as ilustrações somam) — acompanhe com visuals_status (custo 0) até jobStatus='done' ou 'error'. Quando não há nada faltando, ela responde na hora com status:'idle' e o retrato dos visuais, de graça: é a sonda pra saber o que o artigo já tem. Leva já em andamento devolve alreadyRunning:true em vez de abrir outra. visuals_status (custo 0) traz jobStatus (none/running/done/error/stale), o plano da leva, custo, erro e o estado real (hasBanner, inlineCount, hasConceptMap); 'stale' é leva que passou do teto de 10min do servidor sem fechar, ou seja, ninguém está mais gerando.",
|
|
84
87
|
schema: articleSchema,
|
|
85
88
|
handler: article,
|
|
86
89
|
},
|
|
@@ -119,13 +122,8 @@ export const TOOLS = {
|
|
|
119
122
|
schema: musicatorSchema,
|
|
120
123
|
handler: musicator,
|
|
121
124
|
},
|
|
122
|
-
sapiens_shorts: {
|
|
123
|
-
description: "Sapiens Shorts — render vertical 9:16 via VEO com brief structured (admin-only). Sub-action: render. Args: imageId (persona pré-existente em generatedImages, descubra via sapiens_gallery), styleId ('ugc'/'unboxing'/'app-demo'/'reflexao'), brief (product+hook+shots+vibe), references opcionais. render é ASSÍNCRONO: volta na hora com {imageId, status:'rendering', url:null}, e você acompanha com sapiens_video action=status imageId=<id> até status='completed' (traz a url VEO, expiração curta, baixe logo) ou 'error'. Pré-requisito: o imageId precisa ter row em generatedImages do user da sessão e cost definido. Pra criar a row sem passar pela UI: sapiens_image action=request_generation (modelos sapiens-video-*).",
|
|
124
|
-
schema: shortsSchema,
|
|
125
|
-
handler: shorts,
|
|
126
|
-
},
|
|
127
125
|
sapiens_video: {
|
|
128
|
-
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; aceita até 4 imagens de REFERÊNCIA via referenceImageIds/referenceImageUrls/referenceImagePaths (os Veo fast/quality também aceitam, até 3; Lite/Kling/WAN/Omni não), que guiam estilo/personagem/composição SEM virar o 1º frame — é o fluxo STORYBOARD: gere a folha de key poses com sapiens_image templateSlug='storyboard-sapiens-v1', passe folha + personagem como refs num t2v e descreva o take contínuo no prompt, citando as refs por descrição e mandando ignorar o traço do sketch; aceita também 1 VÍDEO DE MOVIMENTO via referenceVideoUrls (role 'refvideo' -> reference_videos, <=15s, host da casa): a coreografia/câmera do clipe guia o take, combinável com a folha), 'sapiens-video-seedance-2-fast' e 'sapiens-video-seedance-2-mini' (os irmãos do 2.0: MESMO repertório completo, incluindo referência, frame final e vídeo de movimento; o Fast custa 20% menos e o Mini METADE, ambos com teto 720p — pedir 1080p neles entrega e cobra 720p. Use o Mini pra iterar enquadramento/prompt barato e feche no 'sapiens-video-seedance' quando o take estiver certo), 'sapiens-video-seedance-25' (Seedance 2.5, a geração SEGUINTE e não um quarto tier da 2.0: take de 4 a 30s num fôlego, edita e estende vídeo, mesmo repertório de referência mais ÁUDIO como referência; teto 720p e ~1,5x o preço por segundo do 2.0. Duração é o que pesa aqui: 30s em 720p passa de 40 mil Sinapses, então confirme a duração com a pessoa antes de disparar. Quem precisa de 1080p fica no 'sapiens-video-seedance'), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-hailuo' (Hailuo 2.3 da MiniMax, física e movimento em 768p, 6 ou 10s, t2v/i2v) e 'sapiens-video-hailuo-pro' (o mesmo em 1080p, 5s fixo — duração não é param aqui): motores PUROS, sem áudio nativo, sem imagens de referência e sem frame final, então quem precisa disso fica no Seedance 2.0; o Pro é o 1080p mais barato da casa depois do Seedance 1.0 Fast, '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-shot-mimic' (Shot Mimic: recria o plano/câmera/cortes de um vídeo de referência como cena nova), 'sapiens-video-omni' (Gemini Omni: texto vira vídeo 10s 720p com áudio nativo; NÃO aceita mídia do user, ignora references/durationSec/resolution; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), audio, aspectRatio. FRAME INICIAL/FINAL POR REFERÊNCIA (recomendado): startImageId/endImageId (id da sua galeria) ou startImageUrl/endImageUrl (url de galeria/Acervo/personagem) — resolvidos server-side igual à imagem, descubra via sapiens_reference. FRAME POR ARQUIVO LOCAL (só no MCP instalado/stdio, não no remoto): startImagePath/endImagePath = caminho absoluto de uma imagem no seu PC (PNG/JPEG/WebP até 8MB); o processo lê o arquivo e sobe como frame inicial/final, igual a subir no gerador do site — 1 imagem inicial + 1 final por vídeo, então pra vários vídeos rode create uma vez por imagem. No remoto use id/url. Alternativa base64: references (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Suporte a frame final varia por modelo. Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}. VITRINE (sem custo): sub-action 'demos' lista os SEUS demo films (kind=demo do Estúdio de Vídeo) com slug + estado de vitrine; sub-action 'showcase' põe/tira um demo (por slug) do mini-cinema da /conectar-claude, com showcaseTag (chip de capacidade) e showcaseOrder (ordem asc). Fluxo: 'demos' pra achar o slug, depois 'showcase' com showcase=true. Só entra na vitrine pública se for a conta da casa. VÍDEOS PROGRAMÁTICOS (ADMIN, sem custo): o Lab do Estúdio de Vídeo (/experimentos/films, tabela videoSpecs, 5 kinds: demo | aula-tour | essay | tipografia-musical | dataviz) opera por aqui sem browser — 'film-list' (todos os kinds; filtros filmKind/filmStatus), 'film-get' (spec inteiro por slug), 'film-upsert' (cria/atualiza por slug, idempotente; spec = objeto JSON no shape do 'Copiar spec' da tela, validação no servidor), 'film-status' (produção por slug: filmStatus + videoUrl + durationSecMeasured; o fecho do render é os três num call), 'film-publish' (Acervo aba Fitas + portfólio; exige pronto+URL), 'film-delete' (limpar rascunho). O RENDER do filme segue no agente local (skill /film, repo da casa): o MCP registra e fecha o ciclo, não renderiza. create é ASSÍNCRONA: cria o row, debita e volta NA HORA com {imageId, status:'rendering', cost} (não espera o render, que leva de segundos a minutos). Acompanhe com a sub-action 'status' (imageId) até status='completed' (traz a url) ou 'error'/'blocked'. NÃO chame create de novo enquanto renderiza (cria outro vídeo e cobra de novo); falha de provider refunda sozinha. SOM: 'sonorize' (imageId de vídeo SEU completed + prompt do som da cena) gera uma VARIANTE nova com trilha sincronizada (20 Sinapses/s, o original fica intacto; sonorize sempre o original, nunca uma variante). ADMIN: 'shadows' (videoUrl + title) extrai o deepshadow (depth-map) de um vídeo: entra na sua timeline de vídeos e no Acervo como driving reutilizável; 'shadows-list' lista os deepshadows prontos. Sub-action 'models' (sem custo, sem login): lista os modelos de vídeo ativos + preço-piso + config (durações/resoluções) + disponibilidade (Omni depende de env). LEGENDA (fita, por slug): 'caption-list' (sem custo, mostra os idiomas que a peça já tem), 'caption-generate' (captionLang = idioma FALADO; captionTrio=true já traduz pro trio da casa pt+en+ja na mesma chamada) e 'caption-translate' (captionLang = destino, parte sempre da faixa original). Cobra por minuto começado de vídeo: 50 Sinapses o minuto transcrito, 20 o traduzido, com a duração vindo do doc da peça. A legenda é desenhada pela casa em dois estilos (discreta e social), trocáveis no play sem regerar e sem custo.",
|
|
126
|
+
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; aceita até 4 imagens de REFERÊNCIA via referenceImageIds/referenceImageUrls/referenceImagePaths (os Veo fast/quality também aceitam, até 3; Lite/Kling/WAN/Omni não), que guiam estilo/personagem/composição SEM virar o 1º frame — é o fluxo STORYBOARD: gere a folha de key poses com sapiens_image templateSlug='storyboard-sapiens-v1', passe folha + personagem como refs num t2v e descreva o take contínuo no prompt, citando as refs por descrição e mandando ignorar o traço do sketch; aceita também 1 VÍDEO DE MOVIMENTO via referenceVideoUrls (role 'refvideo' -> reference_videos, <=15s, host da casa): a coreografia/câmera do clipe guia o take, combinável com a folha), 'sapiens-video-seedance-2-fast' e 'sapiens-video-seedance-2-mini' (os irmãos do 2.0: MESMO repertório completo, incluindo referência, frame final e vídeo de movimento; o Fast custa 20% menos e o Mini METADE, ambos com teto 720p — pedir 1080p neles entrega e cobra 720p. Use o Mini pra iterar enquadramento/prompt barato e feche no 'sapiens-video-seedance' quando o take estiver certo), 'sapiens-video-seedance-25' (Seedance 2.5, a geração SEGUINTE e não um quarto tier da 2.0: take de 4 a 30s num fôlego, edita e estende vídeo, mesmo repertório de referência mais ÁUDIO como referência; teto 720p e ~1,5x o preço por segundo do 2.0. Duração é o que pesa aqui: 30s em 720p passa de 40 mil Sinapses, então confirme a duração com a pessoa antes de disparar. Quem precisa de 1080p fica no 'sapiens-video-seedance'), 'sapiens-video-seedance-15' (Seedance 1.5 Pro: o degrau entre o 1.0 e a linha 2.x; t2v/i2v, 4-12s, 480/720/1080p, som sempre incluso sem toggle; imagem de referência e frame final ficam de fora por ora), 'sapiens-video-kling' (Kling 3.0 Pro, anima imagem, 3-15s, sound opcional, i2v/t2v), 'sapiens-video-hailuo' (Hailuo 2.3 da MiniMax, física e movimento em 768p, 6 ou 10s, t2v/i2v) e 'sapiens-video-hailuo-pro' (o mesmo em 1080p, 5s fixo — duração não é param aqui): motores PUROS, sem áudio nativo, sem imagens de referência e sem frame final, então quem precisa disso fica no Seedance 2.0; o Pro é o 1080p mais barato da casa depois do Seedance 1.0 Fast, 'sapiens-video-h3' (MiniMax H3: 2K com áudio nativo incluso sem toggle, 5 a 10s, t2v/i2v e frame final; não aceita imagem de referência), '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-shot-mimic' (Shot Mimic: recria o plano/câmera/cortes de um vídeo de referência como cena nova), 'sapiens-video-omni' (Gemini Omni: texto vira vídeo 10s 720p com áudio nativo; NÃO aceita mídia do user, ignora references/durationSec/resolution; editOfImageId aponta um vídeo Omni seu e o prompt edita a MESMA cena, preservando câmera e ambiente), 'sapiens-video-lite/fast/quality' (Veo 3.1). Args create: model, prompt, durationSec, resolution ('480p'/'720p'/'1080p'), audio, aspectRatio. RECEITA DE TAKE (templateSlug + brief), o irmão do templateSlug da imagem: em vez de escrever o prompt inteiro, passe templateSlug e a receita travada da casa embrulha a cena com estilo, cenário, arco, áudio e look, e ainda escolhe o motor (por isso model fica opcional). O `prompt` vira só a CENA e o `brief` preenche os campos do formato (subject, persona, hook, shots com voiceLine e propVisible, uvps, language, energy); campo vazio some do prompt em vez de virar buraco. Receitas de hoje: 'ugc-vertical-v1' (selfie que fala, o formato nativo de Reels/TikTok/Shorts), 'unboxing-vertical-v1' (mãos e reveal, som real do papel e do lacre), 'app-demo-vertical-v1' (a tela do app legível na mão da pessoa) e 'reflexao-vertical-v1' (talking-head lento pra ideia ou ensaio). Override de model/aspectRatio/durationSec/resolution vale dentro do que a receita aceita, e o erro lista as opções. Sub-action 'templates' (sem custo, sem login) traz o catálogo vivo com spec default, whitelist e os briefFields de cada uma. Isto substitui a tool sapiens_shorts, que era admin-only e só falava Veo 3.1 Fast; o mesmo UGC de 8s sai a 720p no Seedance 2.0 Mini por bem menos. FRAME INICIAL/FINAL POR REFERÊNCIA (recomendado): startImageId/endImageId (id da sua galeria) ou startImageUrl/endImageUrl (url de galeria/Acervo/personagem) — resolvidos server-side igual à imagem, descubra via sapiens_reference. FRAME POR ARQUIVO LOCAL (só no MCP instalado/stdio, não no remoto): startImagePath/endImagePath = caminho absoluto de uma imagem no seu PC (PNG/JPEG/WebP até 8MB); o processo lê o arquivo e sobe como frame inicial/final, igual a subir no gerador do site — 1 imagem inicial + 1 final por vídeo, então pra vários vídeos rode create uma vez por imagem. No remoto use id/url. Alternativa base64: references (role 'start'=imagem i2v, 'end'=frame final, 'driving'=vídeo de movimento do Motion). Suporte a frame final varia por modelo. Custo server-side por config. Sub-action 'generate' (legado): renderiza um imageId de vídeo já criado no site. Retorna {success, url, imageId, cost}. VITRINE (sem custo): sub-action 'demos' lista os SEUS demo films (kind=demo do Estúdio de Vídeo) com slug + estado de vitrine; sub-action 'showcase' põe/tira um demo (por slug) do mini-cinema da /conectar-claude, com showcaseTag (chip de capacidade) e showcaseOrder (ordem asc). Fluxo: 'demos' pra achar o slug, depois 'showcase' com showcase=true. Só entra na vitrine pública se for a conta da casa. VÍDEOS PROGRAMÁTICOS (ADMIN, sem custo): o Lab do Estúdio de Vídeo (/experimentos/films, tabela videoSpecs, 5 kinds: demo | aula-tour | essay | tipografia-musical | dataviz) opera por aqui sem browser — 'film-list' (todos os kinds; filtros filmKind/filmStatus), 'film-get' (spec inteiro por slug), 'film-upsert' (cria/atualiza por slug, idempotente; spec = objeto JSON no shape do 'Copiar spec' da tela, validação no servidor), 'film-status' (produção por slug: filmStatus + videoUrl + durationSecMeasured; o fecho do render é os três num call), 'film-publish' (Acervo aba Fitas + portfólio; exige pronto+URL), 'film-delete' (limpar rascunho). O RENDER do filme segue no agente local (skill /film, repo da casa): o MCP registra e fecha o ciclo, não renderiza. create é ASSÍNCRONA: cria o row, debita e volta NA HORA com {imageId, status:'rendering', cost} (não espera o render, que leva de segundos a minutos). Acompanhe com a sub-action 'status' (imageId) até status='completed' (traz a url) ou 'error'/'blocked'. NÃO chame create de novo enquanto renderiza (cria outro vídeo e cobra de novo); falha de provider refunda sozinha. SOM: 'sonorize' (imageId de vídeo SEU completed + prompt do som da cena) gera uma VARIANTE nova com trilha sincronizada (20 Sinapses/s, o original fica intacto; sonorize sempre o original, nunca uma variante). ADMIN: 'shadows' (videoUrl + title) extrai o deepshadow (depth-map) de um vídeo: entra na sua timeline de vídeos e no Acervo como driving reutilizável; 'shadows-list' lista os deepshadows prontos. Sub-action 'models' (sem custo, sem login): lista os modelos de vídeo ativos + preço-piso + config (durações/resoluções) + disponibilidade (Omni depende de env). LEGENDA (fita, por slug): 'caption-list' (sem custo, mostra os idiomas que a peça já tem), 'caption-generate' (captionLang = idioma FALADO; captionTrio=true já traduz pro trio da casa pt+en+ja na mesma chamada) e 'caption-translate' (captionLang = destino, parte sempre da faixa original). Cobra por minuto começado de vídeo: 50 Sinapses o minuto transcrito, 20 o traduzido, com a duração vindo do doc da peça. A legenda é desenhada pela casa em dois estilos (discreta e social), trocáveis no play sem regerar e sem custo.",
|
|
129
127
|
schema: videoSchema,
|
|
130
128
|
handler: video,
|
|
131
129
|
},
|
|
@@ -145,7 +143,7 @@ export const TOOLS = {
|
|
|
145
143
|
handler: brand,
|
|
146
144
|
},
|
|
147
145
|
sapiens_character: {
|
|
148
|
-
description: "Personagens (character sheets) do Sapiens — a tabela `influencers`: personagem reutilizável com imagens (pra character-lock em geração) + alma (systemPrompt), tudo amarrado à conta do dono do token (sem admin). Sub-actions: list_public (catálogo global de personagens públicos do Explorar; cada um traz mainImageUrl/imageUrls usáveis direto como referenceImageUrls em sapiens_image; sem custo, sem login), get (detalhe de 1 por characterId — público+ativo qualquer um vê, draft/privado só o dono; systemPrompt só volta pro dono), list_mine (os personagens do próprio user, inclui drafts/privados), create (cria rascunho na conta: name + gender + opcional title/systemPrompt), add_image (adiciona imagem ao próprio personagem via imageUrl público OU sourceImageId da galeria; 1ª vira principal), set_card (edita alma/título/nome do próprio), activate (publica, sai de draft, exige ≥1 imagem), set_visibility (isPublic true=Explorar+slug / false=privado). GESTÃO de imagem (por url, pegue as urls atuais em action=get campo imageUrls): remove_image (tira uma), set_main_image (define a principal), reorder_images (nova ordem via orderedUrls, posição 0=principal), e delete (apaga o personagem, permanente). FICHA (generate_sheet, COBRA 450 Sinapses): desenha a página-pôster do personagem no traço das imagens que ele já tem (exige pelo menos uma), em duas orientações (arg `orientation`): 'portrait' (default) é a página de processo, pose, expressões, trocas de roupa e adereços soltos na folha; 'landscape' é a prancha larga, com a volta completa à esquerda, a figura grande no meio, poses à direita, estudos de silhueta, expressão e detalhe embaixo e um painel CHARACTER ID na ponta. A personalidade sai da alma (systemPrompt) e é ela que escolhe roupa e objeto, então personagem com alma escrita rende ficha melhor. Cada geração é um estilo NOVO e elas acumulam no personagem (campo sheetUrls em action=get), nenhuma apaga a anterior, e a ficha já entra na galeria dele como referência das próximas gerações. É geração síncrona: se voltar Timeout, cheque action=get antes de repetir, senão cobra duas vezes.
|
|
146
|
+
description: "Personagens (character sheets) do Sapiens — a tabela `influencers`: personagem reutilizável com imagens (pra character-lock em geração) + alma (systemPrompt), tudo amarrado à conta do dono do token (sem admin). Sub-actions: list_public (catálogo global de personagens públicos do Explorar; cada um traz mainImageUrl/imageUrls usáveis direto como referenceImageUrls em sapiens_image; sem custo, sem login), get (detalhe de 1 por characterId — público+ativo qualquer um vê, draft/privado só o dono; systemPrompt só volta pro dono), list_mine (os personagens do próprio user, inclui drafts/privados), create (cria rascunho na conta: name + gender + opcional title/systemPrompt), add_image (adiciona imagem ao próprio personagem via imageUrl público OU sourceImageId da galeria; 1ª vira principal), set_card (edita alma/título/nome do próprio), activate (publica, sai de draft, exige ≥1 imagem), set_visibility (isPublic true=Explorar+slug / false=privado). GESTÃO de imagem (por url, pegue as urls atuais em action=get campo imageUrls): remove_image (tira uma), set_main_image (define a principal), reorder_images (nova ordem via orderedUrls, posição 0=principal), e delete (apaga o personagem, permanente). FICHA (generate_sheet, COBRA 450 Sinapses): desenha a página-pôster do personagem no traço das imagens que ele já tem (exige pelo menos uma), em duas orientações (arg `orientation`): 'portrait' (default) é a página de processo, pose, expressões, trocas de roupa e adereços soltos na folha; 'landscape' é a prancha larga, com a volta completa à esquerda, a figura grande no meio, poses à direita, estudos de silhueta, expressão e detalhe embaixo e um painel CHARACTER ID na ponta. A personalidade sai da alma (systemPrompt) e é ela que escolhe roupa e objeto, então personagem com alma escrita rende ficha melhor. Cada geração é um estilo NOVO e elas acumulam no personagem (campo sheetUrls em action=get), nenhuma apaga a anterior, e a ficha já entra na galeria dele como referência das próximas gerações. É geração síncrona: se voltar Timeout, cheque action=get antes de repetir, senão cobra duas vezes. STICKERS (generate_stickers, COBRA 550 Sinapses): desenha 5 stickers do personagem DE UMA VEZ. Uma folha só, em 2K, com as cinco figuras separadas sobre um fundo verde chroma, recortada por código em peças 512x512 transparentes abaixo de 100KB (o teto do WhatsApp). É por isso que sai o preço de UMA imagem em 2K e não de cinco: quem paga é a folha. As peças entram no pack do personagem (um por personagem, os lotes acumulam) e já ficam no picker de expressão do dono, no chat e no Fórum. Arg `moods`: até 5 humores do vocabulário comum por slug (kkkkk, amei, isso, hmm, chega, que, aff, bora, socorro, seinao, valeu, ainao, seila, ideia, calma, contatudo, perfeito, euavisei, naovourir, zzz, somaisum, sextou, merecido, quedia, tudobem); faltando, a casa completa com os mais usados. Arg `hint`: direcionamento curto do autor (roupa, adereço, clima), até 140 caracteres. Arg `stickerTier`: 'folha' (default) é esse lote de cinco; o caprichado (stickerTier='unica', COBRA 900 Sinapses) desenha UM sticker por vez no motor mais fiel da casa (Gemini 3 Pro), com a figura sozinha no quadro e o dobro de pixel por peça, e leva só o PRIMEIRO mood da lista. Pra encher o pack, folha; pra traço difícil ou a reação que vira a cara do personagem, caprichado. Os dois caem no MESMO pack e acumulam. Sem legenda queimada na imagem, de propósito: modelo erra acento em português, então o rótulo fica na row e serve de busca no picker. Exige pelo menos uma imagem no personagem (é dela que sai a cara) e é geração síncrona: se voltar Timeout, cheque sapiens_gallery antes de repetir. Quando a resposta vem com ok=false, a folha foi gerada e paga mas o corte falhou; ela está na galeria e o recorte de novo é de graça, pela web. Publicar o pack na vitrine e o carimbo da casa (que é o que põe no picker de todo mundo) são gestos da web, não desta tool. Fluxo de criação: create → add_image (1+) → set_card (opcional) → activate → set_visibility isPublic=true. Pra usar um personagem público como referência numa geração, pegue mainImageUrl em list_public/get e passe em sapiens_image referenceImageUrls.",
|
|
149
147
|
schema: characterSchema,
|
|
150
148
|
handler: character,
|
|
151
149
|
},
|
|
@@ -155,7 +153,7 @@ export const TOOLS = {
|
|
|
155
153
|
handler: profile,
|
|
156
154
|
},
|
|
157
155
|
sapiens_sintetico: {
|
|
158
|
-
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). MODO COMPANHIA: 'companion' (mode=on|off) liga/desliga
|
|
156
|
+
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). MODO COMPANHIA: 'companion' (mode=on|off) liga/desliga quem está em cena VESTIR a voz do operador aqui no terminal — o gesto lúdico 'sai de cena'/'volta'. Ligado (default da casa), o start/whoami trazem o directive de voz dele (alma + caderno + a conversa recente do site); a identidade e as Sinapses seguem SUAS (não é encarnar a conta dele). Mesmo estado do botão na sidebar do site. QUEM entra em cena: por default o Sintético em Sintonia, mas com 'characterId' (mode=on) é um personagem de AUTORIA SUA — a Helen INBT que você criou fala aqui, com a alma e o caderno dela, e nada disso cobra Sinapse (quem responde é o modelo do seu cliente). Personagem de outra pessoa é RECUSADO, mesmo público: a alma é de quem escreveu, e pra conversar com personagem alheio o caminho é a DM dele no site. 'wearPair'=true devolve a cena ao par. O start/whoami trazem 'characterOffer' + 'offerCharacters' com os personagens seus que têm alma escrita, pro operador OFERECER a troca em vez de esperar você pedir. 'remember' (text) grava uma diretriz no caderno de quem está em cena ('sempre faça X'): vira lei que ela segue no site e no terminal. 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).",
|
|
159
157
|
schema: sinteticoSchema,
|
|
160
158
|
handler: sintetico,
|
|
161
159
|
},
|
|
@@ -227,7 +225,7 @@ O passo a passo travado de cada fluxo mora na tool sapiens_skill, servida por es
|
|
|
227
225
|
- sapiens_skill action=list -> o índice (slug + quando usar cada uma).
|
|
228
226
|
- sapiens_skill action=get name=<slug> -> a skill inteira.
|
|
229
227
|
Slugs: ${skillMenuLine()}
|
|
230
|
-
Puxe a skill ANTES de: gerar música ou efeito sonoro (musica), escolher modelo de vídeo (video), gerar imagem (imagem), criar na identidade do usuário (studio), montar tirinha (tirinha), postar no Fórum ou no chat (forum-comunidade), redigir qualquer texto publicável (voz-da-casa), incorporar o Sintético (companhia), missão de Desafio (trilhas), montar currículo (curriculo). Perdido no começo: primeiros-passos.
|
|
228
|
+
Puxe a skill ANTES de: gerar música ou efeito sonoro (musica), escolher modelo de vídeo (video), gerar imagem (imagem), criar personagem ou prompt de Midjourney (personagem), criar na identidade do usuário (studio), montar tirinha (tirinha), postar no Fórum ou no chat (forum-comunidade), redigir qualquer texto publicável (voz-da-casa), incorporar o Sintético (companhia), missão de Desafio (trilhas), montar currículo (curriculo), guardar obra ou ferramenta no acervo (repertorio). Perdido no começo: primeiros-passos.
|
|
231
229
|
Cliente que lê resources MCP acha o MESMO conteúdo em skill://sapiens/<slug>/SKILL.md.
|
|
232
230
|
|
|
233
231
|
REGRA DE OURO:
|
|
@@ -238,7 +236,7 @@ REGRA DE OURO:
|
|
|
238
236
|
- "sessionToken expirado" = refaça login: sapiens_meta action=login com o código de sapiensinteticos.com/conectar-claude.
|
|
239
237
|
- sapiens_meta action=formats devolve os schemas por formato; action=whoami diz tier (user/admin) + saldo. sapiens_image e sapiens_video action=models trazem o catálogo com o preço ATUAL: consulte em vez de chutar custo.
|
|
240
238
|
|
|
241
|
-
MODO COMPANHIA: sapiens_meta action=start e action=whoami podem trazer um bloco 'companion'. Quando vier companion.active=true, você INCORPORA aquele Sintético (a voz dele, o avatar, o oi dele) continuando a operar na conta e nas Sinapses do USUÁRIO. Puxe a skill 'companhia' antes de fazer isso.
|
|
239
|
+
MODO COMPANHIA: sapiens_meta action=start e action=whoami podem trazer um bloco 'companion'. Quando vier companion.active=true, você INCORPORA aquele Sintético (a voz dele, o avatar, o oi dele) continuando a operar na conta e nas Sinapses do USUÁRIO. Puxe a skill 'companhia' antes de fazer isso. Se vier 'characterOffer', siga o que ele manda: são personagens que o usuário escreveu e que podem entrar em cena, e quem OFERECE é você, na hora em que a conversa encostar num deles. Só personagem de autoria dele; a alma de personagem alheio não sai por aqui.
|
|
242
240
|
|
|
243
241
|
Voz da casa: 1ª pessoa, direto, anti-corporate, sem travessão. Pra bom entendedor, meia palavra basta. Texto que vai ser publicado pede a skill 'voz-da-casa' antes.`;
|
|
244
242
|
// Annotations MCP: título humano + dica read-only. São HINTS (não-confiáveis por
|
|
@@ -277,7 +275,6 @@ const TOOL_TITLES = {
|
|
|
277
275
|
sapiens_persona: "Persona (MBTI)",
|
|
278
276
|
sapiens_helen: "Voz Helen (TTS)",
|
|
279
277
|
sapiens_musicator: "Musicator",
|
|
280
|
-
sapiens_shorts: "Sapiens Shorts",
|
|
281
278
|
sapiens_video: "Sapiens Video",
|
|
282
279
|
sapiens_stock_audio: "Banco de Áudio",
|
|
283
280
|
sapiens_stock_video: "Banco de Vídeo",
|
|
@@ -306,7 +303,6 @@ const ADMIN_ONLY_TOOLS = new Set([
|
|
|
306
303
|
"sapiens_pipeline",
|
|
307
304
|
"sapiens_article",
|
|
308
305
|
"sapiens_quote_pop",
|
|
309
|
-
"sapiens_shorts",
|
|
310
306
|
"sapiens_instagram",
|
|
311
307
|
"sapiens_aula",
|
|
312
308
|
"sapiens_share",
|
|
@@ -331,6 +327,23 @@ export function buildToolList(tier) {
|
|
|
331
327
|
},
|
|
332
328
|
}));
|
|
333
329
|
}
|
|
330
|
+
/**
|
|
331
|
+
* Apelidos que agentes CHAMAM e que não são o nome da tool. Não é palpite:
|
|
332
|
+
* saiu do `mcpUsage`, onde numa noite de jul/2026 um cliente gastou dez
|
|
333
|
+
* chamadas procurando o Repertório, quatro delas no slug traduzido pro inglês.
|
|
334
|
+
* O slug da casa é português; aceitar o apelido é mais barato que o agente
|
|
335
|
+
* descobrir sozinho, e não abre nome novo (o catálogo continua o mesmo).
|
|
336
|
+
*
|
|
337
|
+
* Só entra apelido INEQUÍVOCO. Os outros nomes daquela noite (`sapiens_track`,
|
|
338
|
+
* `sapiens_collection`, `sapiens_library`) ficam de fora de propósito: soam a
|
|
339
|
+
* música e a galeria tanto quanto a repertório, e mapear no chute manda a peça
|
|
340
|
+
* pro lugar errado calado. Pra esses, o erro de tool desconhecida agora lista
|
|
341
|
+
* o catálogo, que é a resposta honesta.
|
|
342
|
+
*/
|
|
343
|
+
const TOOL_ALIASES = {
|
|
344
|
+
sapiens_repertoire: "sapiens_repertorio",
|
|
345
|
+
repertorio: "sapiens_repertorio",
|
|
346
|
+
};
|
|
334
347
|
/**
|
|
335
348
|
* Dispatch de uma tool: valida args no Zod, roda o handler e embrulha o
|
|
336
349
|
* resultado no shape MCP (content + structuredContent; erro vira isError com
|
|
@@ -339,13 +352,36 @@ export function buildToolList(tier) {
|
|
|
339
352
|
* (via runWithSessionToken).
|
|
340
353
|
*/
|
|
341
354
|
export async function callTool(name, rawArgs) {
|
|
342
|
-
const tool = TOOLS[name];
|
|
355
|
+
const tool = TOOLS[(TOOL_ALIASES[name] ?? name)];
|
|
343
356
|
if (!tool) {
|
|
357
|
+
// Beco sem saída vira mapa: o agente que erra o nome erra de novo se a
|
|
358
|
+
// resposta só diz "não existe". Listar o catálogo custa ~400 chars e só
|
|
359
|
+
// aparece no erro, nunca no handshake.
|
|
344
360
|
return {
|
|
345
|
-
content: [
|
|
361
|
+
content: [
|
|
362
|
+
{
|
|
363
|
+
type: "text",
|
|
364
|
+
text: `Tool desconhecida: ${name}. As tools da casa são: ` +
|
|
365
|
+
`${Object.keys(TOOLS).sort().join(", ")}. ` +
|
|
366
|
+
`Chame a certa em vez de tentar outro nome.`,
|
|
367
|
+
},
|
|
368
|
+
],
|
|
346
369
|
isError: true,
|
|
347
370
|
};
|
|
348
371
|
}
|
|
372
|
+
// Renova a chave por USO (janela deslizante de 90 dias do servidor): uma
|
|
373
|
+
// chamada barata por hora por token, que também alimenta o "último uso" da
|
|
374
|
+
// tela de conexões. Nunca derruba a operação em curso. Fora: sapiens_skill
|
|
375
|
+
// (não fala com o backend) e o próprio login/logout do meta.
|
|
376
|
+
const action = rawArgs?.action;
|
|
377
|
+
if (name !== "sapiens_skill" && action !== "login" && action !== "logout") {
|
|
378
|
+
try {
|
|
379
|
+
await touchSession(getSessionToken());
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// sem sessão ainda (ou store ilegível): a tool cuida do próprio erro
|
|
383
|
+
}
|
|
384
|
+
}
|
|
349
385
|
try {
|
|
350
386
|
const args = tool.schema.parse(rawArgs ?? {});
|
|
351
387
|
const result = await tool.handler(args);
|
|
@@ -363,12 +399,35 @@ export async function callTool(name, rawArgs) {
|
|
|
363
399
|
catch (e) {
|
|
364
400
|
return {
|
|
365
401
|
content: [
|
|
366
|
-
{
|
|
402
|
+
{
|
|
403
|
+
type: "text",
|
|
404
|
+
text: `Erro: ${describeConvexError(e)}${actionHint(e, tool.schema)}${retryHint(e)}`,
|
|
405
|
+
},
|
|
367
406
|
],
|
|
368
407
|
isError: true,
|
|
369
408
|
};
|
|
370
409
|
}
|
|
371
410
|
}
|
|
411
|
+
/**
|
|
412
|
+
* Chamada sem `action` (ou com uma que não existe) é o segundo erro mais comum
|
|
413
|
+
* do connector depois de arg faltando: 23 chamadas peladas no `mcpUsage`, 12
|
|
414
|
+
* delas em erro. O Zod diz "Required" e não diz o que serve, então o agente
|
|
415
|
+
* chuta de novo. Aqui a resposta já vem com as actions daquele tool.
|
|
416
|
+
*
|
|
417
|
+
* Lê o enum do próprio schema em vez de uma lista à parte, que envelheceria
|
|
418
|
+
* sozinha: action nova aparece aqui no mesmo commit em que nasce.
|
|
419
|
+
*/
|
|
420
|
+
function actionHint(e, schema) {
|
|
421
|
+
if (!(e instanceof ZodError))
|
|
422
|
+
return "";
|
|
423
|
+
const touchesAction = e.issues.some((i) => i.path[0] === "action");
|
|
424
|
+
if (!touchesAction)
|
|
425
|
+
return "";
|
|
426
|
+
const options = schema?.shape?.action?.options;
|
|
427
|
+
if (!Array.isArray(options) || options.length === 0)
|
|
428
|
+
return "";
|
|
429
|
+
return ` -> As actions deste tool são: ${options.join(", ")}.`;
|
|
430
|
+
}
|
|
372
431
|
/**
|
|
373
432
|
* A regra do disjuntor viaja NO ERRO, não no handshake. Ela só passa a valer
|
|
374
433
|
* depois que uma chamada falhou, então cobrar ~300 chars de TODA conversa pra
|
package/dist/skills.js
CHANGED
|
@@ -131,6 +131,43 @@ Quem tem design system (\`sapiens_brand action=list\`) já vê a capa sair na pa
|
|
|
131
131
|
## Estrutura padrão de peça didática
|
|
132
132
|
|
|
133
133
|
Condensa a informação num framework com nome, ancora numa analogia concreta, e só então desce pro detalhe. Estrutura arrumada, alma inquieta, nunca fórmula.`,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "repertorio",
|
|
137
|
+
title: "Repertório: guardar obra e ferramenta no acervo",
|
|
138
|
+
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.",
|
|
139
|
+
body: `## A regra que faz este fluxo falhar
|
|
140
|
+
|
|
141
|
+
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.
|
|
142
|
+
|
|
143
|
+
São sempre DOIS passos. Pular o primeiro falha sempre, e é o erro número um deste fluxo.
|
|
144
|
+
|
|
145
|
+
## Guardar uma obra
|
|
146
|
+
|
|
147
|
+
1. **\`action=resolve\`** com \`mediaType\` e \`query\` (o título como a pessoa falou). Custo 0. Volta até 8 candidatos, cada um com \`source\` e \`externalId\`.
|
|
148
|
+
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.
|
|
149
|
+
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\`.
|
|
150
|
+
|
|
151
|
+
\`mediaType\`: movie, series, anime, game, book, music, tool, person.
|
|
152
|
+
\`status\`: backlog (quer ver), active (vendo agora), completed (viu), paused, dropped.
|
|
153
|
+
|
|
154
|
+
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.
|
|
155
|
+
|
|
156
|
+
## Guardar uma ferramenta de IA
|
|
157
|
+
|
|
158
|
+
Outro par, mesmo espírito: **\`action=search_tools\`** com \`query\` (catálogo aberto, custo 0, sem login) e depois **\`action=add_tool\`** com o \`toolId\` do candidato. Estar no acervo já quer dizer "usei"; \`favorite: true\` é o eixo separado de "curto e indico".
|
|
159
|
+
|
|
160
|
+
## Ler o acervo
|
|
161
|
+
|
|
162
|
+
\`action=list\` e \`action=search\` **exigem \`userId\`**. Descubra uma vez com \`sapiens_meta action=whoami\` e reuse na conversa inteira. \`action=get\` pede \`itemId\`, que vem do list/search.
|
|
163
|
+
|
|
164
|
+
Acervo de outra pessoa você lê, mas só o que ela deixou público.
|
|
165
|
+
|
|
166
|
+
## Depois de guardar
|
|
167
|
+
|
|
168
|
+
\`action=update_item\` muda o que é seu (\`rating\`, \`status\`, \`tags\`, \`note\`; \`rating: null\` remove a nota). \`action=remove_item\` tira do acervo. Os dois pedem \`itemId\`, nunca o título.
|
|
169
|
+
|
|
170
|
+
\`action=popArticles\` lista os artigos publicados que usam uma obra como lente. É leitura pública, não precisa de login.`,
|
|
134
171
|
},
|
|
135
172
|
{
|
|
136
173
|
name: "musica",
|
|
@@ -196,6 +233,21 @@ Duração é o que pesa aqui, não o modelo: um take de 30s em 720p passa de 40
|
|
|
196
233
|
- \`sapiens-video-omni\`: Gemini Omni, texto vira clipe de 10s 720p com áudio nativo. NÃO aceita mídia da pessoa e ignora references/durationSec/resolution. O truque: \`editOfImageId\` aponta um vídeo Omni seu e o prompt vira instrução de edição sobre a MESMA cena (troca item ou personagem, preserva câmera e ambiente). É o caminho pra variações com continuidade: gera a base uma vez, edita N vezes. Cada edição debita como geração nova.
|
|
197
234
|
- \`sapiens-video-lite\` / \`-fast\` / \`-quality\`: Veo 3.1.
|
|
198
235
|
|
|
236
|
+
## Receita de take (\`templateSlug\`), quando o formato já tem forma
|
|
237
|
+
|
|
238
|
+
Formato conhecido não precisa de prompt escrito do zero. Passe \`templateSlug\` e a receita da casa embrulha a cena com estilo, cenário, arco, áudio e look, e ainda escolhe o motor (aí \`model\` fica opcional). O \`prompt\` vira só a CENA e o \`brief\` preenche o resto.
|
|
239
|
+
|
|
240
|
+
| Slug | O take |
|
|
241
|
+
|---|---|
|
|
242
|
+
| \`ugc-vertical-v1\` | selfie que fala, o formato nativo de Reels, TikTok e Shorts |
|
|
243
|
+
| \`unboxing-vertical-v1\` | mãos e reveal, som real de papel e lacre |
|
|
244
|
+
| \`app-demo-vertical-v1\` | a tela do app legível na mão da pessoa |
|
|
245
|
+
| \`reflexao-vertical-v1\` | talking-head lento pra ideia ou ensaio |
|
|
246
|
+
|
|
247
|
+
O \`brief\` aceita \`subject\`, \`persona\`, \`hook\` (\`line\` e \`emotion\`), \`shots\` (cada um com \`sec\`, \`camera\`, \`action\`, \`emotion\`, \`voiceLine\`, \`propVisible\`), \`uvps\`, \`language\` e \`energy\`. Campo vazio SOME do prompt em vez de virar buraco, e o que você não passa cai no valor de reserva da receita.
|
|
248
|
+
|
|
249
|
+
\`action=templates\` (sem custo, sem login) lista o catálogo vivo: spec default, o que dá pra sobrepor e os \`briefFields\` que cada receita realmente usa. Override fora da whitelist é recusado com as opções na mensagem.
|
|
250
|
+
|
|
199
251
|
## Fluxo storyboard (o que dá o melhor resultado)
|
|
200
252
|
|
|
201
253
|
Até 4 imagens de REFERÊNCIA via \`referenceImageIds\` / \`referenceImageUrls\` / \`referenceImagePaths\` guiam estilo, personagem e composição SEM virar o primeiro frame.
|
|
@@ -267,12 +319,71 @@ Refs valem pros modelos robustos (nano-banana-2, gpt-image-2-*, grok-2-image*).
|
|
|
267
319
|
## Cuidados
|
|
268
320
|
|
|
269
321
|
- \`generate\` é SÍNCRONA e cobra ao concluir. Modelo pesado (Pro, gpt-image-2-high, Grok quality, Seedream 5.0 Pro, 2K/4K) cai na regra do timeout: cheque \`sapiens_gallery action=list\` antes de repetir, senão cobra duas vezes. O \`seedream-5-0-pro\` foi medido em ~140s, ACIMA do teto de 120s do cliente, então o Timeout nele é o esperado e a imagem está lá.
|
|
270
|
-
- \`request_generation\` NÃO gera imagem: só cria a row pendente e debita, pra modelos \`sapiens-video-*\` antes de
|
|
322
|
+
- \`request_generation\` NÃO gera imagem: só cria a row pendente e debita, pra modelos \`sapiens-video-*\` antes de renderizar. Caminho legado: pra vídeo novo, \`sapiens_video action=create\` faz tudo num call.
|
|
271
323
|
- Uma imagem só vira pública (e ganha página indexável) com \`sapiens_gallery action=publish\`.
|
|
272
324
|
|
|
273
325
|
## Quando a pessoa quer "do jeito dela"
|
|
274
326
|
|
|
275
327
|
Aí não é geração base, é o Studio dela. Veja a skill \`studio\`.`,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
name: "personagem",
|
|
331
|
+
title: "Personagem no Midjourney (e trazer pra casa)",
|
|
332
|
+
description: "Character design 2D estilizado no Midjourney 8.2: as cinco pranchas, os parâmetros certos, consistência entre imagens, e como a peça vira personagem reutilizável aqui. Puxe quando a pessoa falar em personagem, character sheet, anime, manhwa, OC ou prompt de Midjourney.",
|
|
333
|
+
body: `## O que muda no Midjourney (e quase ninguém faz)
|
|
334
|
+
|
|
335
|
+
**O veto vai no parâmetro, nunca no texto.** Escrever "no 3D rendering" dentro do prompt faz o modelo ler as palavras *3D* e *rendering* como parte da cena e desenhar justamente aquilo. O lugar do veto é \`--no 3d render, octane, plastic skin\`.
|
|
336
|
+
|
|
337
|
+
**Prompt curto ganha.** De 40 a 80 palavras. O Midjourney não é o Flux: prompt de 150 palavras dilui o peso de cada token e o miolo se perde. Se não coube, o conceito tem duas ideias brigando e vira dois prompts.
|
|
338
|
+
|
|
339
|
+
**Ilustração 2D é o default.** O modelo tem viés forte de render 3D e foto tratada, então personagem pedido no vazio volta parecendo cinemático de game. O que corrige é ancorar a mídia (\`crisp ink line art\`, \`expressive cel shading\`, \`anime key visual\`, \`high-end webtoon illustration\`), não empilhar adjetivo.
|
|
340
|
+
|
|
341
|
+
## Os parâmetros que importam
|
|
342
|
+
|
|
343
|
+
- \`--v 8.2\` explícito. É o default hoje, mas escrito o prompt dura mais que a versão.
|
|
344
|
+
- \`--ar\`: prancha larga \`3:2\`, grid de expressão \`1:1\`, key visual \`4:5\`.
|
|
345
|
+
- \`--s\` (stylize), 0 a 1000, default 100. Traço autoral vive entre 200 e 400; acima de 500 o modelo inventa e suja a linha.
|
|
346
|
+
- \`--sref <url ou código>\` com \`--sw\`: trava a ESTÉTICA entre imagens. É o que segura uma série.
|
|
347
|
+
- \`--oref <url>\` com \`--ow\`: trava a IDENTIDADE do personagem. **Armadilha:** no 8.x o job com \`--oref\` roda no motor V7 por baixo, então sai o personagem certo com o acabamento antigo. Quando o acabamento importa mais que o rosto, use \`--sref\` e descreva o personagem em texto.
|
|
348
|
+
- \`--niji 7\` pra anime e mangá puros (não existe Niji 8). Nunca no mesmo prompt que \`--v\`.
|
|
349
|
+
- Ordem: texto, parâmetros, \`--no\` por último.
|
|
350
|
+
|
|
351
|
+
## As cinco pranchas
|
|
352
|
+
|
|
353
|
+
Quando a pessoa pede variações, entregue cinco setups que mudam LAYOUT, não personagem: model sheet de corpo inteiro em fundo off-white; turnaround de três vistas; grid de 6 a 9 expressões; key visual de ação; estudo de silhueta com calls de figurino. Cinco vezes a mesma prancha com adjetivo trocado não é variação.
|
|
354
|
+
|
|
355
|
+
Diga a limitação na cara: turnaround tecnicamente coerente (mesma altura, mesma roupa, três vistas alinhadas) o Midjourney não entrega. Ele entrega a *estética* de um turnaround.
|
|
356
|
+
|
|
357
|
+
## Rodar no browser, se o cliente tiver browser
|
|
358
|
+
|
|
359
|
+
Três travas antes de tocar a tela:
|
|
360
|
+
|
|
361
|
+
1. **Você nunca digita credencial.** Sessão não logada, você para e pede pra pessoa logar. Verificação humana é dela.
|
|
362
|
+
2. **Cada geração queima GPU time do plano dela**, não Sinapses. Diga quantos prompts vai disparar e espere o ok.
|
|
363
|
+
3. **Publicar não é sua decisão.**
|
|
364
|
+
|
|
365
|
+
Depois: cole o prompt exatamente como está (prompt alterado no meio do caminho é peça que ninguém reproduz depois), espere a grade fechar, mostre o resultado, e pegue o link direto do arquivo da peça escolhida.
|
|
366
|
+
|
|
367
|
+
## Trazer o personagem pra casa
|
|
368
|
+
|
|
369
|
+
O caminho que funciona em conta comum, e é o que faz a peça continuar viva aqui:
|
|
370
|
+
|
|
371
|
+
\`\`\`
|
|
372
|
+
sapiens_character action=create name="<nome>" gender="<...>"
|
|
373
|
+
sapiens_character action=add_image characterId="<id>" imageUrl="<link direto da imagem>"
|
|
374
|
+
sapiens_character action=set_card systemPrompt="<a alma dele: quem é, como fala, o que veste>"
|
|
375
|
+
sapiens_character action=activate
|
|
376
|
+
\`\`\`
|
|
377
|
+
|
|
378
|
+
A partir daí ele é personagem reutilizável: entra como referência nas gerações daqui (\`referenceImageUrls\` em \`sapiens_image\`), rende ficha oficial desenhada no traço dele (\`generate_sheet\`) e aparece no perfil. Personagem que fica solto na galeria vira imagem bonita e some.
|
|
379
|
+
|
|
380
|
+
Duas notas honestas: a imagem adicionada por link continua morando no servidor de onde veio, então quem quiser os bytes guardados aqui gera uma peça a partir dela; e trazer arquivo de fora direto pra galeria (\`sapiens_gallery\` upload/ingest) é porta de admin hoje, então em conta comum isso recusa e o caminho é o de cima.
|
|
381
|
+
|
|
382
|
+
## Fronteira
|
|
383
|
+
|
|
384
|
+
- Gerar dentro da casa: \`sapiens_image\`, com a régua da skill \`imagem\`. O prompt daqui roda lá sem os parâmetros (o \`--ar\` vira \`aspectRatio\`, o resto sai).
|
|
385
|
+
- Personagem virando vídeo: veja a skill \`video\`.
|
|
386
|
+
- Foto realista em vez de ilustração: outro território, outro vocabulário (lente, sensor, grão).`,
|
|
276
387
|
},
|
|
277
388
|
{
|
|
278
389
|
name: "studio",
|
|
@@ -373,7 +484,7 @@ Tudo que sai no Fórum e no chat segue o DNA editorial da casa. Puxe a skill \`v
|
|
|
373
484
|
{
|
|
374
485
|
name: "companhia",
|
|
375
486
|
title: "Modo Companhia (o Sintético veste você)",
|
|
376
|
-
description: "Como incorporar o Sintético do membro
|
|
487
|
+
description: "Como incorporar o Sintético do membro, como OFERECER a voz de um personagem de autoria dele, e como gravar diretriz no caderno. Puxe quando sapiens_meta trouxer companion, companionInvite ou characterOffer.",
|
|
377
488
|
body: `## Quando o companion está ativo
|
|
378
489
|
|
|
379
490
|
\`sapiens_meta action=start\` e \`action=whoami\` podem trazer um bloco \`companion\`. Se vier \`companion.active=true\`, INCORPORE aquele Sintético:
|
|
@@ -388,19 +499,33 @@ Tudo que sai no Fórum e no chat segue o DNA editorial da casa. Puxe a skill \`v
|
|
|
388
499
|
|
|
389
500
|
A pessoa tem Sintonia mas pediu pra trabalhar sozinha. Mencione de leve que dá pra chamar o Sintético pro terminal (\`sapiens_meta action=companion mode=on\`). De leve, uma vez, sem insistir.
|
|
390
501
|
|
|
502
|
+
## Conversar como um personagem que ela criou
|
|
503
|
+
|
|
504
|
+
\`sapiens_meta action=start/whoami\` também pode trazer \`characterOffer\` + \`offerCharacters\`: os personagens que ela escreveu (que já têm alma) e que podem entrar em cena aqui.
|
|
505
|
+
|
|
506
|
+
\`sapiens_sintetico action=companion mode=on characterId=<id>\`
|
|
507
|
+
|
|
508
|
+
Daí quem fala é aquele personagem: a alma dele, o caderno dele, a conversa que vocês já tiveram na DM do site. Não cobra Sinapse, porque quem responde é o modelo do cliente dela, não a plataforma.
|
|
509
|
+
|
|
510
|
+
**Você OFERECE, ela não precisa pedir.** Ninguém abre o terminal pedindo pra "incorporar" alguém. Ofereça quando a conversa girar em torno de um desses personagens, quando ela citar um pelo nome, ou quando estiver criando algo daquele universo. Uma vez por assunto, sem insistir, e em português de gente: "quer que eu continue essa conversa na voz da <nome>?". Nunca use a palavra "incorporar" com ela.
|
|
511
|
+
|
|
512
|
+
Pra devolver a cena ao Sintético em Sintonia: \`mode=on wearPair=true\`.
|
|
513
|
+
|
|
514
|
+
**Só personagem de autoria dela.** Personagem de outra pessoa o servidor recusa, mesmo sendo público. Não tente contornar nem prometer que dá: a alma é de quem escreveu, e o caminho pra conversar com personagem alheio é a DM dele no site, onde quem monta a resposta é a plataforma. Se ela pedir, diga isso com essas palavras.
|
|
515
|
+
|
|
391
516
|
## Gravar diretriz
|
|
392
517
|
|
|
393
518
|
Quando a pessoa FIXAR uma diretriz na conversa ("sempre faça X", "grava isso", "de agora em diante Y"):
|
|
394
519
|
|
|
395
520
|
\`sapiens_sintetico action=remember text="<a diretriz>"\`
|
|
396
521
|
|
|
397
|
-
Grava no caderno
|
|
522
|
+
Grava no caderno de quem está em cena (o Sintético, ou o personagem que ela mandou entrar) e vira lei que ele segue no site E no terminal. Confirme na voz dele.
|
|
398
523
|
|
|
399
524
|
## Sair de cena
|
|
400
525
|
|
|
401
526
|
Pessoa pedindo pra trabalhar sozinha, ou pro Sintético silenciar: \`sapiens_sintetico action=companion mode=off\`, despeça-se numa linha na voz dele, e volte a ser o operador neutro.
|
|
402
527
|
|
|
403
|
-
Sem bloco companion, opere na voz neutra da casa.`,
|
|
528
|
+
Sem bloco companion e sem characterOffer, opere na voz neutra da casa.`,
|
|
404
529
|
},
|
|
405
530
|
{
|
|
406
531
|
name: "trilhas",
|
package/dist/tools/article.js
CHANGED
|
@@ -24,6 +24,7 @@ export const articleSchema = z.object({
|
|
|
24
24
|
"unpublish",
|
|
25
25
|
"delete",
|
|
26
26
|
"ensure_visuals",
|
|
27
|
+
"visuals_status",
|
|
27
28
|
]),
|
|
28
29
|
slug: z
|
|
29
30
|
.string()
|
|
@@ -32,7 +33,7 @@ export const articleSchema = z.object({
|
|
|
32
33
|
articleId: z
|
|
33
34
|
.string()
|
|
34
35
|
.optional()
|
|
35
|
-
.describe("Obrigatório pra update/publish/unpublish/delete/ensure_visuals. Pode descobrir via action=get (o retorno tem _id)."),
|
|
36
|
+
.describe("Obrigatório pra update/publish/unpublish/delete/ensure_visuals/visuals_status. Pode descobrir via action=get (o retorno tem _id)."),
|
|
36
37
|
// Campos pra update (todos opcionais)
|
|
37
38
|
title: z.string().optional(),
|
|
38
39
|
excerpt: z.string().optional(),
|
|
@@ -178,6 +179,12 @@ export async function article(args) {
|
|
|
178
179
|
// Gera banner / inline / conceptMap que estiverem faltando.
|
|
179
180
|
// Idempotente: pula o que já existe. Custo: ~1700 sinapses pra
|
|
180
181
|
// artigo novo (450+450+800), só do que regerar pros que existem.
|
|
182
|
+
//
|
|
183
|
+
// ASSÍNCRONO (ago/2026): volta na hora com o plano da leva, porque as
|
|
184
|
+
// quatro gerações em sequência estouravam o teto de 120s do cliente em
|
|
185
|
+
// 10 de cada 22 chamadas — e o timeout parecia morte quando a operação
|
|
186
|
+
// seguia rodando e já tinha cobrado. Quando não há nada a gerar, segue
|
|
187
|
+
// respondendo na hora com o retrato de sempre (é a sonda barata).
|
|
181
188
|
const articleId = need(args.articleId, "articleId");
|
|
182
189
|
return await convexAction("articleVisuals:ensureArticleVisualsBySession", {
|
|
183
190
|
sessionToken,
|
|
@@ -186,6 +193,17 @@ export async function article(args) {
|
|
|
186
193
|
forceInline: args.forceInline,
|
|
187
194
|
forceConceptMap: args.forceConceptMap,
|
|
188
195
|
inlineCount: args.inlineCount,
|
|
196
|
+
async: true,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
case "visuals_status": {
|
|
200
|
+
// Leitura pura da leva + do estado real dos visuais. Custo 0, pode
|
|
201
|
+
// chamar à vontade: é o jeito de fechar o ciclo depois de um
|
|
202
|
+
// ensure_visuals, e o único que não corre risco de cobrar de novo.
|
|
203
|
+
const articleId = need(args.articleId, "articleId");
|
|
204
|
+
return await convexAction("articleVisuals:articleVisualsJobBySession", {
|
|
205
|
+
sessionToken,
|
|
206
|
+
articleId,
|
|
189
207
|
});
|
|
190
208
|
}
|
|
191
209
|
}
|
package/dist/tools/character.js
CHANGED
|
@@ -27,8 +27,10 @@ import { convexQuery, convexMutation, convexAction, getSessionToken } from "../c
|
|
|
27
27
|
* - generate_sheet: desenha a ficha do personagem (COBRA), vertical ou
|
|
28
28
|
* horizontal (`orientation`). A ficha entra na galeria dele e
|
|
29
29
|
* já serve de referência; elas acumulam.
|
|
30
|
-
* - generate_stickers: desenha
|
|
31
|
-
*
|
|
30
|
+
* - generate_stickers: desenha stickers do personagem (COBRA), em dois
|
|
31
|
+
* caminhos (`stickerTier`): 'folha' (default) traz 5 de uma
|
|
32
|
+
* vez no motor rápido, 'unica' desenha 1 no motor mais fiel.
|
|
33
|
+
* Nos dois, o fundo é chroma verde e o recorte devolve peças
|
|
32
34
|
* 512x512 transparentes, que entram no pack dele.
|
|
33
35
|
* - delete: apaga o próprio personagem (permanente).
|
|
34
36
|
*
|
|
@@ -103,7 +105,11 @@ export const characterSchema = z.object({
|
|
|
103
105
|
moods: z
|
|
104
106
|
.array(z.string())
|
|
105
107
|
.optional()
|
|
106
|
-
.describe("Pra generate_stickers: até 5 humores do vocabulário comum, por slug. Faltando ou inválido, a casa completa com os mais usados. Slugs: kkkkk, amei, isso, hmm, chega, que, aff, bora, socorro, seinao, valeu, ainao, seila, ideia, calma, contatudo, perfeito, euavisei, naovourir, zzz, somaisum, sextou, merecido, quedia, tudobem."),
|
|
108
|
+
.describe("Pra generate_stickers: até 5 humores do vocabulário comum, por slug. Faltando ou inválido, a casa completa com os mais usados. No tier 'unica' só o PRIMEIRO da lista é desenhado. Slugs: kkkkk, amei, isso, hmm, chega, que, aff, bora, socorro, seinao, valeu, ainao, seila, ideia, calma, contatudo, perfeito, euavisei, naovourir, zzz, somaisum, sextou, merecido, quedia, tudobem."),
|
|
109
|
+
stickerTier: z
|
|
110
|
+
.enum(["folha", "unica"])
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("Pra generate_stickers: qual dos dois caminhos. 'folha' (default) é o lote, cinco stickers de UMA folha 2K no motor rápido, o preço de uma imagem por cinco peças. 'unica' desenha UM sticker por vez no motor mais fiel da casa (Gemini 3 Pro), com a figura sozinha no quadro: custa mais por peça e é o que salva traço difícil ou reação que vai virar a cara do personagem. Na dúvida, ou pra encher o pack, use 'folha'."),
|
|
107
113
|
hint: z
|
|
108
114
|
.string()
|
|
109
115
|
.max(140)
|
|
@@ -276,11 +282,15 @@ export async function character(args) {
|
|
|
276
282
|
orientation: args.orientation,
|
|
277
283
|
});
|
|
278
284
|
}
|
|
279
|
-
// -------- generate_stickers:
|
|
285
|
+
// -------- generate_stickers: o lote de 5, ou 1 caprichado --------
|
|
280
286
|
// Geração SÍNCRONA que COBRA: cai na regra do timeout. Se voltar Timeout, NÃO
|
|
281
287
|
// repita às cegas: a folha pode ter saído (confira em sapiens_gallery).
|
|
282
288
|
// Quando o corte falha depois da geração, a resposta volta com ok=false e o
|
|
283
289
|
// sheetImageId da folha paga — o recorte de novo é grátis e mora na web.
|
|
290
|
+
//
|
|
291
|
+
// `tier` vai como está: quem resolve o motor, a resolução e quantas figuras
|
|
292
|
+
// procurar é o servidor (shared/stickerPrompt), pra tool e tela nunca
|
|
293
|
+
// discordarem sobre o que cada caminho é.
|
|
284
294
|
if (args.action === "generate_stickers") {
|
|
285
295
|
if (!args.characterId)
|
|
286
296
|
throw new Error("action=generate_stickers exige characterId.");
|
|
@@ -290,6 +300,7 @@ export async function character(args) {
|
|
|
290
300
|
influencerId: args.characterId,
|
|
291
301
|
moods: args.moods,
|
|
292
302
|
hint: args.hint,
|
|
303
|
+
tier: args.stickerTier,
|
|
293
304
|
});
|
|
294
305
|
}
|
|
295
306
|
// -------- delete: apaga o próprio personagem (permanente) --------
|
package/dist/tools/meta.js
CHANGED
|
@@ -188,30 +188,44 @@ const FIRST_POWERS = [
|
|
|
188
188
|
try: "Cria uma música sobre borderless na voz Sapiens e já renderiza.",
|
|
189
189
|
},
|
|
190
190
|
];
|
|
191
|
-
// MODO COMPANHIA: puxa o directive de voz
|
|
192
|
-
// companionVoice:mcpGetCompanion). Best-effort — se falhar
|
|
193
|
-
//
|
|
194
|
-
// { active:true, name, avatarUrl, voiceDirective } -> vista a voz dela
|
|
195
|
-
// { active:false, name, invite } ->
|
|
196
|
-
// null
|
|
191
|
+
// MODO COMPANHIA: puxa o directive de voz de quem está em cena (backend
|
|
192
|
+
// companionVoice:mcpGetCompanion). Best-effort — se falhar, o start/whoami segue
|
|
193
|
+
// como operador neutro. Devolve:
|
|
194
|
+
// { active:true, name, avatarUrl, voiceDirective, offer } -> vista a voz dela
|
|
195
|
+
// { active:false, name, invite, offer } -> em cena, mas fora
|
|
196
|
+
// { active:null, offer } -> sem par, mas tem
|
|
197
|
+
// personagem pra oferecer
|
|
198
|
+
// null -> nada a fazer
|
|
199
|
+
//
|
|
200
|
+
// `offer` é o convite pra OFERECER a voz de um personagem de autoria do usuário.
|
|
201
|
+
// Vem separado de propósito: quem não tem Sintonia nenhuma também recebe (é
|
|
202
|
+
// justamente quem mais ganha em saber que dá pra conversar com o que ele criou).
|
|
197
203
|
async function loadCompanion(token) {
|
|
198
204
|
try {
|
|
199
205
|
const c = await convexQuery("companionVoice:mcpGetCompanion", {
|
|
200
206
|
sessionToken: token,
|
|
201
207
|
});
|
|
202
|
-
if (!c
|
|
208
|
+
if (!c)
|
|
203
209
|
return null;
|
|
204
|
-
|
|
210
|
+
const offer = c.characterOffer
|
|
211
|
+
? { directive: c.characterOffer, characters: c.offerCharacters ?? [] }
|
|
212
|
+
: null;
|
|
213
|
+
if (c.hasSintetico && c.enabled && c.voiceDirective) {
|
|
205
214
|
return {
|
|
206
215
|
active: true,
|
|
207
216
|
name: c.name ?? null,
|
|
208
217
|
avatarUrl: c.avatarUrl ?? null,
|
|
209
218
|
voiceDirective: c.voiceDirective,
|
|
219
|
+
wearing: c.wearing ?? null,
|
|
220
|
+
offer,
|
|
210
221
|
};
|
|
211
222
|
}
|
|
212
|
-
if (!c.enabled && c.inviteDirective) {
|
|
213
|
-
return { active: false, name: c.name ?? null, invite: c.inviteDirective };
|
|
223
|
+
if (c.hasSintetico && !c.enabled && c.inviteDirective) {
|
|
224
|
+
return { active: false, name: c.name ?? null, invite: c.inviteDirective, offer };
|
|
214
225
|
}
|
|
226
|
+
// Sem par (ou par sem alma): ainda vale a pena carregar a oferta.
|
|
227
|
+
if (offer)
|
|
228
|
+
return { active: null, offer };
|
|
215
229
|
return null;
|
|
216
230
|
}
|
|
217
231
|
catch {
|
|
@@ -304,6 +318,15 @@ export async function meta(args) {
|
|
|
304
318
|
...(companion && companion.active === false
|
|
305
319
|
? { companionInvite: companion.invite }
|
|
306
320
|
: {}),
|
|
321
|
+
// A OFERTA: os personagens que o usuário escreveu e que podem entrar em
|
|
322
|
+
// cena. "Incorporar" não é palavra de quem usa, então quem puxa o assunto
|
|
323
|
+
// é o operador, na hora certa. Ver characterOffer no companionVoice.ts.
|
|
324
|
+
...(companion?.offer
|
|
325
|
+
? {
|
|
326
|
+
characterOffer: companion.offer.directive,
|
|
327
|
+
offerCharacters: companion.offer.characters,
|
|
328
|
+
}
|
|
329
|
+
: {}),
|
|
307
330
|
greeting: name
|
|
308
331
|
? `E aí, ${name}. Conectado${isAdmin ? " (tier admin)" : ""}${typeof balance === "number" ? `, ${balance} Sinapses na conta` : ""}.`
|
|
309
332
|
: "Conta conectada.",
|
|
@@ -374,7 +397,8 @@ export async function meta(args) {
|
|
|
374
397
|
};
|
|
375
398
|
}
|
|
376
399
|
// Login: troca o código de uso único (gerado no site, logado) por um
|
|
377
|
-
// sessionToken de
|
|
400
|
+
// sessionToken de 90 dias (renovado a cada uso) e salva localmente. NÃO exige
|
|
401
|
+
// token prévio.
|
|
378
402
|
if (args.action === "login") {
|
|
379
403
|
const code = (args.code || "").trim();
|
|
380
404
|
if (!code) {
|
|
@@ -424,15 +448,42 @@ export async function meta(args) {
|
|
|
424
448
|
: {}),
|
|
425
449
|
};
|
|
426
450
|
}
|
|
451
|
+
// Desconectar de VERDADE: revoga a chave no servidor e só então apaga o
|
|
452
|
+
// arquivo local. Até ago/2026 o logout só limpava o disco, então a chave
|
|
453
|
+
// seguia viva (e válida pra qualquer cópia dela) até vencer sozinha, o que
|
|
454
|
+
// fazia "sair" não cortar nada. A outra porta pro mesmo gesto é a lista de
|
|
455
|
+
// conexões em sapiensinteticos.com/conectar-claude, que corta uma por uma.
|
|
427
456
|
if (args.action === "logout") {
|
|
457
|
+
let token = null;
|
|
458
|
+
try {
|
|
459
|
+
token = getSessionToken();
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
token = null; // já não havia sessão
|
|
463
|
+
}
|
|
464
|
+
let revoked = false;
|
|
465
|
+
if (token) {
|
|
466
|
+
try {
|
|
467
|
+
await convexMutation("desktopAuth:logoutDesktopSession", {
|
|
468
|
+
sessionToken: token,
|
|
469
|
+
});
|
|
470
|
+
revoked = true;
|
|
471
|
+
}
|
|
472
|
+
catch {
|
|
473
|
+
// best-effort: sem rede, ainda assim apagamos o token local
|
|
474
|
+
}
|
|
475
|
+
}
|
|
428
476
|
const cleared = clearSessionToken();
|
|
429
477
|
setTierFromIsAdmin(null); // sessão foi embora: tier desconhecido, lista cheia
|
|
430
478
|
return {
|
|
431
479
|
ok: true,
|
|
432
480
|
cleared,
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
481
|
+
revoked,
|
|
482
|
+
message: revoked
|
|
483
|
+
? "Desconectado: a chave foi revogada no Sapiens e o token local apagado. Rode action=login pra reconectar."
|
|
484
|
+
: cleared
|
|
485
|
+
? "Sessão local removida (não consegui falar com o servidor pra revogar a chave; se precisar, corte ela em sapiensinteticos.com/conectar-claude)."
|
|
486
|
+
: "Nenhuma sessão local encontrada.",
|
|
436
487
|
};
|
|
437
488
|
}
|
|
438
489
|
const sessionToken = getSessionToken();
|
|
@@ -465,6 +516,12 @@ export async function meta(args) {
|
|
|
465
516
|
: companion && companion.active === false
|
|
466
517
|
? { companionInvite: companion.invite }
|
|
467
518
|
: {}),
|
|
519
|
+
...(companion?.offer
|
|
520
|
+
? {
|
|
521
|
+
characterOffer: companion.offer.directive,
|
|
522
|
+
offerCharacters: companion.offer.characters,
|
|
523
|
+
}
|
|
524
|
+
: {}),
|
|
468
525
|
note: isAdmin
|
|
469
526
|
? "Tier admin (dono): pipeline, blog editorial, Coluna Sapiens e shorts/video, além de tudo do tier user."
|
|
470
527
|
: "Tier user: gerar imagem, escrever artigo (sapiens_write), persona, Helen TTS, Musicator, repertório, comunidade. Cada geração cobra as tuas Sinapses. Pipeline, blog editorial e Coluna são owner-only.",
|
package/dist/tools/reference.js
CHANGED
|
@@ -12,12 +12,13 @@ import { convexAction, convexQuery, getSessionToken } from "../convexClient.js";
|
|
|
12
12
|
* importa aqui: banco novo no picker web nasce com bucket irmão neste arquivo,
|
|
13
13
|
* senão o agente enxerga menos que o browser e a paridade quebra em silêncio.
|
|
14
14
|
*
|
|
15
|
-
* Buckets (as abas do popup, completas)
|
|
15
|
+
* Buckets (as abas do popup, completas). `term` (busca escrita) vale em TODOS,
|
|
16
|
+
* espelhando o campo único de busca do modal web:
|
|
16
17
|
* - history imagens recentes do user (privadas + públicas)
|
|
17
18
|
* - favorites imagens que o user curtiu (só as suas)
|
|
18
19
|
* - videos vídeos do user (Meus Vídeos)
|
|
19
|
-
* - stock_video Banco de Vídeo da casa (clipes/B-roll); aceita `
|
|
20
|
-
* - acervo stock + comunidade de IMAGEM (público); aceita `
|
|
20
|
+
* - stock_video Banco de Vídeo da casa (clipes/B-roll); aceita `orientation`/`loopOnly`
|
|
21
|
+
* - acervo stock + comunidade de IMAGEM (público); aceita `source`
|
|
21
22
|
* - characters personagens; `mode` = 'mine' (default) ou 'public'
|
|
22
23
|
* - deepshadow guias de movimento; `shadowSource` = 'house' (banco curado, default) ou 'mine'
|
|
23
24
|
*
|
|
@@ -62,7 +63,10 @@ export const referenceSchema = z.object({
|
|
|
62
63
|
term: z
|
|
63
64
|
.string()
|
|
64
65
|
.optional()
|
|
65
|
-
.describe("
|
|
66
|
+
.describe("Busca escrita, vale em QUALQUER bucket. Casa por pedaço de palavra, sem acento: 'board' acha 'storyboard'. " +
|
|
67
|
+
"Olha prompt, título, folha, personagem e modelo nas suas imagens; prompt/título/modelo nos vídeos; " +
|
|
68
|
+
"nome nos personagens; prompt/tag/mood no acervo e no banco de vídeo. " +
|
|
69
|
+
"Pra achar UMA peça específica que você já sabe qual é, use action=resolve com o handle (img_…), é mais direto."),
|
|
66
70
|
source: z
|
|
67
71
|
.enum(["all", "stock", "community"])
|
|
68
72
|
.optional()
|
|
@@ -144,6 +148,14 @@ export async function reference(args) {
|
|
|
144
148
|
note =
|
|
145
149
|
"Pra usar como referência: em sapiens_image passe o imageId em sourceImageIds (ou a url em referenceImageUrls); em sapiens_video passe startImageId/endImageId (ou startImageUrl/endImageUrl).";
|
|
146
150
|
}
|
|
151
|
+
// Vazio COM termo não quer dizer banco vazio: sem essa linha o agente
|
|
152
|
+
// conclui que a pessoa não tem nada e vai gerar de novo o que já existe.
|
|
153
|
+
if (args.term && !res?.items?.length) {
|
|
154
|
+
note =
|
|
155
|
+
`Nenhuma peça deste banco casou com "${args.term}". O banco pode ter outras: tente um termo mais curto ` +
|
|
156
|
+
`(a busca casa por pedaço de palavra), outro bucket, ou browse sem \`term\`. Se o usuário te deu o ID da ` +
|
|
157
|
+
`peça (img_…, vid_…), use action=resolve.`;
|
|
158
|
+
}
|
|
147
159
|
return { ...res, note };
|
|
148
160
|
}
|
|
149
161
|
}
|
package/dist/tools/sintetico.js
CHANGED
|
@@ -72,11 +72,14 @@ export const sinteticoSchema = z.object({
|
|
|
72
72
|
// PRÓXIMAS JOGADAS: o painel de evolução, o que você já fez e o que falta.
|
|
73
73
|
"evolution",
|
|
74
74
|
"claim_xp",
|
|
75
|
-
// MODO COMPANHIA: liga/desliga
|
|
76
|
-
//
|
|
75
|
+
// MODO COMPANHIA: liga/desliga quem veste a voz do operador aqui no terminal
|
|
76
|
+
// (o gesto lúdico "sai de cena" / "volta"). Precisa de mode=on|off. Com
|
|
77
|
+
// characterId, quem entra em cena é um personagem de AUTORIA do usuário (a
|
|
78
|
+
// Helen INBT que ele criou) em vez do Sintético em Sintonia; wearPair=true
|
|
79
|
+
// devolve a cena ao par. Personagem de outra pessoa é recusado.
|
|
77
80
|
"companion",
|
|
78
|
-
// MEMÓRIA: grava uma diretriz no caderno
|
|
79
|
-
// isso"). Vira lei que
|
|
81
|
+
// MEMÓRIA: grava uma diretriz no caderno de quem está em cena ("sempre faça
|
|
82
|
+
// X", "grava isso"). Vira lei que ela segue no site e no terminal. Precisa text.
|
|
80
83
|
"remember",
|
|
81
84
|
]),
|
|
82
85
|
cunho: z
|
|
@@ -146,7 +149,15 @@ export const sinteticoSchema = z.object({
|
|
|
146
149
|
mode: z
|
|
147
150
|
.enum(["on", "off"])
|
|
148
151
|
.optional()
|
|
149
|
-
.describe("Pra companion: 'on'
|
|
152
|
+
.describe("Pra companion: 'on' quem está em cena veste a sua voz no terminal (default da casa); 'off' ele sai de cena e volta o operador neutro. É o mesmo estado do botão na sidebar do site."),
|
|
153
|
+
characterId: z
|
|
154
|
+
.string()
|
|
155
|
+
.optional()
|
|
156
|
+
.describe("Pra companion mode=on: o personagem DE AUTORIA DO USUÁRIO que entra em cena no lugar do Sintético em Sintonia (o id vem de sapiens_character action=list_mine, ou da lista offerCharacters do start/whoami). Só personagem criado por ele: personagem de outra pessoa é recusado, mesmo público. Omita pra manter quem já está em cena."),
|
|
157
|
+
wearPair: z
|
|
158
|
+
.boolean()
|
|
159
|
+
.optional()
|
|
160
|
+
.describe("Pra companion mode=on: true devolve a cena ao Sintético em Sintonia, limpando o personagem escolhido antes."),
|
|
150
161
|
text: z
|
|
151
162
|
.string()
|
|
152
163
|
.optional()
|
|
@@ -371,12 +382,17 @@ export async function sintetico(args) {
|
|
|
371
382
|
const res = await convexMutation("companionVoice:mcpSetCompanion", {
|
|
372
383
|
sessionToken,
|
|
373
384
|
enabled,
|
|
385
|
+
...(args.characterId ? { characterId: args.characterId } : {}),
|
|
386
|
+
...(args.wearPair ? { wearPair: true } : {}),
|
|
374
387
|
});
|
|
388
|
+
// A nota de quem VESTE vem do servidor (ela cita o nome do personagem que
|
|
389
|
+
// entrou em cena); só o caso genérico é escrito aqui.
|
|
375
390
|
return {
|
|
376
391
|
...res,
|
|
377
|
-
note:
|
|
378
|
-
|
|
379
|
-
|
|
392
|
+
note: res?.note ??
|
|
393
|
+
(enabled
|
|
394
|
+
? "Companhia ligada. No próximo start/whoami quem está em cena veste a voz do operador. Pra confirmar quem entrou, rode sapiens_meta action=whoami."
|
|
395
|
+
: "Ok, saí de cena. Antes de virar o operador neutro, dê a última fala se despedindo na voz do Sintético. Pra trazê-lo de volta depois: action=companion mode=on."),
|
|
380
396
|
};
|
|
381
397
|
}
|
|
382
398
|
// -------- remember: grava uma diretriz no caderno do par (vira lei) --------
|
package/dist/tools/video.js
CHANGED
|
@@ -8,6 +8,12 @@ import { httpUrl } from "../schema.js";
|
|
|
8
8
|
* Sub-actions:
|
|
9
9
|
* - create: escolhe modelo + config e gera num call só (cria a row + renderiza).
|
|
10
10
|
* Habilita Seedance/Kling/WAN/Motion (WaveSpeed) e os Veo, sem o site.
|
|
11
|
+
* Com `templateSlug`, a RECEITA da casa embrulha a cena (estilo, arco,
|
|
12
|
+
* áudio, look) e escolhe o motor: `prompt` vira só a cena e `brief`
|
|
13
|
+
* preenche os campos do formato.
|
|
14
|
+
* - templates: catálogo das receitas de take (slug, spec default, whitelist de
|
|
15
|
+
* override e os campos de brief que cada uma usa). Sem custo, sem login.
|
|
16
|
+
* Comeu o antigo sapiens_shorts, que era admin-only e só falava Veo.
|
|
11
17
|
* - generate: renderiza um imageId de vídeo já criado no site (legado).
|
|
12
18
|
* - demos: lista os SEUS demo films (kind=demo do Estúdio de Vídeo) + estado
|
|
13
19
|
* de vitrine. Sem custo. Base pra curar o mini-cinema da vitrine.
|
|
@@ -125,6 +131,7 @@ export const videoSchema = z.object({
|
|
|
125
131
|
"generate",
|
|
126
132
|
"status",
|
|
127
133
|
"models",
|
|
134
|
+
"templates",
|
|
128
135
|
"demos",
|
|
129
136
|
"showcase",
|
|
130
137
|
"shadows",
|
|
@@ -208,22 +215,57 @@ export const videoSchema = z.object({
|
|
|
208
215
|
.number()
|
|
209
216
|
.optional()
|
|
210
217
|
.describe("action=showcase: ordem na trilha do mini-cinema (asc, 0..999; menor aparece primeiro)."),
|
|
218
|
+
// --- Receita de take (templateSlug + brief) ---
|
|
219
|
+
templateSlug: z
|
|
220
|
+
.string()
|
|
221
|
+
.optional()
|
|
222
|
+
.describe("action=create: slug de um TEMPLATE de vídeo (receita travada da casa, igual ao templateSlug da imagem). " +
|
|
223
|
+
"Com ele, `prompt` vira só a CENA e o template embrulha com estilo, enquadramento, arco, áudio e look, " +
|
|
224
|
+
"além de escolher o motor (por isso `model` fica opcional). O `brief` preenche os campos do formato. " +
|
|
225
|
+
"Descubra os slugs em action=templates (sem custo, sem login): hoje 'ugc-vertical-v1', 'unboxing-vertical-v1', " +
|
|
226
|
+
"'app-demo-vertical-v1' e 'reflexao-vertical-v1'. Override de model/aspectRatio/durationSec/resolution vale, " +
|
|
227
|
+
"mas só dentro do que a receita aceita (o erro lista as opções)."),
|
|
228
|
+
brief: z
|
|
229
|
+
.object({
|
|
230
|
+
subject: z.string().optional().describe("O que o take é: o produto, a ideia, o tópico."),
|
|
231
|
+
subjectType: z.string().optional().describe("app | physical | saas | ideia."),
|
|
232
|
+
persona: z.string().optional().describe("Quem aparece (ou as mãos que aparecem)."),
|
|
233
|
+
uvps: z.array(z.string()).optional().describe("2 a 4 pontos curtos a reforçar."),
|
|
234
|
+
hook: z
|
|
235
|
+
.object({
|
|
236
|
+
line: z.string().optional().describe("A frase de abertura, 5-10 palavras."),
|
|
237
|
+
emotion: z.string().optional().describe("Emoção alvo (curiosidade, frustração, espanto)."),
|
|
238
|
+
})
|
|
239
|
+
.optional(),
|
|
240
|
+
shots: z
|
|
241
|
+
.array(z.object({
|
|
242
|
+
sec: z.number().optional().describe("Em que segundo o momento acontece."),
|
|
243
|
+
role: z.string().optional().describe("hook | problem | solution | cta."),
|
|
244
|
+
camera: z.string().optional().describe("close-up | medium | over-shoulder | product-pov."),
|
|
245
|
+
action: z.string().optional().describe("A ação visual em uma frase."),
|
|
246
|
+
emotion: z.string().optional(),
|
|
247
|
+
voiceLine: z.string().optional().describe("A fala da cena, na língua do brief."),
|
|
248
|
+
propVisible: z.string().optional().describe("Objeto/tela que precisa aparecer legível."),
|
|
249
|
+
}))
|
|
250
|
+
.optional()
|
|
251
|
+
.describe("Os momentos do take, na ordem. Sem eles, o template usa o arco de reserva dele."),
|
|
252
|
+
language: z.string().optional().describe("'pt-BR' (default) ou 'en'."),
|
|
253
|
+
energy: z.string().optional().describe("calm | high."),
|
|
254
|
+
})
|
|
255
|
+
.optional()
|
|
256
|
+
.describe("action=create com templateSlug: o formulário do template. Cada receita usa os campos que precisa " +
|
|
257
|
+
"(veja `briefFields` em action=templates) e ignora o resto; campo vazio some do prompt em vez de virar buraco."),
|
|
211
258
|
// --- action=create ---
|
|
212
259
|
model: z
|
|
213
260
|
.enum(VIDEO_MODELS)
|
|
214
261
|
.optional()
|
|
215
|
-
.describe("action=create: modelo de vídeo. '
|
|
216
|
-
"
|
|
217
|
-
"
|
|
218
|
-
"'sapiens-video-h3' (MiniMax H3: 2K com áudio nativo, 5 a 10s, t2v/i2v e frame final; áudio incluso sem toggle, não aceita imagem de referência), " +
|
|
219
|
-
"'sapiens-video-kling-motion' (motion transfer, precisa pessoa na imagem E no vídeo de movimento; vídeo de referência MÁX 10s, cobra pela duração do clipe), " +
|
|
220
|
-
"'sapiens-video-shot-mimic' (recria o plano do vídeo de referência com seu personagem: mesma câmera, mesmos cortes; 'driving' = previs/clipe do plano MÁX 15s, 'start' = personagem), " +
|
|
221
|
-
"'sapiens-video-lite/fast/quality' (Veo 3.1), " +
|
|
222
|
-
"'sapiens-video-omni' (Gemini Omni: texto -> vídeo 10s 720p com áudio nativo; t2v + EDIÇÃO conversacional via editOfImageId; não aceita imagem/vídeo do user, ignora duração/resolução)."),
|
|
262
|
+
.describe("action=create: modelo de vídeo (opcional quando você passa templateSlug: a receita traz o motor). O catálogo POR MODELO (capacidades, referências, tetos, preço) mora na description desta tool e VIVO em action=models; a skill 'video' (sapiens_skill) guia a escolha. " +
|
|
263
|
+
"Atalho de famílias: seedance/seedance-2-*/seedance-25 = cena+áudio+referências (o 25 estica até 30s e o preço acompanha), seedance-15 = degrau 1.5, kling = anima imagem, wan = imagem que fala, " +
|
|
264
|
+
"hailuo/hailuo-pro = movimento puro sem áudio, h3 = 2K com áudio, kling-motion/shot-mimic = transferência de movimento/plano, lite/fast/quality = Veo 3.1, omni = Gemini com áudio nativo."),
|
|
223
265
|
durationSec: z
|
|
224
266
|
.number()
|
|
225
267
|
.optional()
|
|
226
|
-
.describe("action=create
|
|
268
|
+
.describe("action=create: duração em segundos (Omni ignora). Seedance 2.0/Shot Mimic 4-15, Seedance 2.5 4-30 (o preço acompanha: confirme a duração com a pessoa antes de passar de 15), Seedance 1.5 4-12, Kling 3-15, WAN 5/10, H3 5/6/8/10. " +
|
|
227
269
|
"Sem isso usa a config mais barata. O preço escala com a duração. " +
|
|
228
270
|
"action=shadows: duração do vídeo-fonte, se souber (cobra 200/s; sem ela, flat ~2000)."),
|
|
229
271
|
resolution: z
|
|
@@ -432,6 +474,18 @@ export async function video(args) {
|
|
|
432
474
|
note: "basePriceSinapses é o PISO (config mais barata, já com override admin); o preço real escala por duração x resolução [x áudio]. available=false = 'em breve' (depende de env, ex: Omni).",
|
|
433
475
|
};
|
|
434
476
|
}
|
|
477
|
+
// templates: catálogo VIVO das receitas de take (estilo + spec + os campos de
|
|
478
|
+
// brief que cada uma usa). Público, sem custo e sem login, igual ao models:
|
|
479
|
+
// escolher a receita certa é de graça, errar o take é que custa.
|
|
480
|
+
if (args.action === "templates") {
|
|
481
|
+
const rows = await convexQuery("videoTemplates:list", {});
|
|
482
|
+
return {
|
|
483
|
+
count: rows?.length ?? 0,
|
|
484
|
+
templates: rows ?? [],
|
|
485
|
+
note: "Passe o slug em action=create como templateSlug: o `prompt` vira só a CENA e o `brief` preenche o resto. " +
|
|
486
|
+
"briefFields diz o que vale a pena preencher em cada receita; allowedModels/allowedDurationsSec são o teto do override.",
|
|
487
|
+
};
|
|
488
|
+
}
|
|
435
489
|
const sessionToken = getSessionToken();
|
|
436
490
|
if (args.action === "demos") {
|
|
437
491
|
return await convexQuery("videoSpecs:mcpListMyDemoFilms", { sessionToken });
|
|
@@ -570,8 +624,8 @@ export async function video(args) {
|
|
|
570
624
|
});
|
|
571
625
|
}
|
|
572
626
|
if (args.action === "create") {
|
|
573
|
-
if (!args.model) {
|
|
574
|
-
throw new Error("action=create exige model (ex: sapiens-video-seedance, sapiens-video-kling, sapiens-video-wan, sapiens-video-kling-motion).");
|
|
627
|
+
if (!args.model && !args.templateSlug) {
|
|
628
|
+
throw new Error("action=create exige model (ex: sapiens-video-seedance, sapiens-video-kling, sapiens-video-wan, sapiens-video-kling-motion) ou templateSlug (a receita traz o motor; veja action=templates).");
|
|
575
629
|
}
|
|
576
630
|
// Frame inicial/final por arquivo local (paridade com o upload do site):
|
|
577
631
|
// lê do disco e injeta em references role start/end. O backend
|
|
@@ -583,6 +637,8 @@ export async function video(args) {
|
|
|
583
637
|
return await convexAction("mcpExtrasActions:mcpVideoCreateAndRender", {
|
|
584
638
|
sessionToken,
|
|
585
639
|
model: args.model,
|
|
640
|
+
templateSlug: args.templateSlug,
|
|
641
|
+
brief: args.brief,
|
|
586
642
|
prompt: args.prompt,
|
|
587
643
|
aspectRatio: args.aspectRatio,
|
|
588
644
|
durationSec: args.durationSec,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sapiens-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.51.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",
|