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.
@@ -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.21
11
+ generated_at: "2026-02-22T14:46:40.983Z"
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