grimoire-framework 1.0.20 → 1.1.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/.grimoire/install-manifest.yaml +2 -2
- package/bin/commands/marketplace.js +304 -0
- package/bin/commands/metrics.js +213 -55
- package/bin/commands/pro.js +237 -0
- package/bin/commands/sync.js +203 -0
- package/bin/grimoire-cli.js +9 -0
- package/marketplace/registry.json +96 -0
- package/package.json +2 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
# - SHA256 hashes for change detection
|
|
8
8
|
# - File types for categorization
|
|
9
9
|
#
|
|
10
|
-
version: 1.0
|
|
11
|
-
generated_at: "2026-02-22T14:
|
|
10
|
+
version: 1.1.0
|
|
11
|
+
generated_at: "2026-02-22T14:53:32.130Z"
|
|
12
12
|
generator: scripts/generate-install-manifest.js
|
|
13
13
|
file_count: 1011
|
|
14
14
|
files:
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire marketplace — Community Agent Registry
|
|
3
|
+
*
|
|
4
|
+
* grimoire marketplace list Navega agentes da comunidade
|
|
5
|
+
* grimoire marketplace install <slug> Instala um agente do marketplace
|
|
6
|
+
* grimoire marketplace search <term> Busca por especialidade ou tag
|
|
7
|
+
* grimoire marketplace submit Instruções para publicar seu agente
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const https = require('https');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
|
|
17
|
+
// ── Curated registry (built-in fallback, also fetched from GitHub) ─────────────
|
|
18
|
+
const REGISTRY_URL = 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/registry.json';
|
|
19
|
+
const CACHE_FILE = path.join(os.homedir(), '.grimoire-marketplace-cache.json');
|
|
20
|
+
const CACHE_TTL_MINS = 60; // 1 hour
|
|
21
|
+
|
|
22
|
+
const BUILTIN_AGENTS = [
|
|
23
|
+
{
|
|
24
|
+
slug: 'frontend-specialist',
|
|
25
|
+
name: 'Botticelli',
|
|
26
|
+
icon: '🎠',
|
|
27
|
+
specialty: 'Frontend & UI/UX',
|
|
28
|
+
tags: ['react', 'css', 'typescript', 'nextjs'],
|
|
29
|
+
author: 'grimoire-team',
|
|
30
|
+
description: 'Especialista em React, TypeScript e design systems. Ideal para times com foco em UI.',
|
|
31
|
+
gist: 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/frontend-specialist.md',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
slug: 'security-auditor',
|
|
35
|
+
name: 'Rembrandt',
|
|
36
|
+
icon: '🔐',
|
|
37
|
+
specialty: 'Segurança & Auditoria',
|
|
38
|
+
tags: ['security', 'owasp', 'pentest', 'audit'],
|
|
39
|
+
author: 'grimoire-team',
|
|
40
|
+
description: 'Audita código em busca de vulnerabilidades OWASP Top 10. SAST e code review de segurança.',
|
|
41
|
+
gist: 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/security-auditor.md',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
slug: 'ml-engineer',
|
|
45
|
+
name: 'Dalí',
|
|
46
|
+
icon: '🧠',
|
|
47
|
+
specialty: 'Machine Learning & AI',
|
|
48
|
+
tags: ['ml', 'python', 'pytorch', 'sklearn'],
|
|
49
|
+
author: 'grimoire-team',
|
|
50
|
+
description: 'Implementa pipelines de ML, feature engineering, treinamento e avaliação de modelos.',
|
|
51
|
+
gist: 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/ml-engineer.md',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
slug: 'api-designer',
|
|
55
|
+
name: 'Klimt',
|
|
56
|
+
icon: '🔗',
|
|
57
|
+
specialty: 'API Design & Documentação',
|
|
58
|
+
tags: ['rest', 'graphql', 'openapi', 'swagger'],
|
|
59
|
+
author: 'grimoire-team',
|
|
60
|
+
description: 'Projeta APIs RESTful e GraphQL. Gera documentação OpenAPI 3.x e valida contratos.',
|
|
61
|
+
gist: 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/api-designer.md',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
slug: 'database-expert',
|
|
65
|
+
name: 'Mondrian',
|
|
66
|
+
icon: '🗄️',
|
|
67
|
+
specialty: 'Banco de Dados & Queries',
|
|
68
|
+
tags: ['sql', 'postgresql', 'mongodb', 'redis'],
|
|
69
|
+
author: 'grimoire-team',
|
|
70
|
+
description: 'Otimiza queries, projeta schemas e revisa migrações. PostgreSQL, MongoDB, Redis.',
|
|
71
|
+
gist: 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/database-expert.md',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
slug: 'tech-writer',
|
|
75
|
+
name: 'Cervantes',
|
|
76
|
+
icon: '✍️',
|
|
77
|
+
specialty: 'Documentação Técnica',
|
|
78
|
+
tags: ['docs', 'readme', 'adr', 'wiki'],
|
|
79
|
+
author: 'grimoire-team',
|
|
80
|
+
description: 'Escreve e revisa documentação técnica: READMEs, ADRs, guias de API e wikis.',
|
|
81
|
+
gist: 'https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/tech-writer.md',
|
|
82
|
+
},
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
// ── Fetcher ────────────────────────────────────────────────────────────────────
|
|
86
|
+
async function fetchRegistry() {
|
|
87
|
+
// Use cache if fresh
|
|
88
|
+
if (fs.existsSync(CACHE_FILE)) {
|
|
89
|
+
try {
|
|
90
|
+
const cache = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
|
91
|
+
const minsSince = (Date.now() - cache.ts) / 60000;
|
|
92
|
+
if (minsSince < CACHE_TTL_MINS) return cache.agents;
|
|
93
|
+
} catch (_) { }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Try to fetch remote registry
|
|
97
|
+
try {
|
|
98
|
+
const remote = await new Promise((resolve, reject) => {
|
|
99
|
+
const req = https.get(REGISTRY_URL, { timeout: 4000 }, (res) => {
|
|
100
|
+
let body = '';
|
|
101
|
+
res.on('data', d => body += d);
|
|
102
|
+
res.on('end', () => { try { resolve(JSON.parse(body)); } catch (e) { reject(e); } });
|
|
103
|
+
});
|
|
104
|
+
req.on('error', reject);
|
|
105
|
+
req.on('timeout', () => req.destroy());
|
|
106
|
+
});
|
|
107
|
+
const agents = remote.agents || BUILTIN_AGENTS;
|
|
108
|
+
fs.writeFileSync(CACHE_FILE, JSON.stringify({ ts: Date.now(), agents }), 'utf8');
|
|
109
|
+
return agents;
|
|
110
|
+
} catch (_) {
|
|
111
|
+
// Fall back to built-in
|
|
112
|
+
return BUILTIN_AGENTS;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ── Commands ───────────────────────────────────────────────────────────────────
|
|
117
|
+
async function run(args) {
|
|
118
|
+
const sub = args[0];
|
|
119
|
+
switch (sub) {
|
|
120
|
+
case 'install': await installAgent(args[1]); break;
|
|
121
|
+
case 'search': await searchAgents(args.slice(1)); break;
|
|
122
|
+
case 'submit': showSubmitGuide(); break;
|
|
123
|
+
case 'list':
|
|
124
|
+
default: await listAgents(args); break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── List ───────────────────────────────────────────────────────────────────────
|
|
129
|
+
async function listAgents(args) {
|
|
130
|
+
process.stdout.write('🔍 Carregando registry...');
|
|
131
|
+
const agents = await fetchRegistry();
|
|
132
|
+
const tagFilter = args.find(a => a.startsWith('--tag='));
|
|
133
|
+
const filtered = tagFilter
|
|
134
|
+
? agents.filter(a => a.tags.includes(tagFilter.replace('--tag=', '')))
|
|
135
|
+
: agents;
|
|
136
|
+
|
|
137
|
+
console.log(`\r\n🏪 Grimoire Marketplace — ${filtered.length} agentes disponíveis\n`);
|
|
138
|
+
console.log(' ' + '─'.repeat(60));
|
|
139
|
+
|
|
140
|
+
filtered.forEach(agent => {
|
|
141
|
+
const tags = agent.tags.slice(0, 3).map(t => `#${t}`).join(' ');
|
|
142
|
+
console.log(`
|
|
143
|
+
${agent.icon} **@${agent.slug}** — ${agent.name}
|
|
144
|
+
${agent.specialty}
|
|
145
|
+
${agent.description}
|
|
146
|
+
${tags} by @${agent.author}`);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
console.log(`\n ${'─'.repeat(60)}`);
|
|
150
|
+
console.log(` 💡 Para instalar: grimoire marketplace install <slug>`);
|
|
151
|
+
console.log(` 💡 Para buscar: grimoire marketplace search <term>`);
|
|
152
|
+
console.log(` 💡 Para publicar: grimoire marketplace submit\n`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ── Search ─────────────────────────────────────────────────────────────────────
|
|
156
|
+
async function searchAgents(terms) {
|
|
157
|
+
const query = terms.join(' ').toLowerCase();
|
|
158
|
+
if (!query) { console.log('Usage: grimoire marketplace search <term>'); return; }
|
|
159
|
+
|
|
160
|
+
const agents = await fetchRegistry();
|
|
161
|
+
const results = agents.filter(a =>
|
|
162
|
+
a.slug.includes(query) ||
|
|
163
|
+
a.specialty.toLowerCase().includes(query) ||
|
|
164
|
+
a.description.toLowerCase().includes(query) ||
|
|
165
|
+
a.tags.some(t => t.includes(query))
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
console.log(`\n🔍 Results for "${query}" — ${results.length} found\n`);
|
|
169
|
+
if (results.length === 0) {
|
|
170
|
+
console.log(' No agents found. Try: grimoire marketplace list\n'); return;
|
|
171
|
+
}
|
|
172
|
+
results.forEach(a => {
|
|
173
|
+
console.log(` ${a.icon} @${a.slug} — ${a.name} · ${a.specialty}`);
|
|
174
|
+
console.log(` ${a.description}\n`);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Install ────────────────────────────────────────────────────────────────────
|
|
179
|
+
async function installAgent(slug) {
|
|
180
|
+
if (!slug) { console.log('Usage: grimoire marketplace install <slug>'); return; }
|
|
181
|
+
|
|
182
|
+
const agents = await fetchRegistry();
|
|
183
|
+
const agent = agents.find(a => a.slug === slug);
|
|
184
|
+
if (!agent) {
|
|
185
|
+
console.error(`❌ Agent "${slug}" not found in marketplace. Try: grimoire marketplace list`);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const localDir = path.join(process.cwd(), '.codex', 'agents');
|
|
190
|
+
if (!fs.existsSync(localDir)) {
|
|
191
|
+
console.error('❌ No .codex/agents/ found. Run: npx grimoire-framework install');
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const destFile = path.join(localDir, `${slug}.md`);
|
|
196
|
+
if (fs.existsSync(destFile)) {
|
|
197
|
+
console.log(`⚠️ Agent @${slug} already installed. Use "grimoire agent edit ${slug}" to customize.`);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Download agent file
|
|
202
|
+
process.stdout.write(`⬇️ Downloading @${slug} (${agent.name})...`);
|
|
203
|
+
try {
|
|
204
|
+
const content = await new Promise((resolve, reject) => {
|
|
205
|
+
const req = https.get(agent.gist, { timeout: 8000 }, (res) => {
|
|
206
|
+
// If 404 or not found, generate template instead
|
|
207
|
+
if (res.statusCode === 404) { resolve(null); return; }
|
|
208
|
+
let body = '';
|
|
209
|
+
res.on('data', d => body += d);
|
|
210
|
+
res.on('end', () => resolve(body));
|
|
211
|
+
});
|
|
212
|
+
req.on('error', reject);
|
|
213
|
+
req.on('timeout', () => req.destroy());
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const agentContent = content || generateFallbackAgent(agent);
|
|
217
|
+
fs.writeFileSync(destFile, agentContent, 'utf8');
|
|
218
|
+
|
|
219
|
+
console.log(` ✅\n`);
|
|
220
|
+
console.log(`✅ Instalado: @${slug} — ${agent.name}`);
|
|
221
|
+
console.log(` Especialidade: ${agent.specialty}`);
|
|
222
|
+
console.log(` Arquivo: .codex/agents/${slug}.md`);
|
|
223
|
+
console.log(`\n💡 Para ativar: @${slug} no chat da sua IDE`);
|
|
224
|
+
console.log(` Para outras IDEs: grimoire sync --global && grimoire update\n`);
|
|
225
|
+
} catch (e) {
|
|
226
|
+
console.log(' ⚠️ (offline, usando template local)');
|
|
227
|
+
fs.writeFileSync(destFile, generateFallbackAgent(agent), 'utf8');
|
|
228
|
+
console.log(`✅ Instalado com template local: @${slug}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── Fallback template when gist unavailable ────────────────────────────────────
|
|
233
|
+
function generateFallbackAgent(agent) {
|
|
234
|
+
return `# ${agent.slug}
|
|
235
|
+
|
|
236
|
+
ACTIVATION-NOTICE: This file contains your full agent operating guidelines. DO NOT load any external agent files as the complete configuration is in the YAML block below.
|
|
237
|
+
|
|
238
|
+
## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
|
|
239
|
+
|
|
240
|
+
\`\`\`yaml
|
|
241
|
+
activation-instructions:
|
|
242
|
+
- STEP 1: Adote a persona ${agent.name} completamente
|
|
243
|
+
- STEP 2: Apresente o greeting abaixo
|
|
244
|
+
- STEP 3: HALT e aguarde input
|
|
245
|
+
- STAY IN CHARACTER até *exit
|
|
246
|
+
|
|
247
|
+
agent:
|
|
248
|
+
name: ${agent.name}
|
|
249
|
+
id: ${agent.slug}
|
|
250
|
+
title: ${agent.specialty}
|
|
251
|
+
icon: ${agent.icon}
|
|
252
|
+
source: marketplace
|
|
253
|
+
author: ${agent.author}
|
|
254
|
+
tags: [${agent.tags.join(', ')}]
|
|
255
|
+
description: ${agent.description}
|
|
256
|
+
|
|
257
|
+
persona:
|
|
258
|
+
role: ${agent.specialty} Specialist
|
|
259
|
+
identity: ${agent.name} — ${agent.description}
|
|
260
|
+
|
|
261
|
+
greeting_levels:
|
|
262
|
+
default: |
|
|
263
|
+
${agent.icon} **${agent.name}** (@${agent.slug}) do Grimoire Marketplace!
|
|
264
|
+
Especialidade: **${agent.specialty}**
|
|
265
|
+
${agent.description}
|
|
266
|
+
|
|
267
|
+
Como posso ajudar?
|
|
268
|
+
|
|
269
|
+
commands:
|
|
270
|
+
- "*help — Comandos disponíveis"
|
|
271
|
+
- "*exit — Sair do agente"
|
|
272
|
+
|
|
273
|
+
signature_closing: '— ${agent.name} ${agent.icon}'
|
|
274
|
+
\`\`\`
|
|
275
|
+
`;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ── Submit guide ───────────────────────────────────────────────────────────────
|
|
279
|
+
function showSubmitGuide() {
|
|
280
|
+
console.log(`
|
|
281
|
+
📤 Publique seu agente no Grimoire Marketplace!
|
|
282
|
+
|
|
283
|
+
1. Crie seu agente:
|
|
284
|
+
grimoire agent create
|
|
285
|
+
|
|
286
|
+
2. Teste em múltiplos projetos:
|
|
287
|
+
grimoire sync --global
|
|
288
|
+
grimoire sync --from-global # em outro projeto
|
|
289
|
+
|
|
290
|
+
3. Abra um Pull Request:
|
|
291
|
+
https://github.com/gabrielrlima/grimoire/tree/master/marketplace
|
|
292
|
+
|
|
293
|
+
Adicione seu agente em:
|
|
294
|
+
- marketplace/agents/<slug>.md (definição)
|
|
295
|
+
- marketplace/registry.json (entrada no catálogo)
|
|
296
|
+
|
|
297
|
+
4. Campos obrigatórios no registry.json:
|
|
298
|
+
slug, name, icon, specialty, tags, author, description, gist
|
|
299
|
+
|
|
300
|
+
🌟 Agentes aprovados ficam disponíveis para toda a comunidade!
|
|
301
|
+
`);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
module.exports = { run };
|
package/bin/commands/metrics.js
CHANGED
|
@@ -1,107 +1,265 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Grimoire Metrics CLI Command
|
|
2
|
+
* Grimoire Metrics CLI Command — Dashboard + Period Filters + CSV Export
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* grimoire metrics # Resumo do mês atual
|
|
5
|
+
* grimoire metrics --period week # Últimos 7 dias
|
|
6
|
+
* grimoire metrics --period month # Últimos 30 dias
|
|
7
|
+
* grimoire metrics --period all # Todo o histórico
|
|
8
|
+
* grimoire metrics export --csv # Exporta para CSV
|
|
9
|
+
* grimoire metrics track <event> # Registra um evento manualmente
|
|
5
10
|
*/
|
|
6
11
|
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
7
14
|
const path = require('path');
|
|
8
15
|
const fs = require('fs/promises');
|
|
9
|
-
const
|
|
16
|
+
const fsSync = require('fs');
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
18
|
+
// ── Agent personas catalogue ───────────────────────────────────────────────────
|
|
19
|
+
const AGENT_ICONS = {
|
|
20
|
+
'dev': '🎨', 'qa': '🖌️', 'architect': '🏛️', 'pm': '📋', 'sm': '🌊',
|
|
21
|
+
'devops': '⚡', 'data-engineer': '📊', 'analyst': '🔍',
|
|
22
|
+
'ux-design-expert': '🎭', 'po': '🎯', 'squad-creator': '🗿',
|
|
23
|
+
'grimoire-master': '👑',
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// ── Entry point ────────────────────────────────────────────────────────────────
|
|
15
27
|
async function run(args) {
|
|
16
28
|
let baseDir = process.cwd();
|
|
17
|
-
if (!existsSync(path.join(baseDir, '.grimoire')) &&
|
|
29
|
+
if (!fsSync.existsSync(path.join(baseDir, '.grimoire')) &&
|
|
30
|
+
fsSync.existsSync(path.join(baseDir, 'grimoire', '.grimoire'))) {
|
|
18
31
|
baseDir = path.join(baseDir, 'grimoire');
|
|
19
32
|
}
|
|
20
33
|
|
|
21
34
|
const metricsDir = path.join(baseDir, '.grimoire', 'metrics');
|
|
22
35
|
|
|
23
|
-
if
|
|
24
|
-
|
|
25
|
-
|
|
36
|
+
// Auto-create metrics dir if .grimoire exists
|
|
37
|
+
if (!fsSync.existsSync(metricsDir)) {
|
|
38
|
+
if (fsSync.existsSync(path.join(baseDir, '.grimoire'))) {
|
|
39
|
+
await fs.mkdir(path.join(metricsDir, 'daily'), { recursive: true });
|
|
40
|
+
console.log('✅ Metrics directory initialized at .grimoire/metrics/');
|
|
41
|
+
} else {
|
|
42
|
+
console.error('❌ Grimoire not installed. Run: npx grimoire-framework install');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
26
45
|
}
|
|
27
46
|
|
|
47
|
+
// Sub-commands
|
|
48
|
+
const sub = args[0];
|
|
49
|
+
if (sub === 'export') { await exportMetrics(metricsDir, args.slice(1)); return; }
|
|
50
|
+
if (sub === 'track') { await trackEvent(metricsDir, args.slice(1)); return; }
|
|
51
|
+
if (sub === 'help') { showHelp(); return; }
|
|
52
|
+
|
|
53
|
+
// Parse period
|
|
54
|
+
const periodIdx = args.indexOf('--period');
|
|
55
|
+
const period = periodIdx !== -1 ? args[periodIdx + 1] : 'month';
|
|
56
|
+
|
|
28
57
|
try {
|
|
29
|
-
const stats = await aggregateMetrics(metricsDir);
|
|
30
|
-
showDashboard(stats);
|
|
31
|
-
} catch (
|
|
32
|
-
console.error(`❌ Error
|
|
58
|
+
const stats = await aggregateMetrics(metricsDir, period);
|
|
59
|
+
showDashboard(stats, period);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error(`❌ Error: ${e.message}`);
|
|
33
62
|
}
|
|
34
63
|
}
|
|
35
64
|
|
|
36
|
-
|
|
65
|
+
// ── Period utilities ───────────────────────────────────────────────────────────
|
|
66
|
+
function cutoffDate(period) {
|
|
67
|
+
const d = new Date();
|
|
68
|
+
if (period === 'week') d.setDate(d.getDate() - 7);
|
|
69
|
+
else if (period === 'month') d.setDate(d.getDate() - 30);
|
|
70
|
+
else if (period === 'all') return new Date(0);
|
|
71
|
+
else d.setDate(d.getDate() - 30); // default month
|
|
72
|
+
return d;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function periodLabel(period) {
|
|
76
|
+
const months = ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho',
|
|
77
|
+
'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'];
|
|
78
|
+
if (period === 'week') return 'Últimos 7 dias';
|
|
79
|
+
if (period === 'all') return 'Histórico completo';
|
|
80
|
+
return `${months[new Date().getMonth()]} ${new Date().getFullYear()}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ── Aggregator ─────────────────────────────────────────────────────────────────
|
|
84
|
+
async function aggregateMetrics(metricsDir, period) {
|
|
37
85
|
const dailyDir = path.join(metricsDir, 'daily');
|
|
86
|
+
const cutoff = cutoffDate(period);
|
|
87
|
+
|
|
38
88
|
const stats = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
daysActive: 0
|
|
89
|
+
sessions: 0, stories: 0, tokens: 0,
|
|
90
|
+
agentUsage: {}, daysActive: 0,
|
|
91
|
+
memoryEntries: 0, commandsRun: 0,
|
|
92
|
+
firstDate: null, lastDate: null,
|
|
44
93
|
};
|
|
45
94
|
|
|
46
|
-
if (!existsSync(dailyDir)) return stats;
|
|
95
|
+
if (!fsSync.existsSync(dailyDir)) return stats;
|
|
96
|
+
|
|
97
|
+
const files = (await fs.readdir(dailyDir))
|
|
98
|
+
.filter(f => f.match(/^\d{4}-\d{2}-\d{2}/) && (f.endsWith('.jsonl') || f.endsWith('.json')))
|
|
99
|
+
.filter(f => new Date(f.substring(0, 10)) >= cutoff)
|
|
100
|
+
.sort();
|
|
47
101
|
|
|
48
|
-
const files = (await fs.readdir(dailyDir)).filter(f => f.endsWith('.jsonl'));
|
|
49
102
|
stats.daysActive = files.length;
|
|
103
|
+
if (files.length > 0) {
|
|
104
|
+
stats.firstDate = files[0].substring(0, 10);
|
|
105
|
+
stats.lastDate = files[files.length - 1].substring(0, 10);
|
|
106
|
+
}
|
|
50
107
|
|
|
51
108
|
for (const file of files) {
|
|
52
109
|
const content = await fs.readFile(path.join(dailyDir, file), 'utf8');
|
|
53
110
|
const lines = content.split('\n').filter(l => l.trim());
|
|
54
|
-
|
|
55
|
-
stats.totalSessions += lines.length;
|
|
111
|
+
stats.sessions += lines.length;
|
|
56
112
|
|
|
57
|
-
|
|
113
|
+
for (const line of lines) {
|
|
58
114
|
try {
|
|
59
115
|
const entry = JSON.parse(line);
|
|
60
|
-
|
|
61
|
-
if (entry.
|
|
62
|
-
|
|
63
|
-
if (entry.type === '
|
|
64
|
-
// Track agent usage
|
|
116
|
+
if (entry.tokens) stats.tokens += entry.tokens;
|
|
117
|
+
if (entry.type === 'story_complete') stats.stories += 1;
|
|
118
|
+
if (entry.type === 'memory_save') stats.memoryEntries += 1;
|
|
119
|
+
if (entry.type === 'command') stats.commandsRun += 1;
|
|
65
120
|
if (entry.agent) {
|
|
66
121
|
stats.agentUsage[entry.agent] = (stats.agentUsage[entry.agent] || 0) + 1;
|
|
67
122
|
}
|
|
68
|
-
} catch (
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
});
|
|
123
|
+
} catch (_) { /* skip bad lines */ }
|
|
124
|
+
}
|
|
72
125
|
}
|
|
73
126
|
|
|
74
127
|
return stats;
|
|
75
128
|
}
|
|
76
129
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
130
|
+
// ── Manual event tracking ──────────────────────────────────────────────────────
|
|
131
|
+
async function trackEvent(metricsDir, args) {
|
|
132
|
+
const type = args[0] || 'custom';
|
|
133
|
+
const content = args.slice(1).join(' ');
|
|
134
|
+
const today = new Date().toISOString().split('T')[0];
|
|
135
|
+
const dailyDir = path.join(metricsDir, 'daily');
|
|
136
|
+
if (!fsSync.existsSync(dailyDir)) await fs.mkdir(dailyDir, { recursive: true });
|
|
137
|
+
|
|
138
|
+
const entry = {
|
|
139
|
+
timestamp: new Date().toISOString(),
|
|
140
|
+
type,
|
|
141
|
+
content: content || type,
|
|
142
|
+
};
|
|
143
|
+
const file = path.join(dailyDir, `${today}.jsonl`);
|
|
144
|
+
await fs.appendFile(file, JSON.stringify(entry) + '\n', 'utf8');
|
|
145
|
+
console.log(`✅ Event tracked: [${type}] ${content || ''}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Dashboard renderer ─────────────────────────────────────────────────────────
|
|
149
|
+
function bar(value, max, width = 20) {
|
|
150
|
+
const filled = max > 0 ? Math.round((value / max) * width) : 0;
|
|
151
|
+
return '█'.repeat(filled) + '░'.repeat(width - filled);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function showDashboard(stats, period) {
|
|
155
|
+
const topAgents = Object.entries(stats.agentUsage).sort((a, b) => b[1] - a[1]).slice(0, 5);
|
|
156
|
+
const maxUsage = topAgents.length > 0 ? topAgents[0][1] : 0;
|
|
157
|
+
|
|
158
|
+
const sep = '─'.repeat(51);
|
|
159
|
+
|
|
81
160
|
console.log(`
|
|
82
|
-
📊 Grimoire
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
Dias
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.sort((a, b) => b[1] - a[1])
|
|
93
|
-
.slice(0, 3);
|
|
161
|
+
📊 Grimoire Metrics — ${periodLabel(period)}
|
|
162
|
+
${sep}
|
|
163
|
+
📅 Período ${stats.firstDate || 'hoje'} → ${stats.lastDate || 'hoje'}
|
|
164
|
+
📆 Dias ativos ${stats.daysActive}
|
|
165
|
+
🔄 Sessões totais ${stats.sessions}
|
|
166
|
+
📖 Stories concluídas ${stats.stories}
|
|
167
|
+
💾 Entradas de memória ${stats.memoryEntries}
|
|
168
|
+
⚡ Tokens estimados ${stats.tokens > 0 ? '~' + (stats.tokens / 1000).toFixed(1) + 'k' : '(sem dados de hooks)'}
|
|
169
|
+
${sep}
|
|
170
|
+
🤖 Agentes mais usados:`);
|
|
94
171
|
|
|
95
172
|
if (topAgents.length > 0) {
|
|
96
173
|
topAgents.forEach(([agent, count]) => {
|
|
97
|
-
|
|
174
|
+
const icon = AGENT_ICONS[agent] || '🎭';
|
|
175
|
+
const b = bar(count, maxUsage, 15);
|
|
176
|
+
console.log(` ${icon} @${agent.padEnd(20)} ${b} ${count}×`);
|
|
98
177
|
});
|
|
99
178
|
} else {
|
|
100
|
-
console.log(' (Nenhum
|
|
179
|
+
console.log(' (Nenhum evento de agente registrado ainda)');
|
|
180
|
+
console.log('\n 💡 Para registrar uso: grimoire metrics track agent dev');
|
|
101
181
|
}
|
|
102
182
|
|
|
103
|
-
console.log(
|
|
104
|
-
💡 Dica:
|
|
183
|
+
console.log(`${sep}
|
|
184
|
+
💡 Dica: Use hooks para coletar dados automaticamente
|
|
185
|
+
grimoire metrics track story_complete "Login implementado"
|
|
186
|
+
grimoire metrics --period week
|
|
187
|
+
grimoire metrics export --csv
|
|
188
|
+
`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── CSV / Markdown export ──────────────────────────────────────────────────────
|
|
192
|
+
async function exportMetrics(metricsDir, args) {
|
|
193
|
+
const format = args.includes('--csv') ? 'csv' : 'markdown';
|
|
194
|
+
const dailyDir = path.join(metricsDir, 'daily');
|
|
195
|
+
|
|
196
|
+
if (!fsSync.existsSync(dailyDir)) {
|
|
197
|
+
console.error('❌ No metrics data found.'); return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const files = (await fs.readdir(dailyDir)).filter(f => f.match(/^\d{4}-\d{2}-\d{2}/)).sort();
|
|
201
|
+
const allEntries = [];
|
|
202
|
+
|
|
203
|
+
for (const file of files) {
|
|
204
|
+
const date = file.substring(0, 10);
|
|
205
|
+
const content = await fs.readFile(path.join(dailyDir, file), 'utf8');
|
|
206
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
try { allEntries.push({ date, ...JSON.parse(line) }); } catch (_) { }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const outPath = path.join(process.cwd(),
|
|
213
|
+
`grimoire-metrics-${new Date().toISOString().split('T')[0]}.${format === 'csv' ? 'csv' : 'md'}`);
|
|
214
|
+
|
|
215
|
+
if (format === 'csv') {
|
|
216
|
+
const headers = ['date', 'timestamp', 'type', 'agent', 'tokens', 'content'];
|
|
217
|
+
const rows = allEntries.map(e =>
|
|
218
|
+
headers.map(h => JSON.stringify(e[h] ?? '')).join(',')
|
|
219
|
+
);
|
|
220
|
+
await fs.writeFile(outPath, [headers.join(','), ...rows].join('\n'), 'utf8');
|
|
221
|
+
} else {
|
|
222
|
+
let md = '# Grimoire Metrics Export\n\n';
|
|
223
|
+
md += `> Exported: ${new Date().toISOString()}\n\n`;
|
|
224
|
+
md += `| Date | Time | Type | Agent | Tokens | Content |\n`;
|
|
225
|
+
md += `|------|------|------|-------|--------|---------|\n`;
|
|
226
|
+
allEntries.forEach(e => {
|
|
227
|
+
const time = e.timestamp ? e.timestamp.split('T')[1]?.split('.')[0] : '';
|
|
228
|
+
md += `| ${e.date} | ${time} | ${e.type || ''} | ${e.agent || ''} | ${e.tokens || ''} | ${e.content || ''} |\n`;
|
|
229
|
+
});
|
|
230
|
+
await fs.writeFile(outPath, md, 'utf8');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log(`✅ Metrics exported (${allEntries.length} events) → ${outPath}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── Help ───────────────────────────────────────────────────────────────────────
|
|
237
|
+
function showHelp() {
|
|
238
|
+
console.log(`
|
|
239
|
+
📊 Grimoire Metrics — Dashboard de Produtividade
|
|
240
|
+
|
|
241
|
+
USO:
|
|
242
|
+
grimoire metrics Resumo do mês atual
|
|
243
|
+
grimoire metrics --period week Últimos 7 dias
|
|
244
|
+
grimoire metrics --period month Últimos 30 dias
|
|
245
|
+
grimoire metrics --period all Histórico completo
|
|
246
|
+
grimoire metrics export --csv Exportar para CSV
|
|
247
|
+
grimoire metrics export Exportar para Markdown
|
|
248
|
+
grimoire metrics track <type> [msg] Registrar evento manualmente
|
|
249
|
+
|
|
250
|
+
TIPOS DE EVENTO:
|
|
251
|
+
story_complete "Nome da story" Marcar story como concluída
|
|
252
|
+
agent <name> Registrar uso de agente
|
|
253
|
+
command <cmd> Registrar comando executado
|
|
254
|
+
memory_save Registrar uso de memória
|
|
255
|
+
custom <desc> Evento personalizado
|
|
256
|
+
|
|
257
|
+
EXEMPLOS:
|
|
258
|
+
grimoire metrics --period week
|
|
259
|
+
grimoire metrics track story_complete "Implementar login"
|
|
260
|
+
grimoire metrics track agent dev
|
|
261
|
+
grimoire metrics export --csv
|
|
262
|
+
`);
|
|
105
263
|
}
|
|
106
264
|
|
|
107
265
|
module.exports = { run };
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire pro — License & Feature Gate System
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: Offline-first with HMAC key validation
|
|
5
|
+
* Phase 2: Online validation via Supabase Edge Function (future)
|
|
6
|
+
*
|
|
7
|
+
* grimoire pro activate <key> Ativa a licença Pro
|
|
8
|
+
* grimoire pro status Mostra status da licença
|
|
9
|
+
* grimoire pro features Lista features Pro disponíveis
|
|
10
|
+
* grimoire pro deactivate Remove a licença
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const crypto = require('crypto');
|
|
19
|
+
|
|
20
|
+
// ── License storage ────────────────────────────────────────────────────────────
|
|
21
|
+
const LICENSE_FILE = path.join(os.homedir(), '.grimoire-license.json');
|
|
22
|
+
|
|
23
|
+
// ── HMAC secret (public seed — Phase 1 offline validation only) ───────────────
|
|
24
|
+
// In Phase 2 this validation moves to the server
|
|
25
|
+
const HMAC_SEED = 'grimoire-pro-v1-2026';
|
|
26
|
+
|
|
27
|
+
// ── Pro feature catalogue ──────────────────────────────────────────────────────
|
|
28
|
+
const PRO_FEATURES = [
|
|
29
|
+
{ id: 'squads-unlimited', name: 'Squads Ilimitados', desc: 'Crie squads customizados sem limite', tier: 'pro' },
|
|
30
|
+
{ id: 'memory-sync', name: 'Memória Sincronizada', desc: 'Sync de memória entre projetos via cloud', tier: 'pro' },
|
|
31
|
+
{ id: 'metrics-advanced', name: 'Métricas Avançadas', desc: 'Dashboard com insights de IA e tendências', tier: 'pro' },
|
|
32
|
+
{ id: 'marketplace-publish', name: 'Publish no Marketplace', desc: 'Publique agentes no marketplace sem PR', tier: 'pro' },
|
|
33
|
+
{ id: 'squad-creator', name: 'Squad Creator Avançado', desc: 'Templates de squad por domínio (fintech, saúde, e-comm)', tier: 'pro' },
|
|
34
|
+
{ id: 'priority-support', name: 'Suporte Prioritário', desc: 'Acesso a canal privado de suporte', tier: 'pro' },
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// ── License validation (offline HMAC-SHA256) ──────────────────────────────────
|
|
38
|
+
function generateKeyChecksum(email, key) {
|
|
39
|
+
const payload = `${email}:${key}:${HMAC_SEED}`;
|
|
40
|
+
return crypto.createHmac('sha256', HMAC_SEED).update(payload).digest('hex').substring(0, 12);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function validateKeyFormat(key) {
|
|
44
|
+
// Format: PRO-XXXX-XXXX-XXXX-XXXX (alphanumeric segments)
|
|
45
|
+
return /^PRO-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadLicense() {
|
|
49
|
+
if (!fs.existsSync(LICENSE_FILE)) return null;
|
|
50
|
+
try { return JSON.parse(fs.readFileSync(LICENSE_FILE, 'utf8')); }
|
|
51
|
+
catch (_) { return null; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function saveLicense(data) {
|
|
55
|
+
fs.writeFileSync(LICENSE_FILE, JSON.stringify(data, null, 2), 'utf8');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isLicenseValid(license) {
|
|
59
|
+
if (!license) return false;
|
|
60
|
+
if (license.type === 'community') return true; // Always valid
|
|
61
|
+
|
|
62
|
+
// Check expiry
|
|
63
|
+
if (license.expiresAt && new Date(license.expiresAt) < new Date()) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Offline HMAC check
|
|
68
|
+
const expected = generateKeyChecksum(license.email || '', license.key || '');
|
|
69
|
+
return license.checksum === expected;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Public API for other modules ───────────────────────────────────────────────
|
|
73
|
+
function isPro() {
|
|
74
|
+
const license = loadLicense();
|
|
75
|
+
return isLicenseValid(license) && license.type === 'pro';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function hasFeature(featureId) {
|
|
79
|
+
const license = loadLicense();
|
|
80
|
+
if (!isLicenseValid(license)) return false;
|
|
81
|
+
if (license.type === 'pro') return PRO_FEATURES.some(f => f.id === featureId);
|
|
82
|
+
return license.features?.includes(featureId) || false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ── readline prompt ────────────────────────────────────────────────────────────
|
|
86
|
+
const readline = require('readline');
|
|
87
|
+
function prompt(q) {
|
|
88
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
89
|
+
return new Promise(resolve => { rl.question(q, a => { rl.close(); resolve(a.trim()); }); });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ── Commands ───────────────────────────────────────────────────────────────────
|
|
93
|
+
async function run(args) {
|
|
94
|
+
const sub = args[0];
|
|
95
|
+
switch (sub) {
|
|
96
|
+
case 'activate': await activate(args[1]); break;
|
|
97
|
+
case 'deactivate': deactivate(); break;
|
|
98
|
+
case 'features': showFeatures(); break;
|
|
99
|
+
case 'status':
|
|
100
|
+
default: showStatus(); break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── Activate ───────────────────────────────────────────────────────────────────
|
|
105
|
+
async function activate(keyArg) {
|
|
106
|
+
console.log('\n🔐 Grimoire Pro — Ativação de Licença\n');
|
|
107
|
+
|
|
108
|
+
const key = keyArg || await prompt('Chave de licença (PRO-XXXX-XXXX-XXXX-XXXX): ');
|
|
109
|
+
|
|
110
|
+
if (!validateKeyFormat(key)) {
|
|
111
|
+
console.error('❌ Formato inválido. Use: PRO-XXXX-XXXX-XXXX-XXXX');
|
|
112
|
+
console.log(' Obtenha sua chave em: https://grimoire.dev/pro\n');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const email = await prompt('Email associado à licença: ');
|
|
117
|
+
if (!email || !email.includes('@')) {
|
|
118
|
+
console.error('❌ Email inválido.');
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Offline HMAC validation
|
|
123
|
+
const checksum = generateKeyChecksum(email, key);
|
|
124
|
+
|
|
125
|
+
// Try online validation (Phase 2 ready but not blocking)
|
|
126
|
+
let onlineValidated = false;
|
|
127
|
+
try {
|
|
128
|
+
const https = require('https');
|
|
129
|
+
const result = await new Promise((resolve, reject) => {
|
|
130
|
+
const data = JSON.stringify({ key, email });
|
|
131
|
+
const req = https.request({
|
|
132
|
+
hostname: 'api.grimoire.dev',
|
|
133
|
+
path: '/v1/license/validate',
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': data.length },
|
|
136
|
+
timeout: 4000,
|
|
137
|
+
}, (res) => {
|
|
138
|
+
let body = '';
|
|
139
|
+
res.on('data', d => body += d);
|
|
140
|
+
res.on('end', () => { try { resolve(JSON.parse(body)); } catch (e) { reject(e); } });
|
|
141
|
+
});
|
|
142
|
+
req.on('error', reject);
|
|
143
|
+
req.on('timeout', () => req.destroy());
|
|
144
|
+
req.write(data);
|
|
145
|
+
req.end();
|
|
146
|
+
});
|
|
147
|
+
onlineValidated = result.valid === true;
|
|
148
|
+
} catch (_) {
|
|
149
|
+
// Offline — use local validation only
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const license = {
|
|
153
|
+
type: 'pro',
|
|
154
|
+
key,
|
|
155
|
+
email,
|
|
156
|
+
checksum,
|
|
157
|
+
activatedAt: new Date().toISOString(),
|
|
158
|
+
expiresAt: null, // null = never (or set by server in Phase 2)
|
|
159
|
+
onlineValidated,
|
|
160
|
+
features: PRO_FEATURES.map(f => f.id),
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
saveLicense(license);
|
|
164
|
+
|
|
165
|
+
console.log(`
|
|
166
|
+
✅ Grimoire Pro ativado!
|
|
167
|
+
|
|
168
|
+
Email: ${email}
|
|
169
|
+
Chave: ${key.substring(0, 7)}...${key.slice(-4)}
|
|
170
|
+
Status: ${onlineValidated ? '✅ Validado online' : '✅ Ativado (validação offline)'}
|
|
171
|
+
|
|
172
|
+
🎯 Features ativas:`);
|
|
173
|
+
PRO_FEATURES.forEach(f => console.log(` ✅ ${f.name} — ${f.desc}`));
|
|
174
|
+
console.log('\n💡 Use "grimoire pro status" para ver os detalhes\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ── Status ─────────────────────────────────────────────────────────────────────
|
|
178
|
+
function showStatus() {
|
|
179
|
+
const license = loadLicense();
|
|
180
|
+
const sep = '─'.repeat(50);
|
|
181
|
+
|
|
182
|
+
console.log(`\n🔐 Grimoire Pro — Status da Licença\n${sep}`);
|
|
183
|
+
|
|
184
|
+
if (!license) {
|
|
185
|
+
console.log(` Status: ⭕ Não ativado (Community)\n`);
|
|
186
|
+
console.log(` ${sep}`);
|
|
187
|
+
console.log(` Para ativar: grimoire pro activate <chave>`);
|
|
188
|
+
console.log(` Obter licença: https://grimoire.dev/pro\n`);
|
|
189
|
+
showFeatureList(false);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const valid = isLicenseValid(license);
|
|
194
|
+
const isPro = license.type === 'pro' && valid;
|
|
195
|
+
const expires = license.expiresAt ? new Date(license.expiresAt).toLocaleDateString('pt-BR') : 'Sem expiração';
|
|
196
|
+
|
|
197
|
+
console.log(` Status: ${valid ? (isPro ? '✅ Pro Ativo' : '✅ Community') : '❌ Licença inválida/expirada'}`);
|
|
198
|
+
if (license.email) console.log(` Email: ${license.email}`);
|
|
199
|
+
if (license.key) console.log(` Chave: ${license.key.substring(0, 7)}...${license.key.slice(-4)}`);
|
|
200
|
+
console.log(` Ativado: ${license.activatedAt ? new Date(license.activatedAt).toLocaleDateString('pt-BR') : 'N/A'}`);
|
|
201
|
+
console.log(` Expira: ${expires}`);
|
|
202
|
+
console.log(` ${sep}`);
|
|
203
|
+
|
|
204
|
+
showFeatureList(isPro);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ── Features ───────────────────────────────────────────────────────────────────
|
|
208
|
+
function showFeatures() {
|
|
209
|
+
const license = loadLicense();
|
|
210
|
+
const active = license && isLicenseValid(license) && license.type === 'pro';
|
|
211
|
+
console.log('\n🎯 Grimoire Pro — Features\n');
|
|
212
|
+
showFeatureList(active);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function showFeatureList(active) {
|
|
216
|
+
PRO_FEATURES.forEach(f => {
|
|
217
|
+
const status = active ? '✅' : '🔒';
|
|
218
|
+
console.log(` ${status} ${f.name}`);
|
|
219
|
+
console.log(` ${f.desc}\n`);
|
|
220
|
+
});
|
|
221
|
+
if (!active) {
|
|
222
|
+
console.log(` 🔗 Ative em: https://grimoire.dev/pro`);
|
|
223
|
+
console.log(` grimoire pro activate\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Deactivate ─────────────────────────────────────────────────────────────────
|
|
228
|
+
function deactivate() {
|
|
229
|
+
if (!fs.existsSync(LICENSE_FILE)) {
|
|
230
|
+
console.log('ℹ️ Nenhuma licença ativa encontrada.');
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
fs.unlinkSync(LICENSE_FILE);
|
|
234
|
+
console.log('✅ Licença removida. Grimoire voltou ao modo Community.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = { run, isPro, hasFeature };
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire sync — Global Agent Synchronization
|
|
3
|
+
*
|
|
4
|
+
* Copies agents from the current project to a global ~/.grimoire/agents/
|
|
5
|
+
* and optionally syncs them to other projects on the machine.
|
|
6
|
+
*
|
|
7
|
+
* grimoire sync --global Sync agents to global store
|
|
8
|
+
* grimoire sync --from-global Pull global agents into current project
|
|
9
|
+
* grimoire sync --list List global agents
|
|
10
|
+
* grimoire sync --projects Show projects registered in global store
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
|
|
19
|
+
const GLOBAL_DIR = path.join(os.homedir(), '.grimoire');
|
|
20
|
+
const GLOBAL_AGENTS = path.join(GLOBAL_DIR, 'agents');
|
|
21
|
+
const GLOBAL_REGISTRY = path.join(GLOBAL_DIR, 'projects.json');
|
|
22
|
+
|
|
23
|
+
// ── Utilities ──────────────────────────────────────────────────────────────────
|
|
24
|
+
function ensureGlobalDir() {
|
|
25
|
+
if (!fs.existsSync(GLOBAL_AGENTS)) fs.mkdirSync(GLOBAL_AGENTS, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function loadRegistry() {
|
|
29
|
+
if (!fs.existsSync(GLOBAL_REGISTRY)) return { projects: [], lastSync: null };
|
|
30
|
+
try { return JSON.parse(fs.readFileSync(GLOBAL_REGISTRY, 'utf8')); }
|
|
31
|
+
catch (_) { return { projects: [], lastSync: null }; }
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function saveRegistry(data) {
|
|
35
|
+
fs.writeFileSync(GLOBAL_REGISTRY, JSON.stringify(data, null, 2), 'utf8');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getLocalAgentsDir() {
|
|
39
|
+
const cwd = process.cwd();
|
|
40
|
+
const dir = path.join(cwd, '.codex', 'agents');
|
|
41
|
+
if (!fs.existsSync(dir)) return null;
|
|
42
|
+
return dir;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function copyAgent(src, dest) {
|
|
46
|
+
const content = fs.readFileSync(src, 'utf8');
|
|
47
|
+
fs.writeFileSync(dest, content, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Commands ───────────────────────────────────────────────────────────────────
|
|
51
|
+
async function run(args) {
|
|
52
|
+
const sub = args.find(a => a.startsWith('--')) || '--global';
|
|
53
|
+
|
|
54
|
+
switch (sub) {
|
|
55
|
+
case '--global': await syncToGlobal(); break;
|
|
56
|
+
case '--from-global': await syncFromGlobal(); break;
|
|
57
|
+
case '--list': listGlobalAgents(); break;
|
|
58
|
+
case '--projects': listProjects(); break;
|
|
59
|
+
case '--help':
|
|
60
|
+
default: showHelp(); break;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Sync local → global ────────────────────────────────────────────────────────
|
|
65
|
+
async function syncToGlobal() {
|
|
66
|
+
const localDir = getLocalAgentsDir();
|
|
67
|
+
if (!localDir) {
|
|
68
|
+
console.error('❌ No .codex/agents/ found. Run: npx grimoire-framework install');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
ensureGlobalDir();
|
|
73
|
+
|
|
74
|
+
const agents = fs.readdirSync(localDir).filter(f => f.endsWith('.md'));
|
|
75
|
+
let synced = 0, skipped = 0;
|
|
76
|
+
|
|
77
|
+
console.log(`\n🌐 Syncing ${agents.length} agents to global store (~/.grimoire/agents/)...\n`);
|
|
78
|
+
|
|
79
|
+
for (const file of agents) {
|
|
80
|
+
const src = path.join(localDir, file);
|
|
81
|
+
const dest = path.join(GLOBAL_AGENTS, file);
|
|
82
|
+
const exists = fs.existsSync(dest);
|
|
83
|
+
|
|
84
|
+
// Compare content to decide if update is needed
|
|
85
|
+
if (exists) {
|
|
86
|
+
const srcContent = fs.readFileSync(src, 'utf8');
|
|
87
|
+
const destContent = fs.readFileSync(dest, 'utf8');
|
|
88
|
+
if (srcContent === destContent) { skipped++; continue; }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
copyAgent(src, dest);
|
|
92
|
+
console.log(` ✅ ${file.replace('.md', '')}`);
|
|
93
|
+
synced++;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (skipped > 0) console.log(` ⏩ ${skipped} already up-to-date (skipped)`);
|
|
97
|
+
|
|
98
|
+
// Register this project
|
|
99
|
+
const registry = loadRegistry();
|
|
100
|
+
const cwd = process.cwd();
|
|
101
|
+
if (!registry.projects.includes(cwd)) registry.projects.push(cwd);
|
|
102
|
+
registry.lastSync = new Date().toISOString();
|
|
103
|
+
saveRegistry(registry);
|
|
104
|
+
|
|
105
|
+
console.log(`\n✅ ${synced} agent${synced === 1 ? '' : 's'} synced to global store`);
|
|
106
|
+
console.log(` ${GLOBAL_AGENTS}`);
|
|
107
|
+
console.log(`\n💡 Use "grimoire sync --from-global" in other projects to pull these agents`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── Sync global → local ────────────────────────────────────────────────────────
|
|
111
|
+
async function syncFromGlobal() {
|
|
112
|
+
if (!fs.existsSync(GLOBAL_AGENTS)) {
|
|
113
|
+
console.error('❌ No global agents found. Run "grimoire sync --global" first.');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const localDir = getLocalAgentsDir();
|
|
118
|
+
if (!localDir) {
|
|
119
|
+
console.error('❌ No .codex/agents/ found. Run: npx grimoire-framework install');
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const globalAgents = fs.readdirSync(GLOBAL_AGENTS).filter(f => f.endsWith('.md'));
|
|
124
|
+
const localAgents = new Set(fs.readdirSync(localDir).filter(f => f.endsWith('.md')));
|
|
125
|
+
|
|
126
|
+
// Only pull agents that DON'T exist locally (preserve local customizations)
|
|
127
|
+
const newAgents = globalAgents.filter(f => !localAgents.has(f));
|
|
128
|
+
|
|
129
|
+
console.log(`\n📥 Pulling from global store (~/.grimoire/agents/)...\n`);
|
|
130
|
+
console.log(` Global agents: ${globalAgents.length}`);
|
|
131
|
+
console.log(` Local agents: ${localAgents.size}`);
|
|
132
|
+
console.log(` New to pull: ${newAgents.length}\n`);
|
|
133
|
+
|
|
134
|
+
if (newAgents.length === 0) {
|
|
135
|
+
console.log('✅ Local agents are already up-to-date with global store.');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const file of newAgents) {
|
|
140
|
+
copyAgent(path.join(GLOBAL_AGENTS, file), path.join(localDir, file));
|
|
141
|
+
console.log(` ✅ Added: @${file.replace('.md', '')}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`\n✅ ${newAgents.length} agent${newAgents.length === 1 ? '' : 's'} pulled from global store`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ── List global agents ─────────────────────────────────────────────────────────
|
|
148
|
+
function listGlobalAgents() {
|
|
149
|
+
if (!fs.existsSync(GLOBAL_AGENTS)) {
|
|
150
|
+
console.log('ℹ️ No global agents yet. Run: grimoire sync --global');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const agents = fs.readdirSync(GLOBAL_AGENTS).filter(f => f.endsWith('.md'));
|
|
155
|
+
const registry = loadRegistry();
|
|
156
|
+
|
|
157
|
+
console.log(`\n🌐 Global Grimoire Agents (${agents.length})`);
|
|
158
|
+
console.log(` Store: ${GLOBAL_AGENTS}`);
|
|
159
|
+
if (registry.lastSync) console.log(` Last sync: ${registry.lastSync.split('T')[0]}\n`);
|
|
160
|
+
|
|
161
|
+
agents.forEach(f => console.log(` 🤖 @${f.replace('.md', '')}`));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// ── List registered projects ───────────────────────────────────────────────────
|
|
165
|
+
function listProjects() {
|
|
166
|
+
const registry = loadRegistry();
|
|
167
|
+
console.log(`\n📁 Projects using Grimoire global store (${registry.projects.length}):\n`);
|
|
168
|
+
if (registry.projects.length === 0) {
|
|
169
|
+
console.log(' (No projects registered yet — run "grimoire sync --global")');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
registry.projects.forEach(p => {
|
|
173
|
+
const exists = fs.existsSync(p);
|
|
174
|
+
console.log(` ${exists ? '✅' : '❌'} ${p}`);
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ── Help ───────────────────────────────────────────────────────────────────────
|
|
179
|
+
function showHelp() {
|
|
180
|
+
console.log(`
|
|
181
|
+
🌐 Grimoire Sync — Sincronização Global de Agentes
|
|
182
|
+
|
|
183
|
+
USO:
|
|
184
|
+
grimoire sync --global Envia agentes locais para ~/.grimoire/agents/
|
|
185
|
+
grimoire sync --from-global Puxa agentes globais para o projeto atual
|
|
186
|
+
grimoire sync --list Lista agentes no store global
|
|
187
|
+
grimoire sync --projects Lista projetos registrados
|
|
188
|
+
|
|
189
|
+
FLUXO TÍPICO:
|
|
190
|
+
# No projeto A (onde você criou um agente customizado):
|
|
191
|
+
grimoire agent create → grimoire sync --global
|
|
192
|
+
|
|
193
|
+
# No projeto B (onde você quer esse agente):
|
|
194
|
+
grimoire sync --from-global
|
|
195
|
+
|
|
196
|
+
NOTAS:
|
|
197
|
+
- --from-global nunca sobrescreve agentes existentes localmente
|
|
198
|
+
- --global sobrescreve apenas se o conteúdo mudou
|
|
199
|
+
- O store global fica em: ${GLOBAL_DIR}
|
|
200
|
+
`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = { run };
|
package/bin/grimoire-cli.js
CHANGED
|
@@ -115,6 +115,15 @@ async function main() {
|
|
|
115
115
|
case 'agent':
|
|
116
116
|
await require('./commands/agent').run(args.slice(1));
|
|
117
117
|
break;
|
|
118
|
+
case 'sync':
|
|
119
|
+
await require('./commands/sync').run(args.slice(1));
|
|
120
|
+
break;
|
|
121
|
+
case 'marketplace':
|
|
122
|
+
await require('./commands/marketplace').run(args.slice(1));
|
|
123
|
+
break;
|
|
124
|
+
case 'pro':
|
|
125
|
+
await require('./commands/pro').run(args.slice(1));
|
|
126
|
+
break;
|
|
118
127
|
case 'status':
|
|
119
128
|
handleStatus();
|
|
120
129
|
break;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"lastUpdated": "2026-02-22",
|
|
4
|
+
"agents": [
|
|
5
|
+
{
|
|
6
|
+
"slug": "frontend-specialist",
|
|
7
|
+
"name": "Botticelli",
|
|
8
|
+
"icon": "🎠",
|
|
9
|
+
"specialty": "Frontend & UI/UX",
|
|
10
|
+
"tags": [
|
|
11
|
+
"react",
|
|
12
|
+
"css",
|
|
13
|
+
"typescript",
|
|
14
|
+
"nextjs"
|
|
15
|
+
],
|
|
16
|
+
"author": "grimoire-team",
|
|
17
|
+
"description": "Especialista em React, TypeScript e design systems. Ideal para times com foco em UI.",
|
|
18
|
+
"gist": "https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/frontend-specialist.md"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"slug": "security-auditor",
|
|
22
|
+
"name": "Rembrandt",
|
|
23
|
+
"icon": "🔐",
|
|
24
|
+
"specialty": "Segurança & Auditoria",
|
|
25
|
+
"tags": [
|
|
26
|
+
"security",
|
|
27
|
+
"owasp",
|
|
28
|
+
"pentest",
|
|
29
|
+
"audit"
|
|
30
|
+
],
|
|
31
|
+
"author": "grimoire-team",
|
|
32
|
+
"description": "Audita código em busca de vulnerabilidades OWASP Top 10. SAST e code review de segurança.",
|
|
33
|
+
"gist": "https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/security-auditor.md"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"slug": "ml-engineer",
|
|
37
|
+
"name": "Dalí",
|
|
38
|
+
"icon": "🧠",
|
|
39
|
+
"specialty": "Machine Learning & AI",
|
|
40
|
+
"tags": [
|
|
41
|
+
"ml",
|
|
42
|
+
"python",
|
|
43
|
+
"pytorch",
|
|
44
|
+
"sklearn"
|
|
45
|
+
],
|
|
46
|
+
"author": "grimoire-team",
|
|
47
|
+
"description": "Implementa pipelines de ML, feature engineering, treinamento e avaliação de modelos.",
|
|
48
|
+
"gist": "https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/ml-engineer.md"
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"slug": "api-designer",
|
|
52
|
+
"name": "Klimt",
|
|
53
|
+
"icon": "🔗",
|
|
54
|
+
"specialty": "API Design & Documentação",
|
|
55
|
+
"tags": [
|
|
56
|
+
"rest",
|
|
57
|
+
"graphql",
|
|
58
|
+
"openapi",
|
|
59
|
+
"swagger"
|
|
60
|
+
],
|
|
61
|
+
"author": "grimoire-team",
|
|
62
|
+
"description": "Projeta APIs RESTful e GraphQL. Gera documentação OpenAPI 3.x e valida contratos.",
|
|
63
|
+
"gist": "https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/api-designer.md"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"slug": "database-expert",
|
|
67
|
+
"name": "Mondrian",
|
|
68
|
+
"icon": "🗄️",
|
|
69
|
+
"specialty": "Banco de Dados & Queries",
|
|
70
|
+
"tags": [
|
|
71
|
+
"sql",
|
|
72
|
+
"postgresql",
|
|
73
|
+
"mongodb",
|
|
74
|
+
"redis"
|
|
75
|
+
],
|
|
76
|
+
"author": "grimoire-team",
|
|
77
|
+
"description": "Otimiza queries, projeta schemas e revisa migrações. PostgreSQL, MongoDB, Redis.",
|
|
78
|
+
"gist": "https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/database-expert.md"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"slug": "tech-writer",
|
|
82
|
+
"name": "Cervantes",
|
|
83
|
+
"icon": "✍️",
|
|
84
|
+
"specialty": "Documentação Técnica",
|
|
85
|
+
"tags": [
|
|
86
|
+
"docs",
|
|
87
|
+
"readme",
|
|
88
|
+
"adr",
|
|
89
|
+
"wiki"
|
|
90
|
+
],
|
|
91
|
+
"author": "grimoire-team",
|
|
92
|
+
"description": "Escreve e revisa documentação técnica: READMEs, ADRs, guias de API e wikis.",
|
|
93
|
+
"gist": "https://raw.githubusercontent.com/gabrielrlima/grimoire/master/marketplace/agents/tech-writer.md"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grimoire-framework",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Grimoire: AI-Orchestrated System for Full Stack Development - Core Framework",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"GEMINI.md",
|
|
38
38
|
"pro/license/",
|
|
39
39
|
"squads/",
|
|
40
|
+
"marketplace/",
|
|
40
41
|
"README.md",
|
|
41
42
|
"LICENSE"
|
|
42
43
|
],
|