elo-skills-mcp 1.0.0 → 2.0.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 +30 -0
- package/package.json +6 -1
- package/src/api-client.js +90 -0
- package/src/device.js +35 -0
- package/src/license.js +7 -55
- package/src/make-tool.js +32 -0
- package/src/render.js +32 -0
- package/src/server.js +6 -5
- package/src/tools/conteudo.js +18 -260
- package/src/tools/estrategia.js +9 -180
- package/src/tools/estrutura.js +15 -270
- package/src/tools/index.js +1 -0
- package/src/tools/marca.js +12 -324
- package/src/tools/orquestrador.js +33 -0
- package/src/tools/setup.js +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# ELO Skills MCP
|
|
2
|
+
|
|
3
|
+
**O Método ELO Criativo dentro do seu Claude.** 18 skills de estratégia de conteúdo — post, reels, carrossel, stories, legenda, hooks, brief, ideias, análise, essência, branding, posicionamento, anúncio, público e mais — todas no seu tom de marca.
|
|
4
|
+
|
|
5
|
+
> Produto de acesso pago. Requer uma **chave de licença** (`ELO_LICENSE_KEY`). Adquira em **[ramonniellymorais.com.br/elo-skills](https://ramonniellymorais.com.br/elo-skills)**.
|
|
6
|
+
|
|
7
|
+
## Como usar (após a compra)
|
|
8
|
+
|
|
9
|
+
Você recebe sua chave por e-mail com o passo a passo completo. Em resumo:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g elo-skills-mcp
|
|
13
|
+
claude mcp add elo-skills --env ELO_LICENSE_KEY=SUA-CHAVE -- elo-skills-mcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Reinicie o Claude Code e rode `elo_setup` para configurar sua marca. Depois é só usar as skills (`elo_post`, `elo_carrossel`, etc.).
|
|
17
|
+
|
|
18
|
+
## Requisitos
|
|
19
|
+
|
|
20
|
+
- [Claude Code](https://claude.ai/download)
|
|
21
|
+
- [Node.js](https://nodejs.org) 18+
|
|
22
|
+
- Conexão com a internet (as skills validam a licença a cada uso)
|
|
23
|
+
|
|
24
|
+
## Suporte
|
|
25
|
+
|
|
26
|
+
Dúvida ou erro? **contato@ramonniellymorais.com.br**
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
© Ramonnielly Silva Morais — Método ELO Criativo. Uso pessoal mediante licença. Redistribuição não autorizada.
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "elo-skills-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Método ELO Criativo — Skills de Estratégia de Conteúdo para Claude",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"elo-skills-mcp": "index.js"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"index.js",
|
|
12
|
+
"src/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
10
15
|
"scripts": {
|
|
11
16
|
"start": "node index.js",
|
|
12
17
|
"dev": "node --watch index.js"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Cliente da API de prompts do Método ELO.
|
|
2
|
+
// O método NÃO viaja no pacote npm: cada tool busca seu template aqui,
|
|
3
|
+
// numa API autenticada que valida a licença server-side.
|
|
4
|
+
//
|
|
5
|
+
// Cache só em memória (some ao fechar o Claude) — nunca grava o método em disco.
|
|
6
|
+
// Fallback offline limitado: usa cache em RAM por até HARD_OFFLINE_LIMIT desde a
|
|
7
|
+
// última validação online bem-sucedida; passado esse limite, bloqueia mesmo offline.
|
|
8
|
+
|
|
9
|
+
import { getDeviceHash, getDeviceLabel } from './device.js'
|
|
10
|
+
|
|
11
|
+
const API_URL = process.env.ELO_API_URL || 'https://elo-skills-mcp.vercel.app'
|
|
12
|
+
const TEMPLATE_TTL = 1000 * 60 * 20 // 20 min de cache em RAM por tool
|
|
13
|
+
const HARD_OFFLINE_LIMIT = 1000 * 60 * 60 * 72 // 72h sem validação online → bloqueia
|
|
14
|
+
|
|
15
|
+
const PURCHASE_URL = 'ramonniellymorais.com.br/elo-skills'
|
|
16
|
+
const SUPPORT_EMAIL = 'contato@ramonniellymorais.com.br'
|
|
17
|
+
|
|
18
|
+
const templateCache = new Map() // toolName -> { template, ts }
|
|
19
|
+
let lastOnlineOk = 0
|
|
20
|
+
|
|
21
|
+
export async function fetchTemplate(toolName) {
|
|
22
|
+
const key = process.env.ELO_LICENSE_KEY
|
|
23
|
+
if (!key) throw new Error(missingKeyMessage())
|
|
24
|
+
|
|
25
|
+
const now = Date.now()
|
|
26
|
+
const cached = templateCache.get(toolName)
|
|
27
|
+
if (cached && now - cached.ts < TEMPLATE_TTL) return cached.template
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const res = await fetch(`${API_URL}/api/tool`, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'Authorization': `Bearer ${key}`
|
|
35
|
+
},
|
|
36
|
+
body: JSON.stringify({
|
|
37
|
+
tool: toolName,
|
|
38
|
+
device_hash: getDeviceHash(key),
|
|
39
|
+
device_label: getDeviceLabel()
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
if (res.status === 401 || res.status === 403) {
|
|
44
|
+
const { reason } = await safeJson(res)
|
|
45
|
+
throw new Error(licenseError(reason))
|
|
46
|
+
}
|
|
47
|
+
if (res.status === 429) {
|
|
48
|
+
throw new Error('❌ Muitas requisições em pouco tempo. Aguarde alguns minutos e tente de novo.')
|
|
49
|
+
}
|
|
50
|
+
if (!res.ok) throw new Error(`Erro ${res.status} ao buscar a skill.`)
|
|
51
|
+
|
|
52
|
+
const data = await safeJson(res)
|
|
53
|
+
if (!data.template) throw new Error('Resposta inválida do servidor.')
|
|
54
|
+
|
|
55
|
+
templateCache.set(toolName, { template: data.template, ts: now })
|
|
56
|
+
lastOnlineOk = now
|
|
57
|
+
return data.template
|
|
58
|
+
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err.message?.startsWith('❌')) throw err
|
|
61
|
+
// Falha de rede: usa cache em RAM se ainda dentro da janela offline
|
|
62
|
+
if (cached && now - lastOnlineOk < HARD_OFFLINE_LIMIT) return cached.template
|
|
63
|
+
throw new Error('❌ Sem conexão com o servidor das skills ELO. Verifique sua internet e tente de novo.')
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function missingKeyMessage() {
|
|
68
|
+
return (
|
|
69
|
+
'❌ Licença não encontrada.\n\n' +
|
|
70
|
+
'Adicione sua chave de licença no arquivo de configuração do Claude Code:\n\n' +
|
|
71
|
+
'"ELO_LICENSE_KEY": "SUA-CHAVE-AQUI"\n\n' +
|
|
72
|
+
`Adquira sua licença em: ${PURCHASE_URL}`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function licenseError(reason) {
|
|
77
|
+
switch (reason) {
|
|
78
|
+
case 'not_found': return `❌ Chave de licença inválida. Verifique a chave ou adquira em ${PURCHASE_URL}`
|
|
79
|
+
case 'revoked': return `❌ Sua licença foi revogada. Entre em contato: ${SUPPORT_EMAIL}`
|
|
80
|
+
case 'refunded': return '❌ Esta licença foi reembolsada e está inativa.'
|
|
81
|
+
case 'expired': return `❌ Sua licença expirou. Renove em ${PURCHASE_URL}`
|
|
82
|
+
case 'suspended': return `❌ Sua licença está suspensa. Entre em contato: ${SUPPORT_EMAIL}`
|
|
83
|
+
case 'too_many_devices': return `❌ Limite de dispositivos atingido para esta licença. Entre em contato: ${SUPPORT_EMAIL}`
|
|
84
|
+
default: return `❌ Não foi possível validar sua licença. Adquira ou renove em ${PURCHASE_URL}`
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function safeJson(res) {
|
|
89
|
+
try { return await res.json() } catch { return {} }
|
|
90
|
+
}
|
package/src/device.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Identidade de dispositivo para binding de licença.
|
|
2
|
+
// Gera um id estável por máquina (guardado em ~/.elo/device.json) e
|
|
3
|
+
// só envia ao servidor o HASH combinado com a chave — nunca o id cru.
|
|
4
|
+
|
|
5
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs'
|
|
6
|
+
import { homedir, hostname } from 'os'
|
|
7
|
+
import { join } from 'path'
|
|
8
|
+
import { createHash, randomUUID } from 'crypto'
|
|
9
|
+
|
|
10
|
+
const ELO_DIR = join(homedir(), '.elo')
|
|
11
|
+
const DEVICE_PATH = join(ELO_DIR, 'device.json')
|
|
12
|
+
|
|
13
|
+
function getDeviceId() {
|
|
14
|
+
if (!existsSync(ELO_DIR)) mkdirSync(ELO_DIR, { recursive: true })
|
|
15
|
+
if (existsSync(DEVICE_PATH)) {
|
|
16
|
+
try {
|
|
17
|
+
const saved = JSON.parse(readFileSync(DEVICE_PATH, 'utf-8'))
|
|
18
|
+
if (saved.id) return saved.id
|
|
19
|
+
} catch { /* recria abaixo */ }
|
|
20
|
+
}
|
|
21
|
+
const id = randomUUID()
|
|
22
|
+
writeFileSync(DEVICE_PATH, JSON.stringify({ id, created_at: new Date().toISOString() }, null, 2))
|
|
23
|
+
return id
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Hash estável do dispositivo combinado com a chave. O servidor limita
|
|
27
|
+
// quantos hashes distintos uma licença pode ter (max_activations).
|
|
28
|
+
export function getDeviceHash(licenseKey) {
|
|
29
|
+
return createHash('sha256').update(`${getDeviceId()}:${licenseKey}`).digest('hex')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Rótulo amigável só para suporte ("MacBook da Ramon"). Não identifica de forma única.
|
|
33
|
+
export function getDeviceLabel() {
|
|
34
|
+
try { return hostname() } catch { return 'unknown' }
|
|
35
|
+
}
|
package/src/license.js
CHANGED
|
@@ -1,66 +1,18 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
1
|
+
// Checagem local mínima: só confirma que a chave está presente.
|
|
2
|
+
// A validação REAL (ativa / expirada / revogada / dispositivos) acontece
|
|
3
|
+
// server-side, na API de prompts, a cada chamada de tool. Nenhuma URL ou
|
|
4
|
+
// chave de Supabase vive mais no pacote.
|
|
3
5
|
|
|
4
|
-
const
|
|
5
|
-
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImR6aWNvcHN3cWlqbGpocmltdGVlIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzU2NzExNjUsImV4cCI6MjA5MTI0NzE2NX0.JgL9jhJIxqQLm8aeKNLKh8HOS2idGqUOslwAqJUMoaY'
|
|
6
|
+
const PURCHASE_URL = 'ramonniellymorais.com.br/elo-skills'
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
let cacheTime = 0
|
|
9
|
-
const CACHE_TTL = 1000 * 60 * 60 // 1 hora
|
|
10
|
-
|
|
11
|
-
export async function validateLicense() {
|
|
8
|
+
export function assertLicensePresent() {
|
|
12
9
|
const key = process.env.ELO_LICENSE_KEY
|
|
13
|
-
|
|
14
10
|
if (!key) {
|
|
15
11
|
throw new Error(
|
|
16
12
|
'❌ Licença não encontrada.\n\n' +
|
|
17
13
|
'Adicione sua chave de licença no arquivo de configuração do Claude Code:\n\n' +
|
|
18
14
|
'"ELO_LICENSE_KEY": "SUA-CHAVE-AQUI"\n\n' +
|
|
19
|
-
|
|
15
|
+
`Adquira sua licença em: ${PURCHASE_URL}`
|
|
20
16
|
)
|
|
21
17
|
}
|
|
22
|
-
|
|
23
|
-
// Cache válido — não bate no Supabase a cada chamada
|
|
24
|
-
const now = Date.now()
|
|
25
|
-
if (cache && now - cacheTime < CACHE_TTL) return cache
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const res = await fetch(
|
|
29
|
-
`${SUPABASE_URL}/rest/v1/licenses?key=eq.${encodeURIComponent(key)}&select=active,expires_at,owner_name`,
|
|
30
|
-
{
|
|
31
|
-
headers: {
|
|
32
|
-
'apikey': SUPABASE_ANON_KEY,
|
|
33
|
-
'Authorization': `Bearer ${SUPABASE_ANON_KEY}`
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
if (!res.ok) throw new Error('Erro ao verificar licença.')
|
|
39
|
-
|
|
40
|
-
const data = await res.json()
|
|
41
|
-
|
|
42
|
-
if (!data.length) {
|
|
43
|
-
throw new Error('❌ Chave de licença inválida. Verifique a chave ou adquira em ramonniellymorais.com.br/elo-skills')
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const license = data[0]
|
|
47
|
-
|
|
48
|
-
if (!license.active) {
|
|
49
|
-
throw new Error('❌ Sua licença está desativada. Entre em contato: contato@ramonniellymorais.com.br')
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if (license.expires_at && new Date(license.expires_at) < new Date()) {
|
|
53
|
-
throw new Error('❌ Sua licença expirou. Renove em ramonniellymorais.com.br/elo-skills')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
cache = license
|
|
57
|
-
cacheTime = now
|
|
58
|
-
return license
|
|
59
|
-
|
|
60
|
-
} catch (err) {
|
|
61
|
-
if (err.message.startsWith('❌')) throw err
|
|
62
|
-
// Fallback offline: se Supabase estiver fora, usa cache anterior
|
|
63
|
-
if (cache) return cache
|
|
64
|
-
throw new Error('❌ Não foi possível verificar a licença. Verifique sua conexão.')
|
|
65
|
-
}
|
|
66
18
|
}
|
package/src/make-tool.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Fábrica de tools "finas". Os metadados (name/description/inputSchema) ficam
|
|
2
|
+
// locais e públicos — o MCP precisa deles para listar. O TEMPLATE do método é
|
|
3
|
+
// buscado na API autenticada em runtime e renderizado em memória com o contexto
|
|
4
|
+
// de marca local (que nunca vai ao servidor).
|
|
5
|
+
|
|
6
|
+
import { getBrandConfig, formatBrandContext } from './brand.js'
|
|
7
|
+
import { fetchTemplate } from './api-client.js'
|
|
8
|
+
import { renderTemplate } from './render.js'
|
|
9
|
+
|
|
10
|
+
function resolveContext(marca) {
|
|
11
|
+
const config = getBrandConfig(marca || 'minha-marca')
|
|
12
|
+
if (!config) {
|
|
13
|
+
return `⚠️ Marca "${marca || 'minha-marca'}" não configurada. Use elo_setup primeiro.`
|
|
14
|
+
}
|
|
15
|
+
return formatBrandContext(config)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function makeTool({ name, description, inputSchema, prepare }) {
|
|
19
|
+
return {
|
|
20
|
+
name,
|
|
21
|
+
description,
|
|
22
|
+
inputSchema,
|
|
23
|
+
handler: async (args = {}) => {
|
|
24
|
+
const { marca, ...params } = args
|
|
25
|
+
const ctx = resolveContext(marca)
|
|
26
|
+
const data = prepare ? prepare(params) : params
|
|
27
|
+
const template = await fetchTemplate(name) // valida licença server-side; lança erro amigável
|
|
28
|
+
const body = renderTemplate(template, data)
|
|
29
|
+
return { content: [{ type: 'text', text: `${ctx}\n\n---\n\n${body}` }] }
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
package/src/render.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Renderizador leve estilo mustache para os templates do método.
|
|
2
|
+
// Suporta:
|
|
3
|
+
// {{var}} → substitui pelo valor (vazio se ausente)
|
|
4
|
+
// {{#var}}...{{/var}} → inclui o bloco só se var for "verdadeiro"
|
|
5
|
+
// {{^var}}...{{/var}} → inclui o bloco só se var for "falso" (else)
|
|
6
|
+
//
|
|
7
|
+
// Os templates vêm da API autenticada; o cliente injeta os params aqui,
|
|
8
|
+
// em memória, e nunca grava o método em disco.
|
|
9
|
+
|
|
10
|
+
function isTruthy(v) {
|
|
11
|
+
if (Array.isArray(v)) return v.length > 0
|
|
12
|
+
return v !== undefined && v !== null && v !== '' && v !== false && v !== 0
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function renderTemplate(template, data = {}) {
|
|
16
|
+
// Seções (normais e invertidas) — resolvidas primeiro
|
|
17
|
+
let out = template.replace(
|
|
18
|
+
/\{\{([#^])(\w+)\}\}([\s\S]*?)\{\{\/\2\}\}/g,
|
|
19
|
+
(_, kind, key, inner) => {
|
|
20
|
+
const show = kind === '#' ? isTruthy(data[key]) : !isTruthy(data[key])
|
|
21
|
+
return show ? inner : ''
|
|
22
|
+
}
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
// Variáveis simples
|
|
26
|
+
out = out.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
27
|
+
const v = data[key]
|
|
28
|
+
return v === undefined || v === null ? '' : String(v)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
return out
|
|
32
|
+
}
|
package/src/server.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
4
|
-
import {
|
|
4
|
+
import { assertLicensePresent } from './license.js'
|
|
5
5
|
import * as tools from './tools/index.js'
|
|
6
6
|
|
|
7
7
|
// Lista de todas as ferramentas registradas
|
|
@@ -9,7 +9,7 @@ const ALL_TOOLS = Object.values(tools)
|
|
|
9
9
|
|
|
10
10
|
export async function createServer() {
|
|
11
11
|
const server = new Server(
|
|
12
|
-
{ name: 'elo-skills-mcp', version: '
|
|
12
|
+
{ name: 'elo-skills-mcp', version: '2.0.0' },
|
|
13
13
|
{ capabilities: { tools: {} } }
|
|
14
14
|
)
|
|
15
15
|
|
|
@@ -26,12 +26,13 @@ export async function createServer() {
|
|
|
26
26
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
27
27
|
const { name, arguments: args } = request.params
|
|
28
28
|
|
|
29
|
-
//
|
|
30
|
-
//
|
|
29
|
+
// Checagem local de presença da chave antes de qualquer execução.
|
|
30
|
+
// A validação real (ativa/expirada/revogada) é server-side, na API de prompts.
|
|
31
|
+
// Setup é liberado sem licença para facilitar o onboarding.
|
|
31
32
|
const FREE_TOOLS = ['elo_setup']
|
|
32
33
|
if (!FREE_TOOLS.includes(name)) {
|
|
33
34
|
try {
|
|
34
|
-
|
|
35
|
+
assertLicensePresent()
|
|
35
36
|
} catch (err) {
|
|
36
37
|
return {
|
|
37
38
|
content: [{ type: 'text', text: err.message }],
|
package/src/tools/conteudo.js
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
1
|
// Skills de criação de conteúdo: post, reels, stories, legenda, hooks
|
|
2
|
+
// Tools "finas": só metadados. O template do método vem da API autenticada.
|
|
2
3
|
|
|
3
|
-
import {
|
|
4
|
+
import { makeTool } from '../make-tool.js'
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
return {
|
|
7
|
-
marca: { type: 'string', description: 'Slug da marca configurada (ex: minha-marca). Se omitido, usa a primeira marca salva.' }
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function resolveContext(marca) {
|
|
12
|
-
const config = getBrandConfig(marca || 'minha-marca')
|
|
13
|
-
if (!config) {
|
|
14
|
-
return `⚠️ Marca "${marca || 'minha-marca'}" não configurada. Use elo_setup primeiro.`
|
|
15
|
-
}
|
|
16
|
-
return formatBrandContext(config)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// ─── POST ────────────────────────────────────────────────────────────────────
|
|
6
|
+
const marcaParam = { type: 'string', description: 'Slug da marca configurada (ex: minha-marca). Se omitido, usa a primeira marca salva.' }
|
|
20
7
|
|
|
21
|
-
export const postTool = {
|
|
8
|
+
export const postTool = makeTool({
|
|
22
9
|
name: 'elo_post',
|
|
23
10
|
description: 'Cria um post completo para Instagram com estrutura ELO. Inclui: gancho, desenvolvimento, CTA, sugestão de formato, etapa C.R.I.A. e KPI.',
|
|
24
11
|
inputSchema: {
|
|
@@ -27,64 +14,13 @@ export const postTool = {
|
|
|
27
14
|
tema: { type: 'string', description: 'Tema ou assunto do post' },
|
|
28
15
|
formato: { type: 'string', enum: ['carrossel', 'reels', 'foto', 'livre'], description: 'Formato preferido' },
|
|
29
16
|
cria: { type: 'string', enum: ['C', 'R', 'I', 'A'], description: 'Etapa do C.R.I.A. (Conexão/Repertório/Identidade/Ação)' },
|
|
30
|
-
|
|
17
|
+
marca: marcaParam
|
|
31
18
|
},
|
|
32
19
|
required: ['tema']
|
|
33
|
-
},
|
|
34
|
-
handler: async ({ tema, formato, cria, marca }) => {
|
|
35
|
-
const ctx = resolveContext(marca)
|
|
36
|
-
return {
|
|
37
|
-
content: [{
|
|
38
|
-
type: 'text',
|
|
39
|
-
text: `${ctx}
|
|
40
|
-
|
|
41
|
-
---
|
|
42
|
-
|
|
43
|
-
# INSTRUÇÃO — POST INSTAGRAM COM MÉTODO ELO
|
|
44
|
-
|
|
45
|
-
Crie um post completo para Instagram sobre: **"${tema}"**
|
|
46
|
-
${formato ? `Formato preferido: ${formato}` : ''}
|
|
47
|
-
${cria ? `Etapa do C.R.I.A.: ${cria}` : ''}
|
|
48
|
-
|
|
49
|
-
## ESTRUTURA OBRIGATÓRIA
|
|
50
|
-
|
|
51
|
-
### 1. DIAGNÓSTICO ESTRATÉGICO (silencioso — não mostre para a usuária)
|
|
52
|
-
- Qual etapa do C.R.I.A. este post serve?
|
|
53
|
-
- Qual gatilho mental será ativado?
|
|
54
|
-
- Qual nível de consciência do avatar?
|
|
55
|
-
|
|
56
|
-
### 2. GANCHO (primeira linha — máx 1 frase)
|
|
57
|
-
Use a identidade verbal da marca. Provoque, gere identificação ou quebre expectativa.
|
|
58
|
-
NUNCA comece com "Você sabia que..." ou "Hoje vou falar sobre..."
|
|
59
|
-
|
|
60
|
-
### 3. DESENVOLVIMENTO (3 a 5 parágrafos)
|
|
61
|
-
- Alterne frases longas com frases curtíssimas de impacto
|
|
62
|
-
- Inclua pelo menos 1 número, dado ou exemplo real
|
|
63
|
-
- Use o vocabulário da marca
|
|
64
|
-
|
|
65
|
-
### 4. VIRADA
|
|
66
|
-
Momento de reframe ou insight inesperado. A frase mais forte do post.
|
|
67
|
-
|
|
68
|
-
### 5. CTA (convite, não ordem)
|
|
69
|
-
Alinhado com o objetivo de negócio. Nunca forçado.
|
|
70
|
-
|
|
71
|
-
### 6. LEGENDA TÉCNICA
|
|
72
|
-
- Sugestão de formato visual
|
|
73
|
-
- 3 a 5 hashtags estratégicas (não genéricas)
|
|
74
|
-
- Melhor horário de publicação
|
|
75
|
-
|
|
76
|
-
### 7. MÉTRICAS
|
|
77
|
-
- Etapa C.R.I.A.: [C/R/I/A]
|
|
78
|
-
- KPI principal: [alcance / salvamentos / cliques / conversões]
|
|
79
|
-
- Gatilho ativado: [nome do gatilho]`
|
|
80
|
-
}]
|
|
81
|
-
}
|
|
82
20
|
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ─── REELS ───────────────────────────────────────────────────────────────────
|
|
21
|
+
})
|
|
86
22
|
|
|
87
|
-
export const reelsTool = {
|
|
23
|
+
export const reelsTool = makeTool({
|
|
88
24
|
name: 'elo_reels',
|
|
89
25
|
description: 'Cria roteiro completo de Reels (30 segundos) com cortes, legenda e análise estratégica.',
|
|
90
26
|
inputSchema: {
|
|
@@ -92,62 +28,13 @@ export const reelsTool = {
|
|
|
92
28
|
properties: {
|
|
93
29
|
tema: { type: 'string', description: 'Tema do Reels' },
|
|
94
30
|
estilo: { type: 'string', enum: ['dialogo_interno', 'quebra_padrao', 'broll_narracao', 'livre'], description: 'Estilo visual' },
|
|
95
|
-
|
|
31
|
+
marca: marcaParam
|
|
96
32
|
},
|
|
97
33
|
required: ['tema']
|
|
98
|
-
},
|
|
99
|
-
handler: async ({ tema, estilo, marca }) => {
|
|
100
|
-
const ctx = resolveContext(marca)
|
|
101
|
-
return {
|
|
102
|
-
content: [{
|
|
103
|
-
type: 'text',
|
|
104
|
-
text: `${ctx}
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
# INSTRUÇÃO — ROTEIRO DE REELS 30s
|
|
109
|
-
|
|
110
|
-
Tema: **"${tema}"**
|
|
111
|
-
${estilo ? `Estilo: ${estilo}` : ''}
|
|
112
|
-
|
|
113
|
-
## ESTRUTURA DO ROTEIRO
|
|
114
|
-
|
|
115
|
-
### GANCHO (0–3s)
|
|
116
|
-
A primeira frase dita ou o texto na tela. Deve parar o scroll. Use a voz da marca.
|
|
117
|
-
|
|
118
|
-
### CORTE 1 (3–8s)
|
|
119
|
-
Desenvolvimento inicial. Problema ou provocação.
|
|
120
|
-
|
|
121
|
-
### CORTE 2 (8–16s)
|
|
122
|
-
Tensão ou dado surpreendente.
|
|
123
|
-
|
|
124
|
-
### CORTE 3 (16–24s)
|
|
125
|
-
Virada / insight principal.
|
|
126
|
-
|
|
127
|
-
### FECHAMENTO (24–30s)
|
|
128
|
-
CTA falado + texto na tela. Simples, direto.
|
|
129
|
-
|
|
130
|
-
---
|
|
131
|
-
|
|
132
|
-
## LEGENDA DO REELS
|
|
133
|
-
(máx 150 caracteres — continua nos comentários se necessário)
|
|
134
|
-
|
|
135
|
-
## TEXTO NA TELA (overlays)
|
|
136
|
-
Liste cada texto que aparece na tela, em ordem.
|
|
137
|
-
|
|
138
|
-
## ANÁLISE ESTRATÉGICA
|
|
139
|
-
- Avatar ativado
|
|
140
|
-
- Etapa C.R.I.A.
|
|
141
|
-
- Gatilho principal
|
|
142
|
-
- KPI esperado: [alcance / compartilhamentos / salvamentos]`
|
|
143
|
-
}]
|
|
144
|
-
}
|
|
145
34
|
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// ─── STORIES ─────────────────────────────────────────────────────────────────
|
|
35
|
+
})
|
|
149
36
|
|
|
150
|
-
export const storiesTool = {
|
|
37
|
+
export const storiesTool = makeTool({
|
|
151
38
|
name: 'elo_stories',
|
|
152
39
|
description: 'Cria sequência de 5 stories com 5 ângulos diferentes, análise neurológica e score de impacto.',
|
|
153
40
|
inputSchema: {
|
|
@@ -155,58 +42,13 @@ export const storiesTool = {
|
|
|
155
42
|
properties: {
|
|
156
43
|
tema: { type: 'string', description: 'Tema ou objetivo da sequência' },
|
|
157
44
|
objetivo: { type: 'string', enum: ['engajamento', 'autoridade', 'venda', 'bastidor', 'livre'], description: 'Objetivo da sequência' },
|
|
158
|
-
|
|
45
|
+
marca: marcaParam
|
|
159
46
|
},
|
|
160
47
|
required: ['tema']
|
|
161
|
-
},
|
|
162
|
-
handler: async ({ tema, objetivo, marca }) => {
|
|
163
|
-
const ctx = resolveContext(marca)
|
|
164
|
-
return {
|
|
165
|
-
content: [{
|
|
166
|
-
type: 'text',
|
|
167
|
-
text: `${ctx}
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
# INSTRUÇÃO — SEQUÊNCIA DE 5 STORIES
|
|
172
|
-
|
|
173
|
-
Tema: **"${tema}"**
|
|
174
|
-
${objetivo ? `Objetivo: ${objetivo}` : ''}
|
|
175
|
-
|
|
176
|
-
## PARA CADA STORY (1 a 5):
|
|
177
|
-
|
|
178
|
-
**Story [N] — [Nome do ângulo]**
|
|
179
|
-
- **Tipo:** (texto / foto / vídeo / enquete / caixinha)
|
|
180
|
-
- **Ângulo:** (provocação / humor / bastidor / dado / virada)
|
|
181
|
-
- **Texto principal:** (exatamente o que vai escrito)
|
|
182
|
-
- **Elemento interativo:** (enquete, caixinha, contagem, link — se aplicável)
|
|
183
|
-
- **Transição:** como leva para o próximo story
|
|
184
|
-
|
|
185
|
-
---
|
|
186
|
-
|
|
187
|
-
## OS 5 ÂNGULOS OBRIGATÓRIOS
|
|
188
|
-
1. Provocação / identificação imediata
|
|
189
|
-
2. Dado ou prova inesperada
|
|
190
|
-
3. Bastidor ou história real
|
|
191
|
-
4. Humor ou ironia com sentido
|
|
192
|
-
5. Virada + CTA
|
|
193
|
-
|
|
194
|
-
---
|
|
195
|
-
|
|
196
|
-
## ANÁLISE NEUROLÓGICA
|
|
197
|
-
Para cada story: qual mecanismo psicológico ativa? (curiosidade, identidade, contraste, pertencimento, urgência)
|
|
198
|
-
|
|
199
|
-
## SCORE DE IMPACTO
|
|
200
|
-
Dê uma nota de 0 a 10 para a sequência em: Retenção / Engajamento / Conversão
|
|
201
|
-
Justifique cada nota em uma linha.`
|
|
202
|
-
}]
|
|
203
|
-
}
|
|
204
48
|
}
|
|
205
|
-
}
|
|
49
|
+
})
|
|
206
50
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
export const legendaTool = {
|
|
51
|
+
export const legendaTool = makeTool({
|
|
210
52
|
name: 'elo_legenda',
|
|
211
53
|
description: 'Cria ou aprimora a legenda de um post pronto. Adiciona: linha de impacto, expansão, momento aha e CTA com sentido.',
|
|
212
54
|
inputSchema: {
|
|
@@ -214,52 +56,13 @@ export const legendaTool = {
|
|
|
214
56
|
properties: {
|
|
215
57
|
conteudo: { type: 'string', description: 'O post ou conteúdo que precisa de legenda' },
|
|
216
58
|
objetivo: { type: 'string', description: 'Objetivo da legenda: engajar, vender, salvar, compartilhar' },
|
|
217
|
-
|
|
59
|
+
marca: marcaParam
|
|
218
60
|
},
|
|
219
61
|
required: ['conteudo']
|
|
220
|
-
},
|
|
221
|
-
handler: async ({ conteudo, objetivo, marca }) => {
|
|
222
|
-
const ctx = resolveContext(marca)
|
|
223
|
-
return {
|
|
224
|
-
content: [{
|
|
225
|
-
type: 'text',
|
|
226
|
-
text: `${ctx}
|
|
227
|
-
|
|
228
|
-
---
|
|
229
|
-
|
|
230
|
-
# INSTRUÇÃO — LEGENDA COM IMPACTO
|
|
231
|
-
|
|
232
|
-
Conteúdo recebido:
|
|
233
|
-
"""
|
|
234
|
-
${conteudo}
|
|
235
|
-
"""
|
|
236
|
-
|
|
237
|
-
${objetivo ? `Objetivo: ${objetivo}` : ''}
|
|
238
|
-
|
|
239
|
-
## ESTRUTURA DA LEGENDA
|
|
240
|
-
|
|
241
|
-
### 1. LINHA DE IMPACTO (primeira linha — máx 1 frase)
|
|
242
|
-
A frase que aparece antes do "ver mais". Deve parar o scroll. Use a voz da marca.
|
|
243
|
-
|
|
244
|
-
### 2. EXPANSÃO (2 a 4 parágrafos)
|
|
245
|
-
Aprofunda o tema. Alterna frases longas com curtas. Inclui 1 dado ou exemplo real.
|
|
246
|
-
|
|
247
|
-
### 3. MOMENTO AHA
|
|
248
|
-
A frase mais forte. O insight que a pessoa vai querer salvar ou copiar.
|
|
249
|
-
|
|
250
|
-
### 4. CTA COM SENTIDO
|
|
251
|
-
Convite genuíno, não ordem. Alinhado ao objetivo de negócio.
|
|
252
|
-
|
|
253
|
-
### 5. HASHTAGS
|
|
254
|
-
5 hashtags: 2 de nicho + 2 de tema + 1 de comunidade. Sem hashtags genéricas (#vida, #amor).`
|
|
255
|
-
}]
|
|
256
|
-
}
|
|
257
62
|
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
// ─── HOOKS ───────────────────────────────────────────────────────────────────
|
|
63
|
+
})
|
|
261
64
|
|
|
262
|
-
export const hooksTool = {
|
|
65
|
+
export const hooksTool = makeTool({
|
|
263
66
|
name: 'elo_hooks',
|
|
264
67
|
description: 'Gera 20 hooks para um tema. Você escolhe os melhores e recebe 10 refinados com variações.',
|
|
265
68
|
inputSchema: {
|
|
@@ -267,53 +70,8 @@ export const hooksTool = {
|
|
|
267
70
|
properties: {
|
|
268
71
|
tema: { type: 'string', description: 'Tema para gerar hooks' },
|
|
269
72
|
formato: { type: 'string', enum: ['reels', 'carrossel', 'post', 'stories', 'qualquer'], description: 'Formato de destino' },
|
|
270
|
-
|
|
73
|
+
marca: marcaParam
|
|
271
74
|
},
|
|
272
75
|
required: ['tema']
|
|
273
|
-
},
|
|
274
|
-
handler: async ({ tema, formato, marca }) => {
|
|
275
|
-
const ctx = resolveContext(marca)
|
|
276
|
-
return {
|
|
277
|
-
content: [{
|
|
278
|
-
type: 'text',
|
|
279
|
-
text: `${ctx}
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
# INSTRUÇÃO — GERADOR DE HOOKS VIRAIS
|
|
284
|
-
|
|
285
|
-
Tema: **"${tema}"**
|
|
286
|
-
${formato ? `Formato: ${formato}` : ''}
|
|
287
|
-
|
|
288
|
-
## REGRAS DOS HOOKS
|
|
289
|
-
- NUNCA use "descubra" — é fraco e genérico
|
|
290
|
-
- Prefira perguntas desconfortáveis a afirmações seguras
|
|
291
|
-
- Use a voz e os bordões da marca
|
|
292
|
-
- Cada hook deve parar o scroll em 0,3 segundos
|
|
293
|
-
- Varie os padrões: pergunta / afirmação ousada / dado / paradoxo / inversão
|
|
294
|
-
|
|
295
|
-
## FASE 1 — GERAÇÃO
|
|
296
|
-
|
|
297
|
-
Crie 20 hooks para o tema. Organize em 4 categorias de 5:
|
|
298
|
-
|
|
299
|
-
**Pergunta Desconfortável (5 hooks)**
|
|
300
|
-
**Afirmação Polarizante (5 hooks)**
|
|
301
|
-
**Dado ou Número Inesperado (5 hooks)**
|
|
302
|
-
**Inversão ou Paradoxo (5 hooks)**
|
|
303
|
-
|
|
304
|
-
---
|
|
305
|
-
|
|
306
|
-
Após apresentar os 20, peça à usuária para escolher os favoritos.
|
|
307
|
-
Com as escolhas, refine os 10 melhores com variações (versão A e B para cada).
|
|
308
|
-
|
|
309
|
-
## FASE 2 — REFINAMENTO (após escolha da usuária)
|
|
310
|
-
|
|
311
|
-
Para cada hook escolhido:
|
|
312
|
-
- **Versão A:** mais provocativa
|
|
313
|
-
- **Versão B:** mais intrigante
|
|
314
|
-
- **Melhor formato de destino:** reels / carrossel / stories
|
|
315
|
-
- **Gatilho ativado:** curiosidade / identidade / contraste / urgência`
|
|
316
|
-
}]
|
|
317
|
-
}
|
|
318
76
|
}
|
|
319
|
-
}
|
|
77
|
+
})
|