grimoire-framework 1.3.0 → 1.4.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/README.md +150 -112
- package/bin/commands/backup.js +165 -0
- package/bin/commands/exportall.js +200 -0
- package/bin/commands/memory.js +14 -0
- package/bin/commands/search.js +171 -0
- package/bin/commands/story.js +45 -6
- package/bin/grimoire-cli.js +35 -15
- package/package.json +1 -1
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
# - SHA256 hashes for change detection
|
|
8
8
|
# - File types for categorization
|
|
9
9
|
#
|
|
10
|
-
version: 1.
|
|
11
|
-
generated_at: "2026-02-
|
|
10
|
+
version: 1.4.0
|
|
11
|
+
generated_at: "2026-02-22T17:13:01.000Z"
|
|
12
12
|
generator: scripts/generate-install-manifest.js
|
|
13
13
|
file_count: 1011
|
|
14
14
|
files:
|
package/README.md
CHANGED
|
@@ -1,171 +1,209 @@
|
|
|
1
|
-
# Grimoire
|
|
1
|
+
# 🔮 Grimoire Framework
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://opensource.org/licenses/MIT)
|
|
5
|
-
[](https://nodejs.org)
|
|
3
|
+
> **CLI de gestão para frameworks de agentes de IA** — instale uma vez, use em todos os seus projetos
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/grimoire-framework)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
[](https://nodejs.org)
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## ⚡ Quick Start (5 passos)
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
#
|
|
14
|
+
# 1. Instale no projeto
|
|
15
15
|
npx grimoire-framework install
|
|
16
16
|
|
|
17
|
-
#
|
|
18
|
-
|
|
19
|
-
```
|
|
17
|
+
# 2. Gere o prompt de início de sessão
|
|
18
|
+
grimoire session start
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
# 3. Cole o prompt no chat da sua IDE e ative um agente
|
|
21
|
+
# @dev @qa @architect @grimoire-master
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
# 4. Crie e rastreie stories
|
|
24
|
+
grimoire story create "Implementar autenticação JWT"
|
|
25
|
+
|
|
26
|
+
# 5. Veja o relatório ao final do dia
|
|
27
|
+
grimoire report
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
---
|
|
29
31
|
|
|
30
|
-
##
|
|
31
|
-
|
|
32
|
-
### Gerenciamento do Framework
|
|
33
|
-
|
|
34
|
-
| Comando | Descrição |
|
|
35
|
-
|---|---|
|
|
36
|
-
| `npx grimoire-framework install` | Instalar no projeto atual |
|
|
37
|
-
| `npx grimoire-framework update` | Atualizar preservando customizações |
|
|
38
|
-
| `npx grimoire-framework update --force` | Forçar resync de todos os arquivos |
|
|
39
|
-
| `npx grimoire-framework update --dry-run` | Simular update (sem aplicar) |
|
|
40
|
-
|
|
41
|
-
### Diagnóstico
|
|
32
|
+
## 🧙 O que é o Grimoire?
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|---|---|
|
|
45
|
-
| `grimoire status` | Saúde do framework + integração IDE |
|
|
46
|
-
| `grimoire doctor` | Diagnóstico detalhado com dicas de fix |
|
|
47
|
-
| `grimoire agents list` | Listar agentes com personas |
|
|
48
|
-
| `grimoire whoami` | Contexto da sessão atual |
|
|
49
|
-
| `grimoire --version` | Versão instalada |
|
|
34
|
+
O Grimoire é um framework que adiciona **agentes de IA especializados** ao chat da sua IDE (Gemini CLI, Cursor, VS Code, Claude). Cada agente tem uma persona, expertise e estilo próprios.
|
|
50
35
|
|
|
51
|
-
|
|
36
|
+
```
|
|
37
|
+
CLI (terminal) → gerencia o framework
|
|
38
|
+
Agentes (IDE) → @dev, @qa, @architect no chat da IDE
|
|
39
|
+
```
|
|
52
40
|
|
|
53
41
|
---
|
|
54
42
|
|
|
55
|
-
##
|
|
56
|
-
|
|
57
|
-
Após instalar, ative os agentes diretamente no chat da sua IDE:
|
|
43
|
+
## 🤖 Agentes incluídos
|
|
58
44
|
|
|
59
|
-
|
|
|
45
|
+
| Agente | Persona | Especialidade |
|
|
60
46
|
|---|---|---|
|
|
61
|
-
| `@grimoire-master` | 👑 Michelangelo | Orquestração
|
|
62
|
-
| `@dev` | 🎨 Da Vinci |
|
|
63
|
-
| `@qa` | 🖌️ Dürer |
|
|
64
|
-
| `@architect` | 🏛️ Gaudí | Arquitetura
|
|
65
|
-
| `@pm` | 📋 Raphael |
|
|
66
|
-
| `@
|
|
67
|
-
| `@devops` | ⚡ Boccioni | DevOps
|
|
68
|
-
| `@data-engineer` | 📊 Escher |
|
|
69
|
-
| `@analyst` | 🔍 Vermeer | Pesquisa
|
|
70
|
-
| `@ux-design-expert` | 🎭 Matisse | UX/UI Design |
|
|
47
|
+
| `@grimoire-master` | 👑 Michelangelo | Orquestração |
|
|
48
|
+
| `@dev` | 🎨 Da Vinci | Full-stack |
|
|
49
|
+
| `@qa` | 🖌️ Dürer | QA & testes |
|
|
50
|
+
| `@architect` | 🏛️ Gaudí | Arquitetura |
|
|
51
|
+
| `@pm` | 📋 Raphael | Produto |
|
|
52
|
+
| `@ux-design-expert` | 🎭 Matisse | UX/Design |
|
|
53
|
+
| `@devops` | ⚡ Boccioni | DevOps |
|
|
54
|
+
| `@data-engineer` | 📊 Escher | Dados |
|
|
55
|
+
| `@analyst` | 🔍 Vermeer | Pesquisa |
|
|
71
56
|
| `@po` | 🎯 Velázquez | Product Owner |
|
|
57
|
+
| `@sm` | 🌊 Monet | Scrum Master |
|
|
58
|
+
| `@squad-creator` | 🗿 Rodin | Criação de squads |
|
|
72
59
|
|
|
73
60
|
---
|
|
74
61
|
|
|
75
|
-
##
|
|
76
|
-
|
|
77
|
-
### Gemini CLI (Recomendado)
|
|
78
|
-
O Grimoire configura automaticamente o arquivo `GEMINI.md` na raiz do projeto. O Michelangelo se apresenta ao abrir o Gemini CLI.
|
|
62
|
+
## 📋 Referência de comandos
|
|
79
63
|
|
|
64
|
+
### Essenciais
|
|
80
65
|
```bash
|
|
81
|
-
|
|
82
|
-
#
|
|
83
|
-
#
|
|
66
|
+
grimoire status # saúde do framework
|
|
67
|
+
grimoire whoami # contexto completo (config+pro+stories)
|
|
68
|
+
grimoire doctor # diagnóstico completo com dicas
|
|
69
|
+
grimoire update # atualiza agentes e arquivos
|
|
70
|
+
grimoire update --dry-run # preview sem aplicar
|
|
84
71
|
```
|
|
85
72
|
|
|
86
|
-
|
|
87
|
-
```
|
|
88
|
-
|
|
73
|
+
### Sessão & Workflow
|
|
74
|
+
```bash
|
|
75
|
+
grimoire session start # prompt para IDE (squad+stories+memória)
|
|
76
|
+
grimoire session start --squad dev # com squad específico
|
|
77
|
+
grimoire story create "Título" # nova story
|
|
78
|
+
grimoire story list # stories ativas
|
|
79
|
+
grimoire story done US-001 # marcar concluída
|
|
80
|
+
grimoire story note US-001 "obs" # adicionar nota
|
|
81
|
+
grimoire story show US-001 # detalhes + memórias linkadas
|
|
82
|
+
grimoire report # relatório de hoje
|
|
83
|
+
grimoire report --period week # relatório da semana (7 dias)
|
|
84
|
+
grimoire report --md # exportar relatório para Markdown
|
|
89
85
|
```
|
|
90
86
|
|
|
91
|
-
###
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
87
|
+
### Agentes
|
|
88
|
+
```bash
|
|
89
|
+
grimoire agents list # lista com personas
|
|
90
|
+
grimoire agent create # wizard interativo
|
|
91
|
+
grimoire agent validate dev # valida YAML e campos (score 0-100)
|
|
92
|
+
grimoire agent edit dev # abre no $EDITOR
|
|
93
|
+
grimoire agent remove dev # remove agente customizado
|
|
94
|
+
```
|
|
98
95
|
|
|
99
|
-
|
|
96
|
+
### Squads
|
|
97
|
+
```bash
|
|
98
|
+
grimoire squads list # lista squads disponíveis
|
|
99
|
+
grimoire squads use fullstack # prompt de ativação para IDE
|
|
100
|
+
grimoire squads info fullstack # detalhes do squad
|
|
101
|
+
```
|
|
100
102
|
|
|
103
|
+
### Memória
|
|
104
|
+
```bash
|
|
105
|
+
grimoire memory save "texto" # salvar entrada
|
|
106
|
+
grimoire memory save --tag decisão "txt" # com tag
|
|
107
|
+
grimoire memory save --story US-001 "txt" # linkada a story
|
|
108
|
+
grimoire memory show # entradas de hoje
|
|
109
|
+
grimoire memory search "termo" # buscar em sessões
|
|
110
|
+
grimoire memory list-tags # tags únicas com contagem
|
|
111
|
+
grimoire memory export # exportar para Markdown
|
|
112
|
+
grimoire memory clear --older-than 30 # limpar antigas
|
|
101
113
|
```
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
|
|
115
|
+
### Busca & Backup
|
|
116
|
+
```bash
|
|
117
|
+
grimoire search "JWT" # busca global (memória+stories+agentes)
|
|
118
|
+
grimoire search "JWT" --memory # só memória
|
|
119
|
+
grimoire search "JWT" --stories # só stories
|
|
120
|
+
grimoire backup # backup de .grimoire/
|
|
121
|
+
grimoire backup --list # listar backups
|
|
122
|
+
grimoire backup --restore <arq> # restaurar backup
|
|
123
|
+
grimoire export --all # bundle Markdown completo
|
|
124
|
+
grimoire export --all --json # bundle JSON completo
|
|
111
125
|
```
|
|
112
126
|
|
|
113
|
-
|
|
127
|
+
### Métricas
|
|
128
|
+
```bash
|
|
129
|
+
grimoire metrics # dashboard do mês
|
|
130
|
+
grimoire metrics --period week # últimos 7 dias
|
|
131
|
+
grimoire metrics --period all # histórico completo
|
|
132
|
+
grimoire metrics export --csv # exportar para CSV
|
|
133
|
+
grimoire metrics track session "desc" # registrar evento manual
|
|
134
|
+
```
|
|
114
135
|
|
|
115
|
-
|
|
136
|
+
### Config & Hooks
|
|
137
|
+
```bash
|
|
138
|
+
grimoire config list # todas as configurações
|
|
139
|
+
grimoire config get default_agent # valor de uma chave
|
|
140
|
+
grimoire config set default_agent dev # definir valor
|
|
141
|
+
grimoire config reset # restaurar defaults
|
|
142
|
+
grimoire hooks install # git post-commit (auto-tracking)
|
|
143
|
+
grimoire hooks status # verificar status do hook
|
|
144
|
+
grimoire hooks uninstall # remover hook
|
|
145
|
+
```
|
|
116
146
|
|
|
117
|
-
|
|
147
|
+
### Sync Global
|
|
148
|
+
```bash
|
|
149
|
+
grimoire sync --global # envia agentes → ~/.grimoire/agents/
|
|
150
|
+
grimoire sync --from-global # puxa agentes do store global
|
|
151
|
+
grimoire sync --list # lista agentes no store
|
|
152
|
+
```
|
|
118
153
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
154
|
+
### Marketplace
|
|
155
|
+
```bash
|
|
156
|
+
grimoire marketplace list # navega agentes da comunidade
|
|
157
|
+
grimoire marketplace install slug # instala agente
|
|
158
|
+
grimoire marketplace search "termo" # busca por especialidade
|
|
159
|
+
grimoire marketplace submit # guia para publicar
|
|
160
|
+
```
|
|
122
161
|
|
|
162
|
+
### Grimoire Pro
|
|
123
163
|
```bash
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
164
|
+
grimoire pro activate <chave> # ativa licença Pro
|
|
165
|
+
grimoire pro status # status e features
|
|
166
|
+
grimoire pro features # lista features Pro
|
|
167
|
+
grimoire pro deactivate # remove licença
|
|
127
168
|
```
|
|
128
169
|
|
|
129
170
|
---
|
|
130
171
|
|
|
131
|
-
##
|
|
172
|
+
## 📁 Estrutura do projeto
|
|
132
173
|
|
|
133
|
-
|
|
134
|
-
-
|
|
135
|
-
|
|
174
|
+
```
|
|
175
|
+
seu-projeto/
|
|
176
|
+
├── .grimoire/
|
|
177
|
+
│ ├── memory/sessions/ # memória JSONL
|
|
178
|
+
│ ├── metrics/ # métricas JSONL
|
|
179
|
+
│ ├── stories/ # stories JSON
|
|
180
|
+
│ ├── backups/ # backups .grimbak
|
|
181
|
+
│ ├── exports/ # bundles exportados
|
|
182
|
+
│ └── config.yaml # configurações do projeto
|
|
183
|
+
├── .codex/agents/ # agentes Gemini CLI
|
|
184
|
+
├── .gemini/ # config Gemini CLI
|
|
185
|
+
├── .cursor/ # regras Cursor
|
|
186
|
+
└── GEMINI.md # persona principal
|
|
187
|
+
```
|
|
136
188
|
|
|
137
189
|
---
|
|
138
190
|
|
|
139
|
-
##
|
|
191
|
+
## 📦 Versões
|
|
140
192
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
- [ ] **Enterprise Connectors** — Integração com Jira, Notion e Slack
|
|
193
|
+
| Versão | Features |
|
|
194
|
+
|---|---|
|
|
195
|
+
| v1.0.x | Setup, agents, sync global |
|
|
196
|
+
| v1.1.x | Marketplace, Grimoire Pro |
|
|
197
|
+
| v1.2.0 | Squads use, config, hooks, agent validate, memory tags |
|
|
198
|
+
| v1.3.0 | Report, story, session start, doctor v3, whoami v2 |
|
|
199
|
+
| **v1.4.0** | **Search global, backup, export --all, memory --story, story note** |
|
|
149
200
|
|
|
150
201
|
---
|
|
151
202
|
|
|
152
203
|
## 🤝 Contribuindo
|
|
153
204
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
```bash
|
|
157
|
-
git clone https://github.com/gabrielrlima/grimoire.git
|
|
158
|
-
cd grimoire
|
|
159
|
-
npm install
|
|
160
|
-
npm test
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
---
|
|
164
|
-
|
|
165
|
-
## 📄 Licença
|
|
166
|
-
|
|
167
|
-
Distribuído sob a licença MIT. Veja [LICENSE](LICENSE) para mais informações.
|
|
205
|
+
Veja [CONTRIBUTING.md](CONTRIBUTING.md) e [docs/CHANGELOG.md](docs/CHANGELOG.md).
|
|
168
206
|
|
|
169
207
|
---
|
|
170
208
|
|
|
171
|
-
|
|
209
|
+
*Grimoire Framework · MIT License · Made with ❤️ by the community*
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire backup — Backup & Restore .grimoire/
|
|
3
|
+
*
|
|
4
|
+
* grimoire backup Cria backup zip de .grimoire/
|
|
5
|
+
* grimoire backup --restore <file> Restaura a partir de um zip
|
|
6
|
+
* grimoire backup --list Lista backups existentes
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const zlib = require('zlib');
|
|
14
|
+
|
|
15
|
+
function findGrimoireDir() {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const direct = path.join(cwd, '.grimoire');
|
|
18
|
+
const sub = path.join(cwd, 'grimoire', '.grimoire');
|
|
19
|
+
if (fs.existsSync(direct)) return { dir: direct, root: cwd };
|
|
20
|
+
if (fs.existsSync(sub)) return { dir: sub, root: path.join(cwd, 'grimoire') };
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getBackupDir(grimoireDir) {
|
|
25
|
+
const d = path.join(grimoireDir, 'backups');
|
|
26
|
+
if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true });
|
|
27
|
+
return d;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Simple tar-like serialization using NDJSON (no native zip without deps)
|
|
31
|
+
// Format: one header line + files as base64 encoded NDJSON entries
|
|
32
|
+
function createBackup(grimoireDir, backupDir) {
|
|
33
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
34
|
+
const backupFile = path.join(backupDir, `backup-${timestamp}.grimbak`);
|
|
35
|
+
|
|
36
|
+
const SKIP_DIRS = new Set(['backups', 'exports']);
|
|
37
|
+
const entries = [];
|
|
38
|
+
|
|
39
|
+
function walk(dir, rel = '') {
|
|
40
|
+
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
41
|
+
const relPath = rel ? `${rel}/${item.name}` : item.name;
|
|
42
|
+
const abs = path.join(dir, item.name);
|
|
43
|
+
if (item.isDirectory()) {
|
|
44
|
+
if (!SKIP_DIRS.has(item.name)) walk(abs, relPath);
|
|
45
|
+
} else {
|
|
46
|
+
try {
|
|
47
|
+
const content = fs.readFileSync(abs).toString('base64');
|
|
48
|
+
entries.push({ path: relPath, content, size: fs.statSync(abs).size });
|
|
49
|
+
} catch (_) { }
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
walk(grimoireDir);
|
|
55
|
+
|
|
56
|
+
const header = JSON.stringify({
|
|
57
|
+
version: 1,
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
59
|
+
files: entries.length,
|
|
60
|
+
source: grimoireDir
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const lines = [header, ...entries.map(e => JSON.stringify(e))];
|
|
64
|
+
const raw = lines.join('\n');
|
|
65
|
+
const buf = zlib.gzipSync(Buffer.from(raw, 'utf8'));
|
|
66
|
+
fs.writeFileSync(backupFile, buf);
|
|
67
|
+
|
|
68
|
+
return { file: backupFile, count: entries.length, size: buf.length };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function restoreBackup(backupFile, grimoireDir) {
|
|
72
|
+
if (!fs.existsSync(backupFile)) {
|
|
73
|
+
throw new Error(`Arquivo não encontrado: ${backupFile}`);
|
|
74
|
+
}
|
|
75
|
+
const buf = fs.readFileSync(backupFile);
|
|
76
|
+
const raw = zlib.gunzipSync(buf).toString('utf8');
|
|
77
|
+
const lines = raw.split('\n').filter(l => l.trim());
|
|
78
|
+
const header = JSON.parse(lines[0]);
|
|
79
|
+
|
|
80
|
+
let restored = 0;
|
|
81
|
+
for (const line of lines.slice(1)) {
|
|
82
|
+
try {
|
|
83
|
+
const entry = JSON.parse(line);
|
|
84
|
+
const dest = path.join(grimoireDir, entry.path);
|
|
85
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
86
|
+
fs.writeFileSync(dest, Buffer.from(entry.content, 'base64'));
|
|
87
|
+
restored++;
|
|
88
|
+
} catch (_) { }
|
|
89
|
+
}
|
|
90
|
+
return { count: restored, header };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function listBackups(backupDir) {
|
|
94
|
+
if (!fs.existsSync(backupDir)) return [];
|
|
95
|
+
return fs.readdirSync(backupDir)
|
|
96
|
+
.filter(f => f.endsWith('.grimbak'))
|
|
97
|
+
.map(f => {
|
|
98
|
+
const stat = fs.statSync(path.join(backupDir, f));
|
|
99
|
+
return { name: f, size: stat.size, mtime: stat.mtime };
|
|
100
|
+
})
|
|
101
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ── run ───────────────────────────────────────────────────────────────────────
|
|
105
|
+
function run(args) {
|
|
106
|
+
const found = findGrimoireDir();
|
|
107
|
+
if (!found) {
|
|
108
|
+
console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const { dir: grimoireDir } = found;
|
|
112
|
+
const backupDir = getBackupDir(grimoireDir);
|
|
113
|
+
|
|
114
|
+
if (args.includes('--list')) {
|
|
115
|
+
const backups = listBackups(backupDir);
|
|
116
|
+
console.log(`\n💾 Backups (${backups.length}):\n`);
|
|
117
|
+
if (backups.length === 0) {
|
|
118
|
+
console.log(' (nenhum backup encontrado)');
|
|
119
|
+
console.log(' grimoire backup ← criar agora\n');
|
|
120
|
+
} else {
|
|
121
|
+
backups.forEach(b => {
|
|
122
|
+
const kb = (b.size / 1024).toFixed(1);
|
|
123
|
+
const d = b.mtime.toLocaleDateString('pt-BR');
|
|
124
|
+
console.log(` 📦 ${b.name} (${kb} KB · ${d})`);
|
|
125
|
+
});
|
|
126
|
+
console.log(`\n grimoire backup --restore <arquivo> ← restaurar\n`);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const restoreIdx = args.indexOf('--restore');
|
|
132
|
+
if (restoreIdx !== -1) {
|
|
133
|
+
const file = args[restoreIdx + 1];
|
|
134
|
+
if (!file) {
|
|
135
|
+
console.log('Usage: grimoire backup --restore <arquivo.grimbak>\n');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const absFile = path.isAbsolute(file) ? file : path.join(backupDir, file);
|
|
139
|
+
console.log(`\n⏳ Restaurando backup: ${path.basename(absFile)}...\n`);
|
|
140
|
+
try {
|
|
141
|
+
const { count, header } = restoreBackup(absFile, grimoireDir);
|
|
142
|
+
console.log(`✅ ${count} arquivos restaurados`);
|
|
143
|
+
console.log(` Backup criado em: ${new Date(header.createdAt).toLocaleString('pt-BR')}\n`);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.error(`❌ Erro ao restaurar: ${e.message}\n`);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Default: create backup
|
|
151
|
+
console.log('\n⏳ Criando backup de .grimoire/ ...\n');
|
|
152
|
+
try {
|
|
153
|
+
const { file, count, size } = createBackup(grimoireDir, backupDir);
|
|
154
|
+
const kb = (size / 1024).toFixed(1);
|
|
155
|
+
const rel = path.relative(process.cwd(), file);
|
|
156
|
+
console.log(`✅ Backup criado: ${rel}`);
|
|
157
|
+
console.log(` ${count} arquivos · ${kb} KB\n`);
|
|
158
|
+
console.log(`💡 Para restaurar: grimoire backup --restore ${path.basename(file)}`);
|
|
159
|
+
console.log(` Para listar: grimoire backup --list\n`);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.error(`❌ Erro ao criar backup: ${e.message}\n`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = { run };
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire export — Full Export
|
|
3
|
+
*
|
|
4
|
+
* grimoire export --all Markdown bundle de tudo
|
|
5
|
+
* grimoire export --all --json JSON bundle de tudo
|
|
6
|
+
* grimoire export --all --out <dir> Diretório de saída customizado
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
|
|
14
|
+
function findGrimoireDir() {
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const direct = path.join(cwd, '.grimoire');
|
|
17
|
+
const sub = path.join(cwd, 'grimoire', '.grimoire');
|
|
18
|
+
if (fs.existsSync(direct)) return direct;
|
|
19
|
+
if (fs.existsSync(sub)) return sub;
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ── Loaders ───────────────────────────────────────────────────────────────────
|
|
24
|
+
function loadAllStories(grimoireDir) {
|
|
25
|
+
const dir = path.join(grimoireDir, 'stories');
|
|
26
|
+
if (!fs.existsSync(dir)) return [];
|
|
27
|
+
return fs.readdirSync(dir)
|
|
28
|
+
.filter(f => f.endsWith('.json'))
|
|
29
|
+
.map(f => { try { return JSON.parse(fs.readFileSync(path.join(dir, f), 'utf8')); } catch (_) { return null; } })
|
|
30
|
+
.filter(Boolean)
|
|
31
|
+
.sort((a, b) => a.createdAt > b.createdAt ? 1 : -1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function loadAllMemory(grimoireDir) {
|
|
35
|
+
const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
|
|
36
|
+
if (!fs.existsSync(sessionsDir)) return [];
|
|
37
|
+
const entries = [];
|
|
38
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl')).sort();
|
|
39
|
+
for (const f of files) {
|
|
40
|
+
const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
|
|
41
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
42
|
+
try { entries.push({ date: f.slice(0, 10), ...JSON.parse(line) }); } catch (_) { }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function loadMetrics(grimoireDir) {
|
|
49
|
+
const dir = path.join(grimoireDir, 'metrics');
|
|
50
|
+
if (!fs.existsSync(dir)) return { sessions: 0, commits: 0, storiesDone: 0 };
|
|
51
|
+
let sessions = 0, commits = 0, storiesDone = 0;
|
|
52
|
+
for (const f of fs.readdirSync(dir).filter(f => f.endsWith('.jsonl'))) {
|
|
53
|
+
const raw = fs.readFileSync(path.join(dir, f), 'utf8');
|
|
54
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
55
|
+
try {
|
|
56
|
+
const e = JSON.parse(line);
|
|
57
|
+
if (e.type === 'session_start' || e.type === 'agent_session') sessions++;
|
|
58
|
+
if (e.type === 'commit') commits++;
|
|
59
|
+
if (e.type === 'story_complete') storiesDone++;
|
|
60
|
+
} catch (_) { }
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { sessions, commits, storiesDone };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Markdown export ───────────────────────────────────────────────────────────
|
|
67
|
+
function buildMarkdown(stories, memory, metrics, projectName) {
|
|
68
|
+
const now = new Date().toISOString().split('T')[0];
|
|
69
|
+
const lines = [];
|
|
70
|
+
|
|
71
|
+
lines.push(`# Grimoire Export — ${projectName}`);
|
|
72
|
+
lines.push(`\n> Exportado em: ${now}\n`);
|
|
73
|
+
|
|
74
|
+
// Stats
|
|
75
|
+
lines.push(`## 📈 Resumo\n`);
|
|
76
|
+
lines.push(`| Métrica | Valor |`);
|
|
77
|
+
lines.push(`|---|---|`);
|
|
78
|
+
lines.push(`| Stories totais | ${stories.length} |`);
|
|
79
|
+
lines.push(`| Sessões | ${metrics.sessions} |`);
|
|
80
|
+
lines.push(`| Commits | ${metrics.commits} |`);
|
|
81
|
+
lines.push(`| Stories concluídas | ${metrics.storiesDone} |`);
|
|
82
|
+
lines.push(`| Entradas de memória | ${memory.length} |`);
|
|
83
|
+
lines.push('');
|
|
84
|
+
|
|
85
|
+
// Stories
|
|
86
|
+
lines.push(`## 📋 Stories\n`);
|
|
87
|
+
const openStories = stories.filter(s => s.status !== 'done');
|
|
88
|
+
const doneStories = stories.filter(s => s.status === 'done');
|
|
89
|
+
|
|
90
|
+
if (openStories.length) {
|
|
91
|
+
lines.push(`### Em andamento\n`);
|
|
92
|
+
openStories.forEach(s => {
|
|
93
|
+
lines.push(`#### 🔄 [${s.id}] ${s.title}`);
|
|
94
|
+
lines.push(`- **Criada:** ${s.createdAt.slice(0, 10)}`);
|
|
95
|
+
if (s.notes && s.notes.length) {
|
|
96
|
+
lines.push(`- **Notas:**`);
|
|
97
|
+
s.notes.forEach(n => lines.push(` - ${n}`));
|
|
98
|
+
}
|
|
99
|
+
lines.push('');
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (doneStories.length) {
|
|
103
|
+
lines.push(`### Concluídas\n`);
|
|
104
|
+
doneStories.forEach(s => {
|
|
105
|
+
lines.push(`- ✅ **[${s.id}]** ${s.title}${s.doneAt ? ` — ${s.doneAt.slice(0, 10)}` : ''}`);
|
|
106
|
+
});
|
|
107
|
+
lines.push('');
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Memory by tag
|
|
111
|
+
lines.push(`## 🧠 Memória\n`);
|
|
112
|
+
const byTag = {};
|
|
113
|
+
const noTag = [];
|
|
114
|
+
for (const e of memory) {
|
|
115
|
+
if (e.tag) { byTag[e.tag] = byTag[e.tag] || []; byTag[e.tag].push(e); }
|
|
116
|
+
else noTag.push(e);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const [tag, entries] of Object.entries(byTag)) {
|
|
120
|
+
lines.push(`### #${tag}\n`);
|
|
121
|
+
entries.forEach(e => {
|
|
122
|
+
const story = e.story ? ` \`[${e.story}]\`` : '';
|
|
123
|
+
lines.push(`- **${e.date}** ${e.content}${story}`);
|
|
124
|
+
});
|
|
125
|
+
lines.push('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (noTag.length) {
|
|
129
|
+
lines.push(`### Sem tag\n`);
|
|
130
|
+
noTag.slice(0, 20).forEach(e => lines.push(`- **${e.date}** ${e.content}`));
|
|
131
|
+
if (noTag.length > 20) lines.push(`- *(e mais ${noTag.length - 20} entradas...)*`);
|
|
132
|
+
lines.push('');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return lines.join('\n');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── JSON export ───────────────────────────────────────────────────────────────
|
|
139
|
+
function buildJSON(stories, memory, metrics, projectName) {
|
|
140
|
+
return JSON.stringify({
|
|
141
|
+
exportedAt: new Date().toISOString(),
|
|
142
|
+
project: projectName,
|
|
143
|
+
metrics,
|
|
144
|
+
stories,
|
|
145
|
+
memory,
|
|
146
|
+
}, null, 2);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── run ───────────────────────────────────────────────────────────────────────
|
|
150
|
+
function run(args) {
|
|
151
|
+
if (!args.includes('--all')) {
|
|
152
|
+
console.log('\nUsage:\n');
|
|
153
|
+
console.log(' grimoire export --all Markdown bundle de tudo');
|
|
154
|
+
console.log(' grimoire export --all --json JSON bundle de tudo\n');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const grimoireDir = findGrimoireDir();
|
|
159
|
+
if (!grimoireDir) {
|
|
160
|
+
console.error('❌ .grimoire/ not found. Run: npx grimoire-framework install');
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Project name
|
|
165
|
+
let projectName = path.basename(process.cwd());
|
|
166
|
+
try {
|
|
167
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
|
|
168
|
+
projectName = pkg.name || projectName;
|
|
169
|
+
} catch (_) { }
|
|
170
|
+
|
|
171
|
+
// Output dir
|
|
172
|
+
const outIdx = args.indexOf('--out');
|
|
173
|
+
const outDir = outIdx !== -1 && args[outIdx + 1]
|
|
174
|
+
? args[outIdx + 1]
|
|
175
|
+
: path.join(grimoireDir, 'exports');
|
|
176
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
177
|
+
|
|
178
|
+
const now = new Date().toISOString().split('T')[0];
|
|
179
|
+
const isJson = args.includes('--json');
|
|
180
|
+
const ext = isJson ? 'json' : 'md';
|
|
181
|
+
const outFile = path.join(outDir, `${now}-export.${ext}`);
|
|
182
|
+
|
|
183
|
+
console.log('\n⏳ Exportando dados do .grimoire/ ...\n');
|
|
184
|
+
|
|
185
|
+
const stories = loadAllStories(grimoireDir);
|
|
186
|
+
const memory = loadAllMemory(grimoireDir);
|
|
187
|
+
const metrics = loadMetrics(grimoireDir);
|
|
188
|
+
|
|
189
|
+
const content = isJson
|
|
190
|
+
? buildJSON(stories, memory, metrics, projectName)
|
|
191
|
+
: buildMarkdown(stories, memory, metrics, projectName);
|
|
192
|
+
|
|
193
|
+
fs.writeFileSync(outFile, content, 'utf8');
|
|
194
|
+
|
|
195
|
+
const rel = path.relative(process.cwd(), outFile);
|
|
196
|
+
console.log(`✅ Export gerado: ${rel}`);
|
|
197
|
+
console.log(` Stories: ${stories.length} · Memória: ${memory.length} · Formato: ${isJson ? 'JSON' : 'Markdown'}\n`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = { run };
|
package/bin/commands/memory.js
CHANGED
|
@@ -85,6 +85,19 @@ async function saveMemory(args, memoryDir) {
|
|
|
85
85
|
else { filteredArgs = filteredArgs.filter(a => a !== args[tagIdx] && a !== tag); }
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
// Extract --story value if present
|
|
89
|
+
const storyIdx = filteredArgs.findIndex(a => a === '--story' || a.startsWith('--story='));
|
|
90
|
+
let storyId = null;
|
|
91
|
+
if (storyIdx !== -1) {
|
|
92
|
+
if (filteredArgs[storyIdx].includes('=')) {
|
|
93
|
+
storyId = filteredArgs[storyIdx].split('=')[1];
|
|
94
|
+
filteredArgs.splice(storyIdx, 1);
|
|
95
|
+
} else {
|
|
96
|
+
storyId = filteredArgs[storyIdx + 1];
|
|
97
|
+
filteredArgs.splice(storyIdx, 2);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
88
101
|
const content = filteredArgs.join(' ');
|
|
89
102
|
if (!content) {
|
|
90
103
|
console.error('❌ Please provide content to save.');
|
|
@@ -98,6 +111,7 @@ async function saveMemory(args, memoryDir) {
|
|
|
98
111
|
timestamp: new Date().toISOString(),
|
|
99
112
|
content: content,
|
|
100
113
|
...(tag ? { tag } : {}),
|
|
114
|
+
...(storyId ? { story: storyId } : {}),
|
|
101
115
|
};
|
|
102
116
|
|
|
103
117
|
// Ensure file exists for lockfile
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* grimoire search — Global Search
|
|
3
|
+
*
|
|
4
|
+
* grimoire search "termo" Busca em memory + stories + agents
|
|
5
|
+
* grimoire search "JWT" --memory Só memória
|
|
6
|
+
* grimoire search "JWT" --stories Só stories
|
|
7
|
+
* grimoire search "JWT" --agents Só agentes
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
|
|
15
|
+
function findGrimoireDir() {
|
|
16
|
+
const cwd = process.cwd();
|
|
17
|
+
const direct = path.join(cwd, '.grimoire');
|
|
18
|
+
const sub = path.join(cwd, 'grimoire', '.grimoire');
|
|
19
|
+
if (fs.existsSync(direct)) return direct;
|
|
20
|
+
if (fs.existsSync(sub)) return sub;
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function findAgentsDir() {
|
|
25
|
+
const cwd = process.cwd();
|
|
26
|
+
const dirs = [
|
|
27
|
+
path.join(cwd, '.codex', 'agents'),
|
|
28
|
+
path.join(cwd, 'node_modules', 'grimoire-framework', '.codex', 'agents'),
|
|
29
|
+
];
|
|
30
|
+
return dirs.find(d => fs.existsSync(d)) || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function hl(text, query) {
|
|
34
|
+
if (!query) return text;
|
|
35
|
+
const re = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
|
|
36
|
+
return text.replace(re, m => `\x1b[33m${m}\x1b[0m`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── Search memory sessions ────────────────────────────────────────────────────
|
|
40
|
+
function searchMemory(grimoireDir, query) {
|
|
41
|
+
const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
|
|
42
|
+
if (!fs.existsSync(sessionsDir)) return [];
|
|
43
|
+
const lq = query.toLowerCase();
|
|
44
|
+
const results = [];
|
|
45
|
+
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl')).sort().reverse();
|
|
46
|
+
for (const f of files) {
|
|
47
|
+
const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
|
|
48
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
49
|
+
try {
|
|
50
|
+
const e = JSON.parse(line);
|
|
51
|
+
if ((e.content || '').toLowerCase().includes(lq)) {
|
|
52
|
+
results.push({ date: f.slice(0, 10), content: e.content, tag: e.tag || null, story: e.story || null });
|
|
53
|
+
}
|
|
54
|
+
} catch (_) { }
|
|
55
|
+
}
|
|
56
|
+
if (results.length >= 20) break;
|
|
57
|
+
}
|
|
58
|
+
return results;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Search stories ────────────────────────────────────────────────────────────
|
|
62
|
+
function searchStories(grimoireDir, query) {
|
|
63
|
+
const storiesDir = path.join(grimoireDir, 'stories');
|
|
64
|
+
if (!fs.existsSync(storiesDir)) return [];
|
|
65
|
+
const lq = query.toLowerCase();
|
|
66
|
+
return fs.readdirSync(storiesDir)
|
|
67
|
+
.filter(f => f.endsWith('.json'))
|
|
68
|
+
.map(f => { try { return JSON.parse(fs.readFileSync(path.join(storiesDir, f), 'utf8')); } catch (_) { return null; } })
|
|
69
|
+
.filter(Boolean)
|
|
70
|
+
.filter(s => (s.title || '').toLowerCase().includes(lq) ||
|
|
71
|
+
(s.notes || []).some(n => n.toLowerCase().includes(lq)));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Search agents ─────────────────────────────────────────────────────────────
|
|
75
|
+
function searchAgents(agentsDir, query) {
|
|
76
|
+
if (!agentsDir) return [];
|
|
77
|
+
const lq = query.toLowerCase();
|
|
78
|
+
const results = [];
|
|
79
|
+
for (const f of fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'))) {
|
|
80
|
+
const raw = fs.readFileSync(path.join(agentsDir, f), 'utf8').toLowerCase();
|
|
81
|
+
if (raw.includes(lq)) {
|
|
82
|
+
const id = f.replace('.md', '');
|
|
83
|
+
// Extract name from yaml block
|
|
84
|
+
let name = id;
|
|
85
|
+
const nameMatch = raw.match(/name:\s*([^\n]+)/);
|
|
86
|
+
if (nameMatch) name = nameMatch[1].trim();
|
|
87
|
+
const titleMatch = raw.match(/title:\s*([^\n]+)/);
|
|
88
|
+
const title = titleMatch ? titleMatch[1].trim() : '';
|
|
89
|
+
results.push({ id, name, title });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return results;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── run ───────────────────────────────────────────────────────────────────────
|
|
96
|
+
function run(args) {
|
|
97
|
+
const query = args.filter(a => !a.startsWith('-')).join(' ');
|
|
98
|
+
if (!query) {
|
|
99
|
+
console.log('Usage: grimoire search "termo"\n');
|
|
100
|
+
console.log('Busca em: memória · stories · agentes\n');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const onlyMemory = args.includes('--memory');
|
|
105
|
+
const onlyStories = args.includes('--stories');
|
|
106
|
+
const onlyAgents = args.includes('--agents');
|
|
107
|
+
const all = !onlyMemory && !onlyStories && !onlyAgents;
|
|
108
|
+
|
|
109
|
+
const grimoireDir = findGrimoireDir();
|
|
110
|
+
const agentsDir = findAgentsDir();
|
|
111
|
+
|
|
112
|
+
let totalResults = 0;
|
|
113
|
+
|
|
114
|
+
console.log(`\n🔍 Grimoire Search — "${query}"\n${'─'.repeat(44)}`);
|
|
115
|
+
|
|
116
|
+
// Memory
|
|
117
|
+
if (all || onlyMemory) {
|
|
118
|
+
if (grimoireDir) {
|
|
119
|
+
const memResults = searchMemory(grimoireDir, query);
|
|
120
|
+
if (memResults.length > 0) {
|
|
121
|
+
console.log('\n🧠 Memória:');
|
|
122
|
+
for (const r of memResults) {
|
|
123
|
+
const tag = r.tag ? ` [#${r.tag}]` : '';
|
|
124
|
+
const story = r.story ? ` [${r.story}]` : '';
|
|
125
|
+
console.log(` ${r.date} ${hl(r.content, query)}${tag}${story}`);
|
|
126
|
+
}
|
|
127
|
+
totalResults += memResults.length;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Stories
|
|
133
|
+
if (all || onlyStories) {
|
|
134
|
+
if (grimoireDir) {
|
|
135
|
+
const storyResults = searchStories(grimoireDir, query);
|
|
136
|
+
if (storyResults.length > 0) {
|
|
137
|
+
console.log('\n📋 Stories:');
|
|
138
|
+
for (const s of storyResults) {
|
|
139
|
+
const icon = s.status === 'done' ? '✅' : '🔄';
|
|
140
|
+
console.log(` ${icon} [${s.id}] ${hl(s.title, query)}`);
|
|
141
|
+
for (const n of (s.notes || []).filter(n => n.toLowerCase().includes(query.toLowerCase()))) {
|
|
142
|
+
console.log(` 📝 ${hl(n, query)}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
totalResults += storyResults.length;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Agents
|
|
151
|
+
if (all || onlyAgents) {
|
|
152
|
+
const agentResults = searchAgents(agentsDir, query);
|
|
153
|
+
if (agentResults.length > 0) {
|
|
154
|
+
console.log('\n🤖 Agentes:');
|
|
155
|
+
for (const a of agentResults) {
|
|
156
|
+
console.log(` @${hl(a.id, query)} — ${a.name}${a.title ? ' ' + a.title : ''}`);
|
|
157
|
+
}
|
|
158
|
+
totalResults += agentResults.length;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (totalResults === 0) {
|
|
163
|
+
console.log('\n (nenhum resultado encontrado)');
|
|
164
|
+
console.log(` Dica: tente termos mais curtos ou use --memory / --stories / --agents\n`);
|
|
165
|
+
} else {
|
|
166
|
+
console.log(`\n${'─'.repeat(44)}`);
|
|
167
|
+
console.log(` ${totalResults} resultado(s)\n`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
module.exports = { run };
|
package/bin/commands/story.js
CHANGED
|
@@ -61,9 +61,10 @@ function run(args) {
|
|
|
61
61
|
switch (sub) {
|
|
62
62
|
case 'create': storyCreate(rest, storiesDir, grimoireDir); break;
|
|
63
63
|
case 'done': storyDone(rest[0], storiesDir, grimoireDir); break;
|
|
64
|
+
case 'note': storyNote(rest[0], rest.slice(1), storiesDir); break;
|
|
64
65
|
case 'delete':
|
|
65
66
|
case 'remove': storyDelete(rest[0], storiesDir); break;
|
|
66
|
-
case 'show': storyShow(rest[0], storiesDir); break;
|
|
67
|
+
case 'show': storyShow(rest[0], storiesDir, grimoireDir); break;
|
|
67
68
|
case 'list':
|
|
68
69
|
default: storyList(rest, storiesDir); break;
|
|
69
70
|
}
|
|
@@ -161,8 +162,22 @@ function storyDone(id, storiesDir, grimoireDir) {
|
|
|
161
162
|
console.log(`\n✅ [${story.id}] ${story.title} — marcada como concluída! 🎉\n`);
|
|
162
163
|
}
|
|
163
164
|
|
|
164
|
-
// ──
|
|
165
|
-
function
|
|
165
|
+
// ── note ─────────────────────────────────────────────────────────────────────────
|
|
166
|
+
function storyNote(id, args, storiesDir) {
|
|
167
|
+
if (!id) { console.log('Usage: grimoire story note <id> "nota"\n'); return; }
|
|
168
|
+
const note = args.filter(a => !a.startsWith('-')).join(' ');
|
|
169
|
+
if (!note) { console.log('Usage: grimoire story note <id> "nota"\n'); return; }
|
|
170
|
+
const storyFile = path.join(storiesDir, id.endsWith('.json') ? id : `${id}.json`);
|
|
171
|
+
if (!fs.existsSync(storyFile)) { console.log(`❌ Story "${id}" not found.\n`); return; }
|
|
172
|
+
const story = JSON.parse(fs.readFileSync(storyFile, 'utf8'));
|
|
173
|
+
story.notes = story.notes || [];
|
|
174
|
+
story.notes.push(note);
|
|
175
|
+
fs.writeFileSync(storyFile, JSON.stringify(story, null, 2), 'utf8');
|
|
176
|
+
console.log(`\n📝 Nota adicionada a [${id}]:\n ${note}\n`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── show ─────────────────────────────────────────────────────────────────────────
|
|
180
|
+
function storyShow(id, storiesDir, grimoireDir) {
|
|
166
181
|
if (!id) { storyList([], storiesDir); return; }
|
|
167
182
|
const storyFile = path.join(storiesDir, id.endsWith('.json') ? id : `${id}.json`);
|
|
168
183
|
if (!fs.existsSync(storyFile)) {
|
|
@@ -171,15 +186,39 @@ function storyShow(id, storiesDir) {
|
|
|
171
186
|
const s = JSON.parse(fs.readFileSync(storyFile, 'utf8'));
|
|
172
187
|
const statusIcon = s.status === 'done' ? '✅' : '🔄';
|
|
173
188
|
console.log(`\n${statusIcon} [${s.id}] ${s.title}`);
|
|
174
|
-
console.log(`${'
|
|
189
|
+
console.log(`${'\u2500'.repeat(50)}`);
|
|
175
190
|
console.log(` Status: ${s.status}`);
|
|
176
191
|
console.log(` Criada: ${s.createdAt.slice(0, 10)}`);
|
|
177
192
|
if (s.doneAt) console.log(` Concluída: ${s.doneAt.slice(0, 10)}`);
|
|
178
193
|
if (s.notes && s.notes.length) {
|
|
179
|
-
console.log('\n Notas:');
|
|
194
|
+
console.log('\n 📝 Notas:');
|
|
180
195
|
s.notes.forEach(n => console.log(` - ${n}`));
|
|
181
196
|
}
|
|
182
|
-
|
|
197
|
+
// Linked memory entries
|
|
198
|
+
if (grimoireDir) {
|
|
199
|
+
const sessionsDir = path.join(grimoireDir, 'memory', 'sessions');
|
|
200
|
+
if (fs.existsSync(sessionsDir)) {
|
|
201
|
+
const linked = [];
|
|
202
|
+
for (const f of fs.readdirSync(sessionsDir).filter(f => f.endsWith('.jsonl'))) {
|
|
203
|
+
const raw = fs.readFileSync(path.join(sessionsDir, f), 'utf8');
|
|
204
|
+
for (const line of raw.split('\n').filter(l => l.trim())) {
|
|
205
|
+
try {
|
|
206
|
+
const e = JSON.parse(line);
|
|
207
|
+
if (e.story === s.id) linked.push({ date: f.slice(0, 10), ...e });
|
|
208
|
+
} catch (_) { }
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (linked.length > 0) {
|
|
212
|
+
console.log('\n 🧠 Memórias linkadas:');
|
|
213
|
+
linked.forEach(e => {
|
|
214
|
+
const tag = e.tag ? ` [#${e.tag}]` : '';
|
|
215
|
+
console.log(` ${e.date} ${e.content}${tag}`);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
console.log(`\n grimoire story note ${s.id} "obs" ← adicionar nota`);
|
|
221
|
+
console.log(` grimoire memory save --story ${s.id} "texto" ← linkar memória\n`);
|
|
183
222
|
}
|
|
184
223
|
|
|
185
224
|
// ── delete ────────────────────────────────────────────────────────────────────
|
package/bin/grimoire-cli.js
CHANGED
|
@@ -142,6 +142,15 @@ async function main() {
|
|
|
142
142
|
case 'session':
|
|
143
143
|
require('./commands/session').run(args.slice(1));
|
|
144
144
|
break;
|
|
145
|
+
case 'search':
|
|
146
|
+
require('./commands/search').run(args.slice(1));
|
|
147
|
+
break;
|
|
148
|
+
case 'backup':
|
|
149
|
+
require('./commands/backup').run(args.slice(1));
|
|
150
|
+
break;
|
|
151
|
+
case 'export':
|
|
152
|
+
require('./commands/exportall').run(args.slice(1));
|
|
153
|
+
break;
|
|
145
154
|
case 'whoami':
|
|
146
155
|
handleWhoami();
|
|
147
156
|
break;
|
|
@@ -607,14 +616,16 @@ COMANDOS ESSENCIAIS:
|
|
|
607
616
|
grimoire config set <k> <v> Define uma configuração
|
|
608
617
|
|
|
609
618
|
SESSÃO & WORKFLOW:
|
|
610
|
-
grimoire session start
|
|
611
|
-
grimoire session start --squad <s>
|
|
612
|
-
grimoire story create "Título"
|
|
613
|
-
grimoire story
|
|
614
|
-
grimoire story
|
|
615
|
-
grimoire
|
|
616
|
-
grimoire
|
|
617
|
-
grimoire report
|
|
619
|
+
grimoire session start Prompt de início de sessão para IDE
|
|
620
|
+
grimoire session start --squad <s> Com squad específico
|
|
621
|
+
grimoire story create "Título" Criar story
|
|
622
|
+
grimoire story note US-001 "obs" 🆕 Adicionar nota a uma story
|
|
623
|
+
grimoire story list Ver stories ativas
|
|
624
|
+
grimoire story done US-001 Marcar concluída
|
|
625
|
+
grimoire story show US-001 🆕 Detalhes + memórias linkadas
|
|
626
|
+
grimoire report Relatório de hoje
|
|
627
|
+
grimoire report --period week Relatório da semana
|
|
628
|
+
grimoire report --md Exportar relatório
|
|
618
629
|
|
|
619
630
|
AGENTES:
|
|
620
631
|
grimoire agents list Lista todos os agentes com personas
|
|
@@ -629,13 +640,22 @@ SQUADS:
|
|
|
629
640
|
grimoire squads info <squad> 🆕 Detalhes do squad
|
|
630
641
|
|
|
631
642
|
MEMÓRIA:
|
|
632
|
-
grimoire memory save "texto"
|
|
633
|
-
grimoire memory save --tag <t> "x"
|
|
634
|
-
grimoire memory
|
|
635
|
-
grimoire memory
|
|
636
|
-
grimoire memory
|
|
637
|
-
grimoire memory
|
|
638
|
-
grimoire memory
|
|
643
|
+
grimoire memory save "texto" Salvar entrada
|
|
644
|
+
grimoire memory save --tag <t> "x" Salvar com tag
|
|
645
|
+
grimoire memory save --story US-001 "x" 🆕 Linkar memória a uma story
|
|
646
|
+
grimoire memory show [--last 10] Ver entradas de hoje
|
|
647
|
+
grimoire memory search "termo" Buscar em todas as sessões
|
|
648
|
+
grimoire memory list-tags Listar todas as tags
|
|
649
|
+
grimoire memory export Exportar para Markdown
|
|
650
|
+
grimoire memory clear --older-than 30 Limpar sessões antigas
|
|
651
|
+
|
|
652
|
+
BUSCA & BACKUP:
|
|
653
|
+
grimoire search "JWT" 🆕 Busca global (memória+stories+agentes)
|
|
654
|
+
grimoire backup 🆕 Cria backup de .grimoire/
|
|
655
|
+
grimoire backup --restore <f> 🆕 Restaura backup
|
|
656
|
+
grimoire backup --list 🆕 Lista backups
|
|
657
|
+
grimoire export --all 🆕 Bundle Markdown completo
|
|
658
|
+
grimoire export --all --json 🆕 Bundle JSON completo
|
|
639
659
|
|
|
640
660
|
MÉTRICAS:
|
|
641
661
|
grimoire metrics Dashboard do mês atual
|