grimoire-framework 1.0.18 → 1.0.21

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.
@@ -1,107 +1,265 @@
1
1
  /**
2
- * Grimoire Metrics CLI Command - Advanced Aggregator
2
+ * Grimoire Metrics CLI Command Dashboard + Period Filters + CSV Export
3
3
  *
4
- * Aggregates productivity and usage metrics from daily logs.
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 existsSync = require('fs').existsSync;
16
+ const fsSync = require('fs');
10
17
 
11
- /**
12
- * Run the metrics command
13
- * @param {string[]} args - Command arguments
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')) && existsSync(path.join(baseDir, 'grimoire', '.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 (!existsSync(metricsDir)) {
24
- console.error('❌ Metrics system not found.');
25
- return;
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 (error) {
32
- console.error(`❌ Error aggregating metrics: ${error.message}`);
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
- async function aggregateMetrics(metricsDir) {
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
- totalSessions: 0,
40
- totalStories: 0,
41
- totalTokens: 0,
42
- agentUsage: {},
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
- lines.forEach(line => {
113
+ for (const line of lines) {
58
114
  try {
59
115
  const entry = JSON.parse(line);
60
- // Increment tokens if present
61
- if (entry.tokens) stats.totalTokens += entry.tokens;
62
- // Increment stories if entry marks completion
63
- if (entry.type === 'story_complete') stats.totalStories += 1;
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 (e) {
69
- // Skip malformed lines
70
- }
71
- });
123
+ } catch (_) { /* skip bad lines */ }
124
+ }
72
125
  }
73
126
 
74
127
  return stats;
75
128
  }
76
129
 
77
- function showDashboard(stats) {
78
- const monthNames = ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"];
79
- const now = new Date();
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 Pro Metrics — ${monthNames[now.getMonth()]} ${now.getFullYear()}
83
- ─────────────────────────────────────────────────
84
- Sessões Totais: ${stats.totalSessions}
85
- Dias de Atividade: ${stats.daysActive}
86
- Stories Completas: ${stats.totalStories}
87
- Tokens Estimados: ~${(stats.totalTokens / 1000).toFixed(1)}k
88
- ─────────────────────────────────────────────────
89
- Agentes mais ativos:`);
90
-
91
- const topAgents = Object.entries(stats.agentUsage)
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
- console.log(` - ${agent}: ${count} interações`);
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 dado de agente coletado ainda)');
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: Os dados são coletados automaticamente via hooks.`);
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,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 };
@@ -24,6 +24,7 @@ const FRAMEWORK_SYNC_DIRS = [
24
24
  { src: '.gemini/rules/grimoire', dest: '.gemini/rules/grimoire' },
25
25
  { src: '.cursor/rules/agents', dest: '.cursor/rules/agents' },
26
26
  { src: '.claude/commands/grimoire', dest: '.claude/commands/grimoire' },
27
+ { src: 'squads', dest: '.grimoire/squads' },
27
28
  ];
28
29
 
29
30
  // Single files to sync (framework root → project root)
@@ -12,8 +12,93 @@ const { execSync } = require('child_process');
12
12
  const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
13
13
  const args = process.argv.slice(2);
14
14
  const command = args[0];
