grimoire-framework 1.0.18 → 1.0.20

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.
@@ -7,8 +7,8 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 1.0.18
11
- generated_at: "2026-02-22T14:00:42.557Z"
10
+ version: 1.0.20
11
+ generated_at: "2026-02-22T14:39:48.383Z"
12
12
  generator: scripts/generate-install-manifest.js
13
13
  file_count: 1011
14
14
  files:
@@ -0,0 +1,216 @@
1
+ /**
2
+ * grimoire agent — Agent Management CLI
3
+ *
4
+ * Commands:
5
+ * grimoire agent create — interative wizard to create a custom agent
6
+ * grimoire agent list — alias for grimoire agents list
7
+ * grimoire agent edit <n> — open agent in $EDITOR
8
+ * grimoire agent remove <n>— delete a custom agent
9
+ */
10
+
11
+ 'use strict';
12
+
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+ const { execSync } = require('child_process');
16
+
17
+ // ── Personas catalogue for the wizard ─────────────────────────────────────────
18
+ const BASE_AGENTS = [
19
+ { id: 'dev', icon: '🎨', name: 'Da Vinci', specialty: 'Full-Stack Development' },
20
+ { id: 'qa', icon: '🖌️', name: 'Dürer', specialty: 'Quality Assurance' },
21
+ { id: 'architect', icon: '🏛️', name: 'Gaudí', specialty: 'System Architecture' },
22
+ { id: 'analyst', icon: '🔍', name: 'Vermeer', specialty: 'Research & Analysis' },
23
+ { id: 'grimoire-master', icon: '👑', name: 'Michelangelo', specialty: 'Orchestration' },
24
+ ];
25
+
26
+ const ICON_OPTIONS = ['🎭', '🎪', '🌟', '⚡', '🔮', '🦊', '🐉', '🌊', '🔥', '🌙', '🎯', '🗡️', '🧙', '🎸', '🦅'];
27
+
28
+ // ── Template generator ─────────────────────────────────────────────────────────
29
+ function generateAgentTemplate({ id, name, icon, specialty, baseAgent, description }) {
30
+ const base = BASE_AGENTS.find(a => a.id === baseAgent);
31
+ const baseRef = base ? `Based on ${base.name} (${base.id})` : 'Custom Agent';
32
+
33
+ return `# ${id}
34
+
35
+ 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.
36
+
37
+ CRITICAL: Read the full YAML BLOCK that FOLLOWS IN THIS FILE to understand your operating params, start and follow exactly your activation-instructions to alter your state of being, stay in this being until told to exit this mode:
38
+
39
+ ## COMPLETE AGENT DEFINITION FOLLOWS - NO EXTERNAL FILES NEEDED
40
+
41
+ \`\`\`yaml
42
+ activation-instructions:
43
+ - STEP 1: Read THIS ENTIRE FILE to understand your persona
44
+ - STEP 2: Adopt the ${name} persona completely
45
+ - STEP 3: Present your greeting to the user
46
+ - STEP 4: HALT and await user input
47
+ - STAY IN CHARACTER until user types *exit
48
+
49
+ agent:
50
+ name: ${name}
51
+ id: ${id}
52
+ title: ${specialty}
53
+ icon: ${icon}
54
+ base: ${baseRef}
55
+ whenToUse: ${description}
56
+
57
+ persona:
58
+ role: ${specialty} Specialist
59
+ style: Collaborative and focused on ${specialty.toLowerCase()}
60
+ identity: ${name} — ${description}
61
+
62
+ greeting_levels:
63
+ default: |
64
+ ${icon} **${name}** (${id}) pronto!
65
+ Especialidade: **${specialty}**
66
+
67
+ Como posso ajudar?
68
+
69
+ commands:
70
+ - "*help — Show available commands"
71
+ - "*exit — Exit agent mode"
72
+
73
+ signature_closing: '— ${name} ${icon}'
74
+ \`\`\`
75
+ `;
76
+ }
77
+
78
+ // ── Prompt helpers (without @clack/prompts dependency) ─────────────────────────
79
+ const readline = require('readline');
80
+
81
+ function prompt(question) {
82
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
83
+ return new Promise(resolve => {
84
+ rl.question(question, answer => { rl.close(); resolve(answer.trim()); });
85
+ });
86
+ }
87
+
88
+ async function select(question, options) {
89
+ console.log(`\n${question}`);
90
+ options.forEach((opt, i) => console.log(` ${i + 1}. ${opt.label || opt}`));
91
+ const answer = await prompt(' Escolha (número): ');
92
+ const idx = parseInt(answer) - 1;
93
+ if (idx >= 0 && idx < options.length) return options[idx].value || options[idx];
94
+ return options[0].value || options[0];
95
+ }
96
+
97
+ // ── Commands ───────────────────────────────────────────────────────────────────
98
+ async function run(args) {
99
+ const sub = args[0];
100
+ switch (sub) {
101
+ case 'create': await createWizard(); break;
102
+ case 'edit': await editAgent(args[1]); break;
103
+ case 'remove':
104
+ case 'delete': await removeAgent(args[1]); break;
105
+ case 'list':
106
+ default: listAgents(); break;
107
+ }
108
+ }
109
+
110
+ function listAgents() {
111
+ const cwd = process.cwd();
112
+ const agentsDir = path.join(cwd, '.codex', 'agents');
113
+ if (!fs.existsSync(agentsDir)) {
114
+ console.log('❌ Agents directory not found. Run: npx grimoire-framework install');
115
+ return;
116
+ }
117
+ const agents = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
118
+ console.log(`\n🤖 Grimoire Agents (${agents.length}):\n`);
119
+ agents.forEach(f => {
120
+ const name = f.replace('.md', '');
121
+ const base = BASE_AGENTS.find(a => a.id === name);
122
+ const icon = base ? base.icon : '🎭';
123
+ const persona = base ? ` — ${base.name} (${base.specialty})` : ' — Custom Agent';
124
+ console.log(` ${icon} @${name}${persona}`);
125
+ });
126
+ console.log('\n💡 Para criar um agente customizado: grimoire agent create');
127
+ }
128
+
129
+ async function createWizard() {
130
+ console.log('\n🧙 Grimoire Agent Creator\n');
131
+
132
+ // Step 1: Name/ID
133
+ const id = await prompt('Nome do agente (ex: caravaggio): ');
134
+ if (!id || !/^[a-z0-9-]+$/.test(id)) {
135
+ console.log('❌ Nome inválido. Use apenas letras minúsculas, números e hífens.');
136
+ return;
137
+ }
138
+
139
+ const cwd = process.cwd();
140
+ const agentsDir = path.join(cwd, '.codex', 'agents');
141
+ const agentFile = path.join(agentsDir, `${id}.md`);
142
+
143
+ if (fs.existsSync(agentFile)) {
144
+ console.log(`❌ Agente "${id}" já existe em ${agentFile}`);
145
+ return;
146
+ }
147
+
148
+ // Step 2: Display name
149
+ const name = await prompt(`Nome de exibição (ex: Caravaggio): `);
150
+
151
+ // Step 3: Specialty
152
+ const specialty = await prompt('Especialidade (ex: Arte Generativa e Criatividade): ');
153
+
154
+ // Step 4: Icon
155
+ const iconChoice = await select(
156
+ 'Ícone do agente:',
157
+ ICON_OPTIONS.map((i, idx) => ({ label: `${i} (${idx + 1})`, value: i }))
158
+ );
159
+
160
+ // Step 5: Base agent (inherit style from)
161
+ const baseChoice = await select(
162
+ 'Baseado em (herda o estilo de):',
163
+ [...BASE_AGENTS.map(a => ({ label: `${a.icon} ${a.name} — ${a.specialty}`, value: a.id })),
164
+ { label: '🆕 Custom (do zero)', value: 'custom' }]
165
+ );
166
+
167
+ // Step 6: Short description
168
+ const description = await prompt('Descrição curta (ex: Especialista em arte e criatividade visual): ');
169
+
170
+ // Generate & write
171
+ if (!fs.existsSync(agentsDir)) fs.mkdirSync(agentsDir, { recursive: true });
172
+
173
+ const content = generateAgentTemplate({
174
+ id, name: name || id, icon: iconChoice, specialty: specialty || 'Custom Specialist',
175
+ baseAgent: baseChoice !== 'custom' ? baseChoice : null,
176
+ description: description || `Custom agent: ${id}`
177
+ });
178
+
179
+ fs.writeFileSync(agentFile, content, 'utf8');
180
+
181
+ console.log(`
182
+ ✅ Agente criado: .codex/agents/${id}.md
183
+
184
+ 🎭 ${iconChoice} ${name || id} — ${specialty}
185
+
186
+ Para ativar no Gemini CLI:
187
+ @${id}
188
+
189
+ Para sincronizar com outras IDEs:
190
+ npx grimoire-framework update
191
+ `);
192
+ }
193
+
194
+ async function editAgent(id) {
195
+ if (!id) { console.log('Usage: grimoire agent edit <name>'); return; }
196
+ const file = path.join(process.cwd(), '.codex', 'agents', `${id}.md`);
197
+ if (!fs.existsSync(file)) { console.log(`❌ Agent "${id}" not found.`); return; }
198
+ const editor = process.env.EDITOR || process.env.VISUAL || 'notepad';
199
+ try { execSync(`${editor} "${file}"`, { stdio: 'inherit' }); }
200
+ catch (e) { console.log(`❌ Could not open editor. Edit manually: ${file}`); }
201
+ }
202
+
203
+ async function removeAgent(id) {
204
+ if (!id) { console.log('Usage: grimoire agent remove <name>'); return; }
205
+ const file = path.join(process.cwd(), '.codex', 'agents', `${id}.md`);
206
+ if (!fs.existsSync(file)) { console.log(`❌ Agent "${id}" not found.`); return; }
207
+ const confirmed = await prompt(`Remove @${id}? (y/N): `);
208
+ if (confirmed.toLowerCase() === 'y') {
209
+ fs.unlinkSync(file);
210
+ console.log(`✅ Agent "${id}" removed.`);
211
+ } else {
212
+ console.log('Cancelled.');
213
+ }
214
+ }
215
+
216
+ module.exports = { run };
@@ -16,13 +16,13 @@ const lockfile = require('proper-lockfile');
16
16
  */