15
+ const currentVersion = packageJson.version;
16
+
17
+ // ── Update Banner (async, non-blocking, 24h cooldown) ─────────────────────────
18
+ const UPDATE_CACHE_FILE = path.join(
19
+ process.env.HOME || process.env.USERPROFILE || require('os').homedir(),
20
+ '.grimoire-update-cache.json'
21
+ );
22
+
23
+ function shouldCheckForUpdates() {
24
+ if (process.env.GRIMOIRE_NO_UPDATE_CHECK) return false;
25
+ if (['update', '--version', '-v', 'doctor'].includes(command)) return false;
26
+ try {
27
+ if (fs.existsSync(UPDATE_CACHE_FILE)) {
28
+ const cache = JSON.parse(fs.readFileSync(UPDATE_CACHE_FILE, 'utf8'));
29
+ const hoursSince = (Date.now() - cache.lastCheck) / (1000 * 60 * 60);
30
+ if (hoursSince < 24) return false;
31
+ }
32
+ } catch (e) { /* ignore */ }
33
+ return true;
34
+ }
35
+
36
+ async function checkForUpdates() {
37
+ if (!shouldCheckForUpdates()) return;
38
+ try {
39
+ const https = require('https');
40
+ const data = await new Promise((resolve, reject) => {
41
+ const req = https.get(
42
+ 'https://registry.npmjs.org/grimoire-framework/latest',
43
+ { timeout: 3000 },
44
+ (res) => {
45
+ let body = '';
46
+ res.on('data', d => body += d);
47
+ res.on('end', () => { try { resolve(JSON.parse(body)); } catch (e) { reject(e); } });
48
+ }
49
+ );
50
+ req.on('error', reject);
51
+ req.on('timeout', () => req.destroy());
52
+ });
53
+ const latest = data.version;
54
+ // Save to cache
55
+ fs.writeFileSync(UPDATE_CACHE_FILE, JSON.stringify({ lastCheck: Date.now(), latest }));
56
+ // Compare versions
57
+ const [maj, min, pat] = currentVersion.split('.').map(Number);
58
+ const [lmaj, lmin, lpat] = latest.split('.').map(Number);
59
+ const isNewer = lmaj > maj || (lmaj === maj && lmin > min) || (lmaj === maj && lmin === min && lpat > pat);
60
+ if (isNewer) {
61
+ console.log(`\n💡 Nova versão disponível: v${latest} (você tem v${currentVersion})`);
62
+ console.log(` Atualize com: npx grimoire-framework update\n`);
63
+ }
64
+ } catch (e) { /* offline ou erro — ignorar silenciosamente */ }
65
+ }
66
+
67
+ // ── First-Run Experience ───────────────────────────────────────────────────────
68
+ function showNextSteps(projectDir) {
69
+ const cwd = projectDir || process.cwd();
70
+ // Detect installed IDE
71
+ const hasGemini = fs.existsSync(path.join(cwd, 'GEMINI.md'));
72
+ const hasCursor = fs.existsSync(path.join(cwd, '.cursor'));
73
+ const hasClaude = fs.existsSync(path.join(cwd, '.claude'));
74
+
75
+ let ideStep = ' 2. Abra o Gemini CLI: gemini';
76
+ if (hasClaude) ideStep = ' 2. Abra o Claude Code — agentes prontos com /dev, /qa, /architect';
77
+ else if (hasCursor) ideStep = ' 2. Abra o Cursor — @dev, @qa, @architect disponíveis no chat';
78
+ else if (hasGemini) ideStep = ' 2. Abra o Gemini CLI: gemini → Michelangelo te recebe';
79
+
80
+ const agentsDir = path.join(cwd, '.codex', 'agents');
81
+ const agentCount = fs.existsSync(agentsDir)
82
+ ? fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).length : 0;
83
+
84
+ console.log(`
85
+ 🎯 Próximos passos:
86
+ 1. Verifique a instalação: grimoire status
87
+ ${ideStep}
88
+ 3. Para ver todos os agentes: grimoire agents list
89
+ 4. Para diagnóstico: grimoire doctor
90
+
91
+ 💡 Dica rápida: Diga \"@dev implementa o login\" no chat da sua IDE
92
+ Você tem ${agentCount} agentes prontos: @dev, @qa, @architect, @grimoire-master...
93
+
94
+ 📚 Docs: https://github.com/gabrielrlima/grimoire#readme
95
+ `);
96
+ }
15
97
 
16
98
  async function main() {
99
+ // Non-blocking update check (runs in background)
100
+ const updatePromise = checkForUpdates().catch(() => { });
101
+
17
102
  switch (command) {
18
103
  case 'memory':
19
104
  await require('./commands/memory').run(args.slice(1));
@@ -27,6 +112,12 @@ async function main() {
27
112
  case 'agents':
28
113
  handleAgents(args.slice(1));
29
114
  break;
115
+ case 'agent':
116
+ await require('./commands/agent').run(args.slice(1));
117
+ break;
118
+ case 'sync':
119
+ await require('./commands/sync').run(args.slice(1));
120
+ break;
30
121
  case 'status':
31
122
  handleStatus();
32
123
  break;
@@ -47,7 +138,7 @@ async function main() {
47
138
  break;
48
139
  case '--version':
49
140
  case '-v':
50
- console.log(`Grimoire Pro v${packageJson.version}`);
141
+ console.log(`Grimoire v${packageJson.version}`);
51
142
  break;
52
143
  case '--help':
53
144
  case '-h':
@@ -55,6 +146,9 @@ async function main() {
55
146
  showHelp();
56
147
  break;
57
148
  }
149
+
150
+ // Wait for update check to finish (prints banner after main command if needed)
151
+ await updatePromise;
58
152
  }
59
153
 
60
154
  function handleSquads(args) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grimoire-framework",
3
- "version": "1.0.18",
3
+ "version": "1.0.21",
4
4
  "description": "Grimoire: AI-Orchestrated System for Full Stack Development - Core Framework",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -36,6 +36,7 @@
36
36
  ".claude/hooks/",
37
37
  "GEMINI.md",
38
38
  "pro/license/",
39
+ "squads/",
39
40
  "README.md",
40
41
  "LICENSE"
41
42
  ],