17
17
  async function run(args) {
18
18
  const subCommand = args[0];
19
-
19
+
20
20
  // Look for .grimoire directory in cwd or in grimoire/ subdirectory
21
21
  let baseDir = process.cwd();
22
22
  if (!existsSync(path.join(baseDir, '.grimoire')) && existsSync(path.join(baseDir, 'grimoire', '.grimoire'))) {
23
23
  baseDir = path.join(baseDir, 'grimoire');
24
24
  }
25
-
25
+
26
26
  const memoryDir = path.join(baseDir, '.grimoire', 'memory');
27
27
 
28
28
  if (!existsSync(memoryDir)) {
@@ -41,6 +41,15 @@ async function run(args) {
41
41
  case 'show':
42
42
  await showSession(args.slice(1), memoryDir);
43
43
  break;
44
+ case 'search':
45
+ await searchMemory(args.slice(1), memoryDir);
46
+ break;
47
+ case 'export':
48
+ await exportMemory(args.slice(1), memoryDir);
49
+ break;
50
+ case 'clear':
51
+ await clearMemory(args.slice(1), memoryDir);
52
+ break;
44
53
  case 'digest':
45
54
  await digestMemory(args.slice(1), memoryDir);
46
55
  break;
@@ -80,7 +89,7 @@ async function saveMemory(args, memoryDir) {
80
89
  let release;
81
90
  try {
82
91
  // Acquire lock with retry logic
83
- release = await lockfile.lock(sessionFile, {
92
+ release = await lockfile.lock(sessionFile, {
84
93
  retries: {
85
94
  retries: 5,
86
95
  factor: 3,
@@ -115,8 +124,14 @@ async function listSessions(memoryDir) {
115
124
  * Shows session parsing JSONL or legacy JSON
116
125
  */
117
126
  async function showSession(args, memoryDir) {
118
- const date = args[0] || new Date().toISOString().split('T')[0];
119
-
127
+ // Support: grimoire memory show --last 5
128
+ let date, lastN;
129
+ for (let i = 0; i < args.length; i++) {
130
+ if (args[i] === '--last' && args[i + 1]) lastN = parseInt(args[i + 1]);
131
+ else if (!args[i].startsWith('-')) date = args[i];
132
+ }
133
+ date = date || new Date().toISOString().split('T')[0];
134
+
120
135
  let sessionFile = path.join(memoryDir, 'sessions', `${date}.jsonl`);
121
136
  let isJsonl = true;
122
137
 
@@ -131,20 +146,101 @@ async function showSession(args, memoryDir) {
131
146
  }
132
147
 
133
148
  const rawContent = await fs.readFile(sessionFile, 'utf8');
134
- console.log(`\n📖 Session: ${date}`);
135
-
149
+ let entries;
136
150
  if (isJsonl) {
137
- const lines = rawContent.split('\n').filter(l => l.trim());
138
- lines.forEach(line => {
139
- const e = JSON.parse(line);
140
- console.log(` [${e.timestamp.split('T')[1].split('.')[0]}] ${e.content}`);
141
- });
151
+ entries = rawContent.split('\n').filter(l => l.trim()).map(l => JSON.parse(l));
142
152
  } else {
143
- const data = JSON.parse(rawContent);
144
- data.entries.forEach(e => {
145
- console.log(` [${e.timestamp.split('T')[1].split('.')[0]}] ${e.content}`);
153
+ entries = JSON.parse(rawContent).entries;
154
+ }
155
+
156
+ if (lastN) entries = entries.slice(-lastN);
157
+
158
+ console.log(`\n📖 Session: ${date} (${entries.length} entr${entries.length === 1 ? 'y' : 'ies'})`);
159
+ entries.forEach(e => {
160
+ console.log(` [${e.timestamp.split('T')[1].split('.')[0]}] ${e.content}`);
161
+ });
162
+ }
163
+
164
+ /**
165
+ * Search entries across all sessions
166
+ */
167
+ async function searchMemory(args, memoryDir) {
168
+ const keyword = args.join(' ');
169
+ if (!keyword) { console.error('❌ Provide a search term.'); return; }
170
+
171
+ const sessionsDir = path.join(memoryDir, 'sessions');
172
+ const files = (await fs.readdir(sessionsDir)).filter(f => f.endsWith('.jsonl') || f.endsWith('.json'));
173
+
174
+ let matches = [];
175
+ for (const file of files) {
176
+ const raw = await fs.readFile(path.join(sessionsDir, file), 'utf8');
177
+ const entries = file.endsWith('.jsonl')
178
+ ? raw.split('\n').filter(l => l.trim()).map(l => JSON.parse(l))
179
+ : JSON.parse(raw).entries || [];
180
+ const date = file.replace(/\.(jsonl|json)$/, '');
181
+ for (const e of entries) {
182
+ if (e.content.toLowerCase().includes(keyword.toLowerCase())) {
183
+ matches.push({ date, time: e.timestamp.split('T')[1].split('.')[0], content: e.content });
184
+ }
185
+ }
186
+ }
187
+
188
+ console.log(`\n🔍 Search: "${keyword}" — ${matches.length} result${matches.length === 1 ? '' : 's'}`);
189
+ if (matches.length === 0) { console.log(' (No entries found)'); return; }
190
+ matches.forEach(m => console.log(` [${m.date} ${m.time}] ${m.content}`));
191
+ }
192
+
193
+ /**
194
+ * Export all sessions to a Markdown file
195
+ */
196
+ async function exportMemory(args, memoryDir) {
197
+ const format = args.includes('--format') ? args[args.indexOf('--format') + 1] : 'markdown';
198
+ const sessionsDir = path.join(memoryDir, 'sessions');
199
+ const files = (await fs.readdir(sessionsDir)).filter(f => f.endsWith('.jsonl') || f.endsWith('.json')).sort();
200
+
201
+ let output = '# Grimoire Memory Export\n\n';
202
+ output += `> Exported: ${new Date().toISOString()}\n\n`;
203
+
204
+ for (const file of files) {
205
+ const date = file.replace(/\.(jsonl|json)$/, '');
206
+ const raw = await fs.readFile(path.join(sessionsDir, file), 'utf8');
207
+ const entries = file.endsWith('.jsonl')
208
+ ? raw.split('\n').filter(l => l.trim()).map(l => JSON.parse(l))
209
+ : JSON.parse(raw).entries || [];
210
+
211
+ output += `## ${date}\n\n`;
212
+ entries.forEach(e => {
213
+ output += `- \`${e.timestamp.split('T')[1].split('.')[0]}\` ${e.content}\n`;
146
214
  });
215
+ output += '\n';
147
216
  }
217
+
218
+ const outFile = path.join(process.cwd(), `grimoire-memory-export-${Date.now()}.md`);
219
+ await fs.writeFile(outFile, output, 'utf8');
220
+ console.log(`✅ Memory exported to: ${outFile}`);
221
+ }
222
+
223
+ /**
224
+ * Clear sessions older than N days
225
+ */
226
+ async function clearMemory(args, memoryDir) {
227
+ const olderThan = args.find(a => a.startsWith('--older-than'));
228
+ const days = olderThan ? parseInt(olderThan.replace('--older-than=', '').replace('--older-than', '') || args[args.indexOf(olderThan) + 1] || '30') : 30;
229
+
230
+ const sessionsDir = path.join(memoryDir, 'sessions');
231
+ const files = (await fs.readdir(sessionsDir)).filter(f => f.match(/^\d{4}-\d{2}-\d{2}/));
232
+ const cutoff = new Date();
233
+ cutoff.setDate(cutoff.getDate() - days);
234
+
235
+ let removed = 0;
236
+ for (const file of files) {
237
+ const datePart = file.replace(/\.(jsonl|json)$/, '');
238
+ if (new Date(datePart) < cutoff) {
239
+ await fs.unlink(path.join(sessionsDir, file));
240
+ removed++;
241
+ }
242
+ }
243
+ console.log(`✅ Cleared ${removed} session${removed === 1 ? '' : 's'} older than ${days} days.`);
148
244
  }
149
245
 
150
246
  /**
@@ -175,7 +271,7 @@ async function digestMemory(args, memoryDir) {
175
271
  categories.misc = entries.filter(e => !categorizedIds.has(e));
176
272
 
177
273
  console.log(`📊 Analysis complete: ${entries.length} entries processed.`);
178
-
274
+
179
275
  if (categories.decisions.length > 0) {
180
276
  console.log('\n🏛️ PROPOSED DECISIONS (Update entities/decisions.md):');
181
277
  categories.decisions.forEach(e => console.log(` - ${e.content}`));
@@ -194,21 +290,31 @@ async function digestMemory(args, memoryDir) {
194
290
  console.log('\n--- SESSION ARCHIVING ---');
195
291
  const archiveDir = path.join(memoryDir, 'sessions', 'archived');
196
292
  if (!existsSync(archiveDir)) await fs.mkdir(archiveDir);
197
-
293
+
198
294
  await fs.rename(sessionFile, path.join(archiveDir, `${date}.jsonl`));
199
295
  console.log(`✅ Session ${date} digested and archived.`);
200
296
  }
201
297
 
202
298
  function showHelp() {
203
299
  console.log(`
204
- 📊 Grimoire Memory Management (Turbo I/O)
205
-
206
- USAGE:
207
- grimoire memory save "<text>" # Save a new entry (Fast Append)
208
- grimoire memory list # List all recorded sessions
209
- grimoire memory show <date> # Show entries for a specific date
210
- grimoire memory digest <date> # Consolidate daily logs into permanent docs
211
- grimoire memory help # Show this help
300
+ 💾 Grimoire Memory Gestão de Contexto
301
+
302
+ USO:
303
+ grimoire memory save "<texto>" Salva uma entrada na sessão de hoje
304
+ grimoire memory list Lista todas as sessões
305
+ grimoire memory show [data] Mostra entradas de uma data (padrão: hoje)
306
+ grimoire memory show --last 5 Mostra as últimas 5 entradas de hoje
307
+ grimoire memory search "<termo>" Busca em todas as sessões
308
+ grimoire memory export Exporta tudo para Markdown
309
+ grimoire memory digest [data] Categoriza e arquiva uma sessão
310
+ grimoire memory clear --older-than 30d Remove sessões antigas
311
+
312
+ EXEMPLOS:
313
+ grimoire memory save "Decidimos usar PostgreSQL por performance"
314
+ grimoire memory show --last 10
315
+ grimoire memory search "PostgreSQL"
316
+ grimoire memory export --format markdown
317
+ grimoire memory clear --older-than 30
212
318
  `);
213
319
  }
214
320
 
@@ -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,9 @@ 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;
30
118
  case 'status':
31
119
  handleStatus();
32
120
  break;
@@ -47,7 +135,7 @@ async function main() {
47
135
  break;
48
136
  case '--version':
49
137
  case '-v':
50
- console.log(`Grimoire Pro v${packageJson.version}`);
138
+ console.log(`Grimoire v${packageJson.version}`);
51
139
  break;
52
140
  case '--help':
53
141
  case '-h':
@@ -55,6 +143,9 @@ async function main() {
55
143
  showHelp();
56
144
  break;
57
145
  }
146
+
147
+ // Wait for update check to finish (prints banner after main command if needed)
148
+ await updatePromise;
58
149
  }
59
150
 
60
151
  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.20",
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
  ],
@@ -19,73 +19,73 @@ const colors = {
19
19
  // ============================================
20
20
  // CORE BRAND COLORS
21
21
  // ============================================
22
-
22
+
23
23
  /**
24
24
  * Primary brand color - ClickUp-inspired purple
25
25
  * Usage: Main questions, headers, CTAs, primary actions
26
26
  */
27
27
  primary: chalk.hex('#8B5CF6'),
28
-
28
+
29
29
  /**
30
30
  * Secondary brand color - Magenta accent from logo gradient
31
31
  * Usage: Important highlights, special emphasis, key information
32
32
  */
33
33
  secondary: chalk.hex('#EC4899'),
34
-
34
+
35
35
  /**
36
36
  * Tertiary brand color - Blue accent from logo gradient
37
37
  * Usage: Secondary actions, links, complementary elements
38
38
  */
39
39
  tertiary: chalk.hex('#3B82F6'),
40
-
40
+
41
41
  // ============================================
42
42
  // FUNCTIONAL COLORS
43
43
  // ============================================
44
-
44
+
45
45
  /**
46
46
  * Success state color
47
47
  * Usage: Checkmarks, completed steps, success messages
48
48
  */
49
49
  success: chalk.hex('#10B981'),
50
-
50
+
51
51
  /**
52
52
  * Warning state color - Orange from logo gradient
53
53
  * Usage: Warnings, confirmations, caution states
54
54
  */
55
55
  warning: chalk.hex('#F59E0B'),
56
-
56
+
57
57
  /**
58
58
  * Error state color
59
59
  * Usage: Errors, critical alerts, validation failures
60
60
  */
61
61
  error: chalk.hex('#EF4444'),
62
-
62
+
63
63
  /**
64
64
  * Info state color - Cyan-blue from logo gradient
65
65
  * Usage: Info messages, tips, helper text, additional context
66
66
  */
67
67
  info: chalk.hex('#06B6D4'),
68
-
68
+
69
69
  // ============================================
70
70
  // NEUTRAL COLORS
71
71
  // ============================================
72
-
72
+
73
73
  /**
74
74
  * Muted text color
75
75
  * Usage: Subtle text, disabled states, secondary content
76
76
  */
77
77
  muted: chalk.hex('#94A3B8'),
78
-
78
+
79
79
  /**
80
80
  * Dim text color
81
81
  * Usage: Secondary text, muted content, less important info
82
82
  */
83
83
  dim: chalk.hex('#64748B'),
84
-
84
+
85
85
  // ============================================
86
86
  // GRADIENT SYSTEM
87
87
  // ============================================
88
-
88
+
89
89
  /**
90
90
  * Brand gradient colors for animations and special effects
91
91
  * Matches grimoire logo gradient: magenta → purple → blue
@@ -93,28 +93,28 @@ const colors = {
93
93
  gradient: {
94
94
  /** Gradient start - Magenta (logo top) */
95
95
  start: chalk.hex('#EC4899'),
96
-
96
+
97
97
  /** Gradient middle - Purple (brand) */
98
98
  middle: chalk.hex('#8B5CF6'),
99
-
99
+
100
100
  /** Gradient end - Blue (logo bottom) */
101
101
  end: chalk.hex('#3B82F6'),
102
102
  },
103
-
103
+
104
104
  // ============================================
105
105
  // SEMANTIC SHORTCUTS
106
106
  // ============================================
107
-
107
+
108
108
  /**
109
109
  * Highlighted text - Bold magenta for key information
110
110
  */
111
111
  highlight: chalk.hex('#EC4899').bold,
112
-
112
+
113
113
  /**
114
114
  * Primary branding - Bold purple for grimoire brand moments
115
115
  */
116
116
  brandPrimary: chalk.hex('#8B5CF6').bold,
117
-
117
+
118
118
  /**
119
119
  * Secondary branding - Cyan for supporting brand elements
120
120
  */
@@ -127,25 +127,25 @@ const colors = {
127
127
  const status = {
128
128
  /** Success indicator: ✓ (green) */
129
129
  success: (text) => `${colors.success('✓')} ${text}`,
130
-
130
+
131
131
  /** Error indicator: ✗ (red) */
132
132
  error: (text) => `${colors.error('✗')} ${text}`,
133
-
133
+
134
134
  /** Warning indicator: ⚠️ (orange) */
135
135
  warning: (text) => `${colors.warning('⚠️')} ${text}`,
136
-
136
+
137
137
  /** Info indicator: ℹ (cyan) */
138
138
  info: (text) => `${colors.info('ℹ')} ${text}`,
139
-
139
+
140
140
  /** Loading indicator: ⏳ (cyan) */
141
141
  loading: (text) => `${colors.info('⏳')} ${text}`,
142
-
142
+
143
143
  /** Skipped indicator: ⊘ (muted) */
144
144
  skipped: (text) => `${colors.muted('⊘')} ${text}`,
145
-
145
+
146
146
  /** Tip indicator: 💡 (info) */
147
147
  tip: (text) => `${colors.info('💡')} ${text}`,
148
-
148
+
149
149
  /** Party indicator: 🎉 (brand primary) */
150
150
  celebrate: (text) => `${colors.brandPrimary('🎉')} ${text}`,
151
151
  };
@@ -156,13 +156,13 @@ const status = {
156
156
  const headings = {
157
157
  /** H1 - Brand primary, bold, large spacing */
158
158
  h1: (text) => `\n${colors.brandPrimary(text)}\n`,
159
-
159
+
160
160
  /** H2 - Primary color, bold */
161
161
  h2: (text) => `\n${colors.primary.bold(text)}\n`,
162
-
162
+
163
163
  /** H3 - Primary color */
164
164
  h3: (text) => colors.primary(text),
165
-
165
+
166
166
  /** Section divider */
167
167
  divider: () => colors.dim('─'.repeat(50)),
168
168
  };
@@ -173,10 +173,10 @@ const headings = {
173
173
  const lists = {
174
174
  /** Bullet point (primary) */
175
175
  bullet: (text) => `${colors.primary('•')} ${text}`,
176
-
176
+
177
177
  /** Numbered item (primary) */
178
178
  numbered: (num, text) => `${colors.primary(`${num}.`)} ${text}`,
179
-
179
+
180
180
  /** Checkbox unchecked */
181
181
  checkbox: (text, checked = false) => {
182
182
  const icon = checked ? colors.success('☑') : colors.muted('☐');
@@ -192,31 +192,60 @@ const examples = {
192
192
  console.log(headings.h1('🎉 Welcome to grimoire v4 Installer!'));
193
193
  console.log(colors.info('Let\'s configure your project in just a few steps...\n'));
194
194
  },
195
-
195
+
196
196
  question: () => {
197
197
  console.log(colors.primary('? Select your project type:'));
198
198
  console.log(lists.bullet('Greenfield (new project)'));
199
199
  console.log(lists.bullet('Brownfield (existing project)'));
200
200
  },
201
-
201
+
202
202
  progress: () => {
203
203
  console.log(status.loading('Installing dependencies...'));
204
204
  console.log(status.success('Dependencies installed'));
205
205
  console.log(status.loading('Configuring environment...'));
206
206
  },
207
-
207
+
208
208
  feedback: () => {
209
209
  console.log(status.success('Configuration complete!'));
210
210
  console.log(status.warning('Existing .env found. Overwrite?'));
211
211
  console.log(status.error('Invalid path provided'));
212
212
  console.log(status.tip('You can change this later in settings'));
213
213
  },
214
-
215
- complete: () => {
214
+
215
+ complete: (projectDir) => {
216
216
  console.log('\n' + headings.divider());
217
217
  console.log(status.celebrate('Installation Complete!'));
218
218
  console.log(colors.info('Your grimoire project is ready to use.'));
219
219
  console.log(headings.divider() + '\n');
220
+
221
+ // First-Run Next Steps
222
+ const path = require('path');
223
+ const fs = require('fs');
224
+ const cwd = projectDir || process.cwd();
225
+ const hasGemini = fs.existsSync(path.join(cwd, 'GEMINI.md'));
226
+ const hasClaude = fs.existsSync(path.join(cwd, '.claude'));
227
+ const hasCursor = fs.existsSync(path.join(cwd, '.cursor'));
228
+
229
+ let ideStep = ' 2. Abra o Gemini CLI: gemini → Michelangelo te recebe';
230
+ if (hasClaude) ideStep = ' 2. Abra o Claude Code — use /dev, /qa, /architect';
231
+ else if (hasCursor) ideStep = ' 2. Abra o Cursor — @dev, @qa, @architect no chat';
232
+ else if (hasGemini) ideStep = ' 2. Abra o Gemini CLI: gemini → Michelangelo te recebe';
233
+
234
+ const agentsDir = path.join(cwd, '.codex', 'agents');
235
+ const agentCount = fs.existsSync(agentsDir)
236
+ ? fs.readdirSync(agentsDir).filter(f => f.endsWith('.md')).length : 0;
237
+
238
+ console.log(`🎯 Próximos passos:
239
+ 1. Verifique a instalação: grimoire status
240
+ ${ideStep}
241
+ 3. Liste os agentes: grimoire agents list
242
+ 4. Diagnóstico completo: grimoire doctor
243
+
244
+ 💡 Dica: Diga "@dev implementa o login" no chat da sua IDE.
245
+ Você tem ${agentCount} agentes prontos: @dev @qa @architect @grimoire-master...
246
+
247
+ 📚 Docs: https://github.com/gabrielrlima/grimoire#readme
248
+ `);
220
249
  },
221
250
  };
222
251
 
@@ -0,0 +1,2 @@
1
+ # This file ensures the squads directory is tracked by Git
2
+ # Add your squads (squads) here
@@ -0,0 +1,25 @@
1
+ # Grimoire Squads
2
+
3
+ Este diretório contém os squads pré-configurados para organizar o fluxo de trabalho dos agentes.
4
+
5
+ ## Squads Disponíveis
6
+
7
+ ### 1. Full Stack Development
8
+ **Agentes:** @dev, @qa, @architect
9
+ **Foco:** Planejamento técnico, codificação e testes.
10
+
11
+ ### 2. Planning & Strategy
12
+ **Agentes:** @analyst, @pm, @architect, @ux-design-expert
13
+ **Foco:** Transformar ideias em PRDs e arquitetura.
14
+
15
+ ### 3. DevOps & Management
16
+ **Agentes:** @devops, @sm, @po
17
+ **Foco:** Backlog, sprints e pipelines de deploy.
18
+
19
+ ## Como Usar
20
+ Para carregar um squad durante a instalação ou re-setup, selecione as opções correspondentes no CLI do Grimoire.
21
+
22
+ ## Como Criar Squads Customizados
23
+ 1. Crie uma nova pasta dentro de `squads/`.
24
+ 2. Adicione um arquivo `squad.yaml` com nome, descrição e a lista de agentes (referenciando arquivos em `.codex/agents/`).
25
+ 3. Adicione fluxos de trabalho em uma pasta `workflows/` opcional.
@@ -0,0 +1,9 @@
1
+ name: "DevOps & Management Squad"
2
+ description: "Squad responsável pelo ciclo de entrega, automação e gerenciamento de backlog."
3
+ agents:
4
+ - devops
5
+ - sm
6
+ - po
7
+ workflows:
8
+ - id: deploy-pipeline
9
+ file: workflows/deploy-pipeline.md
@@ -0,0 +1,22 @@
1
+ # Workflow: Pipeline de Deploy (Deploy Pipeline)
2
+
3
+ **Objetivo:** Garantir a entrega segura e automatizada do software.
4
+
5
+ ## Etapas do Fluxo
6
+
7
+ ### 1. Quality Gate
8
+ - **Agente:** @devops
9
+ - **Ação:** Rodar lint, testes e build no CI antes do push.
10
+
11
+ ### 2. Versionamento
12
+ - **Agente:** @devops
13
+ - **Ação:** Realizar o bump de versão e criar tags de release.
14
+
15
+ ### 3. Promoção de Ambiente
16
+ - **Agente:** @devops
17
+ - **Ação:** Orquestrar o deploy para staging ou produção.
18
+
19
+ ## Ferramentas
20
+ - GitHub Actions
21
+ - Semantic Release
22
+ - Quality Gate Hooks
@@ -0,0 +1,11 @@
1
+ name: "Full Stack Development Squad"
2
+ description: "Squad completo para planejamento, implementação e teste de features de software."
3
+ agents:
4
+ - dev
5
+ - qa
6
+ - architect
7
+ workflows:
8
+ - id: new-feature
9
+ file: workflows/new-feature.md
10
+ - id: code-review
11
+ file: workflows/code-review.md
@@ -0,0 +1,22 @@
1
+ # Workflow: Revisão de Código (Code Review)
2
+
3
+ **Objetivo:** Garantir a qualidade e consistência do código antes do merge.
4
+
5
+ ## Etapas do Fluxo
6
+
7
+ ### 1. Análise Estática
8
+ - **Agente:** @qa (em conjunto com CodeRabbit)
9
+ - **Ação:** Identificar bugs latentes, vulnerabilidades e dívida técnica.
10
+
11
+ ### 2. Revisão de Padrões
12
+ - **Agente:** @architect
13
+ - **Ação:** Validar se o código segue os padrões arquiteturais do projeto.
14
+
15
+ ### 3. Validação de Lógica
16
+ - **Agente:** @dev (Peer Review)
17
+ - **Ação:** Revisar a lógica de negócio implementada.
18
+
19
+ ## Critérios de Aprovação
20
+ - Zero problemas críticos de segurança.
21
+ - Lint e testes passando 100%.
22
+ - Conformidade com ADRs existentes.
@@ -0,0 +1,24 @@
1
+ # Workflow: Nova Funcionalidade (New Feature)
2
+
3
+ **Objetivo:** Levar uma ideia de funcionalidade até a produção através de um fluxo estruturado.
4
+
5
+ ## Etapas do Fluxo
6
+
7
+ ### 1. Planejamento Arquitetural
8
+ - **Agente:** @architect
9
+ - **Ação:** Criar ou atualizar o design do sistema para a nova feature.
10
+ - **Saída:** Documento de arquitetura técnica.
11
+
12
+ ### 2. Implementação
13
+ - **Agente:** @dev
14
+ - **Ação:** Codificar a funcionalidade seguindo o design aprovado.
15
+ - **Saída:** Código-fonte implementado e commitado localmente.
16
+
17
+ ### 3. Garantia de Qualidade (QA)
18
+ - **Agente:** @qa
19
+ - **Ação:** Validar a implementação contra os critérios de aceitação.
20
+ - **Saída:** Aprovação de QA e sugestões de melhoria.
21
+
22
+ ## Gatilhos (Triggers)
23
+ - Solicitação de nova feature pelo @pm
24
+ - Backlog de sprints atualizado pelo @sm
@@ -0,0 +1,10 @@
1
+ name: "Planning & Strategy Squad"
2
+ description: "Squad focado em requisitos, design de sistema e experiência do usuário (UX)."
3
+ agents:
4
+ - analyst
5
+ - pm
6
+ - architect
7
+ - ux-design-expert
8
+ workflows:
9
+ - id: prd-to-stories
10
+ file: workflows/prd-to-stories.md
@@ -0,0 +1,20 @@
1
+ # Workflow: PRD para Histórias (PRD to Stories)
2
+
3
+ **Objetivo:** Fragmentar requisitos de alto nível em unidades de desenvolvimento acionáveis.
4
+
5
+ ## Etapas do Fluxo
6
+
7
+ ### 1. Refinamento de Requisitos
8
+ - **Agente:** @analyst + @pm
9
+ - **Ação:** Validar o briefing e transformar em um PRD completo.
10
+
11
+ ### 2. Design Visual (UX)
12
+ - **Agente:** @ux-design-expert
13
+ - **Ação:** Criar fluxos de usuário e wireframes para os requisitos.
14
+
15
+ ### 3. Fragmentação (Sharding)
16
+ - **Agente:** @sm
17
+ - **Ação:** Criar as histórias (stories) em `docs/stories/` com base no PRD.
18
+
19
+ ## Saída Esperada
20
+ - Backlog de stories priorizado e pronto para implementação.